Files
Cyrene/docs/debug_log/2026-05-20-round8-docker-pwa-websocket.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

17 KiB
Raw Blame History

第8轮调试报告:Docker 容器化 + PWA/Service Worker + WebSocket 深度测试

日期: 2026年5月20日 15:23 CST 范围: Docker 配置审计、PWA 配置审计、WebSocket 代码审查与连接测试、前端构建配置审计 方法: 静态代码审查 + HTTP/WebSocket 端点测试


目录

  1. Docker 配置审计
  2. PWA 配置审计
  3. WebSocket 代码审查与测试
  4. 前端构建配置审计
  5. 问题汇总与修复优先级

1. Docker 配置审计

1.1 Docker Compose 文件概览

文件 用途 服务数
docker-compose.yml 生产环境 10 (含基础设施)
docker-compose.dev.yml 开发环境 (全栈) 11 (含 NATS)
docker-compose.dev.db.yml 开发环境 (仅基础设施) 5

1.2 发现的问题

🔴 严重 (P0)

# 问题 位置 影响
1 Caddyfile 缺失 docker-compose.yml:12 引用 ./Caddyfile,但文件不存在 生产环境 docker-compose up 时 Caddy 容器将启动失败,整个集群不可访问
2 docker-compose.dev.yml 服务定义顺序错误 docker-compose.dev.yml:108-133 ai-coreiot-debug-service 之前定义,但 depends_on 引用了后者 Docker Compose 会按依赖顺序启动,但配置中 ai-core 出现在 iot-debug-service 前面,可能造成启动顺序混乱

🟡 中等 (P1)

# 问题 位置 影响
3 memory-service 和 tool-engine Dockerfile 缺少健康检查 backend/memory-service/Dockerfilebackend/tool-engine/Dockerfile 没有 HEALTHCHECK 指令 Docker Compose depends_on: condition: service_healthy 将无法判断这两个服务是否就绪
4 memory-service 和 tool-engine Dockerfile 缺少非 root 用户 同上,缺少 RUN adduser -D -H cyrene && USER cyrene 容器以 root 运行,违反最小权限原则
5 memory-service 和 tool-engine Dockerfile 缺少时区设置 同上,缺少 tzdata 安装和 Asia/Shanghai 时区配置 日志时间戳使用 UTC,与 gateway/ai-core 不一致
6 Redis 生产环境无密码 docker-compose.yml:38 REDIS_PASSWORD: ${REDIS_PASSWORD:-} 默认为空 生产环境 Redis 无密码保护,存在安全隐患
7 Qdrant 和 MinIO 无健康检查 docker-compose.yml:133-147 无法在 depends_on 中使用 condition 判断这些服务是否就绪

🟢 轻微 (P2)

