Files
Cyrene/docs/debug_log/2026-05-21-round11-api-contract.md
T
AskaEth b123a36aae fix: 第四轮调试 — 回复去重/消息时序/UI布局/自主思考深度优化 + 文档重整
后端修复:
- main.go: 恢复 /api/v1/chat 路由中丢失的 handleChat 调用 (空响应回归)
- orchestrator.go: splitChatByLines 改为双换行分割, 避免单换行误拆
- chat_handler.go: multi_message 增加 !hasReview 守卫, 消息延迟 200→800ms
- thinker.go: RecordUserMessage 追踪活跃会话ID, 推送主动消息到正确会话
- thinker.go: 增强思考提示词 — 禁止在用户休息/离开时发送主动消息

前端修复:
- useWebSocket.ts: stream_segments 不再创建消息气泡, 消除重复回复
- MessageBubble.tsx: 动作消息居左对齐无头像, 时间戳移至气泡外侧 hover 显示
- ChatInput.tsx: 昔涟输入提示移至输入框上方, 波点动画效果
- MessageList/TypingIndicator/ChatContainer: 清理冗余 isTyping 传递
- MemoryPanel.tsx: 新增记忆面板组件

文档重整:
- docs/debug/ → docs/debug_log/ 重命名统一
- 新增 debug_log/README.md 索引
- .gitignore: 新增 android/ 排除规则

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 13:09:18 +08:00

14 KiB

Round 11: API 契约测试与错误处理审计

日期: 2026-05-21 类型: 诊断报告 (只诊断,不修改代码) 测试范围: 全量 API 端点契约验证 + 源码级错误处理审计


1. 测试环境

项目
Gateway URL http://localhost:8080
运行进程 PID 8300, ./main (cyrene-gateway)
运行二进制 /home/aska/Code/Cyrene/backend/gateway/main
二进制编译时间 2026-05-17 21:36 (13.2MB)
最新源码修改 2026-05-20 22:09 (cmd/main.go)
源码-二进制匹配 不匹配 — 二进制早于源码约3天
较新可用二进制 cmd/gateway (May 20 13:55, 14.9MB), cmd/main (May 18 22:49, 14.2MB)
测试脚本 debug/cache/test_api_round11.py
数据库 PostgreSQL (连接状态未知,运行中二进制可能无法连接)
整体通过率 25/56 (44.6%)

2. 根本原因分析

2.1 反思可能的7个问题源

# 假设 可能性
1 运行中的二进制文件过旧,缺少当前源码中的路由注册 极高
2 Admin 用户的 JWT user_id 不是 "admin" 导致权限中间件失败 极高
3 数据库连接在运行中二进制中未初始化 (h.db == nil)
4 Login 逻辑在旧二进制中缺少用户名/密码验证链
5 Register 在旧二进制中缺少格式校验 (regex, 重复检查)
6 Gin 框架默认不处理 HEAD 请求 中 (框架行为)
7 Session Store 未初始化导致 500 db_error

2.2 收敛至2个根本原因

根因 #1: 运行中二进制严重过时

  • 运行进程 (PID 8300) 使用 backend/gateway/main,编译于 5月17日 21:36
  • 当前源码 (cmd/main.go, router.go, 各 handler) 最后修改于 5月20日
  • 源码中存在 2 个更新的已编译二进制: cmd/gateway (5/20) 和 cmd/main (5/18),但均未被使用
  • 运行中二进制缺少: /files, /knowledge, /automation, /reminders, /briefings, /voice 路由 → 全部返回 404
  • 运行中二进制的 Auth 处理逻辑可能与当前源码完全不同

