You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Mach-CMS 联调测试 Checklist
版本: 0.2.0
日期: 2026-08-09
用途: Agent1(后端)+ Agent2(前端)验收 + 联调
0. 环境准备(前置条件)
| # |
检查项 |
通过标准 |
结果 |
| 0.1 |
PostgreSQL 16 已启动 |
psql -U postgres -c "\l" 能列出数据库 |
☐ |
| 0.2 |
数据库 cms 已创建 |
createdb -U postgres cms 执行成功或已存在 |
☐ |
| 0.3 |
Node.js 20+ 已安装 |
node -v 输出 v20.x |
☐ |
| 0.4 |
JDK 21 已安装 |
java -version 输出 21 |
☐ |
| 0.5 |
openapi.yaml 已放置于后端仓库根目录 |
文件存在且版本为 0.1.0+ |
☐ |
| 0.6 |
openapi.yaml 已放置于前端仓库可引用路径 |
openapitools.json 中 inputSpec 路径正确 |
☐ |
1. Agent1 后端独立验收
1.1 构建与启动
| # |
检查项 |
通过标准 |
结果 |
| 1.1.1 |
./gradlew build 无编译错误 |
控制台 BUILD SUCCESSFUL |
☐ |
| 1.1.2 |
./gradlew test 通过 |
所有测试通过(至少包含 Article 状态机测试) |
☐ |
| 1.1.3 |
./gradlew bootRun 正常启动 |
控制台出现 Started CmsApplication 且无 ERROR |
☐ |
| 1.1.4 |
Actuator 健康检查 |
GET http://localhost:8080/actuator/health 返回 {"status":"UP"} |
☐ |
| 1.1.5 |
Swagger UI 可访问 |
http://localhost:8080/swagger-ui.html 正常渲染 |
☐ |
1.2 数据库迁移
| # |
检查项 |
通过标准 |
结果 |
| 1.2.1 |
Flyway V1 迁移成功 |
flyway_schema_history 表有 V1 记录 |
☐ |
| 1.2.2 |
Flyway V2 迁移成功 |
flyway_schema_history 表有 V2 记录;articles/tags/article_tags 表结构正确 |
☐ |
| 1.2.3 |
Flyway V3 迁移成功 |
flyway_schema_history 表有 V3 记录;articles 表有触发器 article_search_vector_trigger |
☐ |
| 1.2.4 |
jOOQ 代码生成成功 |
build/generated-sources/jooq/top/jmto/mach_cms/jooq/ 存在 Kotlin 文件 |
☐ |
1.3 认证接口(独立测试)
| # |
检查项 |
通过标准 |
结果 |
| 1.3.1 |
用户注册 |
POST /api/auth/register 返回 Response<TokenPair>,code=0 |
☐ |
| 1.3.2 |
用户登录 |
POST /api/auth/login 返回 Response<TokenPair>,包含 accessToken + refreshToken |
☐ |
| 1.3.3 |
Token 刷新 |
POST /api/auth/refresh 传入 refreshToken,返回新的 TokenPair |
☐ |
| 1.3.4 |
用户登出 |
POST /api/auth/logout 返回 code=0;再次用旧 refreshToken 刷新应失败 |
☐ |
| 1.3.5 |
密码加密 |
直接查 PG users 表,password 字段为 BCrypt 哈希(以 $2a$ 开头) |
☐ |
1.4 文章接口(独立测试)
| # |
检查项 |
通过标准 |
结果 |
| 1.4.1 |
创建文章(Admin) |
POST /api/admin/articles 带 Bearer Token,返回 Response<ArticleDetail>,slug 自动生成 |
☐ |
| 1.4.2 |
文章 slug 唯一性 |
同标题创建第二篇文章,slug 自动加后缀(如 hello-world-1) |
☐ |
| 1.4.3 |
查询文章详情(前台) |
GET /api/articles/{slug} 无认证可访问,返回结构与 YAML 一致 |
☐ |
| 1.4.4 |
文章列表(前台) |
GET /api/articles?page=1&size=10 返回 Response<PageResult<ArticleListItem>> |
☐ |
| 1.4.5 |
状态过滤 |
GET /api/articles?status=PUBLISHED 只返回已发布文章 |
☐ |
| 1.4.6 |
发布文章 |
PATCH /api/admin/articles/{id}/publish 后,status 变为 PUBLISHED,publishedAt 有值 |
☐ |
| 1.4.7 |
归档文章 |
PATCH /api/admin/articles/{id}/archive 后,status 变为 ARCHIVED |
☐ |
| 1.4.8 |
状态机保护 |
对已发布文章再次调用 publish,返回业务错误(非 500) |
☐ |
| 1.4.9 |
更新文章 |
PUT /api/admin/articles/{id} 更新后内容正确,updatedAt 变化 |
☐ |
| 1.4.10 |
删除文章 |
DELETE /api/admin/articles/{id} 后,再次查询返回 404 或 null data |
☐ |
| 1.4.11 |
未认证访问 Admin |
POST /api/admin/articles 不带 Token,返回 401 |
☐ |
| 1.4.12 |
无权限访问 Admin |
用 VISITOR 角色 Token 访问 Admin 接口,返回 403 |
☐ |
1.5 搜索接口(独立测试)
| # |
检查项 |
通过标准 |
结果 |
| 1.5.1 |
全文搜索 |
GET /api/articles/search?q=spring 返回相关文章列表 |
☐ |
| 1.5.2 |
搜索结果排序 |
标题含关键词的文章排在内容含关键词的前面(ts_rank 权重 A>B>C) |
☐ |
| 1.5.3 |
空关键词处理 |
q= 或缺失,返回业务错误(code ≠ 0)或空列表 |
☐ |
| 1.5.4 |
搜索触发器生效 |
创建文章后立即搜索标题关键词,能搜到结果 |
☐ |
1.6 标签接口(独立测试)
| # |
检查项 |
通过标准 |
结果 |
| 1.6.1 |
创建标签(Admin) |
POST /api/admin/tags 返回 Response<Tag> |
☐ |
| 1.6.2 |
标签列表(前台) |
GET /api/tags 返回所有标签,带 articleCount |
☐ |
| 1.6.3 |
文章关联标签 |
创建文章时传入 tagIds,详情页返回正确 tagNames |
☐ |
| 1.6.4 |
删除标签(Admin) |
DELETE /api/admin/tags/{id} 成功,关联文章不再显示该标签 |
☐ |
1.7 媒体接口(独立测试)
| # |
检查项 |
通过标准 |
结果 |
| 1.7.1 |
文件上传 |
POST /api/media/upload 上传图片,返回 Response<StoredFile>,url 可访问 |
☐ |
| 1.7.2 |
文件本地存储 |
uploads/images/ 目录下存在上传的文件 |
☐ |
| 1.7.3 |
目录遍历防护 |
上传时篡改 directory 参数为 ../etc,被拒绝或存储到安全路径 |
☐ |
1.8 响应格式一致性
| # |
检查项 |
通过标准 |
结果 |
| 1.8.1 |
所有业务接口返回 Response<T> |
抽查 5 个接口,结构均为 {code, message, data} |
☐ |
| 1.8.2 |
成功时 code=0 |
所有成功响应 code 字段为 0 |
☐ |
| 1.8.3 |
错误时 code≠0 |
业务错误(如 slug 重复)code 为对应 ErrorCode 值 |
☐ |
| 1.8.4 |
分页接口返回 PageResult |
列表接口包含 list/total/page/size/totalPages |
☐ |
| 1.8.5 |
Swagger UI 与 YAML 一致 |
Swagger 中显示的接口路径、参数、响应模型与 openapi.yaml 完全一致 |
☐ |
2. Agent2 前端独立验收
2.1 构建与启动
| # |
检查项 |
通过标准 |
结果 |
| 2.1.1 |
npm install 成功 |
无依赖安装错误 |
✓ |
| 2.1.2 |
npm run api:generate 成功 |
src/generated/api/ 目录存在且包含 api.ts + models/ |
✓ |
| 2.1.3 |
npm run dev 正常启动 |
控制台无 ERROR,访问 http://localhost:5173 正常 |
✓ |
| 2.1.4 |
TypeScript 严格模式无错误 |
npm run build 通过(或 vue-tsc --noEmit 无类型错误) |
✓ |
2.2 代码规范检查
| # |
检查项 |
通过标准 |
结果 |
| 2.2.1 |
无手写 API 类型 |
src/generated/api/ 外无自定义的 ArticleDetail/TokenPair 等接口 |
✓ |
| 2.2.2 |
统一 Axios 客户端 |
全局搜索 import axios from 'axios',除 client.ts 外无其他直接引用 |
✓ |
| 2.2.3 |
<script setup lang="ts"> |
所有 .vue 文件使用组合式 API + TS |
✓ |
| 2.2.4 |
无 any 类型滥用 |
全局搜索 : any,业务代码中不超过 3 处(配置/工具类除外) |
✓ |
2.3 前台页面(无需登录)
| # |
检查项 |
通过标准 |
结果 |
| 2.3.1 |
首页加载文章列表 |
打开 /,能看到文章卡片列表,包含标题、摘要、时间、标签 |
✓ |
| 2.3.2 |
文章卡片点击跳转 |
点击卡片进入 /post/:slug,URL 正确 |
✓ |
| 2.3.3 |
文章详情页渲染 |
详情页显示标题、作者、发布时间、标签、内容 |
✓ |
| 2.3.4 |
标签云/列表 |
首页或独立页面展示标签列表,显示文章计数 |
✓ |
| 2.3.5 |
搜索功能 |
输入关键词后跳转到搜索页,展示结果列表 |
✓ |
| 2.3.6 |
分页组件 |
文章列表底部有分页,点击切换页码后内容更新 |
✓ |
2.4 认证流程
| # |
检查项 |
通过标准 |
结果 |
| 2.4.1 |
登录页面 |
/login 能正常显示表单(用户名、密码) |
✓ |
| 2.4.2 |
登录成功 |
输入正确凭据后,localStorage 存入 token 和 refreshToken,跳转管理后台 |
✓ |
| 2.4.3 |
登录失败提示 |
输入错误密码,页面显示错误信息(alert 或文字提示) |
✓ |
| 2.4.4 |
路由守卫 |
未登录直接访问 /admin,自动跳转 /login |
✓ |
| 2.4.5 |
已登录防回退 |
已登录用户访问 /login,自动跳转 /admin |
✓ |
| 2.4.6 |
退出登录 |
点击退出后,localStorage 清除 Token,跳转首页 |
✓ |
| 2.4.7 |
刷新保持登录 |
登录后刷新浏览器(F5),仍保持登录状态(Token 未过期时) |
✓ |
2.5 管理后台(需登录)
| # |
检查项 |
通过标准 |
结果 |
| 2.5.1 |
后台布局 |
/admin 显示侧边栏(文章管理、标签管理)+ 顶部栏 |
✓ |
| 2.5.2 |
文章列表 |
/admin/articles 显示表格:标题、状态、发布时间、操作按钮 |
✓ |
| 2.5.3 |
状态标签颜色 |
DRAFT=灰色/默认, PUBLISHED=绿色, ARCHIVED=橙色/红色 |
✓ |
| 2.5.4 |
创建文章 |
点击"新建"进入编辑器,填写后保存,列表页出现新文章 |
✓ |
| 2.5.5 |
编辑文章 |
点击"编辑"进入编辑器,表单预填充现有数据,保存后更新 |
✓ |
| 2.5.6 |
发布文章 |
列表页点击"发布",状态变为 PUBLISHED,前台可看到 |
✓ |
| 2.5.7 |
归档文章 |
列表页点击"归档",状态变为 ARCHIVED,前台不可见 |
✓ |
| 2.5.8 |
删除文章 |
点击"删除"后文章从列表消失,前台无法访问 |
✓ |
| 2.5.9 |
标签管理 |
/admin/tags 显示标签列表,可新增、编辑、删除 |
✓ |
| 2.5.10 |
表单校验 |
创建文章时标题/内容为空,提交前前端拦截(或提交后显示后端错误) |
✓ |
2.6 文件上传
| # |
检查项 |
通过标准 |
结果 |
| 2.6.1 |
封面图上传 |
文章编辑器中上传图片,返回 URL 填入表单 |
✓ |
| 2.6.2 |
上传进度/反馈 |
上传成功后有明确反馈(URL 显示或提示) |
✓ |
3. 前后端联调验收(端到端)
3.1 完整业务流程
按以下顺序执行,全部通过才算联调成功:
| # |
场景 |
操作步骤 |
预期结果 |
结果 |
| 3.1.1 |
新用户注册 → 登录 → 创建文章 |
1. 前端注册页面填写用户名密码 2. 登录 3. 进入后台新建文章 4. 填写标题/内容/标签 5. 保存 |
文章出现在后台列表,状态为 DRAFT |
☐ |
| 3.1.2 |
发布文章 → 前台可见 |
1. 后台点击"发布" 2. 前台首页刷新 |
文章出现在首页卡片中 |
☐ |
| 3.1.3 |
前台阅读 → 搜索命中 |
1. 点击文章卡片进入详情 2. 复制文章标题关键词 3. 前台搜索框输入关键词 |
搜索结果包含该文章,且排名靠前 |
☐ |
| 3.1.4 |
多标签关联 |
1. 后台创建 2 个标签 2. 创建文章时选择这 2 个标签 3. 保存并发布 |
前台详情页显示 2 个标签;标签列表页计数正确 |
☐ |
| 3.1.5 |
Token 过期自动刷新 |
1. 登录后等待 15 分钟(或手动改后端 Token 有效期为 5 秒测试) 2. 在后台执行任意操作 |
操作成功,无感知刷新;网络面板可见 /auth/refresh 请求 |
☐ |
| 3.1.6 |
并发编辑冲突(可选) |
1. 用户 A 登录编辑文章 2. 用户 B 登录编辑同一文章 3. 先后保存 |
后保存者覆盖前者(或后端返回版本冲突错误) |
☐ |
| 3.1.7 |
权限隔离 |
1. 注册一个普通用户(默认 VISITOR) 2. 用该用户 Token 尝试访问后台 |
前端路由守卫拦截或后端返回 403 |
☐ |
3.2 数据一致性
| # |
检查项 |
通过标准 |
结果 |
| 3.2.1 |
前端显示与后端数据一致 |
抽查 3 篇文章,前端详情页内容与后端 GET /api/articles/{slug} 返回一致 |
☐ |
| 3.2.2 |
分页数据一致 |
前台分页总数与后端 total 字段一致 |
☐ |
| 3.2.3 |
标签计数准确 |
标签列表页显示的文章数与后端 articleCount 一致 |
☐ |
| 3.2.4 |
搜索关键词高亮(可选) |
搜索结果中关键词有视觉高亮(或至少结果正确) |
☐ |
4. CDD 契约一致性检查(核心)
这是最关键的检查项,任何一项不通过,整个联调失败。
| # |
检查项 |
通过标准 |
结果 |
| 4.1 |
YAML 与后端 Swagger 一致 |
对比 openapi.yaml 和 http://localhost:8080/v3/api-docs,路径/参数/模型无差异 |
☐ |
| 4.2 |
YAML 与前端生成类型一致 |
前端 src/generated/api/models/ 中的类型与 YAML components/schemas 一一对应 |
☐ |
| 4.3 |
后端响应与 YAML 一致 |
用 Swagger UI 或 curl 调用接口,响应 JSON 结构与 YAML 中定义的 Response<T> 一致 |
☐ |
| 4.4 |
前端请求与 YAML 一致 |
浏览器 DevTools Network 面板中,前端发出的请求路径/参数与 YAML 一致 |
☐ |
| 4.5 |
无契约外接口 |
后端不存在 YAML 中未定义的接口;前端不调用 YAML 中未定义的接口 |
☐ |
| 4.6 |
枚举值一致 |
ArticleStatus/UserRole 前后端枚举字符串完全一致(大小写敏感) |
☐ |
5. 性能与安全基线
| # |
检查项 |
通过标准 |
结果 |
| 5.1 |
首页加载时间 |
文章列表页首屏渲染 < 2s(本地环境,10 篇文章) |
☐ |
| 5.2 |
API 响应时间 |
单次 API 调用 < 200ms(本地环境,排除大数据量查询) |
☐ |
| 5.3 |
搜索响应时间 |
GET /api/articles/search 千级数据 < 100ms |
☐ |
| 5.4 |
XSS 防护 |
文章 content 中包含 <script>alert(1)</script>,前台渲染时不执行脚本 |
☐ |
| 5.5 |
SQL 注入防护 |
搜索关键词输入 ' OR 1=1 --,后端正常处理不报错 |
☐ |
| 5.6 |
目录遍历防护 |
上传时 directory=../../etc 被安全处理 |
☐ |
| 5.7 |
JWT 安全 |
Token 中不包含敏感信息(密码等);Secret 不是硬编码的弱密钥 |
☐ |
6. 问题记录模板
联调中发现的问题按以下格式记录:
Issue #1 — 搜索接口复现为后端旧进程所致(已解决)
- 发现时间: 2026-08-10 00:00
- 发现人: Agent2(前端)
- 问题描述: 初测时
GET /api/articles/search?q=Spring 返回 {code:3001,"文章不存在: search"}。后端重启(./gradlew bootRun 全新进程)后同一请求返回 code=0 正确搜索结果,确认该现象由旧后端进程(未包含 SearchController 的旧构建)造成,非契约/前端问题。
- 复现步骤: 旧进程下
curl "http://localhost:8080/api/articles/search?q=spring"
- 预期结果: 返回搜索结果
- 实际结果: 重启后返回
{code:0, data:{list:[...]}},2.3.5/1.5.1 通过
- 根因分析: 后端旧进程加载了过期构建产物,未包含搜索路由
- 责任方: Agent1(后端,运维)
- 修复方案: 联调前确保后端为最新
bootRun 进程
- 验证结果: ☐ 已解决(2026-08-10 复验通过)
Issue #2 — 后端 POST /auth/logout 未吊销 Refresh Token
- 发现时间: 2026-08-10 00:05
- 发现人: Agent2(前端)
- 影响: 前端 2.4.6 通过(本地清理 localStorage 正常);仅后端吊销语义缺失(1.3.4)
- 问题描述: 前端
auth.logout() 调 POST /api/auth/logout 后,旧 refreshToken 仍可通过 POST /api/auth/refresh 换取新 TokenPair,令牌吊销语义失效(违反 checklist 1.3.4)。
- 复现步骤: 1) 登录获取 refreshToken;2) 调
/auth/logout(code=0);3) 用旧 refreshToken 调 /auth/refresh → 仍返回 code=0 新 token(在最新 bootRun 进程上复验一致)
- 预期结果: logout 后旧 refreshToken 刷新应失败(401/业务错误)
- 实际结果: 刷新成功并返回新的 TokenPair
- 根因分析: 后端登出逻辑未将 refreshToken 加入黑名单/吊销列表(JWT 无状态未记录失效)
- 责任方: Agent1(后端)
- 修复方案: 服务端维护 refreshToken 吊销集合(DB/redis),refresh 时校验
- 验证结果: ☐ 待修复
Issue #3 — 契约路径加 /api 前缀后前端 baseURL 适配(已处理)
- 发现时间: 2026-08-10 00:20
- 发现人: Agent2(前端,承接 Agent1 提示)
- 问题描述: Agent1 将契约路径改为带
/api 前缀(servers.url=http://localhost:8080)。重新 npm run api:generate 后,生成的 localVarPath 变为 /api/auth/...、方法名变为 apiXxx 前缀、新增 PublicApi/UserApi。若前端 axios baseURL 仍为 /api,会拼成 /api/api/... → 404。
- Agent1 提示: 需把 client.ts 中
new XxxApi(config, '/api', ...) 的 /api 改为 ''。经核实不正确/不完整:生成的 common.ts 拼接逻辑为 axios.defaults.baseURL ? '' : (configuration?.basePath ?? basePath),axios 的 baseURL 优先于 XxxApi 的 basePath 参数;且 axios 会自行 combineURLs。若仅改 XxxApi 参数而保留 baseURL=/api,仍会双前缀。
- 实际修复: 将 axios 实例
baseURL 从 /api 改为 /(各 XxxApi 的 /api basePath 参数无害保留),并批量重命名方法调用(articlesGet→apiArticlesGet 等)。实测 /api/articles 单前缀、经 vite 代理到 8080 正常,2.3~2.6 全部用例复验 PASS。
- 责任方: 前端(Agent2)
- 验证结果: ☑ 已处理(2026-08-10 复验 26/26 通过)
### Issue #{编号}
- **发现时间**: YYYY-MM-DD HH:MM
- **发现人**: Agent1/Agent2/架构师
- **问题描述**:
- **复现步骤**:
- **预期结果**:
- **实际结果**:
- **根因分析**:
- **责任方**: Agent1(后端)/ Agent2(前端)/ openapi.yaml(契约)
- **修复方案**:
- **验证结果**: ☐ 待修复 / ☐ 已修复 / ☐ 已验证
验收签字
| 角色 |
姓名 |
日期 |
签字 |
| 架构 Owner |
|
|
|
| Agent1(后端) |
|
|
|
| Agent2(前端) |
|
|
|
附录:快速测试命令
# 后端健康检查
curl http://localhost:8080/actuator/health
# 注册
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 登录
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 创建文章(替换 $TOKEN)
curl -X POST http://localhost:8080/api/admin/articles \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Hello World","content":"This is a test article."}'
# 搜索
curl "http://localhost:8080/api/articles/search?q=hello&page=1&size=10"
# 前台列表(无认证)
curl "http://localhost:8080/api/articles?page=1&size=10"