108 KiB
校园活动组织与报名系统 - 软件设计说明书
项目名称: 校园活动组织与报名系统
编写团队: 第14组
文档版本: V1.0
编写日期: 2026年1月15日
完成时间: 2025年1月14日
文档修订历史
| 版本 | 修订日期 | 修订人 | 修订说明 |
|---|---|---|---|
| V1.0 | 2026-01-15 | 第14组 | 初始版本,完成软件设计 |
目录
第一章 引言
1.1 编写目的
本文档旨在详细描述"校园活动组织与报名系统"的软件设计方案,为开发人员提供明确的技术实现指导,为测试人员提供测试依据,为项目管理人员提供技术决策参考。
本文档的主要目标读者包括:
- 开发人员:了解系统架构、模块设计、接口设计,指导代码实现
- 测试人员:了解系统设计细节,制定测试计划和测试用例
- 项目管理人员:了解技术架构和设计方案,进行技术决策和项目管理
- 维护人员:了解系统设计原理,便于后续维护和升级
1.2 文档范围
本文档涵盖以下设计内容:
- 系统架构设计:总体架构、技术架构、部署架构
- 数据库设计:概念结构、逻辑结构、物理结构、完整性约束
- 详细设计:各功能模块的类结构、算法设计、状态机设计
- 接口设计:所有RESTful API接口的详细设计
- 安全设计:认证机制、权限控制、数据安全、接口安全
- 性能设计:数据库优化、缓存策略、并发控制
- 异常处理设计:异常分类、处理机制、错误码、日志设计
- 测试设计:测试策略、测试用例
- 部署设计:部署架构、部署步骤、配置管理
本文档不包含以下内容:
- 功能需求分析(详见《软件需求分析说明书》)
- 用户操作手册(详见《软件使用手册》)
1.3 术语定义
| 术语/缩写 | 全称 | 说明 |
|---|---|---|
| JWT | JSON Web Token | 用于身份认证的令牌 |
| RESTful | Representational State Transfer | 一种Web服务架构风格 |
| BCrypt | - | 一种密码加密算法 |
| ER图 | Entity-Relationship Diagram | 实体关系图 |
| VO | View Object | 视图对象,用于前端展示 |
| DTO | Data Transfer Object | 数据传输对象 |
| API | Application Programming Interface | 应用程序编程接口 |
| Portable Document Format | 便携式文档格式 | |
| QR Code | Quick Response Code | 二维码 |
| RBAC | Role-Based Access Control | 基于角色的访问控制 |
| CORS | Cross-Origin Resource Sharing | 跨域资源共享 |
| CSRF | Cross-Site Request Forgery | 跨站请求伪造 |
| XSS | Cross-Site Scripting | 跨站脚本攻击 |
| MTBF | Mean Time Between Failures | 平均无故障时间 |
| MTTR | Mean Time To Repair | 平均修复时间 |
1.4 参考资料
[1] 张海藩,牟勇敏.《软件工程导论(第6版)》 2008
[2] Cay S. Horstmann《Java核心技术 卷Ⅰ(原书第11版)》2019
[3] 霍春阳《Vue.js设计与实现》2022
[4] Abraham Silberschatz.《数据库系统概念(原书第7版)》2021
[5] 许晓斌《Maven实战》2011
[6] 《校园活动组织与报名系统 - 软件需求分析说明书》
[7] 《校园活动组织与报名系统 - 数据库设计》
[8] 《校园活动组织与报名系统 - API接口设计》
[9] 《校园活动组织与报名系统 - 后端开发规范》
[10] 《校园活动组织与报名系统 - 业务流程图》
第二章 系统架构设计
2.1 总体架构
系统采用经典的分层架构设计,遵循高内聚、低耦合的设计原则,确保系统的可维护性和可扩展性。
graph TB
subgraph "表现层 Presentation Layer"
A[Web前端<br/>Vue.js + TypeScript]
end
subgraph "应用层 Application Layer"
B[Controller层<br/>RESTful API]
end
subgraph "业务逻辑层 Business Logic Layer"
C[Service层<br/>业务逻辑处理]
end
subgraph "数据访问层 Data Access Layer"
D[Mapper层<br/>MyBatis-Plus]
end
subgraph "数据存储层 Data Storage Layer"
E[(MySQL数据库)]
end
subgraph "安全层 Security Layer"
F[JWT认证]
G[权限控制]
end
A -->|HTTP/HTTPS| B
B --> C
C --> D
D --> E
F --> B
G --> B
F --> C
架构层次说明:
-
表现层(Presentation Layer)
- 负责用户界面展示和用户交互
- 使用Vue.js + TypeScript开发
- 通过HTTP/HTTPS协议与后端通信
-
应用层(Application Layer - Controller)
- 接收前端HTTP请求
- 参数校验和转换
- 调用业务逻辑层
- 返回统一响应格式
-
业务逻辑层(Business Logic Layer - Service)
- 实现核心业务逻辑
- 事务管理
- 业务规则验证
- 调用数据访问层
-
数据访问层(Data Access Layer - Mapper)
- 与数据库交互
- SQL映射和执行
- 结果集映射
-
数据存储层(Data Storage Layer)
- MySQL数据库
- 数据持久化存储
-
安全层(Security Layer)
- JWT令牌认证
- 基于角色的权限控制
- 横切所有层
2.2 技术架构
2.2.1 前端技术架构
graph LR
A[Vue.js 3.x] --> B[TypeScript]
A --> C[Vue Router]
A --> D[Pinia]
A --> E[Axios]
A --> F[Element Plus]
A --> G[Bootstrap CSS]
前端技术栈:
| 技术 | 版本 | 说明 |
|---|---|---|
| Vue.js | 3.x | 前端框架 |
| TypeScript | 5.x | 类型安全 |
| Vue Router | 4.x | 路由管理 |
| Pinia | 2.x | 状态管理 |
| Axios | 1.x | HTTP客户端 |
| Element Plus | 2.x | UI组件库 |
| Bootstrap CSS | 5.x | CSS框架 |
| Vite | 5.x | 构建工具 |
2.2.2 后端技术架构
graph LR
A[Spring Boot 3.2.x] --> B[Spring Security 6.x]
A --> C[MyBatis-Plus 3.5.x]
A --> D[Spring Validation]
A --> E[Knife4j 4.x]
A --> F[Hutool 5.8.x]
A --> G[Lombok 1.18.x]
后端技术栈:
| 技术 | 版本 | 说明 |
|---|---|---|
| Java | 21 | 编程语言 |
| Spring Boot | 3.2.x | 应用框架 |
| Spring Security | 6.x | 安全框架 |
| MyBatis-Plus | 3.5.x | ORM框架 |
| Spring Validation | - | 参数校验 |
| Knife4j | 4.x | API文档 |
| Hutool | 5.8.x | 工具类库 |
| Lombok | 1.18.x | 代码简化 |
| JWT | 0.12.x (jjwt) | 令牌认证 |
| ZXing | 3.5.x | 二维码生成 |
| iText | 7.x | PDF生成 |
| EasyExcel | 3.3.x | Excel导出 |
2.2.3 数据存储架构
graph LR
A[MySQL 8.0+] --> B[InnoDB引擎]
A --> C[utf8mb4字符集]
A --> D[逻辑删除]
A --> E[索引优化]
数据库技术栈:
| 技术 | 版本 | 说明 |
|---|---|---|
| MySQL | 8.0+ | 关系型数据库 |
| InnoDB | - | 存储引擎 |
| utf8mb4 | - | 字符集 |
| 逻辑删除 | - | 数据删除策略 |
| 索引 | - | 查询优化 |
2.3 部署架构
graph TB
subgraph "客户端 Client"
A1[PC浏览器]
A2[移动浏览器]
end
subgraph "网络层 Network"
B[HTTPS<br/>防火墙]
end
subgraph "应用服务器 Application Server"
C1[Nginx<br/>反向代理]
C2[Spring Boot应用<br/>端口:8080]
C3[静态资源<br/>Vue.js打包]
end
subgraph "数据服务器 Data Server"
D1[(MySQL数据库<br/>端口:3306)]
end
A1 --> B
A2 --> B
B --> C1
C1 --> C2
C1 --> C3
C2 --> D1
部署架构说明:
-
客户端层
- PC浏览器(Chrome、Firefox、Edge等)
- 移动浏览器(iOS Safari、Chrome Mobile)
-
网络层
- HTTPS加密传输
- 防火墙安全防护
-
应用服务器
- Nginx:反向代理、负载均衡、静态文件服务
- Spring Boot应用:后端API服务(端口8080)
- 静态资源:Vue.js打包后的前端文件
-
数据服务器
- MySQL数据库:数据持久化存储(端口3306)
2.4 模块划分
系统划分为以下功能模块:
graph TB
A[校园活动系统] --> B[认证模块 Auth]
A --> C[活动管理模块 Activity]
A --> D[报名管理模块 Registration]
A --> E[签到管理模块 CheckIn]
A --> F[评价管理模块 Review]
A --> G[统计模块 Statistics]
B --> B1[用户注册]
B --> B2[用户登录]
B --> B3[Token刷新]
B --> B4[获取用户信息]
B --> B5[修改密码]
C --> C1[创建活动]
C --> C2[更新活动]
C --> C3[删除活动]
C --> C4[查询活动列表]
C --> C5[查询活动详情]
C --> C6[日历视图]
C --> C7[时间冲突检测]
D --> D1[报名活动]
D --> D2[取消报名]
D --> D3[我的报名]
D --> D4[活动报名列表]
D --> D5[下载电子票]
E --> E1[生成签到二维码]
E --> E2[学生扫码签到]
E --> E3[管理员扫票签到]
E --> E4[签到列表]
F --> F1[提交评价]
F --> F2[活动评价列表]
F --> F3[我的评价]
G --> G1[活动统计]
G --> G2[数据导出]
G --> G3[总体统计]
模块职责说明:
| 模块 | 职责 |
|---|---|
| 认证模块 | 用户注册、登录、Token管理、密码管理 |
| 活动管理模块 | 活动的增删改查、时间冲突检测、状态管理 |
| 报名管理模块 | 活动报名、取消报名、电子票生成 |
| 签到管理模块 | 二维码生成、扫码签到、签到记录管理 |
| 评价管理模块 | 活动评价、评分统计、评价查询 |
| 统计模块 | 数据统计、报表生成、数据导出 |
第三章 数据库设计
3.1 概念结构设计
系统包含5个核心实体:用户(User)、活动(Activity)、报名(Registration)、签到(CheckIn)、评价(Review)。
erDiagram
USER ||--o{ ACTIVITY : creates
USER ||--o{ REGISTRATION : makes
USER ||--o{ REVIEW : writes
USER ||--o{ CHECK_IN : performs
ACTIVITY ||--o{ REGISTRATION : has
ACTIVITY ||--o{ REVIEW : receives
ACTIVITY ||--o{ CHECK_IN : requires
REGISTRATION ||--|| CHECK_IN : generates
USER {
bigint id
varchar username
varchar password
varchar name
varchar student_id
varchar email
varchar phone
varchar avatar
tinyint role
tinyint status
datetime created_at
datetime updated_at
tinyint deleted
}
ACTIVITY {
bigint id
varchar title
text description
varchar cover_image
datetime start_time
datetime end_time
datetime registration_deadline
varchar location
int max_participants
int current_participants
tinyint status
varchar category
bigint admin_id
varchar qr_code
int version
datetime created_at
datetime updated_at
tinyint deleted
}
REGISTRATION {
bigint id
bigint user_id
bigint activity_id
varchar ticket_code
varchar ticket_pdf_url
tinyint status
datetime created_at
datetime updated_at
datetime canceled_at
}
CHECK_IN {
bigint id
bigint registration_id
bigint user_id
bigint activity_id
datetime check_in_time
tinyint check_in_method
}
REVIEW {
bigint id
bigint user_id
bigint activity_id
tinyint rating
text content
datetime created_at
datetime updated_at
}
3.2 逻辑结构设计
3.2.1 用户表 (user)
存储系统用户信息,包括学生和管理员。
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 约束/索引 | 说明 |
|---|---|---|---|---|---|---|
| id | BIGINT | - | 是 | - | PK, AI | 用户ID |
| username | VARCHAR | 50 | 是 | - | Unique | 登录用户名 |
| password | VARCHAR | 255 | 是 | - | - | BCrypt加密密码 |
| name | VARCHAR | 50 | 是 | - | - | 真实姓名 |
| student_id | VARCHAR | 20 | 否 | NULL | Unique | 学号 |
| VARCHAR | 100 | 否 | NULL | - | 邮箱 | |
| phone | VARCHAR | 20 | 否 | NULL | - | 手机号 |
| avatar | VARCHAR | 255 | 否 | NULL | - | 头像URL |
| role | TINYINT | - | 是 | 0 | Index | 角色:0-学生,1-管理员 |
| status | TINYINT | - | 否 | 1 | - | 状态:0-禁用,1-正常 |
| created_at | DATETIME | - | 否 | CURRENT_TIMESTAMP | - | 创建时间 |
| updated_at | DATETIME | - | 否 | - | - | 更新时间 |
| deleted | TINYINT | - | 否 | 0 | - | 逻辑删除:0-否,1-是 |
索引设计:
- 主键索引:
id - 唯一索引:
uk_username(username) - 唯一索引:
uk_student_id(student_id) - 普通索引:
idx_role(role)
3.2.2 活动表 (activity)
存储活动信息。
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 约束/索引 | 说明 |
|---|---|---|---|---|---|---|
| id | BIGINT | - | 是 | - | PK, AI | 活动ID |
| title | VARCHAR | 100 | 是 | - | - | 活动名称 |
| description | TEXT | - | 否 | NULL | - | 活动简介 |
| cover_image | VARCHAR | 255 | 否 | NULL | - | 封面图片URL |
| start_time | DATETIME | - | 是 | - | Index | 开始时间 |
| end_time | DATETIME | - | 是 | - | - | 结束时间 |
| registration_deadline | DATETIME | - | 否 | NULL | - | 报名截止时间 |
| location | VARCHAR | 200 | 是 | - | - | 活动地点 |
| max_participants | INT | - | 是 | - | - | 报名人数上限 |
| current_participants | INT | - | 否 | 0 | - | 当前报名人数 |
| status | TINYINT | - | 否 | 0 | Index | 状态:0-未开始,1-报名中,2-进行中,3-已结束 |
| category | VARCHAR | 50 | 否 | NULL | - | 活动分类 |
| admin_id | BIGINT | - | 是 | - | FK, Index | 创建者ID |
| qr_code | VARCHAR | 255 | 否 | NULL | - | 签到二维码URL |
| version | INT | - | 否 | 0 | - | 乐观锁版本号 |
| created_at | DATETIME | - | 否 | CURRENT_TIMESTAMP | - | 创建时间 |
| updated_at | DATETIME | - | 否 | - | - | 更新时间 |
| deleted | TINYINT | - | 否 | 0 | - | 逻辑删除 |
索引设计:
- 主键索引:
id - 普通索引:
idx_status(status) - 普通索引:
idx_start_time(start_time) - 普通索引:
idx_admin_id(admin_id) - 外键约束:
fk_activity_admin(admin_id) → user(id) - 乐观锁字段:
version(用于并发控制)
3.2.3 报名表 (registration)
存储学生活动报名信息。
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 约束/索引 | 说明 |
|---|---|---|---|---|---|---|
| id | BIGINT | - | 是 | - | PK, AI | 报名ID |
| user_id | BIGINT | - | 是 | - | FK, Unique(1) | 用户ID |
| activity_id | BIGINT | - | 是 | - | FK, Unique(2) | 活动ID |
| ticket_code | VARCHAR | 100 | 否 | NULL | Unique | 电子票唯一码 |
| ticket_pdf_url | VARCHAR | 255 | 否 | NULL | - | 电子票PDF URL |
| status | TINYINT | - | 否 | 1 | - | 状态:0-已取消,1-已报名,2-已签到 |
| created_at | DATETIME | - | 否 | CURRENT_TIMESTAMP | - | 报名时间 |
| updated_at | DATETIME | - | 否 | - | - | 更新时间 |
| canceled_at | DATETIME | - | 否 | NULL | - | 取消时间 |
索引设计:
- 主键索引:
id - 唯一索引:
uk_user_activity(user_id, activity_id) - 唯一索引:
uk_ticket_code(ticket_code) - 普通索引:
idx_activity_id(activity_id) - 外键约束:
fk_registration_user(user_id) → user(id) - 外键约束:
fk_registration_activity(activity_id) → activity(id)
3.2.4 签到表 (check_in)
存储签到记录。
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 约束/索引 | 说明 |
|---|---|---|---|---|---|---|
| id | BIGINT | - | 是 | - | PK, AI | 签到ID |
| registration_id | BIGINT | - | 是 | - | FK, Unique | 报名ID |
| user_id | BIGINT | - | 是 | - | FK, Index | 用户ID |
| activity_id | BIGINT | - | 是 | - | FK, Index | 活动ID |
| check_in_time | DATETIME | - | 否 | CURRENT_TIMESTAMP | - | 签到时间 |
| check_in_method | TINYINT | - | 否 | 0 | - | 方式:0-扫码,1-管理员代签 |
索引设计:
- 主键索引:
id - 唯一索引:
uk_registration_id(registration_id) - 普通索引:
idx_activity_id(activity_id) - 普通索引:
idx_user_id(user_id) - 外键约束:
fk_checkin_registration(registration_id) → registration(id) - 外键约束:
fk_checkin_user(user_id) → user(id) - 外键约束:
fk_checkin_activity(activity_id) → activity(id)
3.2.5 评价表 (review)
存储活动评价信息。
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 约束/索引 | 说明 |
|---|---|---|---|---|---|---|
| id | BIGINT | - | 是 | - | PK, AI | 评价ID |
| user_id | BIGINT | - | 是 | - | FK, Unique(1) | 用户ID |
| activity_id | BIGINT | - | 是 | - | FK, Unique(2) | 活动ID |
| rating | TINYINT | - | 是 | - | - | 评分:1-5 |
| content | TEXT | - | 否 | NULL | - | 评论内容 |
| created_at | DATETIME | - | 否 | CURRENT_TIMESTAMP | - | 评价时间 |
| updated_at | DATETIME | - | 否 | - | - | 更新时间 |
索引设计:
- 主键索引:
id - 唯一索引:
uk_user_activity(user_id, activity_id) - 普通索引:
idx_activity_id(activity_id) - 外键约束:
fk_review_user(user_id) → user(id) - 外键约束:
fk_review_activity(activity_id) → activity(id)
3.3 物理结构设计
3.3.1 存储引擎
- 所有表使用 InnoDB 存储引擎
- 支持事务、行级锁、外键约束
3.3.2 字符集和排序规则
- 字符集:utf8mb4
- 排序规则:utf8mb4_general_ci
- 支持存储emoji表情和特殊字符
3.3.3 索引策略
索引设计原则:
- 主键索引:所有表使用自增BIGINT主键
- 唯一索引:业务唯一性约束(用户名、学号、电子票码等)
- 普通索引:频繁查询字段(status、start_time、admin_id等)
- 联合索引:多条件查询优化(user_id + activity_id)
索引使用场景:
idx_status:按状态筛选活动idx_start_time:按时间排序活动idx_admin_id:查询管理员创建的活动idx_activity_id:查询活动的报名、签到、评价记录uk_user_activity:防止重复报名、重复评价
3.3.4 分区策略
当前数据量较小,暂不采用表分区。如数据量增长到百万级别,可考虑:
- 按时间范围分区(activity表按created_at)
- 按用户ID哈希分区(registration表)
3.4 数据完整性约束
3.4.1 实体完整性
- 所有表设置主键约束
- 主键字段不允许为NULL
- 使用自增策略保证主键唯一性
3.4.2 参照完整性
- 外键约束确保关联数据一致性
- 级联删除策略:删除用户时,相关报名记录保留(逻辑删除)
- 级联更新策略:更新用户ID时,相关记录同步更新
3.4.3 域完整性
- 字段类型约束:VARCHAR长度、INT范围、TINYINT范围
- 非空约束:关键字段不允许为NULL
- 默认值约束:设置合理的默认值(status=1, deleted=0)
3.4.4 用户定义完整性
- 唯一约束:username、student_id、ticket_code唯一
- 检查约束:rating范围1-5,status范围0-3
- 业务约束:同一用户不能重复报名同一活动
第四章 详细设计
4.1 认证模块设计
4.1.1 JWT认证机制设计
Token设计:
graph LR
A[用户登录] --> B[验证用户名密码]
B --> C[生成Access Token<br/>有效期2小时]
B --> D[生成Refresh Token<br/>有效期7天]
C --> E[返回Token给客户端]
D --> E
E --> F[客户端存储Token]
F --> G[请求携带Access Token]
G --> H[服务端验证Token]
H --> I{Token有效?}
I -->|是| J[允许访问]
I -->|否| K[使用Refresh Token刷新]
K --> L[生成新Access Token]
L --> J
Access Token结构:
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"userId": 1,
"username": "student001",
"role": 0,
"iat": 1736620800,
"exp": 1736628000
}
}
Refresh Token结构:
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"userId": 1,
"iat": 1736620800,
"exp": 1737225600
}
}
认证流程:
- 用户登录成功后,服务端生成Access Token和Refresh Token
- 客户端存储两个Token(localStorage或Cookie)
- 每次请求携带Access Token(Header: Authorization: Bearer {token})
- 服务端验证Token有效性
- Access Token过期时,使用Refresh Token获取新Access Token
- Refresh Token过期时,用户需重新登录
4.1.2 类结构设计
classDiagram
class JwtTokenProvider {
-String secret
-long accessTokenExpiration
-long refreshTokenExpiration
+generateAccessToken(userId, username, role)
+generateRefreshToken(userId)
+validateToken(token)
+getUserIdFromToken(token)
+getUsernameFromToken(token)
+getRoleFromToken(token)
}
class JwtAuthenticationFilter {
-JwtTokenProvider jwtTokenProvider
+doFilterInternal(request, response, chain)
-getJwtFromRequest(request)
}
class UserDetailsServiceImpl {
-UserMapper userMapper
+loadUserByUsername(username)
}
class AuthController {
-AuthService authService
+register(request)
+login(request)
+refreshToken(request)
+getCurrentUser()
+changePassword(request)
}
class AuthServiceImpl {
-UserMapper userMapper
-JwtTokenProvider jwtTokenProvider
-PasswordEncoder passwordEncoder
+register(request)
+login(request)
+refreshToken(request)
+getCurrentUser(userId)
+changePassword(userId, request)
}
JwtAuthenticationFilter --> JwtTokenProvider
AuthController --> AuthServiceImpl
AuthServiceImpl --> JwtTokenProvider
AuthServiceImpl --> UserMapper
UserDetailsServiceImpl --> UserMapper
核心类说明:
| 类名 | 职责 |
|---|---|
| JwtTokenProvider | JWT Token的生成、解析、验证 |
| JwtAuthenticationFilter | JWT认证过滤器,拦截请求验证Token |
| UserDetailsServiceImpl | Spring Security用户详情服务 |
| AuthController | 认证接口控制器 |
| AuthServiceImpl | 认证业务逻辑实现 |
4.1.3 密码加密算法设计
加密算法:BCrypt
加密流程:
graph LR
A[用户输入密码] --> B[生成随机盐值]
B --> C[BCrypt哈希运算]
C --> D[存储加密密码]
加密特点:
- 自动生成随机盐值,防止彩虹表攻击
- 可调整计算强度(cost factor),默认10
- 相同密码每次加密结果不同
- 验证时自动提取盐值进行比对
示例代码:
// 加密
String encodedPassword = passwordEncoder.encode("123456");
// 验证
boolean matches = passwordEncoder.matches("123456", encodedPassword);
4.2 活动管理模块设计
4.2.1 活动状态机设计
stateDiagram-v2
[*] --> 草稿: 创建活动
草稿 --> 报名中: 发布活动
报名中 --> 进行中: 活动开始
进行中 --> 已结束: 活动结束
报名中 --> 已取消: 取消活动
进行中 --> 已取消: 取消活动
草稿 --> 已删除: 删除活动
报名中 --> 已删除: 删除活动
进行中 --> 已删除: 删除活动
已结束 --> 已删除: 删除活动
已取消 --> 已删除: 删除活动
已结束 --> [*]
已取消 --> [*]
已删除 --> [*]
状态说明:
| 状态值 | 状态名称 | 说明 | 可执行操作 |
|---|---|---|---|
| 0 | 草稿 | 活动未发布,仅管理员可见 | 修改、发布、删除 |
| 1 | 报名中 | 活动已发布,学生可报名 | 修改、取消、删除 |
| 2 | 进行中 | 活动进行中 | 查看详情 |
| 3 | 已结束 | 活动已结束,学生可评价 | 查看详情、删除 |
| 4 | 已取消 | 活动已取消 | 查看详情、删除 |
状态转换规则:
- 草稿 → 报名中:管理员发布活动
- 报名中 → 进行中:到达活动开始时间(定时任务自动更新)
- 进行中 → 已结束:到达活动结束时间(定时任务自动更新)
- 报名中/进行中 → 已取消:管理员取消活动
- 任意状态 → 已删除:管理员删除活动(逻辑删除)
4.2.2 时间冲突检测算法
冲突判断标准:
两个活动时间区间 [start1, end1] 和 [start2, end2] 存在冲突的条件:
!(end1 < start2 || end2 < start1)
即:两个时间区间有交集。
检测算法:
SELECT * FROM activity
WHERE deleted = 0
AND status IN (1, 2, 3)
AND id != ? -- 排除当前活动(更新时)
AND location = ? -- 同一地点
AND (
(start_time <= ? AND end_time >= ?) -- 新活动开始时间在旧活动时间范围内
OR (start_time <= ? AND end_time >= ?) -- 新活动结束时间在旧活动时间范围内
OR (start_time >= ? AND end_time <= ?) -- 新活动完全包含旧活动
)
检测流程:
graph TD
A[输入活动时间] --> B[查询同地点活动]
B --> C{遍历活动列表}
C -->|有活动| D{时间区间有交集?}
D -->|是| E[记录冲突活动]
D -->|否| C
C -->|无活动| F[无冲突]
E --> G[返回冲突列表]
F --> H[返回无冲突]
4.2.3 类结构设计
classDiagram
class ActivityController {
-ActivityService activityService
+pageActivities(page, params)
+getActivityById(id)
+createActivity(request)
+updateActivity(id, request)
+deleteActivity(id)
+getCalendarActivities(year, month)
+checkConflict(request)
}
class ActivityService {
<<interface>>
+pageActivities(page, params)
+getActivityById(id)
+createActivity(request, adminId)
+updateActivity(id, request, adminId)
+deleteActivity(id, adminId)
+getCalendarActivities(year, month)
+checkConflict(startTime, endTime, excludeId)
+updateActivityStatus()
}
class ActivityServiceImpl {
-ActivityMapper activityMapper
-UserMapper userMapper
-RegistrationMapper registrationMapper
+pageActivities(page, params)
+getActivityById(id)
+createActivity(request, adminId)
+updateActivity(id, request, adminId)
+deleteActivity(id, adminId)
+getCalendarActivities(year, month)
+checkConflict(startTime, endTime, excludeId)
+updateActivityStatus()
}
class ActivityMapper {
+selectPage(page, params)
+selectById(id)
+insert(activity)
+updateById(activity)
+deleteById(id)
+selectByMonth(year, month)
+selectConflictActivities(startTime, endTime, excludeId)
+selectActivitiesByStatus(status)
}
class Activity {
-Long id
-String title
-String description
-String coverImage
-LocalDateTime startTime
-LocalDateTime endTime
-LocalDateTime registrationDeadline
-String location
-Integer maxParticipants
-Integer currentParticipants
-Integer status
-String category
-Long adminId
-String qrCode
-Integer version
-LocalDateTime createdAt
-LocalDateTime updatedAt
-Integer deleted
}
ActivityController --> ActivityService
ActivityService <|.. ActivityServiceImpl
ActivityServiceImpl --> ActivityMapper
ActivityMapper --> Activity
4.3 报名管理模块设计
4.3.1 报名流程设计
flowchart TD
A[学生发起报名] --> B{活动存在?}
B -->|否| C[返回活动不存在]
B -->|是| D{活动在报名中?}
D -->|否| E[返回活动不在报名中]
D -->|是| F{报名已截止?}
F -->|是| E
F -->|否| G{报名人数已满?}
G -->|是| H[返回报名人数已满]
G -->|否| I{已报名该活动?}
I -->|是| J[返回已报名]
I -->|否| K{时间冲突?}
K -->|是| L[返回时间冲突]
K -->|否| M[生成电子票码]
M --> N[创建报名记录]
N --> O[更新活动报名人数]
O --> P[生成电子票PDF]
P --> Q[返回报名成功]
报名验证规则:
- 活动必须存在且未删除
- 活动状态必须为"报名中"
- 当前时间必须在报名截止时间之前
- 当前报名人数必须小于报名人数上限
- 用户不能重复报名同一活动
- 用户不能报名时间冲突的活动
4.3.2 电子票生成算法
电子票唯一码生成:
格式:TICKET{YYYYMMDD}{6位随机数}
示例:TICKET202601150001234
生成流程:
graph LR
A[获取当前日期] --> B[生成6位随机数]
B --> C[拼接成电子票码]
C --> D{检查唯一性}
D -->|已存在| B
D -->|不存在| E[返回电子票码]
PDF电子票生成:
使用iText库生成PDF文件,包含以下内容:
- 活动信息:名称、时间、地点
- 学生信息:姓名、学号
- 电子票码
- 二维码(包含电子票码)
PDF布局设计:
┌─────────────────────────────────────┐
│ 校园活动电子票 │
├─────────────────────────────────────┤
│ 活动名称: 校园音乐节 │
│ 活动时间: 2026-01-15 19:00-22:00 │
│ 活动地点: 大礼堂 │
│ │
│ 学生姓名: 张三 │
│ 学 号: 2021000001 │
│ │
│ 电子票号: TICKET202601150001234 │
│ │
│ [二维码图片] │
├─────────────────────────────────────┤
│ 请凭此票入场,请勿转借他人 │
└─────────────────────────────────────┘
4.3.3 并发控制设计
并发报名问题:
- 多个用户同时报名同一活动
- 可能导致报名人数超过上限
解决方案:使用数据库乐观锁
乐观锁实现:
-- 报名时更新报名人数(使用version乐观锁)
UPDATE activity
SET current_participants = current_participants + 1,
version = version + 1
WHERE id = ?
AND current_participants < max_participants
AND version = ?;
并发控制流程:
sequenceDiagram
participant User1 as 用户1
participant User2 as 用户2
participant DB as 数据库
User1->>DB: 查询活动信息(version=1)
User2->>DB: 查询活动信息(version=1)
User1->>DB: 更新报名人数<br/>WHERE version=1
DB-->>User1: 更新成功(version=2)
User2->>DB: 更新报名人数<br/>WHERE version=1
DB-->>User2: 更新失败(version已变)
User2->>User2: 重新查询活动信息
User2->>DB: 查询活动信息(version=2)
User2->>DB: 更新报名人数<br/>WHERE version=2
DB-->>User2: 更新成功(version=3)
4.3.4 类结构设计
classDiagram
class RegistrationController {
-RegistrationService registrationService
+register(request)
+cancelRegistration(id)
+getMyRegistrations(page, status)
+getActivityRegistrations(activityId, page)
+downloadTicketPdf(id)
}
class RegistrationService {
<<interface>>
+register(userId, activityId)
+cancelRegistration(userId, registrationId)
+getMyRegistrations(userId, page, status)
+getActivityRegistrations(activityId, page)
+downloadTicketPdf(userId, registrationId)
+checkTimeConflict(userId, activityId)
}
class RegistrationServiceImpl {
-RegistrationMapper registrationMapper
-ActivityMapper activityMapper
-PdfUtil pdfUtil
+register(userId, activityId)
+cancelRegistration(userId, registrationId)
+getMyRegistrations(userId, page, status)
+getActivityRegistrations(activityId, page)
+downloadTicketPdf(userId, registrationId)
+checkTimeConflict(userId, activityId)
+generateTicketCode()
}
class RegistrationMapper {
+selectOne(userId, activityId)
+insert(registration)
+updateById(registration)
+selectMyPage(userId, page, status)
+selectActivityPage(activityId, page)
+selectByTicketCode(ticketCode)
+selectUserRegistrations(userId, status)
}
class Registration {
-Long id
-Long userId
-Long activityId
-String ticketCode
-String ticketPdfUrl
-Integer status
-LocalDateTime createdAt
-LocalDateTime updatedAt
-LocalDateTime canceledAt
}
class PdfUtil {
+generateTicketPdf(user, activity, registration)
}
RegistrationController --> RegistrationService
RegistrationService <|.. RegistrationServiceImpl
RegistrationServiceImpl --> RegistrationMapper
RegistrationServiceImpl --> PdfUtil
RegistrationMapper --> Registration
4.4 签到管理模块设计
4.4.1 二维码生成与解析设计
签到二维码内容格式:
CHECKIN:{activityId}:{timestamp}:{signature}
示例:
CHECKIN:1:1736928000:a1b2c3d4e5f6
二维码生成流程:
graph TD
A[管理员生成签到二维码] --> B[获取活动ID]
B --> C[生成时间戳]
C --> D[生成签名<br/>HMAC-SHA256]
D --> E[拼接二维码内容]
E --> F[使用ZXing生成二维码图片]
F --> G[保存二维码图片]
G --> H[返回二维码URL]
签名生成算法:
String signature = HMACSHA256(
secretKey,
activityId + ":" + timestamp
);
二维码解析流程:
graph TD
A[扫描二维码] --> B[解析二维码内容]
B --> C{格式正确?}
C -->|否| D[返回二维码无效]
C -->|是| E[提取activityId]
E --> F[提取timestamp]
F --> G[提取signature]
G --> H{签名验证通过?}
H -->|否| D
H -->|是| I{时间有效?}
I -->|否| J[返回二维码已过期]
I -->|是| K[返回活动ID]
时间有效性判断:
- 二维码有效期:活动开始前1小时至活动结束
- 判断条件:
startTime - 1hour <= now <= endTime
4.4.2 签到流程设计
学生扫码签到流程:
flowchart TD
A[学生扫描签到二维码] --> B[解析二维码]
B --> C{二维码有效?}
C -->|否| D[返回二维码无效]
C -->|是| E{活动存在?}
E -->|否| F[返回活动不存在]
E -->|是| G{签到时间有效?}
G -->|否| H[返回签到时间已过期]
G -->|是| I{已报名该活动?}
I -->|否| J[返回未报名该活动]
I -->|是| K{已签到?}
K -->|是| L[返回已签到]
K -->|否| M[创建签到记录]
M --> N[更新报名状态为已签到]
N --> O[返回签到成功]
管理员扫票签到流程:
flowchart TD
A[管理员扫描学生电子票] --> B[解析二维码]
B --> C[获取报名记录ID]
C --> D{报名记录存在?}
D -->|否| E[返回电子票无效]
D -->|是| F{活动匹配?}
F -->|否| E
F -->|是| G{签到时间有效?}
G -->|否| H[返回签到时间已过期]
G -->|是| I{已签到?}
I -->|是| J[返回已签到]
I -->|否| K[创建签到记录]
K --> L[更新报名状态为已签到]
L --> M[记录签到方式和签到人]
M --> N[返回签到成功]
4.4.3 类结构设计
classDiagram
class CheckInController {
-CheckInService checkInService
+generateQrCode(activityId)
+scanCheckIn(request)
+ticketCheckIn(request)
+getActivityCheckIns(activityId, page)
}
class CheckInService {
<<interface>>
+generateQrCode(activityId, adminId)
+scanCheckIn(userId, qrCodeContent)
+ticketCheckIn(adminId, activityId, ticketCode)
+getActivityCheckIns(activityId, page)
}
class CheckInServiceImpl {
-CheckInMapper checkInMapper
-RegistrationMapper registrationMapper
-ActivityMapper activityMapper
-QrCodeUtil qrCodeUtil
+generateQrCode(activityId, adminId)
+scanCheckIn(userId, qrCodeContent)
+ticketCheckIn(adminId, activityId, ticketCode)
+getActivityCheckIns(activityId, page)
-validateCheckInTime(activity)
}
class CheckInMapper {
+selectOne(registrationId)
+insert(checkIn)
+selectActivityPage(activityId, page)
+selectUserCheckIns(userId)
}
class QrCodeUtil {
+generateQrCode(content)
+parseQrCode(image)
+generateSignature(activityId, timestamp)
+validateSignature(content, signature)
}
class CheckIn {
-Long id
-Long registrationId
-Long userId
-Long activityId
-LocalDateTime checkInTime
-Integer checkInMethod
}
CheckInController --> CheckInService
CheckInService <|.. CheckInServiceImpl
CheckInServiceImpl --> CheckInMapper
CheckInServiceImpl --> QrCodeUtil
CheckInMapper --> CheckIn
4.5 评价管理模块设计
4.5.1 评分计算算法
平均评分计算:
SELECT AVG(rating) as average_rating,
COUNT(*) as review_count
FROM review
WHERE activity_id = ?;
评分分布统计:
SELECT rating,
COUNT(*) as count
FROM review
WHERE activity_id = ?
GROUP BY rating
ORDER BY rating;
评分星级显示:
- 1星:⭐
- 2星:⭐⭐
- 3星:⭐⭐⭐
- 4星:⭐⭐⭐⭐
- 5星:⭐⭐⭐⭐⭐
4.5.2 类结构设计
classDiagram
class ReviewController {
-ReviewService reviewService
+createReview(request)
+getActivityReviews(activityId, page)
+getMyReviews(page)
}
class ReviewService {
<<interface>>
+createReview(userId, request)
+getActivityReviews(activityId, page)
+getMyReviews(userId, page)
+calculateAverageRating(activityId)
}
class ReviewServiceImpl {
-ReviewMapper reviewMapper
-CheckInMapper checkInMapper
-ActivityMapper activityMapper
+createReview(userId, request)
+getActivityReviews(activityId, page)
+getMyReviews(userId, page)
+calculateAverageRating(activityId)
}
class ReviewMapper {
+selectOne(userId, activityId)
+insert(review)
+selectActivityPage(activityId, page)
+selectMyPage(userId, page)
+selectRatingDistribution(activityId)
}
class Review {
-Long id
-Long userId
-Long activityId
-Integer rating
-String content
-LocalDateTime createdAt
-LocalDateTime updatedAt
}
ReviewController --> ReviewService
ReviewService <|.. ReviewServiceImpl
ReviewServiceImpl --> ReviewMapper
ReviewMapper --> Review
4.6 统计模块设计
4.6.1 统计指标计算
活动统计指标:
-
报名数据
- 总报名人数:
COUNT(*) FROM registration WHERE activity_id = ? AND status != 0
- 总报名人数:
-
签到数据
- 总签到人数:
COUNT(*) FROM check_in WHERE activity_id = ? - 签到率:
签到人数 / 报名人数
- 总签到人数:
-
评价数据
- 总评价人数:
COUNT(*) FROM review WHERE activity_id = ? - 评价率:
评价人数 / 签到人数 - 平均评分:
AVG(rating) FROM review WHERE activity_id = ? - 评分分布:各星级评价数量
- 总评价人数:
总体统计指标:
-
活动数据
- 总活动数:
COUNT(*) FROM activity WHERE deleted = 0
- 总活动数:
-
报名数据
- 总报名数:
COUNT(*) FROM registration WHERE status != 0
- 总报名数:
-
签到数据
- 总签到数:
COUNT(*) FROM check_in
- 总签到数:
-
评价数据
- 总评价数:
COUNT(*) FROM review - 平均评分:
AVG(rating) FROM review
- 总评价数:
-
月度统计
- 按月份统计活动数和报名数
4.6.2 数据导出设计
导出格式:Excel (.xlsx)
导出内容:
-
活动报名数据导出
- 学生信息:姓名、学号、手机号、邮箱
- 报名信息:报名时间、报名状态、电子票号
- 签到信息:签到时间、签到方式
-
活动签到数据导出
- 学生信息:姓名、学号
- 签到信息:签到时间、签到方式
-
活动评价数据导出
- 学生信息:姓名
- 评价信息:评分、评论内容、评价时间
导出流程:
graph TD
A[管理员发起导出] --> B[查询活动数据]
B --> C[组装导出数据]
C --> D[使用EasyExcel生成Excel]
D --> E[返回文件流]
E --> F[客户端下载文件]
第五章 接口设计
5.1 接口设计规范
5.1.1 RESTful API规范
| 操作 | HTTP方法 | URL示例 | 说明 |
|---|---|---|---|
| 创建 | POST | /api/v1/activities |
创建资源 |
| 查询 | GET | /api/v1/activities/{id} |
查询单个资源 |
| 更新 | PUT | /api/v1/activities/{id} |
更新资源 |
| 删除 | DELETE | /api/v1/activities/{id} |
删除资源 |
| 列表 | GET | /api/v1/activities |
查询资源列表 |
5.1.2 统一响应格式
成功响应:
{
"code": 200,
"message": "success",
"data": {},
"timestamp": 1736620800000
}
错误响应:
{
"code": 400,
"message": "请求参数错误",
"data": null,
"timestamp": 1736620800000
}
5.1.3 状态码说明
| 状态码 | 说明 |
|---|---|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未认证或Token已过期 |
| 403 | 无权限访问 |
| 404 | 资源不存在 |
| 409 | 业务冲突 |
| 500 | 服务器内部错误 |
5.1.4 业务错误码
| 错误码 | 说明 |
|---|---|
| 1001 | 用户不存在 |
| 1002 | 用户已存在 |
| 1003 | 密码错误 |
| 1004 | 用户名或密码错误 |
| 1005 | 学号已存在 |
| 2001 | 活动不存在 |
| 2002 | 活动已开始,无法取消报名 |
| 2003 | 活动时间冲突 |
| 2004 | 活动报名人数已满 |
| 3001 | 报名记录不存在 |
| 3002 | 您已报名该活动 |
| 3003 | 您未报名该活动 |
| 4001 | 签到失败 |
| 4002 | 已签到 |
| 4003 | 签到时间已过期 |
| 5001 | 您已评价该活动 |
| 5002 | 评价记录不存在 |
| 5003 | 您未参加该活动,无法评价 |
5.2 认证接口设计
5.2.1 用户注册接口
接口地址:POST /api/v1/auth/register
请求权限:无需认证
请求参数:
{
"username": "student001",
"password": "123456",
"name": "张三",
"studentId": "2021000001",
"email": "zhangsan@example.com",
"phone": "13800138000"
}
响应示例:
{
"code": 200,
"message": "注册成功",
"data": null,
"timestamp": 1736620800000
}
5.2.2 用户登录接口
接口地址:POST /api/v1/auth/login
请求权限:无需认证
请求参数:
{
"username": "student001",
"password": "123456"
}
响应示例:
{
"code": 200,
"message": "登录成功",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600,
"tokenType": "Bearer",
"userInfo": {
"id": 1,
"username": "student001",
"name": "张三",
"role": 0,
"avatar": null
}
},
"timestamp": 1736620800000
}
5.2.3 Token刷新接口
接口地址:POST /api/v1/auth/refresh
请求权限:无需认证
请求参数:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
响应示例:
{
"code": 200,
"message": "success",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600
},
"timestamp": 1736620800000
}
5.2.4 获取用户信息接口
接口地址:GET /api/v1/auth/me
请求权限:需要认证
请求头:
Authorization: Bearer {accessToken}
响应示例:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"username": "student001",
"password": "$2a$10$...",
"name": "张三",
"studentId": "2021000001",
"email": "zhangsan@example.com",
"phone": "13800138000",
"avatar": null,
"role": 0,
"status": 1,
"createdAt": "2026-01-01T00:00:00",
"updatedAt": "2026-01-01T00:00:00"
},
"timestamp": 1736620800000
}
5.3 活动接口设计
5.3.1 活动列表查询接口
接口地址:GET /api/v1/activities
请求权限:无需认证
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
| status | Integer | 否 | 活动状态 | - |
| keyword | String | 否 | 关键词搜索 | - |
| category | String | 否 | 活动分类 | - |
| startDate | LocalDateTime | 否 | 开始时间 | - |
| endDate | LocalDateTime | 否 | 结束时间 | - |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"id": 1,
"title": "校园音乐节",
"description": "一年一度的校园音乐盛会",
"coverImage": "https://example.com/cover.jpg",
"startTime": "2026-01-15T19:00:00",
"endTime": "2026-01-15T22:00:00",
"registrationDeadline": "2026-01-14T18:00:00",
"location": "大礼堂",
"maxParticipants": 500,
"currentParticipants": 300,
"status": 1,
"category": "文艺活动",
"adminId": 1,
"adminName": "管理员",
"averageRating": 4.5,
"reviewCount": 20,
"isRegistered": false,
"createdAt": "2026-01-01T00:00:00"
}
],
"total": 1,
"pages": 1,
"current": 1,
"size": 10
},
"timestamp": 1736620800000
}
5.3.2 活动详情查询接口
接口地址:GET /api/v1/activities/{id}
请求权限:无需认证
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 活动ID |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"title": "校园音乐节",
"description": "一年一度的校园音乐盛会",
"coverImage": "https://example.com/cover.jpg",
"startTime": "2026-01-15T19:00:00",
"endTime": "2026-01-15T22:00:00",
"registrationDeadline": "2026-01-14T18:00:00",
"location": "大礼堂",
"maxParticipants": 500,
"currentParticipants": 300,
"status": 1,
"category": "文艺活动",
"adminId": 1,
"adminName": "管理员",
"averageRating": 4.5,
"reviewCount": 20,
"isRegistered": false,
"createdAt": "2026-01-01T00:00:00"
},
"timestamp": 1736620800000
}
5.3.3 创建活动接口
接口地址:POST /api/v1/activities
请求权限:管理员 (ADMIN)
请求参数:
{
"title": "校园音乐节",
"description": "一年一度的校园音乐盛会",
"coverImage": "https://example.com/cover.jpg",
"startTime": "2026-01-15T19:00:00",
"endTime": "2026-01-15T22:00:00",
"registrationDeadline": "2026-01-14T18:00:00",
"location": "大礼堂",
"maxParticipants": 500,
"category": "文艺活动"
}
响应示例:
{
"code": 200,
"message": "创建成功",
"data": 1,
"timestamp": 1736620800000
}
5.3.4 更新活动接口
接口地址:PUT /api/v1/activities/{id}
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 活动ID |
请求参数:
{
"title": "校园音乐节",
"description": "一年一度的校园音乐盛会",
"coverImage": "https://example.com/cover.jpg",
"startTime": "2026-01-15T19:00:00",
"endTime": "2026-01-15T22:00:00",
"registrationDeadline": "2026-01-14T18:00:00",
"location": "大礼堂",
"maxParticipants": 500,
"status": 1,
"category": "文艺活动"
}
响应示例:
{
"code": 200,
"message": "更新成功",
"data": null,
"timestamp": 1736620800000
}
5.3.5 删除活动接口
接口地址:DELETE /api/v1/activities/{id}
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 活动ID |
响应示例:
{
"code": 200,
"message": "删除成功",
"data": null,
"timestamp": 1736620800000
}
5.3.6 日历视图接口
接口地址:GET /api/v1/activities/calendar
请求权限:无需认证
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| year | Integer | 是 | 年份 |
| month | Integer | 是 | 月份 |
响应示例:
{
"code": 200,
"message": "success",
"data": [
{
"id": 1,
"title": "校园音乐节",
"description": "一年一度的校园音乐盛会",
"coverImage": "https://example.com/cover.jpg",
"startTime": "2026-01-15T19:00:00",
"endTime": "2026-01-15T22:00:00",
"registrationDeadline": "2026-01-14T18:00:00",
"location": "大礼堂",
"maxParticipants": 500,
"currentParticipants": 300,
"status": 1,
"category": "文艺活动",
"adminId": 1,
"adminName": "管理员",
"averageRating": 4.5,
"reviewCount": 20,
"isRegistered": false,
"createdAt": "2026-01-01T00:00:00"
}
],
"timestamp": 1736620800000
}
5.3.7 时间冲突检测接口
接口地址:POST /api/v1/activities/check-conflict
请求权限:管理员 (ADMIN)
请求参数:
{
"startTime": "2026-01-15T19:00:00",
"endTime": "2026-01-15T22:00:00",
"excludeActivityId": 1
}
响应示例:
{
"code": 200,
"message": "success",
"data": {
"hasConflict": true,
"conflictActivities": [
{
"id": 2,
"title": "校园篮球赛",
"startTime": "2026-01-15T18:00:00",
"endTime": "2026-01-15T20:00:00"
}
]
},
"timestamp": 1736620800000
}
5.4 报名接口设计
5.4.1 报名活动接口
接口地址:POST /api/v1/registrations
请求权限:需要认证
请求参数:
{
"activityId": 1
}
响应示例:
{
"code": 200,
"message": "报名成功",
"data": {
"id": 1,
"activityId": 1,
"activityTitle": "校园音乐节",
"activityStartTime": "2026-01-15T19:00:00",
"activityEndTime": "2026-01-15T22:00:00",
"activityLocation": "大礼堂",
"ticketCode": "TICKET202601150001",
"ticketPdfUrl": "https://example.com/tickets/TICKET202601150001.pdf",
"status": 1,
"createdAt": "2026-01-10T10:00:00",
"canceledAt": null
},
"timestamp": 1736620800000
}
5.4.2 取消报名接口
接口地址:DELETE /api/v1/registrations/{id}
请求权限:需要认证
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 报名记录ID |
响应示例:
{
"code": 200,
"message": "取消成功",
"data": null,
"timestamp": 1736620800000
}
5.4.3 我的报名列表接口
接口地址:GET /api/v1/registrations/my
请求权限:需要认证
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
| status | Integer | 否 | 报名状态 | - |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"id": 1,
"activityId": 1,
"activityTitle": "校园音乐节",
"activityStartTime": "2026-01-15T19:00:00",
"activityEndTime": "2026-01-15T22:00:00",
"activityLocation": "大礼堂",
"ticketCode": "TICKET202601150001",
"ticketPdfUrl": "https://example.com/tickets/TICKET202601150001.pdf",
"status": 1,
"createdAt": "2026-01-10T10:00:00",
"canceledAt": null
}
],
"total": 1,
"pages": 1,
"current": 1,
"size": 10
},
"timestamp": 1736620800000
}
5.4.4 活动报名列表接口
接口地址:GET /api/v1/registrations/activity/{activityId}
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityId | Long | 是 | 活动ID |
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"id": 1,
"activityId": 1,
"activityTitle": "校园音乐节",
"activityStartTime": "2026-01-15T19:00:00",
"activityEndTime": "2026-01-15T22:00:00",
"activityLocation": "大礼堂",
"ticketCode": "TICKET202601150001",
"ticketPdfUrl": "https://example.com/tickets/TICKET202601150001.pdf",
"status": 1,
"createdAt": "2026-01-10T10:00:00",
"canceledAt": null
}
],
"total": 1,
"pages": 1,
"current": 1,
"size": 10
},
"timestamp": 1736620800000
}
5.4.5 下载电子票接口
接口地址:GET /api/v1/registrations/{id}/ticket
请求权限:需要认证
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 报名记录ID |
响应:PDF文件流
响应头:
Content-Disposition: attachment; filename=ticket.pdf
Content-Type: application/pdf
5.5 签到接口设计
5.5.1 生成签到二维码接口
接口地址:POST /api/v1/checkin/qrcode/{activityId}
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityId | Long | 是 | 活动ID |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"qrCodeUrl": "https://example.com/qrcode/ACTIVITY1.png",
"qrCodeContent": "CHECKIN:1:1736928000:a1b2c3d4e5f6",
"expiresAt": "2026-01-15T23:00:00"
},
"timestamp": 1736620800000
}
5.5.2 学生扫码签到接口
接口地址:POST /api/v1/checkin/scan
请求权限:需要认证
请求参数:
{
"qrCodeContent": "CHECKIN:1:1736928000:a1b2c3d4e5f6"
}
响应示例:
{
"code": 200,
"message": "签到成功",
"data": {
"id": 1,
"userId": 1,
"userName": "张三",
"studentId": "2021000001",
"activityId": 1,
"checkInTime": "2026-01-15T19:30:00",
"checkInMethod": 1
},
"timestamp": 1736620800000
}
5.5.3 管理员扫票签到接口
接口地址:POST /api/v1/checkin/ticket
请求权限:管理员 (ADMIN)
请求参数:
{
"activityId": 1,
"ticketCode": "TICKET202601150001"
}
响应示例:
{
"code": 200,
"message": "签到成功",
"data": {
"id": 1,
"userId": 1,
"userName": "张三",
"studentId": "2021000001",
"activityId": 1,
"checkInTime": "2026-01-15T19:30:00",
"checkInMethod": 2
},
"timestamp": 1736620800000
}
5.5.4 签到列表查询接口
接口地址:GET /api/v1/checkin/activity/{activityId}
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityId | Long | 是 | 活动ID |
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"id": 1,
"userId": 1,
"userName": "张三",
"studentId": "2021000001",
"activityId": 1,
"checkInTime": "2026-01-15T19:30:00",
"checkInMethod": 1
}
],
"total": 1,
"pages": 1,
"current": 1,
"size": 10
},
"timestamp": 1736620800000
}
5.6 评价接口设计
5.6.1 提交评价接口
接口地址:POST /api/v1/reviews
请求权限:需要认证
请求参数:
{
"activityId": 1,
"rating": 5,
"content": "非常精彩的活动!"
}
响应示例:
{
"code": 200,
"message": "评价成功",
"data": {
"id": 1,
"userId": 1,
"userName": "张三",
"userAvatar": null,
"activityId": 1,
"activityTitle": "校园音乐节",
"rating": 5,
"content": "非常精彩的活动!",
"createdAt": "2026-01-16T10:00:00"
},
"timestamp": 1736620800000
}
5.6.2 活动评价列表接口
接口地址:GET /api/v1/reviews/activity/{activityId}
请求权限:无需认证
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityId | Long | 是 | 活动ID |
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"id": 1,
"userId": 1,
"userName": "张三",
"userAvatar": null,
"activityId": 1,
"activityTitle": "校园音乐节",
"rating": 5,
"content": "非常精彩的活动!",
"createdAt": "2026-01-16T10:00:00"
}
],
"total": 1,
"pages": 1,
"current": 1,
"size": 10
},
"timestamp": 1736620800000
}
5.6.3 我的评价列表接口
接口地址:GET /api/v1/reviews/my
请求权限:需要认证
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"id": 1,
"userId": 1,
"userName": "张三",
"userAvatar": null,
"activityId": 1,
"activityTitle": "校园音乐节",
"rating": 5,
"content": "非常精彩的活动!",
"createdAt": "2026-01-16T10:00:00"
}
],
"total": 1,
"pages": 1,
"current": 1,
"size": 10
},
"timestamp": 1736620800000
}
5.7 统计接口设计
5.7.1 活动统计接口
接口地址:GET /api/v1/statistics/activity/{activityId}
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityId | Long | 是 | 活动ID |
响应示例:
{
"code": 200,
"message": "success",
"data": {
"activityId": 1,
"activityTitle": "校园音乐节",
"registeredCount": 300,
"checkedInCount": 250,
"checkInRate": 0.8333,
"reviewCount": 20,
"averageRating": 4.5,
"ratingDistribution": {
"1": 0,
"2": 1,
"3": 3,
"4": 6,
"5": 10
}
},
"timestamp": 1736620800000
}
5.7.2 数据导出接口
接口地址:GET /api/v1/statistics/activity/{activityId}/export
请求权限:管理员 (ADMIN)
路径参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| activityId | Long | 是 | 活动ID |
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|---|---|---|---|---|
| format | String | 否 | 导出格式 | excel |
响应:文件流 (Excel或PDF)
5.7.3 总体统计接口
接口地址:GET /api/v1/statistics/overview
请求权限:管理员 (ADMIN)
响应示例:
{
"code": 200,
"message": "success",
"data": {
"totalActivities": 50,
"totalRegistrations": 1000,
"totalCheckIns": 800,
"totalReviews": 500,
"averageRating": 4.3,
"monthlyStats": [
{
"month": "2026-01",
"activityCount": 10,
"registrationCount": 200
},
{
"month": "2026-02",
"activityCount": 15,
"registrationCount": 300
}
]
},
"timestamp": 1736620800000
}
第六章 安全设计
6.1 认证机制设计
6.1.1 JWT Token生成
Access Token生成:
public String generateAccessToken(Long userId, String username, Integer role) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + accessTokenExpiration);
return Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("username", username)
.claim("role", role)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
Refresh Token生成:
public String generateRefreshToken(Long userId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + refreshTokenExpiration);
return Jwts.builder()
.setSubject(String.valueOf(userId))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
6.1.2 JWT Token验证
Token验证流程:
public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
// 签名无效
} catch (MalformedJwtException ex) {
// Token格式错误
} catch (ExpiredJwtException ex) {
// Token已过期
} catch (UnsupportedJwtException ex) {
// 不支持的Token
} catch (IllegalArgumentException ex) {
// Token为空
}
return false;
}
6.1.3 Token刷新策略
Token刷新流程:
sequenceDiagram
participant Client as 客户端
participant Server as 服务端
participant DB as 数据库
Client->>Server: 请求携带Access Token
Server->>Server: 验证Access Token
alt Access Token有效
Server-->>Client: 返回数据
else Access Token过期
Client->>Server: 使用Refresh Token刷新
Server->>DB: 验证Refresh Token
alt Refresh Token有效
Server->>Server: 生成新Access Token
Server-->>Client: 返回新Access Token
else Refresh Token过期
Server-->>Client: 返回401,需重新登录
end
end
6.2 权限控制设计
6.2.1 基于角色的访问控制(RBAC)
角色定义:
| 角色 | 角色值 | 说明 |
|---|---|---|
| 学生 | 0 | 普通学生用户 |
| 管理员 | 1 | 活动管理员 |
权限配置:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/register", "/api/v1/auth/login").permitAll()
.requestMatchers("/api/v1/activities/**").permitAll()
.requestMatchers("/api/v1/registrations/**").authenticated()
.requestMatchers("/api/v1/reviews/**").authenticated()
.requestMatchers("/api/v1/checkin/**").authenticated()
.requestMatchers("/api/v1/statistics/**").hasRole("ADMIN")
.anyRequest().authenticated())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
6.2.2 接口权限注解
权限注解使用:
// 仅管理员可访问
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public Result<Long> createActivity(@RequestBody ActivityCreateRequest request) {
// ...
}
// 已登录用户可访问
@PreAuthorize("isAuthenticated()")
@GetMapping("/my")
public Result<IPage<RegistrationVO>> getMyRegistrations(
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long size) {
// ...
}
6.3 数据安全设计
6.3.1 密码加密存储
加密算法:BCrypt
密码强度要求:
- 最少6个字符
- 最多20个字符
- 建议包含字母和数字
加密实现:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
6.3.2 敏感数据传输加密
HTTPS加密:
- 使用HTTPS协议传输所有数据
- SSL/TLS证书加密
- 防止中间人攻击
敏感字段脱敏:
@Data
public class UserVO {
private Long id;
private String username;
private String name;
private String studentId; // 可选脱敏
private String email; // 可选脱敏
private String phone; // 可选脱敏
private String avatar;
private Integer role;
private Integer status;
}
6.3.3 SQL注入防护
防护措施:
- 使用MyBatis-Plus的参数化查询
- 使用
#{}占位符而非${} - 输入参数校验
安全查询示例:
// 安全:使用参数化查询
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(@Param("username") String username);
// 危险:字符串拼接(禁止使用)
@Select("SELECT * FROM user WHERE username = '${username}'")
User selectByUsername(@Param("username") String username);
6.3.4 XSS攻击防护
防护措施:
- 前端输入校验和转义
- 后端输出转义
- Content-Security-Policy头
输入校验:
@NotBlank(message = "活动名称不能为空")
@Size(max = 100, message = "活动名称不能超过100个字符")
@Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9\\s]+$", message = "活动名称包含非法字符")
private String title;
6.4 接口安全设计
6.4.1 CORS配置
跨域配置:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
6.4.2 请求频率限制
限流策略:
- 登录接口:5次/分钟
- 报名接口:10次/分钟
- 其他接口:100次/分钟
限流实现(使用Redis):
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String key = "rate_limit:" + request.getRemoteAddr() + ":" + request.getRequestURI();
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
if (count > 100) {
response.setStatus(429);
return false;
}
return true;
}
}
6.4.3 CSRF防护
防护措施:
- 使用JWT Token(无状态,天然防护CSRF)
- SameSite Cookie属性
- 验证Referer头
SameSite Cookie配置:
server.servlet.session.cookie.same-site=strict
第七章 性能设计
7.1 数据库性能优化
7.1.1 索引优化策略
索引设计原则:
- 为频繁查询的字段创建索引
- 为JOIN操作的外键字段创建索引
- 为WHERE条件、ORDER BY、GROUP BY字段创建索引
- 避免过度索引,影响写入性能
索引优化示例:
-- 活动表索引
CREATE INDEX idx_status ON activity(status);
CREATE INDEX idx_start_time ON activity(start_time);
CREATE INDEX idx_admin_id ON activity(admin_id);
-- 报名表索引
CREATE UNIQUE INDEX uk_user_activity ON registration(user_id, activity_id);
CREATE INDEX idx_activity_id ON registration(activity_id);
CREATE UNIQUE INDEX uk_ticket_code ON registration(ticket_code);
-- 评价表索引
CREATE UNIQUE INDEX uk_user_activity ON review(user_id, activity_id);
CREATE INDEX idx_activity_id ON review(activity_id);
7.1.2 查询优化方案
分页查询优化:
// 使用MyBatis-Plus分页插件
Page<Activity> page = new Page<>(current, size);
IPage<ActivityVO> result = activityMapper.selectActivityPage(page, params);
复杂查询优化:
-- 使用子查询优化
SELECT a.*, COUNT(r.id) as registration_count
FROM activity a
LEFT JOIN registration r ON a.id = r.activity_id AND r.status != 0
WHERE a.deleted = 0
GROUP BY a.id
ORDER BY a.start_time DESC;
7.1.3 分页查询设计
分页参数:
- current:当前页码(从1开始)
- size:每页数量(默认10,最大100)
分页响应:
{
"records": [],
"total": 100,
"pages": 10,
"current": 1,
"size": 10
}
7.2 缓存设计
缓存策略:使用Redis缓存热点数据
缓存数据类型:
- 活动列表(缓存5分钟)
- 活动详情(缓存10分钟)
- 用户信息(缓存30分钟)
- Token黑名单(缓存至Token过期)
缓存实现:
@Service
public class ActivityServiceImpl implements ActivityService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Cacheable(value = "activity", key = "#id")
public ActivityVO getActivityById(Long id) {
// ...
}
@CacheEvict(value = "activity", key = "#activity.id")
public void updateActivity(Activity activity) {
// ...
}
}
7.3 并发控制设计
7.3.1 报名并发控制
乐观锁实现:
UPDATE activity
SET current_participants = current_participants + 1,
version = version + 1
WHERE id = ?
AND current_participants < max_participants
AND version = ?;
乐观锁流程:
sequenceDiagram
participant User as 用户
participant Service as 服务
participant DB as 数据库
User->>Service: 发起报名
Service->>DB: 查询活动信息(version=1)
DB-->>Service: 返回活动
Service->>Service: 验证报名条件
Service->>DB: 更新报名人数<br/>WHERE version=1
alt 更新成功
DB-->>Service: 更新成功(version=2)
Service-->>User: 报名成功
else 更新失败
DB-->>Service: 更新失败
Service->>Service: 重新查询活动
Service->>DB: 查询活动信息(version=2)
Service->>DB: 更新报名人数<br/>WHERE version=2
DB-->>Service: 更新成功
Service-->>User: 报名成功
end
7.3.2 签到并发控制
唯一约束防止重复签到:
CREATE UNIQUE INDEX uk_registration_id ON check_in(registration_id);
签到流程:
@Transactional
public void checkIn(Long registrationId) {
// 检查是否已签到(唯一约束保证)
CheckIn existing = checkInMapper.selectOne(registrationId);
if (existing != null) {
throw new BusinessException(4002, "已签到");
}
// 创建签到记录
CheckIn checkIn = new CheckIn();
checkIn.setRegistrationId(registrationId);
checkInMapper.insert(checkIn);
// 更新报名状态
Registration registration = registrationMapper.selectById(registrationId);
registration.setStatus(2); // 已签到
registrationMapper.updateById(registration);
}
7.4 文件处理优化
7.4.1 PDF生成优化
异步生成PDF:
@Async
public CompletableFuture<String> generateTicketPdfAsync(Registration registration) {
String pdfUrl = pdfUtil.generateTicketPdf(registration);
return CompletableFuture.completedFuture(pdfUrl);
}
PDF模板缓存:
@Component
public class PdfTemplateCache {
private final Map<String, byte[]> templateCache = new ConcurrentHashMap<>();
public byte[] getTemplate(String templateName) {
return templateCache.computeIfAbsent(templateName, name -> {
// 加载PDF模板
return loadPdfTemplate(name);
});
}
}
7.4.2 文件上传下载优化
文件存储策略:
- 本地存储:开发环境
- 云存储:生产环境(阿里云OSS、腾讯云COS)
文件压缩:
@PostMapping("/upload")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file) {
// 文件大小限制
if (file.getSize() > 5 * 1024 * 1024) {
return Result.error(400, "文件大小不能超过5MB");
}
// 文件类型校验
String contentType = file.getContentType();
if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType)) {
return Result.error(400, "仅支持JPG和PNG格式");
}
// 压缩图片
byte[] compressed = ImageUtil.compress(file.getBytes());
String url = fileService.upload(compressed, file.getOriginalFilename());
return Result.success(url);
}
第八章 异常处理设计
8.1 异常分类
异常类型:
| 异常类型 | 说明 | 处理方式 |
|---|---|---|
| 系统异常 | 运行时异常、数据库异常等 | 全局异常处理器捕获 |
| 业务异常 | 业务规则违反(如已报名、人数已满) | 抛出BusinessException |
| 参数异常 | 请求参数校验失败 | 返回400错误 |
| 认证异常 | 未认证、Token过期 | 返回401错误 |
| 权限异常 | 无权限访问 | 返回403错误 |
8.2 异常处理机制
自定义业务异常:
@Getter
public class BusinessException extends RuntimeException {
private final Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
}
全局异常处理器:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 业务异常
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
// 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, message);
}
// 认证异常
@ExceptionHandler(AccessDeniedException.class)
public Result<?> handleAccessDeniedException(AccessDeniedException e) {
return Result.error(403, "无权限访问");
}
// 系统异常
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
log.error("系统异常", e);
return Result.error(500, "服务器内部错误");
}
}
8.3 错误码设计
错误码规范:
- 1xxx:用户相关错误
- 2xxx:活动相关错误
- 3xxx:报名相关错误
- 4xxx:签到相关错误
- 5xxx:评价相关错误
错误码定义:
public enum ResultCode {
// 通用错误码
SUCCESS(200, "success"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未认证或Token已过期"),
FORBIDDEN(403, "无权限访问"),
NOT_FOUND(404, "资源不存在"),
CONFLICT(409, "业务冲突"),
INTERNAL_ERROR(500, "服务器内部错误"),
// 用户相关错误码 (1xxx)
USER_NOT_FOUND(1001, "用户不存在"),
USER_ALREADY_EXISTS(1002, "用户已存在"),
PASSWORD_ERROR(1003, "密码错误"),
USERNAME_OR_PASSWORD_ERROR(1004, "用户名或密码错误"),
STUDENT_ID_ALREADY_EXISTS(1005, "学号已存在"),
// 活动相关错误码 (2xxx)
ACTIVITY_NOT_FOUND(2001, "活动不存在"),
ACTIVITY_ALREADY_STARTED(2002, "活动已开始,无法取消报名"),
ACTIVITY_TIME_CONFLICT(2003, "活动时间冲突"),
ACTIVITY_FULL(2004, "活动报名人数已满"),
// 报名相关错误码 (3xxx)
REGISTRATION_NOT_FOUND(3001, "报名记录不存在"),
ALREADY_REGISTERED(3002, "您已报名该活动"),
NOT_REGISTERED(3003, "您未报名该活动"),
// 签到相关错误码 (4xxx)
CHECKIN_FAILED(4001, "签到失败"),
ALREADY_CHECKED_IN(4002, "已签到"),
CHECKIN_TIME_EXPIRED(4003, "签到时间已过期"),
// 评价相关错误码 (5xxx)
REVIEW_ALREADY_EXISTS(5001, "您已评价该活动"),
REVIEW_NOT_FOUND(5002, "评价记录不存在"),
NOT_PARTICIPATED(5003, "您未参加该活动,无法评价");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
8.4 日志设计
8.4.1 日志级别定义
| 日志级别 | 说明 | 使用场景 |
|---|---|---|
| ERROR | 错误 | 系统错误、异常 |
| WARN | 警告 | 潜在问题、业务异常 |
| INFO | 信息 | 重要业务操作 |
| DEBUG | 调试 | 调试信息(生产环境关闭) |
8.4.2 日志格式设计
日志格式:
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
日志示例:
2026-01-15 10:00:00 [http-nio-8080-exec-1] INFO c.c.a.controller.AuthController - 用户登录成功: student001
2026-01-15 10:00:01 [http-nio-8080-exec-2] ERROR c.c.a.exception.GlobalExceptionHandler - 业务异常: 活动报名人数已满
8.4.3 日志存储策略
日志文件配置:
logging:
file:
name: logs/campus-activity.log
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
total-size-cap: 1GB
日志保留策略:
- 单个日志文件最大10MB
- 保留最近30天的日志
- 日志总大小不超过1GB
第九章 测试设计
9.1 测试策略
测试类型:
| 测试类型 | 测试内容 | 测试工具 |
|---|---|---|
| 单元测试 | Service层业务逻辑 | JUnit 5, Mockito |
| 集成测试 | 接口集成测试 | TestContainers, MockMvc |
| 接口测试 | API接口功能测试 | Postman, Knife4j |
测试覆盖率目标:
- Service层:≥ 80%
- Controller层:≥ 70%
- Mapper层:≥ 60%
9.2 测试用例设计
9.2.1 认证模块测试用例
| 用例编号 | 测试场景 | 输入数据 | 预期结果 |
|---|---|---|---|
| AUTH-001 | 用户注册成功 | username: "test001", password: "123456", name: "测试" | 注册成功,返回200 |
| AUTH-002 | 用户名已存在 | username: "admin", password: "123456", name: "测试" | 注册失败,返回1002 |
| AUTH-003 | 学号已存在 | username: "test002", studentId: "2021000001" | 注册失败,返回1005 |
| AUTH-004 | 用户登录成功 | username: "admin", password: "admin123" | 登录成功,返回Token |
| AUTH-005 | 密码错误 | username: "admin", password: "wrongpassword" | 登录失败,返回1004 |
| AUTH-006 | Token刷新成功 | refreshToken: 有效的refreshToken | 刷新成功,返回新Token |
| AUTH-007 | Token过期 | refreshToken: 过期的refreshToken | 刷新失败,返回401 |
9.2.2 活动管理模块测试用例
| 用例编号 | 测试场景 | 输入数据 | 预期结果 |
|---|---|---|---|
| ACT-001 | 创建活动成功 | title: "测试活动", startTime: "2026-02-01 10:00:00" | 创建成功,返回活动ID |
| ACT-002 | 活动时间冲突 | startTime: "2026-01-15 19:00:00", endTime: "2026-01-15 22:00:00" | 创建失败,返回2003 |
| ACT-003 | 查询活动列表 | status: 1, current: 1, size: 10 | 返回活动列表 |
| ACT-004 | 查询活动详情 | id: 1 | 返回活动详情 |
| ACT-005 | 活动不存在 | id: 99999 | 返回404 |
| ACT-006 | 更新活动成功 | id: 1, title: "更新后的活动" | 更新成功 |
| ACT-007 | 删除活动成功 | id: 1 | 删除成功 |
9.2.3 报名管理模块测试用例
| 用例编号 | 测试场景 | 输入数据 | 预期结果 |
|---|---|---|---|
| REG-001 | 报名成功 | activityId: 1 | 报名成功,生成电子票 |
| REG-002 | 活动不存在 | activityId: 99999 | 报名失败,返回2001 |
| REG-003 | 已报名该活动 | activityId: 1(已报名) | 报名失败,返回3002 |
| REG-004 | 报名人数已满 | activityId: 1(已满) | 报名失败,返回2004 |
| REG-005 | 时间冲突 | activityId: 2(时间冲突) | 报名失败,返回2003 |
| REG-006 | 取消报名成功 | id: 1 | 取消成功 |
| REG-007 | 活动已开始 | id: 1(活动已开始) | 取消失败,返回2002 |
9.2.4 签到管理模块测试用例
| 用例编号 | 测试场景 | 输入数据 | 预期结果 |
|---|---|---|---|
| CHK-001 | 生成签到二维码 | activityId: 1 | 生成成功,返回二维码URL |
| CHK-002 | 扫码签到成功 | qrCodeContent: 有效的二维码 | 签到成功 |
| CHK-003 | 二维码无效 | qrCodeContent: "invalid" | 签到失败,返回400 |
| CHK-004 | 未报名该活动 | qrCodeContent: 有效的二维码(未报名) | 签到失败,返回3003 |
| CHK-005 | 已签到 | qrCodeContent: 有效的二维码(已签到) | 签到失败,返回4002 |
| CHK-006 | 签到时间已过期 | qrCodeContent: 有效的二维码(已过期) | 签到失败,返回4003 |
| CHK-007 | 管理员扫票签到 | activityId: 1, ticketCode: "TICKET202601150001" | 签到成功 |
9.2.5 评价管理模块测试用例
| 用例编号 | 测试场景 | 输入数据 | 预期结果 |
|---|---|---|---|
| REV-001 | 提交评价成功 | activityId: 1, rating: 5, content: "很好" | 评价成功 |
| REV-002 | 已评价该活动 | activityId: 1(已评价) | 评价失败,返回5001 |
| REV-003 | 未参加该活动 | activityId: 1(未参加) | 评价失败,返回5003 |
| REV-004 | 评分超出范围 | activityId: 1, rating: 6 | 参数校验失败 |
| REV-005 | 查询活动评价列表 | activityId: 1 | 返回评价列表 |
| REV-006 | 查询我的评价列表 | - | 返回我的评价列表 |
第十章 部署设计
10.1 部署架构图
graph TB
subgraph "外网 Internet"
User[用户浏览器]
end
subgraph "DMZ区"
Firewall[防火墙]
Nginx[Nginx<br/>反向代理]
end
subgraph "内网 Intranet"
AppServer[应用服务器<br/>Spring Boot<br/>端口:8080]
DBServer[数据库服务器<br/>MySQL<br/>端口:3306]
FileServer[文件服务器<br/>静态资源]
end
User --> Firewall
Firewall --> Nginx
Nginx --> AppServer
Nginx --> FileServer
AppServer --> DBServer
10.2 部署步骤
10.2.1 数据库部署
1. 安装MySQL:
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install mysql-server
# CentOS/RHEL
sudo yum install mysql-server
2. 创建数据库:
CREATE DATABASE campus_activity
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
3. 执行建表脚本:
mysql -u root -p campus_activity < init.sql
4. 创建数据库用户:
CREATE USER 'campus_user'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON campus_activity.* TO 'campus_user'@'%';
FLUSH PRIVILEGES;
10.2.2 后端部署
1. 打包应用:
cd server
mvn clean package -DskipTests
2. 上传jar包:
scp target/campus-activity-system-1.0.0.jar user@server:/opt/app/
3. 创建启动脚本:
#!/bin/bash
APP_NAME="campus-activity-system"
JAR_NAME="$APP_NAME-1.0.0.jar"
PID=$(ps -ef | grep $JAR_NAME | grep -v grep | awk '{print $2}')
if [ -n "$PID" ]; then
echo "Stopping $APP_NAME (PID: $PID)..."
kill -15 $PID
sleep 5
fi
echo "Starting $APP_NAME..."
nohup java -jar $JAR_NAME \
--spring.profiles.active=prod \
> /dev/null 2>&1 &
echo "$APP_NAME started successfully!"
4. 配置systemd服务:
[Unit]
Description=Campus Activity System
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/app
ExecStart=/usr/bin/java -jar /opt/app/campus-activity-system-1.0.0.jar --spring.profiles.active=prod
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
5. 启动服务:
sudo systemctl daemon-reload
sudo systemctl enable campus-activity
sudo systemctl start campus-activity
sudo systemctl status campus-activity
10.2.3 前端部署
1. 打包前端:
cd web
npm run build
2. 上传静态文件:
scp -r dist/* user@server:/var/www/campus-activity/
3. 配置Nginx:
server {
listen 80;
server_name campus.example.com;
# 前端静态文件
location / {
root /var/www/campus-activity;
index index.html;
try_files $uri $uri/ /index.html;
}
# 后端API代理
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
4. 重启Nginx:
sudo nginx -t
sudo systemctl reload nginx
10.3 配置管理
10.3.1 开发环境配置
application-dev.yml:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/campus_activity?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
jwt:
secret: dev-secret-key-change-in-production
expiration: 7200000
refresh-expiration: 604800000
logging:
level:
root: INFO
com.campus.activity: DEBUG
10.3.2 测试环境配置
application-test.yml:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://test-db-server:3306/campus_activity?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: campus_test
password: test_password
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
jwt:
secret: test-secret-key
expiration: 7200000
refresh-expiration: 604800000
logging:
level:
root: INFO
com.campus.activity: DEBUG
file:
name: logs/campus-activity-test.log
10.3.3 生产环境配置
application-prod.yml:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://prod-db-server:3306/campus_activity?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: campus_prod
password: ${DB_PASSWORD}
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
jwt:
secret: ${JWT_SECRET}
expiration: 7200000
refresh-expiration: 604800000
logging:
level:
root: WARN
com.campus.activity: INFO
file:
name: /var/log/campus-activity/campus-activity.log
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
total-size-cap: 1GB
环境变量配置:
# /etc/profile.d/campus-activity.sh
export DB_PASSWORD=your_secure_password
export JWT_SECRET=your_jwt_secret_key
附录
附录A 完整数据库建表SQL脚本
-- 创建数据库
CREATE DATABASE IF NOT EXISTS `campus_activity`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
USE `campus_activity`;
-- 创建用户表
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL COMMENT '登录用户名',
`password` VARCHAR(255) NOT NULL COMMENT '密码',
`name` VARCHAR(50) NOT NULL COMMENT '真实姓名',
`student_id` VARCHAR(20) DEFAULT NULL COMMENT '学号',
`email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
`role` TINYINT NOT NULL DEFAULT 0 COMMENT '角色:0-学生,1-管理员',
`status` TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_student_id` (`student_id`),
KEY `idx_role` (`role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
-- 创建活动表
CREATE TABLE `activity` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '活动ID',
`title` VARCHAR(100) NOT NULL COMMENT '活动名称',
`description` TEXT DEFAULT NULL COMMENT '活动简介',
`cover_image` VARCHAR(255) DEFAULT NULL COMMENT '封面图片',
`start_time` DATETIME NOT NULL COMMENT '开始时间',
`end_time` DATETIME NOT NULL COMMENT '结束时间',
`registration_deadline` DATETIME DEFAULT NULL COMMENT '报名截止时间',
`location` VARCHAR(200) NOT NULL COMMENT '活动地点',
`max_participants` INT NOT NULL COMMENT '报名人数上限',
`current_participants` INT DEFAULT 0 COMMENT '当前报名人数',
`status` TINYINT DEFAULT 0 COMMENT '状态:0-未开始,1-报名中,2-进行中,3-已结束',
`category` VARCHAR(50) DEFAULT NULL COMMENT '活动分类',
`admin_id` BIGINT NOT NULL COMMENT '创建者ID',
`qr_code` VARCHAR(255) DEFAULT NULL COMMENT '签到二维码',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除',
`version` INT DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`),
KEY `idx_status` (`status`),
KEY `idx_start_time` (`start_time`),
KEY `idx_admin_id` (`admin_id`),
CONSTRAINT `fk_activity_admin` FOREIGN KEY (`admin_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='活动表';
-- 创建报名表
CREATE TABLE `registration` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '报名ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`activity_id` BIGINT NOT NULL COMMENT '活动ID',
`ticket_code` VARCHAR(100) DEFAULT NULL COMMENT '电子票唯一码',
`ticket_pdf_url` VARCHAR(255) DEFAULT NULL COMMENT '电子票PDF地址',
`status` TINYINT DEFAULT 1 COMMENT '状态:0-已取消,1-已报名,2-已签到',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '报名时间',
`updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`canceled_at` DATETIME DEFAULT NULL COMMENT '取消时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_activity` (`user_id`, `activity_id`),
UNIQUE KEY `uk_ticket_code` (`ticket_code`),
KEY `idx_activity_id` (`activity_id`),
CONSTRAINT `fk_registration_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `fk_registration_activity` FOREIGN KEY (`activity_id`) REFERENCES `activity` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='报名表';
-- 创建签到表
CREATE TABLE `check_in` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '签到ID',
`registration_id` BIGINT NOT NULL COMMENT '报名ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`activity_id` BIGINT NOT NULL COMMENT '活动ID',
`check_in_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '签到时间',
`check_in_method` TINYINT DEFAULT 0 COMMENT '签到方式:0-扫码,1-管理员代签',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_registration_id` (`registration_id`),
KEY `idx_activity_id` (`activity_id`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `fk_checkin_registration` FOREIGN KEY (`registration_id`) REFERENCES `registration` (`id`),
CONSTRAINT `fk_checkin_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `fk_checkin_activity` FOREIGN KEY (`activity_id`) REFERENCES `activity` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='签到表';
-- 创建评价表
CREATE TABLE `review` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '评价ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`activity_id` BIGINT NOT NULL COMMENT '活动ID',
`rating` TINYINT NOT NULL COMMENT '评分(1-5)',
`content` TEXT DEFAULT NULL COMMENT '评论内容',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '评价时间',
`updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_activity` (`user_id`, `activity_id`),
KEY `idx_activity_id` (`activity_id`),
CONSTRAINT `fk_review_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `fk_review_activity` FOREIGN KEY (`activity_id`) REFERENCES `activity` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='评价表';
-- 插入测试管理员账号(密码:admin123,BCrypt加密)
INSERT INTO `user` (`username`, `password`, `name`, `role`) VALUES
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iKXP6X3SqmI8Q0WJLoUbWAHZJ.5i', '系统管理员', 1);
附录B 关键业务流程时序图
B.1 用户认证流程时序图
sequenceDiagram
autonumber
participant Client as 客户端
participant Controller as AuthController
participant Service as AuthService
participant Security as SecurityConfig
participant JwtProvider as JwtTokenProvider
participant Mapper as UserMapper
participant DB as MySQL数据库
Note over Client,DB: 用户注册流程
Client->>Controller: POST /api/v1/auth/register
Controller->>Service: register(request)
Service->>Mapper: 检查用户名是否存在
Mapper->>DB: SELECT COUNT(*) FROM user WHERE username = ?
DB-->>Mapper: 返回数量
Mapper-->>Service: 返回数量
alt 用户名已存在
Service-->>Controller: 抛出异常 USER_ALREADY_EXISTS
Controller-->>Client: 400 错误
else 用户名不存在
Service->>Mapper: 检查学号是否存在
Mapper->>DB: SELECT COUNT(*) FROM user WHERE student_id = ?
DB-->>Mapper: 返回数量
Mapper-->>Service: 返回数量
alt 学号已存在
Service-->>Controller: 抛出异常 STUDENT_ID_ALREADY_EXISTS
Controller-->>Client: 400 错误
else 学号不存在
Service->>Service: 创建用户对象 passwordEncoder.encode()
Service->>Mapper: insert(user)
Mapper->>DB: INSERT INTO user(...)
DB-->>Mapper: 返回插入ID
Mapper-->>Service: 返回结果
Service-->>Controller: 注册成功
Controller-->>Client: 200 成功
end
end
Note over Client,DB: 用户登录流程
Client->>Controller: POST /api/v1/auth/login
Controller->>Security: authenticationManager.authenticate()
Security->>Mapper: loadUserByUsername()
Mapper->>DB: SELECT * FROM user WHERE username = ?
DB-->>Mapper: 返回用户信息
Mapper-->>Security: 返回UserDetails
Security->>Security: 验证密码 passwordEncoder.matches()
alt 密码错误
Security-->>Controller: 抛出异常
Controller-->>Client: 401 未授权
else 密码正确
Security-->>Controller: 认证成功
Controller->>Service: login(request)
Service->>Mapper: selectById(username)
Mapper->>DB: SELECT * FROM user WHERE username = ?
DB-->>Mapper: 返回用户信息
Mapper-->>Service: 返回User对象
Service->>JwtProvider: generateToken(userId, username, role)
JwtProvider-->>Service: 返回accessToken
Service->>JwtProvider: generateRefreshToken(userId)
JwtProvider-->>Service: 返回refreshToken
Service-->>Controller: 返回LoginResponse
Controller-->>Client: 200 成功
end
B.2 活动报名流程时序图
sequenceDiagram
autonumber
participant Client as 客户端
participant Controller as RegistrationController
participant Service as RegistrationService
participant ActMapper as ActivityMapper
participant RegMapper as RegistrationMapper
participant Auth as AuthService
participant PdfUtil as PdfUtil
participant DB as MySQL数据库
Note over Client,DB: 报名活动流程
Client->>Controller: POST /api/v1/registrations
Controller->>Service: register(request)
Service->>Auth: getCurrentUser()
Auth-->>Service: 返回当前用户
Service->>ActMapper: selectById(activityId)
ActMapper->>DB: SELECT * FROM activity WHERE id = ?
DB-->>ActMapper: 返回活动信息
ActMapper-->>Service: 返回Activity对象
alt 活动不存在或已删除
Service-->>Controller: 抛出异常 ACTIVITY_NOT_FOUND
Controller-->>Client: 404 未找到
else 活动存在
alt 活动不在报名中
Service-->>Controller: 抛出异常 BAD_REQUEST
Controller-->>Client: 400 错误
else 活动在报名中
alt 报名已截止
Service-->>Controller: 抛出异常 BAD_REQUEST
Controller-->>Client: 400 错误
else 报名未截止
alt 报名人数已满
Service-->>Controller: 抛出异常 ACTIVITY_FULL
Controller-->>Client: 409 冲突
else 报名未满
Service->>RegMapper: 检查是否已报名
RegMapper->>DB: SELECT * FROM registration WHERE user_id = ? AND activity_id = ?
DB-->>RegMapper: 返回报名记录
RegMapper-->>Service: 返回Registration对象
alt 已报名该活动
Service-->>Controller: 抛出异常 ALREADY_REGISTERED
Controller-->>Client: 409 冲突
else 未报名该活动
Service->>Service: 检查时间冲突
alt 存在时间冲突
Service-->>Controller: 抛出异常 ACTIVITY_TIME_CONFLICT
Controller-->>Client: 409 冲突
else 无时间冲突
Service->>Service: 生成电子票码
Service->>RegMapper: insert(registration)
RegMapper->>DB: INSERT INTO registration(...)
DB-->>RegMapper: 返回插入ID
RegMapper-->>Service: 返回registrationId
Service->>ActMapper: 更新活动报名人数
ActMapper->>DB: UPDATE activity SET current_participants = current_participants + 1
DB-->>ActMapper: 返回更新结果
ActMapper-->>Service: 返回成功
Service->>PdfUtil: 生成电子票PDF
PdfUtil-->>Service: 返回PDF URL
Service-->>Controller: 返回RegistrationVO
Controller-->>Client: 200 成功
end
end
end
end
end
end
B.3 签到管理流程时序图
sequenceDiagram
autonumber
participant Client as 客户端
participant Controller as CheckInController
participant Service as CheckInService
participant Mapper as CheckInMapper
participant RegMapper as RegistrationMapper
participant ActMapper as ActivityMapper
participant Auth as AuthService
participant QrUtil as QrCodeUtil
participant DB as MySQL数据库
Note over Client,DB: 学生扫码签到流程
Client->>Controller: POST /api/v1/checkin/scan
Controller->>Service: scanCheckIn(request)
Service->>Auth: getCurrentUser()
Auth-->>Service: 返回当前用户
Service->>QrUtil: parseActivityIdFromQrCode(qrCodeContent)
QrUtil-->>Service: 返回activityId
alt 二维码无效
Service-->>Controller: 抛出异常 BAD_REQUEST
Controller-->>Client: 400 错误
else 二维码有效
Service->>ActMapper: selectById(activityId)
ActMapper->>DB: SELECT * FROM activity WHERE id = ?
DB-->>ActMapper: 返回活动信息
ActMapper-->>Service: 返回Activity对象
alt 活动不存在或已删除
Service-->>Controller: 抛出异常 ACTIVITY_NOT_FOUND
Controller-->>Client: 404 未找到
else 活动存在
Service->>Service: 检查签到时间
alt 签到时间已过期
Service-->>Controller: 抛出异常 CHECKIN_TIME_EXPIRED
Controller-->>Client: 400 错误
else 签到时间有效
Service->>RegMapper: 查询报名记录
RegMapper->>DB: SELECT * FROM registration WHERE user_id = ? AND activity_id = ? AND status = 1
DB-->>RegMapper: 返回报名记录
RegMapper-->>Service: 返回Registration对象
alt 未报名该活动
Service-->>Controller: 抛出异常 NOT_REGISTERED
Controller-->>Client: 400 错误
else 已报名该活动
Service->>Mapper: 检查是否已签到
Mapper->>DB: SELECT * FROM check_in WHERE registration_id = ?
DB-->>Mapper: 返回签到记录
Mapper-->>Service: 返回CheckIn对象
alt 已签到
Service-->>Controller: 抛出异常 ALREADY_CHECKED_IN
Controller-->>Client: 409 冲突
else 未签到
Service->>Mapper: insert(checkIn)
Mapper->>DB: INSERT INTO check_in(...)
DB-->>Mapper: 返回插入ID
Mapper-->>Service: 返回checkInId
Service->>RegMapper: 更新报名状态为已签到
RegMapper->>DB: UPDATE registration SET status = 2
DB-->>RegMapper: 返回更新结果
RegMapper-->>Service: 返回成功
Service-->>Controller: 返回CheckInVO
Controller-->>Client: 200 成功
end
end
end
end
end
附录C 核心类图设计
classDiagram
class AuthController {
-AuthService authService
+register(request)
+login(request)
+refreshToken(request)
+getCurrentUser()
+changePassword(request)
}
class ActivityController {
-ActivityService activityService
+pageActivities(page, params)
+getActivityById(id)
+createActivity(request)
+updateActivity(id, request)
+deleteActivity(id)
+getCalendarActivities(year, month)
+checkConflict(request)
}
class RegistrationController {
-RegistrationService registrationService
+register(request)
+cancelRegistration(id)
+getMyRegistrations(page, status)
+getActivityRegistrations(activityId, page)
+downloadTicketPdf(id)
}
class CheckInController {
-CheckInService checkInService
+generateQrCode(activityId)
+scanCheckIn(request)
+ticketCheckIn(request)
+getActivityCheckIns(activityId, page)
}
class ReviewController {
-ReviewService reviewService
+createReview(request)
+getActivityReviews(activityId, page)
+getMyReviews(page)
}
class StatisticsController {
-StatisticsService statisticsService
+getActivityStatistics(activityId)
+exportActivityData(activityId, format)
+getOverallStatistics()
}
class ActivityService {
<<interface>>
+pageActivities(page, params)
+getActivityById(id)
+createActivity(request, adminId)
+updateActivity(id, request, adminId)
+deleteActivity(id, adminId)
}
class RegistrationService {
<<interface>>
+register(userId, activityId)
+cancelRegistration(userId, registrationId)
}
class CheckInService {
<<interface>>
+generateQrCode(activityId, adminId)
+scanCheckIn(userId, qrCodeContent)
}
class ReviewService {
<<interface>>
+createReview(userId, request)
+getActivityReviews(activityId, page)
}
class StatisticsService {
<<interface>>
+getActivityStatistics(activityId)
+exportActivityData(activityId, format)
}
AuthController --> AuthService
ActivityController --> ActivityService
RegistrationController --> RegistrationService
CheckInController --> CheckInService
ReviewController --> ReviewService
StatisticsController --> StatisticsService
文档结束