# 校园活动组织与报名系统 - 软件设计说明书

项目名称: 校园活动组织与报名系统

编写团队: 第14组

文档版本: V1.0

编写日期: 2026年1月15日

完成时间: 2025年1月14日

---

## 文档修订历史

| 版本 | 修订日期 | 修订人 | 修订说明 |
|------|----------|--------|----------|
| V1.0 | 2026-01-15 | 第14组 | 初始版本，完成软件设计 |

---

## 目录

- [第一章 引言](#第一章-引言)
  - [1.1 编写目的](#11-编写目的)
  - [1.2 文档范围](#12-文档范围)
  - [1.3 术语定义](#13-术语定义)
  - [1.4 参考资料](#14-参考资料)
- [第二章 系统架构设计](#第二章-系统架构设计)
  - [2.1 总体架构](#21-总体架构)
  - [2.2 技术架构](#22-技术架构)
  - [2.3 部署架构](#23-部署架构)
  - [2.4 模块划分](#24-模块划分)
- [第三章 数据库设计](#第三章-数据库设计)
  - [3.1 概念结构设计](#31-概念结构设计)
  - [3.2 逻辑结构设计](#32-逻辑结构设计)
  - [3.3 物理结构设计](#33-物理结构设计)
  - [3.4 数据完整性约束](#34-数据完整性约束)
- [第四章 详细设计](#第四章-详细设计)
  - [4.1 认证模块设计](#41-认证模块设计)
  - [4.2 活动管理模块设计](#42-活动管理模块设计)
  - [4.3 报名管理模块设计](#43-报名管理模块设计)
  - [4.4 签到管理模块设计](#44-签到管理模块设计)
  - [4.5 评价管理模块设计](#45-评价管理模块设计)
  - [4.6 统计模块设计](#46-统计模块设计)
- [第五章 接口设计](#第五章-接口设计)
  - [5.1 接口设计规范](#51-接口设计规范)
  - [5.2 认证接口设计](#52-认证接口设计)
  - [5.3 活动接口设计](#53-活动接口设计)
  - [5.4 报名接口设计](#54-报名接口设计)
  - [5.5 签到接口设计](#55-签到接口设计)
  - [5.6 评价接口设计](#56-评价接口设计)
  - [5.7 统计接口设计](#57-统计接口设计)
- [第六章 安全设计](#第六章-安全设计)
  - [6.1 认证机制设计](#61-认证机制设计)
  - [6.2 权限控制设计](#62-权限控制设计)
  - [6.3 数据安全设计](#63-数据安全设计)
  - [6.4 接口安全设计](#64-接口安全设计)
- [第七章 性能设计](#第七章-性能设计)
  - [7.1 数据库性能优化](#71-数据库性能优化)
  - [7.2 缓存设计](#72-缓存设计)
  - [7.3 并发控制设计](#73-并发控制设计)
  - [7.4 文件处理优化](#74-文件处理优化)
- [第八章 异常处理设计](#第八章-异常处理设计)
  - [8.1 异常分类](#81-异常分类)
  - [8.2 异常处理机制](#82-异常处理机制)
  - [8.3 错误码设计](#83-错误码设计)
  - [8.4 日志设计](#84-日志设计)
- [第九章 测试设计](#第九章-测试设计)
  - [9.1 测试策略](#91-测试策略)
  - [9.2 测试用例设计](#92-测试用例设计)
- [第十章 部署设计](#第十章-部署设计)
  - [10.1 部署架构图](#101-部署架构图)
  - [10.2 部署步骤](#102-部署步骤)
  - [10.3 配置管理](#103-配置管理)
- [附录](#附录)
  - [附录A 完整数据库建表SQL脚本](#附录a-完整数据库建表sql脚本)
  - [附录B 关键业务流程时序图](#附录b-关键业务流程时序图)
  - [附录C 核心类图设计](#附录c-核心类图设计)

---

## 第一章 引言

### 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 | 应用程序编程接口 |
| PDF | 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 总体架构

系统采用经典的分层架构设计，遵循高内聚、低耦合的设计原则，确保系统的可维护性和可扩展性。

```mermaid
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
```

**架构层次说明**：

1. **表现层（Presentation Layer）**
   - 负责用户界面展示和用户交互
   - 使用Vue.js + TypeScript开发
   - 通过HTTP/HTTPS协议与后端通信

2. **应用层（Application Layer - Controller）**
   - 接收前端HTTP请求
   - 参数校验和转换
   - 调用业务逻辑层
   - 返回统一响应格式

3. **业务逻辑层（Business Logic Layer - Service）**
   - 实现核心业务逻辑
   - 事务管理
   - 业务规则验证
   - 调用数据访问层

4. **数据访问层（Data Access Layer - Mapper）**
   - 与数据库交互
   - SQL映射和执行
   - 结果集映射

5. **数据存储层（Data Storage Layer）**
   - MySQL数据库
   - 数据持久化存储

6. **安全层（Security Layer）**
   - JWT令牌认证
   - 基于角色的权限控制
   - 横切所有层

### 2.2 技术架构

#### 2.2.1 前端技术架构

```mermaid
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 后端技术架构

```mermaid
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 数据存储架构

```mermaid
graph LR
    A[MySQL 8.0+] --> B[InnoDB引擎]
    A --> C[utf8mb4字符集]
    A --> D[逻辑删除]
    A --> E[索引优化]
```

**数据库技术栈**：

| 技术 | 版本 | 说明 |
|------|------|------|
| MySQL | 8.0+ | 关系型数据库 |
| InnoDB | - | 存储引擎 |
| utf8mb4 | - | 字符集 |
| 逻辑删除 | - | 数据删除策略 |
| 索引 | - | 查询优化 |

### 2.3 部署架构

```mermaid
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
```

**部署架构说明**：

1. **客户端层**
   - PC浏览器（Chrome、Firefox、Edge等）
   - 移动浏览器（iOS Safari、Chrome Mobile）

2. **网络层**
   - HTTPS加密传输
   - 防火墙安全防护

3. **应用服务器**
   - Nginx：反向代理、负载均衡、静态文件服务
   - Spring Boot应用：后端API服务（端口8080）
   - 静态资源：Vue.js打包后的前端文件

4. **数据服务器**
   - MySQL数据库：数据持久化存储（端口3306）

### 2.4 模块划分

系统划分为以下功能模块：

```mermaid
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）。

```mermaid
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 | 学号 |
| email | 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 索引策略

**索引设计原则**：
1. 主键索引：所有表使用自增BIGINT主键
2. 唯一索引：业务唯一性约束（用户名、学号、电子票码等）
3. 普通索引：频繁查询字段（status、start_time、admin_id等）
4. 联合索引：多条件查询优化（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设计**：

```mermaid
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结构**：
```json
{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "userId": 1,
    "username": "student001",
    "role": 0,
    "iat": 1736620800,
    "exp": 1736628000
  }
}
```

**Refresh Token结构**：
```json
{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "userId": 1,
    "iat": 1736620800,
    "exp": 1737225600
  }
}
```

**认证流程**：
1. 用户登录成功后，服务端生成Access Token和Refresh Token
2. 客户端存储两个Token（localStorage或Cookie）
3. 每次请求携带Access Token（Header: Authorization: Bearer {token}）
4. 服务端验证Token有效性
5. Access Token过期时，使用Refresh Token获取新Access Token
6. Refresh Token过期时，用户需重新登录

#### 4.1.2 类结构设计

```mermaid
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

**加密流程**：
```mermaid
graph LR
    A[用户输入密码] --> B[生成随机盐值]
    B --> C[BCrypt哈希运算]
    C --> D[存储加密密码]
```

**加密特点**：
- 自动生成随机盐值，防止彩虹表攻击
- 可调整计算强度（cost factor），默认10
- 相同密码每次加密结果不同
- 验证时自动提取盐值进行比对

**示例代码**：
```java
// 加密
String encodedPassword = passwordEncoder.encode("123456");

// 验证
boolean matches = passwordEncoder.matches("123456", encodedPassword);
```

### 4.2 活动管理模块设计

#### 4.2.1 活动状态机设计

```mermaid
stateDiagram-v2
    [*] --> 草稿: 创建活动
    草稿 --> 报名中: 发布活动
    报名中 --> 进行中: 活动开始
    进行中 --> 已结束: 活动结束
    报名中 --> 已取消: 取消活动
    进行中 --> 已取消: 取消活动
    草稿 --> 已删除: 删除活动
    报名中 --> 已删除: 删除活动
    进行中 --> 已删除: 删除活动
    已结束 --> 已删除: 删除活动
    已取消 --> 已删除: 删除活动
    已结束 --> [*]
    已取消 --> [*]
    已删除 --> [*]
```

**状态说明**：

| 状态值 | 状态名称 | 说明 | 可执行操作 |
|--------|----------|------|-----------|
| 0 | 草稿 | 活动未发布，仅管理员可见 | 修改、发布、删除 |
| 1 | 报名中 | 活动已发布，学生可报名 | 修改、取消、删除 |
| 2 | 进行中 | 活动进行中 | 查看详情 |
| 3 | 已结束 | 活动已结束，学生可评价 | 查看详情、删除 |
| 4 | 已取消 | 活动已取消 | 查看详情、删除 |

**状态转换规则**：
1. 草稿 → 报名中：管理员发布活动
2. 报名中 → 进行中：到达活动开始时间（定时任务自动更新）
3. 进行中 → 已结束：到达活动结束时间（定时任务自动更新）
4. 报名中/进行中 → 已取消：管理员取消活动
5. 任意状态 → 已删除：管理员删除活动（逻辑删除）

#### 4.2.2 时间冲突检测算法

**冲突判断标准**：

两个活动时间区间 `[start1, end1]` 和 `[start2, end2]` 存在冲突的条件：

```
!(end1 < start2 || end2 < start1)
```

即：两个时间区间有交集。

**检测算法**：
```sql
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 <= ?)  -- 新活动完全包含旧活动
  )
```

**检测流程**：
```mermaid
graph TD
    A[输入活动时间] --> B[查询同地点活动]
    B --> C{遍历活动列表}
    C -->|有活动| D{时间区间有交集?}
    D -->|是| E[记录冲突活动]
    D -->|否| C
    C -->|无活动| F[无冲突]
    E --> G[返回冲突列表]
    F --> H[返回无冲突]
```

#### 4.2.3 类结构设计

```mermaid
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 报名流程设计

```mermaid
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[返回报名成功]
```

**报名验证规则**：
1. 活动必须存在且未删除
2. 活动状态必须为"报名中"
3. 当前时间必须在报名截止时间之前
4. 当前报名人数必须小于报名人数上限
5. 用户不能重复报名同一活动
6. 用户不能报名时间冲突的活动

#### 4.3.2 电子票生成算法

**电子票唯一码生成**：

格式：`TICKET{YYYYMMDD}{6位随机数}`

示例：`TICKET202601150001234`

**生成流程**：
```mermaid
graph LR
    A[获取当前日期] --> B[生成6位随机数]
    B --> C[拼接成电子票码]
    C --> D{检查唯一性}
    D -->|已存在| B
    D -->|不存在| E[返回电子票码]
```

**PDF电子票生成**：

使用iText库生成PDF文件，包含以下内容：
1. 活动信息：名称、时间、地点
2. 学生信息：姓名、学号
3. 电子票码
4. 二维码（包含电子票码）

**PDF布局设计**：
```
┌─────────────────────────────────────┐
│         校园活动电子票               │
├─────────────────────────────────────┤
│ 活动名称: 校园音乐节                │
│ 活动时间: 2026-01-15 19:00-22:00   │
│ 活动地点: 大礼堂                    │
│                                     │
│ 学生姓名: 张三                      │
│ 学    号: 2021000001                │
│                                     │
│ 电子票号: TICKET202601150001234     │
│                                     │
│        [二维码图片]                 │
├─────────────────────────────────────┤
│  请凭此票入场，请勿转借他人         │
└─────────────────────────────────────┘
```

#### 4.3.3 并发控制设计

**并发报名问题**：
- 多个用户同时报名同一活动
- 可能导致报名人数超过上限

**解决方案：使用数据库乐观锁**

**乐观锁实现**：
```sql
-- 报名时更新报名人数（使用version乐观锁）
UPDATE activity
SET current_participants = current_participants + 1,
    version = version + 1
WHERE id = ?
  AND current_participants < max_participants
  AND version = ?;
```

**并发控制流程**：
```mermaid
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 类结构设计

```mermaid
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
```

**二维码生成流程**：
```mermaid
graph TD
    A[管理员生成签到二维码] --> B[获取活动ID]
    B --> C[生成时间戳]
    C --> D[生成签名<br/>HMAC-SHA256]
    D --> E[拼接二维码内容]
    E --> F[使用ZXing生成二维码图片]
    F --> G[保存二维码图片]
    G --> H[返回二维码URL]
```

**签名生成算法**：
```java
String signature = HMACSHA256(
    secretKey,
    activityId + ":" + timestamp
);
```

**二维码解析流程**：
```mermaid
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 签到流程设计

**学生扫码签到流程**：
```mermaid
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[返回签到成功]
```

**管理员扫票签到流程**：
```mermaid
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 类结构设计

```mermaid
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 评分计算算法

**平均评分计算**：

```sql
SELECT AVG(rating) as average_rating,
       COUNT(*) as review_count
FROM review
WHERE activity_id = ?;
```

**评分分布统计**：

```sql
SELECT rating,
       COUNT(*) as count
FROM review
WHERE activity_id = ?
GROUP BY rating
ORDER BY rating;
```

**评分星级显示**：
- 1星：⭐
- 2星：⭐⭐
- 3星：⭐⭐⭐
- 4星：⭐⭐⭐⭐
- 5星：⭐⭐⭐⭐⭐

#### 4.5.2 类结构设计

```mermaid
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 统计指标计算

**活动统计指标**：

1. **报名数据**
   - 总报名人数：`COUNT(*) FROM registration WHERE activity_id = ? AND status != 0`

2. **签到数据**
   - 总签到人数：`COUNT(*) FROM check_in WHERE activity_id = ?`
   - 签到率：`签到人数 / 报名人数`

3. **评价数据**
   - 总评价人数：`COUNT(*) FROM review WHERE activity_id = ?`
   - 评价率：`评价人数 / 签到人数`
   - 平均评分：`AVG(rating) FROM review WHERE activity_id = ?`
   - 评分分布：各星级评价数量

**总体统计指标**：

1. **活动数据**
   - 总活动数：`COUNT(*) FROM activity WHERE deleted = 0`

2. **报名数据**
   - 总报名数：`COUNT(*) FROM registration WHERE status != 0`

3. **签到数据**
   - 总签到数：`COUNT(*) FROM check_in`

4. **评价数据**
   - 总评价数：`COUNT(*) FROM review`
   - 平均评分：`AVG(rating) FROM review`

5. **月度统计**
   - 按月份统计活动数和报名数

#### 4.6.2 数据导出设计

**导出格式**：Excel (.xlsx)

**导出内容**：

1. **活动报名数据导出**
   - 学生信息：姓名、学号、手机号、邮箱
   - 报名信息：报名时间、报名状态、电子票号
   - 签到信息：签到时间、签到方式

2. **活动签到数据导出**
   - 学生信息：姓名、学号
   - 签到信息：签到时间、签到方式

3. **活动评价数据导出**
   - 学生信息：姓名
   - 评价信息：评分、评论内容、评价时间

**导出流程**：
```mermaid
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 统一响应格式

**成功响应**：
```json
{
  "code": 200,
  "message": "success",
  "data": {},
  "timestamp": 1736620800000
}
```

**错误响应**：
```json
{
  "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`

**请求权限**：无需认证

**请求参数**：
```json
{
  "username": "student001",
  "password": "123456",
  "name": "张三",
  "studentId": "2021000001",
  "email": "zhangsan@example.com",
  "phone": "13800138000"
}
```

**响应示例**：
```json
{
  "code": 200,
  "message": "注册成功",
  "data": null,
  "timestamp": 1736620800000
}
```

#### 5.2.2 用户登录接口

**接口地址**：`POST /api/v1/auth/login`

**请求权限**：无需认证

**请求参数**：
```json
{
  "username": "student001",
  "password": "123456"
}
```

**响应示例**：
```json
{
  "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`

**请求权限**：无需认证

**请求参数**：
```json
{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

**响应示例**：
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 3600
  },
  "timestamp": 1736620800000
}
```

#### 5.2.4 获取用户信息接口

**接口地址**：`GET /api/v1/auth/me`

**请求权限**：需要认证

**请求头**：
```
Authorization: Bearer {accessToken}
```

**响应示例**：
```json
{
  "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 | 否 | 结束时间 | - |

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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)

**请求参数**：
```json
{
  "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": "文艺活动"
}
```

**响应示例**：
```json
{
  "code": 200,
  "message": "创建成功",
  "data": 1,
  "timestamp": 1736620800000
}
```

#### 5.3.4 更新活动接口

**接口地址**：`PUT /api/v1/activities/{id}`

**请求权限**：管理员 (ADMIN)

**路径参数**：
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 活动ID |

**请求参数**：
```json
{
  "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": "文艺活动"
}
```

**响应示例**：
```json
{
  "code": 200,
  "message": "更新成功",
  "data": null,
  "timestamp": 1736620800000
}
```

#### 5.3.5 删除活动接口

**接口地址**：`DELETE /api/v1/activities/{id}`

**请求权限**：管理员 (ADMIN)

**路径参数**：
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 活动ID |

**响应示例**：
```json
{
  "code": 200,
  "message": "删除成功",
  "data": null,
  "timestamp": 1736620800000
}
```

#### 5.3.6 日历视图接口

**接口地址**：`GET /api/v1/activities/calendar`

**请求权限**：无需认证

**请求参数**：
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| year | Integer | 是 | 年份 |
| month | Integer | 是 | 月份 |

**响应示例**：
```json
{
  "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)

**请求参数**：
```json
{
  "startTime": "2026-01-15T19:00:00",
  "endTime": "2026-01-15T22:00:00",
  "excludeActivityId": 1
}
```

**响应示例**：
```json
{
  "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`

**请求权限**：需要认证

**请求参数**：
```json
{
  "activityId": 1
}
```

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "code": 200,
  "message": "取消成功",
  "data": null,
  "timestamp": 1736620800000
}
```

#### 5.4.3 我的报名列表接口

**接口地址**：`GET /api/v1/registrations/my`

**请求权限**：需要认证

**请求参数**：
| 参数名 | 类型 | 必填 | 说明 | 默认值 |
|--------|------|------|------|--------|
| current | Long | 否 | 当前页码 | 1 |
| size | Long | 否 | 每页数量 | 10 |
| status | Integer | 否 | 报名状态 | - |

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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`

**请求权限**：需要认证

**请求参数**：
```json
{
  "qrCodeContent": "CHECKIN:1:1736928000:a1b2c3d4e5f6"
}
```

**响应示例**：
```json
{
  "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)

**请求参数**：
```json
{
  "activityId": 1,
  "ticketCode": "TICKET202601150001"
}
```

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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`

**请求权限**：需要认证

**请求参数**：
```json
{
  "activityId": 1,
  "rating": 5,
  "content": "非常精彩的活动！"
}
```

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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 |

**响应示例**：
```json
{
  "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)

**响应示例**：
```json
{
  "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生成**：
```java
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生成**：
```java
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验证流程**：
```java
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刷新流程**：
```mermaid
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 | 活动管理员 |

**权限配置**：
```java
@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 接口权限注解

**权限注解使用**：
```java
// 仅管理员可访问
@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个字符
- 建议包含字母和数字

**加密实现**：
```java
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
```

#### 6.3.2 敏感数据传输加密

**HTTPS加密**：
- 使用HTTPS协议传输所有数据
- SSL/TLS证书加密
- 防止中间人攻击

**敏感字段脱敏**：
```java
@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注入防护

**防护措施**：
1. 使用MyBatis-Plus的参数化查询
2. 使用`#{}`占位符而非`${}`
3. 输入参数校验

**安全查询示例**：
```java
// 安全：使用参数化查询
@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攻击防护

**防护措施**：
1. 前端输入校验和转义
2. 后端输出转义
3. Content-Security-Policy头

**输入校验**：
```java
@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配置

**跨域配置**：
```java
@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）：
```java
@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防护

**防护措施**：
1. 使用JWT Token（无状态，天然防护CSRF）
2. SameSite Cookie属性
3. 验证Referer头

**SameSite Cookie配置**：
```properties
server.servlet.session.cookie.same-site=strict
```

---

## 第七章 性能设计

### 7.1 数据库性能优化

#### 7.1.1 索引优化策略

**索引设计原则**：
1. 为频繁查询的字段创建索引
2. 为JOIN操作的外键字段创建索引
3. 为WHERE条件、ORDER BY、GROUP BY字段创建索引
4. 避免过度索引，影响写入性能

**索引优化示例**：
```sql
-- 活动表索引
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 查询优化方案

**分页查询优化**：
```java
// 使用MyBatis-Plus分页插件
Page<Activity> page = new Page<>(current, size);
IPage<ActivityVO> result = activityMapper.selectActivityPage(page, params);
```

**复杂查询优化**：
```sql
-- 使用子查询优化
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）

**分页响应**：
```json
{
  "records": [],
  "total": 100,
  "pages": 10,
  "current": 1,
  "size": 10
}
```

### 7.2 缓存设计

**缓存策略**：使用Redis缓存热点数据

**缓存数据类型**：
1. 活动列表（缓存5分钟）
2. 活动详情（缓存10分钟）
3. 用户信息（缓存30分钟）
4. Token黑名单（缓存至Token过期）

**缓存实现**：
```java
@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 报名并发控制

**乐观锁实现**：
```sql
UPDATE activity
SET current_participants = current_participants + 1,
    version = version + 1
WHERE id = ?
  AND current_participants < max_participants
  AND version = ?;
```

**乐观锁流程**：
```mermaid
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 签到并发控制

**唯一约束防止重复签到**：
```sql
CREATE UNIQUE INDEX uk_registration_id ON check_in(registration_id);
```

**签到流程**：
```java
@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**：
```java
@Async
public CompletableFuture<String> generateTicketPdfAsync(Registration registration) {
    String pdfUrl = pdfUtil.generateTicketPdf(registration);
    return CompletableFuture.completedFuture(pdfUrl);
}
```

**PDF模板缓存**：
```java
@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）

**文件压缩**：
```java
@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 异常处理机制

**自定义业务异常**：
```java
@Getter
public class BusinessException extends RuntimeException {
    private final Integer code;

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}
```

**全局异常处理器**：
```java
@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：评价相关错误

**错误码定义**：
```java
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 日志存储策略

**日志文件配置**：
```yaml
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 部署架构图

```mermaid
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**：
```bash
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install mysql-server

# CentOS/RHEL
sudo yum install mysql-server
```

**2. 创建数据库**：
```sql
CREATE DATABASE campus_activity
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
```

**3. 执行建表脚本**：
```bash
mysql -u root -p campus_activity < init.sql
```

**4. 创建数据库用户**：
```sql
CREATE USER 'campus_user'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON campus_activity.* TO 'campus_user'@'%';
FLUSH PRIVILEGES;
```

#### 10.2.2 后端部署

**1. 打包应用**：
```bash
cd server
mvn clean package -DskipTests
```

**2. 上传jar包**：
```bash
scp target/campus-activity-system-1.0.0.jar user@server:/opt/app/
```

**3. 创建启动脚本**：
```bash
#!/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服务**：
```ini
[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. 启动服务**：
```bash
sudo systemctl daemon-reload
sudo systemctl enable campus-activity
sudo systemctl start campus-activity
sudo systemctl status campus-activity
```

#### 10.2.3 前端部署

**1. 打包前端**：
```bash
cd web
npm run build
```

**2. 上传静态文件**：
```bash
scp -r dist/* user@server:/var/www/campus-activity/
```

**3. 配置Nginx**：
```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**：
```bash
sudo nginx -t
sudo systemctl reload nginx
```

### 10.3 配置管理

#### 10.3.1 开发环境配置

**application-dev.yml**：
```yaml
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**：
```yaml
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**：
```yaml
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
```

**环境变量配置**：
```bash
# /etc/profile.d/campus-activity.sh
export DB_PASSWORD=your_secure_password
export JWT_SECRET=your_jwt_secret_key
```

---

## 附录

### 附录A 完整数据库建表SQL脚本

```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 用户认证流程时序图

```mermaid
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 活动报名流程时序图

```mermaid
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 签到管理流程时序图

```mermaid
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 核心类图设计

```mermaid
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
```

---

**文档结束**