根因 #2: Admin 用户 JWT identity 不匹配

  • JWT 负载显示: "user_id": "user_admin" (来自实际 token 解码)
  • 中间件 auth.go:45 检查: userID == "admin"
  • 当前源码 auth_handler.go:149authenticated=true 路径中设置 userID = "admin",但运行中二进制可能走的是不同的路径或旧逻辑
  • 结果: Admin token 始终被判定为 is_admin = false → 所有 /admin/* 端点返回 403

3. 测试结果详情

3.1 Part 1: 健康检查

# 端点 方法 期望 实际 结果 备注
1 /health GET 200 200 PASS
2 /health HEAD 200 404 FAIL Gin 默认不处理 HEAD;需显式注册或使用 router.HEAD()

3.2 Part 2: 注册 (Register)

# 测试用例 输入 期望 实际 结果 诊断
3 缺少 username {password,email,nickname,verify_code} 400 400 PASS
4 缺少 password {username,email,nickname,verify_code} 400 400 PASS
5 username 过短 "ab" (2字符) username:"ab" 400 201 FAIL 绑定 min=2 通过;源码 regex {3,32} 应拦截,但运行中二进制可能无此 regex 检查
6 username 过长 (33字符) username:"a"*33 400 400 PASS 绑定 max=32 生效
7 username 含特殊字符 "@!" username:"user@name!" 400 201 FAIL 同上,regex 检查未在运行中二进制中生效
8 password 过短 "Ab1!" (4字符) password:"Ab1!" 400 400 PASS 绑定 min=6 生效
9 正常注册 完整有效请求 201 201 PASS
10 重复注册 相同 username 409 201 FAIL h.db != nil 检查可能失败或 GetUserByUsername 返回 nil

ISSUE-11-001: Register 输入校验不完整 — username 正则校验和重复用户名检查在运行中二进制中缺失

3.3 Part 3: 登录 (Login)

# 测试用例 输入 期望 实际 结果 诊断
11 错误用户名 username:"nonexistent_user_99" 401 200 FAIL 返回了有效 JWT user_id:"user_nonexistent_user_99" — 旧版 Login 可能无 verifyUserPassword 检查
12 错误密码 正确用户名 + 错误密码 401 200 FAIL 同上,任意凭据均可登录
13 正确登录 有效凭据 200 200 PASS
14 Admin 登录 admin/admin123 200 200 PASS JWT 中 user_id:"user_admin" 而非 "admin"
15 JWT 刷新 有效 token 200 200 PASS
16 缺少 password {username} 400 400 PASS
17 空 body {} 400 400 PASS
18 username 格式无效 "ab" username:"ab" 400 200 FAIL Login 也缺少 username 格式校验

ISSUE-11-002: Login 端点无实际认证 — 任意不存在的用户名+密码组合均返回 200 + 有效 JWT。这是因为运行中二进制缺少 verifyUserPassword 数据库验证逻辑。

ISSUE-11-003: Admin JWT 身份不一致 — Admin 登录后 token 中的 user_id"user_admin",而中间件 IsAdminKey 检查的是 "admin"。需要统一: 要么 Login 返回 userID = "admin",要么中间件接受 "user_admin"

3.4 Part 4: 会话 (Sessions)

# 端点 期望 实际 结果 诊断
19 GET /sessions (无认证) 401 401 PASS
20 POST /sessions (无认证) 401 401 PASS
21 GET /sessions (有认证) 200 500 FAIL {"error":"查询会话失败","errorType":"db_error"}
22 POST /sessions (有认证) 201 500 FAIL {"error":"创建会话失败","errorType":"db_error"}
23 GET /sessions/:id (不存在) 404 500 FAIL {"error":"查询会话失败"}
24 POST /sessions (空标题) 201 500 FAIL {"error":"创建会话失败"}
25 DELETE /sessions/:id (不存在) 200 500 FAIL {"error":"删除会话失败"}
26 GET /sessions/:id/messages 200 500 FAIL {"error":"查询消息失败"}

ISSUE-11-004: 会话 API 全部返回 500 db_error — SessionStore 无法连接 PostgreSQL。SessionStore 在启动时初始化失败 (可能),或数据库不可达。

3.5 Part 5-10: 面板 API (文件/知识库/自动化/提醒/简报/通知)

# 端点 期望 实际 结果 诊断
27 GET /files (无认证) 401 404 FAIL 路由未注册
28 GET /files (有认证) 200 404 FAIL 路由未注册
29 GET /files/:id (不存在) 404 404 PASS* 巧合匹配 (Gin 404)
30 GET /knowledge/bases (无认证) 401 404 FAIL 路由未注册
31 GET /knowledge/bases (有认证) 200 404 FAIL 路由未注册
32 GET /knowledge/bases/:id (不存在) 404 404 PASS* 巧合匹配
33 GET /automation/rules (无认证) 401 404 FAIL 路由未注册
34 GET /automation/rules (有认证) 200 404 FAIL 路由未注册
35 GET /automation/scenes (有认证) 200 404 FAIL 路由未注册
36 GET /automation/rules/:id (不存在) 404 404 PASS* 巧合匹配
37 GET /reminders (无认证) 401 404 FAIL 路由未注册
38 GET /reminders (有认证) 200 404 FAIL 路由未注册
39 POST /reminders (缺少字段) 400 404 FAIL 路由未注册
40 GET /briefings (无认证) 401 404 FAIL 路由未注册
41 GET /briefings (有认证) 200 404 FAIL 路由未注册
42 GET /briefings/latest (有认证) 200 404 FAIL 路由未注册
43 POST /notifications/push (无认证) 401 404 FAIL 路由未注册
44 POST /notifications/push (空 body) 400 404 FAIL 路由未注册
45 GET /voice/status (有认证) 200 404 FAIL 路由未注册

ISSUE-11-005: 面板 API 路由全部返回 404 — 运行中二进制 (5月17日) 早于 router.go 中这些路由的实现 (5月20日)。确认方式: strings main | grep -E "api/v1/(files|knowledge|automation)" 返回空。

3.6 Part 11: 记忆 (Memories)

# 端点 期望 实际 结果 备注
46 GET /memory/search (无认证) 401 401 PASS
47 GET /memory/search?q=test (有认证) 200 200 PASS 正常代理到 memory-service
48 GET /memory (有认证) 200 200 PASS 返回 "数据库连接不可用" (预期行为)
49 POST /memory (缺少字段) 400 400 PASS

记忆 API 是唯一完整通过的面板端点,证明 memory_handler.go 的代理逻辑正确。

3.7 Part 12-13: Admin 端点

# 端点 期望 实际 结果 备注
50 GET /admin/sessions (admin) 200 403 FAIL {"error":"需要管理员权限"}
51 GET /admin/sessions/active (admin) 200 403 FAIL {"error":"需要管理员权限"}
52 GET /admin/sessions (普通用户) 403 403 PASS

ISSUE-11-006: Admin 端点始终返回 403 — IsAdminKey 检查 userID == "admin",但 admin token JWT 中实际为 "user_admin"

3.8 Part 14-15: 无效 Token / 刷新

# 端点 期望 实际 结果 备注
53 GET /sessions (无效 token) 401 401 PASS
54 GET /sessions (过期/畸形 token) 401 401 PASS
55 POST /auth/refresh (无认证) 401 401 PASS
56 POST /auth/refresh (无效 token) 401 401 PASS

4. 源码级错误处理审计

4.1 auth_handler.go

行号 问题 严重度
43 binding:"min=2" 与 regex {3,32} 不一致 — min 应为 3 🟡
75 if h.db != nil 包裹重复检查 — db 为 nil 时静默跳过,无警告 🔴
199-218 verifyUserPassword: 用户不存在和密码错误返回相同的 (false, nil) — 无法区分失败原因 🟡
153-177 Login fallback 路径: 3个不同分支处理 admin,逻辑复杂易出 bug 🟡

4.2 session_handler.go

行号 问题 严重度
Delete 无所有权检查 — 任何认证用户可以删除任何会话 🔴
全局 Session 操作失败返回 500 db_error 但无重试或降级到 Hub 内存 🟡

4.3 reminder_handler.go

行号 问题 严重度
List user_id 查询参数为必填,不 fallback 到 JWT 中间件中的 GetUserID(c) 🟡

4.4 auth.go

行号 问题 严重度
45 IsAdminKey 设置为 userID == "admin" — 但 Login 返回的 admin userID 是 "user_admin" 🔴

4.5 router.go

行号 问题 严重度
47-53 Health 端点仅注册 GET — Gin 不自动处理 HEAD 请求,收到 HEAD 返回 404 🟡

5. 问题汇总

编号 类别 描述 严重度 状态
ISSUE-11-001 Auth Register 输入校验不完整 (regex, 重复检查) 🔴 已发现
ISSUE-11-002 Auth Login 无实际认证 (任意凭据通过) 🔴 严重 已发现
ISSUE-11-003 Auth Admin userID 不一致 ("user_admin" vs "admin") 🔴 已发现
ISSUE-11-004 Session 全部 Session API 返回 500 (DB 不可达) 🔴 已发现
ISSUE-11-005 Router 面板 API 路由未注册 (过时二进制) 🔴 严重 已发现
ISSUE-11-006 Auth Admin 端点始终返回 403 (权限检查失败) 🔴 已发现
ISSUE-11-007 Health HEAD /health 返回 404 🟡 已发现
ISSUE-11-008 Session Delete 操作无所有权检查 🔴 代码审计
ISSUE-11-009 Auth Register binding min=2 与 regex min=3 不一致 🟡 代码审计
ISSUE-11-010 Auth verifyUserPassword 无法区分"用户不存在"和"密码错误" 🟡 代码审计

6. 建议修复优先级

P0 — 立即修复 (阻塞性问题)

  1. 重新编译并部署最新源代码 — 使用 cmd/main.go 最新源码编译,替换运行中的 May 17 二进制。这将解决 ISSUE-11-001, 002, 004, 005, 006。
  2. 统一 Admin userID — 在 auth_handler.go:149 确保 admin 用户返回 userID = "admin",与中间件 auth.go:45 一致。

P1 — 高优先级

  1. 为 Session Delete 添加所有权检查
  2. 确保数据库在启动时可用 (检查 PostgreSQL 连接状态)
  3. Register 绑定 min 改为 3 与 regex 一致

P2 — 中优先级

  1. Health 端点注册 HEAD handler 或使用 router.HEAD()
  2. verifyUserPassword 返回不同错误 以区分失败原因
  3. Reminder List fallback 到 JWT user_id

7. 测试执行信息

测试脚本: debug/cache/test_api_round11.py
执行时间: 2026-05-21 14:15 CST
总测试数: 56
通过: 25
失败: 31
通过率: 44.6%

注意: 测试脚本在 auth API 调用之间添加了 1 秒延迟,以避免触发速率限制器 (5 req/min/IP/endpoint)。


8. 附录: 二进制文件对比

路径 大小 编译时间 SHA256 (前16字符)
backend/gateway/main (运行中) 13.2MB May 17 21:36 ebdb2b2723df4e0d
backend/gateway/cmd/main 14.2MB May 18 22:49 6ebb63e6bdedfb7d
backend/gateway/cmd/gateway 14.9MB May 20 13:55 f689d2d50bf899f2

当前运行的是最旧的二进制 (May 17),缺少后续3天内的所有源码更改。