b123a36aae
后端修复: - 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>
12 KiB
12 KiB
持续性调试第3轮: 面板功能API + 前端Vite构建 + Chat SSE/WebSocket
日期: 2026-05-20 14:11 CST (UTC+8)
测试者: Debug Mode (deepseek-v4-pro)
Gateway 端口: 8080 | PostgreSQL: localhost:5432
测试概览
| 测试项 | 状态 | 详情 |
|---|---|---|
| Admin 登录 | ⚠️ | 密码不一致问题,需用 admin123 或注册新用户 |
| Files GET/POST | ✅ | 列表和上传端点正常 |
| Knowledge GET/POST | ✅ | Knowledge Base 创建/列表正常 |
| Automation GET/POST | ✅ | Rules 和 Scenes 正常 |
| Briefing GET/POST | ✅ | 简报生成正常 (fallback 模式) |
| Reminder GET/POST | ✅ | 提醒创建/列表正常 |
| 前端 Vite 构建 | ✅ | 77 modules, 1.02s, 无错误 |
| WebSocket Hub | ✅ | 架构完善,支持 IoT 广播、会话状态追踪 |
| CORS 中间件 | ⚠️ | * + credentials:true 组合无效 |
| 安全头 | ❌ | 缺少 CSP, HSTS, X-Frame-Options 等 |
1. 认证登录测试
测试命令
# 尝试默认管理员密码
curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"cyrene-dev-admin"}'
# 输出: {"error":"用户名或密码错误"}
# 成功注册新用户
curl -s -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"test123456","email":"test@test.com","nickname":"Test","verify_code":"000000"}'
# 输出: 成功返回 token + user_id
根因分析: 种子密码与配置密码不一致
config.go:97:AdminPassword默认值为"cyrene-dev-admin"main.go:68-70: 种子管理员密码取自os.Getenv("ADMIN_PASSWORD"),回退值为"admin123"auth_handler.go:189-202:verifyUserPassword使用 bcrypt 验证数据库中的密码 hash- 第一次启动时,若
ADMIN_PASSWORD环境变量未设置,数据库存入的是admin123的 bcrypt hash;但config.AdminPassword返回的是"cyrene-dev-admin" - 实际情况: admin 已被迁移到 users 表,密码为
"admin123"
🔧 建议修复
统一密码源:让 main.go 中的种子操作使用 cfg.AdminPassword 而非单独的 os.Getenv("ADMIN_PASSWORD"):
// main.go:68 改为
defaultAdminPassword := cfg.AdminPassword
2. 面板 API 测试
2.1 测试结果
所有面板 API 使用 user_testuser token 测试,后端路由路径与用户文档预期路径存在差异:
| 用户文档路径 (第1轮) | 实际后端路径 | GET 状态 | POST 状态 |
|---|---|---|---|
/api/v1/files |
/api/v1/files |
✅ 200 | ✅ (multipart upload) |
/api/v1/knowledge |
/api/v1/knowledge/bases |
✅ 200 | ✅ 201 |
/api/v1/automation |
/api/v1/automation/rules |
✅ 200 | ✅ 201 |
/api/v1/briefing |
/api/v1/briefings |
✅ 200 | ✅ (generate) |
/api/v1/reminder |
/api/v1/reminders |
✅ 200 | ✅ 201 |
2.2 发现的问题
P1: 前端 Knowledge API 响应键名不匹配
- 文件:
frontend/web/src/api/knowledge.ts:43 - 前端期望:
{ bases: KnowledgeBase[] } - 后端实际返回:
{ knowledge_bases: [...], total: 0 } - 影响: 知识库列表无法正常显示
- 修复: 将
bases改为knowledge_bases,或统一后端响应键名
// knowledge.ts:42-44 当前代码
interface KBListResponse {
bases: KnowledgeBase[]; // ❌ 后端返回 knowledge_bases
}
// 应改为
interface KBListResponse {
knowledge_bases: KnowledgeBase[];
total: number;
}
P2: 简报 created_at 零值时间
- 测试输出:
"created_at":"0001-01-01T00:00:00Z" - 原因:
briefing_handler.go中创建 Briefing 时未设置CreatedAt字段 - 影响: 前端格式化日期异常
- 修复: 生成简报时设置
CreatedAt: time.Now()
P3: 分页参数不一致
- Files: 使用
page+limit - Reminders: 使用
offset+limit - 建议统一为
offset+limit或cursor模式
3. 前端 Vite 生产构建
构建结果
vite v6.4.2 building for production...
✓ 77 modules transformed.
dist/index.html 0.82 kB │ gzip: 0.50 kB
dist/assets/index-B6Z-MAXg.css 38.53 kB │ gzip: 7.11 kB
dist/assets/index-C0wremLr.js 287.20 kB │ gzip: 82.77 kB
✓ built in 1.02s
✅ 构建成功,无错误,产物大小合理。
4. Chat SSE / WebSocket 代码审查
4.1 架构概述
Browser WebSocket ──→ Gateway /ws/chat (chat_handler.go)
│
├── ReadPump (client.go) ← 用户消息
│ └── handleMessage() → streamResponse()
│
├── WritePump (client.go) → 推送消息给浏览器
│
└── Hub (hub.go)
├── 会话状态追踪 (SessionState)
├── 对话缓存 (ConversationCache)
├── IoT 设备广播 (每10秒)
├── 闲置会话清理 (每5分钟)
└── AI-Core SSE 调用 (streamResponse)
4.2 发现的问题
P4 (Critical): randomStr() 生成极弱的随机ID
- 文件:
chat_handler.go:435-442 - 问题:
time.Now().UnixNano()在循环中几乎不变,导致generateID()产生重复字符(如"aaaaaa") - 影响: 消息 ID 碰撞风险,会话 ID 可预测性
- 修复:
// 当前代码 (有缺陷)
func randomStr(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, n)
for i := range b {
b[i] = letters[time.Now().UnixNano()%int64(len(letters))] // ❌ 始终同一值
}
return string(b)
}
// 建议修复
import "crypto/rand"
func randomStr(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, n)
rand.Read(b) // ✅ 密码学安全随机
for i := range b {
b[i] = letters[int(b[i])%len(letters)]
}
return string(b)
}
P5: WebSocket 仅限管理员访问
- 文件:
chat_handler.go:70-77 - 当前行为:
!strings.HasPrefix(userID, "admin_")返回 403 - 影响: 普通用户无法使用 WebSocket 聊天功能
- 评估: 可能是设计意图 (MVP 阶段仅管理员可用),但需确认
P6: history_response 竞态条件已防御但注释不清晰
- 文件:
useWebSocket.ts:191-202 - 前端对 WebSocket
history_response和 HTTPloadMessagesFromServer之间的竞态有防御逻辑 - 但有隐藏 bug:如果 HTTP 加载了 0 条消息(新会话),WS 的
history_response仍会被忽略,导致会话恢复信息丢失
4.3 WebSocket 协议完整性
ServerMessage 支持的消息类型:
response,stream_chunk,stream_end,stream_segments,multi_messageerror,pong,notification,device_update,background_thinkinghistory_response
前端 useWebSocket.ts 已正确处理:response, stream_chunk, stream_end, history_response, device_update, background_thinking, notification, error, pong
⚠️ 前端未处理 stream_segments 和 multi_message 消息类型 — 这些是新功能的后端消息,前端尚未实现对应 UI。
5. CORS 和安全头检查
5.1 CORS 中间件
- 文件:
cors.go - OPTIONS 预检返回
204 No Content✅ - CORS 头正确返回 ✅
P7 (Medium): Access-Control-Allow-Origin: * 与 Access-Control-Allow-Credentials: true 冲突
// cors.go:12-15 当前代码
c.Header("Access-Control-Allow-Origin", "*") // ❌ 通配符
c.Header("Access-Control-Allow-Credentials", "true") // ❌ 与通配符不兼容
根据 CORS 规范,当 credentials: true 时,Access-Control-Allow-Origin 不能为 *。浏览器会拒绝此类响应。
修复:
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
if origin == "" {
origin = "*"
}
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Credentials", "true")
// ...
}
}
5.2 缺失的安全头
当前响应头中完全没有以下安全相关 HTTP 头:
| 安全头 | 状态 | 建议值 |
|---|---|---|
X-Content-Type-Options |
❌ 缺失 | nosniff |
X-Frame-Options |
❌ 缺失 | DENY |
Strict-Transport-Security |
❌ 缺失 | max-age=31536000; includeSubDomains |
Content-Security-Policy |
❌ 缺失 | 至少 default-src 'self' |
Referrer-Policy |
❌ 缺失 | strict-origin-when-cross-origin |
X-XSS-Protection |
❌ 缺失 | 1; mode=block |
建议: 添加安全头中间件或增强现有 CORS 中间件。
6. 前后端 API 路径对照表
| 前端 API 文件 | 调用的路径 | 后端路由 (router.go) | 匹配? |
|---|---|---|---|
api/files.ts |
/files |
protected.Group("/files") |
✅ |
api/knowledge.ts |
/knowledge/bases |
protected.Group("/knowledge") |
✅ |
api/knowledge.ts |
/knowledge/search |
POST /knowledge/search |
✅ |
api/automation.ts |
/automation/rules |
automation.Group("/rules") |
✅ |
api/automation.ts |
/automation/scenes |
automation.Group("/scenes") |
✅ |
api/reminders.ts |
/reminders |
protected.Group("/reminders") |
✅ |
api/briefings.ts |
/briefings |
protected.Group("/briefings") |
✅ |
api/briefings.ts |
/briefings/latest |
GET /briefings/latest |
✅ |
api/briefings.ts |
/briefings/generate |
POST /briefings/generate |
✅ |
api/client.ts |
/sessions |
sessions := protected.Group("/sessions") |
✅ |
api/client.ts |
/memory |
memory := protected.Group("/memory") |
✅ |
api/client.ts |
/auth/login |
auth.POST("/login") |
✅ |
hooks/useWebSocket.ts |
/ws/chat |
wsGroup.GET("/chat") |
✅ |
结论: 前后端 API 路径完全匹配 ✅
7. 问题优先级汇总
| # | 严重度 | 问题描述 | 文件 |
|---|---|---|---|
| P1 | 🔴 High | Knowledge API 响应键名 knowledge_bases vs 前端期望 bases |
knowledge.ts:43 |
| P2 | 🟡 Medium | 简报 created_at 为零值 0001-01-01T00:00:00Z |
briefing_handler.go |
| P3 | 🟢 Low | Files/Reminders 分页参数不统一 (page vs offset) | 多个文件 |
| P4 | 🔴 High | randomStr() 使用 time.Now().UnixNano() 产生弱随机 |
chat_handler.go:435 |
| P5 | 🟡 Medium | WebSocket 仅限管理员(设计确认后决定) | chat_handler.go:70 |
| P6 | 🟢 Low | history_response 丢弃边缘情况 |
useWebSocket.ts:195 |
| P7 | 🟡 Medium | CORS * + credentials:true 违反规范 |
cors.go:12-15 |
| P8 | 🟡 Medium | 缺少 6 个安全 HTTP 头 | cors.go / 新中间件 |
| P9 | 🟢 Low | Admin 种子密码与配置默认密码不一致 | main.go:68 |
8. 系统状态
- Gateway: ✅ 运行中,端口 8080,0 个活跃 WS 连接
- PostgreSQL: ✅ 可用
- 前端构建: ✅
dist/目录已生成 - 系统时间: 2026-05-20 14:11 CST (UTC+8)
9. 测试覆盖率
- Files GET (返回空列表,200)
- Files 上传端点注册正确
- Knowledge Base CREATE (201)
- Knowledge Base LIST (200)
- Automation Rule CREATE (201)
- Automation Rule LIST (200)
- Automation Scene LIST (200)
- Briefing GET (200, 无当日简报)
- Briefing GENERATE (200, fallback 模式生成)
- Reminder CREATE (201)
- Reminder LIST (200)
- 前端 Vite build (成功)
- CORS 预检 (OPTIONS 返回 204)
- WebSocket 端点可达 (401 未认证, 预期行为)
- Health 端点 (GET 200)
- 完整源码审查 (hub.go, client.go, protocol.go, chat_handler.go, cors.go, auth.go, ratelimit.go, logging.go)
- 前后端 API 路径对照