Files
Jog/doc/jog-system-technical-design.md
2026-05-17 15:55:06 +08:00

1517 lines
55 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 博客系统技术设计说明书
| 文档版本 | 修订日期 | 修订人 | 修订内容 |
| :------ | :------- | :----- | :------- |
| V1.0 | 2026-05-17 | — | 初始版本,完整技术设计 |
---
## 1. 项目概述
### 1.1 项目定位
基于需求文档 `blog-system-requirements.md` 定义的博客系统功能需求与非功能性需求,本技术设计文档将作为 AI 编程的唯一输入依据,指导系统从零开始的完整开发。
### 1.2 技术栈选型
| 层级 | 技术 | 版本/说明 |
| :--- | :--- | :-------- |
| 后端框架 | Spring Boot | 3.4.x(最新稳定版) |
| JDK | OpenJDK | 21LTS |
| 项目构建 | Maven | 3.9+ |
| 数据库 | MySQL | 8.0+ |
| 缓存 | Redis | 7.2.xBSD 3-Clause 许可证,用于会话管理、验证码、限流计数) |
| ORM | MyBatis-Plus | 3.5.x |
| 认证鉴权 | Spring Security + JWT | 无状态令牌认证 |
| 接口文档 | SpringDoc OpenAPI | 2.xSwagger UI |
| 前端框架 | Vue.js | 3.xVite 构建) |
| UI 组件库 | Element Plus | 2.x |
| 富文本编辑器 | TiptapMIT 许可证)/ 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 认证鉴权架构
使用 **JWTJSON 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 | 父评论 IDNULL 表示顶级评论) |
| 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. 生成 AccessToken2h 有效)+ RefreshToken24h 或 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_atstatus=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)
生成邮箱验证 TokenUUID,有效期 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@2026bcrypt 哈希)
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。