基于 Spring Boot 的社团活动报名管理后端项目,面向校园社团 / 工作室,支持普通用户查看、报名、取消报名活动,管理员发布、修改、删除活动并查看报名情况。
| 类型 | 说明 |
|---|---|
| 语言 | Java 21 |
| 框架 | Spring Boot 4.1.0(Spring Framework 7.0) |
| 构建工具 | Maven(含 mvnw 包装器) |
| 数据库 | MySQL 8.0.46 |
| 持久层 | Spring Data JPA(Hibernate 7.4) |
| 安全框架 | Spring Security |
| 认证方式 | JWT(jjwt 0.13.0) |
| 密码加密 | BCrypt |
| 参数校验 | Spring Bean Validation(jakarta.validation) |
| 实体简化 | Lombok |
| 接口文档 | Apifox(见下文) |
| 版本管理 | Git |
JDK 21(见 pom.xml 中 java.version)。
MySQL 8.0.x(本机验证版本 8.0.46),数据库名 activity_management。
- 准备 MySQL 8.x,创建数据库
activity_management(可执行schema.sql初始化,见下节)。 - 修改
src/main/resources/application.properties中的数据库账号密码:spring.datasource.username=root spring.datasource.password=你的数据库密码
- 启动应用:
./mvnw spring-boot:run
- 应用监听
http://localhost:8080。 - 应用启动时
DataInitializer会自动创建管理员账号root(密码为配置项root.password),无需手工建管理员。
- 一键初始化脚本:
schema.sql(项目根目录),可直接执行,使用IF NOT EXISTS,可重复执行且不会影响已有数据。 - 脚本包含 4 张表:
users、activities、signins、operation_logs。 - 本脚本不创建 root 管理员(由应用启动时
DataInitializer创建,见上节)。 - 应用侧配置
spring.jpa.hibernate.ddl-auto=update,即使不手工执行脚本,首次启动也会自动建表;两份方式完全兼容。
src/main/resources/application.properties:
| 配置项 | 说明 |
|---|---|
server.port |
服务端口,默认 8080 |
spring.datasource.url |
MySQL 连接串,指向 activity_management 库 |
spring.datasource.username / password |
数据库账号密码 |
spring.jpa.hibernate.ddl-auto=update |
由 Hibernate 自动创建 / 更新表结构 |
spring.jpa.show-sql |
打印 SQL 日志(便于调试) |
jwt.secret |
JWT 签名密钥(生产环境应通过环境变量注入) |
root.password |
启动时自动创建的 root 管理员密码 |
- 公开链接(占位,导入后替换):
https://apifox.com/apidoc/shared/REPLACE_WITH_YOUR_SHARED_LINK - 生成方式:用 Apifox 新建项目 → 导入项目根目录的
apifox-openapi.yaml(OpenAPI 3.0 格式)→ 项目概览页"分享文档"生成公开链接 → 替换上方占位链接。 - 导入文件与代码保持同步,文档包含全部接口、权限说明、成功 / 失败响应示例以及完整业务流程示例。
共 4 张表(物理列名由 Hibernate 命名策略自动转为下划线小写):
| 表 | 说明 | 软删除 / 状态字段 | 关键索引 |
|---|---|---|---|
users |
用户(普通用户 + 管理员) | is_unregistered |
PK;name、email 唯一键 |
activities |
活动 | is_deleted |
PK;start_time、is_deleted |
signins |
报名记录 | is_cancelled |
PK;user_id、activity_id |
operation_logs |
操作日志(扩展数据) | - | PK;user_id |
设计要点:
- 无物理外键,通过业务逻辑保证数据一致性(符合需求建议)。
- 主要业务表支持软删除:活动
is_deleted、用户is_unregistered、报名is_cancelled。 - 活动冗余保存
current_participants报名人数,报名 / 取消时同步增减,并用事务保证一致。 - 活动报名截止、开始、结束时间均以
DATETIME(6)存储;capacity = -1表示不限人数。 - 常用查询字段(报名按用户 / 活动、活动按开始时间)已建索引。
说明:分类功能未单独建表,采用"活动不区分分类"的替代设计(详见"特殊设计说明")。
- 统一响应格式
Result:成功{ "code": 200, "message": "Success", "data": {} }code = 200;失败code != 200且message说明原因。 - 分页响应
PageResponse:{ "records": [], "total": 0, "page": 0, "size": 15 },page从 0 开始,分页大小固定为 15。 - 接口路径遵循 RESTful 语义,
/api为统一前缀;GET /api/activity(列表)、GET /api/activity/{id}(详情)公开访问,其余按角色鉴权。 - 主要接口一览:
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| POST | /api/auth/register |
公开 | 用户注册 |
| POST | /api/auth/login |
公开 | 用户登录,返回 JWT |
| GET | /api/activity |
公开 | 活动列表(分页,description 截断前 40 字符) |
| GET | /api/activity/{id} |
公开 | 活动详情 |
| POST | /api/activity/{id}/signin |
登录用户 | 报名活动 |
| DELETE | /api/activity/{id}/signin |
登录用户 | 取消自己的报名 |
| GET | /api/mysignins |
登录用户 | 我的报名记录(分页) |
| GET | /api/activity/{id}/signins |
管理员 | 某活动的报名名单(分页) |
| POST | /api/activity/create |
管理员 | 创建活动 |
| PUT | /api/activity/{id}/update |
管理员 | 修改活动 |
| DELETE | /api/activity/delete/{id} |
管理员 | 删除活动(软删除) |
| GET | /api/user/{id}/ |
管理员或本人 | 查看用户信息(不含密码) |
| GET | /api/user/{id}/unregister |
管理员或本人 | 注销用户 |
| GET | /api/admin/logs |
管理员 | 查看操作日志(分页) |
- 注册:密码经
BCryptPasswordEncoder加密后入库,默认角色为普通用户USER。 - 登录:
AuthService.login通过AuthenticationManager认证,成功后用JwtUtil生成 JWT(签名密钥取自jwt.secret,有效期 1 天)。 - Token 校验:
JwtAuthenticationFilter在每个请求中解析Authorization: Bearer <token>,校验签名与过期时间,并从数据库加载用户构造Authentication写入SecurityContext。 - 权限控制:
- 路径级:
SecurityConfig中公开接口(/api/auth/**、活动浏览)permitAll,其余要求认证。 - 方法级:
@EnableMethodSecurity+@PreAuthorize,如管理员接口hasRole('ADMIN')、登录接口isAuthenticated()。
- 路径级:
- 统一异常:Token 缺失 / 无效、权限不足时由安全配置或全局异常处理器返回统一
Result。
本项目使用 Spring Data JPA(而非需求文档技术栈表中所列的 MyBatis / MyBatis-Plus),原因如下:
- JPA 的实体映射、仓库抽象与 Spring Boot 集成度更高,能显著减少样板代码;
- 复杂查询同样具备清晰的表达方式:本项目中的"我的报名记录""活动报名名单"均使用自定义
@Query联表投影查询(SigninRepository),配合显式countQuery实现分页; - 派生查询(
findByUserIdAndActivityIdAndIsCancelledFalse等)用于多条件查询;软删除通过查询条件显式过滤(如a.isDeleted = false、s.isCancelled = false)。
使用 JPA 提供的能力覆盖:单表 CRUD、多条件查询、分页查询、联表查询 / 数据组装、软删除过滤。项目不依赖框架默认 CRUD 堆功能,报名 / 取消 / 删除等复杂业务逻辑集中在 Service 层。
- 角色由
users.is_admin区分,CustomUserDetails.getAuthorities()映射为ROLE_ADMIN/ROLE_USER。 - 普通用户无法访问任何管理员接口(方法级
hasRole('ADMIN')拦截,返回PERMISSION_DENIED)。 - 未登录用户只能访问注册、登录与活动浏览等公开接口。
- 报名、取消报名、我的报名记录等要求登录;身份一律取自 JWT(
@AuthenticationPrincipal),杜绝伪造他人身份。
报名活动(signinActivity):活动存在且未删除 → 未重复报名 → 名额未满(capacity != -1 时)→ 未过报名截止时间 → 报名人数 +1 并写入报名记录,全程事务。
取消报名(signoutActivity):活动存在且未删除 → 活动未开始 → 只能取消自己的报名(按 JWT 用户 ID 查询)→ 软取消(is_cancelled = true)并人数 -1,全程事务。
修改活动(updateActivity):活动存在且未删除 → 活动未开始 → 重新校验时间合法性 → 新容量不得小于当前报名人数。
删除活动(deleteActivity):软删除(is_deleted = true),并级联软取消该活动所有有效报名、释放名额。
注销用户(unregisterUser):软删除(is_unregistered = true),并级联软取消该用户所有有效报名、释放相应活动名额。
采用 AOP 注解式操作日志(行业常见的审计日志做法),业务代码无需侵入式调用:
- 定义
@LogOperation(action, targetType)注解,标注在需要审计的 Service 方法上(注册、登录、创建 / 修改 / 删除活动、报名 / 取消报名、注销用户)。 OperationLogAspect通过@Around环绕通知自动记录:操作人(取自 SecurityContext 的 JWT 身份)、操作类型、目标类型与目标 ID、操作结果(成功 / 失败及失败原因)、来源 IP、操作时间。- 切面以最高优先级(
@Order(HIGHEST_PRECEDENCE))包裹业务方法,日志在业务事务提交 / 回滚之后独立落库——即使业务失败也能记录,且日志写入异常不影响业务。 - 目标 ID 提取规则:优先取返回值实体的 id,其次取最后一个
Integer参数(如活动 id),再取实体参数。 - 日志写入
operation_logs表,管理员通过GET /api/admin/logs分页查看。
- 管理员是否可以报名活动:允许。管理员同时承担普通用户行为(报名 / 取消),便于演示与数据验证;权限控制上两者互不影响。
- 活动开始后是否允许取消报名:不允许。活动开始后名额已锁定,取消操作会被拒绝(
ACTIVITY_STARTED)。 - 活动开始后是否允许修改:不允许。避免已启动活动被改动造成混乱。
- 删除已有报名的活动时如何处理:软删除活动并级联软取消其全部有效报名记录,报名记录保留在表中(
is_cancelled = true),活动不再对外展示。选择此方案可完整保留历史数据。 - 分类功能:未实现分类模块。采用"活动不区分分类"的替代设计——活动列表支持按开始时间排序与分页浏览,不设置分类字段;如需扩展,可新增
categories表并通过activity_id ↔ category_id关联,代码结构已按此预留分层。
- 使用 Git 管理,提交信息采用
类型: 说明风格(如Feature:、Bugfix:),按功能模块拆分提交,不一次性提交整个项目。 - 已忽略
target/、IDE 配置等无关文件(见.gitignore)。
开发过程中使用 AI 辅助完成以下工作,核心设计(数据库、接口、权限、业务规则)均由本人理解并实现:
- 查询语法与框架用法(Spring Security、JWT、JPA 等);
- 排查报错(如数据库残留列导致注册失败、Token 校验失败等);
- 生成少量模板代码后自行修改,并理解每一处含义;
- 辅助整理本文档与接口文档。
- 数据库残留
last_signin_time列导致注册报错:旧版本实体字段变更后,表内残留了无默认值的 NOT NULL 列,插入用户时报Field 'last_signin_time' doesn't have a default value。通过ALTER TABLE users DROP COLUMN last_signin_time清理残留列解决。 - JWT 校验失败(Token 无效):
application.properties中jwt.secret值带引号,Properties加载后密钥含引号导致签名不匹配,统一后正常。 - 取消报名错误语义:原先未报名也提示"已取消",改为专用错误码
REGISTRATION_NOT_FOUND,语义更准确。 - 时区偏移:JDBC 连接串使用
serverTimezone=UTC,在非 UTC 环境下时间展示可能偏移,建议按部署环境调整时区配置。 - 持久层选型差异:需求技术栈表要求 MyBatis / MyBatis-Plus,本项目基于开发效率与查询表达力选择 Spring Data JPA,相关能力与理由见"持久层实现说明"。