mach-cms的前台,用vue写的
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.
 
 
 
 
 

19 KiB

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.jsoninputSpec 路径正确

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 存入 tokenrefreshToken,跳转管理后台
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.yamlhttp://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 参数无害保留),并批量重命名方法调用(articlesGetapiArticlesGet 等)。实测 /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"