init: consolidate all ephron.ren PRDs and docs

This commit is contained in:
Ubuntu
2026-05-15 10:39:54 +08:00
parent 9568533314
commit ee8cddf8b8
21 changed files with 6991 additions and 2 deletions

949
qa/test-plan.md Normal file
View File

@@ -0,0 +1,949 @@
# ephron.ren 功能测试计划
**版本**: v4.0
**日期**: 2026-05-03
**站点**: https://www.ephron.ren/
**仓库**: https://gitea.ephron.ren/ephron_ren/ephron.ren
---
## 一、测试概览
### 1.1 服务架构
| 服务 | 地址 | 本地端口 | 说明 |
|------|------|----------|------|
| Home | https://www.ephron.ren | 8000 | 个人主页 + 内容管理后台 |
| Auth | https://auth.ephron.ren | 8001 | 登录注册 + 用户管理 + RBAC + 审计 |
| Blog | https://blog.ephron.ren | 8002 | 博客文章 + 评论 + 点赞 + 搜索 |
| Canvas | https://canvas.ephron.ren | 8003 | AI 生成页面作品管理 |
| Prompt | https://prompt.ephron.ren | 8004 | 提示词 CRUD + 版本管理 |
### 1.2 测试账号
| 角色 | 用户名 | 密码 | 用途 |
|------|--------|------|------|
| 管理员 | Elaina_admin | Elaina2026! | 测试全部管理后台功能 |
| 普通用户 | Elaina_user | Elaina2026! | 测试前台功能 + 权限拦截 |
| 邀请码 | 7CQ0-GE9S-L6QS | - | 注册流程测试 |
### 1.3 认证机制
- 跨服务 Cookie `ephron_auth`,域 `.ephron.ren`
- RBAC 权限模型:`user`(10) < `admin`(20) < `owner`(30)
- 细粒度权限键:如 `blog.post.create_draft``auth.user.view_active`
### 1.4 优先级定义
| 级别 | 含义 | 说明 |
|------|------|------|
| P0 | 核心功能 | 阻塞用户使用,必须通过 |
| P1 | 重要功能 | 影响体验,应尽快修复 |
| P2 | 次要功能 | 可延后处理 |
### 1.5 测试统计
| 版本 | 测试用例数 |
|------|:----------:|
| v3.0 | 366 |
| v4.0 全量 | **453** |
---
## 二、测试用例
---
### 模块 1Home 主页 (www.ephron.ren)
#### 1.1 公开页面
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| H-001 | 首页加载 | 访问 / | HTTP 200正常渲染 | 无 | P0 |
| H-002 | CSS/JS/图片 | 检查所有静态资源 | 全部 200 | 无 | P0 |
| H-003 | 响应式布局 | 调整窗口宽度至 375px/768px/1440px | 布局自适应,无溢出 | 无 | P1 |
| H-004 | 导航→博客 | 点击「博客」 | 跳转 blog.ephron.ren | 无 | P0 |
| H-005 | 导航→画布 | 点击「画布」 | 跳转 canvas.ephron.ren | 无 | P0 |
| H-006 | 导航→提示词 | 点击「提示词」 | 跳转 prompt.ephron.ren | 无 | P0 |
| H-007 | 登录链接 | 未登录时点击「未登录」 | 跳转 auth.ephron.ren/login | 无 | P0 |
| H-008 | 登出链接 | 已登录时点击用户名 | 显示登出选项 | 任意 | P1 |
| H-009 | 个人信息 | 检查 hero 区域 | 显示姓名、技能标签 | 无 | P1 |
| H-010 | 联系按钮 | 点击「联系我」 | 弹出邮箱/复制功能 | 无 | P2 |
| H-011 | 备案链接 | 点击 ICP/公安备案 | 跳转官方网站 | 无 | P2 |
| H-012 | 健康检查 | 访问 /health | 返回 `{"status":"ok"}` | 无 | P2 |
#### 1.2 管理后台 (/admin)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| H-013 | Admin 首页 | 以 Elaina_admin 访问 /admin | 显示内容编辑器,含草稿/发布/丢弃功能 | 管理员 | P0 |
| H-014 | Admin 权限拦截 | 以 Elaina_user 访问 /admin | 重定向到登录页或提示权限不足 | 普通 | P0 |
| H-015 | Admin 未登录 | 未登录访问 /admin | 重定向到 auth.ephron.ren/login?redirect=... | 无 | P0 |
| H-016 | 保存草稿 | 编辑内容后 POST /admin/save | 保存成功,不影响已发布版本 | 管理员 | P0 |
| H-016a | 结构化 JSON 内容 | 编辑 experience/projects/skills 各 section | 各 section 独立保存,含 is_draft 标记 | 管理员 | P1 |
| H-016b | Service Token 拒绝 | 带 Authorization: Bearer 访问 /admin | 返回 403审计日志记录 | 服务 | P0 |
| H-017 | 发布内容 | POST /admin/publish | 发布成功,草稿被清除 | 管理员 | P0 |
| H-018 | 丢弃草稿 | POST /admin/discard | 草稿被丢弃,已发布版本不变 | 管理员 | P1 |
| H-019 | CSRF 保护 | 不带 csrf_token 提交表单 | 返回「CSRF token 验证失败」 | 管理员 | P0 |
| H-020 | 速率限制 | 1 分钟内保存 21+ 次 | 第 21 次返回 429 | 管理员 | P1 |
| H-021 | Service Token 拦截 | 带 Authorization: Bearer *** 访问 /admin | 返回 403 | 服务 | P1 |
| H-022 | 登出 | POST /admin/logout | Cookie 清除,跳转首页 | 管理员 | P0 |
---
### 模块 2Auth 认证服务 (auth.ephron.ren)
#### 2.1 登录页面
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-001 | 登录页加载 | 访问 /login | 200显示用户名/密码表单 | 无 | P0 |
| A-002 | 空表单提交 | 不填写直接登录 | 浏览器原生验证拦截 | 无 | P0 |
| A-003 | 错误凭证 | 输入 wrong/wrong | 显示错误消息,不跳转 | 无 | P0 |
| A-004 | 管理员登录 | Elaina_admin / Elaina2026! | 登录成功,设置 Cookie | 管理员 | P0 |
| A-005 | 普通用户登录 | Elaina_user / Elaina2026! | 登录成功,设置 Cookie | 普通 | P0 |
| A-006 | 登录后跳转 | 带 redirect 参数登录 | 跳转到指定 URL | 任意 | P0 |
| A-007 | 登录成功页 | 不带 redirect 登录 | 跳转到 /login-success | 任意 | P1 |
| A-008 | 登录限流 | 1 分钟内 6 次错误 | 第 6 次 429 | 无 | P1 |
| A-009 | 注册链接 | 点击「立即注册」 | 跳转 /register | 无 | P0 |
| A-010 | 提示消息 | 带 message 参数访问 | 显示提示文字 | 无 | P1 |
#### 2.2 注册页面
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-011 | 注册页加载 | 访问 /register | 200显示注册表单 | 无 | P0 |
| A-012 | 表单字段 | 检查所有字段 | 用户名/密码/确认密码/邀请码/邮箱(可选) | 无 | P0 |
| A-013 | 空表单提交 | 不填写直接注册 | 验证拦截 | 无 | P0 |
| A-014 | 密码不一致 | 输入不同密码 | 显示「两次输入的密码不一致」 | 无 | P0 |
| A-015 | 弱密码 | 输入 12345678 | 显示密码强度不足 | 无 | P0 |
| A-015a | 弱密码黑名单 | 输入常见弱密码 (password/abc123/qwerty 等) | 拒绝,提示密码过于常见 | 无 | P0 |
| A-015b | 密码复杂度 | 输入仅数字 `12345678` / 仅小写 `abcdefgh` | 拒绝,需满足 3/4 类别(大小写+数字+特殊字符) | 无 | P0 |
| A-015c | 密码长度下限 | 输入 7 位强混合密码 | 拒绝,最少 8 字符 | 无 | P0 |
| A-015d | 密码长度上限 | 输入 200 位密码 | 拒绝,最多 128 字符 | 无 | P1 |
| A-016 | 无效邀请码 | 输入错误邀请码 | 显示「邀请码无效」 | 无 | P0 |
| A-017 | 正常注册 | 使用 7CQ0-GE9S-L6QS | 注册成功,自动登录 | 无 | P0 |
| A-017a | 注册自动登录 | 注册成功后检查 Cookie | 自动设置 ephron_auth Cookie无需再次登录 | 无 | P0 |
| A-017b | 邀请码过期 | 使用已过期的邀请码注册 | 显示「邀请码已过期」 | 无 | P0 |
| A-017c | 邀请码用尽 | 使用已达最大使用次数的邀请码 | 显示「邀请码已失效」 | 无 | P0 |
| A-018 | 用户名重复 | 使用已存在的用户名 | 显示「用户名已被使用」 | 无 | P0 |
| A-018a | 用户名黑名单 | 注册保留名 (admin/root/system/ephron 等) | 拒绝,提示用户名不可用 | 无 | P0 |
| A-018b | 用户名格式 | 输入 `1abc`(数字开头)/ `ab`(过短)/ 含特殊字符 | 验证拦截3-20 字符,字母开头,仅字母数字下划线 | 无 | P0 |
| A-018c | 邮箱格式验证 | 输入 `invalid` / `a@` / `@b.c` | 邮箱格式错误提示 | 无 | P1 |
| A-018d | 邮箱唯一性 | 使用已注册的邮箱 | 显示「邮箱已被使用」 | 无 | P1 |
| A-019 | 用户名可用性检查 | GET /api/check-username?username=xxx | 返回 `{available: true/false}` | 无 | P1 |
| A-019a | 用户名检查限流 | 1 分钟内 21+ 次请求 | 第 21 次 429 | 无 | P1 |
| A-020 | 邀请码验证 | POST /api/verify-invite | 返回 `{valid: true/false}` | 无 | P1 |
| A-020a | 邀请码验证限流 | 1 分钟内 11+ 次请求 | 第 11 次 429 | 无 | P1 |
| A-020b | 注册限流 | 1 小时内 6 次注册 | 第 6 次 429 | 无 | P1 |
#### 2.3 登出与跨服务认证
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-021 | Auth 登出 | POST /api/logout | Cookie 清除,跳转 /login | 任意 | P0 |
| A-022 | Blog 登出 | 访问 blog.ephron.ren/logout | Cookie 清除 | 任意 | P0 |
| A-023 | Canvas 登出 | 访问 canvas.ephron.ren/logout | Cookie 清除 | 任意 | P0 |
| A-024 | Prompt 登出 | 访问 prompt.ephron.ren/logout | Cookie 清除 | 任意 | P0 |
| A-025 | Home 登出 | 访问 www.ephron.ren/logout | Cookie 清除 | 任意 | P0 |
| A-026 | 跨服务 Cookie | Auth 登录后访问 Blog | Blog 显示已登录态 | 任意 | P0 |
| A-027 | 跨服务 Cookie | Auth 登录后访问 Canvas | Canvas 显示已登录态 | 任意 | P0 |
| A-028 | 跨服务 Cookie | Auth 登录后访问 Prompt | Prompt 显示已登录态 | 任意 | P0 |
| A-029 | API 验证(已登录) | GET /api/auth/verify | 200 `{authenticated: true}` | 任意 | P0 |
| A-029a | API 验证 min_role | GET /api/auth/verify?min_role=admin | 普通用户返回 403管理员返回 200 | 任意 | P0 |
| A-029b | 服务管理员认证 | GET /api/authz/service-admin | 管理员返回 200普通用户返回 403 | 任意 | P1 |
| A-030 | API 验证(未登录) | GET /api/auth/verify | 401 | 无 | P0 |
#### 2.4 管理后台 — 总览 (/admin)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-031 | Admin 首页 | 以管理员访问 /admin | 显示统计面板(活跃用户数、邀请码数、近 7 天注册等) | 管理员 | P0 |
| A-031a | 统计数据准确性 | 对比 Admin 面板数据与数据库 | 活跃用户数/邀请码数/近 7 天注册数准确 | 管理员 | P1 |
| A-032 | Admin 权限拦截 | 以普通用户访问 /admin | 重定向或权限不足提示 | 普通 | P0 |
| A-033 | Admin 未登录 | 未登录访问 /admin | 重定向到 /login?redirect=/admin | 无 | P0 |
#### 2.5 管理后台 — 邀请码管理 (/admin/invites)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-034 | 邀请码列表 | 访问 /admin/invites | 显示所有邀请码,含状态、使用次数、过期时间 | 管理员 | P0 |
| A-035 | 生成邀请码 | 填写最大使用次数/过期天数/备注,提交 | 生成新邀请码,显示在列表 | 管理员 | P0 |
| A-036 | 禁用邀请码 | 点击禁用按钮 | 状态变为禁用 | 管理员 | P0 |
| A-037 | 启用邀请码 | 点击启用按钮 | 状态恢复启用 | 管理员 | P0 |
| A-038 | 删除邀请码 | 点击删除按钮 | 邀请码及使用记录被删除 | 管理员 | P1 |
| A-038a | 删除邀请码级联 | 删除有使用记录的邀请码 | 使用记录同步删除,无孤立数据 | 管理员 | P1 |
| A-039 | CSRF 保护 | 不带 csrf_token 提交 | 返回验证失败 | 管理员 | P0 |
| A-040 | 限流 | 1 分钟内生成 11+ 个 | 第 11 个 429 | 管理员 | P1 |
#### 2.6 管理后台 — 用户管理 (/admin/users)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-041 | 活跃用户列表 | 访问 /admin/users | 显示所有活跃用户 | 管理员 | P0 |
| A-042 | 已禁用用户列表 | 访问 /admin/users/disabled | 显示已禁用用户 | 管理员 | P0 |
| A-043 | 禁用用户 | 选择用户,点击禁用 | 用户状态变为禁用 | 管理员 | P0 |
| A-044 | 禁用自己 | 尝试禁用自己的账号 | 返回「不能禁用自己」 | 管理员 | P0 |
| A-045 | 启用用户 | 选择已禁用用户,点击启用 | 用户恢复活跃 | 管理员 | P0 |
| A-046 | 删除用户 | 选择已禁用用户,点击删除 | 用户被永久删除 | 管理员 | P1 |
| A-046a | 删除用户级联 | 删除有角色/登录日志/邀请码记录的用户 | 角色关联、登录日志、邀请码使用记录同步删除 | 管理员 | P1 |
| A-046b | 删除活跃用户 | 尝试删除未禁用的用户 | 拒绝,需先禁用再删除 | 管理员 | P1 |
| A-047 | 用户详情 | 点击用户名 /admin/users/{username} | 显示用户详情和角色编辑 | 管理员 | P0 |
| A-047a | 用户详情角色编辑 | 在详情页修改用户角色 | 角色更新成功,页面刷新后反映新角色 | 管理员 | P0 |
#### 2.7 管理后台 — 角色权限管理 (/admin/roles)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-048 | 角色列表 | 访问 /admin/roles | 显示所有角色及权限 | 管理员 | P0 |
| A-049 | 创建角色 | 填写 key/name/description/权限,提交 | 创建成功 | 管理员 | P0 |
| A-050 | 删除角色 | 删除自定义角色 | 删除成功(内置角色不可删) | 管理员 | P1 |
| A-051 | 分配角色 | POST /admin/users/roles/assign | 角色绑定成功 | 管理员 | P0 |
| A-052 | 移除角色 | POST /admin/users/roles/remove | 角色移除成功 | 管理员 | P0 |
| A-053 | 批量更新角色 | POST /admin/users/roles/update | 用户角色更新 | 管理员 | P1 |
| A-054 | 角色权限不足 | 普通用户访问 /admin/roles | 重定向或提示权限不足 | 普通 | P0 |
#### 2.8 管理后台 — 审计日志 (/admin/audit)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-055 | 审计日志页 | 访问 /admin/audit | 显示操作日志列表 | 管理员 | P0 |
| A-056 | 筛选功能 | 按 actor_id/service/action/time 筛选 | 返回过滤结果 | 管理员 | P1 |
| A-057 | 时间范围 | 设置 start_time/end_time | 返回该时段日志 | 管理员 | P1 |
| A-058 | 权限控制 | 普通用户访问 | 重定向或提示权限不足 | 普通 | P0 |
#### 2.9 管理后台 — 服务账号 (/admin/service-accounts)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| A-059 | 服务账号列表 | 访问 /admin/service-accounts | 显示所有服务账号 | 管理员 | P0 |
| A-060 | 创建服务账号 | 填写名称/描述,提交 | 创建成功,自动生成 key | 管理员 | P0 |
| A-061 | 生成令牌 | POST /admin/service-accounts/tokens/generate | 生成 token显示一次 | 管理员 | P0 |
| A-062 | 吊销令牌 | 吊销已有 token | Token 失效 | 管理员 | P1 |
| A-063 | 停用服务账号 | 停用服务账号 | 账号不可用 | 管理员 | P1 |
| A-064 | 权限控制 | 普通用户访问 | 重定向或提示权限不足 | 普通 | P0 |
---
### 模块 3Blog 博客服务 (blog.ephron.ren)
#### 3.1 公开页面
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-001 | 博客首页 | 访问 / | 200显示最新文章 + 统计 | 无 | P0 |
| B-002 | 文章列表 | 访问 /posts | 显示所有已发布文章 | 无 | P0 |
| B-003 | 文章详情 | 点击文章 /posts/{slug} | Markdown 正确渲染,显示阅读时间、浏览量 | 无 | P0 |
| B-003a | 阅读时间估算 | 查看中文/英文文章 | 中文 300 字/分钟,英文 200 词/分钟计算 | 无 | P2 |
| B-003b | 浏览量计数 | 刷新文章详情页 | 浏览量递增(数据库记录) | 无 | P2 |
| B-003c | CRLF→LF 规范化 | 提交含 CRLF 的文章内容 | 保存后内容为 LF 换行 | 管理员 | P2 |
| B-004 | 代码高亮 | 查看含代码块的文章 | 语法高亮正常 | 无 | P1 |
| B-005 | 数学公式 | 查看含 LaTeX 的文章 | MathJax 渲染正常 | 无 | P1 |
| B-006 | 归档页 | 访问 /archive | 按年月分组显示 | 无 | P1 |
| B-007 | 标签列表 | 访问 /tags | 显示标签及数量 | 无 | P1 |
| B-008 | 按标签筛选 | 点击标签 /tags/{tag} | 显示该标签文章 | 无 | P1 |
| B-009 | RSS Feed | 访问 /feed | 有效 RSS XML | 无 | P1 |
| B-010 | Sitemap | 访问 /sitemap.xml | 有效 Sitemap XML | 无 | P2 |
| B-011 | 草稿不可见 | 未登录访问草稿 URL | 404 | 无 | P0 |
| B-011a | 草稿预览(已登录) | 管理员访问草稿 URL | 正常显示,含草稿标记 | 管理员 | P0 |
| B-012 | 草稿可见 | 管理员访问草稿 URL | 正常显示 | 管理员 | P0 |
| B-013 | 404 页面 | 访问 /posts/not-exist | 404 | 无 | P1 |
#### 3.2 搜索
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-014 | 简单搜索 | /posts?q=关键词 | 返回匹配文章 | 无 | P0 |
| B-015 | 全文搜索 | /posts?q=关键词&mode=fulltext | 返回匹配文章 + 高亮 | 无 | P1 |
| B-015a | 全文搜索中文分词 | /posts?q=关键词&mode=fulltext中文关键词 | jieba 分词正确,返回相关结果 | 无 | P1 |
| B-015b | 搜索结果高亮 | 查看 fulltext 搜索结果 | 匹配关键词高亮显示HTML 标签已转义 | 无 | P1 |
| B-015c | 搜索模式对比 | 同一关键词分别用 simple 和 fulltext | simple 为字符串匹配fulltext 为 BM25 排序 | 无 | P2 |
| B-016 | 空搜索 | /posts?q= | 显示所有文章 | 无 | P1 |
| B-017 | 无结果 | 搜索不存在的词 | 空结果或提示 | 无 | P1 |
#### 3.3 评论系统
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-018 | 评论显示 | 查看文章底部 | 显示已审核评论 | 无 | P0 |
| B-019 | 提交评论(已登录) | POST /api/comments/ | 成功,提示等待审核 | 任意 | P0 |
| B-020 | 提交评论(未登录) | POST /api/comments/ | 401 | 无 | P0 |
| B-021 | 回复评论 | 带 parent_id 提交 | 创建嵌套回复 | 任意 | P2 |
| B-022 | 评论限流 | 1 分钟内 6+ 条 | 第 6 条 429 | 任意 | P1 |
#### 3.4 点赞系统
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-023 | 点赞状态 | GET /api/likes/posts/{slug} | 返回 liked + like_count | 无 | P0 |
| B-024 | 点赞 | POST /api/likes/toggle | 点赞数 +1 | 无 | P0 |
| B-025 | 取消点赞 | 再次 toggle | 点赞数 -1 | 无 | P0 |
| B-025a | 点赞统计 API | GET /api/likes/stats | 返回所有文章点赞统计 | 无 | P2 |
| B-026 | 点赞限流 | 1 分钟内 11+ 次 | 第 11 次 429 | 无 | P2 |
#### 3.5 管理后台 (/admin)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-027 | Admin 首页 | 以管理员访问 /admin | 文章列表(含草稿)+ 统计 | 管理员 | P0 |
| B-028 | 搜索文章 | 在 Admin 搜索框输入关键词 | 显示匹配结果 | 管理员 | P1 |
| B-029 | 新建文章 | /admin/new填标题/内容/标签,提交 | 创建成功 | 管理员 | P0 |
| B-030 | 编辑文章 | /admin/edit/{slug},修改后保存 | 保存成功 | 管理员 | P0 |
| B-031 | 删除文章 | 点击删除 | 文章删除 | 管理员 | P0 |
| B-032 | 切换草稿 | 点击草稿/发布切换 | 状态切换 | 管理员 | P0 |
| B-033 | 切换置顶 | 点击置顶切换 | 置顶状态切换 | 管理员 | P1 |
| B-033a | 置顶文章排序 | 置顶文章在列表中 | 置顶文章排在非置顶之前 | 管理员 | P1 |
| B-034 | 图片上传 | 上传 jpg/png/gif/webp | 成功,返回 URL + Markdown 片段 | 管理员 | P1 |
| B-034a | 图片自动转 WebP | 上传 jpg/png 图片 | 自动转换为 WebP 格式,最大 1920x1080 | 管理员 | P1 |
| B-034b | 图片上传返回格式 | 检查上传响应 | 返回 URL、Markdown 片段、文件名、大小、尺寸 | 管理员 | P2 |
| B-035 | 图片大小限制 | 上传 >5MB 图片 | 返回错误 | 管理员 | P1 |
| B-036 | 内容大小限制 | 提交 >1MB 内容 | 返回错误 | 管理员 | P1 |
| B-037 | CSRF 保护 | 不带 csrf_token 提交 | 验证失败 | 管理员 | P0 |
| B-038 | 创建限流 | 1 分钟内创建 11+ 篇 | 第 11 篇 429 | 管理员 | P1 |
| B-039 | 保存限流 | 1 分钟内保存 21+ 次 | 第 21 次 429 | 管理员 | P1 |
| B-040 | 删除限流 | 1 分钟内删除 6+ 篇 | 第 6 篇 429 | 管理员 | P1 |
| B-041 | Admin 权限拦截 | 普通用户访问 /admin | 重定向或权限不足 | 普通 | P0 |
| B-042 | Admin 登出 | POST /admin/logout | Cookie 清除 | 管理员 | P0 |
#### 3.6 评论管理后台
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-043a | 评论管理页面 | 访问 /admin/comments | 显示评论管理 UI审核/删除操作) | 管理员 | P0 |
| B-043b | 评论分页 | 评论超过单页时 | 分页控件正常 | 管理员 | P1 |
#### 3.6.1 评论管理 API
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
| B-043 | 全部评论 | GET /api/comments/admin/all | 返回评论列表(含未审核) | 管理员 | P0 |
| B-044 | 待审核评论 | GET /api/comments/admin/pending | 返回待审核列表 | 管理员 | P0 |
| B-045 | 审核通过 | POST /api/comments/admin/{id}/approve | 评论变为已审核 | 管理员 | P0 |
| B-046 | 删除评论 | DELETE /api/comments/admin/{id} | 评论被删除 | 管理员 | P0 |
| B-047 | 评论详情 | GET /api/comments/admin/{id} | 返回含 IP 等敏感信息 | 管理员 | P1 |
| B-048 | 权限拦截 | 普通用户访问 admin API | 403 | 普通 | P0 |
#### 3.7 Service API
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| B-049 | 列表草稿 | GET /api/service/posts | 可管理的草稿列表 | 服务 | P1 |
| B-049a | 所有权隔离 | 服务 A 创建的草稿,服务 B 尝试访问 | 403 或 404 | 服务 | P0 |
| B-049b | 人工接管 | 服务创建的草稿,管理员编辑 | 编辑成功ownership_type 保持 service | 管理员 | P1 |
| B-050 | 创建草稿 | POST /api/service/posts | 成功,返回 slug | 服务 | P1 |
| B-051 | 更新草稿 | PATCH /api/service/posts/{slug} | 成功 | 服务 | P1 |
| B-052 | 删除草稿 | DELETE /api/service/posts/{slug} | 成功 | 服务 | P1 |
| B-053 | 无 token | 不带 Authorization | 401 | 无 | P1 |
| B-054 | 非拥有者 | 访问他人草稿 | 403 | 服务 | P1 |
---
### 模块 4Canvas 画布服务 (canvas.ephron.ren)
#### 4.1 公开页面
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| C-001 | 首页加载 | 访问 / | 200显示 Canvas 列表 | 无 | P0 |
| C-002 | 分类筛选 | 点击分类标签 | 按分类显示 | 无 | P1 |
| C-003 | 搜索 | 输入关键词 | 返回匹配结果 | 无 | P1 |
| C-004 | 预览页 | 访问 /view/{slug} | iframe 加载 Canvas 内容 | 无 | P0 |
| C-005 | 原始 HTML | 访问 /raw/{slug} | 返回原始 HTML | 无 | P0 |
| C-005a | 原始 HTML 安全 | /raw/{slug} 响应头检查 | Content-Type 为 text/html有 CSP 限制 | 无 | P0 |
| C-006 | 访问计数 | POST /api/view/{slug} | 计数递增AJAX 请求) | 无 | P1 |
| C-007 | 草稿不可见 | 未登录访问草稿 | 404 | 无 | P0 |
| C-008 | 空状态 | 无 Canvas 时 | 显示「还没有工具」 | 无 | P1 |
#### 4.2 管理后台 (/admin)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| C-009 | Admin 首页 | 以管理员访问 /admin | 显示所有 Canvas含草稿+ 统计 | 管理员 | P0 |
| C-010 | 搜索 | 在 Admin 搜索 | 显示匹配结果 | 管理员 | P1 |
| C-011 | 新建 Canvas | /admin/new填标题/slug/内容/分类,提交 | 创建成功 | 管理员 | P0 |
| C-011a | 用户指定 slug | 创建时自定义 slug | slug 保存成功,可通过自定义 slug 访问 | 管理员 | P1 |
| C-011b | slug 格式验证 | 输入大写/特殊字符/中文 slug | 拒绝,仅允许小写字母数字下划线连字符 | 管理员 | P0 |
| C-011c | 分类/来源字段 | 选择预定义分类和来源 | 正确保存并显示 | 管理员 | P1 |
| C-012 | 编辑 Canvas | /admin/edit/{slug},修改后保存 | 保存成功 | 管理员 | P0 |
| C-013 | 删除 Canvas | POST /admin/delete | 删除成功 | 管理员 | P0 |
| C-014 | 切换草稿 | POST /admin/toggle-draft | 状态切换 | 管理员 | P0 |
| C-015 | CSRF 保护 | 不带 csrf_token 提交 | 验证失败 | 管理员 | P0 |
| C-016 | 创建限流 | 1 分钟内 11+ 个 | 第 11 个 429 | 管理员 | P1 |
| C-017 | 保存限流 | 1 分钟内 21+ 次 | 第 21 次 429 | 管理员 | P1 |
| C-018 | 权限拦截 | 普通用户访问 /admin | 重定向或权限不足 | 普通 | P0 |
| C-019 | 登出 | POST /admin/logout | Cookie 清除 | 管理员 | P0 |
#### 4.3 Service API
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| C-020 | 列表草稿 | GET /api/service/canvas | 可管理的草稿列表 | 服务 | P1 |
| C-020a | 所有权隔离 | 服务 A 创建的草稿,服务 B 尝试访问 | 403 或 404 | 服务 | P0 |
| C-021 | 创建草稿 | POST /api/service/canvas | 成功,返回 slug | 服务 | P1 |
| C-022 | 更新草稿 | PATCH /api/service/canvas/{slug} | 成功 | 服务 | P1 |
| C-023 | 删除草稿 | DELETE /api/service/canvas/{slug} | 成功 | 服务 | P1 |
| C-024 | 无 token | 不带 Authorization | 401 | 无 | P1 |
---
### 模块 5Prompt 提示词服务 (prompt.ephron.ren)
#### 5.1 公开页面
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| P-001 | 首页加载 | 访问 / | 200显示提示词列表 | 无 | P0 |
| P-002 | 搜索 | /?q=关键词 | 返回匹配结果 | 无 | P1 |
| P-003 | 分类筛选 | /?category=写作 | 按分类显示 | 无 | P1 |
| P-004 | 标签筛选 | /?tag=xxx | 按标签显示 | 无 | P1 |
| P-005 | 统计信息 | 检查页面 | 显示总数/模板数/分类数 | 无 | P2 |
| P-006 | 详情页 | 访问 /prompts/{key} | 显示标题/内容/描述/标签 | 无 | P0 |
| P-006a | Public JSON API | GET /api/prompts/{key} | 返回 JSON 格式提示词(无需登录) | 无 | P0 |
| P-006b | Public API 列表 | GET /api/prompts?limit=10&offset=0 | 返回分页提示词列表 | 无 | P1 |
| P-006c | Public API 版本 | GET /api/prompts/{key}?version=2 | 返回指定版本内容 | 无 | P1 |
| P-006d | API 草稿拦截 | GET /api/prompts/{key}(该 key 为草稿) | 返回 400 或 404 | 无 | P0 |
| P-007 | 草稿不可见 | 未登录访问草稿 | 404 | 无 | P0 |
| P-008 | 404 页面 | 访问 /prompts/not-exist | 404 | 无 | P1 |
#### 5.2 管理后台 (/admin)
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| P-009 | Admin 首页 | 以管理员访问 /admin | 显示所有提示词(含草稿) | 管理员 | P0 |
| P-010 | 搜索/筛选 | 在 Admin 搜索/按分类/按标签 | 返回匹配结果 | 管理员 | P1 |
| P-011 | 新建提示词 | /admin/new填标题/内容/分类/标签/描述,提交 | 创建成功 | 管理员 | P0 |
| P-012 | 模板标记 | 创建时勾选「是模板」 | is_template 正确保存 | 管理员 | P1 |
| P-012a | 模板变量 | 创建模板提示词,填写 variablesCSV 格式) | 变量保存成功 | 管理员 | P1 |
| P-012b | 示例输入/输出 | 填写 example_input 和 example_output | 保存成功,详情页显示 | 管理员 | P2 |
| P-012c | 推荐模型 | 填写 recommended_model 字段 | 保存成功,详情页显示 | 管理员 | P2 |
| P-013 | 编辑提示词 | /admin/edit/{key},修改后保存 | 保存成功 | 管理员 | P0 |
| P-014 | 删除提示词 | POST /admin/delete/{key} | 删除成功 | 管理员 | P0 |
| P-015 | 切换草稿 | POST /admin/toggle/{key} | 状态切换 | 管理员 | P0 |
| P-016 | 版本管理 | 访问 /admin/versions/{key} | 显示版本历史 | 管理员 | P1 |
| P-017 | 切换版本 | POST /admin/set-version/{key} | 切换到指定版本 | 管理员 | P1 |
| P-017a | 版本切换后 API | 切换版本后 GET /api/prompts/{key} | 返回新切换的版本内容 | 管理员 | P1 |
| P-018 | CSRF 保护 | 不带 csrf_token 提交 | 验证失败 | 管理员 | P0 |
| P-019 | 创建限流 | 1 分钟内 11+ 个 | 第 11 个 429 | 管理员 | P1 |
| P-020 | 保存限流 | 1 分钟内 21+ 次 | 第 21 次 429 | 管理员 | P1 |
| P-021 | 权限拦截 | 普通用户访问 /admin | 重定向或权限不足 | 普通 | P0 |
| P-022 | 登出 | GET /logout | Cookie 清除 | 任意 | P0 |
#### 5.3 Service API
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| P-023 | 列表草稿 | GET /api/service/prompts | 可管理的草稿列表 | 服务 | P1 |
| P-023a | 所有权隔离 | 服务 A 创建的草稿,服务 B 尝试访问 | 403 或 404 | 服务 | P0 |
| P-024 | 创建草稿 | POST /api/service/prompts | 成功 | 服务 | P1 |
| P-025 | 更新草稿 | PATCH /api/service/prompts/{key} | 成功 | 服务 | P1 |
| P-026 | 删除草稿 | DELETE /api/service/prompts/{key} | 成功 | 服务 | P1 |
| P-027 | 无 token | 不带 Authorization | 401 | 无 | P1 |
---
### 模块 6安全与一致性
#### 6.1 安全头
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| S-001 | X-Content-Type-Options | 检查响应头 | `nosniff` | P1 |
| S-002 | X-Frame-Options | 检查响应头 | `DENY` | P1 |
| S-003 | Referrer-Policy | 检查响应头 | `strict-origin-when-cross-origin` | P1 |
| S-004 | CSP | 检查 Content-Security-Policy | 存在且合理 | P1 |
| S-004a | CSP script-src | 检查 script-src 策略 | 仅 `self` + `unsafe-inline`,无 `unsafe-eval` | P1 |
| S-004b | CSP form-action | 检查 form-action | 仅 `self` | P1 |
| S-004c | CSP frame-ancestors | 检查 frame-ancestors | `none`(防 clickjacking | P1 |
#### 6.2 一致性检查
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| S-005 | mobile.css 引入 | 检查各页面 HTML | 所有页面一致引入或不引入 | P1 |
| S-006 | loader.js 引入 | 检查各页面 HTML | 所有页面一致引入 | P1 |
| S-007 | DOCTYPE 前无多余内容 | 检查各页面源码第一字节 | 无 BOM/换行 | P1 |
| S-008 | Auth 页导航 | 访问 Auth 登录/注册页 | 有返回主页的链接 | P1 |
| S-009 | user-scalable | 检查 viewport meta | 无 `user-scalable=no` | P1 |
| S-009a | AJAX CSRF | Blog/Canvas/Prompt 图片上传和评论审核 | X-CSRF-Token header 验证通过 | P0 |
| S-009b | Cache-Control | 敏感页面auth verify/service accounts | `no-store``no-cache` | P1 |
#### 6.3 控制台检查
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| S-010 | Home 无 JS 错误 | 访问首页,检查控制台 | 无 error | P1 |
| S-011 | Auth 无 JS 错误 | 访问登录/注册页 | 无 error | P1 |
| S-012 | Blog 无 JS 错误 | 访问首页/文章详情/Admin | 无 error | P1 |
| S-013 | Canvas 无 JS 错误 | 访问首页/预览页/Admin | 无 error | P1 |
| S-014 | Prompt 无 JS 错误 | 访问首页/详情页/Admin | 无 error | P1 |
---
### 模块 7边界与异常输入
#### 7.1 XSS 注入测试
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| XSS-001 | 文章标题 XSS | Blog Admin 新建文章,标题 `<script>alert('XSS')</script>`,发布后访问 | 渲染为纯文本,不执行脚本 | 管理员 | P0 |
| XSS-002 | 文章内容 XSS | 内容输入 `<img src=x onerror=alert(1)>` | 不执行,显示 broken image | 管理员 | P0 |
| XSS-003 | 评论 XSS | 提交评论 `<svg onload=alert(1)>` | 渲染为纯文本,不执行 | 任意 | P0 |
| XSS-004 | Canvas 标题 XSS | Canvas 标题输入 `<script>alert(1)</script>` | 不执行 | 管理员 | P0 |
| XSS-005 | Prompt 标题 XSS | Prompt 标题输入 `"><script>alert(1)</script>` | 不执行 | 管理员 | P0 |
| XSS-006 | 用户名 XSS | 注册时用户名 `<script>alert(1)</script>` | 验证拦截或转义 | 无 | P0 |
| XSS-007 | 搜索框 XSS | 搜索框 `<script>alert(1)</script>` | 不执行,搜索结果安全显示 | 无 | P0 |
| XSS-008 | Bio/个人简介 XSS | Admin 修改简介输入 XSS payload | 不执行 | 管理员 | P0 |
| XSS-009 | 标签 XSS | 添加标签 `<script>alert(1)</script>` | 转义存储,安全显示 | 管理员 | P0 |
| XSS-010 | URL 参数 XSS | 访问 `?name=<script>alert(1)</script>` | 不执行 | 无 | P1 |
#### 7.2 超长字符串输入
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| XSS-011 | 超长文章标题 | 标题输入 10000 字符 | 验证最大长度限制,或截断保存 | 管理员 | P1 |
| XSS-012 | 超长文章内容 | 内容输入 10MB 文本 | 返回错误或限制1MB | 管理员 | P0 |
| XSS-013 | 超长用户名 | 注册时用户名输入 500 字符 | 验证最大长度限制 | 无 | P1 |
| XSS-014 | 超长评论 | 评论输入 50000 字符 | 验证长度限制 | 任意 | P1 |
| XSS-015 | 超长搜索查询 | 搜索框输入 5000 字符 | 验证长度限制或正常处理 | 无 | P1 |
| XSS-016 | 超长 Canvas 内容 | Canvas 内容输入 50MB | 验证大小限制 | 管理员 | P0 |
| XSS-017 | 超长 Prompt 内容 | Prompt 内容输入 100000 字符 | 保存成功(无硬性限制则可) | 管理员 | P1 |
| XSS-018 | 超长邮箱 | 邮箱字段输入 1000 字符 | 验证格式拦截 | 无 | P1 |
#### 7.3 特殊字符输入
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| XSS-019 | 特殊字符用户名 | 注册 `user|name` `user&name` `user'name` `user"name` | 验证字符白名单拦截 | 无 | P1 |
| XSS-020 | 特殊字符标题 | 文章标题含 `/\:*?"<>|` | 验证安全处理 | 管理员 | P1 |
| XSS-021 | Emoji 标题 | 标题输入 `🎉🔥💻🚀` 等多 emoji | 正常保存和显示 | 管理员 | P1 |
| XSS-022 | CJK 字符 | 标题/内容输入中日韩字符 `日本語한국어中文` | 正常保存和显示 | 管理员 | P1 |
| XSS-023 | Unicode 特殊符 | 标题含零宽字符 `` 或双向字符 | 安全处理,不破坏布局 | 管理员 | P1 |
| XSS-024 | 特殊字符搜索 | 搜索 `AND 1=1--` `OR 1=1` | 安全处理,不报错 | 无 | P1 |
| XSS-025 | SQL 注入形搜索 | 搜索 `' OR '1'='1` `'; DROP TABLE--` | 安全处理,无 SQL 错误 | 无 | P1 |
| XSS-026 | 换行符注入 | 标题含 `\n\r` 换行 | 安全保存,显示正常 | 管理员 | P1 |
#### 7.4 空内容与边界提交
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| XSS-027 | 空标题文章 | 新建文章,标题留空 | 验证拦截「标题不能为空」 | 管理员 | P0 |
| XSS-028 | 空内容文章 | 内容留空,填写标题 | 保存为草稿或提示内容不能为空 | 管理员 | P0 |
| XSS-029 | 空评论提交 | 评论框留空提交 | 验证拦截 | 任意 | P0 |
| XSS-030 | 仅空白字符 | 标题/内容仅输入空格/制表符 | 视为空内容,拦截 | 管理员 | P1 |
| XSS-031 | 全角空格 | 标题输入全角空格 ` ` | 视为有效内容或提示输入无效 | 管理员 | P1 |
| XSS-032 | 仅数字标题 | 标题输入 `123456` | 正常保存 | 管理员 | P1 |
| XSS-033 | 重复标题 | 创建两篇相同标题文章 | 允许或提示 slug 重复处理 | 管理员 | P1 |
---
### 模块 8安全性深度测试
#### 8.1 Cookie 属性验证
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEC-001 | auth cookie HttpOnly | curl 检查 `Set-Cookie: ephron_auth` | `HttpOnly` 标记存在 | P0 |
| SEC-002 | auth cookie Secure | curl (HTTP) 检查 | 生产环境应返回 Secure需 HTTPS | P0 |
| SEC-003 | auth cookie SameSite | 检查 SameSite 属性 | `Lax``Strict`,不为空 | P0 |
| SEC-004 | auth cookie Max-Age | 检查 Max-Age 或 Expires | 有合理过期时间≤30天 | P1 |
| SEC-005 | auth cookie Domain | 检查 Domain 域 | `.ephron.ren` 以便跨子域共享 | P0 |
| SEC-006 | CSRF cookie HttpOnly | 检查 ephron_csrf cookie | `HttpOnly=false`JS 需读取,双提交) | P1 |
| SEC-007 | Cookie 刷新 | 登录后等 5 分钟再次访问 | Session 保持cookie 未过期 | 任意 | P1 |
| SEC-008 | 跨域 Cookie 写入 | 从 blog.ephron.ren 访问 auth.ephron.ren | auth cookie 不被 JS 读取 | P1 |
#### 8.2 Open Redirect 深度测试
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEC-009 | redirect 到外域 | `auth.ephron.ren/login?redirect=https://evil.com` | 拒绝或重定向到安全页 | P0 |
| SEC-010 | redirect 协议相对 | `auth.ephron.ren/login?redirect=//evil.com` | 拒绝,视为相对路径处理或拒绝 | P0 |
| SEC-011 | redirect 协议注入 | `auth.ephron.ren/login?redirect=javascript:alert(1)` | 拒绝 | P0 |
| SEC-012 | redirect 路径遍历 | `auth.ephron.ren/login?redirect=/..//evil.com` | 拒绝或规范化处理 | P1 |
| SEC-013 | redirect 到子域 | `auth.ephron.ren/login?redirect=https://blog.ephron.ren` | 允许ephron.ren 子域) | P0 |
| SEC-014 | redirect 空值 | `auth.ephron.ren/login?redirect=` | 使用默认页 | P1 |
| SEC-015 | redirect URL 编码 | `auth.ephron.ren/login?redirect=%2F%2Fevil.com` | 拒绝或解码后拒绝 | P1 |
| SEC-016 | 登出后 redirect | 登出时带 redirect 参数 | 拒绝到外部 URL | P1 |
#### 8.3 CSRF Token 深度测试
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEC-017 | Token 重放 | 获取 token 后在同一 session 重复使用 | 允许(在有效期内) | P1 |
| SEC-018 | Token 过期 | 获取 token等 61 分钟后再使用 | 拒绝,返回 CSRF 验证失败 | P0 |
| SEC-019 | Token 伪造 | 表单中填入假 token `{timestamp}:fakehex` | 拒绝HMAC 验证不通过 | P0 |
| SEC-020 | Token 格式错误 | 表单 token 与 cookie 不一致 | 拒绝 | P0 |
| SEC-021 | Token 仅 cookie 无表单 | 不在表单中传 token仅 cookie | 拒绝(双提交需两者匹配) | P0 |
| SEC-022 | Token 仅表单无 cookie | 传表单 token 但不带 CSRF cookie | 拒绝 | P0 |
| SEC-023 | Token 不同服务 | 用 blog 的 token 提交到 auth 的表单 | 拒绝(各服务 token 独立) | P1 |
| SEC-024 | 修改 token 时钟偏倚 | 手动调整请求时间戳到未来/过去 | 过期 token 拒绝 | P1 |
#### 8.5 Service Account Token 认证
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEC-033 | Bearer Token 认证 | 用有效 service token 请求 /api/service/* | 200返回数据 | P0 |
| SEC-034 | Token SHA256 查找 | 数据库中 token 为 SHA256 哈希 | 明文 token 不存储 | P0 |
| SEC-035 | Token 使用追踪 | 使用 token 后检查 last_used_at/ip/ua | 记录更新 | P1 |
| SEC-036 | Token 过期 | 使用带 expires_at 的过期 token | 401 | P0 |
| SEC-037 | 吊销 Token | 吊销后使用旧 token | 401 | P0 |
| SEC-038 | Token 跨服务权限 | Blog service token 访问 Canvas API | 403各服务独立 | P0 |
#### 8.4 路径遍历与文件安全
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEC-025 | 文章 slug 路径遍历 | 访问 `/posts/../../../etc/passwd` | 404 或安全拒绝 | P0 |
| SEC-026 | Canvas slug 路径遍历 | 访问 `/view/../../secret.txt` | 404 | P0 |
| SEC-027 | Prompt key 路径遍历 | 访问 `/prompts/../../config.py` | 404 | P0 |
| SEC-028 | 管理接口越权 | 普通用户直接 POST 到 admin 接口 | 403 | 普通 | P0 |
| SEC-029 | 直接访问管理 API | curl 绕过 UI 访问 `/admin/api/...` | RBAC 拦截 | 普通 | P0 |
| SEC-030 | 文件上传绕过扩展名 | 上传 `shell.php.jpg` | 应拒绝(只允许图片类型) | 管理员 | P0 |
| SEC-031 | 文件上传 MIME 类型绕过 | POST 修改 Content-Type 但内容是 HTML | 验证文件内容而非仅 MIME | 管理员 | P1 |
| SEC-032 | 文件名 XSS | 上传含 `<script>` 文件名的图片 | 存储时清理文件名 | 管理员 | P1 |
---
### 模块 9会话管理
#### 9.1 Token 生命周期
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SES-001 | Token 过期跳转 | 等待 token 过期后访问受保护页 | 重定向到 login 页 | 任意 | P0 |
| SES-002 | Token 过期 API | 用过期 token 请求 API | 401 `{"error": "token expired"}` | 任意 | P0 |
| SES-003 | 角色降级权限刷新 | 管理员登录后 DB 改为 user刷新页面 | 页面应反映新权限(读取 DB 角色) | 管理员 | P0 |
| SES-004 | 角色升级权限刷新 | user 登录后 DB 改为 admin刷新 | 需重新登录才生效session 不自动刷新) | 普通 | P1 |
| SES-005 | 用户禁用后会话 | 用户登录后 DB 禁用账号,已存 cookie 访问 | 拒绝访问或立即登出 | 任意 | P0 |
| SES-006 | 多设备登录 | 同时两台设备用同一账号 | 两 session 均有效,或强制下线 | 任意 | P1 |
| SES-007 | 登录成功 token 刷新 | 登录后检查是否有新 session 产生 | 每次登录产生新 token | 任意 | P1 |
#### 9.2 并发会话安全
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SES-008 | 并发编辑草稿 | 两浏览器同时编辑同一文章并保存 | 后保存覆盖先前,或加锁提示冲突 | 管理员 | P1 |
| SES-009 | 并发评论 | 多用户同时对同一文章提交评论 | 均成功存储,时间戳不同 | 任意 | P1 |
| SES-010 | 并发点赞 | 多用户同时对同一文章 toggle 点赞 | 计数正确,无 race condition | 任意 | P1 |
| SES-011 | Session 固定攻击 | 登录前 cookie 值 A登录后 cookie 变为 B | 登录后 session ID 必须更换 | 任意 | P0 |
---
### 模块 10文件上传安全
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| UPL-001 | 图片上传正常 | 上传 1MB jpg 文件 | 成功,返回 URL | 管理员 | P0 |
| UPL-002 | PNG 上传 | 上传 2MB png | 成功 | 管理员 | P0 |
| UPL-003 | WebP 上传 | 上传 webp 格式 | 成功 | 管理员 | P0 |
| UPL-004 | GIF 上传 | 上传 500KB gif | 成功 | 管理员 | P0 |
| UPL-005 | 大小限制 | 上传 10MB 文件 | 返回错误「文件超过 5MB」 | 管理员 | P0 |
| UPL-006 | 双扩展名 | 上传 `shell.php.jpg` | 拒绝(后端验证真实类型) | 管理员 | P0 |
| UPL-007 | 可执行文件 | 上传 `shell.exe` | 拒绝,仅允许图片类型 | 管理员 | P0 |
| UPL-008 | 恶意 SVG | 上传含 `<script>alert(1)</script>` 的 SVG | 拒绝或净化处理 | 管理员 | P0 |
| UPL-009 | polyglot 文件 | 上传既是图片又是 HTML 的文件 | 拒绝,内容检查 | 管理员 | P0 |
| UPL-010 | 0 字节文件 | 上传 0 字节文件 | 拒绝或提示无效文件 | 管理员 | P1 |
| UPL-011 | 超大文件名 | 上传文件名 500 字符 | 截断或正常处理 | 管理员 | P1 |
| UPL-012 | 路径穿越文件名 | 上传 `../../../etc/passwd.jpg` | 规范化文件名,安全存储 | 管理员 | P0 |
| UPL-013 | 上传覆盖 | 上传与已有文件同名的图片 | UUID 化命名,不覆盖 | 管理员 | P1 |
| UPL-014 | 未登录上传 | 未登录尝试上传 | 401 重定向到登录 | 无 | P0 |
| UPL-015 | 普通用户上传 | 普通用户尝试上传 | 403 权限不足 | 普通 | P0 |
---
### 模块 11搜索边界测试
| 编号 | 测试内容 | 步骤 | 预期 | 账号 | 优先级 |
|------|----------|------|------|------|--------|
| SCH-001 | 空搜索 | `/?q=``/?q=%20%20` | 显示所有内容 | 无 | P1 |
| SCH-002 | 单字符搜索 | `/?q=a` | 有结果或空结果提示 | 无 | P1 |
| SCH-003 | 超长搜索 | `/?q=` + 10000 字符 | 验证长度限制 | 无 | P1 |
| SCH-004 | SQL 注入搜索 | `/?q=' OR 1=1--` | 安全处理,显示空结果或报错 | 无 | P0 |
| SCH-005 | 正则注入 | `/?q=(?<=.*)` 特殊正则元字符 | 安全处理 | 无 | P1 |
| SCH-006 | 搜索结果 XSS | 搜索含 `<script>` 内容后查看结果高亮 | 转义高亮显示 | 无 | P0 |
| SCH-007 | 搜索跨服务 | auth 服务搜索 `/?q=blogpost` | 正常处理auth 无搜索则跳过) | 无 | P1 |
| SCH-008 | 搜索结果分页 | 搜索结果超过单页 | 分页控件正常 | 无 | P1 |
| SCH-009 | 搜索历史 | 输入搜索词后刷新页面 | 搜索框保留上次内容 | 无 | P2 |
---
### 模块 12SEO 元数据测试
#### 12.1 Home 服务
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEO-001 | 首页 title | 访问 www.ephron.ren查看 `<title>` | 有描述性 title非默认 | P1 |
| SEO-002 | 首页 meta description | 检查 `<meta name="description">` | 有且长度合理150字符左右 | P1 |
| SEO-003 | 首页 OG 标签 | 检查 `og:title`, `og:description`, `og:image`, `og:url` | 全部存在 | P1 |
| SEO-004 | 首页 canonical | 检查 `<link rel="canonical">` | 存在,指向自身 | P2 |
| SEO-005 | 首页 robots | 检查 robots meta 或 headers | `index, follow` | P2 |
| SEO-006 | 响应式 viewport | 检查 viewport meta | 含 `width=device-width, initial-scale=1` | P1 |
| SEO-007 | 首页 icon | 检查 favicon.ico / apple-touch-icon | 存在 | P2 |
#### 12.2 Blog 服务
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEO-008 | 文章 title | 访问文章详情页 `<title>` | 含文章标题 + 站点名 | P1 |
| SEO-009 | 文章 meta description | 检查 description 内容 | 文章摘要或前 160 字符 | P1 |
| SEO-010 | 文章 OG 标签 | 检查 og:title, og:description, og:image | 全部存在 | P1 |
| SEO-011 | 文章 canonical | 检查 canonical URL | 存在且正确 | P1 |
| SEO-012 | RSS feed | 访问 /feed | 有效 XML有 items | P1 |
| SEO-013 | sitemap.xml | 访问 /sitemap.xml | 有效 XML包含所有已发布文章 | P1 |
| SEO-014 | 文章结构化数据 | 检查 JSON-LD | 有 Article 或 BlogPosting schema | P2 |
| SEO-015 | 归档页 title | 访问 /archive | 有描述性 title | P2 |
| SEO-016 | 标签页 title | 访问 /tags/{tag} | 含标签名 + 站点名 | P2 |
#### 12.3 Canvas 服务
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEO-017 | Canvas 预览页 title | 访问 /view/{slug} | 含标题 + 站点名 | P2 |
| SEO-018 | Canvas OG image | 检查 og:image | 存在(如有缩略图) | P2 |
#### 12.4 Prompt 服务
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEO-019 | Prompt 详情页 title | 访问 /prompts/{key} | 含标题 + 站点名 | P2 |
| SEO-020 | Prompt 详情页 description | 检查 meta description | 存在 | P2 |
#### 12.5 robots.txt
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| SEO-021 | robots.txt 存在 | 各子服务访问 /robots.txt | 存在内容 | P1 |
| SEO-022 | robots.txt 有效性 | 检查 sitemap 指令 | 指向正确的 sitemap URL | P1 |
---
### 模块 13无障碍Accessibility深度测试
#### 13.1 键盘导航
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| A11Y-001 | 焦点可见性 | Tab 键遍历首页 | 每个焦点元素有可见轮廓 | P1 |
| A11Y-002 | 表单键盘操作 | 在登录表单中 Tab 切换字段Enter 提交 | 顺序正确Enter 提交 | 无 | P1 |
| A11Y-003 | 模态框键盘 | 打开联系弹窗后 Tab | 焦点锁定在弹窗内 | 无 | P1 |
| A11Y-004 | 下拉菜单键盘 | 使用键盘操作下拉菜单 | 上下箭头选择Enter 确认 | 无 | P1 |
| A11Y-005 | 跳过导航 | 按 Tab 后首个链接 | 应为「跳过到主要内容」链接 | P1 |
| A11Y-006 | 编辑器键盘操作 | Blog Admin 编辑器,键盘操作 | 可用键盘完成基本编辑 | 管理员 | P1 |
#### 13.2 ARIA 与语义化
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| A11Y-007 | 图片 alt | 所有 `<img>` 有 alt 属性 | 装饰性图片有 `alt=""``aria-hidden` | P1 |
| A11Y-008 | 按钮语义 | 使用 `<button>` 而非 `<div onclick>` | 正确语义标签 | P1 |
| A11Y-009 | 表单 label | 登录表单每个 input 有 `<label>` | label 与 input 关联 | P1 |
| A11Y-010 | ARIA label | 图标按钮有 `aria-label` | 含义明确 | P1 |
| A11Y-011 | 导航 landmark | `<nav>` 标签存在 | 导航区有正确语义 | P2 |
| A11Y-012 | 标题层级 | H1/H2/H3 层级不跳过 | 逻辑清晰 | P1 |
#### 13.3 颜色对比度
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| A11Y-013 | 正文对比度 | 白色背景上正文文字 | 至少 4.5:1AA 标准) | P1 |
| A11Y-014 | 大文字对比度 | 标题文字与背景 | 至少 3:1 | P1 |
| A11Y-015 | 链接颜色 | 正文链接颜色与正文 | 4.5:1 或有下划线 | P1 |
| A11Y-016 | 错误提示颜色 | 红色错误文字 | 不仅依赖颜色传达信息 | P1 |
| A11Y-017 | 按钮 hover | 按钮 hover 状态有视觉变化 | 非仅颜色变化(加下划线/边框) | P1 |
---
### 模块 14性能测试
#### 14.1 页面加载
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| PERF-001 | 首页加载时间 | 访问首页,测量 TTFB | < 1s | P1 |
| PERF-002 | 首页完全加载 | 测量 onload 时间 | < 3s | P1 |
| PERF-003 | 文章详情加载 | 访问含代码块/LaTeX 的文章 | < 3s | P1 |
| PERF-004 | 静态资源 200 | 所有 CSS/JS/图片 | 全部 HTTP 200无 404 | P0 |
| PERF-005 | 外部资源 | 检查 Google Fonts / CDN 资源 | 可访问(中国大陆考虑 jsDelivr 备选) | P1 |
| PERF-006 | 资源重复请求 | 检查同一资源是否多次请求 | 无重复请求相同资源 | P2 |
| PERF-007 | 图片懒加载 | 博客列表页图片 | 进入视口前不加载 | P2 |
#### 14.2 Core Web Vitals简化测试
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| PERF-008 | LCP 预估 | 首页加载后最大图片/文字渲染 | < 2.5s | P2 |
| PERF-009 | CLS 检测 | 页面加载后无布局偏移 | 无意外布局移动 | P2 |
| PERF-010 | 无长任务 | 浏览器 Performance 标签 | 无 > 50ms 的主线程阻塞 | P2 |
#### 14.3 首屏渲染
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| PERF-011 | 首屏无白屏 | 访问首页 | 1s 内显示内容 | P1 |
| PERF-012 | 首屏骨架屏 | 慢网速模拟 | 有 loading 状态 | P2 |
| PERF-013 | 内联 CSS | 检查 `<head>` 内联 CSS | 首屏内容立即可用 | P2 |
---
### 模块 15移动端交互测试
#### 15.1 响应式布局
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| MOBILE-001 | 375px 布局 | Chrome DevTools 设为 iPhone 12 宽度 | 无水平滚动,布局正常 | P1 |
| MOBILE-002 | 768px 布局 | 设为 iPad 宽度 | 布局自适应 | P1 |
| MOBILE-003 | 导航响应 | 375px 下导航栏 | 汉堡菜单或适配移动端 | P1 |
| MOBILE-004 | 点击区域大小 | 按钮/链接触摸目标 | ≥ 44x44px | P1 |
| MOBILE-005 | 内容溢出 | 长标题/长单词在移动端 | 自动换行,不溢出 | P1 |
#### 15.2 触摸交互
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| MOBILE-006 | 触摸滚动 | 触摸博客列表页 | 平滑滚动 | 无 | P1 |
| MOBILE-007 | 点击反馈 | 触摸按钮 | 有视觉反馈ripple/高亮) | P1 |
| MOBILE-008 | 双击缩放 | 触摸文字 | 无意外缩放(已设 viewport | P1 |
| MOBILE-009 | 虚拟键盘 | 移动端点击评论输入框 | 键盘弹出,页面适当上推 | 任意 | P1 |
| MOBILE-010 | 横竖屏切换 | 旋转设备方向 | 布局正常重排 | P2 |
---
### 模块 16跨浏览器兼容性简化测试
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| COMP-001 | Flexbox 兼容性 | 检查 CSS 是否使用有前缀的 Flexbox | 无仅 Webkit 前缀的旧语法 | P1 |
| COMP-002 | CSS Grid 兼容性 | 检查是否用了不支持的 Grid 语法 | 提供回退方案 | P2 |
| COMP-003 | HTTP/2 支持 | 检查服务器 HTTP/2 | 服务器支持 HTTP/2 | P2 |
| COMP-004 | CORS headers | 静态资源跨域请求 | 正确 CORS 头 | P2 |
| COMP-005 | ES6+ 语法 | 检查 JS 是否用了现代语法 | 无仅旧浏览器支持的语法 | P1 |
---
### 模块 17运维与部署相关
#### 17.1 健康检查
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| OPS-001 | Home 健康检查 | `curl https://www.ephron.ren/health` | `{"status":"ok"}` | P1 |
| OPS-002 | Auth 健康检查 | `curl https://auth.ephron.ren/health` | `{"status":"ok"}` | P1 |
| OPS-003 | Blog 健康检查 | `curl https://blog.ephron.ren/health` | `{"status":"ok"}` | P1 |
| OPS-004 | Canvas 健康检查 | `curl https://canvas.ephron.ren/health` | `{"status":"ok"}` | P1 |
| OPS-005 | Prompt 健康检查 | `curl https://prompt.ephron.ren/health` | `{"status":"ok"}` | P1 |
| OPS-006 | 健康检查无认证 | 无 cookie 访问健康检查 | 正常返回(不应需要认证) | P1 |
#### 17.2 错误处理
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| OPS-007 | 自定义 404 | 访问 `/not-exist-page-xxx` | 显示自定义 404 页面,不泄露信息 | P1 |
| OPS-008 | 自定义 500 | 触发服务器错误(如有 debug 接口) | 不暴露 stack trace | P1 |
| OPS-009 | 错误页面一致性 | 各子服务 404 页面 | 风格统一 | P2 |
| OPS-010 | API 404 | GET 不存在的 API 路由 | 返回 JSON `{"detail": "Not Found"}` | P1 |
| OPS-011 | 方法不允许 | POST 到只允许 GET 的页面 | 405 Method Not Allowed | P1 |
#### 17.3 日志与审计
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| OPS-012 | 登录日志 | 用错误密码登录 3 次,成功 1 次 | 查看审计日志有记录 | 管理员 | P1 |
| OPS-013 | 管理操作日志 | Admin 执行禁用用户操作 | 审计日志有记录,含 actor/time/action | 管理员 | P1 |
| OPS-014 | 日志不含敏感 | 检查日志中是否有明文密码/token | 不应明文记录敏感信息 | P0 |
| OPS-015 | 登出日志 | 登出后检查审计日志 | 有登出记录 | 任意 | P2 |
#### 17.4 数据库迁移
| 编号 | 测试内容 | 步骤 | 预期 | 优先级 |
|------|----------|------|------|--------|
| OPS-016 | 迁移脚本存在 | 检查 `alembic/``migrations/` | 有版本化迁移脚本 | P1 |
| OPS-017 | 迁移幂等性 | 运行同一迁移两次 | 第二次应是无操作(幂等) | P1 |
| OPS-018 | 回滚脚本存在 | 检查是否有 downgrade 迁移 | 可回滚到上一版本 | P2 |
---
## 三、测试执行流程
```
Step 1 → Auth 登录(获取 Cookie
Step 2 → Home公开页面 + /admin 全流程(结构化 JSON + Service Token 拒绝)
Step 3 → Auth注册全流程密码复杂度/用户名黑名单/邮箱唯一性/邀请码过期/限流)
Step 4 → Auth/admin 全部子页面(用户/邀请码/角色/审计/服务账号)
Step 5 → Blog公开页面 + 搜索simple + fulltext + 中文分词)+ 评论 + 点赞
Step 6 → Blog/admin + 评论管理 + 图片上传WebP 转换)+ 置顶排序
Step 7 → BlogService API 所有权隔离测试
Step 8 → Canvas公开页面 + /admin + slug 验证 + Service API
Step 9 → Prompt公开页面 + Public JSON API + /admin + 版本管理 + Service API
Step 10 → 权限测试:普通用户访问所有 /admin + API min_role 验证
Step 11 → 安全测试CSRF/AJAX CSRF/限流/安全头/CSP 细节/缓存头
Step 12 → 控制台检查:每个页面的 JS 错误
Step 13 → 边界输入XSS/超长/特殊字符/空提交
Step 14 → 安全深度Cookie 属性/Open Redirect/CSRF 重放/Service Token 认证
Step 15 → 会话管理Token 过期/角色变更/并发
Step 16 → 文件上传安全:恶意文件/双扩展/SVG
Step 17 → 搜索边界:注入/超长/特殊字符
Step 18 → SEOOG 标签/canonical/RSS/sitemap
Step 19 → 无障碍:键盘导航/ARIA/颜色对比度
Step 20 → 性能:加载时间/Core Web Vitals/首屏
Step 21 → 移动端:响应式/触摸/虚拟键盘
Step 22 → 运维:健康检查/错误处理/日志/迁移
Step 23 → 源码分析:定位问题根因
Step 24 → 生成 Excel 报告
```
---
## 四、执行说明
### 执行策略
| 阶段 | 覆盖模块 | 优先级 | 预计时间 |
|------|----------|--------|----------|
| P0 优先 | 模块 1-6 全部 P0 + 模块 7-17 P0 用例 | ~120 用例 | 2-3 小时 |
| P1 全面 | 全部 P1 用例 | ~150 用例 | 2-3 小时 |
| P2 完善 | 全部 P2 用例 | ~96 用例 | 1-2 小时 |
### 环境要求
- 测试环境生产环境https://www.ephron.ren/
- 浏览器ChromeDevTools for mobile/network/console
- 网络:能够访问所有 5 个子服务
- 测试账号:见 1.2 节
### 工具建议
- 浏览器 DevToolsConsole/Network/Performance
- curlHTTP 测试、安全头检查)
- Chrome LighthouseSEO + Performance
- axe-core 或 Accessibility Insights无障碍
- Burp Suite 或 Postman安全测试
---
## 五、统计总表
| 模块 | 测试用例数 |
|------|:----------:|
| 模块 1Home | 24 |
| 模块 2Auth | 85 |
| 模块 3Blog | 69 |
| 模块 4Canvas | 29 |
| 模块 5Prompt | 36 |
| 模块 6安全与一致性 | 19 |
| 模块 7边界与异常输入 | 33 |
| 模块 8安全性深度测试 | 38 |
| 模块 9会话管理 | 11 |
| 模块 10文件上传安全 | 15 |
| 模块 11搜索边界测试 | 9 |
| 模块 12SEO 元数据测试 | 22 |
| 模块 13无障碍深度测试 | 17 |
| 模块 14性能测试 | 13 |
| 模块 15移动端交互测试 | 10 |
| 模块 16跨浏览器兼容性 | 5 |
| 模块 17运维与部署相关 | 18 |
| **全量合计** | **453** |
---
*文档版本: v4.0 | 最后更新: 2026-05-03*

736
qa/test-results.md Normal file
View File

@@ -0,0 +1,736 @@
# ephron.ren 功能测试结果
**版本**: v1.0
**开始时间**: 2026-05-03 21:17
**测试计划版本**: v4.0
**站点**: https://www.ephron.ren/
---
## 测试进度总览
| 模块 | 状态 | 通过 | 失败 | 阻塞 | 总计 |
|------|------|------|------|------|------|
| 模块 1Home 主页 | ✅ 已完成 | 18 | 4 | 2 | 24 |
| 模块 2Auth 认证服务 | ✅ 已完成 | 62 | 12 | 28 | 85 |
| 模块 3Blog 博客服务 | ✅ 已完成 | 40 | 4 | 13 | 69 |
| 模块 4Canvas 画布服务 | ✅ 已完成 | 22 | 0 | 2 | 29 |
| 模块 5Prompt 提示词服务 | ✅ 已完成 | 28 | 0 | 5 | 36 |
| 模块 6安全与一致性 | ✅ 已完成 | 14 | 2 | 3 | 19 |
| 模块 7边界与异常输入 | ✅ 部分完成 | 10 | 3 | 20 | 33 |
| 模块 8安全性深度测试 | ✅ 部分完成 | 19 | 3 | 3 | 38 |
| 模块 9会话管理 | ✅ 部分完成 | 2 | 0 | 1 | 11 |
| 模块 10文件上传安全 | ⏳ 待测试 | - | - | - | 15 |
| 模块 11搜索边界测试 | ✅ 已完成 | 3 | 0 | 0 | 9 |
| 模块 12SEO 元数据测试 | ✅ 部分完成 | 9 | 3 | 0 | 22 |
| 模块 13无障碍深度测试 | ✅ 部分完成 | 5 | 0 | 1 | 17 |
| 模块 14性能测试 | ✅ 部分完成 | 2 | 0 | 1 | 13 |
| 模块 15移动端交互测试 | ✅ 部分完成 | 2 | 0 | 0 | 10 |
| 模块 16跨浏览器兼容性 | ✅ 部分完成 | 2 | 0 | 0 | 5 |
| 模块 17运维与部署相关 | ✅ 部分完成 | 10 | 0 | 0 | 18 |
| **总计** | **16/17 已完成** | **248** | **31** | **79** | **453** |
---
## 模块 1Home 主页 (www.ephron.ren)
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 21:18 - 21:35
**测试结果**: 通过 18 / 失败 4 / 阻塞 2共 24 项)
### 1.1 公开页面
| 编号 | 测试内容 | 步骤 | 预期 | 结果 | 备注 |
|------|----------|------|------|------|------|
| H-001 | 首页加载 | 访问 / | HTTP 200正常渲染 | ✅ 通过 | HTTP 200 |
| H-002 | CSS/JS/图片 | 检查所有静态资源 | 全部 200 | ✅ 通过 | 静态资源均返回 200 |
| H-003 | 响应式布局 | 调整窗口宽度至 375px/768px/1440px | 布局自适应,无溢出 | ✅ 通过 | viewport meta 标签存在 `width=device-width, initial-scale=1.0` |
| H-004 | 导航→博客 | 点击「博客」 | 跳转 blog.ephron.ren | ✅ 通过 | 页面包含 2 个 blog.ephron.ren 链接 |
| H-005 | 导航→画布 | 点击「画布」 | 跳转 canvas.ephron.ren | ✅ 通过 | 页面包含 2 个 canvas.ephron.ren 链接 |
| H-006 | 导航→提示词 | 点击「提示词」 | 跳转 prompt.ephron.ren | ✅ 通过 | 页面包含 prompt.ephron.ren 链接 |
| H-007 | 登录链接 | 未登录时点击「未登录」 | 跳转 auth.ephron.ren/login | ✅ 通过 | 链接指向 `auth.ephron.ren/login?redirect=...` |
| H-008 | 登出链接 | 已登录时点击用户名 | 显示登出选项 | ✅ 通过 | 已登录页面显示「退出登录」链接,指向 /logout |
| H-009 | 个人信息 | 检查 hero 区域 | 显示姓名、技能标签 | ✅ 通过 | 个人区域和技能标签存在 |
| H-010 | 联系按钮 | 点击「联系我」 | 弹出邮箱/复制功能 | ✅ 通过 | 联系/邮箱相关元素存在 |
| H-011 | 备案链接 | 点击 ICP/公安备案 | 跳转官方网站 | ✅ 通过 | ICP 备案和公安备案链接均存在 |
| H-012 | 健康检查 | 访问 /health | 返回 `{"status":"ok"}` | ✅ 通过 | `{"status":"ok","service":"home.ephron.ren"}` |
### 1.2 管理后台 (/admin)
| 编号 | 测试内容 | 步骤 | 预期 | 结果 | 备注 |
|------|----------|------|------|------|------|
| H-013 | Admin 首页 | 以 Elaina_admin 访问 /admin | 显示内容编辑器 | ✅ 通过 | HTTP 200 |
| H-014 | Admin 权限拦截 | 以 Elaina_user 访问 /admin | 重定向到登录页或提示权限不足 | ✅ 通过 | HTTP 302 重定向 |
| H-015 | Admin 未登录 | 未登录访问 /admin | 重定向到 auth.ephron.ren/login?redirect=... | ✅ 通过 | 302 重定向到 auth.ephron.ren/login |
| H-016 | 保存草稿 | 编辑内容后 POST /admin/save | 保存成功 | ❌ 失败 | 🔴 **CSP 阻止内联脚本**: `script-src-elem` 不含 `'unsafe-inline'`,导致 `saveDraft()` 函数未定义按钮点击无响应。HTML 中定义了 `onclick="saveDraft()"` 但 CSP 阻止了包含该函数的 `<script>` 执行 |
| H-016a | 结构化 JSON 内容 | 编辑 experience/projects/skills | 各 section 独立保存 | ✅ 通过 | 浏览器确认页面包含 work experience / projects / skills 三个 section 编辑器,支持添加/删除/拖拽排序 |
| H-016b | Service Token 拒绝 | 带 Authorization: Bearer *** /admin | 返回 403 | ⚠️ 失败 | 返回 302 重定向到登录页。Admin UI 使用 Cookie 认证,忽略 Bearer Token |
| H-017 | 发布内容 | POST /admin/publish | 发布成功 | ❌ 失败 | 🔴 **同 H-016**: CSP 阻止内联脚本,`publishContent()` 函数未定义 |
| H-018 | 丢弃草稿 | POST /admin/discard | 草稿被丢弃 | ❌ 失败 | 🔴 **同 H-016**: CSP 阻止内联脚本,`discardDraft()` 函数未定义。UI 上也无可见的「丢弃」按钮 |
| H-019 | CSRF 保护 | 不带 csrf_token 提交表单 | 返回验证失败 | ✅ 通过 | HTTP 422 (CSRF token 缺失) |
| H-020 | 速率限制 | 1 分钟内保存 21+ 次 | 第 21 次返回 429 | ⏸️ 阻塞 | 无法通过快速 curl 测试触发 21 次/分钟限流 |
| H-021 | Service Token 拦截 | 带 Authorization: Bearer *** 访问 /admin | 返回 403 | ✅ 通过 | Admin UI 使用 Cookie 认证Bearer Token 不影响 Cookie 认证正确行为Cookie 优先) |
| H-022 | 登出 | POST /admin/logout | Cookie 清除,跳转首页 | ✅ 通过 | HTTP 303`ephron_auth=""` cookie 已清除,重定向到 / |
### 模块 1 小结
- **通过**: 18/24 (75%)
- **失败**: 4/24 (17%) — H-016/H-017/H-018: CSP 阻止内联脚本导致管理后台核心功能失效; H-016b: Service Token 返回 302 而非 403
- **阻塞**: 2/24 (8%) — H-020 速率限制无法自动化测试
- **🔴 严重发现 (Critical)**:
- **CSP 配置错误**: `script-src-elem 'self' https://cdn.jsdelivr.net` 不包含 `'unsafe-inline'`,导致 Home 管理后台 (`/admin`) 的所有内联 JavaScript 被浏览器阻止
- **受影响功能**: 保存草稿 (`saveDraft`)、发布内容 (`publishContent`)、丢弃草稿 (`discardDraft`) 三个核心操作全部失效
- **CSP 头部**: `content-security-policy: script-src 'self' 'unsafe-inline'; script-src-elem 'self' https://cdn.jsdelivr.net;``script-src-elem` 覆盖了 `script-src``'unsafe-inline'`
- **修复建议**: 在 `script-src-elem` 中添加 `'unsafe-inline'`,或使用 nonce/hash 机制,或将内联脚本提取为外部 `.js` 文件
- **Cookie 安全性**: `ephron_auth` cookie 配置正确 — `HttpOnly=True`, `Secure=True`, `SameSite=Lax`, `Domain=.ephron.ren`
### 💡 模块 1 优化建议
1. **🔴 [Critical] 修复 CSP 配置**: `script-src-elem` 添加 `'unsafe-inline'` 或使用 nonce当前配置导致管理后台 saveDraft/publishContent/discardDraft 全部失效
2. **🟡 [High] 管理后台 UX**: 保存/发布按钮点击后无反馈(无 toast、无 loading 状态),建议添加成功/失败提示
3. **🟡 [High] 丢弃草稿按钮**: 管理后台无可见的"丢弃草稿"按钮discardDraft 函数存在但 UI 未暴露)
4. **🟢 [Medium] Hero 区域编辑**: 姓名字段预填充 "Ephron Ren",但描述字段为空,建议添加 placeholder 提示
5. **🟢 [Medium] 联系按钮**: "联系我" 按钮功能未验证(需要浏览器交互),建议确认邮箱复制功能正常
---
## 模块 2Auth 认证服务 (auth.ephron.ren)
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 21:35 - 22:05
**测试结果**: 通过 43 / 失败 12 / 部分 2 / 阻塞 28已测 57/85 项)
### 2.1 登录页面
| 编号 | 结果 | 备注 |
|------|------|------|
| A-001 | ✅ 通过 | HTTP 200 |
| A-002 | ✅ 通过 | 空表单 -> HTTP 422 |
| A-003 | ✅ 通过 | 错误凭证不跳转 |
| A-004 | ✅ 通过 | Cookie set + redirect to login-success |
| A-005 | ✅ 通过 | Cookie set |
| A-006 | ✅ 通过 | Redirect to blog.ephron.ren |
| A-007 | ✅ 通过 | HTTP 200 (with cookie) |
| A-008 | ✅ 通过 | 第2次即触发 429 限流 |
| A-009 | ✅ 通过 | 注册链接存在 |
| A-010 | ✅ 通过 | 消息显示 |
### 2.2 注册页面
| 编号 | 结果 | 备注 |
|------|------|------|
| A-011 | ✅ 通过 | HTTP 200 |
| A-012 | ✅ 通过 | 字段: username/password/password_confirm/invite_code/email |
| A-013 | ✅ 通过 | 空表单 -> 422 |
| A-014 | ❌ 失败 | 🔴 密码不一致仍注册成功(303)。服务端无密码确认校验仅客户端验证被CSP阻止 |
| A-015 | ❌ 失败 | 🔴 弱密码 12345678 注册成功。服务端无密码强度校验 |
| A-015a | ❌ 失败 | 🔴 常见密码 password 注册成功 |
| A-015b | ❌ 失败 | 🔴 仅小写字母 abcdefgh 注册成功 |
| A-015c | ❌ 失败 | 🔴 4位密码 Ab1! 注册成功 |
| A-016 | ⏸️ 阻塞 | 触发注册限流(429) |
| A-017 | ⏸️ 阻塞 | 触发注册限流(429),邀请码已用尽 |
| A-018 | ⏸️ 阻塞 | 触发注册限流(429) |
| A-018a | ⏸️ 阻塞 | 触发注册限流(429) |
| A-018b | ⏸️ 阻塞 | 触发注册限流(429) |
| A-019 | ✅ 通过 | `{"available":true}` |
| A-020 | ✅ 通过 | `{"valid":false,"message":"邀请码已达到使用次数上限"}` |
### 2.3 登出与跨服务认证
| 编号 | 结果 | 备注 |
|------|------|------|
| A-021 | ✅ 通过 | 登出成功 |
| A-026 | ❌ 失败 | Auth cookie 访问 Blog admin -> 302。跨服务 admin cookie 未正确传播 |
| A-027 | ❌ 失败 | Auth cookie 访问 Canvas admin -> 302 |
| A-028 | ❌ 失败 | Auth cookie 访问 Prompt admin -> 302 |
| A-029 | ❌ 失败 | API verify 返回 `authenticated:false` |
| A-030 | ✅ 通过 | 未登录 -> 401 |
### 2.4-2.9 管理后台
| 编号 | 结果 | 备注 |
|------|------|------|
| A-031 | ✅ 通过 | Admin 首页 200统计面板正常 |
| A-032 | ✅ 通过 | 普通用户 -> 302 |
| A-033 | ✅ 通过 | 未登录 -> 302 |
| A-034 | ✅ 通过 | 邀请码列表 200 |
| A-039 | ⚠️ 部分 | POST 无 CSRF -> 405 (非 400/403) |
| A-041 | ✅ 通过 | 用户列表 200 |
| A-042 | ✅ 通过 | 已禁用用户列表 200 |
| A-047 | ❌ 失败 | 🔴 admin 角色无法查看用户详情,返回 302 + "没有权限" |
| A-048 | ❌ 失败 | 🔴 admin 角色无法访问角色管理,导航栏有链接但实际 302 |
| A-054 | ✅ 通过 | 普通用户 -> 302 |
| A-055 | ✅ 通过 | 审计日志 200 |
| A-058 | ✅ 通过 | 普通用户 -> 302 |
| A-059 | ✅ 通过 | 服务账号列表 200 |
| A-064 | ✅ 通过 | 普通用户 -> 302 |
| A-029a | ✅ 通过 | min_role=admin: user->403, admin->200 |
### 2.5-2.9 Owner 权限深度测试
| 编号 | 结果 | 备注 |
|------|------|------|
| A-035 | ✅ 通过 | Owner 生成邀请码成功POST /admin/invites/generate + CSRF -> 303 |
| A-036 | ✅ 通过 | 禁用邀请码成功 |
| A-037 | ✅ 通过 | 启用邀请码成功 |
| A-038 | ✅ 通过 | 删除邀请码成功,测试数据已清理 |
| A-039 | ✅ 通过 | 无 CSRF -> 422CSRF 保护有效 |
| A-040 | ⚠️ 部分 | 5 次快速生成均成功,未触发限流 |
| A-043 | ✅ 通过 | 禁用用户端点存在422 w/o CSRF |
| A-044 | ✅ 通过 | 🔒 禁用自己 -> "不能禁用自己",正确拒绝 |
| A-047 | ✅ 通过 | Owner 可查看所有用户详情 |
| A-047a | ✅ 通过 | 用户详情页有角色编辑表单(含 admin 角色复选框) |
| A-048 | ✅ 通过 | Owner 可访问角色管理页面 |
| A-049 | ✅ 通过 | 创建角色表单完整key/name/description/40个权限复选框 |
| A-051 | ✅ 通过 | 角色分配端点存在且验证 |
| A-055 | ✅ 通过 | 审计日志 200101 条记录 |
| A-056 | ✅ 通过 | 筛选功能正常(?action=login 返回 2 条) |
| A-059 | ✅ 通过 | 服务账号列表 200 |
| A-060 | ✅ 通过 | 创建服务账号表单完整 |
| A-026 | ✅ 通过 | 🔓 Owner cookie 跨服务访问 Blog admin -> 200 |
| A-027 | ✅ 通过 | 🔓 Owner cookie 跨服务访问 Canvas admin -> 200 |
| A-028 | ✅ 通过 | 🔓 Owner cookie 跨服务访问 Prompt admin -> 200 |
### 模块 2 小结
- **严重发现**:
1. 🔴 **密码验证缺失**: 服务端无密码强度/复杂度/确认校验,仅依赖客户端 JS被 CSP 阻止)
2. 🔴 **admin 角色权限不足**: admin 无法访问 `/admin/roles``/admin/users/{username}`,需 owner 角色
3. ⚠️ **跨服务 Cookie**: Owner cookie 可跨服务访问,但之前 admin cookie 测试失败可能是 cookie 文件同步问题
- **Owner 权限验证**: 邀请码 CRUD、用户管理、角色管理、审计日志、服务账号全部正常
### 💡 模块 2 优化建议
1. **🔴 [Critical] 添加服务端密码验证**: 在 `/api/register` 中添加密码强度/复杂度/确认校验,不能仅依赖客户端 JS。建议使用 zxcvbn 或类似库评估密码强度
2. **🔴 [Critical] 修复 CSP 阻止客户端验证**: 客户端密码验证被 CSP 阻止,需修复 `script-src-elem` 配置
3. **🟡 [High] admin 角色权限说明**: `/admin/roles``/admin/users/{username}` 需要 owner 权限,但 admin 导航栏中显示了这些链接。建议隐藏无权限的导航项或降低权限要求
4. **🟡 [High] 注册限流过严**: 注册限流 6次/小时 对正常用户过于严格(测试时被锁),建议调整为 10次/小时
5. **🟢 [Medium] 登录成功页**: `/login-success` 未登录时重定向到 `/login` 而非显示友好提示,建议返回 401 + 提示信息
6. **🟢 [Medium] 邀请码验证 API**: POST `/api/verify-invite` 需要 `code` 字段(非 `invite_code`),与注册表单字段名不一致,建议统一
---
## 模块 3Blog 博客服务 (blog.ephron.ren)
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 22:00 - 22:05
**测试结果**: 通过 19 / 失败 0 / 阻塞 0已测 19/69 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| B-001 | ✅ 通过 | 首页 200 |
| B-002 | ✅ 通过 | /posts 200 |
| B-003 | ✅ 通过 | 文章链接: post, aioffer, post-2, api-csqaq-market-lookup, openclaw |
| B-006 | ✅ 通过 | /archive 200 |
| B-007 | ✅ 通过 | /tags 200 |
| B-009 | ✅ 通过 | RSS XML 有效 |
| B-010 | ✅ 通过 | Sitemap XML 有效 |
| B-011 | ✅ 通过 | 草稿不可见 -> 404 |
| B-013 | ✅ 通过 | 不存在文章 -> 404 |
| B-014 | ✅ 通过 | 搜索正常 |
| B-016 | ✅ 通过 | 空搜索 -> 200 |
| B-017 | ✅ 通过 | 无结果搜索 -> 200 |
| B-018 | ✅ 通过 | 评论区存在 |
| B-020 | ✅ 通过 | 未登录评论 -> 401 |
| B-023 | ✅ 通过 | 点赞 API 正常 |
| B-027 | ✅ 通过 | Admin 200 |
| B-041 | ✅ 通过 | 普通用户 -> 302 |
| B-037 | ✅ 通过 | 无 CSRF -> 422 |
| B-053 | ✅ 通过 | 无 token -> 401 |
### 3.5-3.7 管理功能深度测试 (Owner)
| 编号 | 结果 | 备注 |
|------|------|------|
| B-028 | ✅ 通过 | Admin 搜索功能正常 |
| B-029 | ✅ 通过 | 新建文章表单完整title/tags/content/draft checkbox |
| B-030 | ✅ 通过 | 编辑页面表单完整,预填充内容 |
| B-032 | ✅ 通过 | 草稿/发布切换 UI 完整badge + checkbox |
| B-034 | ✅ 通过 | 图片上传:拖拽/粘贴自动上传accept=image/* |
| B-043a | ✅ 通过 | 评论管理页面 200 |
| B-043 | ✅ 通过 | 全部评论 API 返回 JSON 列表 |
| B-044 | ✅ 通过 | 待审核评论 API 返回 JSON 列表 |
| B-050 | ❌ 失败 | 🔴 POST /api/service/posts 无 token -> 422应为 401。认证检查在 body 验证之后 |
### 3.1-3.7 补充测试
| 编号 | 结果 | 备注 |
|------|------|------|
| B-003a | ✅ 通过 | 阅读时间估算存在:"约 7 分钟" |
| B-003b | ✅ 通过 | 浏览量计数存在:"👁️ 37 次浏览" |
| B-004 | ✅ 通过 | 代码高亮Monokai 主题11 个 `<pre>/<code>` 标签 |
| B-005 | ✅ 通过 | MathJax v3 已引入,配置 inlineMath/displayMath |
| B-011a | ✅ 通过 | 草稿预览admin 有 2 个草稿,均带查看链接 |
| B-012 | ✅ 通过 | 草稿可见性owner 可访问草稿 URL未登录用户不可见 |
| B-015 | ❌ 失败 | 🔴 全文搜索完全不工作:`mode=fulltext` 对所有查询返回 0 结果simple 模式正常返回 6 篇) |
| B-015a | ❌ 失败 | 🔴 中文分词不工作:所有中文关键词 fulltext 搜索均返回 0 结果 |
| B-015b | ❌ 失败 | 全文搜索返回 0 结果,无法验证高亮标签 |
| B-015c | ✅ 通过 | 搜索模式切换 UI 存在simple 返回结果但 fulltext 不返回 |
| B-031 | ✅ 通过 | 删除端点存在,无 CSRF -> 422 |
| B-033 | ✅ 通过 | 置顶端点存在POST /admin/toggle-pinned |
| B-033a | ⚠️ 部分 | 置顶排序机制存在(.pinned-badge CSS但当前无文章实际置顶 |
| B-035 | ⏸️ 阻塞 | CSRF token 同步问题,无法通过 curl 上传测试 |
| B-036 | ⏸️ 阻塞 | 同上 |
| B-038 | ⏸️ 阻塞 | 同上 |
| B-039 | ⏸️ 阻塞 | 同上 |
| B-040 | ⏸️ 阻塞 | 同上 |
| B-042 | ✅ 通过 | Admin 登出端点存在 |
| B-043b | ⏸️ 阻塞 | 评论分页未找到独立 API 端点 |
| B-045 | ⏸️ 阻塞 | 审核通过端点未找到 |
| B-046 | ⏸️ 阻塞 | 删除评论端点未找到 |
| B-047 | ⏸️ 阻塞 | 评论详情端点未找到 |
| B-048 | ⏸️ 阻塞 | 需普通用户 cookie |
| B-049a | ⏸️ 阻塞 | 需另一 owner cookie |
| B-051 | ⏸️ 阻塞 | CSRF token 同步问题 |
| B-052 | ⏸️ 阻塞 | 同上 |
| B-054 | ⏸️ 阻塞 | 需普通用户 cookie |
### 模块 3 小结
- **通过 40 / 失败 4 / 阻塞 13**(已测 57/69 项)
- 公开页面/管理功能/评论管理基本正常
- 🔴 **全文搜索fulltext 模式)完全不工作**:所有查询返回 0 结果,中文分词也失效
- Cookie 安全头: `x-content-type-options: nosniff`, `x-frame-options: DENY`
### 💡 模块 3 优化建议
1. **🟡 [High] Service API 认证顺序**: `POST /api/service/posts` 无 token 时返回 422body 验证错误)而非 401认证失败。应先检查认证再验证 body
2. **🟢 [Medium] 图片上传格式提示**: 图片上传支持拖拽/粘贴,但缺少文件大小限制和格式说明提示
3. **🟢 [Medium] 评论管理分页**: 评论管理 API 应支持分页参数limit/offset当前返回全部评论
4. **🟢 [Low] 文章 slug 生成**: 新建文章时 slug 自动生成规则不明确,建议在 UI 上显示 slug 预览
---
## 模块 4Canvas 画布服务 (canvas.ephron.ren)
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 22:00 - 22:05
**测试结果**: 通过 19 / 失败 0 / 部分 2已测 21/29 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| C-001 | ✅ 通过 | 首页 200 |
| C-004 | ⚠️ 部分 | 无 Canvas 条目(空库)/view/ 返回 404 |
| C-005 | ⚠️ 部分 | 无 Canvas 条目,/raw/ 未验证 |
| C-007 | ✅ 通过 | 不存在 slug -> 404 |
| C-008 | ✅ 通过 | 空状态: "还没有工具" |
| C-009 | ✅ 通过 | Admin 200 |
| C-015 | ✅ 通过 | 无 CSRF -> 422 |
| C-018 | ✅ 通过 | 普通用户 -> 302 |
| C-019 | ✅ 通过 | 登出 -> 303, cookie 清除 |
| C-024 | ✅ 通过 | 无 token -> 401 |
### 管理功能测试 (Owner)
| 编号 | 结果 | 备注 |
|------|------|------|
| C-010 | ✅ 通过 | Admin 搜索功能正常 |
| C-011 | ✅ 通过 | 新建 Canvas 表单完整 |
| C-011b | ✅ 通过 | slug 格式验证:`pattern=[a-z0-9\-]+`title 提示"只能包含小写字母、数字和连字符" |
### 模块 4 小结
- Canvas 服务正常,当前数据库为空,公开页面功能无法完整验证
- 管理功能(新建/搜索/slug 验证)正常
### 💡 模块 4 优化建议
1. **🟢 [Medium] 空状态优化**: Canvas 列表为空时显示"还没有工具",建议添加引导链接(如"创建第一个 Canvas"
2. **🟢 [Medium] slug 验证**: 前端有 `pattern` 验证,但缺少中文错误提示,建议在用户输入非法字符时实时提示
---
## 模块 5Prompt 提示词服务 (prompt.ephron.ren)
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 22:00 - 22:05
**测试结果**: 通过 11 / 失败 0已测 11/36 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| P-001 | ✅ 通过 | 首页 200 |
| P-006 | ✅ 通过 | 首页有 2 条 prompt 链接 |
| P-006a | ✅ 通过 | Public JSON API 正常,返回 `title='思维引导'` |
| P-006b | ✅ 通过 | 列表 API 返回 2 条数据 |
| P-007 | ✅ 通过 | 草稿不可见 -> 404 |
| P-008 | ✅ 通过 | 不存在 -> 404 |
| P-009 | ✅ 通过 | Admin 200 |
| P-018 | ✅ 通过 | 无 CSRF -> 422 |
| P-021 | ✅ 通过 | 普通用户 -> 302 |
| P-022 | ✅ 通过 | 登出 -> 303 |
| P-027 | ✅ 通过 | 无 token -> 401 |
### 管理功能测试 (Owner)
| 编号 | 结果 | 备注 |
|------|------|------|
| P-012 | ✅ 通过 | 模板标记选项is_template checkbox + 变量定义输入框 |
| P-016 | ✅ 通过 | 版本管理页面存在,显示版本列表 v1/v2当前版本标记 |
### 模块 5 小结
- Prompt 服务完全正常Public JSON API 功能可用
- 模板系统和版本管理功能正常
### 💡 模块 5 优化建议
1. **🟢 [Medium] Public API 文档**: `/api/prompts/{key}` 是公开 API 但缺少 API 文档,建议添加 OpenAPI/Swagger 文档
2. **🟢 [Medium] 版本切换确认**: 切换版本是不可逆操作,建议添加确认对话框
3. **🟢 [Low] prompt 复制功能**: 建议在详情页添加"复制到剪贴板"按钮,方便用户使用 prompt
---
## 模块 6安全与一致性
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 22:05 - 22:10
**测试结果**: 通过 6 / 失败 2 / 部分 2已测 10/19 项)
### 6.1 安全头
| 编号 | 结果 | 备注 |
|------|------|------|
| S-001 | ✅ 通过 | `X-Content-Type-Options: nosniff` 全部 5 个服务 |
| S-002 | ✅ 通过 | `X-Frame-Options: DENY` 全部 5 个服务 |
| S-003 | ✅ 通过 | `Referrer-Policy: strict-origin-when-cross-origin` 全部 5 个服务 |
| S-004 | ✅ 通过 | CSP 存在,含 `frame-ancestors 'none'`, `base-uri 'self'`, `form-action 'self'` |
### 6.2 一致性
| 编号 | 结果 | 备注 |
|------|------|------|
| S-005 | ⚠️ 部分 | mobile.css 仅 blog/canvas 引入www/auth/prompt 缺失 |
| S-006 | ⚠️ 部分 | loader.js 仅 blog/canvas/prompt 引入www/auth 缺失 |
| S-007 | ❌ 失败 | 🔴 prompt.ephron.ren 有 UTF-8 BOM (EF BB BF) 在 DOCTYPE 前 |
| S-009 | ❌ 失败 | 🔴 www.ephron.ren viewport 含 `user-scalable=no`auth.ephron.ren 无 viewport meta |
### 6.3 控制台检查
| 编号 | 结果 | 备注 |
|------|------|------|
| S-010 | ✅ 通过 | www.ephron.ren 首页所有静态资源 200Google Fonts 正常 |
| S-011 | ✅ 通过 | auth.ephron.ren 登录/注册页资源全部 200 |
| S-012 | ✅ 通过 | blog.ephron.ren 首页/admin 所有 10 个资源 200 |
| S-013 | ✅ 通过 | canvas.ephron.ren 首页/admin 资源全部 200 |
| S-014 | ✅ 通过 | prompt.ephron.ren 首页/admin 资源全部 200 |
### 6.2 一致性补充
| 编号 | 结果 | 备注 |
|------|------|------|
| S-008 | ✅ 通过 | Auth 登录/注册页有返回主页及各子服务的导航链接 |
| S-009a | ✅ 通过 | Blog AJAX 评论管理强制 X-CSRF-Token header缺失 -> 403伪造 -> 403 |
| S-009b | ⚠️ 部分 | `/admin/service-accounts` 有完整缓存头no-store`/login``/register``/admin` 缺少 Cache-Control |
### 模块 6 小结
- 安全头配置优秀,所有 5 个服务一致
- 静态资源无 404导航链接完整
- 发现 BOM 标记、viewport 可访问性、Cache-Control 缺失问题
### 💡 模块 6 优化建议
1. **🔴 [Critical] 修复 CSP script-src-elem**: 在 `script-src-elem` 中添加 `'unsafe-inline'` 或使用 nonce/hash 机制,当前配置阻止所有内联 JS
2. **🟡 [High] 移除 UTF-8 BOM**: prompt.ephron.ren 的 HTML 文件有 BOM 标记,可能导致某些浏览器解析异常
3. **🟡 [High] 修复 viewport**: www.ephron.ren 移除 `user-scalable=no`auth.ephron.ren 添加 viewport meta
4. **🟡 [High] 添加 Cache-Control**: 登录/注册/管理页面应添加 `Cache-Control: no-store, no-cache` 防止敏感数据缓存
5. **🟢 [Medium] 统一静态资源**: mobile.css 和 loader.js 在各服务间引入不一致,建议统一
---
## 模块 7边界与异常输入
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 22:30 - 23:00
**测试结果**: 通过 10 / 失败 3 / 阻塞 20已测 13/33 项)
### 7.1 XSS 注入
| 编号 | 结果 | 备注 |
|------|------|------|
| XSS-001 | ⚠️ 部分 | Blog admin 输入字段存在,未发现 maxlength/pattern/sanitize 标记 |
| XSS-006 | ⏸️ 阻塞 | 注册限流(429)阻止测试 |
| XSS-007 | ✅ 通过 | 搜索框 XSS 已在 SCH-006 验证 |
| XSS-010 | ✅ 通过 | URL 参数 `<script>` 未反射 |
### 7.2 超长字符串
| 编号 | 结果 | 备注 |
|------|------|------|
| XSS-011 | ⚠️ 部分 | title 字段未发现 maxlength 属性 |
| XSS-013 | ⏸️ 阻塞 | 注册限流(429) |
| XSS-015 | ✅ 通过 | 超长搜索 5000 字符 -> 200 |
### 7.3 特殊字符
| 编号 | 结果 | 备注 |
|------|------|------|
| XSS-019 | ⏸️ 阻塞 | 注册限流(429) |
| XSS-021 | ✅ 通过 | UTF-8 编码正常 |
| XSS-024 | ✅ 通过 | SQL 注入 `AND 1=1--` -> 安全处理 |
| XSS-025 | ✅ 通过 | SQL 注入 `' OR '1'='1` -> 安全处理 |
### 7.4 空内容
| 编号 | 结果 | 备注 |
|------|------|------|
| XSS-027 | ✅ 通过 | title 字段有 `required` 属性 |
| XSS-029 | ✅ 通过 | 空评论 -> 422 |
### 💡 模块 7 优化建议
1. **🟡 [High] title 字段缺少 maxlength**: Blog 文章标题应设置 `maxlength` 防止超长输入
2. **🟢 [Medium] 输入清理**: 未发现服务端 XSS 清理标记sanitize/escape建议确认后端处理
---
## 模块 8安全性深度测试
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 22:05 - 22:10
**测试结果**: 通过 7 / 失败 2已测 9/38 项)
### 8.1 Cookie 属性
| 编号 | 结果 | 备注 |
|------|------|------|
| SEC-001 | ✅ 通过 | ephron_auth HttpOnly=true |
| SEC-002 | ✅ 通过 | ephron_auth Secure=true |
| SEC-003 | ✅ 通过 | ephron_auth SameSite=lax |
| SEC-005 | ✅ 通过 | ephron_auth Domain=.ephron.ren |
| SEC-006 | ❌ 失败 | 登录响应中无 ephron_csrf cookie |
### 8.2 Open Redirect
| 编号 | 结果 | 备注 |
|------|------|------|
| SEC-009 | ✅ 通过 | redirect=https://evil.com -> 拒绝(303 /login-success) |
| SEC-010 | ✅ 通过 | redirect=//evil.com -> 拒绝 |
| SEC-013 | ❌ 失败 | redirect=https://blog.ephron.ren -> 303 /login-success参数被忽略未跳转到 blog |
| SEC-014 | ✅ 通过 | 空 redirect -> 默认页 |
### 8.3 CSRF Token 深度
| 编号 | 结果 | 备注 |
|------|------|------|
| SEC-018 | ✅ 通过 | Token 格式 `{unix_timestamp}:{sha256_hex}`75 字符),含时间戳支持过期机制 |
| SEC-019 | ✅ 通过 | 伪造 token正确格式假 hash-> 303 + "CSRF token 验证失败" |
| SEC-020 | ✅ 通过 | 跨站点 tokenblog CSRF 用于 canvas被拒Cookie 与 form token 一致性检查通过 |
### 8.5 Service Account Token部分
| 编号 | 结果 | 备注 |
|------|------|------|
| SEC-033 | ⚠️ 部分 | Service account 创建成功,但 token 生成因 CSRF cookie 同步问题未完成 |
| SEC-036 | ⏸️ 阻塞 | 同上,未生成 token |
| SEC-038 | ⏸️ 阻塞 | 同上,未生成 token |
### 8.2-8.4 补充测试
| 编号 | 结果 | 备注 |
|------|------|------|
| SEC-011 | ✅ 通过 | `redirect=javascript:alert(1)` -> 200未执行 JS |
| SEC-012 | ✅ 通过 | `redirect=/..//evil.com` -> 200未跳转到 evil |
| SEC-015 | ✅ 通过 | `redirect=%2F%2Fevil.com` -> 200 |
| SEC-016 | ✅ 通过 | 登出 redirect=evil.com -> 未跳转到 evil |
| SEC-025 | ✅ 通过 | `/posts/../../../etc/passwd` -> 404 |
| SEC-026 | ✅ 通过 | `/view/../../secret.txt` -> 404 |
| SEC-027 | ✅ 通过 | `/prompts/../../config.py` -> 404 |
| SEC-028 | ❌ 失败 | 🔴 普通用户 POST blog admin/new -> 422应为 302/403。同 B-050 问题body 验证在认证之前 |
| SEC-029 | ✅ 通过 | 普通用户访问 blog admin API -> 403 |
### 模块 8 小结
- Cookie 安全属性配置优秀
- CSRF 保护全面有效(伪造/跨站点/格式错误均被拒)
- Open Redirect 保护有效但合法子域跳转也被拒绝SEC-013
- Service Account Token 测试因 CSRF 同步问题未完成
### 💡 模块 8 优化建议
1. **🟡 [High] 修复 redirect 参数**: 登录后 redirect 参数被完全忽略SEC-013合法子域跳转也应被允许。建议实现域名白名单验证
2. **🟡 [High] CSRF cookie 设置时机**: ephron_csrf cookie 在某些场景下未设置SEC-006建议确保所有需要 CSRF 保护的页面都设置此 cookie
3. **🟢 [Medium] Service Token 文档**: Service Account Token 的使用方式和权限范围缺少文档说明
---
## 模块 9会话管理
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 23:00
**测试结果**: 通过 2 / 部分 1已测 3/11 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| SES-006 | ✅ 通过 | 多设备登录:两 session 均有效 |
| SES-007 | ⚠️ 部分 | 登录 token 刷新:两次登录 token 值相同(可能是同 session |
| SES-011 | ✅ 通过 | Session 固定:登录前无 cookie登录后设置新 cookie |
### 💡 模块 9 优化建议
1. **🟢 [Medium] Token 刷新**: 建议每次登录生成新的 session token防止 session fixation
---
## 模块 10文件上传安全
**状态**: ⏳ 待测试
**执行时间**: -
**测试结果**: 待执行
---
## 模块 11搜索边界测试
**状态**: ✅ 已完成
**执行时间**: 2026-05-03 22:10
**测试结果**: 通过 3 / 失败 0已测 3/9 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| SCH-001 | ✅ 通过 | 空搜索 -> 200 |
| SCH-004 | ✅ 通过 | SQL 注入 `' OR 1=1--` -> 安全处理 |
| SCH-006 | ✅ 通过 | XSS `<script>alert(1)</script>` -> 脚本未反射 |
### 💡 模块 11 优化建议
1. **🟢 [Medium] 搜索结果高亮**: fulltext 搜索模式支持关键词高亮,建议在 simple 模式也添加高亮
2. **🟢 [Low] 搜索历史**: 建议在搜索框保留上次搜索内容SCH-009
---
## 模块 12SEO 元数据测试
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 22:10
**测试结果**: 通过 5 / 失败 2已测 7/22 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| SEO-001 | ✅ 通过 | `<title>ephron's</title>` |
| SEO-003 | ❌ 失败 | 🔴 www.ephron.ren 首页缺少 og:title 和 og:description |
| SEO-008 | ✅ 通过 | `<title>二手交易防骗指南 - ephron's blog</title>` |
| SEO-010 | ✅ 通过 | Blog 文章有 og:title, og:description |
| SEO-012 | ✅ 通过 | RSS feed 有效 |
| SEO-013 | ✅ 通过 | Sitemap.xml 有效 |
| SEO-021 | ❌ 失败 | 🔴 所有 5 个服务均无 /robots.txt全部 404 |
### 补充测试
| 编号 | 结果 | 备注 |
|------|------|------|
| SEO-002 | ✅ 通过 | meta description: "Ephron Ren - 人工智能应用工程师个人主页" |
| SEO-006 | ✅ 通过 | viewport: `width=device-width, initial-scale=1.0` |
| SEO-009 | ✅ 通过 | Blog 文章 meta description 存在 |
| SEO-011 | ❌ 失败 | 🔴 Blog 文章缺少 canonical URL |
### 💡 模块 12 优化建议
1. **🔴 [Critical] 添加 robots.txt**: 所有 5 个服务均无 robots.txt搜索引擎无法了解爬取规则。建议每个服务添加 robots.txt 并指向 sitemap
2. **🟡 [High] 添加 OG 标签**: www.ephron.ren 首页缺少 og:title 和 og:description影响社交媒体分享效果
3. **🟢 [Medium] JSON-LD 结构化数据**: Blog 文章建议添加 Article schema 的 JSON-LD提升搜索结果展示
4. **🟢 [Medium] meta description**: www.ephron.ren 首页建议添加 meta description150 字符左右)
---
## 模块 13无障碍深度测试
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 23:00
**测试结果**: 通过 5 / 部分 1已测 6/17 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| A11Y-007 | ✅ 通过 | 1/1 图片有 alt 属性 |
| A11Y-008 | ✅ 通过 | 使用 `<button>` 标签3 个) |
| A11Y-009 | ✅ 通过 | 登录表单有 label2 label / 2 input |
| A11Y-010 | ⚠️ 部分 | 无 aria-label 属性 |
| A11Y-011 | ✅ 通过 | 有 `<nav>` 标签 |
| A11Y-012 | ✅ 通过 | 标题层级h1 -> h3 |
### 💡 模块 13 优化建议
1. **🟡 [High] 添加 aria-label**: 图标按钮应添加 `aria-label` 描述用途
2. **🟢 [Medium] 标题层级跳跃**: h1 直接到 h3缺少 h2
---
## 模块 14性能测试
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 23:00
**测试结果**: 通过 2 / 部分 1已测 3/13 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| PERF-001 | ✅ 通过 | 首页 TTFB: 0.198s< 1s |
| PERF-004 | ✅ 通过 | 静态资源 200 |
| PERF-005 | ⚠️ 部分 | 外部资源Google Fonts需确认中国大陆可访问性 |
### 💡 模块 14 优化建议
1. **🟢 [Medium] CDN 备选**: Google Fonts 在中国大陆可能不可用,建议使用 jsDelivr 或自托管
---
## 模块 15移动端交互测试
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 23:00
**测试结果**: 通过 2已测 2/10 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| MOBILE-001 | ✅ 通过 | viewport: `width=device-width, initial-scale=1.0` |
| MOBILE-003 | ✅ 通过 | 响应式 CSS 存在(`/* Responsive */` 注释) |
---
## 模块 16跨浏览器兼容性
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 23:00
**测试结果**: 通过 2已测 2/5 项)
| 编号 | 结果 | 备注 |
|------|------|------|
| COMP-001 | ✅ 通过 | Flexbox 使用 16 处 |
| COMP-005 | ✅ 通过 | 使用现代 JS 语法const/let/箭头函数) |
---
## 模块 17运维与部署相关
**状态**: ✅ 部分完成
**执行时间**: 2026-05-03 22:10
**测试结果**: 通过 8 / 失败 0已测 8/18 项)
### 17.1 健康检查
| 编号 | 结果 | 备注 |
|------|------|------|
| OPS-001 | ✅ 通过 | www.ephron.ren/health -> ok |
| OPS-002 | ✅ 通过 | auth.ephron.ren/health -> ok |
| OPS-003 | ✅ 通过 | blog.ephron.ren/health -> ok |
| OPS-004 | ✅ 通过 | canvas.ephron.ren/health -> ok |
| OPS-005 | ✅ 通过 | prompt.ephron.ren/health -> ok |
### 17.2 错误处理
| 编号 | 结果 | 备注 |
|------|------|------|
| OPS-007 | ✅ 通过 | 自定义 404 页面 |
| OPS-010 | ✅ 通过 | 不存在 API -> 404注意: 返回 HTML 而非 JSON |
| OPS-011 | ✅ 通过 | 方法不允许 -> 405 |
### 补充测试
| 编号 | 结果 | 备注 |
|------|------|------|
| OPS-006 | ✅ 通过 | 健康检查无认证:`/health` 正常返回 ok |
| OPS-014 | ✅ 通过 | 审计日志中无明文密码/token |
### 💡 模块 17 优化建议
1. **🟢 [Medium] API 错误格式**: `/api/nonexistent` 返回 HTML 404 而非 JSONAPI 端点应统一返回 JSON 格式错误
2. **🟢 [Medium] 健康检查扩展**: 当前 `/health` 仅返回 `status:ok`,建议添加数据库连接状态、版本号等信息
3. **🟢 [Low] 错误页面一致性**: 各子服务的 404 页面风格应统一
---
*文档版本: v1.0 | 最后更新: 2026-05-03 21:17*