26 KiB
校园活动组织与报名系统 - 答辩文档
一、整体框架结构
1.1 技术栈
- 后端框架: Spring Boot 3.1.8
- 数据库: MySQL 8.0
- ORM框架: MyBatis-Plus 3.5.5
- 安全框架: Spring Security + JWT
- API文档: Knife4j (Swagger)
- 工具库: Hutool、Lombok
- 其他: ZXing(二维码生成)、iText(PDF生成)、EasyExcel(Excel导出)
1.2 项目结构
server/
├── src/main/java/com/campus/activity/
│ ├── CampusActivityApplication.java # 启动类
│ ├── common/ # 通用类
│ │ ├── Result.java # 统一响应结果
│ │ ├── PageResult.java # 分页结果
│ │ └── ResultCode.java # 响应状态码
│ ├── config/ # 配置类
│ │ ├── SecurityConfig.java # Spring Security配置
│ │ ├── CorsConfig.java # 跨域配置
│ │ ├── Knife4jConfig.java # API文档配置
│ │ ├── MybatisPlusConfig.java # MyBatis-Plus配置
│ │ └── MyMetaObjectHandler.java # 自动填充处理器
│ ├── controller/ # 控制器层
│ │ ├── AuthController.java # 认证模块
│ │ ├── ActivityController.java # 活动模块
│ │ ├── RegistrationController.java # 报名模块
│ │ ├── CheckInController.java # 签到模块
│ │ ├── ReviewController.java # 评价模块(我负责)
│ │ └── StatisticsController.java # 统计模块(我负责)
│ ├── dto/ # 数据传输对象
│ │ ├── request/ # 请求DTO
│ │ │ ├── ReviewRequest.java # 评价请求
│ │ │ └── ...
│ │ └── response/ # 响应DTO
│ │ └── ...
│ ├── entity/ # 实体类
│ │ ├── User.java # 用户实体
│ │ ├── Activity.java # 活动实体
│ │ ├── Registration.java # 报名实体
│ │ ├── CheckIn.java # 签到实体
│ │ └── Review.java # 评价实体
│ ├── exception/ # 异常处理
│ │ ├── BusinessException.java # 业务异常
│ │ └── GlobalExceptionHandler.java # 全局异常处理器
│ ├── mapper/ # 数据访问层
│ │ ├── UserMapper.java
│ │ ├── ActivityMapper.java
│ │ ├── RegistrationMapper.java
│ │ ├── CheckInMapper.java
│ │ └── ReviewMapper.java # 评价Mapper(我负责)
│ ├── security/ # 安全相关
│ │ ├── JwtTokenProvider.java # JWT令牌提供者
│ │ ├── JwtAuthenticationFilter.java # JWT认证过滤器
│ │ └── UserDetailsServiceImpl.java # 用户详情服务
│ ├── service/ # 服务层
│ │ ├── AuthService.java
│ │ ├── ActivityService.java
│ │ ├── RegistrationService.java
│ │ ├── CheckInService.java
│ │ ├── ReviewService.java # 评价服务接口(我负责)
│ │ └── StatisticsService.java # 统计服务接口(我负责)
│ ├── service/impl/ # 服务实现层
│ │ ├── AuthServiceImpl.java
│ │ ├── ActivityServiceImpl.java
│ │ ├── RegistrationServiceImpl.java
│ │ ├── CheckInServiceImpl.java
│ │ ├── ReviewServiceImpl.java # 评价服务实现(我负责)
│ │ └── StatisticsServiceImpl.java # 统计服务实现(我负责)
│ ├── util/ # 工具类
│ │ ├── QrCodeUtil.java # 二维码工具
│ │ ├── PdfUtil.java # PDF工具
│ │ └── CsvUtil.java # CSV工具(我负责)
│ └── vo/ # 视图对象
│ ├── ReviewVO.java # 评价视图对象(我负责)
│ ├── ActivityStatisticsVO.java # 活动统计视图对象(我负责)
│ └── OverviewStatisticsVO.java # 总体统计视图对象(我负责)
├── src/main/resources/
│ ├── application.yml # 应用配置
│ └── mapper/ # MyBatis XML映射文件
│ ├── ReviewMapper.xml # 评价Mapper XML(我负责)
│ └── ...
└── pom.xml # Maven配置
1.3 分层架构
系统采用经典的分层架构:
- Controller层: 接收HTTP请求,参数校验,调用Service层,返回响应
- Service层: 业务逻辑处理,事务管理
- Mapper层: 数据库访问,使用MyBatis-Plus简化CRUD操作
- Entity层: 数据库实体映射
- DTO层: 数据传输对象,用于接口交互
- VO层: 视图对象,用于返回给前端的数据封装
二、我负责的模块
2.1 模块5.4:活动评分与评论模块
2.1.1 功能概述
活动结束后,学生可对参加过的活动进行评价。每个学生对同一活动只能评价一次,管理员可查看所有评价。
2.1.2 核心功能
-
提交评价
- 学生对已参加的活动进行评分(1-5分)
- 可选填写评论内容(最多500字符)
- 每个学生对同一活动只能评价一次
- 只有签到过的学生才能评价
-
获取活动评价列表
- 分页查询某个活动的所有评价
- 显示评价者信息、评分、评论内容、评价时间
-
获取我的评价列表
- 学生查看自己的所有评价记录
- 支持分页查询
2.1.3 实现细节
Controller层 (ReviewController.java):
@RestController
@RequestMapping("/api/v1/reviews")
public class ReviewController {
// 提交评价
@PostMapping
public Result<ReviewVO> createReview(@Valid @RequestBody ReviewRequest request)
// 获取活动评价列表
@GetMapping("/activity/{activityId}")
public Result<IPage<ReviewVO>> getActivityReviews(...)
// 获取我的评价列表
@GetMapping("/my")
public Result<IPage<ReviewVO>> getMyReviews(...)
}
Service层 (ReviewServiceImpl.java):
核心业务逻辑:
- 验证活动是否存在
- 检查用户是否已评价该活动(通过唯一约束)
- 验证用户是否已签到该活动(只有参加过的学生才能评价)
- 插入评价记录
- 返回评价详情(包含用户信息和活动信息)
数据验证:
@NotNull(message = "活动ID不能为空")
private Long activityId;
@NotNull(message = "评分不能为空")
@Min(value = 1, message = "评分最小为1")
@Max(value = 5, message = "评分最大为5")
private Integer rating;
@Size(max = 500, message = "评论内容不能超过500个字符")
private String content;
数据库设计:
CREATE TABLE `review` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`rating` TINYINT NOT NULL COMMENT '评分(1-5)',
`content` TEXT COMMENT '评论内容',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `uk_user_activity` (`user_id`, `activity_id`),
FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
FOREIGN KEY (`activity_id`) REFERENCES `activity` (`id`)
);
通过唯一索引 uk_user_activity 确保每个学生对同一活动只能评价一次。
MyBatis XML映射 (ReviewMapper.xml):
<!-- 查询活动评价列表(关联用户和活动信息) -->
<select id="selectActivityReviews" resultType="com.campus.activity.vo.ReviewVO">
SELECT
r.id,
r.user_id,
u.name AS user_name,
u.avatar AS user_avatar,
r.activity_id,
a.title AS activity_title,
r.rating,
r.content,
r.created_at
FROM review r
LEFT JOIN user u ON r.user_id = u.id
LEFT JOIN activity a ON r.activity_id = a.id
WHERE r.activity_id = #{activityId}
ORDER BY r.created_at DESC
</select>
2.1.4 技术亮点
- 数据一致性保证: 通过数据库唯一约束和业务双重验证,确保评价的唯一性
- 权限控制: 只有签到过的学生才能评价,防止虚假评价
- 分页查询: 使用MyBatis-Plus的分页插件,提高大数据量查询性能
- 关联查询优化: 在Mapper XML中使用LEFT JOIN一次性获取用户和活动信息,减少N+1查询问题
- 数据验证: 使用Jakarta Validation进行参数校验,提高代码健壮性
2.2 模块5.5:数据统计与导出
2.2.1 功能概述
系统具备基本统计能力,例如每个活动的报名人数、实际签到人数、平均评分。管理员可导出活动数据(CSV格式)。
2.2.2 核心功能
-
获取活动统计数据
- 报名人数(已报名+已签到)
- 签到人数
- 签到率(签到人数/报名人数)
- 评价人数
- 平均评分
- 评分分布(1-5星各有多少人)
-
导出活动数据
- 支持CSV格式导出
- 包含活动基本信息、统计数据
- 支持Excel直接打开(使用UTF-8 BOM头)
- 文件名格式:
activity_{activityId}_statistics.csv
-
获取总体统计
- 总活动数
- 总报名数
- 总签到数
- 总评价数
- 全平台平均评分
- 最近6个月的月度统计(活动和报名数量趋势)
2.2.3 实现细节
Controller层 (StatisticsController.java):
@RestController
@RequestMapping("/api/v1/statistics")
public class StatisticsController {
// 获取活动统计数据(管理员)
@GetMapping("/activity/{activityId}")
@PreAuthorize("hasRole('ADMIN')")
public Result<ActivityStatisticsVO> getActivityStatistics(@PathVariable Long activityId)
// 导出活动数据(管理员)
@GetMapping("/activity/{activityId}/export")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<byte[]> exportActivityData(...)
// 获取总体统计(管理员)
@GetMapping("/overview")
@PreAuthorize("hasRole('ADMIN')")
public Result<OverviewStatisticsVO> getOverviewStatistics()
}
Service层 (StatisticsServiceImpl.java):
核心业务逻辑:
-
活动统计 (
getActivityStatistics):- 统计报名人数(状态为1或2的报名记录)
- 统计签到人数
- 计算签到率(保留两位小数)
- 统计评价人数和平均评分(保留一位小数)
- 计算评分分布(1-5星各有多少人)
-
数据导出 (
exportActivityData):- 调用
CsvUtil生成CSV文件 - 设置响应头(Content-Disposition、Content-Type)
- 返回文件流
- 调用
-
总体统计 (
getOverviewStatistics):- 统计全平台的活动、报名、签到、评价总数
- 计算全平台平均评分
- 计算最近6个月的月度统计数据
CSV导出工具类 (CsvUtil.java):
核心功能:
- 生成UTF-8 BOM头(让Excel正确识别中文)
- 写入表头(活动ID、活动名称、开始时间、结束时间、活动地点、报名人数上限、报名人数、签到人数、签到率、评价人数、平均评分、1星、2星、3星、4星、5星)
- 写入数据行(转义特殊字符,处理逗号、引号、换行符)
- 支持Excel直接打开
// 写入BOM头,让Excel正确识别UTF-8编码
outputStream.write(0xEF);
outputStream.write(0xBB);
outputStream.write(0xBF);
// 写入表头
StringBuilder header = new StringBuilder();
header.append("活动ID,活动名称,开始时间,结束时间,活动地点,报名人数上限,报名人数,签到人数,签到率,评价人数,平均评分,1星,2星,3星,4星,5星");
outputStream.write(header.toString().getBytes(StandardCharsets.UTF_8));
// CSV转义处理
private String escapeCsv(String value) {
if (value == null) return "";
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
视图对象:
-
ActivityStatisticsVO: 活动统计数据视图对象
- 活动ID、活动标题
- 报名人数、签到人数、签到率
- 评价人数、平均评分
- 评分分布(Map<Integer, Long>)
-
OverviewStatisticsVO: 总体统计数据视图对象
- 总活动数、总报名数、总签到数、总评价数
- 平均评分
- 月度统计列表(MonthlyStats内部类)
2.2.4 技术亮点
- 权限控制: 使用
@PreAuthorize("hasRole('ADMIN')")注解,确保只有管理员可以访问统计和导出功能 - 性能优化:
- 使用流式处理(Stream)计算平均评分和评分分布
- 一次性查询所有数据,避免多次数据库访问
- 数据精度控制:
- 签到率保留两位小数
- 平均评分保留一位小数
- CSV导出优化:
- 添加UTF-8 BOM头,解决Excel中文乱码问题
- 实现CSV转义处理,正确处理包含逗号、引号、换行符的数据
- 使用ByteArrayOutputStream提高性能
- 月度统计算法:
- 动态计算最近6个月的起止时间
- 使用DateTimeFormatter格式化月份
- 支持跨年统计
三、答辩模拟
3.1 自我介绍
各位老师好,我是XXX,负责校园活动组织与报名系统的模块5.4(活动评分与评论模块)和模块5.5(数据统计与导出模块)的开发工作。
3.2 模块5.4:活动评分与评论模块
老师提问: 请介绍一下你负责的评分与评论模块的设计思路。
我的回答:
评分与评论模块的核心功能是让学生对已参加的活动进行评价。我在设计时考虑了以下几点:
-
数据一致性: 通过数据库唯一索引
uk_user_activity确保每个学生对同一活动只能评价一次,防止重复评价。 -
权限控制: 只有签到过的学生才能评价,防止虚假评价。在
ReviewServiceImpl.createReview方法中,我会先查询签到表,验证用户是否已签到该活动。 -
数据验证: 使用Jakarta Validation注解对评分和评论内容进行校验,评分必须在1-5分之间,评论内容不能超过500字符。
-
分页查询: 使用MyBatis-Plus的分页插件,支持活动评价列表和我的评价列表的分页查询,提高大数据量查询性能。
-
关联查询优化: 在
ReviewMapper.xml中使用LEFT JOIN一次性获取用户和活动信息,避免N+1查询问题。
老师提问: 如何防止学生评价自己没有参加的活动?
我的回答:
在 ReviewServiceImpl.createReview 方法中,我会先查询签到表:
CheckIn checkIn = checkInMapper.selectOne(
new LambdaQueryWrapper<CheckIn>()
.eq(CheckIn::getUserId, currentUser.getId())
.eq(CheckIn::getActivityId, request.getActivityId())
);
if (checkIn == null) {
throw new BusinessException(ResultCode.NOT_PARTICIPATED);
}
如果签到记录不存在,说明用户没有参加该活动,抛出业务异常,返回错误码 NOT_PARTICIPATED(您未参加该活动,无法评价)。
老师提问: 评分分布是如何计算的?
我的回答:
评分分布在统计模块中计算,使用Java Stream的 Collectors.groupingBy 方法:
Map<Integer, Long> ratingDistribution = reviews.stream()
.collect(Collectors.groupingBy(Review::getRating, Collectors.counting()));
for (int i = 1; i <= 5; i++) {
ratingDistribution.putIfAbsent(i, 0L);
}
首先按评分分组统计,然后确保1-5星都有数据(没有的设为0),最后返回给前端展示。
3.3 模块5.5:数据统计与导出
老师提问: 请介绍一下统计模块的功能和实现方式。
我的回答:
统计模块提供三个核心功能:
-
活动统计: 统计单个活动的报名人数、签到人数、签到率、评价人数、平均评分和评分分布。
-
数据导出: 支持将活动统计数据导出为CSV格式,方便管理员进行数据分析。
-
总体统计: 统计全平台的活动、报名、签到、评价总数,以及最近6个月的月度趋势。
实现上,我使用了MyBatis-Plus的LambdaQueryWrapper进行条件查询,使用Java Stream进行数据聚合计算,确保查询效率和代码可读性。
老师提问: CSV导出时如何解决Excel中文乱码问题?
我的回答:
在 CsvUtil.exportActivityData 方法中,我添加了UTF-8 BOM头:
// 写入BOM头,让Excel正确识别UTF-8编码
outputStream.write(0xEF);
outputStream.write(0xBB);
outputStream.write(0xBF);
BOM(Byte Order Mark)是UTF-8编码的特殊标记,Excel会自动识别BOM头,从而正确显示中文字符。
老师提问: 如果数据中包含逗号、引号或换行符,如何处理?
我的回答:
我实现了 escapeCsv 方法进行CSV转义处理:
private String escapeCsv(String value) {
if (value == null) return "";
// 如果包含逗号、引号或换行符,需要用引号包裹并转义
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
如果数据中包含逗号、引号或换行符,就用双引号包裹,并将原有的双引号替换为两个双引号(CSV标准转义方式)。
老师提问: 月度统计是如何计算的?
我的回答:
在 calculateMonthlyStats 方法中,我动态计算最近6个月的起止时间:
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
for (int i = 5; i >= 0; i--) {
LocalDateTime monthStart = now.minusMonths(i).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0);
LocalDateTime monthEnd = monthStart.plusMonths(1).minusSeconds(1);
String monthKey = monthStart.format(formatter);
// 统计该月的活动数和报名数
Long activityCount = activityMapper.selectCount(
new LambdaQueryWrapper<Activity>()
.eq(Activity::getDeleted, 0)
.between(Activity::getCreatedAt, monthStart, monthEnd)
);
Long registrationCount = registrationMapper.selectCount(
new LambdaQueryWrapper<Registration>()
.eq(Registration::getStatus, 1)
.between(Registration::getCreatedAt, monthStart, monthEnd)
);
stats.add(OverviewStatisticsVO.MonthlyStats.builder()
.month(monthKey)
.activityCount(activityCount)
.registrationCount(registrationCount)
.build());
}
使用 minusMonths 方法计算过去6个月,使用 withDayOfMonth(1) 获取每月第一天,使用 plusMonths(1).minusSeconds(1) 获取每月最后一秒,然后统计该月创建的活动和报名数量。
3.4 技术难点与解决方案
老师提问: 在开发过程中遇到了哪些技术难点?你是如何解决的?
我的回答:
-
CSV导出中文乱码问题: 通过添加UTF-8 BOM头解决,让Excel正确识别中文字符。
-
CSV转义问题: 实现了
escapeCsv方法,正确处理包含逗号、引号、换行符的数据,符合CSV标准。 -
评分分布计算: 使用Java Stream的
groupingBy方法进行分组统计,确保代码简洁高效。 -
月度统计跨年问题: 使用
DateTimeFormatter格式化月份,支持跨年统计(如2025-12到2026-01)。 -
数据精度控制: 使用
Math.round方法保留指定小数位数,确保签到率保留两位小数,平均评分保留一位小数。
3.5 总结
老师提问: 总结一下你在项目中的收获。
我的回答:
通过参与校园活动组织与报名系统的开发,我收获了很多:
-
技术能力提升: 熟练掌握了Spring Boot、MyBatis-Plus、Spring Security等框架的使用,深入理解了分层架构设计。
-
业务理解能力: 通过需求分析和数据库设计,提升了业务理解和抽象能力。
-
问题解决能力: 在开发过程中遇到了CSV导出、数据统计等技术难点,通过查阅文档和调试,成功解决了这些问题。
-
团队协作能力: 与团队成员密切配合,共同完成了系统的开发和测试工作。
-
文档编写能力: 编写了详细的API文档和答辩文档,提升了文档编写能力。
这次项目开发让我对软件工程有了更深入的理解,也为我今后的学习和工作打下了坚实的基础。
四、项目演示
4.1 API文档访问
系统使用Knife4j生成API文档,访问地址:http://localhost:8080/doc.html
4.2 模块5.4 API接口
-
提交评价
- 接口:
POST /api/v1/reviews - 权限: 需要认证
- 请求体:
{ "activityId": 1, "rating": 5, "content": "非常精彩的活动!" } - 接口:
-
获取活动评价列表
- 接口:
GET /api/v1/reviews/activity/{activityId} - 权限: 公开
- 参数: current, size
- 接口:
-
获取我的评价列表
- 接口:
GET /api/v1/reviews/my - 权限: 需要认证
- 参数: current, size
- 接口:
4.3 模块5.5 API接口
-
获取活动统计数据
- 接口:
GET /api/v1/statistics/activity/{activityId} - 权限: 管理员
- 接口:
-
导出活动数据
- 接口:
GET /api/v1/statistics/activity/{activityId}/export - 权限: 管理员
- 参数: format (csv)
- 接口:
-
获取总体统计
- 接口:
GET /api/v1/statistics/overview - 权限: 管理员
- 接口:
五、数据库表结构
5.1 评价表 (review)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGINT | PK, AUTO_INCREMENT | 评价ID |
| user_id | BIGINT | FK, NOT NULL | 用户ID |
| activity_id | BIGINT | FK, NOT NULL | 活动ID |
| rating | TINYINT | NOT NULL | 评分(1-5) |
| content | TEXT | 评论内容 | |
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 评价时间 |
| updated_at | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
索引:
uk_user_activity(user_id, activity_id) UNIQUE - 确保每个学生对同一活动只能评价一次idx_activity_id(activity_id)
5.2 关联表
- user: 用户表
- activity: 活动表
- registration: 报名表
- check_in: 签到表
六、核心代码位置
6.1 模块5.4(评价模块)
- Controller:
src/main/java/com/campus/activity/controller/ReviewController.java - Service接口:
src/main/java/com/campus/activity/service/ReviewService.java - Service实现:
src/main/java/com/campus/activity/service/impl/ReviewServiceImpl.java - Mapper接口:
src/main/java/com/campus/activity/mapper/ReviewMapper.java - Mapper XML:
src/main/resources/mapper/ReviewMapper.xml - Entity:
src/main/java/com/campus/activity/entity/Review.java - VO:
src/main/java/com/campus/activity/vo/ReviewVO.java - Request DTO:
src/main/java/com/campus/activity/dto/request/ReviewRequest.java
6.2 模块5.5(统计模块)
- Controller:
src/main/java/com/campus/activity/controller/StatisticsController.java - Service接口:
src/main/java/com/campus/activity/service/StatisticsService.java - Service实现:
src/main/java/com/campus/activity/service/impl/StatisticsServiceImpl.java - VO:
src/main/java/com/campus/activity/vo/ActivityStatisticsVO.java - VO:
src/main/java/com/campus/activity/vo/OverviewStatisticsVO.java - Util:
src/main/java/com/campus/activity/util/CsvUtil.java
七、测试建议
7.1 评价模块测试
- 正常评价: 学生对已参加的活动进行评价,验证评价成功
- 重复评价: 同一学生对同一活动再次评价,验证返回错误
- 未参加评价: 学生对未参加的活动进行评价,验证返回错误
- 评分边界测试: 测试评分为1和5的情况
- 评论长度测试: 测试评论内容超过500字符的情况
- 分页查询: 测试活动评价列表和我的评价列表的分页功能
7.2 统计模块测试
- 活动统计: 验证报名人数、签到人数、签到率、平均评分计算正确
- 评分分布: 验证1-5星分布统计正确
- 数据导出: 验证CSV文件生成正确,Excel可以打开
- 中文乱码: 验证CSV文件中文显示正常
- 总体统计: 验证全平台统计数据正确
- 月度统计: 验证最近6个月统计数据正确,支持跨年
八、总结
本次答辩文档详细介绍了校园活动组织与报名系统的整体框架结构,以及我负责的模块5.4(活动评分与评论模块)和模块5.5(数据统计与导出模块)的设计与实现。
在开发过程中,我深入理解了Spring Boot、MyBatis-Plus、Spring Security等框架的使用,掌握了分层架构设计、数据验证、权限控制、CSV导出等技术,提升了自己的技术能力和问题解决能力。
通过本次项目开发,我对软件工程有了更深入的理解,也为我今后的学习和工作打下了坚实的基础。
谢谢各位老师!