# 问题 位置 影响
8 Alpine 版本不一致 gateway/ai-core/iot-debug 用 alpine:3.20memory-service/tool-engine/voice-service 用 alpine:3.21 镜像版本碎片化,增加维护成本和安全扫描复杂度
9 memory-service/tool-engine Dockerfile 缺少 -ldflags="-s -w" 编译命令缺少 strip 参数 二进制文件体积更大(约多 30%
10 voice-service Dockerfile 只复制 go.mod 不复制 go.sum backend/voice-service/Dockerfile:9 COPY go.mod ./ 缺少 go.sum go mod download 时可能下载不一致的依赖版本
11 NATS 在开发环境定义但未被使用 docker-compose.dev.yml:54-58 占用资源,增加启动时间;如果计划使用 NATS 则无问题
12 生产环境不暴露后端端口 docker-compose.yml gateway 等服务没有 ports: 映射 这是合理的设计(通过 Caddy 反向代理),但 gateway 的 Dockerfile healthcheck 使用 localhost:8080,依赖 Caddy 链路可能不准确

1.3 Dockerfile 质量对比

特性 Gateway AI-Core IoT-Debug Voice-Svc Memory-Svc Tool-Engine
多阶段构建
静态编译 (CGO_ENABLED=0)
Strip 二进制 (-ldflags="-s -w")
非 root 用户
HEALTHCHECK
时区设置 (Asia/Shanghai)
ca-certificates
git (构建阶段)
go.sum 复制 N/A
运行时资源文件复制 N/A persona N/A N/A N/A N/A

1.4 网络配置

生产环境使用 Docker 内部 DNS(服务名即主机名),服务间通信正确:

  • gatewayai-core:8081, memory-service:8091, tool-engine:8092, voice-service:8093, iot-debug-service:8083
  • tool-engineiot-debug-service:8083
  • 所有服务 → postgres:5432

2. PWA 配置审计

2.1 文件清单

文件 行数 状态
frontend/web/public/manifest.json 45 基本完整
frontend/web/public/sw.js 108 基本完整
frontend/web/public/offline.html 131 良好
frontend/web/src/hooks/usePWA.ts 175 良好但有重复注册问题
frontend/web/index.html 18 缺少 Apple PWA meta 标签

2.2 🔴 严重问题 (P0)

问题 13: Service Worker 重复注册

frontend/web/src/main.tsx:7-15frontend/web/src/hooks/usePWA.ts:25-56 都注册了 Service Worker (/sw.js)。

  • main.tsxwindow.load 事件中注册
  • usePWA.tsregisterServiceWorker() 函数也在 window.load 中注册

两个注册会产生竞争条件:第二个注册调用可能覆盖第一个,导致 SW 更新监听器丢失。如果 usePWA hook 未被挂载(例如未渲染 Header 组件),SW 仍会被 main.tsx 注册,但 usePWA 的更新检测将失效。

2.3 🟡 中等问题 (P1)

# 问题 位置 说明
14 缺少 Apple Web App meta 标签 frontend/web/index.html 缺少 apple-mobile-web-app-capable, apple-mobile-web-app-status-bar-style, apple-mobile-web-app-title — iOS Safari 添加到主屏幕时不会以独立应用模式打开
15 SW 缓存资源列表不完整 frontend/web/public/sw.js:2-5 ASSETS_TO_CACHE 仅包含 //index.html,不包含 CSS/JS bundle — 首次离线访问可能缺少资源
16 CACHE_NAME 硬编码 frontend/web/public/sw.js:1 'cyrene-v1' 硬编码,版本更新需手动修改。建议基于构建时间戳或 hash
17 Push 通知 badge 图标使用非单色图标 frontend/web/public/sw.js:87 badge 应为小尺寸单色图标(Android 通知栏专用),当前使用的 192x192 彩色 PNG 不适合

2.4 🟢 轻微问题 (P2)

# 问题 位置 说明
18 manifest.json start_url 建议用相对路径 frontend/web/public/manifest.json:5 "/" 应改为 "./""/index.html",避免部署在子路径时失效
19 缺少 512x512 专用图标 frontend/web/public/manifest.json:18-22 512x512 图标与 192x192 都指向同一张非正方形图,可能导致缩放失真

2.5 优点

  • sw.js 缓存策略合理:静态资源缓存优先,API 网络优先,WebSocket 不缓存
  • offline.html 设计良好,包含自动重连和优雅降级
  • usePWA.ts 全面覆盖 PWA 生命周期:安装提示、更新检测、在线/离线状态
  • Push 通知点击支持深度链接到会话
  • manifest.json 包含 share_targetshortcuts

3. WebSocket 代码审查与测试

3.1 架构概览

前端 (useWebSocket.ts)
  │ ws://localhost:8080/ws/chat?token=xxx&session_id=xxx
  ▼
Gateway (router.go:213)
  │ GET /ws/chat → chatHandler.HandleWebSocket
  ▼
chat_handler.go
  ├── 1. Token 验证 (query param 或 Authorization header)
  ├── 2. Admin-only 检查 (admin_ 前缀)
  ├── 3. WebSocket Upgrade (gorilla/websocket)
  ├── 4. Client 创建 → Hub.Register
  ├── 5. ReadPump + WritePump goroutines
  └── 消息路由:
       ├── "message" → AI-Core SSE 流式转发
       ├── "voice_input" → (占位,返回提示)
       └── "history" → Hub 对话缓存查询

3.2 连接测试结果

# 1. 健康检查端点正常
$ curl -s http://localhost:8080/api/v1/health
HTTP 200
{"status":"ok","service":"cyrene-gateway","ws_connections":0}

# 2. WebSocket 端点正常响应(无 token 返回认证错误,符合预期)
$ curl -s http://localhost:8080/ws/chat
{"error":"需要认证令牌"}

WebSocket 端点可正常访问,认证机制生效。

3.3 代码质量评估

backend/gateway/internal/ws/hub.go (644行)

特性 状态 说明
客户端管理 (register/unregister) 使用 channel 模式的 Hub,线程安全
用户索引 (userClients) 支持按用户 ID 快速查找连接
会话状态追踪 支持 idle/thinking/streaming/error 状态
广播机制 BroadcastToAll, SendToUser, SendToSession
闲置清理 每5分钟清理,标记超时会话为 idle
IoT 设备轮询 每10秒从 IoT 服务获取设备状态并广播
对话缓存 sync.Map 缓存,最多50条消息
持久化存储集成 可选 SessionStore 注入

backend/gateway/internal/ws/client.go (141行)

特性 状态 说明
Ping/Pong 心跳 服务端每54秒发送 Ping,客户端60秒内需回复 Pong
读写超时 写超时10s,读超时60s(基于 Pong)
消息大小限制 最大 65536 字节
优雅关闭 通道关闭时发送 CloseMessage
通道满处理 记录日志而非静默丢弃

backend/gateway/internal/ws/protocol.go (97行)

消息类型支持完整:

  • 客户端消息: message, voice_input, ping, history
  • 服务端消息: response, stream_chunk, stream_end, history_response, device_update, notification, background_thinking, multi_message, stream_segments, pong, error

3.4 前端 WebSocket Hook

frontend/web/src/hooks/useWebSocket.ts (275行)

特性 状态 说明
自动重连 断开后3秒自动重连
会话感知 currentSessionId 变化时重建连接
消息类型处理 完整覆盖所有服务端消息类型
历史竞态防护 HTTP 已加载时忽略 WS history_response
桌面通知集成 Notification API 集成
连接状态反馈 详细日志,包含 instance ID
消息丢弃保护 未就绪时发送提示消息

3.5 发现的问题

🔴 严重 (P0)

# 问题 位置 说明
20 Hub.Run() 广播循环中 RLock 下执行写操作 backend/gateway/internal/ws/hub.go:239-249 case message := <-h.broadcast: 块中使用了 h.mu.RLock() 但在 default 分支中执行 delete(h.clients, client)close(client.Send) — 这是写操作,应该使用 Lock() 而非 RLock()。虽然 close(channel) 和 delete from map 在 Go 中可能不直接 panic,但这是不正确的锁使用,可能导致竞态条件

🟡 中等 (P1)

# 问题 位置 说明
21 WebSocket CheckOrigin 允许所有来源 backend/gateway/internal/handler/chat_handler.go:38-40 return true 允许任意来源的 WebSocket 连接,生产环境应限制为已知域名
22 WebSocket 消息无速率限制 backend/gateway/internal/handler/chat_handler.go:106 客户端可无限制发送消息,可能导致 AI-Core 过载
23 voice_input 消息类型未实现 backend/gateway/internal/handler/chat_handler.go:378 仅返回占位错误,但 voice-service 已有 STT/TTS 实现

🟢 轻微 (P2)

# 问题 位置 说明
24 SendMessage 通道满时静默返回 nil backend/gateway/internal/ws/client.go:138 函数签名返回 error 但通道满时返回 nil error,调用方无法区分丢弃和成功
25 WebSocket 连接无最大连接数限制 Hub 无全局连接上限 恶意客户端可创建大量连接耗尽资源
26 useWebSocket.ts 中 connect 未被 useCallback 依赖追踪 frontend/web/src/hooks/useWebSocket.ts:92 connect 的依赖数组为 [],内部使用的 getToken() 等是函数调用而非状态依赖,这是有意的设计但需要文档说明

3.6 WebSocket 安全性总结

安全维度 状态
认证 JWT tokenquery 参数或 Authorization header
授权 主对话仅限 admin_ 前缀用户
传输加密 ⚠️ 当前为 ws://(明文),生产需 Caddy TLS 升级为 wss://
消息验证 JSON 解析失败时仅记录日志继续
输入大小限制 最大 65536 字节
跨域控制 ⚠️ CheckOrigin 全允许
速率限制

4. 前端构建配置审计

4.1 Vite 配置 (frontend/web/vite.config.ts)

配置项 状态 说明
React 插件 @vitejs/plugin-react
路径别名 @/./src/
开发服务器端口 5173
API 代理 /apihttp://localhost:8080
WebSocket 代理 /wsws://localhost:8080 (ws: true)

4.2 发现的问题

🟡 中等 (P1)

# 问题 位置 说明
27 缺少 vite-plugin-pwa frontend/web/package.json 未集成 vite-plugin-pwaSW 和 manifest 需要手动维护,无法自动生成 hash-based 缓存策略和 Workbox 集成
28 缺少构建输出配置 frontend/web/vite.config.ts 未配置 build.outDir, build.assetsDir, build.sourcemap 等生产构建参数

🟢 轻微 (P2)

# 问题 位置 说明
29 缺少环境变量类型声明 使用的 import.meta.env.VITE_WS_URL 未在 vite-env.d.ts 中声明 TypeScript 可能报类型错误

5. 问题汇总与修复优先级

统计

严重级别 数量
🔴 严重 (P0) 4
🟡 中等 (P1) 12
🟢 轻微 (P2) 13
总计 29

P0 修复列表(必须立即修复)

# 问题 修复建议
1 Caddyfile 缺失 创建 Caddyfile 或改用 Nginx/Traefik
2 docker-compose.dev.yml 服务定义顺序 iot-debug-service 移到 ai-core 之前
13 SW 重复注册 移除 main.tsx 中的 SW 注册,仅保留 usePWA.tsregisterServiceWorker()
20 Hub.Run() 广播 RLock 下写操作 h.mu.RLock() 改为 h.mu.Lock(),或将 close/delete 操作移至单独加写锁的块

P1 修复列表(建议本迭代修复)

  • Docker: memory-service/tool-engine 添加 HEALTHCHECK、非 root 用户、时区设置、ldflagsRedis 生产密码;voice-service 补全 go.sum
  • PWA: 添加 Apple meta 标签;SW 缓存资源列表扩展;CACHE_NAME 使用构建时变量
  • WebSocket: CheckOrigin 限制生产域名;添加消息速率限制;实现 voice_input
  • 构建: 集成 vite-plugin-pwa;添加生产构建配置

总体评价

  • Docker 配置: gateway/ai-core/iot-debug 三个核心服务的 Dockerfile 质量优秀(多阶段构建、非 root 用户、健康检查、二进制 strip、时区设置),但 memory-service 和 tool-engine 需要补齐。最严重的问题是 Caddyfile 缺失导致生产环境无法启动。
  • PWA: 基础实现扎实,离线页面设计精良,但 SW 重复注册和缺少 Apple meta 标签是两个需要修复的问题。
  • WebSocket: 架构设计成熟,Hub 模式 + 用户索引 + 会话追踪 + 对话缓存 + IoT 广播功能完整。主要问题是广播循环中的锁使用不正确和缺少速率限制。前端 useWebSocket hook 质量高,包含自动重连和竞态防护。
  • 构建配置: 基础配置合理,但缺少 PWA 插件自动化和生产构建优化。