1517 lines
55 KiB
Markdown
1517 lines
55 KiB
Markdown
# 博客系统技术设计说明书
|
||
|
||
| 文档版本 | 修订日期 | 修订人 | 修订内容 |
|
||
| :------ | :------- | :----- | :------- |
|
||
| V1.0 | 2026-05-17 | — | 初始版本,完整技术设计 |
|
||
|
||
---
|
||
|
||
## 1. 项目概述
|
||
|
||
### 1.1 项目定位
|
||
|
||
基于需求文档 `blog-system-requirements.md` 定义的博客系统功能需求与非功能性需求,本技术设计文档将作为 AI 编程的唯一输入依据,指导系统从零开始的完整开发。
|
||
|
||
### 1.2 技术栈选型
|
||
|
||
| 层级 | 技术 | 版本/说明 |
|
||
| :--- | :--- | :-------- |
|
||
| 后端框架 | Spring Boot | 3.4.x(最新稳定版) |
|
||
| JDK | OpenJDK | 21(LTS) |
|
||
| 项目构建 | Maven | 3.9+ |
|
||
| 数据库 | MySQL | 8.0+ |
|
||
| 缓存 | Redis | 7.2.x(BSD 3-Clause 许可证,用于会话管理、验证码、限流计数) |
|
||
| ORM | MyBatis-Plus | 3.5.x |
|
||
| 认证鉴权 | Spring Security + JWT | 无状态令牌认证 |
|
||
| 接口文档 | SpringDoc OpenAPI | 2.x(Swagger UI) |
|
||
| 前端框架 | Vue.js | 3.x(Vite 构建) |
|
||
| UI 组件库 | Element Plus | 2.x |
|
||
| 富文本编辑器 | Tiptap(MIT 许可证)/ TinyMCE 社区版(LGPL,不使用 Premium) | 免费开源版 |
|
||
| 数据库迁移 | Flyway | 9.x |
|
||
|
||
### 1.3 项目结构(单体应用、前后端一体化构建)
|
||
|
||
整个项目为一个 Maven 多模块项目,前端构建产物集成到后端静态资源目录中,通过 `mvn package` 一次编译生成可直接部署的 FAT JAR。
|
||
|
||
```
|
||
blog-system/
|
||
├── pom.xml # 父 POM
|
||
├── blog-common/ # 公共模块
|
||
│ ├── pom.xml
|
||
│ └── src/main/java/com/blog/common/
|
||
│ ├── annotation/ # 自定义注解
|
||
│ ├── config/ # 公共配置
|
||
│ ├── constant/ # 常量定义
|
||
│ ├── enums/ # 枚举定义
|
||
│ ├── exception/ # 统一异常
|
||
│ ├── model/ # 公共模型(分页、统一响应)
|
||
│ └── util/ # 工具类
|
||
│
|
||
├── blog-framework/ # 框架核心模块
|
||
│ ├── pom.xml
|
||
│ └── src/main/java/com/blog/framework/
|
||
│ ├── config/ # Spring Boot 配置
|
||
│ ├── security/ # Spring Security + JWT
|
||
│ ├── interceptor/ # 拦截器
|
||
│ ├── aspect/ # AOP 切面
|
||
│ ├── filter/ # 过滤器
|
||
│ ├── handler/ # 全局异常处理
|
||
│ └── rateLimiter/ # 限流组件
|
||
│
|
||
├── blog-module-system/ # 系统管理模块
|
||
│ ├── pom.xml
|
||
│ └── src/main/java/com/blog/module/system/
|
||
│ ├── controller/
|
||
│ │ ├── admin/ # 后台管理接口
|
||
│ │ └── app/ # 前台用户接口
|
||
│ ├── service/
|
||
│ ├── mapper/
|
||
│ ├── entity/
|
||
│ └── dto/
|
||
│
|
||
├── blog-module-blog/ # 博客内容模块
|
||
│ ├── pom.xml
|
||
│ └── src/main/java/com/blog/module/blog/
|
||
│ ├── controller/
|
||
│ │ ├── admin/
|
||
│ │ ├── app/
|
||
│ │ └── api/ # 公开接口(无需认证)
|
||
│ ├── service/
|
||
│ ├── mapper/
|
||
│ ├── entity/
|
||
│ └── dto/
|
||
│
|
||
├── blog-module-comment/ # 评论模块
|
||
│ ├── pom.xml
|
||
│ └── src/main/java/com/blog/module/comment/
|
||
│ ├── controller/
|
||
│ ├── service/
|
||
│ ├── mapper/
|
||
│ ├── entity/
|
||
│ └── dto/
|
||
│
|
||
├── blog-admin/ # 后台管理 Web 模块
|
||
│ ├── pom.xml
|
||
│ └── src/main/
|
||
│ ├── java/com/blog/admin/
|
||
│ │ └── BlogAdminApplication.java
|
||
│ └── resources/
|
||
│ ├── application.yml
|
||
│ ├── application-dev.yml
|
||
│ └── application-prod.yml
|
||
│
|
||
└── blog-frontend/ # 前端工程(Vue 3)
|
||
├── package.json
|
||
├── vite.config.ts
|
||
├── index.html
|
||
└── src/
|
||
├── api/ # API 调用封装
|
||
├── assets/ # 静态资源
|
||
├── components/ # 公共组件
|
||
├── layouts/ # 布局组件
|
||
├── router/ # 路由
|
||
├── store/ # Pinia 状态管理
|
||
├── utils/ # 工具函数
|
||
├── views/ # 页面
|
||
│ ├── admin/ # 后台管理页面
|
||
│ └── front/ # 前台展示页面
|
||
└── App.vue
|
||
```
|
||
|
||
**前后端一体化构建流程**:
|
||
|
||
```
|
||
mvn clean package
|
||
```
|
||
|
||
执行上述命令时:
|
||
1. `blog-frontend` 模块通过 `frontend-maven-plugin` 自动安装 Node.js 依赖并执行 `npm run build`
|
||
2. 构建产物 (`dist/`) 通过 Maven 资源插件复制到 `blog-admin/src/main/resources/static/`
|
||
3. `blog-admin` 模块打包为 FAT JAR,内嵌 Tomcat,直接通过 `java -jar blog-admin.jar` 启动
|
||
|
||
---
|
||
|
||
## 2. 系统架构设计
|
||
|
||
### 2.1 分层架构
|
||
|
||
系统采用经典的四层架构,严格遵循各层职责单一原则:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Controller 层 │
|
||
│ ┌───────────────┐ ┌───────────────┐ │
|
||
│ │ admin/ │ │ app/ │ │ api/ │
|
||
│ │ 后台管理接口 │ │ 前台用户接口 │ │ 公开接口 │
|
||
│ └──────┬────────┘ └──────┬────────┘ └──────┬─────┘│
|
||
│ │ │ │ │
|
||
├─────────┼──────────────────┼──────────────────┼────────┤
|
||
│ │ DTO / VO 数据校验与转换 │
|
||
├─────────┼──────────────────┼──────────────────┼────────┤
|
||
│ ▼ ▼ ▼ │
|
||
│ Service 层 │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ 接口定义(Interface) → 实现类(Impl) │ │
|
||
│ │ 事务管理 @Transactional │ │
|
||
│ │ 业务校验 → 调用 Mapper → 返回结果 │ │
|
||
│ └──────────────────────┬──────────────────────────┘ │
|
||
│ │ │
|
||
├─────────────────────────┼─────────────────────────────┤
|
||
│ ▼ │
|
||
│ Mapper 层 │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ MyBatis-Plus BaseMapper 扩展 │ │
|
||
│ │ 自定义 XML / 注解 SQL │ │
|
||
│ └──────────────────────┬──────────────────────────┘ │
|
||
│ │ │
|
||
├─────────────────────────┼─────────────────────────────┤
|
||
│ ▼ │
|
||
│ Entity 层 │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ 数据库表映射实体(@TableName, @TableId) │ │
|
||
│ │ 逻辑删除 @TableLogic │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 请求处理链路
|
||
|
||
```
|
||
Client Request
|
||
│
|
||
▼
|
||
Filter Chain: CorsFilter → RateLimitFilter → XssFilter
|
||
│
|
||
▼
|
||
DispatcherServlet
|
||
│
|
||
▼
|
||
Interceptor: LoginInterceptor(校验 JWT Token)
|
||
│
|
||
▼
|
||
AOP Aspect: LogAspect(操作日志)→ RateLimitAspect(限流)
|
||
│
|
||
▼
|
||
Controller → 参数校验(@Valid / @Validated)
|
||
│
|
||
▼
|
||
Service → 业务处理
|
||
│
|
||
▼
|
||
Mapper → 数据库操作
|
||
│
|
||
▼
|
||
统一响应封装 → JSON 返回
|
||
```
|
||
|
||
### 2.3 认证鉴权架构
|
||
|
||
使用 **JWT(JSON Web Token)** 实现无状态认证,结合 Spring Security 进行权限控制。
|
||
|
||
```
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||
│ 客户端 │────▶│ LoginController │───▶│ AuthService │
|
||
│ (浏览器) │ │ /api/auth/* │ │ 认证服务 │
|
||
└──────────────┘ └──────────────┘ └──────┬───────┘
|
||
│
|
||
┌──────────────────────────────────┤
|
||
▼ ▼
|
||
┌────────────────┐ ┌──────────────────┐
|
||
│ 校验通过 │ │ Redis 缓存会话 │
|
||
│ 生成 AccessToken │◀───────────────│ 多端登录控制 │
|
||
│ + RefreshToken │ │ 最多 3 个活跃端 │
|
||
└────────┬───────┘ └──────────────────┘
|
||
│
|
||
▼
|
||
┌────────────────┐
|
||
│ 返回 Token 给 │
|
||
│ 客户端(HTTP Only│
|
||
│ Cookie 或 Header│
|
||
└────────────────┘
|
||
```
|
||
|
||
**Token 策略**:
|
||
|
||
| Token 类型 | 有效期 | 用途 |
|
||
| :--------- | :----- | :--- |
|
||
| AccessToken | 2 小时 | 携带在请求头 `Authorization: Bearer <token>` 中,鉴权通过后访问受保护资源 |
|
||
| RefreshToken | 14 天(记住我)/ 24 小时 | 用于刷新 AccessToken,存储在 Redis 中 |
|
||
|
||
### 2.4 统一响应格式
|
||
|
||
所有 API 接口统一返回如下结构:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {},
|
||
"timestamp": 1715932800000
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| :--- | :--- | :--- |
|
||
| code | Integer | 业务状态码(200=成功, 400=参数错误, 401=未认证, 403=无权限, 404=资源不存在, 429=请求过频, 500=服务端错误) |
|
||
| message | String | 提示信息 |
|
||
| data | Object | 响应数据,分页时包含 `{records, total, page, pageSize}` |
|
||
| timestamp | Long | 响应时间戳 |
|
||
|
||
### 2.5 异常体系
|
||
|
||
```
|
||
BusinessException (业务异常,code + message)
|
||
├── BadRequestException (400)
|
||
├── UnauthorizedException (401)
|
||
├── ForbiddenException (403)
|
||
├── ResourceNotFoundException (404)
|
||
├── RateLimitException (429)
|
||
└── DuplicateOperationException (409)
|
||
```
|
||
|
||
全局异常处理器 `GlobalExceptionHandler` 捕获所有异常并统一返回标准响应格式。
|
||
|
||
---
|
||
|
||
## 3. 模块划分与职责
|
||
|
||
### 3.1 模块总览
|
||
|
||
| 模块 | 职责 | 关键接口前缀 |
|
||
| :--- | :--- | :---------- |
|
||
| `blog-module-system` | 用户管理、角色权限、登录/注册、登录日志、系统配置 | `/api/auth/**`, `/api/admin/system/**` |
|
||
| `blog-module-blog` | 博客文章 CRUD、分类管理、标签管理、草稿管理、全文检索 | `/api/blog/**`, `/api/admin/blog/**` |
|
||
| `blog-module-comment` | 评论 CRUD、多级回复、评论审核、敏感词过滤、回复通知 | `/api/comment/**`, `/api/admin/comment/**` |
|
||
|
||
### 3.2 模块间依赖关系
|
||
|
||
```
|
||
blog-common (无依赖)
|
||
↑
|
||
blog-framework (依赖 blog-common)
|
||
↑
|
||
┌───────────────┼───────────────────┐
|
||
│ │ │
|
||
blog-module-system blog-module-blog──┐
|
||
│ │ │ │
|
||
│ │ │ │
|
||
└───────────────┼───────────────────┘ │
|
||
│ │
|
||
▼ ▼
|
||
blog-module-comment blog-module-blog
|
||
```
|
||
|
||
说明:
|
||
- `blog-module-system` 无业务模块依赖
|
||
- `blog-module-blog` 依赖 `blog-module-system`(获取用户信息、鉴权)
|
||
- `blog-module-comment` 依赖 `blog-module-blog`(获取文章信息)和 `blog-module-system`(获取用户信息、通知)
|
||
|
||
### 3.3 业务能力清单
|
||
|
||
按需求文档功能需求编号映射的模块能力:
|
||
|
||
| 需求编号 | 功能点 | 所属模块 | 接口说明 |
|
||
| :------- | :----- | :------- | :------- |
|
||
| FR-ADM-LOGIN-01~06 | 管理员登录 | blog-module-system | 管理员认证、验证码、锁定、日志 |
|
||
| FR-USER-REG-01~06 | 用户注册 | blog-module-system | 邮箱注册、验证、频率限制 |
|
||
| FR-USER-LOGIN-01~06 | 用户登录 | blog-module-system | 邮箱登录、多端限制、第三方预留 |
|
||
| FR-BLOG-PUB-01~07 | 博客发布 | blog-module-blog | 富文本编辑、草稿、状态管理、标签 |
|
||
| FR-BLOG-LIST-01~08 | 博客列表查看 | blog-module-blog | 排序、筛选、检索、分页、浏览量 |
|
||
| FR-COMMENT-01~07 | 评论功能 | blog-module-comment | 多级回复、审核、屏蔽、通知 |
|
||
|
||
---
|
||
|
||
## 4. 数据模型设计
|
||
|
||
### 4.1 数据库 E-R 关系概览
|
||
|
||
```
|
||
User (1) ──────< Blog (N) # 用户拥有多篇博客
|
||
Blog (N) ──────< BlogTag (N) # 博客关联多个标签(中间表)
|
||
Tag (N) ──────< BlogTag (N) # 标签被多篇博客使用
|
||
Category (1) ──< Blog (N) # 分类下有多篇博客
|
||
Blog (1) ──────< Comment (N) # 博客有多条评论
|
||
Comment (N) ───< Comment (N) # 评论自关联(多级回复)
|
||
User (1) ──────< Comment (N) # 用户发表多条评论
|
||
User (1) ──────< UserFollow (N) # 用户关注关系
|
||
Blog (1) ──────< BlogView (N) # 博客浏览记录
|
||
User (1) ──────< Notification (N) # 用户接收多条通知
|
||
```
|
||
|
||
### 4.2 数据字典
|
||
|
||
#### 4.2.1 用户表 `sys_user`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 默认值 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :----- | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 | 用户 ID |
|
||
| username | VARCHAR | 50 | — | N | NULL | 用户名(管理员专用) |
|
||
| email | VARCHAR | 100 | — | Y | — | 邮箱(唯一索引) |
|
||
| phone | VARCHAR | 20 | — | N | NULL | 手机号(管理员专用) |
|
||
| password | VARCHAR | 255 | — | Y | — | bcrypt 加盐哈希值 |
|
||
| nickname | VARCHAR | 50 | — | N | NULL | 昵称 |
|
||
| avatar | VARCHAR | 500 | — | N | NULL | 头像 URL |
|
||
| user_type | TINYINT | — | — | Y | 1 | 用户类型:0=超级管理员, 1=注册用户 |
|
||
| status | TINYINT | — | — | Y | 0 | 状态:0=正常, 1=锁定 |
|
||
| locked_until | DATETIME | — | — | N | NULL | 锁定截止时间 |
|
||
| login_fail_count | INT | — | — | Y | 0 | 连续登录失败次数 |
|
||
| email_verified | TINYINT | — | — | Y | 0 | 邮箱是否已验证:0=未验证, 1=已验证 |
|
||
| last_login_ip | VARCHAR | 50 | — | N | NULL | 最后登录 IP |
|
||
| last_login_time | DATETIME | — | — | N | NULL | 最后登录时间 |
|
||
| created_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP ON UPDATE | 更新时间 |
|
||
| deleted | TINYINT | — | — | Y | 0 | 逻辑删除:0=未删除, 1=已删除 |
|
||
|
||
**索引**:
|
||
- `uk_email`:唯一索引(email, deleted)
|
||
- `uk_username`:唯一索引(username, deleted)
|
||
- `idx_status`:普通索引(status)
|
||
|
||
**初始化数据**:
|
||
- 系统初始化 Flyway 脚本自动插入超级管理员账号(username: `admin`, password: `Admin@2026`,需登录后强制修改)
|
||
|
||
#### 4.2.2 用户会话表 `sys_user_session`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| user_id | BIGINT | — | N | Y | 用户 ID(外键) |
|
||
| token | VARCHAR | 500 | — | Y | JWT Token(或标识) |
|
||
| device_fingerprint | VARCHAR | 200 | — | N | 设备指纹 |
|
||
| ip_address | VARCHAR | 50 | — | N | 登录 IP |
|
||
| user_agent | VARCHAR | 500 | — | N | 浏览器 UA |
|
||
| expire_time | DATETIME | — | — | Y | 过期时间 |
|
||
| created_at | DATETIME | — | — | Y | 创建时间 |
|
||
|
||
**索引**:
|
||
- `idx_user_id`:普通索引(user_id)
|
||
- `idx_token`:唯一索引(token)
|
||
|
||
**多端限制逻辑**:
|
||
- 查询 `sys_user_session` 统计 user_id 的活跃会话数(未过期的)
|
||
- 如果 ≥ 3,删除最早的一条,再插入新会话
|
||
- 实际 Token 校验走 Redis 缓存,DB 做持久化兜底
|
||
|
||
#### 4.2.3 登录日志表 `sys_login_log`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| user_id | BIGINT | — | — | N | 用户 ID(失败时可能为空) |
|
||
| username | VARCHAR | 50 | — | N | 登录时使用的用户名/邮箱 |
|
||
| login_type | TINYINT | — | — | Y | 登录类型:0=管理员登录, 1=用户登录 |
|
||
| result | TINYINT | — | — | Y | 结果:0=失败, 1=成功 |
|
||
| fail_reason | VARCHAR | 200 | — | N | 失败原因 |
|
||
| ip_address | VARCHAR | 50 | — | Y | 登录 IP |
|
||
| device_fingerprint | VARCHAR | 200 | — | N | 设备指纹 |
|
||
| created_at | DATETIME | — | — | Y | 创建时间 |
|
||
|
||
**索引**:
|
||
- `idx_user_id`:普通索引(user_id)
|
||
- `idx_created_at`:普通索引(created_at)
|
||
|
||
#### 4.2.4 博客分类表 `blog_category`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 默认值 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :----- | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 | 分类 ID |
|
||
| name | VARCHAR | 50 | — | Y | — | 分类名称(唯一) |
|
||
| description | VARCHAR | 200 | — | N | NULL | 分类描述 |
|
||
| sort_order | INT | — | — | Y | 0 | 排序序号 |
|
||
| status | TINYINT | — | — | Y | 0 | 状态:0=启用, 1=禁用 |
|
||
| created_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP ON UPDATE | 更新时间 |
|
||
| deleted | TINYINT | — | — | Y | 0 | 逻辑删除 |
|
||
|
||
**索引**:`uk_name`:唯一索引(name, deleted)
|
||
|
||
#### 4.2.5 博客标签表 `blog_tag`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 默认值 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :----- | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 | 标签 ID |
|
||
| name | VARCHAR | 30 | — | Y | — | 标签名称(唯一) |
|
||
| created_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP | 创建时间 |
|
||
| deleted | TINYINT | — | — | Y | 0 | 逻辑删除 |
|
||
|
||
#### 4.2.6 博客文章表 `blog_article`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 默认值 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :----- | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 | 文章 ID |
|
||
| author_id | BIGINT | — | — | Y | — | 作者用户 ID(外键) |
|
||
| category_id | BIGINT | — | — | N | NULL | 分类 ID(外键) |
|
||
| title | VARCHAR | 200 | — | Y | — | 文章标题 |
|
||
| summary | VARCHAR | 500 | — | N | NULL | 文章摘要(自动截取前 120 字) |
|
||
| content | LONGTEXT | — | — | Y | — | 文章正文(HTML 格式) |
|
||
| content_text | LONGTEXT | — | — | N | NULL | 纯文本内容(用于全文检索) |
|
||
| cover_image | VARCHAR | 500 | — | N | NULL | 封面图 URL |
|
||
| status | TINYINT | — | — | Y | 0 | 发布状态:0=草稿, 1=已发布, 2=回收站 |
|
||
| visibility | TINYINT | — | — | Y | 0 | 可见性:0=公开, 1=私密, 2=仅粉丝可见 |
|
||
| view_count | INT | — | — | Y | 0 | 浏览量 |
|
||
| comment_count | INT | — | — | Y | 0 | 评论数 |
|
||
| like_count | INT | — | — | Y | 0 | 点赞数 |
|
||
| is_top | TINYINT | — | — | Y | 0 | 是否置顶:0=否, 1=是 |
|
||
| published_at | DATETIME | — | — | N | NULL | 发布时间 |
|
||
| created_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP ON UPDATE | 更新时间 |
|
||
| deleted | TINYINT | — | — | Y | 0 | 逻辑删除 |
|
||
|
||
**索引**:
|
||
- `idx_author_id`:普通索引(author_id)
|
||
- `idx_category_id`:普通索引(category_id)
|
||
- `idx_status_visibility`:联合索引(status, visibility)
|
||
- `idx_published_at`:普通索引(published_at)
|
||
- `idx_view_count`:普通索引(view_count)
|
||
- `FULLTEXT idx_fulltext`:全文索引(title, content_text)
|
||
|
||
#### 4.2.7 文章-标签关联表 `blog_article_tag`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| article_id | BIGINT | — | — | Y | 文章 ID(外键) |
|
||
| tag_id | BIGINT | — | — | Y | 标签 ID(外键) |
|
||
|
||
**索引**:`uk_article_tag`:唯一索引(article_id, tag_id)
|
||
|
||
#### 4.2.8 文章浏览记录表 `blog_article_view`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| article_id | BIGINT | — | — | Y | 文章 ID(外键) |
|
||
| user_id | BIGINT | — | — | N | 用户 ID(未登录为 NULL) |
|
||
| ip_address | VARCHAR | 50 | — | Y | 访问者 IP |
|
||
| created_at | DATETIME | — | — | Y | 访问时间 |
|
||
|
||
**索引**:
|
||
- `idx_article_date`:联合索引(article_id, DATE(created_at))
|
||
- `idx_user_article`:唯一索引(user_id, article_id, DATE(created_at)),用于同一天同一用户去重
|
||
|
||
**浏览量计数逻辑**:
|
||
- 同一用户对同一篇文章每天只计入 1 次浏览量
|
||
- 作者本人访问不计入浏览量
|
||
- 未登录访客通过 IP + User-Agent 组合去重(每天仅计 1 次)
|
||
|
||
#### 4.2.9 评论表 `blog_comment`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 默认值 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :----- | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 | 评论 ID |
|
||
| article_id | BIGINT | — | — | Y | — | 文章 ID(外键) |
|
||
| user_id | BIGINT | — | — | Y | — | 评论者用户 ID(外键) |
|
||
| parent_id | BIGINT | — | — | N | NULL | 父评论 ID(NULL 表示顶级评论) |
|
||
| reply_to_id | BIGINT | — | — | N | NULL | 被回复的评论 ID |
|
||
| reply_to_user_id | BIGINT | — | — | N | NULL | 被回复的用户 ID |
|
||
| content | TEXT | — | — | Y | — | 评论内容(已过滤 XSS) |
|
||
| level | TINYINT | — | — | Y | 1 | 评论层级:1~3 |
|
||
| status | TINYINT | — | — | Y | 1 | 状态:0=待审核, 1=已通过, 2=已屏蔽, 3=已删除 |
|
||
| is_author | TINYINT | — | — | Y | 0 | 是否为文章作者:0=否, 1=是 |
|
||
| created_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP | 创建时间 |
|
||
| deleted | TINYINT | — | — | Y | 0 | 逻辑删除 |
|
||
|
||
**索引**:
|
||
- `idx_article_id`:普通索引(article_id)
|
||
- `idx_parent_id`:普通索引(parent_id)
|
||
- `idx_user_id`:普通索引(user_id)
|
||
- `idx_status`:普通索引(status)
|
||
|
||
#### 4.2.10 用户关注表 `sys_user_follow`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| follower_id | BIGINT | — | — | Y | 关注者用户 ID |
|
||
| followee_id | BIGINT | — | — | Y | 被关注者用户 ID |
|
||
| created_at | DATETIME | — | — | Y | 创建时间 |
|
||
|
||
**索引**:`uk_follower_followee`:唯一索引(follower_id, followee_id)
|
||
|
||
#### 4.2.11 通知表 `sys_notification`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 默认值 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :----- | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 | 通知 ID |
|
||
| receiver_id | BIGINT | — | — | Y | — | 接收通知的用户 ID |
|
||
| sender_id | BIGINT | — | — | N | NULL | 触发通知的用户 ID |
|
||
| type | TINYINT | — | — | Y | — | 通知类型:0=回复评论, 1=点赞, 2=系统通知 |
|
||
| content | VARCHAR | 500 | — | Y | — | 通知内容 |
|
||
| related_id | BIGINT | — | — | N | NULL | 关联的业务 ID(如评论 ID) |
|
||
| is_read | TINYINT | — | — | Y | 0 | 是否已读:0=未读, 1=已读 |
|
||
| created_at | DATETIME | — | — | Y | CURRENT_TIMESTAMP | 创建时间 |
|
||
|
||
**索引**:
|
||
- `idx_receiver_read`:联合索引(receiver_id, is_read)
|
||
- `idx_created_at`:普通索引(created_at)
|
||
|
||
#### 4.2.12 系统配置表 `sys_config`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| config_key | VARCHAR | 100 | — | Y | 配置键(唯一) |
|
||
| config_value | TEXT | — | — | Y | 配置值 |
|
||
| description | VARCHAR | 200 | — | N | 配置说明 |
|
||
| updated_at | DATETIME | — | — | Y | 更新时间 |
|
||
|
||
**初始配置数据**:
|
||
|
||
| config_key | config_value | description |
|
||
| :--------- | :----------- | :---------- |
|
||
| comment_audit_enabled | false | 评论审核开关 |
|
||
| oauth_wechat_enabled | false | 微信登录开关 |
|
||
| oauth_github_enabled | false | GitHub 登录开关 |
|
||
|
||
#### 4.2.13 敏感词表 `sys_sensitive_word`
|
||
|
||
| 字段名 | 类型 | 长度 | 主键 | 必填 | 说明 |
|
||
| :----- | :--- | :--- | :--: | :--: | :--- |
|
||
| id | BIGINT | — | Y | Y | 自增 ID |
|
||
| word | VARCHAR | 100 | — | Y | 敏感词 |
|
||
| replacement | VARCHAR | 100 | — | Y | 替换词(默认 `***`) |
|
||
| created_at | DATETIME | — | — | Y | 创建时间 |
|
||
|
||
---
|
||
|
||
## 5. API 接口设计
|
||
|
||
### 5.1 接口规范
|
||
|
||
- **基础路径**:`/api`
|
||
- **公开接口**:无需认证,路径前缀 `/api/public/**`
|
||
- **前台用户接口**:需 JWT 认证,路径前缀 `/api/app/**`
|
||
- **后台管理接口**:需 JWT 认证 + 管理员角色,路径前缀 `/api/admin/**`
|
||
- **分页参数**:统一使用 `page`(页码从 1 开始)和 `pageSize`(默认 15)
|
||
- **请求方法**:遵循 RESTful 规范
|
||
|
||
### 5.2 认证模块接口
|
||
|
||
#### 5.2.1 管理员登录
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 管理员登录 | POST | `/api/admin/auth/login` | 无 | 管理员账号密码登录 |
|
||
| 获取验证码 | GET | `/api/public/captcha` | 无 | 获取图形验证码 |
|
||
| 管理员退出 | POST | `/api/admin/auth/logout` | 需认证 | 退出并清除会话 |
|
||
| 获取管理员信息 | GET | `/api/admin/auth/profile` | 需认证 | 获取当前管理员信息 |
|
||
| 查询登录日志 | GET | `/api/admin/auth/login-logs` | Admin | 分页查询登录日志 |
|
||
|
||
##### POST `/api/admin/auth/login`
|
||
|
||
**Request**:
|
||
```json
|
||
{
|
||
"account": "admin | 13800138000",
|
||
"password": "Admin@2026",
|
||
"captchaKey": "UUID (失败3次后必传)",
|
||
"captchaCode": "验证码 (失败3次后必传)",
|
||
"deviceFingerprint": "设备指纹"
|
||
}
|
||
```
|
||
|
||
**Response (200)**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "登录成功",
|
||
"data": {
|
||
"accessToken": "eyJhbGci...",
|
||
"refreshToken": "eyJhbGci...",
|
||
"expiresIn": 7200,
|
||
"userInfo": {
|
||
"id": 1,
|
||
"username": "admin",
|
||
"nickname": "超级管理员",
|
||
"avatar": null
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Response (429 - 触发限流)**:
|
||
```json
|
||
{
|
||
"code": 429,
|
||
"message": "登录过于频繁,请15分钟后再试"
|
||
}
|
||
```
|
||
|
||
##### GET `/api/public/captcha`
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"captchaKey": "a1b2c3d4-e5f6-...",
|
||
"captchaImage": "data:image/png;base64,iVBORw0KGgo..."
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.2.2 用户登录/注册
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 用户注册 | POST | `/api/public/user/register` | 无 | 邮箱注册 |
|
||
| 验证邮箱 | GET | `/api/public/user/verify-email` | 无 | 点击验证链接 |
|
||
| 重新发送验证邮件 | POST | `/api/public/user/resend-verification` | 无 | 重新发送验证邮件 |
|
||
| 用户登录 | POST | `/api/app/auth/login` | 无 | 邮箱密码登录 |
|
||
| 用户退出 | POST | `/api/app/auth/logout` | 需认证 | 退出登录 |
|
||
| 刷新 Token | POST | `/api/app/auth/refresh` | 无 | 使用 RefreshToken 刷新 |
|
||
| 获取用户信息 | GET | `/api/app/auth/profile` | 需认证 | 获取当前登录用户信息 |
|
||
| 管理员重置用户密码 | PUT | `/api/admin/system/user/{userId}/reset-password` | Admin | 管理员重置指定用户密码 |
|
||
|
||
##### POST `/api/public/user/register`
|
||
|
||
**Request**:
|
||
```json
|
||
{
|
||
"email": "user@example.com",
|
||
"password": "Abc@123456",
|
||
"confirmPassword": "Abc@123456"
|
||
}
|
||
```
|
||
|
||
**Response (200)**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "注册成功,请查看邮箱完成验证"
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
1. 校验邮箱格式和密码强度
|
||
2. 校验邮箱是否已存在
|
||
3. 校验同 IP 注册频率(1h 内 ≤ 3 次)
|
||
4. 密码 bcrypt 加密
|
||
5. 创建用户(status=0正常, email_verified=0)
|
||
6. 发送验证邮件(含 24h 有效期的 token)
|
||
7. 记录注册日志
|
||
|
||
##### POST `/api/app/auth/login`
|
||
|
||
**Request**:
|
||
```json
|
||
{
|
||
"email": "user@example.com",
|
||
"password": "Abc@123456",
|
||
"deviceFingerprint": "设备指纹",
|
||
"rememberMe": false
|
||
}
|
||
```
|
||
|
||
**Response (200)**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"accessToken": "eyJhbGci...",
|
||
"refreshToken": "eyJhbGci...",
|
||
"expiresIn": 7200,
|
||
"userInfo": {
|
||
"id": 2,
|
||
"email": "user@example.com",
|
||
"nickname": "用户昵称",
|
||
"avatar": null
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
1. 验证邮箱 + 密码
|
||
2. 检查账号是否锁定(locked_until > now 则拒绝)
|
||
3. 检查 email_verified
|
||
4. 多端限制检查(同一账号最多 3 个活跃会话)
|
||
5. 生成 AccessToken(2h 有效)+ RefreshToken(24h 或 14d)
|
||
6. 记录登录日志
|
||
7. 重置登录失败计数
|
||
|
||
### 5.3 博客模块接口
|
||
|
||
#### 5.3.1 博客文章 CRUD
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 创建文章 | POST | `/api/app/blog/articles` | 需认证 | 创建新文章(草稿或直接发布) |
|
||
| 编辑文章 | PUT | `/api/app/blog/articles/{id}` | 需认证 | 修改文章(仅作者) |
|
||
| 删除文章 | DELETE | `/api/app/blog/articles/{id}` | 需认证 | 逻辑删除(仅作者) |
|
||
| 获取文章详情 | GET | `/api/public/blog/articles/{id}` | 无 | 公开可见的文章详情 |
|
||
| 获取我的文章列表 | GET | `/api/app/blog/articles` | 需认证 | 获取当前用户的所有文章 |
|
||
| 获取公开文章列表 | GET | `/api/public/blog/articles` | 无 | 公开文章的列表浏览 |
|
||
| 修改文章可见性 | PATCH | `/api/app/blog/articles/{id}/visibility` | 需认证 | 修改发布状态 |
|
||
| 保存草稿 | POST | `/api/app/blog/articles/draft` | 需认证 | 保存为草稿 |
|
||
| 自动保存草稿 | PUT | `/api/app/blog/articles/{id}/draft` | 需认证 | 自动保存草稿 |
|
||
|
||
##### GET `/api/public/blog/articles` — 公开文章列表
|
||
|
||
**Query Parameters**:
|
||
|
||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||
| :--- | :--- | :--: | :----- | :--- |
|
||
| page | Integer | N | 1 | 页码 |
|
||
| pageSize | Integer | N | 15 | 每页条数 |
|
||
| categoryId | Long | N | — | 分类 ID 筛选 |
|
||
| tagId | Long | N | — | 标签 ID 筛选 |
|
||
| keyword | String | N | — | 关键词全文检索 |
|
||
| sortBy | String | N | publishedAt | 排序字段:publishedAt, viewCount |
|
||
| sortOrder | String | N | desc | 排序方向:asc, desc |
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"records": [
|
||
{
|
||
"id": 1,
|
||
"title": "Spring Boot 入门指南",
|
||
"summary": "本文介绍如何使用 Spring Boot 快速构建 Web 应用...",
|
||
"coverImage": "https://...",
|
||
"author": {
|
||
"id": 2,
|
||
"nickname": "作者昵称",
|
||
"avatar": null
|
||
},
|
||
"category": {
|
||
"id": 1,
|
||
"name": "技术教程"
|
||
},
|
||
"tags": [
|
||
{"id": 1, "name": "Java"},
|
||
{"id": 2, "name": "Spring Boot"}
|
||
],
|
||
"viewCount": 1280,
|
||
"commentCount": 15,
|
||
"publishedAt": "2026-05-15T10:30:00"
|
||
}
|
||
],
|
||
"total": 100,
|
||
"page": 1,
|
||
"pageSize": 15
|
||
}
|
||
}
|
||
```
|
||
|
||
**全文检索实现**:
|
||
- 使用 MySQL `FULLTEXT INDEX` + `MATCH...AGAINST` 实现
|
||
- 关键词高亮由前端在搜索结果中处理
|
||
|
||
##### GET `/api/public/blog/articles/{id}` — 文章详情
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"id": 1,
|
||
"title": "Spring Boot 入门指南",
|
||
"content": "<h1>...</h1><p>...</p>",
|
||
"author": {...},
|
||
"category": {...},
|
||
"tags": [...],
|
||
"viewCount": 1280,
|
||
"commentCount": 15,
|
||
"isLiked": false,
|
||
"visibility": 0,
|
||
"publishedAt": "2026-05-15T10:30:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
1. 判断文章可见性:
|
||
- `status ≠ 已发布` → 404
|
||
- `visibility = 私密` 且当前用户非作者 → 404
|
||
- `visibility = 仅粉丝可见` 且当前用户非作者且非粉丝 → 404
|
||
2. 浏览量更新(异步):
|
||
- 同一用户同一天仅计 1 次
|
||
- 作者本人不计
|
||
- 调用 `CompletableFuture` 异步写入 `blog_article_view`
|
||
|
||
##### POST `/api/app/blog/articles` — 创建文章
|
||
|
||
**Request**:
|
||
```json
|
||
{
|
||
"title": "文章标题",
|
||
"content": "<h1>富文本内容</h1>",
|
||
"categoryId": 1,
|
||
"tagIds": [1, 2, 3],
|
||
"coverImage": "https://...",
|
||
"visibility": 0,
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
**参数说明**:
|
||
- `status=0` 保存为草稿,`status=1` 直接发布
|
||
- `visibility` 仅在发布时生效
|
||
|
||
**Response (200)**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"id": 42
|
||
}
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
1. 校验标题非空、内容非空
|
||
2. 校验分类存在且启用
|
||
3. 校验标签数量 ≤ 5
|
||
4. XSS 过滤 content(使用 Jsoup 清理)
|
||
5. 自动生成 summary(截取 content_text 前 120 字)
|
||
6. 设置 published_at(status=1 时)
|
||
7. 插入 blog_article 和 blog_article_tag
|
||
|
||
#### 5.3.2 分类与标签管理
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 创建分类 | POST | `/api/admin/blog/categories` | Admin | 新增分类 |
|
||
| 编辑分类 | PUT | `/api/admin/blog/categories/{id}` | Admin | 修改分类 |
|
||
| 删除分类 | DELETE | `/api/admin/blog/categories/{id}` | Admin | 删除分类(有文章时禁止删除) |
|
||
| 分类列表 | GET | `/api/public/blog/categories` | 无 | 获取所有分类 |
|
||
| 创建标签 | POST | `/api/admin/blog/tags` | Admin | 新增标签 |
|
||
| 删除标签 | DELETE | `/api/admin/blog/tags/{id}` | Admin | 删除标签 |
|
||
| 标签列表 | GET | `/api/public/blog/tags` | 无 | 获取所有标签 |
|
||
|
||
#### 5.3.3 图片上传接口
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 上传图片 | POST | `/api/app/upload/image` | 需认证 | 上传文章图片 |
|
||
|
||
**Request**:`multipart/form-data`,字段名 `file`
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"url": "https://cdn.example.com/images/2026/05/abc123.jpg"
|
||
}
|
||
}
|
||
```
|
||
|
||
**业务规则**:
|
||
- 支持格式:JPEG, PNG, GIF, WebP
|
||
- 单图限制:≤ 10MB
|
||
- 禁止上传 .exe, .js, .bat 等可执行文件
|
||
- 上传后进行图片安全扫描(依赖第三方服务)
|
||
|
||
### 5.4 评论模块接口
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 发表评论 | POST | `/api/app/comments` | 需认证 | 对文章发表评论或回复 |
|
||
| 获取文章评论 | GET | `/api/public/comments/{articleId}` | 无 | 获取指定文章的评论列表 |
|
||
| 删除评论 | DELETE | `/api/admin/comments/{id}` | Admin | 物理删除 |
|
||
| 屏蔽评论 | PATCH | `/api/admin/comments/{id}/block` | Admin | 逻辑屏蔽 |
|
||
| 审核评论 | PATCH | `/api/admin/comments/{id}/approve` | Admin | 审核通过 |
|
||
| 批量审核评论 | POST | `/api/admin/comments/batch-approve` | Admin | 批量审核 |
|
||
| 配置评论审核开关 | PUT | `/api/admin/system/config/comment-audit` | Admin | 开启/关闭评论审核 |
|
||
|
||
#### POST `/api/app/comments` — 发表评论
|
||
|
||
**Request**:
|
||
```json
|
||
{
|
||
"articleId": 1,
|
||
"content": "写得很棒,学习了!",
|
||
"parentId": null,
|
||
"replyToId": null
|
||
}
|
||
```
|
||
|
||
**回复评论 Request**:
|
||
```json
|
||
{
|
||
"articleId": 1,
|
||
"content": "感谢回复!",
|
||
"parentId": 5,
|
||
"replyToId": 8
|
||
}
|
||
```
|
||
|
||
**Response (200)**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"id": 10,
|
||
"status": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
> 当评论审核开启时 `status=0(待审核)`,否则 `status=1(已通过)`
|
||
|
||
**业务逻辑**:
|
||
1. 校验文章存在且可见
|
||
2. 校验 parentId 有效性:
|
||
- 如果 parentId 非空,确认父评论存在且属于同一文章
|
||
- 检查层级:查询父评论的 level,如果父评论 level ≥ 3,则设置为第 3 级的平级评论
|
||
3. 敏感词过滤(替换为 `***`)
|
||
4. XSS 过滤
|
||
5. 判断评论者是否为文章作者(is_author)
|
||
6. 评论审核开关判断:
|
||
- 开启 → status=0(待审核)
|
||
- 关闭 → status=1(已通过)
|
||
7. 异步发送回复通知
|
||
|
||
#### GET `/api/public/comments/{articleId}` — 获取文章评论
|
||
|
||
**Query Parameters**:
|
||
|
||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||
| :--- | :--- | :--: | :----- | :--- |
|
||
| page | Integer | N | 1 | 页码 |
|
||
| pageSize | Integer | N | 15 | 每页条数(顶级评论数) |
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"records": [
|
||
{
|
||
"id": 1,
|
||
"content": "好文章!",
|
||
"user": {
|
||
"id": 2,
|
||
"nickname": "小明",
|
||
"avatar": null
|
||
},
|
||
"isAuthor": false,
|
||
"level": 1,
|
||
"createdAt": "2026-05-16T10:00:00",
|
||
"children": [
|
||
{
|
||
"id": 2,
|
||
"content": "谢谢支持!",
|
||
"user": {
|
||
"id": 1,
|
||
"nickname": "作者大大",
|
||
"avatar": null
|
||
},
|
||
"isAuthor": true,
|
||
"level": 2,
|
||
"replyToUserId": 2,
|
||
"createdAt": "2026-05-16T10:05:00",
|
||
"children": []
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"total": 20,
|
||
"page": 1,
|
||
"pageSize": 15
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.5 管理后台接口
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 用户列表 | GET | `/api/admin/system/users` | Admin | 分页查询用户列表 |
|
||
| 获取用户详情 | GET | `/api/admin/system/users/{id}` | Admin | 获取指定用户信息 |
|
||
| 禁用/启用用户 | PATCH | `/api/admin/system/users/{id}/status` | Admin | 修改用户状态 |
|
||
| 重置用户密码 | PUT | `/api/admin/system/users/{id}/reset-password` | Admin | 重置密码 |
|
||
| 操作日志列表 | GET | `/api/admin/system/logs` | Admin | 分页查询操作日志 |
|
||
| 系统配置列表 | GET | `/api/admin/system/configs` | Admin | 获取所有配置 |
|
||
| 更新系统配置 | PUT | `/api/admin/system/configs/{key}` | Admin | 更新配置项 |
|
||
| 敏感词列表 | GET | `/api/admin/system/sensitive-words` | Admin | 分页查询敏感词 |
|
||
| 添加敏感词 | POST | `/api/admin/system/sensitive-words` | Admin | 新增敏感词 |
|
||
| 删除敏感词 | DELETE | `/api/admin/system/sensitive-words/{id}` | Admin | 删除敏感词 |
|
||
| 后台仪表盘统计 | GET | `/api/admin/dashboard/stats` | Admin | 获取统计数据 |
|
||
|
||
### 5.6 OAuth 2.0 第三方登录扩展接口(预留)
|
||
|
||
| 接口 | 方法 | 路径 | 认证 | 说明 |
|
||
| :--- | :--- | :--- | :--: | :--- |
|
||
| 微信登录授权 | GET | `/api/oauth/wechat/authorize` | 无 | 跳转微信授权页 |
|
||
| 微信登录回调 | GET | `/api/oauth/wechat/callback` | 无 | 微信回调处理 |
|
||
| GitHub 登录授权 | GET | `/api/oauth/github/authorize` | 无 | 跳转 GitHub 授权页 |
|
||
| GitHub 登录回调 | GET | `/api/oauth/github/callback` | 无 | GitHub 回调处理 |
|
||
|
||
> **说明**:当前版本仅定义接口路径和空实现。当 `sys_config` 表中 `oauth_wechat_enabled` / `oauth_github_enabled` 为 `true` 时启用。预留 `OAuth2Service` 接口和 `OAuth2UserInfo` 模型,后续接入时实现接口即可。
|
||
|
||
### 5.7 接口权限映射速查表
|
||
|
||
| 接口路径范围 | 角色要求 | 鉴权方式 |
|
||
| :----------- | :------- | :------- |
|
||
| `/api/public/**` | 无(允许匿名访问) | 无需 Token |
|
||
| `/api/app/**` | 注册用户 | JWT Token + 用户角色校验 |
|
||
| `/api/admin/**` | 超级管理员 | JWT Token + 管理员角色校验 |
|
||
| `/api/oauth/**` | 无(OAuth 流程) | 无(暂为预留空实现) |
|
||
|
||
---
|
||
|
||
## 6. 核心业务流程
|
||
|
||
### 6.1 用户注册流程
|
||
|
||
```
|
||
用户填写注册表单 (邮箱 + 密码)
|
||
│
|
||
▼
|
||
前端校验:邮箱格式、密码强度、两次密码一致
|
||
│
|
||
▼
|
||
POST /api/public/user/register
|
||
│
|
||
▼
|
||
后端校验:
|
||
1. 邮箱格式正则校验
|
||
2. 密码强度校验(8-32位,至少3类字符)
|
||
3. 邮箱唯一性检查
|
||
4. IP 注册频率检查(1h ≤ 3次)
|
||
│
|
||
▼
|
||
密码 bcrypt 加密
|
||
│
|
||
▼
|
||
创建用户 (email_verified = 0)
|
||
│
|
||
▼
|
||
生成邮箱验证 Token(UUID,有效期 24h)
|
||
│
|
||
▼
|
||
发送验证邮件(含验证链接)
|
||
│
|
||
▼
|
||
返回「注册成功,请查看邮箱完成验证」
|
||
│
|
||
▼
|
||
用户点击验证链接 → GET /api/public/user/verify-email?token=xxx
|
||
│
|
||
▼
|
||
校验 Token 有效性 → 用户 email_verified = 1
|
||
│
|
||
▼
|
||
返回「邮箱验证成功,请登录」
|
||
```
|
||
|
||
### 6.2 管理员/用户登录流程(含防暴力破解)
|
||
|
||
```
|
||
用户输入账号密码
|
||
│
|
||
▼
|
||
后端查询账号是否存在
|
||
│
|
||
▼ (不存在)
|
||
返回「账号或密码错误」(模糊提示,不泄露具体字段)
|
||
│
|
||
▼ (存在)
|
||
检查用户状态:
|
||
- status=1 (锁定) 且 locked_until > now → 返回「账号已被锁定,请 X 分钟后重试」
|
||
- 否则继续
|
||
│
|
||
▼
|
||
验证密码(bcrypt.matches)
|
||
│
|
||
▼ (失败)
|
||
记录失败次数 + 1
|
||
│
|
||
▼ (失败次数分析)
|
||
- 失败 3 次(管理员)→ 下次需输入验证码
|
||
- 失败 5 次 → 锁定账号 N 分钟(管理员30min,用户15min)
|
||
- 同一 IP 10 次/5min → IP 封禁 15 分钟
|
||
│
|
||
▼ (成功)
|
||
重置失败计数
|
||
│
|
||
▼
|
||
多端登录控制(仅前台用户):
|
||
查询活跃会话数,≥ 3 个时剔除最早会话
|
||
│
|
||
▼
|
||
生成 JWT Token + RefreshToken
|
||
│
|
||
▼
|
||
记录登录日志(成功)
|
||
│
|
||
▼
|
||
返回 Token + 用户信息
|
||
```
|
||
|
||
### 6.3 博客发布流程
|
||
|
||
```
|
||
用户进入编辑器(需认证)
|
||
│
|
||
▼
|
||
用户编写内容(富文本)
|
||
支持:图片上传、视频嵌入
|
||
│
|
||
▼
|
||
每 30 秒自动保存草稿 (PUT /api/app/blog/articles/{id}/draft)
|
||
│
|
||
▼ (用户点击发布)
|
||
前端弹窗二次确认:选择可见性(公开/私密/仅粉丝可见)
|
||
│
|
||
▼
|
||
POST /api/app/blog/articles
|
||
│
|
||
▼
|
||
后端校验:
|
||
1. 标题、内容非空
|
||
2. 分类存在且启用
|
||
3. 标签数 ≤ 5
|
||
4. 图片/内容安全检测
|
||
5. XSS 过滤
|
||
│
|
||
▼
|
||
生成摘要(纯文本截取前 120 字)
|
||
│
|
||
▼
|
||
设置 published_at = now
|
||
插入 blog_article + blog_article_tag
|
||
│
|
||
▼
|
||
返回成功 + 文章 ID
|
||
│
|
||
▼
|
||
前端跳转到文章详情页
|
||
```
|
||
|
||
### 6.4 多级评论流程
|
||
|
||
```
|
||
用户查看文章 → 查看评论列表 (GET /api/public/comments/{articleId})
|
||
│
|
||
▼
|
||
用户填写评论内容 → 点击发表
|
||
│
|
||
▼
|
||
POST /api/app/comments
|
||
│
|
||
▼
|
||
后端校验:
|
||
1. 文章存在且用户有权限查看
|
||
2. 内容非空、≤ 2000 字
|
||
3. 敏感词过滤 → 替换为 ***
|
||
4. XSS 过滤
|
||
│
|
||
▼
|
||
↓ 是回复评论? ↓ 否,顶级评论
|
||
│ │
|
||
查询父评论信息 │
|
||
检查层级: │
|
||
parent.level = 1 → 新评论 level = 2 │
|
||
parent.level = 2 → 新评论 level = 3 │
|
||
parent.level ≥ 3 → 自动设为 level=3 的平级 │
|
||
│ │
|
||
│ ▼
|
||
判断评论者是否为文章作者 → 设置 is_author
|
||
│
|
||
▼
|
||
评论审核开关判断:
|
||
ON → status=0 (待审核)
|
||
OFF → status=1 (已通过)
|
||
│
|
||
▼
|
||
异步发送通知:
|
||
如果开启了审核且通过,或审核关闭:
|
||
回复评论 → 通知被回复人
|
||
│
|
||
▼
|
||
返回评论结果
|
||
```
|
||
|
||
### 6.5 浏览量统计流程
|
||
|
||
```
|
||
用户访问文章详情页
|
||
│
|
||
▼
|
||
后端接收 GET /api/public/blog/articles/{id} 请求
|
||
│
|
||
▼
|
||
判断是否需要计数:
|
||
1. 当前用户 == 文章作者 → 不计
|
||
2. 当前用户已登录:
|
||
- 查询 blog_article_view 中 user_id + today 是否有记录
|
||
- 有 → 不计;无 → 计 1 次
|
||
3. 当前用户未登录(访客):
|
||
- 查询 blog_article_view 中 ip_address + today 是否有记录
|
||
- 有 → 不计;无 → 计 1 次
|
||
│
|
||
▼ (需要计数)
|
||
使用 CompletableFuture.runAsync() 异步执行:
|
||
1. 插入浏览记录 blog_article_view
|
||
2. UPDATE blog_article SET view_count = view_count + 1 WHERE id = ?
|
||
│
|
||
▼
|
||
返回文章详情(含当前 view_count)
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 开发规范
|
||
|
||
### 7.1 包命名规范
|
||
|
||
```
|
||
com.blog.{module}.{layer}
|
||
```
|
||
|
||
| 占位符 | 说明 | 示例 |
|
||
| :----- | :--- | :--- |
|
||
| {module} | 模块名 | common, framework, module.system, module.blog |
|
||
| {layer} | 分层 | controller, service, mapper, entity, dto, config |
|
||
|
||
### 7.2 命名约定
|
||
|
||
| 元素 | 规范 | 示例 |
|
||
| :--- | :--- | :--- |
|
||
| 类名 | UpperCamelCase | BlogArticleController, AuthService |
|
||
| 方法名 | lowerCamelCase | getUserById, createArticle |
|
||
| 变量名 | lowerCamelCase | articleList, currentUser |
|
||
| 常量 | UPPER_SNAKE_CASE | MAX_LOGIN_ATTEMPTS |
|
||
| 数据库表 | 小写+下划线 | blog_article, sys_user |
|
||
| 数据库字段 | 小写+下划线 | view_count, published_at |
|
||
| 请求映射路径 | 小写+连字符 | `/api/public/blog/articles` |
|
||
| JSON 字段 | lowerCamelCase | viewCount, publishedAt |
|
||
|
||
### 7.3 Controller 层规范
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/app/blog/articles")
|
||
@Tag(name = "博客文章管理", description = "前台博客文章接口")
|
||
public class BlogArticleController {
|
||
|
||
@GetMapping
|
||
@Operation(summary = "获取我的文章列表")
|
||
public ApiResult<PageResult<BlogArticleVO>> listMyArticles(
|
||
@Valid PageParam pageParam,
|
||
@RequestParam(required = false) Long categoryId) {
|
||
// ...
|
||
}
|
||
|
||
@PostMapping
|
||
@Operation(summary = "创建文章")
|
||
public ApiResult<IdVO> createArticle(@Valid @RequestBody ArticleCreateDTO dto) {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.4 Service 层规范
|
||
|
||
- 每个 Service 先定义接口,再写实现类
|
||
- 接口命名:`XxxService`(如 `BlogArticleService`)
|
||
- 实现类命名:`XxxServiceImpl`(如 `BlogArticleServiceImpl`)
|
||
- 使用 `@Service` 注解,事务方法加 `@Transactional(rollbackFor = Exception.class)`
|
||
|
||
### 7.5 异常处理规范
|
||
|
||
- **禁止**在 Controller 中使用 try-catch 捕获业务异常
|
||
- 业务异常直接 `throw new BusinessException(code, message)`
|
||
- 由 `GlobalExceptionHandler` 统一捕获并返回标准响应
|
||
|
||
### 7.6 日志规范
|
||
|
||
使用 Lombok `@Slf4j` 注解,日志级别使用规则:
|
||
|
||
| 级别 | 使用场景 |
|
||
| :--- | :------- |
|
||
| ERROR | 系统异常、数据库错误、调用第三方服务失败 |
|
||
| WARN | 参数校验失败、业务异常(如登录失败)、限流触发 |
|
||
| INFO | 关键业务流程(如用户注册成功、文章发布)、接口调用入参出参(可开关) |
|
||
| DEBUG | 开发调试信息,禁止在生产环境输出 |
|
||
|
||
### 7.7 安全编码规范
|
||
|
||
| 要求 | 实现方式 |
|
||
| :--- | :------- |
|
||
| 密码存储 | 始终使用 bcrypt 哈希,禁止 MD5/SHA1 明文存储 |
|
||
| SQL 注入防护 | 全程使用 MyBatis-Plus 参数化查询,禁止 `${}` 拼接 |
|
||
| XSS 防护 | 用户输入输出前使用 Jsoup.clean() 过滤 HTML 标签 |
|
||
| CSRF 防护 | 前端请求需携带自定义 Header(如 `X-Requested-With: XMLHttpRequest`),后端校验 Origin |
|
||
| 敏感信息脱敏 | 日志中密码、Token 等使用 `****` 替代 |
|
||
| 文件上传安全 | 校验 Content-Type 和文件魔术头,限制可执行文件上传 |
|
||
| 接口防刷 | 核心接口使用限流注解 `@RateLimit` |
|
||
|
||
### 7.8 限流注解使用
|
||
|
||
```java
|
||
@Target(ElementType.METHOD)
|
||
@Retention(RetentionPolicy.RUNTIME)
|
||
public @interface RateLimit {
|
||
String key() default ""; // 限流 key
|
||
int maxCount() default 10; // 最大访问次数
|
||
int duration() default 60; // 时间窗口(秒)
|
||
TimeUnit unit() default TimeUnit.SECONDS;
|
||
}
|
||
```
|
||
|
||
使用 Redis 滑动窗口计数器实现:
|
||
|
||
```java
|
||
// 登录接口限流:同一 IP 5分钟内最多 10 次
|
||
@RateLimit(key = "login:ip:#{ip}", maxCount = 10, duration = 300)
|
||
|
||
// 注册接口限流:同一 IP 1小时内最多 3 次
|
||
@RateLimit(key = "register:ip:#{ip}", maxCount = 3, duration = 3600)
|
||
|
||
// 评论提交限流:同一用户 1分钟内最多 5 次
|
||
@RateLimit(key = "comment:user:#{userId}", maxCount = 5, duration = 60)
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 项目配置文件规范
|
||
|
||
### 8.1 application.yml 核心配置
|
||
|
||
```yaml
|
||
server:
|
||
port: 8080
|
||
|
||
spring:
|
||
datasource:
|
||
url: jdbc:mysql://localhost:3306/blog_system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
|
||
username: root
|
||
password: ${DB_PASSWORD}
|
||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||
data:
|
||
redis:
|
||
host: localhost
|
||
port: 6379
|
||
password: ${REDIS_PASSWORD}
|
||
servlet:
|
||
multipart:
|
||
max-file-size: 10MB
|
||
max-request-size: 50MB
|
||
flyway:
|
||
enabled: true
|
||
locations: classpath:db/migration
|
||
baseline-on-migrate: true
|
||
|
||
jwt:
|
||
secret: ${JWT_SECRET}
|
||
access-token-expire: 7200 # AccessToken 有效期(秒)
|
||
refresh-token-expire: 86400 # RefreshToken 有效期(秒,记住我模式 14 天)
|
||
refresh-token-expire-remember: 1209600
|
||
|
||
app:
|
||
upload:
|
||
image-max-size: 10485760 # 10MB
|
||
allowed-types: image/jpeg,image/png,image/gif,image/webp
|
||
rate-limit:
|
||
login-max-attempts: 5 # 登录最大失败次数
|
||
login-lock-duration: 15 # 锁定分钟数(用户)
|
||
admin-login-lock-duration: 30 # 锁定分钟数(管理员)
|
||
ip-block-threshold: 10 # IP 封禁阈值
|
||
ip-block-duration: 15 # IP 封禁分钟数
|
||
```
|
||
|
||
### 8.2 Maven 多模块依赖管理
|
||
|
||
```xml
|
||
<!-- 父 POM 关键依赖版本管理 -->
|
||
<properties>
|
||
<spring-boot.version>3.4.x</spring-boot.version>
|
||
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||
<jjwt.version>0.12.6</jjwt.version>
|
||
<springdoc.version>2.7.0</springdoc.version>
|
||
<jsoup.version>1.18.1</jsoup.version>
|
||
</properties>
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 构建与部署
|
||
|
||
### 9.1 一体化构建流程
|
||
|
||
```
|
||
mvn clean package
|
||
```
|
||
|
||
执行步骤:
|
||
1. `blog-framework` → 编译
|
||
2. `blog-module-system` → 编译
|
||
3. `blog-module-blog` → 编译
|
||
4. `blog-module-comment` → 编译
|
||
5. `blog-frontend` → **自动安装 Node.js 依赖 → npm run build → 复制 dist/ 到 blog-admin 的 static 目录**
|
||
6. `blog-admin` → 打包为 FAT JAR(包含所有模块 + 前端静态资源)
|
||
|
||
### 9.2 部署方式
|
||
|
||
```bash
|
||
# 生产环境启动
|
||
java -jar blog-admin.jar --spring.profiles.active=prod
|
||
|
||
# 或使用外部配置
|
||
java -jar blog-admin.jar --spring.config.location=/path/to/application-prod.yml
|
||
```
|
||
|
||
FAT JAR 包含:
|
||
- 所有后端模块的字节码
|
||
- MySQL Driver、Redis Client 等依赖
|
||
- 前端静态资源(HTML/CSS/JS)
|
||
- 内嵌 Tomcat 容器
|
||
|
||
---
|
||
|
||
## 10. 数据库初始化脚本(Flyway)
|
||
|
||
Flyway 脚本位于 `blog-admin/src/main/resources/db/migration/`:
|
||
|
||
```
|
||
db/migration/
|
||
├── V1__init_schema.sql # 建表:sys_user, sys_user_session, sys_login_log, blog_category, blog_tag, blog_article, blog_article_tag, blog_comment, sys_user_follow, sys_notification, sys_config, sys_sensitive_word, blog_article_view
|
||
├── V2__init_data.sql # 初始化:超级管理员账号、默认分类、默认配置项
|
||
├── V3__init_indexes.sql # 建索引:全文索引、联合索引
|
||
└── V4__init_sensitive_words.sql # 初始化敏感词库
|
||
```
|
||
|
||
**V2 初始化数据关键内容**:
|
||
|
||
```sql
|
||
-- 超级管理员(密码: Admin@2026,bcrypt 哈希)
|
||
INSERT INTO sys_user (username, email, password, nickname, user_type, status, email_verified)
|
||
VALUES ('admin', 'admin@blogsystem.com',
|
||
'$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
|
||
'超级管理员', 0, 0, 1);
|
||
|
||
-- 默认分类
|
||
INSERT INTO blog_category (name, description) VALUES
|
||
('技术教程', '编程语言、框架、工具等技术教程'),
|
||
('生活随笔', '日常生活感悟与记录'),
|
||
('资源分享', '优质资源与工具推荐');
|
||
|
||
-- 默认配置
|
||
INSERT INTO sys_config (config_key, config_value, description) VALUES
|
||
('comment_audit_enabled', 'false', '评论审核开关'),
|
||
('oauth_wechat_enabled', 'false', '微信登录开关'),
|
||
('oauth_github_enabled', 'false', 'GitHub 登录开关');
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 接口文档与测试
|
||
|
||
- **开发环境**:访问 `/swagger-ui.html` 查看 Swagger 接口文档(由 SpringDoc OpenAPI 自动生成)
|
||
- **API 测试**:所有接口附带 JSON 请求/响应示例,可直接导入 Postman
|
||
- **单元测试**:每个 Service 层方法需编写对应的单元测试,覆盖率 ≥ 80%(使用 JUnit 5 + Mockito)
|
||
- **集成测试**:关键业务流程(登录、注册、发布、评论)需编写集成测试
|
||
|
||
---
|
||
|
||
> **文档结束**
|
||
>
|
||
> 本文档基于 `blog-system-requirements.md` 需求文档编写,覆盖了后端技术架构、模块划分、接口定义、数据模型、业务流程及开发规范六大核心内容。AI 编程可依据本文档中定义的模块结构、数据字段、接口路径和业务逻辑完成系统的完整开发。前后端一体化构建,通过 `mvn clean package` 一条命令即可生成可直接部署的 FAT JAR。
|