Files
Cyrene/docs/debug_log/2026-05-20-round3-panels-frontend.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

315 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 持续性调试第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. 认证登录测试
### 测试命令
```bash
# 尝试默认管理员密码
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`](../../backend/gateway/internal/config/config.go:97): `AdminPassword` 默认值为 `"cyrene-dev-admin"`
- [`main.go:68-70`](../../backend/gateway/cmd/main.go:68): 种子管理员密码取自 `os.Getenv("ADMIN_PASSWORD")`,回退值为 `"admin123"`
- [`auth_handler.go:189-202`](../../backend/gateway/internal/handler/auth_handler.go:189): `verifyUserPassword` 使用 bcrypt 验证数据库中的密码 hash
- 第一次启动时,若 `ADMIN_PASSWORD` 环境变量未设置,数据库存入的是 `admin123` 的 bcrypt hash;但 `config.AdminPassword` 返回的是 `"cyrene-dev-admin"`
- **实际情况**: admin 已被迁移到 users 表,密码为 `"admin123"`
### 🔧 建议修复
统一密码源:让 `main.go` 中的种子操作使用 `cfg.AdminPassword` 而非单独的 `os.Getenv("ADMIN_PASSWORD")`
```go
// 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`](../../frontend/web/src/api/knowledge.ts:43)
- **前端期望**: `{ bases: KnowledgeBase[] }`
- **后端实际返回**: `{ knowledge_bases: [...], total: 0 }`
- **影响**: 知识库列表无法正常显示
- **修复**: 将 `bases` 改为 `knowledge_bases`,或统一后端响应键名
```typescript
// 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`](../../backend/gateway/internal/handler/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`](../../backend/gateway/internal/handler/chat_handler.go:435)
- **问题**: `time.Now().UnixNano()` 在循环中几乎不变,导致 `generateID()` 产生重复字符(如 `"aaaaaa"`
- **影响**: 消息 ID 碰撞风险,会话 ID 可预测性
- **修复**:
```go
// 当前代码 (有缺陷)
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`](../../backend/gateway/internal/handler/chat_handler.go:70)
- **当前行为**: `!strings.HasPrefix(userID, "admin_")` 返回 403
- **影响**: 普通用户无法使用 WebSocket 聊天功能
- **评估**: 可能是设计意图 (MVP 阶段仅管理员可用),但需确认
#### P6: `history_response` 竞态条件已防御但注释不清晰
- **文件**: [`useWebSocket.ts:191-202`](../../frontend/web/src/hooks/useWebSocket.ts:191)
- 前端对 WebSocket `history_response` 和 HTTP `loadMessagesFromServer` 之间的竞态有防御逻辑
- 但有隐藏 bug:如果 HTTP 加载了 0 条消息(新会话),WS 的 `history_response` 仍会被忽略,导致会话恢复信息丢失
### 4.3 WebSocket 协议完整性
`ServerMessage` 支持的消息类型:
- `response`, `stream_chunk`, `stream_end`, `stream_segments`, `multi_message`
- `error`, `pong`, `notification`, `device_update`, `background_thinking`
- `history_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`](../../backend/gateway/internal/middleware/cors.go)
- OPTIONS 预检返回 `204 No Content`
- CORS 头正确返回 ✅
**P7 (Medium): `Access-Control-Allow-Origin: *` 与 `Access-Control-Allow-Credentials: true` 冲突**
```go
// cors.go:12-15 当前代码
c.Header("Access-Control-Allow-Origin", "*") // ❌ 通配符
c.Header("Access-Control-Allow-Credentials", "true") // ❌ 与通配符不兼容
```
根据 CORS 规范,当 `credentials: true` 时,`Access-Control-Allow-Origin` 不能为 `*`。浏览器会拒绝此类响应。
**修复**:
```go
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**: ✅ 运行中,端口 80800 个活跃 WS 连接
- **PostgreSQL**: ✅ 可用
- **前端构建**: ✅ `dist/` 目录已生成
- **系统时间**: 2026-05-20 14:11 CST (UTC+8)
---
## 9. 测试覆盖率
- [x] Files GET (返回空列表,200)
- [x] Files 上传端点注册正确
- [x] Knowledge Base CREATE (201)
- [x] Knowledge Base LIST (200)
- [x] Automation Rule CREATE (201)
- [x] Automation Rule LIST (200)
- [x] Automation Scene LIST (200)
- [x] Briefing GET (200, 无当日简报)
- [x] Briefing GENERATE (200, fallback 模式生成)
- [x] Reminder CREATE (201)
- [x] Reminder LIST (200)
- [x] 前端 Vite build (成功)
- [x] CORS 预检 (OPTIONS 返回 204)
- [x] WebSocket 端点可达 (401 未认证, 预期行为)
- [x] Health 端点 (GET 200)
- [x] 完整源码审查 (hub.go, client.go, protocol.go, chat_handler.go, cors.go, auth.go, ratelimit.go, logging.go)
- [x] 前后端 API 路径对照