Files
Cyrene/docs/debug/2026-05-20-round3-panels-frontend.md
T

12 KiB
Raw Blame History

持续性调试第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 + limitcursor 模式

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 和 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_segmentsmulti_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 路径对照