chore: 从仓库移除 docs/debug_log/ — 调试日志不进版本管理

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 09:04:00 +08:00
parent 914957d667
commit 6a83624579
22 changed files with 0 additions and 5150 deletions
@@ -1,225 +0,0 @@
# Cyrene 持续性调试报告 — 10轮汇总
**日期**: 2026-05-19
**时间范围**: UTC+8 约21:30 — 23:35
**总轮次**: 10
**环境**: 本地开发 (非Docker)7个核心服务 + PostgreSQL + DevTools
---
## 一、调试轮次总览
| 轮次 | 角度 | 关键发现数 |
|------|------|-----------|
| 第1轮 | 服务间通信与API契约验证 | 4 |
| 第2轮 | IoT设备控制与工具链验证 | 2 |
| 第3轮 | 端到端功能测试与安全验证 | 2 |
| 第4轮 | LLM思维记忆优化与工具扩展 | 3 |
| 第5轮 | 独立服务抽取与调用记录 | 4 |
| 第6轮 | 多会话架构、自主思考重构与综合Bug修复 | 8 |
| 第7轮 | 认证安全与前端消息类型完整性 | 2 |
| 第8轮 | 并发安全与跨服务韧性 | 3 |
| 第9轮 | 部署配置与Docker完整性 | 4 |
| 第10轮 | 编译产物与构建完整性 | 2 |
---
## 二、所有Bug汇总 (按优先级)
### 🔴 P0 — 严重/阻塞 (5个)
| # | 来源轮次 | 问题 | 文件位置 | 影响 |
|---|---------|------|---------|------|
| 1 | R6 | Session [`randomID()`](backend/gateway/internal/handler/session_handler.go:576-582) 确定性输出导致ID冲突 | [`session_handler.go:576-582`](backend/gateway/internal/handler/session_handler.go:576) | 使用 `letters[i%len(letters)]` 而非 `crypto/rand`,每次生成相同序列;会话/消息ID可预测且可能碰撞 |
| 2 | R6 | TTS fallback不可达 | [`tts_handler.go:55-65`](backend/voice-service/internal/handler/tts_handler.go:55) | handler层 [`IsAvailable()`](backend/voice-service/internal/handler/tts_handler.go:55) 检查在第55行就返回503,导致 [`voice_handler.go`](backend/gateway/internal/handler/voice_handler.go) 中Gateway配置的fallback逻辑永远不会被触发 |
| 3 | R8 | 编排器/子会话goroutine缺少 `defer recover()` | [`orchestrator.go:81`](backend/ai-core/internal/orchestrator/orchestrator.go:81), [`manager.go:90/137`](backend/ai-core/internal/subsession/manager.go:90) | 3处 `go func()` 启动的goroutine中任何panic都会导致整个进程崩溃,无优雅恢复机制 |
| 4 | R9 | 管理员权限中间件逻辑错误 | [`router.go:231`](backend/gateway/internal/router/router.go:231) | [`adminAuth()`](backend/gateway/internal/router/router.go:231) 检查 `admin_` 前缀但实际 user_id 为 `user_admin` 格式([`auth_handler.go:97`](backend/gateway/internal/handler/auth_handler.go:97)),导致管理员永远无法通过中间件鉴权 |
| 5 | R10 | 40MB编译产物被Git追踪 | [`voice-service/main`](backend/voice-service/main), [`voice-service/main2`](backend/voice-service/main2), [`voice-service/voice-svc-new`](backend/voice-service/voice-svc-new), [`gateway/cmd/gateway`](backend/gateway/cmd/gateway) | [`.gitignore`](.gitignore) 未覆盖 `voice-service/main``voice-service/main2``voice-service/voice-svc-new``gateway/cmd/gateway` 四个二进制文件;每次提交携带约40MB无用二进制 |
### 🟡 P1 — 高优先级 (5个)
| # | 来源轮次 | 问题 | 文件位置 | 影响 |
|---|---------|------|---------|------|
| 6 | R6 | Gateway缺少IoT代理路由 | [`router.go`](backend/gateway/internal/router/router.go) | Gateway未注册IoT设备查询/控制的代理路由,前端无法通过Gateway统一入口访问IoT功能 |
| 7 | R7 | 普通用户登录不验证密码 | [`auth_handler.go:99-101`](backend/gateway/internal/handler/auth_handler.go:99) | MVP阶段注释写明"简化逻辑",任意用户名无需密码即可登录获取JWT Token;需实现bcrypt密码哈希验证 |
| 8 | R8 | IoT Client不使用context | [`iot_client.go:71`](backend/ai-core/internal/tools/iot_client.go:71) | 使用 [`client.Get()`](backend/ai-core/internal/tools/iot_client.go:71) 而非 `NewRequestWithContext`,无法传递超时/取消信号;IoT服务不可用时调用方会无限阻塞 |
| 9 | R8 | 跨服务HTTP调用无重试/熔断 | Gateway → ai-core / memory-service / tool-engine 等多处 | 所有跨服务HTTP调用均为单次请求,无重试策略、无熔断器、无降级逻辑;下游服务短暂不可用即导致请求失败 |
| 10 | R9 | 登录用户名无输入校验 | [`auth_handler.go:91`](backend/gateway/internal/handler/auth_handler.go:91) | 用户名接受任意字符包括SQL注入payload (`'; DROP TABLE--`);虽然使用参数化查询但无长度/字符白名单限制,存在潜在风险 |
### 🟢 P2 — 中优先级 (4个)
| # | 来源轮次 | 问题 | 文件位置 | 影响 |
|---|---------|------|---------|------|
| 11 | R6 | Briefing AI摘要降级 | AI-Core `/api/v1/briefing/summarize` 端点 | AI-Core返回404时前端无友好降级提示;摘要生成失败静默 |
| 12 | R7 | 前端缺少 `multi_message`/`stream_segments` 消息类型 | [`types/chat.ts`](frontend/web/src/types/chat.ts), [`MessageBubble.tsx`](frontend/web/src/components/chat/MessageBubble.tsx) | 第6轮引入的子会话架构产生的 [`MultiMessage`](backend/ai-core/internal/model/sub_session.go) 和 [`StreamEvent`](backend/ai-core/internal/model/sub_session.go) 类型在前端无对应渲染组件;多段消息被当作普通文本显示 |
| 13 | R9 | Docker Compose缺少3个服务 | [`docker-compose.dev.yml`](docker-compose.dev.yml), [`docker-compose.yml`](docker-compose.yml) | memory (8091)、tool (8092)、voice (8093) 三个服务未加入Compose编排;无法一键启动完整环境 |
| 14 | R10 | PWA缺少192x192图标 | [`manifest.json`](frontend/web/public/manifest.json), [`public/images/`](frontend/web/public/images/) | PWA manifest中引用的192x192图标文件不存在;影响PWA安装体验 |
### 🔵 P3 — 低优先级/改进建议 (5个)
| # | 来源轮次 | 问题 | 文件位置 | 影响 |
|---|---------|------|---------|------|
| 15 | R5 | 2个fire-and-forget goroutine无错误处理 | [`thinker.go`](backend/ai-core/internal/background/thinker.go), [`hub.go`](backend/gateway/internal/ws/hub.go) | 后台goroutine中的错误仅log.Printf,无告警/重试/降级;出现问题后运维难以发现 |
| 16 | R5 | .gitignore需添加gateway编译产物 | [`.gitignore`](.gitignore) | 缺失 `backend/gateway/cmd/gateway` 条目;已在P0#5中汇总 |
| 17 | R6 | Reminder `created_at` 为零值 | [`reminder_store.go`](backend/gateway/internal/store/reminder_store.go) | 创建提醒时未设置 `created_at` 字段,数据库中为 `0001-01-01`;影响提醒列表排序和展示 |
| 18 | R9 | voice-service无Dockerfile | [`voice-service/`](backend/voice-service/) | 其他5个Go服务均有Dockerfilevoice-service缺失;无法通过Docker部署 |
| 19 | R9 | Dockerfile Go版本与go.mod不一致 | [`ai-core/Dockerfile`](backend/ai-core/Dockerfile), [`gateway/Dockerfile`](backend/gateway/Dockerfile) 等多处 | 部分Dockerfile使用 `golang:1.22` 但 [`go.mod`](backend/go.work) 声明 `go 1.26`;可能导致编译失败或运行时行为差异 |
---
## 三、已验证通过的功能清单
### 编译验证 (6/6 Go服务 + 1前端)
| 模块 | 命令 | 结果 |
|------|------|------|
| `backend/ai-core` | `go vet ./...` | ✅ 通过 |
| `backend/gateway` | `go vet ./...` | ✅ 通过 |
| `backend/iot-debug-service` | `go vet ./...` | ✅ 通过 |
| `backend/memory-service` | `go vet ./...` | ✅ 通过 |
| `backend/tool-engine` | `go vet ./...` | ✅ 通过 |
| `backend/voice-service` | `go vet ./...` | ✅ 通过 |
| `frontend/web` | `tsc --noEmit` | ✅ 通过 |
| `frontend/web` | `vite build` | ✅ 构建成功 |
### API端点测试 (已验证通过)
- ✅ Gateway 健康检查: `GET /api/v1/health`
- ✅ 管理员登录: `POST /api/v1/auth/login` → 正确校验密码
- ✅ 错误密码拒绝: `POST /api/v1/auth/login` → 401
- ✅ 创建/列出/获取/删除会话
- ✅ 消息持久化 (分页加载)
- ✅ 管理员端点 (所有会话/活跃会话)
- ✅ Memory-Service 记忆 CRUD + 语义查询 + 合并 + 衰减
- ✅ Tool-Engine 工具列表 (13个工具) + 调用记录 + 统计
- ✅ IoT Debug Service 设备列表 + 属性设置 (8种操作)
- ✅ DevTools 仪表盘 + 数据库管理 + STT日志面板
- ✅ WebSocket 实时通信 + SSE流式降级
- ✅ 前端URL哈希路由 + 会话切换 + 竞态条件修复
### 架构功能验证
- ✅ 子会话并行分发架构 (IntentAnalyzer → Manager.Dispatch → Synthesizer)
- ✅ 事件驱动自主思考 (`post_chat` + `silence` 触发)
- ✅ 记忆三层注入 (核心/常用/其他)
- ✅ Gateway代理层 (memory + tool-engine)
- ✅ ai-core HTTP客户端调用外部服务
- ✅ 前端MediaRecorder降级方案 (STT)
- ✅ 前端双重WebSocket修复
- ✅ 数据库模式演化 (ALTER TABLE ADD COLUMN IF NOT EXISTS)
---
## 四、统计
| 指标 | 数值 |
|------|------|
| 测试覆盖的API端点 | 30+ |
| 通过的API测试 | 25+ |
| 发现Bug总数 | 19 (P0: 5, P1: 5, P2: 4, P3: 5) |
| Go服务编译 | 6/6 通过 |
| 前端TypeScript编译 | 通过 |
| 前端Vite构建 | 通过 |
| 涉及Go源文件 | 50+ |
| 涉及前端文件 | 20+ |
---
## 五、建议优先修复路线
### 第一阶段: 安全与稳定性 (P0)
1. **R9-P0#4 — 管理员权限中间件**: 修改 [`router.go:231`](backend/gateway/internal/router/router.go) `adminAuth()``HasPrefix` 检查,使其匹配 [`auth_handler.go:97`](backend/gateway/internal/handler/auth_handler.go:97) 生成的 `admin_` 前缀格式;当前因 `user_admin` 格式不匹配导致管理员鉴权完全失效
2. **R6-P0#1 — Session randomID**: 将 [`session_handler.go:576-582`](backend/gateway/internal/handler/session_handler.go:576) 的确定性伪随机替换为 `crypto/rand``uuid.New()`
3. **R8-P0#3 — Goroutine panic recovery**: 在 [`orchestrator.go:81`](backend/ai-core/internal/orchestrator/orchestrator.go:81)、[`manager.go:90`](backend/ai-core/internal/subsession/manager.go:90)、[`manager.go:137`](backend/ai-core/internal/subsession/manager.go:137) 三处 `go func()` 开头添加 `defer recover()`
4. **R6-P0#2 — TTS fallback**: 将 [`tts_handler.go:55`](backend/voice-service/internal/handler/tts_handler.go:55) 的 `IsAvailable()` 检查移除或改为返回特定错误码,让Gateway [`voice_handler.go`](backend/gateway/internal/handler/voice_handler.go) 的fallback逻辑可触发
5. **R10-P0#5 — 二进制文件清理**: 在 [`.gitignore`](.gitignore) 添加缺失条目并使用 `git rm --cached` 移除已追踪的二进制文件
### 第二阶段: 功能完善 (P1)
6. **R7-P1#7 — 普通用户密码验证**: 在 [`auth_handler.go:99`](backend/gateway/internal/handler/auth_handler.go:99) 实现bcrypt密码哈希存储与验证
7. **R8-P1#8 — IoT Client context传递**: 修改 [`iot_client.go:71`](backend/ai-core/internal/tools/iot_client.go:71) 使用 `NewRequestWithContext`
8. **R8-P1#9 — HTTP重试/熔断**: 引入重试库 (如 `go-resiliency`) 包装跨服务HTTP调用
9. **R6-P1#6 — Gateway IoT代理路由**: 在 [`router.go`](backend/gateway/internal/router/router.go) 注册IoT代理端点
10. **R9-P1#10 — 用户名输入校验**: 在 [`auth_handler.go:91`](backend/gateway/internal/handler/auth_handler.go:91) 添加长度和字符白名单验证
### 第三阶段: 体验与运维 (P2/P3)
11. **R9-P2#13 — Docker Compose补全**: 在 [`docker-compose.dev.yml`](docker-compose.dev.yml) 和 [`docker-compose.yml`](docker-compose.yml) 添加 memory/tool/voice 三个服务
12. **R7-P2#12 — 前端多消息类型渲染**: 在 [`types/chat.ts`](frontend/web/src/types/chat.ts) 和 [`MessageBubble.tsx`](frontend/web/src/components/chat/MessageBubble.tsx) 添加 `multi_message`/`stream_segments` 渲染支持
13. **R10-P2#14 — PWA 192x192图标**: 生成并放置缺失的图标文件
14. **R9-P3#18 — voice-service Dockerfile**: 参照其他服务为voice-service创建Dockerfile
15. **R6-P2#11、R6-P3#17、R9-P3#19、R5-P3#15** — 其余P2/P3项择机修复
---
## 六、附录: 各轮次详细发现
### 第1轮 — 服务间通信与API契约验证
- **发现1**: WebSocket [`Message`](backend/gateway/internal/ws/hub.go:37) 结构体缺少 `ID` 字段 → 前端React key为空无法渲染
- **发现2**: [`chat_handler.go`](backend/gateway/internal/handler/chat_handler.go:163) 缓存消息时未生成ID
- **发现3**: 前端 `sessionStore``useWebSocket` 缺少消息fallback ID生成
- **发现4**: 跨会话切换时旧消息闪现 (缺少 `activeSessionRef`)
### 第2轮 — IoT设备控制与工具链验证
- **发现1**: AI-Core只注册了 [`IoTQueryTool`](backend/ai-core/internal/tools/iot_tools.go) 未注册控制工具
- **发现2**: IoT Debug Service [`Toggle()`](backend/iot-debug-service/cmd/main.go:212) 写锁内调用读锁导致死锁
### 第3轮 — 端到端功能测试与安全验证
- **发现1**: [`auth_handler.go:89`](backend/gateway/internal/handler/auth_handler.go:89) 管理员密码错误时静默降级为普通用户
- **发现2**: 会话列表查询 `user_id` 匹配方式与存储不一致
### 第4轮 — LLM思维记忆优化与工具扩展
- **发现1**: 记忆表缺少 `importance`/`keywords`/`source` 列 → AutoMigrate不自动补列
- **发现2**: [`iot_tools.go:88`](backend/ai-core/internal/tools/iot_tools.go:88) `fmt.Sprintf` 格式字符串参数不足
- **发现3**: DevTools数据库管理缺少预检和重连机制
### 第5轮 — 独立服务抽取与调用记录
- **发现1**: Gateway代理记忆失败 → 缺少 `MEMORY_SERVICE_URL``TOOL_ENGINE_URL` 环境变量
- **发现2**: 前端会话切换3个竞态条件
- **发现3**: 二进制文件未被 `.gitignore` 完全覆盖
- **发现4**: DevTools `startAllSequential` 未包含新服务
### 第6轮 — 多会话架构、自主思考重构与综合Bug修复
- **发现1**: 前端双重WebSocket → [`ChatContainer.tsx`](frontend/web/src/components/chat/ChatContainer.tsx) 和 [`App.tsx`](frontend/web/src/App.tsx) 各自调用 `useChat()`
- **发现2**: Memory-Service数据库迁移失败 → `CREATE TABLE IF NOT EXISTS` 不补列
- **发现3**: 语音STT不可用 → MediaRecorder降级方案
- **发现4**: DevTools仪表盘数据库按钮失效 → 缺少 `id` 属性
- **发现5**: Session `randomID()` 确定性输出 (P0#1)
- **发现6**: TTS fallback不可达 (P0#2)
- **发现7**: Gateway缺少IoT代理路由 (P1#6)
- **发现8**: Briefing AI摘要降级 (P2#11)
### 第7轮 — 认证安全与前端消息类型完整性
- **发现1**: 普通用户登录不验证密码 (P1#7)
- **发现2**: 前端缺少 `multi_message`/`stream_segments` 消息类型 (P2#12)
### 第8轮 — 并发安全与跨服务韧性
- **发现1**: 编排器/子会话goroutine缺少 `defer recover()` (P0#3)
- **发现2**: IoT Client不使用context (P1#8)
- **发现3**: 跨服务HTTP调用无重试/熔断 (P1#9)
### 第9轮 — 部署配置与Docker完整性
- **发现1**: 管理员权限中间件逻辑错误 (P0#4)
- **发现2**: 登录用户名无输入校验 (P1#10)
- **发现3**: Docker Compose缺少3个服务 (P2#13)
- **发现4**: voice-service无Dockerfile (P3#18)
- **发现5**: Dockerfile Go版本与go.mod不一致 (P3#19)
### 第10轮 — 编译产物与构建完整性
- **发现1**: 40MB编译产物被Git追踪 (P0#5)
- **发现2**: PWA缺少192x192图标 (P2#14)
---
*报告由Cyrene持续性调试流程自动生成 — 2026-05-19*
@@ -1,140 +0,0 @@
# 持续性调试第1轮: 修复后回归验证
**日期**: 2026-05-20
**时间**: UTC+8 13:35
**Commit**: 4b35736 (fix: 修复19个Bug (P0-P3) — 持续性调试第7轮发现的问题)
**上一轮**: 持续性调试第7轮 (19个Bug已全部修复)
---
## 环境确认
| 项目 | 状态 |
|------|------|
| SSH 隧道 | ✅ 活跃 (PID 23132, 端口转发: 5432, 6379, 4222, 8222, 6333, 6334, 9000, 9001) |
| PostgreSQL | ✅ 可连接 (`localhost:5432 - accepting connections`) |
| 数据库 `cyrene_ai` | ✅ 存在 (14张表) |
| Gateway (8080) | ✅ 新版编译启动 (PID 12619) |
| AI-Core (8081) | ✅ 运行中 (PID 15037, 旧版) |
---
## 编译验证
| 服务 | 结果 | 说明 |
|------|------|------|
| gateway | ✅ EXIT:0 | `go build -o ./cmd/gateway ./cmd/` |
| ai-core | ✅ EXIT:0 | `go build -o ./cmd/ai-core ./cmd/` |
| memory-service | ✅ EXIT:0 | `go build -o ./cmd/memory-service ./cmd/` |
| tool-engine | ✅ EXIT:0 | `go build -o ./cmd/tool-engine ./cmd/` |
| voice-service | ✅ EXIT:0 | `go build -o ./cmd/voice-service ./cmd/` |
| iot-debug-service | ✅ EXIT:0 | `go build -o ./cmd/iot-debug-service ./cmd/` |
| 前端 TypeScript | ✅ EXIT:0 | `npx tsc --noEmit` |
**结论**: 所有 6 个 Go 服务 + 前端编译通过,无回归编译错误。
---
## Bug 修复验证 (代码审查)
| Bug ID | 描述 | 文件 | 状态 |
|--------|------|------|------|
| P0#1 | session ID 使用 crypto/rand | [`session_handler.go`](../../backend/gateway/internal/handler/session_handler.go:4) — `import "crypto/rand"`, line 580 `rand.Read(b)` + fallback | ✅ **已修复** |
| P0#3 | goroutine defer recover (Hub.Run) | [`hub.go`](../../backend/gateway/internal/ws/hub.go:102-103) — `defer func() { if r := recover(); r != nil { ... } }()` | ✅ **已修复** |
| P0#4 | goroutine defer recover (广播循环) | [`hub.go`](../../backend/gateway/internal/ws/hub.go:462-463) — 同上方 | ✅ **已修复** |
| P0#5 | adminAuth 使用 is_admin 标记 | [`auth.go`](../../backend/gateway/internal/middleware/auth.go:45) — `c.Set(IsAdminKey, strings.HasPrefix(userID, "admin_"))` + [`router.go`](../../backend/gateway/internal/router/router.go:237) — `c.Get(middleware.IsAdminKey)` | ✅ **已修复** |
| P1#1 | 普通用户登录需密码验证 | [`auth_handler.go`](../../backend/gateway/internal/handler/auth_handler.go:105) — `verifyUserPassword()` 调用, 使用 bcrypt + `password_hash` 查询 | ✅ **逻辑正确** (见下方 REG1) |
| P1#3 | priority 范围扩展 0-10 | 提醒模块 (`reminder_handler.go`, `reminder_store.go`) 中未找到 priority 字段定义 | ⚠️ **需确认** |
**P1#3 详细说明**: 在 [`reminder_handler.go`](../../backend/gateway/internal/handler/reminder_handler.go) 和 [`reminder_store.go`](../../backend/gateway/internal/store/reminder_store.go) 中均未找到 `priority` 字段。priority 字段仅在 [`memory_handler.go`](../../backend/gateway/internal/handler/memory_handler.go:126) 中存在 (记忆模块)。提醒模块可能使用不同的优先级机制或未实现此修复。
---
## API 端到端测试
| 端点 | 方法 | 状态码 | 请求 | 响应摘要 | 结果 |
|------|------|--------|------|----------|------|
| `/api/v1/health` | GET | 200 | - | `{"service":"cyrene-gateway","status":"ok","ws_connections":0}` | ✅ |
| `/api/v1/auth/register` | POST | 400→201 | 缺 `nickname`/`verify_code` → 补充后成功 | `{"token":"eyJ...","user_id":"user_testuser_..."}` | ✅ |
| `/api/v1/auth/login` (管理员) | POST | 200 | `yeij0942` / `Jiang1143218570` | `{"token":"eyJ...","user_id":"admin_yeij0942"}` | ✅ |
| `/api/v1/auth/login` (普通用户) | POST | 500 | 任意非管理员用户名 | `{"error":"服务器内部错误"}` | ❌ (REG1) |
| `/api/v1/sessions` (无token) | GET | 401 | - | `{"error":"未提供认证令牌"}` | ✅ |
| `/api/v1/sessions` (管理员token) | GET | 200 | Bearer token | `{"sessions":[...]}` 返回历史会话 | ✅ |
| `/api/v1/sessions` (POST 创建) | POST | 200 | `{"title":"回归测试会话"}` | `{"id":"session_74fn67z6hp2v","title":"回归测试会话"}` | ✅ |
| `/api/v1/admin/sessions` (管理员) | GET | 200 | Bearer admin token | `{"sessions":[],"total":0}` | ✅ |
**注册成功字段**: username, password, email, nickname, verify_code (`"000000"` 开发阶段通行码)
**管理员凭据验证**: 从 `.env` 读取 — `ADMIN_USERNAME=yeij0942`, `ADMIN_PASSWORD=Jiang1143218570`,管理员的 `user_id` 格式为 `admin_yeij0942``admin_` 前缀触发 `is_admin=true`)。
---
## 新发现问题
### REG1: `users` 表缺失导致普通用户登录 500
**严重性**: P2
**现象**: 非管理员用户登录时返回 `{"error":"服务器内部错误"}` (HTTP 500)
**根因**: [`auth_handler.go`](../../backend/gateway/internal/handler/auth_handler.go:142) 中 `verifyUserPassword` 查询 `SELECT password_hash FROM users WHERE username = $1`,但数据库中**不存在 `users` 表**。当前数据库只有 14 张业务表 (sessions, messages, memories 等),没有用户表。
**影响**: 普通用户登录不可用(管理员登录不受影响,因为走独立分支)。
**修复方向**:
1. 创建 `users` 表 (`id`, `username`, `password_hash`, `email`, `nickname`, `created_at`)
2. 注册时写入 `users` 表 (当前注册直接返回 token,不写入数据库)
3. 或者:在 `users` 表不存在时,回退到内存/文件存储模式
### REG2: `backend/gateway/go.mod` 未提交修改
**严重性**: P3 (低)
**现象**: `git status` 显示 `M backend/gateway/go.mod`
**变更内容**: `golang.org/x/crypto v0.23.0``// indirect` 移至 `require` 直接依赖 (因为 `auth_handler.go` 直接 import `bcrypt`)
**影响**: 编译成功但 commit 不干净;其他开发者拉取后可能需手动 `go mod tidy`
**修复方向**: 提交 `go.mod` 变更或还原
### REG3: P1#3 priority 范围修复需确认
**严重性**: P3 (低)
**现象**: 提醒模块 (`reminder_handler.go`, `reminder_store.go`) 中未找到 priority 字段相关定义
**根因**: P1#3 的 priority 范围修复可能仅在记忆模块实现,未同步到提醒模块;或者提醒模块不需要此字段
**影响**: 若提醒功能需要 priority 过滤,当前不支持
**修复方向**: 确认需求后决定是否需要在提醒模块添加 priority 字段
---
## 数据库完整性
| 表名 | 用途 | 状态 |
|------|------|------|
| automation_rules | 自动化规则 | ✅ |
| automation_scenes | 自动化场景 | ✅ |
| daily_briefings | 每日简报 | ✅ |
| files | 文件管理 | ✅ |
| knowledge_bases | 知识库 | ✅ |
| knowledge_chunks | 知识块 | ✅ |
| knowledge_documents | 知识文档 | ✅ |
| memories | 记忆 | ✅ |
| memory_entries | 记忆条目 | ✅ |
| messages | 消息 | ✅ |
| reminders | 提醒 | ✅ |
| sessions | 会话 | ✅ |
| thinking_logs | 思考日志 | ✅ |
| tool_call_logs | 工具调用日志 | ✅ |
| **users** | **用户** | **❌ 缺失** |
---
## 总结
19 个 Bug 的核心修复代码已落地:
- **P0#1**: crypto/rand ✅
- **P0#3/#4**: goroutine defer recover ✅
- **P0#5**: adminAuth is_admin 标记 ✅
- **P1#1**: 登录密码验证逻辑正确,但因 `users` 表缺失无法正常工作
**新发现 3 个问题**:
- **REG1** (P2): `users` 表缺失 — 需要在下一轮修复中创建
- **REG2** (P3): `go.mod` 未提交变更
- **REG3** (P3): P1#3 priority 修复覆盖范围需确认
**编译与启动**: 全部通过,无回归。
**API 测试**: 管理员流程完全正常,注册流程正常 (需正确 payload),普通用户登录受 REG1 阻塞。
@@ -1,181 +0,0 @@
# 第10轮调试修复:P0/Critical 问题修复报告
**日期**: 2026-05-20 17:56 CST
**范围**: 修复前9轮发现的最关键的 P0/Critical 安全和稳定性问题
**编译状态**: ✅ Gateway 编译通过 (exit code 0)
---
## 修复摘要
| # | 问题 | 严重级别 | 文件 | 状态 |
|---|------|---------|------|------|
| 1 | WebSocket Hub 广播循环竞态条件 | P0 | [`backend/gateway/internal/ws/hub.go`](backend/gateway/internal/ws/hub.go) | ✅ 已修复 |
| 2 | Service Worker 重复注册 | P0 | [`frontend/web/src/main.tsx`](frontend/web/src/main.tsx) | ✅ 已修复 |
| 3 | 登录/注册端点缺少速率限制 | P0/SEC-001 | [`backend/gateway/internal/router/router.go`](backend/gateway/internal/router/router.go), [`backend/gateway/internal/middleware/ratelimit.go`](backend/gateway/internal/middleware/ratelimit.go) | ✅ 已修复 |
| 4 | 记忆/知识库/提醒内容未做 HTML 转义 | P0/SEC-003 | 3 个 handler 文件 | ✅ 已修复 |
| 5 | 生产环境 Caddyfile 缺失 | P0 | 新文件 [`Caddyfile`](Caddyfile) | ✅ 已创建 |
| 6 | 用户名输入验证缺失 | P0/SEC-002 | [`backend/gateway/internal/handler/auth_handler.go`](backend/gateway/internal/handler/auth_handler.go) | ✅ 已修复 |
---
## 修复详情
### 修复-1: WebSocket Hub 广播循环竞态条件 (P0)
**文件**: [`backend/gateway/internal/ws/hub.go:238-249`](backend/gateway/internal/ws/hub.go:238)
**问题**: `Hub.Run()` 中的广播循环在 `h.mu.RLock()` (读锁) 保护下执行了写入操作:
- `delete(h.clients, client)` — 修改 map
- `close(client.Send)` — 关闭 channel
这些操作在 RLock 下执行会导致数据竞态和潜在的 panic。
**修复方案**: 两阶段广播:
1. **Phase 1**: 在 `RLock` 下遍历 `h.clients`,尝试发送消息;发送失败的客户端收集到 `staleClients` 切片
2. **Phase 2**: 在 `Lock` (写锁) 下清理失效客户端,包括:
- 二次检查客户端是否仍在 `h.clients` 中(防止已被 unregister 移除)
-`h.clients``h.userClients` 中删除
- `close(client.Send)`
- 更新 session 状态为 `"idle"`
**关键安全措施**:
- 二次检查避免 double-close client.Send
- 完整清理 userClients 索引和 session 状态
- 与 unregister 路径保持一致的清理逻辑
---
### 修复-2: Service Worker 重复注册 (P0)
**文件**: [`frontend/web/src/main.tsx:6-15`](frontend/web/src/main.tsx:6)
**问题**: Service Worker 在两个地方注册:
1. `main.tsx:9` — 直接调用 `navigator.serviceWorker.register('/sw.js')`
2. [`usePWA.ts:29`](frontend/web/src/hooks/usePWA.ts:29) — `registerServiceWorker()` 函数
两次注册产生竞态条件,可能导致 SW 更新处理混乱。
**修复方案**: 从 `main.tsx` 中移除 SW 注册代码,保留 `usePWA.ts` 中的 `registerServiceWorker()` 函数。该函数有更完善的更新处理(`updatefound` 监听、定期检查更新、SKIP_WAITING 消息处理)。
---
### 修复-3: 登录/注册端点速率限制 (P0/SEC-001)
**文件**:
- [`backend/gateway/internal/middleware/ratelimit.go`](backend/gateway/internal/middleware/ratelimit.go) — 新增 `HandlerWithKey``AuthIPKey` 方法
- [`backend/gateway/internal/router/router.go:56-62`](backend/gateway/internal/router/router.go:56) — 应用限流中间件
**问题**: 登录 (`POST /api/v1/auth/login`) 和注册 (`POST /api/v1/auth/register`) 端点没有速率限制,攻击者可暴力破解密码。
**修复方案**:
1.`ratelimit.go` 新增:
- `HandlerWithKey(keyFn)` — 按自定义 key 限流的中间件
- `AuthIPKey(endpoint)` — 返回 `"auth_{endpoint}_{clientIP}"` 作为限流 key
2.`router.go` 中为认证路由创建专用限流器:
- 速率:0.083 tokens/s + 5 burst ≈ 每分钟每 IP 每端点 5 次
- 注册和登录各使用独立的 key`auth_register_IP``auth_login_IP`
---
### 修复-4: XSS 防护 — HTML 转义 (P0/SEC-003)
**文件**:
- [`backend/gateway/internal/handler/memory_handler.go`](backend/gateway/internal/handler/memory_handler.go)
- [`backend/gateway/internal/handler/knowledge_handler.go`](backend/gateway/internal/handler/knowledge_handler.go)
- [`backend/gateway/internal/handler/reminder_handler.go`](backend/gateway/internal/handler/reminder_handler.go)
**问题**: 用户输入的内容(title、content、description、category 等)在存储时未做 HTML 转义,存在存储型 XSS 风险。
**修复方案**: 使用 Go 标准库 [`html.EscapeString()`](https://pkg.go.dev/html#EscapeString) 对所有用户提供的文本字段进行转义:
| Handler | 转义字段 |
|---------|---------|
| memory_handler.go `Add()` | `req.Content`, `req.Category` |
| knowledge_handler.go `CreateKB()` | `req.Name`, `req.Description` |
| knowledge_handler.go `UpdateKB()` | `req.Name`, `req.Description` |
| knowledge_handler.go `AddDocument()` | `req.Title`, `req.SourceType` |
| reminder_handler.go `Create()` | `req.Title`, `req.Description` |
**注意**: `html.EscapeString()``<`, `>`, `&`, `"`, `'` 转换为对应的 HTML 实体,防止 XSS 注入。转义在存储前执行,确保数据库中的数据是安全的。
---
### 修复-5: 创建生产环境 Caddyfile (P0)
**文件**: [`Caddyfile`](Caddyfile) (新建)
**问题**: [`docker-compose.yml:12`](docker-compose.yml:12) 引用了 `./Caddyfile` 但文件不存在,导致生产环境集群完全不可访问。
**修复方案**: 创建完整的 Caddyfile,包含:
- **反向代理规则**:
- `/ws/*``gateway:8080` (WebSocket 支持)
- `/api/*``gateway:8080` (HTTP API)
- 默认 → 前端占位响应
- **安全头**:
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `X-XSS-Protection: 1; mode=block`
- `Referrer-Policy: strict-origin-when-cross-origin`
- **日志**: JSON 格式输出到 stdout
- **转发头**: `X-Forwarded-For`, `X-Forwarded-Proto`
---
### 修复-6: 用户名输入验证 (P0/SEC-002)
**文件**: [`backend/gateway/internal/handler/auth_handler.go`](backend/gateway/internal/handler/auth_handler.go)
**问题**: 注册时用户名接受 `user_test'; DROP TABLE users; --` 等 SQL 注入/XSS payload。
**修复方案**:
1. 添加包级别正则表达式 `usernameRegex = regexp.MustCompile(^[a-zA-Z0-9_]{3,32}$)`
2.`Register()` handler 中,JSON 绑定之后立即校验用户名格式
3.`Login()` handler 中也添加相同的用户名格式校验
4. 不符合格式返回 400: `"用户名格式无效:仅允许字母、数字和下划线,长度 3-32 位"`
虽然项目使用 `bcrypt` 和参数化查询(通过 `database/sql`),用户名格式限制仍是最佳实践,防止:
- SQL 注入 payload 作为用户名存储
- XSS payload 在其他接口展示时被执行
- 特殊字符导致的序列化/反序列化问题
---
## 编译验证
```
cd backend/gateway && go build ./...
```
**结果**: Exit code 0,无警告或错误。
---
## 影响范围
| 组件 | 影响 |
|------|------|
| Gateway | 6 个文件修改 + 编译验证 |
| Frontend | 1 个文件修改 (main.tsx) |
| 基础设施 | 1 个新文件 (Caddyfile) |
| 其他服务 | 无需修改 |
---
## 已知限制
1. **限流器**仍基于内存实现,重启后计数器丢失。生产环境建议迁移到 Redis。
2. **Caddy TLS** 配置为可选 (注释状态),需要有效的域名和端口 443 暴露。
3. **前端 SW 注册** `registerServiceWorker()` 需要在 `App.tsx` 初始化时显式调用(当前仅在 `usePWA.ts` 导出函数,未自动调用)。
---
## 相关 Issue
- SEC-001: 认证端点暴力破解防护
- SEC-002: 用户名格式校验
- SEC-003: 存储型 XSS 防护
- P0: WebSocket Hub 并发安全
- P0: Service Worker 双重注册
- P0: 生产环境反向代理配置缺失
@@ -1,239 +0,0 @@
# 持续性调试第2轮: 用户认证流程端到端 + 服务集成测试
> 日期: 2026-05-20 13:58 CST (UTC+8)
> 基于第1轮 REG1 修复 (commit `9dd1582`): users 表缺失已修复
> 测试环境: Gateway 编译后后台运行, PostgreSQL 通过 SSH 隧道连接
---
## 1. 环境准备
| 检查项 | 状态 | 详情 |
|--------|------|------|
| SSH 隧道 (PostgreSQL 5432) | ✅ | `ssh` 进程监听 127.0.0.1:5432 |
| 旧 gateway 进程 | ✅ 已停止 | PID 12619 已被 kill |
| 编译新 gateway | ✅ 成功 | `go build -o ./cmd/gateway ./cmd/main.go` |
| Gateway 后台启动 | ✅ | PID 19265, 端口 8080 |
---
## 2. users 表 + 种子管理员验证
### 2.1 启动日志 (关键行)
```
2026/05/20 13:55:44 ✅ Users 表已就绪
2026/05/20 13:55:45 🔧 未找到管理员用户,创建默认 admin (username: admin, password: admin123)...
2026/05/20 13:55:45 ✅ 默认管理员用户已创建 (username: admin, password: admin123)
```
### 2.2 PostgreSQL 查询确认
```sql
SELECT id, username, is_admin, created_at FROM users ORDER BY id;
```
| id | username | is_admin | created_at |
|----|-----------|----------|----------------------------|
| 1 | yeij0942 | t | 2026-05-20 05:55:45+00 |
| 2 | testuser1 | f | 2026-05-20 05:57:13+00 |
### 2.3 发现的问题
**🟡 ISSUE-1: 种子日志文案与实际用户名不一致**
- [`main.go:66`](backend/gateway/cmd/main.go:66) 日志写 "username: admin, password: admin123"
- 实际 `cfg.AdminUsername` 来自 `.env` = `yeij0942`
- `main.go:68` 种子密码硬编码 `admin123`,与 `.env``ADMIN_PASSWORD=Jiang1143218570` 不一致
- **影响**: 日志误导,但功能正常。种子用户名为 `yeij0942`,密码为 `admin123`
- **根因**: 日志文案使用字面量 "admin" 而代码使用 `cfg.AdminUsername`
**🟡 ISSUE-2: 种子密码硬编码**
- [`main.go:68`](backend/gateway/cmd/main.go:68) `defaultAdminPassword := "admin123"` 硬编码
- `.env``ADMIN_PASSWORD=Jiang1143218570` 未被使用
- **影响**: 低。种子只在首次创建时使用,后续登录走 bcrypt 验证
---
## 3. 用户认证端到端测试
### 3.1 注册新用户
```bash
POST /api/v1/auth/register
{"username":"testuser1","password":"testpass123","email":"test@example.com","nickname":"测试用户","verify_code":"000000"}
```
**结果**: ✅ `201 Created`
```json
{
"user_id": "user_testuser1",
"token": "eyJ...",
"expires": 1781848633,
"nickname": "测试用户"
}
```
验证点:
-`REGISTRATION_ENABLED=true` 生效
- ✅ bcrypt 密码哈希写入 users 表
- ✅ JWT token 成功生成 (user_id 前缀为 `user_`)
- ✅ 验证码 `000000` MVP 阶段通过
### 3.2 新用户登录 (bcrypt 密码验证)
```bash
POST /api/v1/auth/login
{"username":"testuser1","password":"testpass123"}
```
**结果**: ✅ `200 OK`
验证点:
-`verifyUserPassword` 从 DB 查询用户 → bcrypt `CompareHashAndPassword` 成功
- ✅ userID 前缀为 `user_` (非 admin)
### 3.3 管理员登录
```bash
POST /api/v1/auth/login
{"username":"yeij0942","password":"admin123"}
```
**结果**: ✅ `200 OK`
```json
{
"user_id": "admin_yeij0942",
"token": "eyJ..."
}
```
验证点:
- ✅ 种子 admin 通过 bcrypt 验证 (DB 中存在且密码匹配)
- ✅ userID 前缀为 `admin_`
### 3.4 Token 访问受保护 API
**新用户 token**:
```bash
GET /api/v1/sessions
Authorization: Bearer <user_testuser1_token>
```
`200 OK`, `{"sessions": []}` (空列表,符合预期)
**管理员 token**:
```bash
GET /api/v1/sessions
Authorization: Bearer <admin_yeij0942_token>
```
`200 OK`, 返回 2 条已有会话
**未认证访问**:
```bash
GET /api/v1/sessions (无 Authorization header)
```
`401 Unauthorized`, `{"error": "未提供认证令牌"}` — JWTAuth 中间件正常
**错误密码**:
```bash
POST /api/v1/auth/login
{"username":"testuser1","password":"wrongpass"}
```
`401 Unauthorized`, `{"error": "用户名或密码错误"}` — bcrypt 比对拒绝
---
## 4. 多服务集成测试
### 4.1 Memory Service (8091)
```bash
GET /api/v1/memory (via Gateway, with auth)
```
**结果**: ✅ 正常转发,返回 1 条已有记忆
```json
{
"memories": [{
"id": "8f8af3b7-...",
"content": "测试记忆内容",
"user_id": "admin_yeij0942",
...
}],
"total": 1
}
```
### 4.2 Tool-Engine (8092)
**结果**: ⚠️ 进程在运行 (端口 8092 监听),但无 `/health` 端点 (返回 404),这是预期行为 — tool-engine 未定义该路由。
### 4.3 当前运行的服务
| 服务 | 端口 | 状态 |
|------|------|------|
| Gateway | 8080 | ✅ 运行中 |
| Memory Service | 8091 | ✅ 运行中 |
| Tool Engine | 8092 | ✅ 运行中 |
| PostgreSQL | 5432 | ✅ SSH 隧道 |
---
## 5. 前端 TypeScript 编译
```bash
cd frontend/web && npx tsc --noEmit
```
**结果**: ✅ 无错误,编译通过。
---
## 6. 系统时间
- **UTC**: 2026-05-20 05:58:47 UTC
- **本地 (CST)**: 2026-05-20 13:58:47 CST
---
## 7. 验证点矩阵
| # | 验证点 | 结果 |
|---|--------|------|
| 1 | users 表自动创建 | ✅ |
| 2 | 种子管理员创建 | ✅ (用户名 yeij0942, 密码 admin123) |
| 3 | 普通用户注册 | ✅ |
| 4 | 新用户登录 (bcrypt) | ✅ |
| 5 | 管理员登录 | ✅ |
| 6 | JWT token 访问受保护 API | ✅ |
| 7 | 未认证拒绝 (401) | ✅ |
| 8 | 错误密码拒绝 (401) | ✅ |
| 9 | Memory 服务转发 | ✅ |
| 10 | 前端 TypeScript 编译 | ✅ |
---
## 8. 发现的问题
| ID | 严重程度 | 描述 | 位置 |
|----|----------|------|------|
| ISSUE-1 | 🟡 低 | 种子日志文案写死 "admin" 但实际用户名为 `cfg.AdminUsername` | [`main.go:66`](backend/gateway/cmd/main.go:66) |
| ISSUE-2 | 🟡 低 | 种子密码硬编码 `admin123`,未使用 `.env``ADMIN_PASSWORD` | [`main.go:68`](backend/gateway/cmd/main.go:68) |
---
## 9. 结论
**认证流程端到端完全可用**。第1轮发现的 REG1 (users 表缺失) 已修复,所有关键路径验证通过:
1. users 表在 gateway 启动时自动创建
2. 种子管理员通过 bcrypt 哈希存入 DB
3. 普通用户可以注册 (bcrypt 哈希存储)
4. 所有用户 (admin + 普通) 可以通过 bcrypt 密码验证登录
5. JWT 中间件正确保护受保护路由
6. Gateway → Memory Service 转发正常
7. 前端编译无错误
@@ -1,314 +0,0 @@
# 持续性调试第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 路径对照
@@ -1,231 +0,0 @@
# 持续性调试第4轮: 子服务调试 + 数据库完整性
> **日期**: 2026-05-20 (UTC+8 14:28 CST)
> **状态**: ✅ 全部通过
> **Go 版本**: 1.26.2
---
## 1. 编译与启动
| 服务 | 端口 | 状态 | 编译 | 进程 PID |
|------|------|------|------|----------|
| Gateway | 8080 | ✅ 运行中 | 已有 | 19265 |
| AI-Core | 8081 | ✅ 运行中 | 已有 | 15037 |
| IoT Debug Service | 8083 | ✅ 运行中 | 已有 | 3063 |
| Memory Service | 8091 | ✅ 运行中 | 已有 | 2434 |
| Tool Engine | 8092 | ✅ 运行中 | 本轮新建 | 29391 |
| Voice Service | 8093 | ✅ 运行中 | 已有 | 7641 |
**Tool Engine** 为本轮唯一需要编译和启动的服务。编译命令:
```bash
cd backend && go build -o tool-engine/cmd/tool-engine ./tool-engine/cmd/
```
启动时传入环境变量: `PORT=8092`, `IOT_SERVICE_URL=http://localhost:8083`, `DB_URL=postgres://...`, `DATA_DIR=/tmp/cyrene_data`
---
## 2. 健康检查
### 2.1 Memory Service (8091)
```json
{"status": "ok", "service": "memory-service"}
```
数据库连接正常 (`IsReady() == true`)pgvector extension 已加载。
### 2.2 Tool Engine (8092)
```json
{"status": "ok", "service": "tool-engine"}
```
已注册 **13 个工具**: calculator, datetime, text, crypto, random, markdown, json_ops, file_ops, http_request, web_search, web_fetch, iot_query, iot_control。
IoT 客户端已连接到 `http://localhost:8083`
### 2.3 IoT Debug Service (8083)
```json
{"status": "ok", "service": "iot-debug-service"}
```
模拟 **8 个设备**: 客厅灯, 卧室灯, 客厅空调, 卧室空调, 客厅窗帘, 温度传感器, 湿度传感器, 智能门锁。
传感器波动模拟每 30 秒运行一次。
### 2.4 Voice Service (8093)
```json
{
"status": "ok",
"service": "voice-service",
"stt": {"available": true, "model_loaded": true, "model_name": "ggml-small.bin"},
"tts": {"available": false, "engine": "fallback (silent WAV)", "edge_tts": false}
}
```
- **STT (Whisper)**: ✅ 可用,模型已加载 (`ggml-small.bin`),支持 zh/en/ja/ko/auto
- **TTS (Edge-TTS)**: ⚠️ 降级 — `edge-tts` Python 包未安装,当前使用 fallback silent WAV。安装命令: `pip install edge-tts`
### 2.5 Gateway (8080)
```json
{"status": "ok", "service": "cyrene-gateway", "ws_connections": 0}
```
---
## 3. Gateway 转发测试
### 3.1 Memory CRUD (通过 Gateway)
| 操作 | 端点 | 方法 | 结果 |
|------|------|------|------|
| 列出记忆 | `/api/v1/memory` | GET | ✅ 返回已有记忆 |
| 创建记忆 | `/api/v1/memory` | POST | ✅ `{"status":"saved"}` |
| 搜索记忆 | `/api/v1/memory/search?q=测试` | GET | ✅ 匹配成功 |
认证令牌: `admin_yeij0942` (JWT 验证通过)
### 3.2 注意事项
Memory Service 端点实际路径为 `/api/v1/memories` (复数)Gateway 通过 [`memory_handler.go`](backend/gateway/internal/handler/memory_handler.go:29) 代理到正确路径。搜索功能将 Gateway 的 `GET /api/v1/memory/search?q=xxx` 转发为 `POST /api/v1/memories/query`
---
## 4. 工具引擎功能验证
### 4.1 计算器
```bash
POST /api/v1/tools/calculator/execute
{"arguments": {"expression": "2+3*4"}}
{"output": "表达式: 2+3*4\n结果: 14"}
```
✅ 正确计算 (优先乘除)
### 4.2 日期时间
```bash
POST /api/v1/tools/datetime/execute
{"arguments": {"action": "now"}}
{"output": "当前时间: 2026-05-20 14:27:13\n时区: Local\nUnix时间戳: 1779258433"}
```
✅ 时间戳正确
### 4.3 加密哈希
```bash
POST /api/v1/tools/crypto/execute
{"arguments": {"action": "hash", "algorithm": "sha256", "input": "hello"}}
{"output": "哈希算法: sha256\n输入长度: 5 字节\n哈希值 (hex): 2cf24dba..."}
```
✅ SHA256 哈希正确 (与 `echo -n hello | sha256sum` 一致)
### 4.4 调用日志存储
- 数据库表 `tool_call_logs` 已创建并可使用
- 调用日志异步写入(go routine 方式)
---
## 5. IoT 端点测试
| 操作 | 端点 | 方法 | 结果 |
|------|------|------|------|
| 列出设备 | `/api/v1/devices` | GET | ✅ 返回 8 个设备 |
| 获取设备 | `/api/v1/devices/light-livingroom` | GET | ✅ 含状态历史 |
| 切换灯光 | `/api/v1/devices/light-livingroom/toggle` | POST | ✅ on→off/off→on |
| 设置温度 | `/api/v1/devices/ac-livingroom/set` | POST | ✅ 温度设为 28°C |
设备状态历史正确记录,状态变更带时间戳。
---
## 6. 数据库完整性
### 6.1 表清单 (15/15)
| # | 表名 | 用途 | 服务 |
|---|------|------|------|
| 1 | `users` | 用户账户认证 | Gateway |
| 2 | `sessions` | 会话管理 | Gateway |
| 3 | `messages` | 消息存储 | Gateway |
| 4 | `memories` | 旧版记忆表 | Gateway |
| 5 | `memory_entries` | 新版记忆表 (pgvector) | Memory Service |
| 6 | `thinking_logs` | 自主思考日志 | Memory Service |
| 7 | `tool_call_logs` | 工具调用日志 | Tool Engine |
| 8 | `reminders` | 提醒 | Gateway |
| 9 | `daily_briefings` | 每日简报 | Gateway |
| 10 | `automation_rules` | 自动化规则 | Gateway |
| 11 | `automation_scenes` | 自动化场景 | Gateway |
| 12 | `files` | 文件管理 | Gateway |
| 13 | `knowledge_bases` | 知识库 | Gateway |
| 14 | `knowledge_documents` | 知识文档 | Gateway |
| 15 | `knowledge_chunks` | 知识分块 (含全文搜索) | Gateway |
### 6.2 新增 `users` 表结构
| 列 | 类型 | 约束 |
|----|------|------|
| `id` | INTEGER | PK, NOT NULL |
| `username` | VARCHAR(255) | NOT NULL, UNIQUE |
| `password_hash` | VARCHAR(255) | NOT NULL |
| `is_admin` | BOOLEAN | NULLABLE |
| `created_at` | TIMESTAMPTZ | NULLABLE |
| `updated_at` | TIMESTAMPTZ | NULLABLE |
索引: `users_pkey` (btree id), `users_username_key` (unique btree username), `idx_users_username` (btree username)
### 6.3 外键关系 (3)
| 子表 | 列 | 父表 | 引用列 | 约束名 |
|------|----|------|---------|--------|
| `messages` | `session_id` | `sessions` | `id` | `messages_session_id_fkey` |
| `knowledge_documents` | `kb_id` | `knowledge_bases` | `id` | `knowledge_documents_kb_id_fkey` |
| `knowledge_chunks` | `doc_id` | `knowledge_documents` | `id` | `knowledge_chunks_doc_id_fkey` |
### 6.4 索引统计
- 总计: **58 个索引**
- 所有表均有主键索引 (btree)
- 性能关键索引覆盖: `user_id`, `session_id`, `created_at`, `category`, `priority`, `importance`, `status`
- 全文搜索: `knowledge_chunks` 有 GIN 索引 (`idx_kc_tsv_gin`)
- 唯一约束: `users(username)`, `daily_briefings(user_id, date)`
- JSONB: `tool_call_logs` 使用 JSONB 存储参数
### 6.5 注意事项
- `memories``memory_entries` 是两套独立的记忆表。`memory_entries` 是 Memory Service 通过 pgvector 管理的版本,包含向量嵌入和更丰富的元数据(importance, keywords, source, access_count 等)。`memories` 是 Gateway 的旧版表。
- `users` 表使用 INTEGER 自增 ID,而非 UUID。Gateway 认证生成 `admin_{username}` 格式的 user_id,但不与数据库 `id` 直接关联。
---
## 7. 系统时间
```
UTC: Wed May 20 06:28:13 UTC 2026
北京时间: Wed May 20 14:28:13 CST 2026
```
时间正常,与预期一致。
---
## 8. 总结
### 通过 ✅
- 所有 4 个子服务编译成功并运行
- 所有健康检查返回 `"ok"`
- Gateway 正确转发 Memory CRUD 请求
- Tool Engine 13 个工具全部注册,计算器/日期/加密工具验证通过
- IoT 设备模拟器 8 个设备正常工作,Toggle/Set 属性测试通过
- 数据库 15 张表完整,58 个索引,3 个外键关系正确
- `users` 表结构完整,含唯一用户名约束和管理员标记
### 待改进 ⚠️
- **Voice Service TTS 降级**: `edge-tts` Python 包未安装,当前使用 silent WAV fallback。建议安装: `pip install edge-tts`
- **双记忆表**: `memories``memory_entries` 共存,可能造成混淆,建议迁移合并
- **users 表 ID 类型**: 使用 INTEGER 自增和 VARCHAR user_id 的混合策略,未来可统一为 UUID
---
## 9. 端口占用全景
| 端口 | 服务 | PID | 二进制 |
|------|------|-----|--------|
| 8080 | Gateway | 19265 | `/home/aska/Code/Cyrene/backend/gateway/cmd/gateway` |
| 8081 | AI-Core | 15037 | `./main` |
| 8083 | IoT Debug | 3063 | `/home/aska/Code/Cyrene/backend/iot-debug-service/main` |
| 8091 | Memory | 2434 | `/home/aska/Code/Cyrene/backend/memory-service/main` |
| 8092 | Tool Engine | 29391 | `/home/aska/Code/Cyrene/backend/tool-engine/cmd/tool-engine` |
| 8093 | Voice | 7641 | `/home/aska/Code/Cyrene/backend/voice-service/main` |
@@ -1,348 +0,0 @@
# Cyrene 第5轮调试报告:安全审计 + 边界条件 + 错误处理
> **日期**2026-05-20 14:39 CST (UTC+8)
> **轮次**:第5轮
> **测试方法**:黑盒渗透测试 (curl + 手动审计)
> **测试范围**Gateway (8080) 全端点
---
## 一、测试概况
| 类别 | 测试项数 | 通过 | 发现问题 |
|------|---------|------|---------|
| 认证绕过 | 9 | 8 | 1 |
| SQL注入 | 6 | 4 | 2 |
| XSS | 5 | 0 | 5 |
| 路径遍历 | 3 | 3 | 0 |
| 权限提升 | 1 | 1 | 0 |
| 速率限制 | 2 | 0 | 2 |
| 边界条件 | 10 | 5 | 5 |
| 错误处理 | 8 | 3 | 5 |
| **合计** | **44** | **24** | **20** |
---
## 二、严重发现 (🔴 Critical)
### 🔴 SEC-001: 注册/登录端点无速率限制
**位置**: [`backend/gateway/internal/router/router.go`](backend/gateway/internal/router/router.go:56-60)
**描述**: 登录 (`/auth/login`) 和注册 (`/auth/register`) 端点属于公开路由组,未应用限流中间件。`RateLimiter` 仅附加在 `protected` 路由组上。
**测试方法**:
```bash
for i in $(seq 1 20); do
curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"wrong"}'
done
```
**实际结果**: 20次请求全部返回 `401`,无任何 `429 Too Many Requests`
**预期结果**: 应在短时间内(如1秒内超过5次)返回 `429` 限流响应。
**影响**: 攻击者可对登录端点进行暴力破解,无限尝试密码组合。理论上 JWT 过期时间为 720 小时(30天),但暴力破解风险依然存在。
**严重级别**: 🔴 Critical
---
### 🔴 SEC-002: 用户名未做危险字符过滤 (Stored XSS + Log注入)
**位置**: [`backend/gateway/internal/handler/auth_handler.go`](backend/gateway/internal/handler/auth_handler.go:38-45)
**描述**: 注册端点接受包含 SQL 注入 payload、XSS payload 的用户名。虽然数据库查询使用了参数化查询(`$1`)防止了实际注入执行,但恶意字符串被原样存储到数据库,并嵌入到 JWT token 的 `user_id` 字段中。
**测试方法**:
```bash
curl -s -X POST http://localhost:8080/api/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"username":"test'\''; DROP TABLE users; --","password":"Test@123456",
"email":"sqli@test.com","nickname":"SQLiTest","verify_code":"000000"}'
```
**实际结果**: 用户成功注册,`user_id``user_test'; DROP TABLE users; --`JWT token 中包含此 payload。用户可以使用此用户名正常登录。
**影响**:
1. 日志注入:用户名出现在日志中可能破坏日志格式
2. 存储型 XSS:如果前端任何地方直接渲染 `user_id` 而未转义
3. 数据完整性:数据库中存储了恶意构造的用户名
4. Token 膨胀:JWT 中包含特殊字符
**严重级别**: 🔴 Critical
---
### 🔴 SEC-003: 存储型 XSS — 所有内容端点未对输入做 HTML 转义
**位置**:
- [`backend/gateway/internal/handler/memory_handler.go`](backend/gateway/internal/handler/memory_handler.go)
- [`backend/gateway/internal/handler/knowledge_handler.go`](backend/gateway/internal/handler/knowledge_handler.go)
- [`backend/gateway/internal/handler/reminder_handler.go`](backend/gateway/internal/handler/reminder_handler.go)
**描述**: 记忆、知识库、提醒的标题/内容/便签字段接受并存储 HTML/JavaScript 代码。虽然 JSON 响应中使用了 Unicode 转义(如 `\u003cscript\u003e`),但这依赖 JSON 序列化器的默认行为,后端没有主动做输入验证或输出净化。
**测试方法**:
```bash
# 记忆
curl -X POST /api/v1/memory -H "Authorization: Bearer $TOKEN" \
-d '{"content":"<img src=x onerror=alert(1)>","category":"xss_test"}'
# 知识库
curl -X POST /api/v1/knowledge/bases -H "Authorization: Bearer $TOKEN" \
-d '{"name":"<script>alert(1)</script>","description":"XSS test"}'
# 提醒
curl -X POST /api/v1/reminders -H "Authorization: Bearer $TOKEN" \
-d '{"title":"<b>Test</b>","note":"<script>alert(document.cookie)</script>",...}'
```
**实际结果**: 所有 XSS payload 被原样存储到数据库。JSON 响应中由 Go 的 `encoding/json` 自动转义为 Unicode 形式。
**影响**: 如果前端渲染这些内容时使用 `dangerouslySetInnerHTML` 或未做 HTML 转义,将导致存储型 XSS 攻击。即使后端 JSON 有转义,前端可能自行解析。
**严重级别**: 🔴 Critical (取决于前端实现)
---
### 🔴 SEC-004: 文件名 XSS 过滤过于粗暴且不透明
**位置**: [`backend/gateway/internal/handler/file_handler.go`](backend/gateway/internal/handler/file_handler.go)
**描述**: 文件上传端点对文件名中的 `<script>` 标签做了过滤,但过滤方式是静默删除标签字符,导致用户得到的文件名与预期完全不同。
**测试方法**:
```bash
echo "XSS test" > /tmp/xss_test.txt
curl -X POST /api/v1/files/upload -H "Authorization: Bearer $TOKEN" \
-F "file=@/tmp/xss_test.txt;filename=<script>alert(1)</script>.txt"
```
**实际结果**: 文件名被变成 `script_.txt``<`, `/`, `>` 字符被移除)。原始文件名信息丢失。没有向用户说明文件名被修改的原因。
**影响**:
1. 用户提交 `filename=<script>.txt` → 得到 `script_.txt`,造成困惑
2. 没有告警或说明文件名被清理过
3. 过滤逻辑不透明
**严重级别**: 🔴 High (功能性 + 安全)
---
### 🔴 SEC-005: 超大内容无大小限制
**位置**:
- [`backend/gateway/internal/handler/memory_handler.go`](backend/gateway/internal/handler/memory_handler.go)
- [`backend/gateway/internal/handler/knowledge_handler.go`](backend/gateway/internal/handler/knowledge_handler.go)
**描述**: 记忆和知识库的 content 字段没有大小限制。测试中成功存储了 100,000 字符的内容。
**测试方法**:
```bash
curl -X POST /api/v1/memory -H "Authorization: Bearer $TOKEN" \
-d "{\"content\":\"$(python3 -c "print('X'*100000)")\",\"category\":\"big\"}"
```
**实际结果**: HTTP 200,100KB 内容成功存储。查询时返回完整内容(响应体超过 99KB)。
**影响**:
1. 数据库存储膨胀
2. API 响应过大可能导致前端卡顿或崩溃
3. 可被用于 DoS 攻击
4. 带宽浪费
**严重级别**: 🔴 Medium-High
---
## 三、中等发现 (🟡 Medium)
### 🟡 SEC-006: JWT Secret 使用弱默认值
**位置**: [`backend/gateway/internal/config/config.go`](backend/gateway/internal/config/config.go:92)
**描述**: JWT 签名密钥默认为 `change-me-in-production`,此值在源代码中明文可见。虽然生产环境应通过环境变量覆盖,但开发者可能忘记设置。
**当前值**: `getEnv("JWT_SECRET", "change-me-in-production")`
**影响**: 如果生产部署未覆盖此值,攻击者可伪造任意用户的 JWT token。
**严重级别**: 🟡 Medium
---
### 🟡 SEC-007: 管理员权限判断依赖用户名前缀
**位置**: [`backend/gateway/internal/middleware/auth.go`](backend/gateway/internal/middleware/auth.go:45)
**描述**: 管理员权限通过检查 `user_id` 是否以 `admin_` 前缀开头来判断,而非使用数据库中的 `is_admin` 字段。
```go
c.Set(IsAdminKey, strings.HasPrefix(userID, "admin_"))
```
**影响**: 如果攻击者能注册一个用户名为 `admin_fake` 的账户(如果注册开关开启),其 `user_id` 将变成 `user_admin_fake`(非管理员)。但如果数据库中有用户 `admin_xxx` 且能通过其他方式认证... 不过由于 `user_id = "user_" + username` 的拼接方式,普通用户无法获得 `admin_` 前缀。
**严重级别**: 🟡 Low-Medium (设计缺陷,但实际风险取决于注册是否开放)
---
### 🟡 SEC-008: 负数值分页参数未被拒绝
**位置**: 搜索端点(sessions/messages/search, memory/search
**描述**: `limit=-1``offset=-100` 被静默接受(HTTP 200),虽然实际返回结果正确,但应返回 400 错误。
**测试方法**:
```bash
curl "$BASE/messages/search?q=test&limit=-1&offset=-100" -H "Authorization: Bearer $TOKEN"
# HTTP 200
```
**影响**: 低风险,但不符合健壮 API 设计原则。
**严重级别**: 🟡 Low-Medium
---
### 🟡 SEC-009: 空字节导致 500 内部错误
**位置**: [`backend/gateway/internal/handler/auth_handler.go`](backend/gateway/internal/handler/auth_handler.go:116)
**描述**: 发送包含 `\u0000` 的 JSON 请求体导致服务器返回 `{"error":"服务器内部错误"}` (500),而非 400 参数无效。
**测试方法**:
```bash
curl -X POST /api/v1/auth/login -H 'Content-Type: application/json' \
-d '{"username":"test\u0000user","password":"pass\u0000word"}'
# HTTP 500: {"error":"服务器内部错误"}
```
**影响**: 攻击者可通过发送包含空字节的请求探测服务器内部行为,也可能导致日志膨胀。
**严重级别**: 🟡 Medium
---
### 🟡 SEC-010: 错误 HTTP 方法返回 404 而非 405
**位置**: [`backend/gateway/internal/router/router.go`](backend/gateway/internal/router/router.go)
**描述**: 对端点使用不支持的 HTTP 方法时返回 `404 Not Found` 而非 `405 Method Not Allowed`
| 端点 | 正确方法 | 测试方法 | 实际 | 预期 |
|------|---------|---------|------|------|
| `/health` | GET | PATCH | 404 | 405 |
| `/health` | GET | POST | 404 | 405 |
| `/auth/login` | POST | PUT | 404 | 405 |
**影响**: 误导客户端,不利于 API 可发现性。
**严重级别**: 🟡 Low
---
## 四、低风险发现 (🟢 Low)
### 🟢 SEC-011: 错误消息泄露验证细节
**描述**: 注册失败时的错误消息包含完整的字段验证信息:
```
{"error":"请求参数无效: Key: 'Password' Error:Field validation for 'Password' failed on the 'required' tag\n..."}
```
**影响**: 暴露了后端使用的验证框架(gin binding)和字段名称。
**严重级别**: 🟢 Low
### 🟢 SEC-012: 速率限制仅应用于受保护端点
**描述**: `RateLimiter` 配置为 10 req/s, burst 20,但仅通过 `protected.Use(rateLimiter.Handler())` 应用于需要认证的路由。公开端点(health, login, register)无限制。
**测试**: 15次快速连续请求到受保护端点均返回 200,说明 burst=20 在当前测试中未触发限流。需要更高频率的测试。
**严重级别**: 🟢 Low (限流存在但有盲区)
### 🟢 SEC-013: 超大搜索查询被接受
**描述**: 5000个字符的搜索查询被接受并正常处理(HTTP 200)。
**严重级别**: 🟢 Low
---
## 五、通过的安全检查 ✅
| 测试项 | 结果 | 说明 |
|--------|------|------|
| 无 Token 访问 | ✅ 401 | 所有受保护端点正确拒绝 |
| 空 Authorization 头 | ✅ 401 | 正确处理 |
| 非 Bearer 格式 | ✅ 401 | 格式验证正确 |
| 伪造 Token | ✅ 401 | JWT 签名验证有效 |
| 篡改 Token | ✅ 401 | 签名不匹配被拒绝 |
| 过期 Token | ✅ 401 | 过期检测正常 |
| 管理员权限隔离 | ✅ 403 | 普通用户访问 /admin 被拒绝 |
| 路径遍历 (../) | ✅ 404 | 文件/知识库端点正确阻止 |
| 路径遍历 (URL编码) | ✅ 404 | `%2F` 编码的遍历也被阻止 |
| 并发请求 | ✅ 200x10 | 10个并发请求全部成功,无竞态 |
| 不存在的资源 | ✅ 404 | 正确处理 |
| 健康检查公开 | ✅ 200 | 无需认证即可访问 |
| SQL参数化查询 | ✅ | 所有 DB 查询使用 `$1` 占位符 |
| 文件类型白名单 | ✅ | 仅允许安全类型 |
---
## 六、发现汇总
| ID | 类别 | 标题 | 严重级别 |
|----|------|------|---------|
| SEC-001 | 速率限制 | 登录/注册端点无限流保护 | 🔴 Critical |
| SEC-002 | 输入验证 | 用户名缺少危险字符过滤 | 🔴 Critical |
| SEC-003 | XSS | 内容端点未做HTML转义 | 🔴 Critical |
| SEC-004 | XSS | 文件名过滤不透明 | 🔴 High |
| SEC-005 | 边界条件 | 内容大小无上限 | 🔴 Medium-High |
| SEC-006 | 配置安全 | JWT Secret 弱默认值 | 🟡 Medium |
| SEC-007 | 权限模型 | 管理员判断依赖前缀 | 🟡 Low-Medium |
| SEC-008 | 边界条件 | 负数分页参数被接受 | 🟡 Low-Medium |
| SEC-009 | 错误处理 | 空字节导致500错误 | 🟡 Medium |
| SEC-010 | 错误处理 | 错误方法返回404而非405 | 🟡 Low |
| SEC-011 | 信息泄露 | 错误消息暴露验证细节 | 🟢 Low |
| SEC-012 | 速率限制 | 公开端点无速率限制 | 🟢 Low |
| SEC-013 | 边界条件 | 超大搜索查询被接受 | 🟢 Low |
---
## 七、建议优先级修复顺序
1. **SEC-001** — 为公开端点添加速率限制(最快修复,最高影响)
2. **SEC-002** — 用户名正则校验 + 字符白名单
3. **SEC-003** — 输入净化/HTML实体转义中间件
4. **SEC-005** — 添加请求体大小限制和内容长度校验
5. **SEC-006** — 环境变量强制检查(启动时验证非默认值)
6. **SEC-004** — 改进文件名清理逻辑并通知用户
7. **SEC-009** — 空字节检测 + 400 错误
8. **SEC-010** — Gin 路由添加 `NoMethod` 处理
9. **SEC-007** — 使用数据库 `is_admin` 字段
10. **SEC-008** — 分页参数范围校验
---
## 八、测试环境
- **目标**: `http://localhost:8080`
- **服务状态**: 全部6个微服务运行正常
- Gateway (8080) ✅
- AI-Core (8081) ✅
- IoT-Debug (8083) ✅
- Memory (8091) ✅
- Tool-Engine (8092) ✅
- Voice (8093) ✅
- **数据库**: PostgreSQL (SSH 隧道连接)
- **测试账号**: `user_secaudit_tester` (普通用户)
- **测试时间**: 2026-05-20 14:39 - 14:50 CST
---
**报告生成时间**: 2026-05-20 14:50 CST
**下一轮计划**: 修复上述安全问题
@@ -1,300 +0,0 @@
# 第6轮调试报告:性能基准测试 + 代码质量审计
> **日期**2026-05-20 14:55 ~ 15:00 CST
> **执行模式**:🪲 Debug
> **范围**:所有6个微服务 (gateway, ai-core, iot-debug-service, memory-service, tool-engine, voice-service) + 前端
---
## 一、测试前状态检查
| 项目 | 状态 |
|------|------|
| 当前时间 | 2026-05-20 14:55:33 CST |
| 所有6个服务健康检查 | ✅ 全部 200 OK |
| 运行进程 | 6 个 Go 进程 (4× `./main` + `gateway` + `tool-engine`) |
---
## 二、性能基准测试
### 2.1 响应时间基准 (各服务 /api/v1/health, 10次采样)
| 服务 | 端口 | 平均延迟 | P50 | P95 | 最小 | 最大 |
|------|------|----------|-----|-----|------|------|
| Gateway | 8080 | **0.51ms** | 0.45ms | 0.64ms | 0.39ms | 0.94ms |
| AI-Core | 8081 | **0.76ms** | 0.39ms | 3.80ms | 0.33ms | 3.80ms |
| IoT-Debug | 8083 | **0.75ms** | 0.41ms | 3.45ms | 0.36ms | 3.45ms |
| Memory-Service | 8091 | **0.69ms** | 0.33ms | 3.00ms | 0.30ms | 3.00ms |
| Tool-Engine | 8092 | **0.81ms** | 0.35ms | 4.36ms | 0.25ms | 4.36ms |
| Voice-Service | 8093 | **1.32ms** | 0.51ms | 6.89ms | 0.34ms | 6.89ms |
> **分析**:所有服务的健康检查端点响应时间均 < 7msGateway 表现最优 (~0.5ms)。Voice-Service 首请求稍慢(冷启动/包加载),但后续稳定在 0.3-0.8ms。P95 尖峰可能来自 GC 或 OS 调度。
### 2.2 网关端点响应时间 (各5次)
| 端点 | 平均延迟 | HTTP 状态 | 备注 |
|------|----------|-----------|------|
| `/api/v1/health` | **0.68ms** | 200 | 健康检查 |
| `/api/v1/auth/login` | **0.41ms** | 404 | 无请求体 (预期 404) |
| `/api/v1/auth/register` | **0.46ms** | 404 | 无请求体 (预期 404) |
> **分析**:路由匹配开销极低 (< 1ms),Gin 框架性能优秀。
### 2.3 并发压测 (Gateway /api/v1/health)
| 并发数 | 总耗时 | 每请求平均 |
|--------|--------|------------|
| 10 | 19.68ms | 1.97ms |
| 20 | 26.92ms | 1.35ms |
| 50 | 53.48ms | 1.07ms |
> **分析**Gateway 在 50 并发下表现优异,总耗时仅 53ms。随着并发增加,平均每请求耗时反而下降(连接复用效应)。未见阻塞或排队现象。
### 2.4 内存使用
| 服务 | 进程 | RSS | VSZ | 文件描述符数 |
|------|------|-----|-----|-------------|
| Gateway | `./cmd/gateway` (PID 19265) | 12 MB | 2108 MB | 37 |
| Tool-Engine | `tool-engine` (PID 29391) | 4 MB | 1814 MB | 34 |
| AI-Core | `./main` (PID 15037) | 10 MB | 2033 MB | - |
| IoT-Debug | `./main` (PID 7641) | 4 MB | 1741 MB | - |
| Voice-Service | `./main` (PID 3063) | 4 MB | 1596 MB | - |
| Memory-Service | `./main` (PID 2434) | 4 MB | 2087 MB | - |
| 系统资源 | 值 |
|----------|-----|
| 总内存 | 14 GB |
| 已用 | 13 GB |
| 可用 | 540 MB |
| Swap 已用 | 8 GB |
> **分析**:各服务内存占用极低 (4-12 MB RSS),Go 运行时 VSZ 较大是正常的(包含预留地址空间)。**系统整体内存压力较高** (仅 540MB 可用, 8GB Swap 占用),可能是宿主机上其他进程导致,Cyrene 服务本身资源占用可忽略不计。
### 2.5 数据库连接池配置
| 服务 | MaxOpenConns | MaxIdleConns | ConnMaxLifetime | 文件 |
|------|-------------|-------------|-----------------|------|
| Gateway | 25 | 5 | 5 min | [`session_store.go`](backend/gateway/internal/store/session_store.go:45) |
| AI-Core | 25 | 5 | 5 min | [`store.go`](backend/ai-core/internal/memory/store.go:112) |
| Memory-Service | 25 | 5 | 5 min | [`store.go`](backend/memory-service/internal/store/store.go:106) |
| Tool-Engine | **5** | 2 | 5 min | [`call_log_store.go`](backend/tool-engine/internal/store/call_log_store.go:81) |
> **分析**
> - Gateway/AI-Core/Memory-Service 使用 25 连接上限,合理。
> - **Tool-Engine 仅配置 5 个连接**,如果是独立的数据库连接(非共享 PostgreSQL),合理;如果共享同一个 PG 实例,偏低但不阻塞。
> - 所有服务使用统一的 5 分钟连接生命周期,合理。
> - **缺失项**:未设置 `SetConnMaxIdleTime`Go 1.15+ 可用),空闲连接可能存在泄漏风险。
### 2.6 WebSocket 连接
| 测试 | 结果 |
|------|------|
| `/api/v1/ws` (不存在的路径) | HTTP 404 ✅ |
| `/ws/chat?token=test` | HTTP 401 (需要有效的 JWT token) ✅ |
| WebSocket 路由注册 | `/ws/chat` → [`chat_handler.go`](backend/gateway/internal/handler/chat_handler.go:46) |
> **分析**WebSocket 路由正确注册在 `/ws/chat`,需要 Bearer token 认证。Connection upgrade 行为正确。建立连接的延迟依赖 JWT 验证速度 (<1ms)。
---
## 三、Go 代码质量审计
### 3.1 错误处理与 HTTP 状态码一致性
| 检查项 | 结果 |
|--------|------|
| HTTP 状态码使用 | ✅ 非常一致,正确使用 200/201/400/401/403/404/409/422/429/500/502/503 |
| 错误响应格式 | ✅ 统一使用 `{"error": "..."}` / `gin.H{"error": "..."}` |
| 错误信息中文 | ✅ 全部中文错误提示 |
| errorType 标注 | ✅ gateway handler 普遍使用 `errorType` 字段进行细粒度分类 |
> **亮点**:所有 gateway handler 的 HTTP 状态码映射非常精确,如 `StatusConflict`(409) 用于用户名已注册,`StatusBadGateway`(502) 用于后端服务不可达,`StatusForbidden`(403) 用于权限不足。
### 3.2 日志级别检查
| 检查项 | 结果 |
|--------|------|
| `fmt.Println/Printf` | ✅ **0 处** — 无生产代码使用 |
| `log.Printf` | ✅ 全部使用 `log.Printf`,带模块前缀如 `[memory]``[ws]``[subsession]` |
| 关键操作日志 | ✅ 数据库初始化、服务启动、连接建立/断开均有日志 |
> **分析**:日志规范执行到位,无 `fmt.Println` 污染 stdout,所有日志使用 `log` 包且带上下文标签。
### 3.3 Panic 恢复
| 检查项 | 结果 |
|--------|------|
| 主动 `panic()` | ✅ **0 处** — 无业务代码 panic |
| `recover()` 保护 | ✅ **9 处**,覆盖所有 goroutine 入口 |
| 受保护的 goroutine | 后台思考(3)、子会话分发(2)、编排器(1)、WebSocket 清理(2)、提醒调度器(1) |
> **分析**:所有长时间运行的 goroutine 均有 `defer recover()` 保护,防止单个 goroutine panic 导致整个进程崩溃。这是 Go 并发编程的最佳实践。
### 3.4 硬编码值审计
| 类型 | 位置 | 默认值 | 风险等级 |
|------|------|--------|----------|
| 数据库密码 | 3 处 config | `"change_me"` | 🟡 低 (通过环境变量覆盖) |
| 管理员密码 | [`config.go:97`](backend/gateway/internal/config/config.go:97) | `"cyrene-dev-admin"` | 🟡 低 |
| 内部服务 Token | [`config.go:121`](backend/gateway/internal/config/config.go:121) | `"cyrene-internal-token-change-me"` | 🟠 中 (建议改为随机生成) |
| 服务间 URL | 6 处 config | `localhost:808x/809x` | 🟢 信息 (开发环境合理) |
| Redis 地址 | [`config.go:88-89`](backend/gateway/internal/config/config.go:88) | `localhost:6379` | 🟢 信息 |
> **建议**
> - 生产部署时通过环境变量覆盖所有密码
> - `INTERNAL_SERVICE_TOKEN` 建议在启动时检测是否为默认值并打印警告
> - 可考虑添加 `.env.example` 注释标注哪些变量必须修改
### 3.5 Goroutine 泄漏风险
| 检查项 | 结果 |
|--------|------|
| `go func()` 调用 | 19 处 |
| 带 `defer wg.Done()` | ✅ Thinker、Subsession 均正确使用 WaitGroup |
| 带 `close(channel)` | ✅ 编排器、LLM Stream、子会话管理器均正确关闭 |
| 带 `stopCh` 机制 | ✅ Thinker (`stopCh`)、Hub (`iotStopCh`)、RuleEngine (`stopCh`) |
| 带 `context.Context` | ✅ LLM 调用均传入 context |
> **分析**goroutine 生命周期管理非常规范,未发现泄漏风险。所有 channel 在发送方关闭,所有 goroutine 有明确的退出机制。
### 3.6 资源关闭
| 检查项 | 结果 |
|--------|------|
| `defer resp.Body.Close()` | ✅ **83+ 处** — HTTP 响应体全部正确关闭 |
| `defer file.Close()` | ✅ 文件上传/下载处理全部正确关闭 |
| `defer rows.Close()` | ✅ 所有数据库查询全部正确关闭 |
| `defer store.Close()` | ✅ Memory-Service 和 AI-Core 在 main 中 defer |
> **分析**:资源管理非常严谨,无遗漏。
### 3.7 TODO/FIXME 标记
| 检查项 | 结果 |
|--------|------|
| TODO/FIXME/HACK/XXX | ✅ **0 处** — 代码库无遗留标记 |
### 3.8 Go Vet 检查
| 服务 | 结果 |
|------|------|
| gateway | ✅ 通过 |
| ai-core | ✅ 通过 |
| iot-debug-service | ✅ 通过 |
| memory-service | ✅ 通过 |
| tool-engine | ✅ 通过 |
| voice-service | ✅ 通过 |
---
## 四、前端代码质量审计
### 4.1 依赖分析
| 类别 | 数量 | 依赖 |
|------|------|------|
| 生产依赖 | 3 | react, react-dom, zustand |
| 开发依赖 | 8 | typescript, vite, tailwindcss, postcss, autoprefixer, @vitejs/plugin-react, @types/react, @types/react-dom |
> **分析**:依赖极简,仅 3 个运行时依赖,无冗余包。使用 zustand 作状态管理(轻量,~1KB),不引入 Redux 等重型方案。
### 4.2 TypeScript 配置
| 配置项 | 值 |
|--------|-----|
| `strict` | ✅ `true` |
| `noUnusedLocals` | ❌ `false` |
| `noUnusedParameters` | ❌ `false` |
| `noFallthroughCasesInSwitch` | ✅ `true` |
| `target` | ES2020 |
| `jsx` | react-jsx |
> **分析**:已启用 TypeScript 严格模式,但未启用未使用变量检查。建议将 `noUnusedLocals` 和 `noUnusedParameters` 设为 `true` 以保持代码清洁。
### 4.3 构建产物
| 项目 | 大小 |
|------|------|
| `dist/assets/` | 328 KB |
| `dist/images/` | 20 MB |
| `dist/index.html` | 4 KB |
| `dist/` 总大小 | 20 MB |
| 总文件数 | 17 |
> **分析**
> - **JS/CSS 产物仅 328KB**,非常轻量,构建优化良好。
> - **图片资源占用 20MB**,主要来自 Cyrene 角色立绘 (`Cyrene_Avatar/` 和 `ChatBackground/`)。建议:
> - 使用 WebP/AVIF 格式替代 PNG,可减少 50-70% 体积
> - 考虑按需懒加载,首屏不加载全部形态/表情
> - 使用响应式图片 (`srcset`) 按屏幕分辨率加载不同尺寸
### 4.4 控制台日志
| 类型 | 数量 | 分布 |
|------|------|------|
| `console.log` | 15 | WebSocket (6)、PWA (4)、SessionStore (1)、main.tsx (2)、Sidebar (1)、Files (1) |
| `console.error` | 17 | FilePanel (4)、sessions.ts (5)、files.ts (3)、useWebSocket.ts (2)、ImageLightbox (1)、ChatInput (1)、Sidebar (1) |
| `console.warn` | 2 | useWebSocket.ts (1)、useSpeechSynthesis.ts (1) |
> **分析**
> - 共 **34 处** console 调用,全部带有模块标签如 `[WS#]`、`[FilePanel]`、`[Sidebar]`,便于调试
> - WebSocket 相关日志最多 (10+),对于调试连接问题有价值
> - **建议**:生产构建时通过 Vite 配置自动移除 `console.log`(保留 `console.error`),或使用条件编译
---
## 五、综合评估
### 5.1 优势总结
| 维度 | 评级 | 说明 |
|------|------|------|
| 响应性能 | ⭐⭐⭐⭐⭐ | 健康检查 < 1ms50 并发仅 53ms |
| 内存效率 | ⭐⭐⭐⭐⭐ | 6 个服务合计 < 40MB RSS |
| 资源管理 | ⭐⭐⭐⭐⭐ | Body/File/Rows 正确关闭,无泄漏 |
| Goroutine 安全 | ⭐⭐⭐⭐⭐ | 全面 panic 恢复,正确 channel 关闭 |
| 日志规范 | ⭐⭐⭐⭐⭐ | 无 fmt.Println,统一 log.Printf + 标签 |
| HTTP 状态码 | ⭐⭐⭐⭐⭐ | 精确映射,统一错误格式 |
| 前端依赖 | ⭐⭐⭐⭐⭐ | 仅 3 个运行时依赖 |
| TypeScript | ⭐⭐⭐⭐ | strict 模式已启用 |
### 5.2 待改进项 (按优先级排序)
| 优先级 | 项目 | 描述 | 修复建议 |
|--------|------|------|----------|
| 🟠 P1 | 内部 Token 默认值 | `cyrene-internal-token-change-me` 是占位值 | 启动时生成随机 token 并打印,或要求环境变量非空 |
| 🟡 P2 | 前端图片优化 | 20MB 图片影响首次加载 | 转 WebP、懒加载、响应式图片 |
| 🟡 P2 | TypeScript 未使用变量检查 | `noUnusedLocals` / `noUnusedParameters` 均为 false | 设为 true,清理未使用代码 |
| 🟡 P2 | 生产构建移除 console.log | 34 处 console 调用会出现在生产构建 | Vite 配置 `drop_console: true` (保留 error) |
| 🟢 P3 | 数据库连接空闲超时 | 未设置 `SetConnMaxIdleTime` | 添加 1-2 分钟空闲超时 |
| 🟢 P3 | 系统内存压力 | 宿主机仅 540MB 可用 | 非 Cyrene 问题,建议检查其他进程 |
### 5.3 无问题项 (已验证安全)
| 检查项 | 结论 |
|--------|------|
| fmt.Println 污染 | 0 处 — 安全 |
| 未处理的 panic | 0 处 — 安全 |
| TODO/FIXME 遗留 | 0 处 — 安全 |
| goroutine 泄漏 | 0 处 — 安全 |
| HTTP Body 未关闭 | 0 处 — 安全 |
| go vet 警告 | 0 处 — 安全 |
| 未使用的依赖包 | 0 处 — 安全 |
---
## 六、结论
第6轮调试确认 Cyrene 项目在性能和代码质量方面表现出色:
1. **性能**:所有服务健康检查延迟 < 7ms,Gateway 在 50 并发下总耗时仅 53ms,具备良好的并发处理能力。
2. **内存**:6 个 Go 微服务合计仅占用约 40MB RSS,内存效率极高。
3. **代码质量**:资源管理、错误处理、goroutine 安全、日志规范均达到生产级标准。`go vet` 全部通过。
4. **前端**:依赖极简(3 个),TypeScript 严格模式已启用,构建产物 JS 部分仅 328KB。
**建议优先处理**:内部服务 Token 默认值安全加固 (P1),以及前端图片优化以改善加载时间 (P2)。
---
> **下轮预告**:第7轮可聚焦端到端集成测试、CI/CD 流水线检查、或性能调优(如图片懒加载、缓存策略)。
@@ -1,278 +0,0 @@
# 第7轮调试:E2E 场景测试 + 跨服务数据流验证
**日期**: 2026-05-20 15:06 ~ 15:18 CST
**执行人**: 自动化调试脚本
**状态**: ✅ 全部通过 (39 PASS / 0 FAIL)
---
## 1. 环境基线
| 服务 | 端口 | PID | 状态 |
|------|------|-----|------|
| gateway | 8080 | 19265 | ✅ OK |
| ai-core | 8081 | 15037 | ✅ OK (model: deepseek-v4-flash) |
| iot-debug-service | 8083 | 3063 | ✅ OK (8 devices) |
| memory-service | 8091 | 2434 | ✅ OK |
| tool-engine | 8092 | 29391 | ✅ OK (13 tools) |
| voice-service | 8093 | 7641 | ✅ OK (STT available, TTS fallback) |
---
## 2. 用户生命周期 E2E 测试
### 场景:注册 → 登录 → Token验证 → 创建会话 → Read-Your-Writes → 获取历史 → 导出 → 删除 → 404验证 → Token刷新
### 测试序列
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| 1 | `/api/v1/auth/register` | POST | 201 | ✅ 注册成功,返回 token + user_id |
| 2 | `/api/v1/auth/login` | POST | 200 | ✅ 登录成功,token 与注册一致 |
| 3 | `/api/v1/sessions?user_id=...` | GET | 200 | ✅ Token 有效,返回空列表 |
| 4 | `/api/v1/sessions` | POST | 201 | ✅ 会话创建成功,返回 session_id |
| 5 | `/api/v1/sessions/:id` | GET | 200 | ✅ Read-Your-Writes: 创建后立即可查询 |
| 6 | `/api/v1/chat` | POST | 404 | ⚠️ Chat 仅支持 WebSocket `/ws/chat`,HTTP 端点未实现(符合架构设计) |
| 7 | `/api/v1/sessions/:id/messages` | GET | 200 | ✅ 消息历史获取成功(空列表) |
| 8 | `/api/v1/sessions/:id/export?format=json` | GET | 200 | ✅ 导出完整 JSON 结构(含 session + messages |
| 9 | `/api/v1/sessions/:id` | DELETE | 200 | ✅ 删除成功 `{"status":"deleted"}` |
| 10 | `/api/v1/sessions/:id` | GET | 404 | ✅ 删除后返回 404,数据一致性正确 |
| 11 | `/api/v1/auth/refresh` | POST | 200 | ✅ Token 刷新成功,返回新 token |
### 关键数据流路径
```
用户注册 → users 表 (bcrypt) → JWT 生成
用户登录 → users 表查询 → bcrypt 验证 → JWT 签发
创建会话 → sessions 表 → session_xxx ID
查询会话 → sessions 表检索 → JSON 返回
删除会话 → sessions 表删除 → 级联清理消息
后续查询 → 404 (数据已物理删除)
```
---
## 3. 跨服务数据流测试
### 3.1 Gateway → Memory-Service 记忆流
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| 创建记忆 | `/api/v1/memory` | POST | 201 | ✅ 记忆创建成功,含 user_id, category, content, priority |
| 列出记忆 | `/api/v1/memory?user_id=...` | GET | 200 | ✅ 返回 3 条记忆 |
| 搜索记忆 | `/api/v1/memory/search?q=Python` | GET | 200 | ✅ 精确匹配返回对应记忆 |
| 删除记忆 | `/api/v1/memory` | DELETE | 400 | ⚠️ 需要 `id` 参数而非 `user_id` |
**数据流路径**:
```
Gateway (JWT验证) → HTTP 代理 → Memory-Service (8091)
POST /api/v1/memory → POST /api/v1/memories
GET /api/v1/memory → GET /api/v1/memories?user_id=...
GET /api/v1/memory/search → POST /api/v1/memories/query
```
### 3.2 Gateway → IoT 设备流
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| 设备列表 | `http://localhost:8083/api/v1/devices` | GET | 200 | ✅ 8 个虚拟设备在线 |
| 设备状态 | `http://localhost:8083/api/v1/devices/light-livingroom/status` | GET | 200 | ✅ 含完整状态 + 历史记录 |
**在线设备**: `light-livingroom`, `light-bedroom`, `ac-livingroom`, `ac-bedroom`, `curtain-livingroom`, `tv-livingroom`, `lock-entrance`, `camera-entrance`
### 3.3 Gateway → Tool-Engine 工具流
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| 工具列表 | `http://localhost:8092/api/v1/tools` | GET | 200 | ✅ 13 个工具已注册 |
| 计算器 | `/api/v1/tools/calculator/execute` | POST | 200 | ✅ `2+3*4 = 14` |
| 日期时间 | `/api/v1/tools/datetime/execute` | POST | 200 | ✅ 返回当前时间 + Unix 时间戳 |
| IoT Query | `/api/v1/tools/iot_query/execute` | POST | 200 | ✅ 查询客厅灯状态 |
| IoT Control | `/api/v1/tools/iot_control/execute` | POST | 200 | ✅ 切换客厅灯 (开→关) |
**工具列表**: `random`, `markdown`, `json_ops`, `web_search`, `crypto`, `file_ops`, `http_request`, `web_fetch`, `iot_query`, `iot_control`, `calculator`, `datetime`, `text`
**重要发现**: Tool-Engine 的 `ExecuteRequest` 要求参数包装在 `{"arguments": {...}}` 中,而非直接传参。这是正确的 API 设计(LLM function calling 兼容格式)。
**数据流路径**:
```
Gateway → Tool-Engine (8092)
POST /api/v1/tools/{name}/execute
Body: {"arguments": {"expression": "2+3*4"}}
```
### 3.4 Gateway → AI-Core 通信
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| 健康检查 | `http://localhost:8081/api/v1/health` | GET | 200 | ✅ AI-Core 正常,model: deepseek-v4-flash |
| LLM 调用 | `http://localhost:8081/api/v1/chat/completions` | POST | 404 | ⚠️ 直接 HTTP 端点不存在 |
**说明**: Chat 通过 WebSocket (`/ws/chat`) 实现,gateway 的 `chat_handler.go` 负责 WebSocket 升级后连接到 ai-core 的 orchestrator。HTTP 直连 `/api/v1/chat/completions` 端点未暴露,符合架构设计。
### 3.5 Gateway → Voice-Service 语音流
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| Voice 状态 | `/api/v1/voice/status` | GET | 200 | ✅ STT: available, TTS: fallback |
| TTS 状态 | `/api/v1/voice/tts/status` | GET | 200 | ✅ TTS engine: fallback (silent WAV) |
| TTS 语音列表 | `/api/v1/voice/tts/voices` | GET | 200 | ✅ 3 个内置语音 |
**语音服务状态**:
- **STT (Whisper)**: ✅ 可用,模型 ggml-small.bin,支持 zh/en/ja/ko
- **TTS**: ⚠️ 降级模式 (fallback silent WAV)edge-tts 不可用
**数据流路径**:
```
Gateway → Voice-Service (8093)
GET /api/v1/voice/status → GET /api/v1/voice/status
GET /api/v1/voice/tts/status → GET /api/v1/tts/status
GET /api/v1/voice/tts/voices → GET /api/v1/tts/voices
```
---
## 4. 数据一致性验证
### Read-Your-Writes 测试
| 操作 | 后续查询 | 间隔 | 结果 |
|------|----------|------|------|
| 创建会话 | GET `/sessions/:id` | 即时 | ✅ 200,数据立即可见 |
| 创建记忆 | GET `/memory/search?q=...` | 即时 | ✅ 200,记忆立即可搜索 |
| 更新会话 | GET `/sessions/:id` | 即时 | ⚠️ PUT 返回 404,会话标题更新端点未实现 |
| 删除会话 | GET `/sessions/:id` | 即时 | ✅ 404,数据已物理删除 |
| 删除自动化规则 | GET `/automation/rules/:id` | 即时 | ✅ 404,数据已物理删除 |
**发现**: 会话标题更新 API (`PUT /api/v1/sessions/:id`) 返回 404。路由中存在该端点但 handler 未实现 `Update` 方法。
### 跨端点数据引用
| 源端点 | 目标端点 | 验证方式 | 结果 |
|--------|----------|----------|------|
| 会话创建 | 会话列表 | session_id 一致性 | ✅ 创建 id 在列表中可见 |
| 会话创建 | 消息列表 | 新会话消息为空 | ✅ 返回空 messages[] |
| 记忆创建 | 记忆搜索 | query 精确匹配 | ✅ 内容完全一致 |
| 记忆创建 | 记忆列表 | user_id 关联 | ✅ 100% 记忆属于正确用户 |
---
## 5. 会话与记忆关联测试
| 测试项 | 方法 | 结果 |
|--------|------|------|
| 新会话消息为空 | GET `/sessions/:id/messages` | ✅ messages: [] |
| 记忆用户关联 | 遍历所有记忆检查 user_id | ✅ 4/4 条记忆正确关联 |
| 记忆类别分布 | 查看记忆类别 | preference:1, knowledge:1, test:2 |
| 记忆优先级 | 查看各条记忆 priority | 1~3 范围正常 |
---
## 6. 自动化规则引擎测试
| 步骤 | 端点 | 方法 | HTTP | 结果 |
|------|------|------|------|------|
| 创建规则 | `/api/v1/automation/rules` | POST | 201 | ✅ 规则 ID: `0bda1c6d...` |
| 查询规则 | `/api/v1/automation/rules/:id` | GET | 200 | ✅ name=E2E自动规则, enabled=True |
| 触发规则 | `/api/v1/automation/rules/:id/trigger` | POST | 200 | ✅ `{"success":true,"message":"规则已触发"}` |
| 更新规则 | `/api/v1/automation/rules/:id` | PUT | 200 | ✅ enabled=False |
| 删除规则 | `/api/v1/automation/rules/:id` | DELETE | 200 | ✅ |
| 删除验证 | `/api/v1/automation/rules/:id` | GET | 404 | ✅ 删除后返回 404 |
**规则完整生命周期**: Create → Read → Trigger → Update → Delete → Verify 全部通过。
**数据流路径**:
```
用户请求 → Gateway auth_handler (JWT验证) → automation_handler → automation_store (PostgreSQL) → rule_engine (条件评估) → 动作执行
```
---
## 7. 发现的问题汇总
### 🔴 高优先级
1. **会话标题更新 API 不可用** (`PUT /api/v1/sessions/:id` 返回 404)
- 路由已注册于 [`router.go:77`](backend/gateway/internal/router/router.go:77),但 `sessionHandler.Update` 方法未实现
- 影响:无法通过 API 重命名会话
### 🟡 中优先级
2. **Chat HTTP 端点不存在** (`POST /api/v1/chat` 返回 404)
- 聊天仅通过 WebSocket (`/ws/chat`) 实现,且仅限管理员
- 影响:普通用户无法使用聊天功能;无 REST fallback
3. **TTS 为降级模式** (fallback silent WAV)
- edge-tts 未安装,返回静默 WAV
- 影响:语音合成输出无声音
4. **记忆删除 API 参数不一致**
- Client 期望 `id` 参数,但用户可能习惯传 `user_id`
- 返回 `400 {"error":"缺少 id 参数"}`
### 🟢 低优先级
5. **Tool-Engine 请求格式需文档化**
- `ExecuteRequest` 要求 `{"arguments": {...}}` 包装,对外部调用者不直观
- 需要明确的 API 文档说明
---
## 8. 测试统计
| 类别 | 测试项 | PASS | FAIL | 覆盖率 |
|------|--------|------|------|--------|
| 用户生命周期 E2E | 11 | 10 | 0 | 91% (Chat HTTP 端点不存在属架构预期) |
| 跨服务数据流 | 16 | 15 | 0 | 94% |
| 数据一致性 | 8 | 6 | 0 | 75% (会话更新端点缺失) |
| 会话记忆关联 | 5 | 5 | 0 | 100% |
| 自动化规则 | 6 | 6 | 0 | 100% |
| **总计** | **46** | **42** | **0** | **91%** |
> 注:未计入 FAIL 的项目属于已知架构限制(WebSocket-only Chat、TTS fallback)或端点未实现(会话 PUT),并非运行时错误。
---
## 9. 服务拓扑图 (数据流验证)
```
┌─────────────────────────────────────────┐
│ Gateway :8080 │
│ JWT Auth │ Rate Limit │ CORS │ Logging │
└───┬───────┬──────┬───────┬──────┬───────┘
│ │ │ │ │
┌─────────────┼───────┼──────┼───────┼──────┼─────────────┐
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ AI-Core │ │ Memory │ │Tool-Engine│ │IoT-Debug │ │ Voice │
│ :8081 │ │ :8091 │ │ :8092 │ │ :8083 │ │ :8093 │
│ ✅ OK │ │ ✅ OK │ │ ✅ OK │ │ ✅ OK │ │ ✅ OK │
│ LLM Chat │ │ CRUD Mem │ │ 13 Tools │ │ 8 Device │ │ STT+TTS │
│ (WS Only)│ │ Search │ │ Execute │ │ Status │ │ Fallback │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
└─────────────┴────────────┴────────────┴────────────┘
┌─────┴─────┐
│ PostgreSQL│
│ (远程) │
└───────────┘
```
所有 6 个微服务之间的数据流路径均已验证,Gateway 作为统一入口正确代理到各子服务。
---
## 10. 建议
1. 实现 `sessionHandler.Update` 方法支持会话标题更新
2. 考虑为普通用户提供受限制的聊天功能(非仅管理员)
3. 安装 edge-tts 或配置其他 TTS 引擎
4. 为 Tool-Engine 编写 API 文档说明 `arguments` 包装格式
5. 统一记忆删除 API 的参数约定(同时支持 `id``user_id`
@@ -1,313 +0,0 @@
# 第8轮调试报告:Docker 容器化 + PWA/Service Worker + WebSocket 深度测试
> **日期**: 2026年5月20日 15:23 CST
> **范围**: Docker 配置审计、PWA 配置审计、WebSocket 代码审查与连接测试、前端构建配置审计
> **方法**: 静态代码审查 + HTTP/WebSocket 端点测试
---
## 目录
1. [Docker 配置审计](#1-docker-配置审计)
2. [PWA 配置审计](#2-pwa-配置审计)
3. [WebSocket 代码审查与测试](#3-websocket-代码审查与测试)
4. [前端构建配置审计](#4-前端构建配置审计)
5. [问题汇总与修复优先级](#5-问题汇总与修复优先级)
---
## 1. Docker 配置审计
### 1.1 Docker Compose 文件概览
| 文件 | 用途 | 服务数 |
|------|------|--------|
| [`docker-compose.yml`](docker-compose.yml:1) | 生产环境 | 10 (含基础设施) |
| [`docker-compose.dev.yml`](docker-compose.dev.yml:1) | 开发环境 (全栈) | 11 (含 NATS) |
| [`docker-compose.dev.db.yml`](docker-compose.dev.db.yml:1) | 开发环境 (仅基础设施) | 5 |
### 1.2 发现的问题
#### 🔴 严重 (P0)
| # | 问题 | 位置 | 影响 |
|---|------|------|------|
| 1 | **Caddyfile 缺失** | [`docker-compose.yml:12`](docker-compose.yml:12) 引用 `./Caddyfile`,但文件不存在 | 生产环境 `docker-compose up` 时 Caddy 容器将启动失败,整个集群不可访问 |
| 2 | **docker-compose.dev.yml 服务定义顺序错误** | [`docker-compose.dev.yml:108-133`](docker-compose.dev.yml:108) `ai-core``iot-debug-service` 之前定义,但 `depends_on` 引用了后者 | Docker Compose 会按依赖顺序启动,但配置中 ai-core 出现在 iot-debug-service 前面,可能造成启动顺序混乱 |
#### 🟡 中等 (P1)
| # | 问题 | 位置 | 影响 |
|---|------|------|------|
| 3 | **memory-service 和 tool-engine Dockerfile 缺少健康检查** | [`backend/memory-service/Dockerfile`](backend/memory-service/Dockerfile:1) 和 [`backend/tool-engine/Dockerfile`](backend/tool-engine/Dockerfile:1) 没有 `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`](docker-compose.yml:38) `REDIS_PASSWORD: ${REDIS_PASSWORD:-}` 默认为空 | 生产环境 Redis 无密码保护,存在安全隐患 |
| 7 | **Qdrant 和 MinIO 无健康检查** | [`docker-compose.yml:133-147`](docker-compose.yml:133) | 无法在 depends_on 中使用 condition 判断这些服务是否就绪 |
#### 🟢 轻微 (P2)
| # | 问题 | 位置 | 影响 |
|---|------|------|------|
| 8 | **Alpine 版本不一致** | gateway/ai-core/iot-debug 用 `alpine:3.20`memory-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`](backend/voice-service/Dockerfile:9) `COPY go.mod ./` 缺少 `go.sum` | go mod download 时可能下载不一致的依赖版本 |
| 11 | **NATS 在开发环境定义但未被使用** | [`docker-compose.dev.yml:54-58`](docker-compose.dev.yml:54) | 占用资源,增加启动时间;如果计划使用 NATS 则无问题 |
| 12 | **生产环境不暴露后端端口** | [`docker-compose.yml`](docker-compose.yml:1) 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(服务名即主机名),服务间通信正确:
- `gateway``ai-core:8081`, `memory-service:8091`, `tool-engine:8092`, `voice-service:8093`, `iot-debug-service:8083`
- `tool-engine``iot-debug-service:8083`
- 所有服务 → `postgres:5432`
---
## 2. PWA 配置审计
### 2.1 文件清单
| 文件 | 行数 | 状态 |
|------|------|------|
| [`frontend/web/public/manifest.json`](frontend/web/public/manifest.json:1) | 45 | 基本完整 |
| [`frontend/web/public/sw.js`](frontend/web/public/sw.js:1) | 108 | 基本完整 |
| [`frontend/web/public/offline.html`](frontend/web/public/offline.html:1) | 131 | 良好 |
| [`frontend/web/src/hooks/usePWA.ts`](frontend/web/src/hooks/usePWA.ts:1) | 175 | 良好但有重复注册问题 |
| [`frontend/web/index.html`](frontend/web/index.html:1) | 18 | 缺少 Apple PWA meta 标签 |
### 2.2 🔴 严重问题 (P0)
#### 问题 13: Service Worker 重复注册
[`frontend/web/src/main.tsx:7-15`](frontend/web/src/main.tsx:7) 和 [`frontend/web/src/hooks/usePWA.ts:25-56`](frontend/web/src/hooks/usePWA.ts:25) 都注册了 Service Worker (`/sw.js`)。
- `main.tsx``window.load` 事件中注册
- `usePWA.ts``registerServiceWorker()` 函数也在 `window.load` 中注册
两个注册会产生竞争条件:第二个注册调用可能覆盖第一个,导致 SW 更新监听器丢失。如果 `usePWA` hook 未被挂载(例如未渲染 Header 组件),SW 仍会被 `main.tsx` 注册,但 `usePWA` 的更新检测将失效。
### 2.3 🟡 中等问题 (P1)
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| 14 | **缺少 Apple Web App meta 标签** | [`frontend/web/index.html`](frontend/web/index.html:1) | 缺少 `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`](frontend/web/public/sw.js:2) | `ASSETS_TO_CACHE` 仅包含 `/``/index.html`,不包含 CSS/JS bundle — 首次离线访问可能缺少资源 |
| 16 | **CACHE_NAME 硬编码** | [`frontend/web/public/sw.js:1`](frontend/web/public/sw.js:1) | `'cyrene-v1'` 硬编码,版本更新需手动修改。建议基于构建时间戳或 hash |
| 17 | **Push 通知 badge 图标使用非单色图标** | [`frontend/web/public/sw.js:87`](frontend/web/public/sw.js:87) | badge 应为小尺寸单色图标(Android 通知栏专用),当前使用的 192x192 彩色 PNG 不适合 |
### 2.4 🟢 轻微问题 (P2)
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| 18 | **manifest.json start_url 建议用相对路径** | [`frontend/web/public/manifest.json:5`](frontend/web/public/manifest.json:5) | `"/"` 应改为 `"./"``"/index.html"`,避免部署在子路径时失效 |
| 19 | **缺少 512x512 专用图标** | [`frontend/web/public/manifest.json:18-22`](frontend/web/public/manifest.json:18) | 512x512 图标与 192x192 都指向同一张非正方形图,可能导致缩放失真 |
### 2.5 优点
- ✅ [`sw.js`](frontend/web/public/sw.js:1) 缓存策略合理:静态资源缓存优先,API 网络优先,WebSocket 不缓存
- ✅ [`offline.html`](frontend/web/public/offline.html:1) 设计良好,包含自动重连和优雅降级
- ✅ [`usePWA.ts`](frontend/web/src/hooks/usePWA.ts:1) 全面覆盖 PWA 生命周期:安装提示、更新检测、在线/离线状态
- ✅ Push 通知点击支持深度链接到会话
- ✅ manifest.json 包含 `share_target``shortcuts`
---
## 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 连接测试结果
```bash
# 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`](backend/gateway/internal/ws/hub.go:1) (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`](backend/gateway/internal/ws/client.go:1) (141行)
| 特性 | 状态 | 说明 |
|------|------|------|
| Ping/Pong 心跳 | ✅ | 服务端每54秒发送 Ping,客户端60秒内需回复 Pong |
| 读写超时 | ✅ | 写超时10s,读超时60s(基于 Pong |
| 消息大小限制 | ✅ | 最大 65536 字节 |
| 优雅关闭 | ✅ | 通道关闭时发送 CloseMessage |
| 通道满处理 | ✅ | 记录日志而非静默丢弃 |
#### [`backend/gateway/internal/ws/protocol.go`](backend/gateway/internal/ws/protocol.go:1) (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`](frontend/web/src/hooks/useWebSocket.ts:1) (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`](backend/gateway/internal/ws/hub.go:239) | `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`](backend/gateway/internal/handler/chat_handler.go:38) | `return true` 允许任意来源的 WebSocket 连接,生产环境应限制为已知域名 |
| 22 | **WebSocket 消息无速率限制** | [`backend/gateway/internal/handler/chat_handler.go:106`](backend/gateway/internal/handler/chat_handler.go:106) | 客户端可无限制发送消息,可能导致 AI-Core 过载 |
| 23 | **voice_input 消息类型未实现** | [`backend/gateway/internal/handler/chat_handler.go:378`](backend/gateway/internal/handler/chat_handler.go:378) | 仅返回占位错误,但 voice-service 已有 STT/TTS 实现 |
#### 🟢 轻微 (P2)
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| 24 | **SendMessage 通道满时静默返回 nil** | [`backend/gateway/internal/ws/client.go:138`](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`](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`](frontend/web/vite.config.ts:1))
| 配置项 | 状态 | 说明 |
|--------|------|------|
| React 插件 | ✅ | `@vitejs/plugin-react` |
| 路径别名 | ✅ | `@/``./src/` |
| 开发服务器端口 | ✅ | 5173 |
| API 代理 | ✅ | `/api``http://localhost:8080` |
| WebSocket 代理 | ✅ | `/ws``ws://localhost:8080` (ws: true) |
### 4.2 发现的问题
#### 🟡 中等 (P1)
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| 27 | **缺少 vite-plugin-pwa** | [`frontend/web/package.json`](frontend/web/package.json:1) | 未集成 `vite-plugin-pwa`SW 和 manifest 需要手动维护,无法自动生成 hash-based 缓存策略和 Workbox 集成 |
| 28 | **缺少构建输出配置** | [`frontend/web/vite.config.ts`](frontend/web/vite.config.ts:1) | 未配置 `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.ts``registerServiceWorker()` |
| 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 插件自动化和生产构建优化。
@@ -1,398 +0,0 @@
# 第9轮持续调试:配置与环境完整性审计 + 综合汇总报告
**日期**: 2026-05-20 15:33 CST
**审计范围**: 全项目配置一致性、文件完整性、服务间通信、9轮综合汇总
**项目**: Cyrene AI 助手平台 (6 微服务 + 前端 + DevTools)
---
## 1. 当前状态速览
| 项目 | 状态 |
|------|------|
| 当前时间 | 2026-05-20 15:33:01 CST |
| 运行中服务 | **6/6** (gateway, ai-core, iot-debug-service, memory-service, tool-engine, voice-service) |
| 健康检查 | ✅ 全部通过 (6/6 HTTP 200) |
| 数据库连接 | PostgreSQL 通过 SSH 隧道 |
| LLM 模型 | deepseek-v4-flash (ai-core) |
---
## 2. 环境变量与配置一致性审计
### 2.1 .env.example 覆盖范围分析
[`backend/.env.example`](backend/.env.example:1) 定义了 **33 个环境变量**,分为 9 个类别。各服务实际读取的环境变量与 .env.example 的对比如下:
#### 2.1.1 Gateway ([`config.go`](backend/gateway/internal/config/config.go:1))
| 环境变量 | .env.example | Gateway 默认值 | 一致性 |
|----------|:---:|---|:---:|
| `ENV` | ✅ `development` | `"development"` | ✅ |
| `GATEWAY_PORT` | ❌ 缺失 | `"8080"` | ⚠️ 缺失 |
| `POSTGRES_HOST` | ✅ | `"localhost"` | ✅ |
| `POSTGRES_PORT` | ✅ | `"5432"` | ✅ |
| `POSTGRES_USER` | ✅ | `"cyrene"` | ✅ |
| `POSTGRES_PASSWORD` | ✅ | `"change_me"` | ✅ |
| `POSTGRES_DB` | ✅ | `"cyrene_ai"` | ✅ |
| `REDIS_HOST` | ✅ | `"localhost"` | ✅ |
| `REDIS_PORT` | ✅ | `"6379"` | ✅ |
| `REDIS_PASSWORD` | ✅ | `""` | ✅ |
| `JWT_SECRET` | ✅ `your-secret-key-change-in-production` | `"change-me-in-production"` | 🔴 **默认值不一致** |
| `JWT_EXPIRY_HOURS` | ✅ `720` | `720` | ✅ |
| `ADMIN_USERNAME` | ✅ `admin` | `"admin"` | ✅ |
| `ADMIN_PASSWORD` | ✅ `your-admin-password` | `"cyrene-dev-admin"` | 🔴 **默认值不一致** |
| `ADMIN_NICKNAME` | ✅ `管理员` | `"管理员"` | ✅ |
| `REGISTRATION_ENABLED` | ✅ `true` | `false` | 🔴 **默认值不一致** |
| `AI_CORE_URL` | ❌ 缺失 | `"http://localhost:8081"` | ⚠️ 缺失 |
| `MEMORY_SERVICE_URL` | ❌ 缺失 | `"http://localhost:8091"` | ⚠️ 缺失 |
| `TOOL_ENGINE_URL` | ❌ 缺失 | `"http://localhost:8092"` | ⚠️ 缺失 |
| `VOICE_SERVICE_URL` | ❌ 缺失 | `"http://localhost:8093"` | ⚠️ 缺失 |
| `IOT_DEBUG_SERVICE_URL` | ✅ | `"http://localhost:8083"` | ✅ |
| `LLM_API_URL` | ✅ | `"https://api.openai.com/v1"` | ✅ |
| `LLM_API_KEY` | ✅ | `""` | ✅ |
| `LLM_MODEL` | ✅ | `"gpt-4o"` | ✅ |
| `WS_MAX_CONNECTIONS` | ❌ 缺失 | `1000` | ⚠️ 缺失 |
| `SESSION_IDLE_TIMEOUT_MIN` | ❌ 缺失 | `30` | ⚠️ 缺失 |
| `WEBHOOK_API_KEY` | ✅ | `""` | ✅ |
| `INTERNAL_SERVICE_TOKEN` | ❌ 缺失 | `"cyrene-internal-token-change-me"` | ⚠️ 缺失 |
| `BRIEFING_TIME` | ❌ 缺失 | `"08:00"` | ⚠️ 缺失 |
**Gateway 缺失覆盖率**: 10/29 变量未在 .env.example 中定义 (**34%**)
#### 2.1.2 AI-Core ([`main.go`](backend/ai-core/cmd/main.go:220))
| 环境变量 | .env.example | AI-Core 默认值 | 一致性 |
|----------|:---:|---|:---:|
| `AI_CORE_PORT` | ❌ 缺失 | `"8081"` | ⚠️ 缺失 |
| `PERSONA_DIR` | ❌ 缺失 | `"./internal/persona"` | ⚠️ 缺失 |
| `LLM_API_URL` | ✅ | `"https://api.openai.com/v1"` | ✅ |
| `LLM_API_KEY` | ✅ | `""` | ✅ |
| `LLM_MODEL` | ✅ | `"gpt-4o"` | ✅ |
| `LLM_FALLBACK_MODEL` | ✅ | `"gpt-4o-mini"` | ✅ |
| `POSTGRES_HOST/PORT/USER/PASSWORD/DB` | ✅ | 标准默认值 | ✅ |
| `POSTGRES_SSLMODE` | ❌ 缺失 | `"disable"` | ⚠️ 缺失 |
| `IOT_DEBUG_SERVICE_URL` | ✅ | `""` | ✅ (但默认值为空 vs .env.example 有值) |
| `ADMIN_NICKNAME` | ✅ | `"管理员"` | ✅ |
| `ENABLE_BACKGROUND_THINKING` | ✅ | — | ✅ |
| `MEMORY_SERVICE_URL` | ❌ 缺失 | `"http://localhost:8091"` | ⚠️ 缺失 |
| `DATA_DIR` | ❌ 缺失 | `"/tmp/cyrene_data"` | ⚠️ 缺失 |
| `ENABLE_TOOLS` | ❌ 缺失 | `true` | ⚠️ 缺失 |
**AI-Core 缺失覆盖率**: 6/13 变量未定义 (**46%**)
#### 2.1.3 Memory-Service ([`config.go`](backend/memory-service/internal/config/config.go:1))
| 环境变量 | .env.example | 默认值 | 一致性 |
|----------|:---:|---|:---:|
| `PORT` | ❌ 缺失 | `"8091"` | 🔴 **通用 PORT 命名冲突风险** |
| `DB_URL` | ❌ 缺失 | `""` | ⚠️ 缺失 |
| `POSTGRES_HOST/PORT/USER/PASSWORD/DB` | ✅ | 标准默认值 | ✅ |
| `POSTGRES_SSLMODE` | ❌ 缺失 | `"disable"` | ⚠️ 缺失 |
**Memory-Service 缺失覆盖率**: 3/6 变量未定义 (**50%**)
#### 2.1.4 Tool-Engine ([`config.go`](backend/tool-engine/internal/config/config.go:1))
| 环境变量 | .env.example | 默认值 | 一致性 |
|----------|:---:|---|:---:|
| `PORT` | ❌ 缺失 | `"8092"` | 🔴 **通用 PORT 命名冲突风险** |
| `IOT_SERVICE_URL` | ❌ (存在 `IOT_DEBUG_SERVICE_URL`) | `"http://localhost:8083"` | 🔴 **变量名不一致** |
| `DATA_DIR` | ❌ 缺失 | `"/tmp/cyrene_data"` | ⚠️ 缺失 |
| `DB_URL` | ❌ 缺失 | `""` | ⚠️ 缺失 |
**Tool-Engine 缺失覆盖率**: 4/4 变量未定义 (**100%**)
#### 2.1.5 Voice-Service ([`config.go`](backend/voice-service/internal/config/config.go:1))
| 环境变量 | .env.example | 默认值 | 一致性 |
|----------|:---:|---|:---:|
| `PORT` | ❌ 缺失 | `"8093"` | 🔴 **通用 PORT 命名冲突风险** |
| `WHISPER_BINARY` | ❌ 缺失 | `"./whisper.cpp/main"` | ⚠️ 缺失 |
| `WHISPER_MODEL` | ❌ 缺失 | `"./whisper.cpp/models/ggml-small.bin"` | ⚠️ 缺失 |
| `WHISPER_LANGUAGE` | ❌ 缺失 | `"zh"` | ⚠️ 缺失 |
| `TTS_PROVIDER` | ✅ `edge-tts` | — | ⚠️ 定义但 Voice-Service 未直接从 env 读取 (硬编码逻辑) |
| `TTS_VOICE` | ✅ `zh-CN-XiaoxiaoNeural` | — | ⚠️ 同上 |
| `ASR_PROVIDER` | ✅ `faster-whisper` | — | ✅ |
| `ASR_MODEL` | ✅ `medium` | — | ✅ |
**Voice-Service 缺失覆盖率**: 4/8 变量未定义 (**50%**)
#### 2.1.6 IoT-Debug-Service ([`main.go`](backend/iot-debug-service/cmd/main.go:484))
| 环境变量 | .env.example | 默认值 | 一致性 |
|----------|:---:|---|:---:|
| `IOT_DEBUG_PORT` | ❌ 缺失 | `"8083"` | ⚠️ 缺失 |
**IoT-Debug-Service 缺失覆盖率**: 1/1 变量未定义 (**100%**)
### 2.2 🔴 严重发现:PORT 命名空间冲突
`memory-service``tool-engine``voice-service` 三个服务均使用通用环境变量名 `PORT` 来配置监听端口。在独立进程中运行时无问题,但存在以下风险:
- 如果未来合并到同一进程,将产生端口冲突
- 在 Docker Compose 中显式设置 `PORT` 可规避,但缺乏自文档化
- **建议**: 分别改为 `MEMORY_SERVICE_PORT``TOOL_ENGINE_PORT``VOICE_SERVICE_PORT`
### 2.3 🔴 严重发现:Tool-Engine 使用不一致的变量名
[`tool-engine/internal/config/config.go`](backend/tool-engine/internal/config/config.go:19) 读取 `IOT_SERVICE_URL`,而 [`.env.example`](backend/.env.example:50) 和 Gateway/AI-Core 均使用 `IOT_DEBUG_SERVICE_URL`。虽然 Docker Compose 中显式设置了 `IOT_SERVICE_URL`,但本地开发时如果只配置 `.env.example` 中的 `IOT_DEBUG_SERVICE_URL`Tool-Engine 将使用默认值 `http://localhost:8083`(恰好一致),但链路不透明。
### 2.4 🟡 默认值不一致汇总
| 变量 | .env.example 默认值 | 代码默认值 | 服务 |
|------|---------------------|-----------|------|
| `JWT_SECRET` | `your-secret-key-change-in-production` | `change-me-in-production` | Gateway |
| `ADMIN_PASSWORD` | `your-admin-password` | `cyrene-dev-admin` | Gateway |
| `REGISTRATION_ENABLED` | `true` | `false` | Gateway |
**影响**: 如果开发者复制 `.env.example``.env` 后未修改这些值,实际运行时的行为将与文档描述不一致。特别是 `REGISTRATION_ENABLED``.env.example``true`,但代码默认 `false`,如果用户不设置此变量,注册功能将静默关闭。
### 2.5 ✅ .env.example 覆盖良好的变量
以下变量在所有服务中一致且正确:
- `POSTGRES_HOST/PORT/USER/PASSWORD/DB` — 所有服务统一
- `LLM_API_URL/KEY/MODEL/FALLBACK_MODEL` — Gateway + AI-Core 统一
- `REDIS_HOST/PORT/PASSWORD` — Gateway 统一
- `IOT_DEBUG_SERVICE_URL` — .env.example + Gateway + AI-Core 统一 (仅 Tool-Engine 用不同名称)
- `ADMIN_NICKNAME` — .env.example + Gateway + AI-Core 统一
- `ENABLE_BACKGROUND_THINKING` — .env.example + AI-Core 统一
---
## 3. 项目文件完整性审计
### 3.1 go.work ✅
[`backend/go.work`](backend/go.work:1) 包含全部 6 个服务模块:
```
./ai-core
./gateway
./iot-debug-service
./memory-service
./tool-engine
./voice-service
```
### 3.2 Docker Compose 文件
#### docker-compose.yml (生产环境) ✅
包含全部 6 个后端服务 + Caddy + PostgreSQL + Redis + Qdrant + MinIO = **11 个服务**。所有服务间依赖通过 `depends_on` 正确定义。
#### docker-compose.dev.yml (开发环境) ✅
包含全部 6 个后端服务 + 全部基础设施 (PostgreSQL, Redis, Qdrant, MinIO, NATS) = **11 个服务**。Gateway 依赖所有其他后端服务启动后再启动。
#### docker-compose.dev.db.yml (仅基础设施) ✅
包含 5 个基础设施服务 (PostgreSQL, Redis, Qdrant, MinIO, NATS),设计意图为本地开发时仅启动数据库,后端服务在宿主机直接运行。容器名使用 `cyrene_` 前缀避免冲突。
### 3.3 devtools.sh ✅
[`devtools.sh`](devtools.sh:1) 功能正常:
- 自动加载 `backend/.env` 环境变量
- 端口冲突检测与释放 (`fuser -k`)
- 自动安装 npm 依赖
- 健康检查等待 (最多30秒)
- 彩色日志输出
### 3.4 Deploy.md 🟡 多处过时
[`Deploy.md`](Deploy.md:1) 存在以下与当前代码不一致的问题:
| 问题 | Deploy.md 描述 | 实际情况 |
|------|---------------|---------|
| Go 版本要求 | `Go 1.21+` | `go.work` 使用 `go 1.26.2` |
| 端口表缺失 | 仅列出 8080, 8081, 3001 | 缺少 8083, 8091, 8092, 8093 |
| DevTools 端口 | `3001` | `devtools.sh` 使用 `9090` |
| 服务状态描述 | memory-service/tool-engine/voice-service 标记为"规划中" | 三个服务已全部实现并正常运行 |
| 后端启动步骤 | 仅提及 ai-core 和 gateway | 缺少其他 4 个服务的启动说明 |
| Docker Compose 描述 | "启动 AI-Core 和 Gateway 后端服务" | `docker-compose.dev.yml` 实际启动全部 6 个服务 |
---
## 4. 服务间通信配置审计
### 4.1 Gateway → 子服务 URL 映射
| 目标服务 | Gateway 配置变量 | 默认 URL | 实际端口 | 状态 |
|----------|-----------------|---------|---------|:---:|
| AI-Core | `AI_CORE_URL` | `http://localhost:8081` | 8081 | ✅ |
| Memory-Service | `MEMORY_SERVICE_URL` | `http://localhost:8091` | 8091 | ✅ |
| IoT-Debug | `IOT_DEBUG_SERVICE_URL` | `http://localhost:8083` | 8083 | ✅ |
| Voice-Service | `VOICE_SERVICE_URL` | `http://localhost:8093` | 8093 | ✅ |
| Tool-Engine | `TOOL_ENGINE_URL` | `http://localhost:8092` | 8092 | ✅ |
所有 URL 配置一致,端口与实际监听端口匹配。
### 4.2 AI-Core → 子服务
| 目标服务 | 变量名 | 默认 URL | 状态 |
|----------|--------|---------|:---:|
| IoT-Debug | `IOT_DEBUG_SERVICE_URL` | `""` | ✅ (空值时禁用 IoT 功能) |
| Memory-Service | `MEMORY_SERVICE_URL` | `http://localhost:8091` | ✅ |
### 4.3 Tool-Engine → IoT
| 目标服务 | 变量名 | 默认 URL | 状态 |
|----------|--------|---------|:---:|
| IoT-Debug | `IOT_SERVICE_URL` | `http://localhost:8083` | ⚠️ 变量名不一致 (见 2.3) |
### 4.4 内部服务认证
[`gateway/internal/config/config.go`](backend/gateway/internal/config/config.go:70) 定义了 `INTERNAL_SERVICE_TOKEN`,默认值为 `"cyrene-internal-token-change-me"`。此变量:
- ❌ 未在 `.env.example` 中定义
- ✅ 在 [`router.go`](backend/gateway/internal/router/router.go:204) 中通过 `InternalNotifyAuth()` 中间件使用
- ⚠️ 生产环境部署时必须通过环境变量覆盖
### 4.5 各服务监听地址
所有服务均使用 `:{PORT}` 格式监听(即 `0.0.0.0:{PORT}`),无 `localhost` 绑定限制。这在 Docker 环境中正确(容器需要绑定 0.0.0.0),本地开发也无问题。
---
## 5. 第9轮新发现问题汇总
| ID | 严重级别 | 类别 | 问题描述 | 位置 |
|----|---------|------|---------|------|
| CFG-001 | 🔴 Critical | 配置 | PORT 命名空间冲突:3个服务共用 `PORT` 变量 | memory-service, tool-engine, voice-service config |
| CFG-002 | 🔴 Critical | 配置 | Tool-Engine 使用 `IOT_SERVICE_URL` 而非 `IOT_DEBUG_SERVICE_URL` | [`tool-engine/internal/config/config.go:19`](backend/tool-engine/internal/config/config.go:19) |
| CFG-003 | 🟡 Medium | 配置 | `JWT_SECRET` 默认值不一致 (.env.example vs 代码) | [`gateway/internal/config/config.go:92`](backend/gateway/internal/config/config.go:92) |
| CFG-004 | 🟡 Medium | 配置 | `ADMIN_PASSWORD` 默认值不一致 | [`gateway/internal/config/config.go:97`](backend/gateway/internal/config/config.go:97) |
| CFG-005 | 🟡 Medium | 配置 | `REGISTRATION_ENABLED` 默认值不一致 (.env.example=true vs 代码=false) | [`gateway/internal/config/config.go:101`](backend/gateway/internal/config/config.go:101) |
| CFG-006 | 🟡 Medium | 配置 | `.env.example` 缺少 28+ 个服务所需环境变量 | [`backend/.env.example`](backend/.env.example:1) |
| CFG-007 | 🟡 Medium | 文档 | `Deploy.md` 多处过时 (端口表、服务状态、Go版本) | [`Deploy.md`](Deploy.md:1) |
| CFG-008 | 🟡 Medium | 安全 | `INTERNAL_SERVICE_TOKEN` 未在 .env.example 中定义 | [`gateway/internal/config/config.go:121`](backend/gateway/internal/config/config.go:121) |
| CFG-009 | 🟢 Low | 配置 | `POSTGRES_SSLMODE` 未在 .env.example 中定义 | ai-core, memory-service |
| CFG-010 | 🟢 Low | 配置 | Voice-Service TTS/ASR 配置读取方式不透明 | [`voice-service/internal/config/config.go`](backend/voice-service/internal/config/config.go:1) |
---
## 6. 前9轮综合汇总统计
### 6.1 各轮概况
| 轮次 | 日期 | 主题 | 发现问题 | 已修复 | 待修复 |
|------|------|------|:---:|:---:|:---:|
| 第1轮 | 2026-05-20 | 回归验证 | 3 (REG1-REG3) | 1 (REG1) | 2 (REG2, REG3) |
| 第2轮 | 2026-05-20 | 认证集成 | 0 (验证通过) | — | — |
| 第3轮 | 2026-05-20 | 面板前端 | 3 | 0 | 3 |
| 第4轮 | 2026-05-20 | 子服务数据库 | 0 (全部通过) | — | — |
| 第5轮 | 2026-05-20 | 安全边界审计 | 8 | 0 | 8 |
| 第6轮 | 2026-05-20 | 性能代码质量 | 2 (P3) | 0 | 2 |
| 第7轮 | 2026-05-20 | E2E跨服务 | 2 | 0 | 2 |
| 第8轮 | 2026-05-20 | Docker/PWA/WebSocket | 16 | 0 | 16 |
| **第9轮** | **2026-05-20** | **配置完整性审计** | **10** | **0** | **10** |
| **合计** | | | **44** | **1** | **43** |
### 6.2 按严重级别分类汇总
| 严重级别 | 总数 | 已修复 | 待修复 | 占比 |
|----------|:---:|:---:|:---:|:---:|
| 🔴 Critical (P0) | 6 | 1 | 5 | 14% |
| 🟡 Medium (P1/P2) | 21 | 0 | 21 | 48% |
| 🟢 Low (P3) | 17 | 0 | 17 | 38% |
| **合计** | **44** | **1** | **43** | **100%** |
### 6.3 待修复 Critical 问题清单
| ID | 轮次 | 问题 | 位置 |
|----|------|------|------|
| SEC-001 | R5 | 公开端点无限流 | [`router.go`](backend/gateway/internal/router/router.go:47) |
| SEC-002 | R5 | JWT 密钥明文硬编码默认值 | [`config.go`](backend/gateway/internal/config/config.go:92) |
| DKR-001 | R8 | Caddyfile 缺失 | 项目根目录 |
| CFG-001 | R9 | PORT 命名空间冲突 | 3个子服务 |
| CFG-002 | R9 | Tool-Engine IOT_SERVICE_URL 变量名不一致 | [`tool-engine/config.go`](backend/tool-engine/internal/config/config.go:19) |
### 6.4 各领域健康度评估
| 领域 | 评分 | 说明 |
|------|:---:|------|
| 编译与构建 | ⭐⭐⭐⭐⭐ | 所有服务 `go build` 通过,前端 Vite 构建成功 |
| 运行时稳定性 | ⭐⭐⭐⭐⭐ | 6/6 服务正常运行,无崩溃 |
| 核心功能完整性 | ⭐⭐⭐⭐☆ | 认证/会话/记忆/IoT/工具/语音/E2E 全部通过,缺会话标题更新 |
| 安全性 | ⭐⭐⭐☆☆ | 认证流程正确,但公开端点无限流、JWT默认密钥硬编码 |
| 配置管理 | ⭐⭐☆☆☆ | .env.example 覆盖率不足 50%,多处默认值不一致 |
| 文档完整性 | ⭐⭐⭐☆☆ | Deploy.md 多处过时,端口表不完整 |
| Docker 部署 | ⭐⭐⭐☆☆ | 核心服务可构建,但缺 Caddyfile、部分健康检查 |
| 性能 | ⭐⭐⭐⭐⭐ | 健康检查 <7ms50 并发仅 53ms |
| 代码质量 | ⭐⭐⭐⭐⭐ | `go vet` 全通过,goroutine 管理规范 |
| PWA | ⭐⭐⭐⭐☆ | Service Worker + 离线页面就绪,有重复注册问题 |
### 6.5 综合健康度评分
**总体评分: 78/100 (良好)**
-**优势**: 运行时稳定、性能优异、代码质量高、核心功能完整
- ⚠️ **短板**: 配置管理不统一、文档过时、安全加固不足
- 🔴 **风险**: Docker 生产部署缺少关键文件 (Caddyfile)
---
## 7. 最终健康检查
### 7.1 服务健康检查 (2026-05-20 15:34 CST)
| 服务 | 端口 | HTTP 状态 | 响应 |
|------|------|:---:|------|
| Gateway | 8080 | ✅ 200 | `{"service":"cyrene-gateway","status":"ok","ws_connections":0}` |
| AI-Core | 8081 | ✅ 200 | `{"status":"ok","service":"ai-core","model":"deepseek-v4-flash"}` |
| IoT-Debug | 8083 | ✅ 200 | `{"service":"iot-debug-service","status":"ok"}` |
| Memory-Service | 8091 | ✅ 200 | `{"status":"ok","service":"memory-service"}` |
| Tool-Engine | 8092 | ✅ 200 | `{"status":"ok","service":"tool-engine"}` |
| Voice-Service | 8093 | ✅ 200 | `{"service":"voice-service","status":"ok","stt":{"available":true,...}}` |
### 7.2 冒烟测试结果
| 测试 | 结果 | 说明 |
|------|:---:|------|
| Gateway 健康检查 | ✅ 200 | CORS 头正确设置 |
| IoT 设备列表 | ✅ 200 | 8 个模拟设备正常返回 |
| 记忆搜索 (无认证) | ✅ 401 | 认证保护正确工作 |
| 会话列表 (无认证) | ✅ 401 | 认证保护正确工作 |
| Chat 端点 (HTTP) | ✅ 404 | 预期行为 (通过 WebSocket 实现) |
### 7.3 CORS 安全头验证
```
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, Content-Type, Authorization, X-Request-ID
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 86400
```
---
## 8. 建议修复优先级
| 优先级 | 问题ID | 行动 | 预计工时 |
|--------|--------|------|:---:|
| 🔴 P0 | DKR-001 | 创建 `Caddyfile` | 30min |
| 🔴 P0 | CFG-001 | 重命名子服务 PORT 为独立变量名 | 1h |
| 🔴 P0 | CFG-002 | 统一 `IOT_SERVICE_URL``IOT_DEBUG_SERVICE_URL` | 15min |
| 🔴 P0 | SEC-001 | 为公开端点添加速率限制 | 30min |
| 🟡 P1 | CFG-003/004/005 | 统一 .env.example 与代码默认值 | 30min |
| 🟡 P1 | CFG-006 | 补全 .env.example 缺失的 28+ 变量 | 1h |
| 🟡 P1 | CFG-007 | 更新 Deploy.md | 1h |
| 🟡 P1 | CFG-008 | 在 .env.example 中添加 INTERNAL_SERVICE_TOKEN | 5min |
| 🟢 P2 | 其他 | 低优先级渐进修复 | 持续 |
---
## 9. 结论
第9轮配置与环境完整性审计发现 **10 个新问题** (2 Critical, 6 Medium, 2 Low)。主要发现集中在:
1. **配置管理碎片化**: `.env.example` 仅覆盖约 50% 的实际环境变量需求,且存在默认值不一致
2. **文档过时**: `Deploy.md` 端口表、服务状态描述、DevTools 端口号均已过时
3. **变量命名不一致**: Tool-Engine 使用 `IOT_SERVICE_URL` 而其他服务使用 `IOT_DEBUG_SERVICE_URL`
4. **PORT 命名冲突**: 3 个子服务共用 `PORT` 变量名
前 9 轮累计发现 **44 个问题**,已修复 **1 个**,待修复 **43 个**。项目整体健康度评分为 **78/100**,运行时稳定性和性能表现优秀,但配置管理和安全加固是当前最需改进的领域。
---
*报告生成时间: 2026-05-20 15:36 CST*
*审计工具: 静态代码分析 + 运行时健康检查 + 冒烟测试*
-225
View File
@@ -1,225 +0,0 @@
# 紧急诊断报告:页面崩溃 "Cannot read properties of undefined (reading 'cancel')"
**日期**: 2026-05-21
**严重级别**: 🔴 Critical — 页面持续崩溃,ErrorBoundary 捕获持久性错误
**诊断方法**: 源代码静态分析 + CDP (Chrome DevTools Protocol) 运行时分析
---
## 1. 错误信息
```
⚠️ 应用遇到错误: Cannot read properties of undefined (reading 'cancel')
```
用户报告:
- **初次触发**: 前端在初次对话回复到一半时页面崩溃
- **后续表现**: 刷新页面一直弹出 ErrorBoundary 错误页 + 重试按钮
- **特点**: 持久性错误 — 错误发生在页面初始化阶段
---
## 2. 根本原因分析
### 2.1 `.cancel()` 引用全局搜索
对整个 `frontend/web/src/` 目录进行 `.ts` / `.tsx` 文件搜索,**所有 `.cancel()` 调用仅存在于一个文件**
| 文件 | 行号 | 代码 |
|------|------|------|
| [`useSpeechSynthesis.ts`](frontend/web/src/hooks/useSpeechSynthesis.ts:205) | L205 | `window.speechSynthesis.cancel();``speak()` 回调内(有 `isSupported` 守卫) |
| [`useSpeechSynthesis.ts`](frontend/web/src/hooks/useSpeechSynthesis.ts:224) | L224 | `window.speechSynthesis.cancel();``stop()` 回调内(**无守卫** |
| [`useSpeechSynthesis.ts`](frontend/web/src/hooks/useSpeechSynthesis.ts:256) | L256 | `window.speechSynthesis.cancel();` — 组件卸载 cleanup 内(**无守卫** |
**项目不使用 Axios(无 `CancelToken`),也不使用 `AbortController`。** HTTP 客户端是原生 `fetch`(见 [`client.ts`](frontend/web/src/api/client.ts))。
### 2.2 CDP 运行时确认
通过 Chromium CDP (端口 9225) 在 `about:blank` 页面执行以下诊断:
```javascript
// CDP Runtime.evaluate 结果:
'speechSynthesis' in window true
typeof window.speechSynthesis "object"
window.speechSynthesis === undefined false
window.speechSynthesis === null false
!!window.speechSynthesis true
typeof window.speechSynthesis?.cancel "function"
```
同时确认了错误消息的精确匹配:
```javascript
// 直接测试:
var x = undefined; x.cancel();
// → TypeError: "Cannot read properties of undefined (reading 'cancel')"
```
**结论**: 错误消息 `"Cannot read properties of undefined (reading 'cancel')"` 确实是访问 `undefined.cancel` 导致的 `TypeError`。在当前 headless Chrome 中,`window.speechSynthesis` **存在且有 `.cancel` 方法**。但错误发生在特定运行时条件下。
### 2.3 调用链路分析
```
App.tsx (登录后渲染)
└── ChatContainer.tsx
└── MessageList.tsx
└── MessageBubble.tsx (每个 assistant 消息)
└── AIMessageActions(content) ← 调用 useSpeechSynthesis()
└── useSpeechSynthesis()
├── isSupported = 'speechSynthesis' in window // ✓ 有守卫
├── speak() → window.speechSynthesis.cancel() // ✓ isSupported 守卫
├── stop() → window.speechSynthesis.cancel() // ✗ 无守卫!
└── cleanup → window.speechSynthesis.cancel() // ✗ 无守卫!
```
关键代码对比([`useSpeechSynthesis.ts`](frontend/web/src/hooks/useSpeechSynthesis.ts):
```typescript
// ✓ speak() — 有 isSupported 守卫
const speak = useCallback((text, options) => {
if (!isSupported || !text.trim()) return; // <-- GUARD
window.speechSynthesis.cancel(); // 安全
// ...
}, [isSupported, speakNextChunk]);
// ✗ stop() — 无守卫
const stop = useCallback(() => {
window.speechSynthesis.cancel(); // 未检查 isSupported!
setIsSpeaking(false);
// ...
}, []);
// ✗ cleanup — 无守卫
useEffect(() => {
return () => {
window.speechSynthesis.cancel(); // 未检查 isSupported!
// ...
};
}, []);
```
### 2.4 为什么是「持久性」错误
错误在以下场景触发:
1. **初次对话崩溃**: 用户发送消息 → WebSocket 流式返回 → `MessageBubble` 渲染 assistant 消息 → `AIMessageActions` 挂载 → `useSpeechSynthesis()` 初始化 → 组件因某些原因卸载(React 重渲染、StrictMode 双重挂载、或流式更新导致的条件渲染变化) → **cleanup 执行 `window.speechSynthesis.cancel()`** → 如果此时 `speechSynthesis` 对象不可用 → TypeError → ErrorBoundary 捕获
2. **刷新后持续崩溃**: 用户已登录 → `initSession()` 从服务端加载历史消息 → 历史消息中包含 assistant 消息 → `MessageBubble` 渲染 → `AIMessageActions` 挂载 → **同样的初始化路径触发同样的错误** → ErrorBoundary 持续捕获 → 页面永远无法恢复
3. **为什么 `window.speechSynthesis` 可能为 `undefined`**:
- 尽管 CDP 测试显示它存在,但在某些特定的浏览器/环境组合下(如特定版本的 headless Chrome、无音频设备的服务器环境、或浏览器安全策略限制),`window.speechSynthesis` 可能是一个不完整的对象,或者在某些时序下(如页面刚加载、`onvoiceschanged` 未触发时)表现出意外行为
- React 18 的 `StrictMode`(在 `main.tsx` 第10行启用)会**双重挂载/卸载组件**,增加了 cleanup 被调用的次数和时序复杂度
### 2.5 辅助触发因素
`AIMessageActions` 内部还有一个额外的[无守卫调用](frontend/web/src/components/chat/MessageBubble.tsx:104-105):
```typescript
const checkEnd = setInterval(() => {
if (!window.speechSynthesis.speaking) { // 直接访问 speechSynthesis
setIsThisSpeaking(false);
clearInterval(checkEnd);
}
}, 200);
```
`setInterval` 没有清理机制 — 如果 `AIMessageActions``checkEnd` 回调执行时已卸载,`setIsThisSpeaking` 会触发 React 的 "setState on unmounted component" 警告。虽然这不是 `.cancel` 错误的直接原因,但同样是缺少防御性编程的症状。
---
## 3. Zustand Persist 检查
| Store | 文件 | 使用 persist? |
|-------|------|--------------|
| `authStore` | [`authStore.ts`](frontend/web/src/store/authStore.ts) | ❌ 纯 `create()` |
| `chatStore` | [`chatStore.ts`](frontend/web/src/store/chatStore.ts) | ❌ 纯 `create()` |
| `sessionStore` | [`sessionStore.ts`](frontend/web/src/store/sessionStore.ts) | ❌ 纯 `create()` |
| `notificationStore` | [`notificationStore.ts`](frontend/web/src/store/notificationStore.ts) | ❌ 纯 `create()` |
| `personaStore` | [`personaStore.ts`](frontend/web/src/store/personaStore.ts) | ❌ 纯 `create()` |
**所有 Zustand Store 均未使用 `persist` 中间件**,因此**不存在**序列化到 `localStorage` 导致 `CancelToken`/`AbortController` 实例丢失方法的问题。排除此假设。
---
## 4. 七种可能来源的排查
| # | 假说 | 验证结果 |
|---|------|----------|
| 1 | Axios CancelToken 序列化到 localStorage 后丢失 `.cancel` 方法 | ❌ 项目不使用 Axios |
| 2 | AbortController 被存入 Zustand store 序列化后丢失 | ❌ 项目不使用 AbortController |
| 3 | `window.speechSynthesis.cancel()``speechSynthesis` 不支持时调用 | ✅ **唯一 `.cancel` 来源** |
| 4 | 第三方库中有 `.cancel` 调用 | ❌ 依赖仅 react + zustand |
| 5 | Service Worker 中有 `.cancel` 调用 | ❌ SW 代码中无 `.cancel` |
| 6 | Zustand persist 中间件序列化问题 | ❌ 所有 store 不使用 persist |
| 7 | React StrictMode 双重卸载触发 cleanup 时序问题 | ⚠️ 可能是加剧因素 |
---
## 5. 修复建议
### 5.1 立即修复(推荐)
在 [`useSpeechSynthesis.ts`](frontend/web/src/hooks/useSpeechSynthesis.ts) 中添加防御性守卫:
**`stop()` 函数 (L223)**:
```typescript
const stop = useCallback(() => {
if (isSupported) {
window.speechSynthesis.cancel();
}
setIsSpeaking(false);
setIsPaused(false);
utteranceRef.current = null;
chunksRef.current = [];
chunkIndexRef.current = 0;
}, [isSupported]); // ← 添加 isSupported 到依赖数组
```
**cleanup effect (L254-261)**:
```typescript
useEffect(() => {
return () => {
if (isSupported) {
window.speechSynthesis.cancel();
}
if (resumeIntervalRef.current) {
clearInterval(resumeIntervalRef.current);
}
};
}, [isSupported]); // ← 添加 isSupported 到依赖数组
```
### 5.2 防御性增强(推荐)
在 [`MessageBubble.tsx`](frontend/web/src/components/chat/MessageBubble.tsx) 的 `AIMessageActions` 组件中,为 `checkEnd``setInterval` 添加清理:
```typescript
useEffect(() => {
return () => {
if (checkEndRef.current) {
clearInterval(checkEndRef.current);
}
};
}, []);
```
### 5.3 架构层建议
考虑将 `useSpeechSynthesis()` 调用提升到应用层级(如 `App.tsx``AppLayout.tsx`),而非在每个 `AIMessageActions` 中各自创建一个 hook 实例。这可以避免消息列表渲染时大量重复初始化 SpeechSynthesis 上下文。
---
## 6. 诊断工具与数据
- **CDP 测试脚本**: `debug/cache/test_cdp_cancel_error.py`
- **Chromium 调试端口**: `localhost:9225`
- **前端预览端口**: `localhost:5199`
---
## 7. 结论
**根因**: `useSpeechSynthesis` hook 中的 `stop()` 回调和组件卸载 cleanup effect 直接调用 `window.speechSynthesis.cancel()` **没有任何防御性守卫**。当 `window.speechSynthesis` 在特定运行时条件下不可用(值为 `undefined`)时,抛出 `TypeError: Cannot read properties of undefined (reading 'cancel')`
**持久性原因**: 已登录用户在刷新页面后,`initSession()` 加载历史消息,渲染 assistant 消息气泡时触发 `AIMessageActions``useSpeechSynthesis()`,在 React StrictMode 双重挂载/卸载周期中触发未守卫的 cleanup,导致错误在每次页面加载时必现。
**修复优先级**: 🔴 P0 — 阻塞所有已登录用户使用应用。
@@ -1,270 +0,0 @@
# 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`](backend/gateway/internal/middleware/auth.go:45) 检查: `userID == "admin"`
- 当前源码 [`auth_handler.go:149`](backend/gateway/internal/handler/auth_handler.go:149) 在 `authenticated=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`](backend/gateway/internal/router/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`](backend/gateway/internal/handler/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`](backend/gateway/internal/handler/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`](backend/gateway/internal/handler/session_handler.go)
| 行号 | 问题 | 严重度 |
|------|------|--------|
| Delete | **无所有权检查** — 任何认证用户可以删除任何会话 | 🔴 高 |
| 全局 | Session 操作失败返回 500 `db_error` 但无重试或降级到 Hub 内存 | 🟡 中 |
### 4.3 [`reminder_handler.go`](backend/gateway/internal/handler/reminder_handler.go)
| 行号 | 问题 | 严重度 |
|------|------|--------|
| List | `user_id` 查询参数为必填,不 fallback 到 JWT 中间件中的 `GetUserID(c)` | 🟡 中 |
### 4.4 [`auth.go`](backend/gateway/internal/middleware/auth.go)
| 行号 | 问题 | 严重度 |
|------|------|--------|
| 45 | `IsAdminKey` 设置为 `userID == "admin"` — 但 Login 返回的 admin userID 是 `"user_admin"` | 🔴 高 |
### 4.5 [`router.go`](backend/gateway/internal/router/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`](backend/gateway/internal/handler/auth_handler.go:149) 确保 admin 用户返回 `userID = "admin"`,与中间件 [`auth.go:45`](backend/gateway/internal/middleware/auth.go:45) 一致。
### P1 — 高优先级
3. **为 Session Delete 添加所有权检查**
4. **确保数据库在启动时可用** (检查 PostgreSQL 连接状态)
5. **Register 绑定 min 改为 3** 与 regex 一致
### P2 — 中优先级
6. **Health 端点注册 HEAD handler** 或使用 `router.HEAD()`
7. **verifyUserPassword 返回不同错误** 以区分失败原因
8. **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天内的所有源码更改。
-201
View File
@@ -1,201 +0,0 @@
# Round 2 深度调试报告
**日期**: 2026-05-21
**类型**: 综合性深度调试 + 即时修复
**状态**: ✅ 完成
---
## 1. 执行摘要
本次 Round 2 深度调试通过 Chromium CDP 进行前端 E2E 测试 + 后端全面 API 验证,发现并修复了 **3 个问题**(1 个 P0 崩溃、1 个 P0 后端 PANIC、1 个测试工具缺陷),最终 **14/14 全部通过**
---
## 2. 发现的问题与修复
### 2.1 P0 — `useSpeechSynthesis.ts` `cancel()` 未守卫调用
**文件**: [`frontend/web/src/hooks/useSpeechSynthesis.ts`](../../frontend/web/src/hooks/useSpeechSynthesis.ts)
**问题**: 参考 `docs/debug/2026-05-21-crash-cancel.md` 的详细分析。
- `stop()` 回调 (原 L223):仅当 `utteranceRef.current` 存在时才调用 `cancel()`,但浏览器 Web Speech API 可能在任意时刻有活跃 utterance,导致 `cancel()` 被跳过
- Cleanup effect (原 L261):同样的问题,组件卸载时可能跳过 `cancel()`
**修复**: 将守卫条件从 `utteranceRef.current` 改为 `isSupported`
```typescript
// stop() — 当 isSupported 为 true 时始终调用 cancel()
const stop = useCallback(() => {
if (!isSupported) {
console.warn('[useSpeechSynthesis] stop: speechSynthesis not supported');
return;
}
window.speechSynthesis.cancel();
// ... 重置状态
}, [isSupported]);
// cleanup effect — 同样始终调用 cancel(),并清理 resumeIntervalRef
useEffect(() => {
return () => {
if (isSupported) {
window.speechSynthesis.cancel();
}
if (resumeIntervalRef.current) {
clearInterval(resumeIntervalRef.current);
resumeIntervalRef.current = null;
}
};
}, [isSupported]);
```
**状态**: ✅ 已修复
---
### 2.2 P0 — IoT 子会话 `nil pointer dereference` PANIC
**文件**: [`backend/ai-core/internal/subsession/iot_provider.go`](../../backend/ai-core/internal/subsession/iot_provider.go)
**根因**:
1. [`iot_provider.go:116`](../../backend/ai-core/internal/subsession/iot_provider.go:116): `persona.NewLoader("")` 传入空字符串
2. [`persona/loader.go:24-27`](../../backend/ai-core/internal/persona/loader.go:24): `os.ReadDir("")` 失败,返回 `nil, error`
3. [`iot_provider.go:118`](../../backend/ai-core/internal/subsession/iot_provider.go:118): 仅 `log.Printf` 错误,未检查 `loader` 是否为 nil
4. [`iot_provider.go:120`](../../backend/ai-core/internal/subsession/iot_provider.go:120): `loader.Get("cyrene")` 对 nil 解引用 → **PANIC**
**崩溃日志**:
```
[iot-provider] 加载人格配置失败: 读取人格目录失败: open : no such file or directory
[subsession] dispatch goroutine panic 恢复 (type=iot): runtime error: invalid memory address or nil pointer dereference
```
**修复**: 添加 `loader != nil` 守卫,优雅降级使用默认值:
```go
// 加载人格配置
trueName := "昔涟"
loader, err := persona.NewLoader("")
if err != nil {
log.Printf("[iot-provider] 加载人格配置失败: %v", err)
}
if loader != nil {
if personaConfig, err := loader.Get("cyrene"); err == nil && personaConfig != nil {
trueName = personaConfig.Identity.TrueName
}
}
```
**编译与部署**: 重新编译 `ai-core` (`go build`)kill 旧进程 (PID 12870),启动新进程 (PID 22942)。
**状态**: ✅ 已修复并验证
---
### 2.3 测试工具 — CDP 脚本按钮匹配错误
**文件**: [`debug/cache/test_cdp_e2e_v4.py`](../../debug/cache/test_cdp_e2e_v4.py)
**问题**: 测试脚本查找 `btn.textContent.includes('登录')` 的按钮,但页面上有两个包含"登录"的按钮:
- 模式切换按钮 `"登录"` → 调用 `switchMode('login')`(不触发表单提交)
- 真正的提交按钮 `"进入昔涟的世界 ♪"` → 调用 `handleLogin()`
**修复**: 改为匹配提交按钮的特征文字 `"进入"``"昔涟"`
**状态**: ✅ 已修复并验证
---
## 3. 验证结果汇总
### 3.1 CDP E2E v4 测试 (14/14 全部通过)
| # | 检查项 | 状态 |
|---|--------|------|
| 1 | 页面无 JS 异常 | ✅ |
| 2 | 无硬编码 `test-token-cyrene` | ✅ |
| 3 | 找到用户名输入框 | ✅ |
| 4 | 找到密码输入框 | ✅ |
| 5 | 找到登录按钮 (`"进入昔涟的世界 ♪"`) | ✅ |
| 6 | 无认证错误 (API 调用 8 次) | ✅ |
| 7 | localStorage 有 token | ✅ |
| 8 | 侧边栏或聊天区域存在 | ✅ |
| 9 | 会话列表 | ✅ |
| 10 | 创建新会话 | ✅ |
| 11 | 获取消息历史 | ✅ |
| 12 | 记忆列表 | ✅ |
| 13 | WebSocket 连接 | ✅ |
| 14 | 收到 IoT 响应 | ✅ |
### 3.2 后端 API 深度验证
| API | 方法 | 状态 | 备注 |
|-----|------|------|------|
| `/api/v1/health` | GET | ✅ 200 | 5 服务全部健康 |
| `/api/v1/auth/login` | POST | ✅ 200 | 返回 `token`, `user_id`, `expires` |
| `/api/v1/auth/refresh` | POST | ✅ 200 | 返回新 token |
| `/api/v1/sessions?user_id=admin` | GET | ✅ 200 | `{"sessions": [...]}` |
| `/api/v1/sessions` | POST | ✅ 201 | 创建会话成功 |
| `/api/v1/sessions/{id}/messages` | GET | ✅ 200 | 历史消息正常 |
| `/api/v1/memory` | GET | ✅ 200 | 记忆列表正常 |
| `/api/v1/memory/search?query=IoT` | GET | ⚠️ 400 | 参数格式问题(非阻塞) |
| `/ws/chat` | WS | ✅ 101 | 消息流正常 |
### 3.3 WebSocket + IoT 流验证
- **WebSocket 连接**: ✅ 正常建立
- **设备状态广播**: ✅ 每 10 秒推送 8 个设备状态
- **IoT 查询消息流**: ✅ 完整流程:`device_update``stream_chunk``response`
- **IoT 子会话**: ✅ 不再 PANIC,优雅降级
- **后台思考**: ✅ `post_chat` 触发正常
### 3.4 服务运行时状态
| 服务 | 端口 | PID | 状态 |
|------|------|-----|------|
| gateway | 8080 | 12874 | ✅ |
| ai-core | 8081 | 22942 | ✅ (已更新) |
| memory-service | 8091 | 12864 | ✅ |
| tool-engine | 8092 | 12868 | ✅ |
| iot-debug-service | 8083 | 12866 | ✅ |
| vite preview | 5199 | 1266 | ✅ |
| chromium CDP | 9225 | 4741 | ✅ |
---
## 4. 已知遗留问题
### 4.1 低优先级 — Memory Search 400
`GET /api/v1/memory/search?query=IoT` 返回 400。需要在 `memory_handler.go` 中确认查询参数名是否正确(可能是 `q` 而非 `query`)。
### 4.2 低优先级 — `MessageBubble.tsx` `setInterval` 无清理
[`MessageBubble.tsx:105-110`](../../frontend/web/src/components/chat/MessageBubble.tsx) 中 `AIMessageActions``checkEnd` interval 没有在组件卸载时清理,可能导致内存泄漏。
### 4.3 低优先级 — IoT 子会话未匹配到"列出所有设备"
`iot_provider.go``matchIotOperation()` 函数未匹配 `"列出所有IoT设备"` 关键词。该消息被降级到 General 子会话处理,虽不影响功能但无法触发 IoT 专用响应。
### 4.4 信息 — 登录响应无 `refresh_token` 字段
后端 [`auth_handler.go:208`](../../backend/gateway/internal/handler/auth_handler.go:208) 登录响应仅返回 `token`, `user_id`, `expires`,前端 [`client.ts:80`](../../frontend/web/src/api/client.ts:80) `getRefreshToken()` 读取 `localStorage.getItem('refresh_token')` 始终为 null。
---
## 5. 修改文件清单
| 文件 | 变更类型 | 描述 |
|------|----------|------|
| `frontend/web/src/hooks/useSpeechSynthesis.ts` | 🐛 修复 | P0: cancel() 守卫条件改为 isSupported |
| `backend/ai-core/internal/subsession/iot_provider.go` | 🐛 修复 | P0: 添加 loader nil 检查,防止 PANIC |
| `backend/ai-core/cmd/ai-core` | 🔄 重新编译 | 包含上述修复 |
| `debug/cache/test_cdp_e2e_v4.py` | 🐛 修复 | 按钮匹配逻辑改为匹配提交按钮 |
| `debug/cache/test_cdp_token_investigation.py` | 新增 | test-token-cyrene 来源诊断脚本 |
| `debug/cache/test_cdp_e2e_v3.py` | ➕ 新增 | E2E v3 测试(发现 API 格式差异) |
---
## 6. 结论
Round 2 深度调试成功完成。发现了 **2 个 P0 级别缺陷**useSpeechSynthesis cancel 守卫缺失、IoT 子会话 nil pointer PANIC)和 **1 个测试工具缺陷**(按钮匹配错误),全部已修复并验证通过。系统各组件(gateway、ai-core、memory-service、tool-engine、iot-debug-service)协同工作正常,WebSocket 消息流、IoT 设备广播、记忆存储链路完整。
-250
View File
@@ -1,250 +0,0 @@
# Cyrene 系统修复最终报告 — 2026-05-21~22
> **报告日期**2026-05-22
> **覆盖周期**2026-05-21 ~ 2026-05-22 (UTC+8)
> **分支**`dev`
> **总计轮次**3 轮
> **总计 Commit 数**3 个
> **总计涉及文件数**:约 47 个
---
## 一、修复总览表
| 轮次 | Commit | 修复项 | 涉及文件数 |
|------|--------|--------|-----------|
| 第一轮 | `a058b0a` | 5 项功能修复 + 5 项安全配置修复 | ~35 |
| 第二轮 | `b15e1c9` | 2 个 P0 修复 + E2E 测试 v2-v4 | 7 |
| 第三轮 | `e78d0b2` | E2E v5 测试脚本 + IoT 字段兼容 | 5 |
### 第一轮:功能修复 + 安全配置 (Commit: `a058b0a`)
| # | 类别 | 问题 | 涉及核心文件 |
|---|------|------|-------------|
| 1 | 🔴 功能 | 记忆管理功能 — 数据库连接不可用 | [`orchestrator.go`](backend/ai-core/internal/orchestrator/orchestrator.go), [`store.go`](backend/ai-core/internal/memory/store.go) |
| 2 | 🔴 功能 | IoT 工具操控 — 子会话无响应 | [`iot_client.go`](backend/ai-core/internal/tools/iot_client.go), [`iot_provider.go`](backend/ai-core/internal/subsession/iot_provider.go), [`iot_client.go`](backend/tool-engine/internal/tools/iot_client.go) |
| 3 | 🟡 功能 | 前端历史消息持久化 | [`session_store.go`](backend/gateway/internal/store/session_store.go), [`chat_handler.go`](backend/gateway/internal/handler/chat_handler.go), [`session_handler.go`](backend/gateway/internal/handler/session_handler.go), 前端 5 个文件 |
| 4 | 🟡 功能 | 动作消息 + 最终审查子会话 + 消息拆分 | [`review_provider.go`](backend/ai-core/internal/subsession/review_provider.go), [`sub_session.go`](backend/ai-core/internal/model/sub_session.go), 前端 3 个文件 |
| 5 | 🟢 功能 | 对话链路速度优化 | [`orchestrator.go`](backend/ai-core/internal/orchestrator/orchestrator.go) |
| 6 | 🔴 安全 | JWT 密钥环境变量化 | [`config.go`](backend/gateway/internal/config/config.go) |
| 7 | 🔴 安全 | Token 自动刷新 | [`client.ts`](frontend/web/src/api/client.ts), [`auth_handler.go`](backend/gateway/internal/handler/auth_handler.go), [`authStore.ts`](frontend/web/src/store/authStore.ts) |
| 8 | 🟡 安全 | WebSocket 指数退避重连 | [`useWebSocket.ts`](frontend/web/src/hooks/useWebSocket.ts) |
| 9 | 🟡 安全 | localStorage 清理一致性 | [`authStore.ts`](frontend/web/src/store/authStore.ts), [`useAuth.ts`](frontend/web/src/hooks/useAuth.ts) |
| 10 | 🟢 安全 | IoT 环境变量命名统一 | [`iot_client.go`](backend/ai-core/internal/tools/iot_client.go), [`config.js`](devtools/src/config.js), [`config.go`](backend/tool-engine/internal/config/config.go), [`index.js`](devtools/src/index.js) |
### 第二轮:深度调试 + P0 修复 (Commit: `b15e1c9`)
| # | 类别 | 问题 | 涉及核心文件 |
|---|------|------|-------------|
| 1 | 🔴 P0 | `useSpeechSynthesis.ts` `cancel()` 未守卫调用 | [`useSpeechSynthesis.ts`](frontend/web/src/hooks/useSpeechSynthesis.ts) |
| 2 | 🔴 P0 | IoT 子会话 `nil pointer dereference` PANIC | [`iot_provider.go`](backend/ai-core/internal/subsession/iot_provider.go), [`loader.go`](backend/ai-core/internal/persona/loader.go) |
| 3 | 🟡 工具 | CDP 脚本按钮匹配错误 | [`test_cdp_e2e_v4.py`](debug/cache/test_cdp_e2e_v4.py) |
### 第三轮:E2E v5 综合验证 (Commit: `e78d0b2`)
| # | 类别 | 内容 | 涉及文件 |
|---|------|------|---------|
| 1 | 测试 | E2E v5 24 项综合测试脚本 | [`test_cdp_e2e_v5.py`](debug/cache/test_cdp_e2e_v5.py) |
| 2 | 修复 | IoT 字段兼容 (status vs state) | [`test_cdp_e2e_v5.py`](debug/cache/test_cdp_e2e_v5.py) |
---
## 二、原始问题修复验证
对用户原始提出的 5 个问题进行逐一验证:
| # | 原始问题 | 修复状态 | 验证方式 |
|---|---------|---------|----------|
| 1 | 记忆管理功能数据库连接不可用 | ✅ 已修复 | API 验证:`GET /api/v1/memory?user_id=admin` 返回正常数据;E2E v5 Part D 记忆查询场景通过 |
| 2 | IoT 工具操控无响应 | ✅ 已修复 | E2E v5 Part C:IoT 设备状态变更已确认 (客厅灯 state=on)E2E v5 Part H2WS IoT 响应正常;日志追踪已完善 |
| 3 | 前端历史消息持久化 | ✅ 已修复 | E2E v5 Part I`GET /api/v1/sessions/{id}/messages` 分页 API 正常返回 4 条消息;WS 路径消息已持久化到 PostgreSQL |
| 4 | 动作消息 + 消息拆分 + 审查子会话 | ✅ 已修复 | 后端 [`review_provider.go`](backend/ai-core/internal/subsession/review_provider.go) 已实现;前端 [`MessageBubble.tsx`](frontend/web/src/components/chat/MessageBubble.tsx) `ActionMessageBubble` 组件已实现;E2E v5 Part E 长对话场景断句验证通过 |
| 5 | 对话链路速度优化 | ✅ 已修复 | 非阻塞子会话分发 (goroutine+channel) + 快速问候通道已实现;E2E v5 Part B 问候场景快速通道响应 < 10 秒 |
---
## 三、安全配置修复验证
| # | 安全配置项 | 状态 | 验证细节 |
|---|-----------|------|---------|
| 1 | JWT 密钥环境变量化 | ✅ | 无硬编码默认值;未设置 `JWT_SECRET` 时 panic 阻止启动 |
| 2 | Token 自动刷新 | ✅ | 前端 Axios 401 拦截器 + `POST /api/auth/refresh` 接口已实现 |
| 3 | WebSocket 指数退避 | ✅ | 1s→2s→4s→8s→16s→30s (上限) + ±25% jitter;最大 10 次重试 |
| 4 | localStorage 清理一致性 | ✅ | `cyrene_` 前缀统一;logout 全量清理 + 版本检查机制 |
| 5 | IoT 环境变量命名统一 | ✅ | 全部使用 `IOT_SERVICE_URL`;向后兼容旧变量名 fallback |
---
## 四、E2E 测试结果汇总
| 测试版本 | 通过率 | 关键发现 |
|---------|--------|---------|
| v2 (CDP 基础) | 通过 | 基础 CDP 连接正常,页面可交互 |
| v3 (Token 调查) | 通过 | `test-token-cyrene` 硬编码问题已定位并解决 |
| v4 (14 项测试) | 14/14 ✅ | 发现 P0`cancel()` 守卫缺失 + IoT nil pointer PANIC;全部修复 |
| v5 (24 项测试) | 24/24 ✅ | IoT 字段兼容修复 (status/state)WS 消息持久化验证通过 |
### v5 详细测试结果
```
测试时间: 2026-05-22 12:10 UTC+8
通过: 24 | 失败: 0 | 总计: 24
```
| Part | 测试场景 | 测试项数 | 结果 |
|------|---------|---------|------|
| A | Backend API 初始验证 | 4 | ✅ 全部通过 |
| B | 简单问候 (快速通道) | 4 | ✅ 全部通过 |
| C | IoT 操作 (子会话链路) | 3 | ✅ 全部通过 |
| D | 记忆查询 | 2 | ✅ 全部通过 |
| E | 长对话 (动作消息渲染) | 3 | ✅ 全部通过 |
| F | 会话列表查询 | 1 | ✅ 全部通过 |
| G | CDP 浏览器测试 | 1 | ✅ (Chromium 未运行,跳过) |
| H | WebSocket 实时聊天测试 | 3 | ✅ 全部通过 |
| I | 消息持久化验证 | 2 | ✅ 全部通过 |
---
## 五、关键架构发现
### 5.1 AI-Core 直连 SSE 不持久化消息
用户通过前端直接调用 AI-Core 的 `/api/v1/chat` SSE 端点时,消息不经过 Gateway 的 [`chat_handler.go`](backend/gateway/internal/handler/chat_handler.go),因此不会触发 [`SaveMessage`](backend/gateway/internal/store/session_store.go) 持久化。只有通过 WebSocket 路径 (`/ws/chat`) 的消息才经过 Gateway 的 [`hub.go`](backend/gateway/internal/ws/hub.go) 存储到 PostgreSQL。
```
Frontend → WebSocket → Gateway (chat_handler.go → SaveMessage → PostgreSQL) ✅ 持久化
Frontend → HTTP SSE → AI-Core (直接返回) ❌ 不持久化
```
### 5.2 IoT 子会话关键词驱动
[`iot_provider.go`](backend/ai-core/internal/subsession/iot_provider.go) 中的 `matchIotOperation()` 函数不依赖 LLM 做 IoT 决策,而是通过关键词匹配 (如 "灯"、"开关"、"温度") + 设备状态获取来判断是否为 IoT 请求。这种方式响应快但覆盖度有限(如 "列出所有IoT设备" 未被匹配,降级到 General 子会话处理)。
### 5.3 非阻塞子会话分发
[`orchestrator.go`](backend/ai-core/internal/orchestrator/orchestrator.go) 使用 `goroutine` + `sync.WaitGroup` + 带缓冲 `channel` 实现子会话异步并发执行,避免串行等待。配合快速问候通道(意图分析阶段检测简单问候直接返回),显著降低了端到端响应延迟。
---
## 六、当前系统状态
### 6.1 服务运行状态
| 服务 | 端口 | 状态 | 备注 |
|------|------|------|------|
| gateway | 8080 | ✅ | WebSocket 连接正常,消息持久化正常 |
| ai-core | 8081 | ✅ | 非阻塞子会话分发,SSE 流式响应正常 |
| memory-service | 8091 | ✅ | 记忆 CRUD + 搜索可用 |
| tool-engine | 8092 | ✅ | IoT 工具调用链路正常 |
| iot-debug-service | 8083 | ✅ | 8 个模拟设备状态广播正常 |
| vite preview | 5199 | ✅ | 前端正常渲染,无 JS 异常 |
| chromium CDP | 9225 | ✅ | 按需启动用于 E2E 调试 |
### 6.2 核心 API 健康状态
| 端点 | 方法 | 状态 | 说明 |
|------|------|------|------|
| `/api/v1/health` | GET | ✅ 200 | 所有服务健康 |
| `/api/v1/auth/login` | POST | ✅ 200 | JWT 签发正常 |
| `/api/v1/auth/refresh` | POST | ✅ 200 | Token 刷新正常 |
| `/api/v1/sessions` | GET/POST | ✅ | 会话 CRUD 正常 |
| `/api/v1/sessions/{id}/messages` | GET | ✅ 200 | 分页消息历史正常 |
| `/api/v1/memory` | GET | ✅ 200 | 记忆查询正常 |
| `/ws/chat` | WS | ✅ 101 | WebSocket 消息流正常 |
| `/api/v1/chat` (ai-core) | POST | ✅ 200 | SSE 流式响应正常 |
---
## 七、后续建议
### 短期 (本周)
1. **SSE 直连路径的消息持久化** — 当前只有 WebSocket 路径持久化消息,建议在 AI-Core 的 SSE 流完成后通过回调通知 Gateway 保存消息。非阻塞问题,因前端默认使用 WebSocket。
2. **IoT 子会话改为 LLM 驱动决策** — 当前关键词驱动覆盖度有限(已知 "列出所有IoT设备" 未匹配),建议在意图分析阶段加入 LLM 判断是否涉及 IoT 操作。
3. **Memory Search 参数修复**`GET /api/v1/memory/search?query=IoT` 返回 400,需确认查询参数名 (`q` vs `query`)。
### 中期 (1-2 周)
4. **前端性能优化** — 消息列表使用虚拟滚动 (`@tanstack/react-virtual`) 支持大量历史消息;当前 [`MessageList.tsx`](frontend/web/src/components/chat/MessageList.tsx) 直接渲染所有消息。
5. **MessageBubble setInterval 清理** — [`MessageBubble.tsx:105-110`](frontend/web/src/components/chat/MessageBubble.tsx) 中 `AIMessageActions``checkEnd` interval 缺少组件卸载时清理,存在内存泄漏风险。
6. **登录响应补充 refresh_token** — 后端 [`auth_handler.go:208`](backend/gateway/internal/handler/auth_handler.go:208) 登录响应仅返回 `token`, `user_id`, `expires`,需补充 `refresh_token` 字段以完善 Token 刷新机制。
### 长期 (1 个月+)
7. **生产环境部署前的安全审计** — 全面审查 JWT 配置、CORS 策略、速率限制、输入校验等安全边界。
8. **单元测试和集成测试覆盖** — 当前仅有黑盒 E2E 测试,缺少 Go 单元测试和前端组件测试。
---
## 八、附录:完整修改文件清单
### 第一轮 (约 35 个文件)
```
M backend/ai-core/cmd/main.go
M backend/ai-core/internal/memory/store.go
M backend/ai-core/internal/model/sub_session.go
M backend/ai-core/internal/orchestrator/orchestrator.go
M backend/ai-core/internal/subsession/iot_provider.go
M backend/ai-core/internal/subsession/review_provider.go (+新增)
M backend/ai-core/internal/tools/iot_client.go
M backend/gateway/internal/config/config.go
M backend/gateway/internal/handler/auth_handler.go
M backend/gateway/internal/handler/chat_handler.go
M backend/gateway/internal/handler/session_handler.go
M backend/gateway/internal/router/router.go
M backend/gateway/internal/store/session_store.go
M backend/gateway/internal/ws/hub.go
M backend/tool-engine/internal/config/config.go
M backend/tool-engine/internal/tools/iot_client.go
M devtools/src/config.js
M devtools/src/index.js
M frontend/web/src/api/auth.ts
M frontend/web/src/api/client.ts
M frontend/web/src/api/sessions.ts
M frontend/web/src/components/chat/ChatContainer.tsx
M frontend/web/src/components/chat/MessageBubble.tsx
M frontend/web/src/components/chat/MessageList.tsx
M frontend/web/src/hooks/useAuth.ts
M frontend/web/src/hooks/useWebSocket.ts
M frontend/web/src/index.css
M frontend/web/src/store/authStore.ts
M frontend/web/src/store/chatStore.ts
M frontend/web/src/store/sessionStore.ts
M frontend/web/src/types/chat.ts
M frontend/web/src/types/session.ts
M backend/.env.example
M .gitignore
```
### 第二轮 (7 个文件)
```
M frontend/web/src/hooks/useSpeechSynthesis.ts (P0: cancel() 守卫修复)
M backend/ai-core/internal/subsession/iot_provider.go (P0: nil pointer PANIC 修复)
M backend/ai-core/cmd/ai-core (重新编译)
M debug/cache/test_cdp_e2e_v4.py (按钮匹配修复)
A debug/cache/test_cdp_token_investigation.py (新增: Token 来源诊断)
A debug/cache/test_cdp_e2e_v3.py (新增: E2E v3 测试)
```
### 第三轮 (5 个文件)
```
A debug/cache/test_cdp_e2e_v5.py (新增: E2E v5 24项综合测试)
A debug/logs/chromium/e2e_v5_results_20260522_121002.json (测试结果)
A debug/logs/chromium/e2e_v5_20260522_002005.png (截图)
A debug/logs/chromium/e2e_v5_results_20260522_002016.json (早期结果)
A debug/logs/chromium/e2e_v5_results_20260522_120238.json (中期结果)
```
---
> **报告生成时间**2026-05-22 12:14 UTC+8
> **生成工具**Architect 模式 — 综合 Round 1 & Round 2 文档 + E2E v5 测试结果
> **相关文档**[`2026-05-21-round1-fixes.md`](docs/debug/2026-05-21-round1-fixes.md)、[`2026-05-21-round2-fixes.md`](docs/debug/2026-05-21-round2-fixes.md)、[`2026-05-21-final-summary.md`](docs/debug/2026-05-21-final-summary.md)
@@ -1,170 +0,0 @@
# Cyrene 第四轮修复报告 — IoT 操控 + Review Pipeline + 速度优化
> **报告日期**2026-05-22
> **覆盖周期**2026-05-22 (UTC+8)
> **分支**`dev`
> **涉及文件数**9 个
> **E2E 测试**:全部通过
---
## 一、修复项总览
| # | 类别 | 问题 | 涉及核心文件 |
|---|------|------|-------------|
| 1 | 🔴 功能 | IoT 设备操控 — 多设备命令支持 + Persona 路径 | [`iot_provider.go`](backend/ai-core/internal/subsession/iot_provider.go), [`main.go`](backend/ai-core/cmd/main.go) |
| 2 | 🟡 功能 | Review Pipeline — 主会话输出解析为 action/chat 消息 | [`orchestrator.go`](backend/ai-core/internal/orchestrator/orchestrator.go), [`chat_handler.go`](backend/gateway/internal/handler/chat_handler.go) |
| 3 | 🟡 功能 | 前端 action 消息显示 + msgType 历史映射 | [`protocol.go`](backend/gateway/internal/ws/protocol.go), [`hub.go`](backend/gateway/internal/ws/hub.go), [`sessionStore.ts`](frontend/web/src/store/sessionStore.ts) |
| 4 | 🟢 优化 | 对话链路速度优化 — IoT 快速通道 + 简单消息跳过子会话 | [`intent_analyzer.go`](backend/ai-core/internal/orchestrator/intent_analyzer.go), [`orchestrator.go`](backend/ai-core/internal/orchestrator/orchestrator.go) |
| 5 | 🟢 优化 | Persona 注入 action 格式指令 | [`injector.go`](backend/ai-core/internal/persona/injector.go) |
---
## 二、详细修复说明
### 2.1 IoT 多设备命令支持 + Persona 路径修复
**问题**
- IoT 子会话 `Execute()` 在匹配到第一个设备后立即 `return`,导致"打开客厅灯和卧室灯"只执行第一个
- `NewIoTProvider` 未接收 `personaDir` 参数,persona 配置加载始终使用空路径
**修复**
- `iot_provider.go` — 完全重写 `Execute()` 函数:先收集所有设备-操作匹配对,再批量执行
- `iot_provider.go``IoTProvider` 结构体新增 `personaDir` 字段,`NewIoTProvider` 接收参数
- `main.go` — 注册 IoT provider 时传入 `personaDir`
**关键代码**`Execute` 多设备收集逻辑):
```go
type deviceAction struct {
dev tools.IoTDevice
operation string // "on" | "off" | "query"
}
var actions []deviceAction
for _, dev := range devices {
// 匹配设备名 + 上下文意图词
// 收集所有匹配的 action
}
// 批量执行所有 action,合并 summaries
```
### 2.2 Review Pipeline — 内联解析
**问题**:主会话 LLM 输出需要拆分为 action 消息(描述操作)+ chat 消息(对话文本),前端分别渲染
**修复**
- `orchestrator.go` — 新增 `parseReviewMessages()` 函数:无正则括号匹配状态机,支持 `()``()`
- `orchestrator.go` — 新增 `splitReviewLongMessage()` 函数:80 字符智能断句,在句子边界处分割
- `orchestrator.go` — 合成完成后自动调用审查,发送 `StreamReview` 事件
- `chat_handler.go` — SSE 解析 `review_messages` 字段,逐条发送 WebSocket responseaction 消息 200ms 间隔)
- `protocol.go` — 新增 `ReviewMessage` 结构体 + `ServerMessage.MsgType` 字段
- `hub.go``Message` 结构体新增 `MsgType` 字段
**消息格式约定**
- `(动作描述) 对话文本` → action 消息(角色 `action`,类型 `action`+ chat 消息(角色 `assistant`,类型 `chat`
- 括号支持半角 `()` 和全角 `()`
- 长 chat 消息自动在 80 字符处按句子边界拆分
### 2.3 前端 action 消息显示
**修复**
- `sessionStore.ts` — 历史消息加载时映射 `msg_type` 字段,`action` 类型消息使用 `action` 角色
### 2.4 对话速度优化
**问题**:每条消息都需要 2-3 秒 LLM 意图分析 + 所有子会话调度
**修复**
- `intent_analyzer.go` — 新增 `isStrongIoTCommand()`:控制词 + 设备词同时出现时跳过 LLM 分析,直接使用关键词规则(节省 2-3s)
- `intent_analyzer.go` — 简单问候的快速通道已存在(精确匹配 + ≤4 字符消息)
- `orchestrator.go` — 快速通道扩展:`primary=greeting``(primary=chat && !needsIoT && !needsMemory)` 时跳过所有子会话分派
- `orchestrator.go` — 子会话结果等待超时从 500ms 降至 200ms
### 2.5 Persona action 格式指令
**修复**
- `injector.go` — 在对话风格指令中新增:
```
- 执行操作时(开关设备、查询状态等),用括号包裹动作描述,后面跟自然对话。
例如:"(帮你把客厅灯关掉啦) 嗯,已经关好了~"
```
---
## 三、E2E 验证结果
### 测试场景 1:IoT 设备操控
```
输入:帮我把卧室空调打开
输出:
[ACTION] "指尖轻点,空调立刻嗡鸣启动"
[CHAT] "好啦,已经让卧室凉快下来啦~♪..."
后端日志:
[IoT-client] ✅ 切换设备成功: ac-bedroom
[iot-subsession] 执行操作: 打开 卧室空调 (ac-bedroom)
设备状态:ac-bedroom: off → on ✅
```
### 测试场景 2Review Pipeline 消息拆分
```
输入:帮我把客厅灯打开
输出:
[ACTION] "轻轻感应了一下忆庭对客厅灯的投影"
[CHAT] "嗯...叶酱,客厅灯现在是开着的哦?刚才是不是工作太忙,有点眼花啦♪"
后端日志:
[orchestrator] 审查完成: 2 条带类型消息 ✅
```
### 测试场景 3:简单问候快速通道
```
输入:你好呀
输出:
[CHAT] "诶嘿,叶酱今天主动跟人家打招呼了♪"
[ACTION] "笑着晃了晃食指"
[CHAT] "让我猜猜...是不是有好事要跟姐姐分享呀?"
后端日志:
[intent] 快速通道: 检测到简单问候,跳过 LLM 分析
[orchestrator] 快速通道: 简单消息(primary=greeting),跳过子会话分派
意图分析耗时: 0s ✅
```
### 测试场景 4:IoT 快速通道
```
输入:帮我把卧室空调打开
后端日志:
[intent] 快速通道: 检测到 IoT 操控命令,跳过 LLM 分析
意图分析耗时: 0s ✅
```
---
## 四、性能对比
| 场景 | 修复前 | 修复后 | 节省 |
|------|--------|--------|------|
| IoT 命令 | ~6s (LLM意图2-3s + 子会话 + 合成) | ~3.4s (跳过LLM意图) | ~2.6s |
| 简单问候 | ~5s (LLM意图 + 子会话) | ~3.9s (跳过LLM意图 + 跳过子会话) | ~1.1s |
| 一般聊天 | ~5s | ~4s (仅合成) | ~1s |
---
## 五、涉及文件清单
| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `backend/ai-core/cmd/main.go` | 修改 | 传递 personaDir 给 NewIoTProvider |
| `backend/ai-core/internal/orchestrator/intent_analyzer.go` | 修改 | 新增 isStrongIoTCommand 快速通道 |
| `backend/ai-core/internal/orchestrator/orchestrator.go` | 修改 | parseReviewMessages + 快速通道扩展 + 超时优化 |
| `backend/ai-core/internal/persona/injector.go` | 修改 | 对话风格新增 action 格式指令 |
| `backend/ai-core/internal/subsession/iot_provider.go` | 修改 | 多设备支持 + persona 路径修复 |
| `backend/gateway/internal/handler/chat_handler.go` | 修改 | SSE review_messages 解析 → WebSocket 转发 |
| `backend/gateway/internal/ws/protocol.go` | 修改 | 新增 ReviewMessage 结构体 + MsgType 字段 |
| `backend/gateway/internal/ws/hub.go` | 修改 | Message 结构体新增 MsgType 字段 |
| `frontend/web/src/store/sessionStore.ts` | 修改 | 历史消息 msg_type 映射 |
@@ -1,89 +0,0 @@
# Cyrene 第五轮修复报告 — IoT 边界情况 + 快速通道完善
> **报告日期**2026-05-22
> **分支**`dev`
> **涉及文件数**2 个
> **E2E 测试**:全部通过
---
## 一、修复项
| # | 类别 | 问题 | 涉及文件 |
|---|------|------|---------|
| 1 | 🟡 功能 | IoT 快速通道遗漏 "关掉"/"关上" 关键词 | [`intent_analyzer.go`](backend/ai-core/internal/orchestrator/intent_analyzer.go) |
| 2 | 🟡 功能 | IoT 多设备命令上下文窗口过小(±15字节) | [`iot_provider.go`](backend/ai-core/internal/subsession/iot_provider.go) |
---
## 二、详细说明
### 2.1 IoT 快速通道关键词补充
**问题**`isStrongIoTCommand()` 的控制词列表缺少 "关掉"、"关上",导致 "关掉客厅灯" 等常见命令无法命中快速通道,浪费 2-3s LLM 意图分析。
**修复**:在 `controlWords` 中新增 `"关掉"`, `"关上"`
```
修复前: controlWords := []string{"打开", "关闭", "调到", "设置", ...}
修复后: controlWords := []string{"打开", "关闭", "关掉", "关上", "调到", "设置", ...}
```
**验证**
```
"关掉客厅灯" → 修复前: LLM路径 5.12s → 修复后: 快速通道 2.57s (节省 2.55s)
```
### 2.2 IoT 多设备上下文窗口扩展
**问题**:多设备命令如 "帮我把卧室灯和卧室空调都关掉" 中,"卧室灯" 与 "关掉" 之间间隔超过 15 字节,导致操作判断错误(将 "off" 误判为 "query"),设备未被实际控制。
**根因分析**
```
msgLower = "帮我把卧室灯和卧室空调都关掉" (42 bytes)
"卧室灯" 起始偏移 = 9
contextEnd = 9 + 9 + 15 = 33
nearbyContext = msgLower[0:33] = "帮我把卧室灯和卧室空调" ← 缺少 "都关掉"!
```
**修复**
1. 上下文窗口从 ±15 扩展至 ±30 字节
2. 新增全文回退逻辑:附近上下文无法判断时,搜索整条消息
3. 调整判断逻辑为先计算 `hasOpen`/`hasClose` 布尔值,避免级联 if-else 误判
**验证**
```
"帮我把卧室灯和卧室空调都关掉" →
修复前: IoT 子会话完成: 卧室灯当前状态: on; 已帮你关闭卧室空调~ (灯仅查询,未关闭)
修复后: IoT 子会话完成: 卧室空调已经是关闭状态啦~; 已帮你关闭卧室灯~ (两个设备都正确匹配)
```
---
## 三、E2E 验证
| 场景 | 输入 | 预期 | 结果 |
|------|------|------|------|
| "关掉" 快速通道 | 关掉客厅灯 | fast path, <3s | 2.57s, fast path ✅ |
| 多设备打开 | 打开客厅灯和卧室灯 | 两设备均匹配 | 已帮你打开客厅灯♪; 卧室灯已经是打开状态啦~ ✅ |
| 多设备关闭 | 帮我把卧室灯和卧室空调都关掉 | 两设备均 match "off" | 卧室空调关闭 + 已帮你关闭卧室灯~ ✅ |
| 设备状态查询 | 看看家里设备状态怎么样 | 返回全部设备 | 家里设备状态:8 台设备 ✅ |
| 长消息拆分 | 讲星星故事 | action + 多段 chat | 1 action + 2 chat (69+16 chars at sentence break) ✅ |
| 审查消息格式 | 所有场景 | action/chat 分离 | msg_type 正确 ✅ |
---
## 四、构建流程修正
本次调试中发现 DevTools 构建产物名为 `main.exe` 而非 `ai-core.exe`。后续所有编译应通过 DevTools 的 `/api/services/:id/build` 接口执行,避免手动构建到错误文件名。
---
## 五、性能总结
| 场景 | Round 4 | Round 5 | 改善 |
|------|---------|---------|------|
| "打开" IoT 命令 | ~3.4s | ~2.6s | -0.8s |
| "关掉" IoT 命令 | ~6.2s (LLM路径) | ~2.6s (快速通道) | -3.6s |
| 多设备 IoT 命令 | 仅执行第一个 | 全部执行 | 功能修复 |
| 一般聊天 | ~3.9s | ~3.9s | 持平 |
@@ -1,105 +0,0 @@
# Cyrene 第五~六轮修复最终报告 — 全系统 E2E 验证
> **报告日期**2026-05-22 23:00 (UTC+8)
> **分支**`dev`
> **涉及 Commit**`a67b95c` (Round 4) + `498bf0d` (Round 5)
> **涉及文件数**:11 个修改 + 3 个新增 docs + 7 个新增 tests
---
## 一、原始 5 问题最终验证
| # | 原始问题 | 状态 | 验证结果 |
|---|---------|------|---------|
| 1 | IoT 工具操控无响应 | ✅ 已验证 | 设备 toggle 成功(ac-bedroom off→on→off),IoT-client 日志确认;多设备命令批量执行 |
| 2 | 审查子会话 + 动作消息拆分 | ✅ 已验证 | `parseReviewMessages()` 正确解析 `(动作) 聊天` 格式;WebSocket 返回 `msg_type: "action"/"chat"` |
| 3 | 前端 action 消息显示 | ✅ 已验证 | `MessageBubble.tsx` 支持 `role: "action"` + `msgType: "action"``ActionMessageBubble` 组件渲染 |
| 4 | 对话链路速度优化 | ✅ 已验证 | 问候快速通道 0s 意图分析;IoT 快速通道 0s 意图分析;响应时间 2.6-3.9s |
| 5 | 文档生成 + 仓库推送 | ✅ 已完成 | 2 轮完整文档 + 2 次 push |
---
## 二、本轮核心修复清单
### Round 4: IoT 多设备 + Review Pipeline (Commit: `a67b95c`)
- IoT Provider: `Execute()` 重写为多设备收集→批量执行模式
- IoT Provider: Persona 路径通过构造函数传入(修复空路径错误)
- Intent Analyzer: `isStrongIoTCommand()` 快速通道(节省 2-3s LLM 分析)
- Orchestrator: 快速通道扩展(greeting/chat 无 IoT 无 Memory 跳过子会话)
- Orchestrator: `parseReviewMessages()` 内联审查(括号匹配状态机,无正则)
- Orchestrator: `splitReviewLongMessage()` 80 字符智能断句
- Gateway: SSE `review_messages` 字段解析 → WebSocket `response` 消息逐条转发
- Gateway Protocol: 新增 `ReviewMessage` 结构体 + `MsgType` 字段
- Persona: 对话风格注入 action 格式指令
- Frontend: `sessionStore.ts` 历史消息 `msg_type` 映射
### Round 5: IoT 边界情况修复 (Commit: `498bf0d`)
- Intent Analyzer: `controlWords` 新增 "关掉"、"关上"(修复快速通道漏检)
- IoT Provider: 上下文窗口 ±15→±30 字节 + 全文回退逻辑
- IoT Provider: 操作检测重构为 `hasOpen`/`hasClose` 布尔值判断
---
## 三、性能数据
| 场景 | 响应时间 | 意图分析 | 子会话 |
|------|---------|---------|--------|
| 简单问候 "你好呀" | ~3.9s | 0s (快速通道) | 跳过 |
| IoT 命令 "打开客厅灯" | ~2.6s | 0s (快速通道) | IoT + Memory + General |
| IoT 命令 "关掉客厅灯" | ~2.6s | 0s (快速通道) | IoT + Memory + General |
| 多设备 "打开卧室灯和卧室空调" | ~3.0s | 0s (快速通道) | IoT + Memory + General |
| IoT 查询 "看看设备状态" | ~5.3s | 1.4s (LLM) | IoT + Memory + General |
| 故事/记忆触发 | ~4-5s | LLM 路径 | Memory + General |
---
## 四、E2E 消息流验证
```
用户: "帮我把客厅灯打开"
↓ Gateway WS → AI-Core SSE
↓ Intent: iot_control (快速通道, 0s)
↓ Dispatch: memory + general + iot + review (并行)
↓ IoT: 查询 8 个设备 → 匹配客厅灯 → 已打开
↓ Synthesize: 综合上下文 → LLM 生成 "(歪着头看你) 叶酱,客厅灯早就开着啦♪..."
↓ parseReviewMessages:
action: "歪着头看你"
chat: "叶酱,客厅灯早就开着啦♪ 你是不是工作太累看花了眼呀?"
↓ Gateway: 200ms 间隔逐条发送 WebSocket response
↓ Frontend: ActionMessageBubble + MessageBubble 分别渲染
```
---
## 五、服务健康状态
| 服务 | 端口 | 状态 | 正常运行时间 |
|------|------|------|------------|
| Gateway | 8080 | ✅ running | >2h |
| AI-Core | 8081 | ✅ running | >2h |
| IoT Debug | 8083 | ✅ running | >8h |
| Memory | 8091 | ✅ running | >8h |
| Tool Engine | 8092 | ✅ running | >8h |
| Voice | 8093 | ✅ running | >8h |
| Frontend | 5173 | ✅ running | >8h |
| DevTools | 9090 | ✅ running | >8h |
无错误日志、无 panic、无内存泄漏。
---
## 六、已知限制
1. **LLM 合成延迟**LLM 调用(deepseek-v4-flash)为主要延迟来源,约 3-4s(取决于网络和模型负载)。意图分析已最大化使用快速通道,合成阶段无法绕过 LLM
2. **"开" 字歧义**:无法将单独的 "开" 加入快速通道("开心"/"开始" 等会产生误判),短命令 "开灯" 仍走 LLM 意图分析
3. **Node.js v24 WebSocket bug**Windows 下频繁建立原生 WebSocket 连接可能触发 libuv UV_HANDLE_CLOSING 断言,需使用 `ws` 包或降低连接频率
4. **msg_type 数据库持久化**messages 表暂未添加 `msg_type` 列,通过 `role` 字段区分 action/chatrole="action" 表示动作消息),功能正常但不完美
---
## 七、下一步建议
1. 数据库迁移:为 messages 表添加 `msg_type` 列,完整保留消息类型
2. LLM 缓存:对相似问候/常见 IoT 命令引入响应缓存
3. 流式审查:在 LLM 合成过程中实时识别并分段发送 action/chat(目前需等合成完成)
4. 前端集成测试:使用 Playwright/CDP 端到端测试前端渲染
@@ -1,219 +0,0 @@
# Phase 2: 人格与交互深化 — 开发报告
> **报告日期**2026-05-23
> **分支**`dev`
> **阶段**:Phase 2 — 人格与交互深化
> **总计修改文件数**:16 个 (新增 3 个, 修改 13 个)
> **编译状态**ai-core ✅ / gateway ✅
---
## 一、背景
Phase 1 完成了基础设施层 (ThinkChain 连续性、AutonomousToolPolicy 工具安全、MessageScheduler 自适应节奏、SessionEnrichmentStore 渐进式丰富)。Phase 2 在此之上深化昔涟的人格表现和交互质量,包含三个子任务:
| 任务 | 编号 | 目标 |
|------|------|------|
| 情感状态机 | 2.4 | 运行时心情追踪,基于对话情绪、时间和事件自动转换 |
| 主动消息决策增强 | 2.6 | 多维度推送评估 (静默时段、紧急程度、频率限制、内容校验) |
| 离线自主思考 | 2.3 | 在线状态感知、离线行为调整、重连问候 |
---
## 二、情感状态机 (Task 2.4)
### 2.1 新增文件
#### `backend/ai-core/internal/persona/emotion_state.go`
核心类型:
```
EmotionState — 当前心情、强度(0.0-1.0)、主导情绪、情绪计数、历史记录
MoodTransition — 心情转换记录 (From → To, Reason, Timestamp)
EmotionTracker — 单用户情感追踪器,线程安全
```
**5 种心情** (来自 `cyrene_persona.yaml``mood_system`)
| 心情 | 触发条件 | YAML 表达示例 |
|------|---------|-------------|
| `happy` | 积极情绪积累 ≥3 次 | "今天和你聊得很开心呢,心情像星海一样明朗♪" |
| `thoughtful` | 初始默认 / 长时间沉默 / 情绪消退 | "让我想想……这片记忆之海里,有没有什么能帮到你的呢?" |
| `worried` | 消极情绪积累 ≥3 次 | "开拓者……你是不是有心事?不想说也没关系,人家会一直在这里陪着你。" |
| `playful` | 高强度 (>0.6) 积极情绪积累 | "嘻嘻,想逗你一下而已啦!看到你笑了,人家就开心了♪" |
| `nostalgic` | 触发回忆事件 | "啊……这让我想起很久很久以前的一件事……" |
**转换逻辑**
```
RecordSentiment(sentiment) → 累加计数器
├─ positive ≥ 3 且 intensity ≤ 0.6 → happy
├─ positive ≥ 3 且 intensity > 0.6 → playful
└─ negative ≥ 3 → worried
UpdateMood(trigger) → 显式触发
├─ "user_returned" → happy
├─ "long_silence" → thoughtful
└─ "nostalgic_trigger" → nostalgic
Decay() → 时间衰减
└─ 每小时 -0.1 intensity, < 0.2 时回归 thoughtful 基线
```
### 2.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `persona/injector.go` | 新增 `BuildSystemPromptWithMood(userName, affectionLevel, mood, expression)` — 在"当前情况"段注入心情行 `- 你现在的心情: happy (今天和你聊得很开心呢…)` |
| `orchestrator/orchestrator.go` | 新增 `emotionTracker` 字段 + `SetEmotionTracker`;意图分析后调用 `RecordSentiment(intent.Sentiment)`;构建 prompt 时传入当前心情 |
| `background/think_chain.go` | `ThinkRecord` 新增 `Mood string` + `MoodIntensity float64` 字段 |
| `background/thinker.go` | 新增 `emotionTracker` 字段 + `SetEmotionTracker`;思考提示词注入心情;思考链记录心情;定期/静默思考触发 `Decay()``UpdateMood("long_silence")` |
| `cmd/main.go` | 从 `personaConfig.Personality.MoodSystem` 创建 `EmotionTracker`,注入 `orch``thinker` |
---
## 三、主动消息决策增强 (Task 2.6)
### 3.1 新增文件
#### `backend/ai-core/internal/background/proactive_decision.go`
核心类型:
```
ProactiveGuard — 主动消息决策守卫
├─ QuietHoursStart/End (23:00-07:00)
├─ MinGapByUrgency (low=30min, medium=10min, high=2min)
├─ MaxMessagesPerHour (3)
└─ recentSends (滑动窗口)
ProactiveDecision — { ShouldSend, Urgency, Reason }
```
**决策流程 (5 层检查)**
```
1. 静默时段检查 → 非 high 紧急度在 23:00-07:00 拒绝
2. 用户状态检查 → resting/busy/sleeping 且非 high 拒绝
3. 紧急度频率限制 → 按 low/medium/high 查 MinGapByUrgency
4. 小时频率限制 → 最近 1 小时内消息数 ≥ MaxMessagesPerHour 拒绝
5. 内容校验 → 空消息/超长(>500字符)/机械语言检测
```
辅助函数:
- `ExtractUrgencyFromContent()` — 从消息内容推断紧急程度 (关键词匹配)
- `ValidateProactiveMessage()` — 检测机械语言 (`系统检测到`, `根据分析` 等)
- `DetermineUserState()` — 从用户最后消息推断状态 (休息/忙碌/活跃)
### 3.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `background/thinker.go` | `Thinker` 新增 `proactiveGuard` 字段 + 初始化;`storeThought()` 中硬编码 30 分钟间隔替换为 `ProactiveGuard.Evaluate()` 多维评估 |
| `gateway/internal/ws/hub.go` | 新增 `pendingProactive` 离线队列;`QueueProactiveMessage()` / `FlushPendingProactive()`;注册时检测重连并推送积压消息 |
| `gateway/internal/handler/chat_handler.go` | `HandleProactiveMessage` 中离线用户不再丢弃消息,改为 `QueueProactiveMessage` 排队等待重连 |
---
## 四、离线自主思考 (Task 2.3)
### 4.1 在线状态感知链路
```
Gateway WebSocket connect/disconnect
→ Hub.Run() register/unregister handler
→ notifyAICorePresence(userID, status, sessionID)
→ POST /api/v1/internal/presence (ai-core)
→ Thinker.UpdatePresence(online, sessionID)
```
### 4.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `background/thinker.go` | 新增 `userOnline`, `lastOnlineChange`, `userSessionID` 字段;`UpdatePresence()` 方法 — 上线触发 `performThink("user_returned")` + `UpdateMood("user_returned")``periodicThinkLoop` 内离线时延长间隔至 30 分钟 |
| `background/thinker.go` | 新增 `"user_returned"` 思考提示词分支 — 温和的"欢迎回来"反思,仅在白天/清醒时允许主动消息 |
| `cmd/main.go` | 新增 `POST /api/v1/internal/presence` 端点,验证 `X-Internal-Token`,调用 `thinker.UpdatePresence()` |
| `gateway/internal/ws/hub.go` | 新增 `SetAICoreConfig(url, token)``notifyAICorePresence()` 异步 HTTP POST;注册处理中检测重连并 flush 积压消息 |
| `gateway/cmd/main.go` | `hub.SetAICoreConfig(cfg.AICoreURL, cfg.InternalServiceToken)` |
### 4.3 离线行为差异
| 维度 | 在线 | 离线 |
|------|------|------|
| 思考间隔 | 5 分钟 | 30 分钟 |
| 主动消息 | 通过 ProactiveGuard 评估 | 不发送 (静默时段必拒) |
| 思考重点 | 对话反思、主动关怀 | 记忆整理、日记式反思 |
| 重连 | — | 触发 `user_returned` 思考 + 心情更新 |
---
## 五、完整文件清单
### 新增文件 (3)
| 文件 | 用途 |
|------|------|
| `backend/ai-core/internal/persona/emotion_state.go` | EmotionState / EmotionTracker / MoodTransition |
| `backend/ai-core/internal/background/proactive_decision.go` | ProactiveGuard / ProactiveDecision / 内容校验 |
| `docs/debug_log/2026-05-23-phase2-personality-interaction.md` | 本报告 |
### 修改文件 (13)
| 文件 | 涉及任务 |
|------|---------|
| `backend/ai-core/internal/persona/injector.go` | 2.4 — BuildSystemPromptWithMood |
| `backend/ai-core/internal/orchestrator/orchestrator.go` | 2.4 — EmotionTracker 集成 |
| `backend/ai-core/internal/background/think_chain.go` | 2.4 — ThinkRecord 心情字段 |
| `backend/ai-core/internal/background/thinker.go` | 2.4 + 2.6 + 2.3 — 三者核心集成点 |
| `backend/ai-core/cmd/main.go` | 2.4 + 2.3 — 初始化 + presence 端点 |
| `backend/gateway/internal/ws/hub.go` | 2.6 + 2.3 — 离线队列 + 在线通知 |
| `backend/gateway/internal/handler/chat_handler.go` | 2.6 — 离线消息排队 |
| `backend/gateway/cmd/main.go` | 2.3 — SetAICoreConfig 接线 |
---
## 六、验证指南
### 编译验证
```bash
cd backend/ai-core && GOWORK=off go build ./...
cd backend/gateway && GOWORK=off go build ./...
```
### 运行时验证
1. **情感状态机** — 发几条正面消息,日志应出现 `[情感] 心情转变: thoughtful -> happy`
2. **静默时段** — 在 23:00-07:00 触发思考,日志应出现 `[主动消息决策] 阻止推送 (原因=当前处于安静时段)`
3. **离线队列** — 断开 WebSocket 后触发思考,日志含 `[proactive] 用户离线,消息已排队`;重连后 `[proactive] 推送 N 条积压消息`
4. **在线通知** — 连接/断开 WebSocketai-core 日志含 `[后台思考] 用户上线/离线`gateway 日志含 `[presence] 通知 ai-core`
5. **心情衰减** — 无交互等待,定期思考日志中 `Decay` 逐步降低 intensity
### 日志关键词速查
| 日志标记 | 含义 |
|---------|------|
| `[情感] 心情转变` | 心情状态转换 |
| `[主动消息决策]` | ProactiveGuard 评估结果 |
| `[presence]` | 在线状态通知 |
| `[后台思考] 用户上线/离线` | Thinker 在线状态变更 |
| `[proactive] 用户离线,消息已排队` | Gateway 离线消息排队 |
| `[proactive] 推送 N 条积压消息` | 重连后积压消息推送 |
---
## 七、架构演进
```
Phase 1 (基础设施) Phase 2 (人格交互)
───────────────────── ─────────────────────
ThinkChain ──→ 情感状态融入思考链
MessageScheduler ──→ 主动消息多维决策
AutonomousToolPolicy ──→ 离线思考频率控制
SessionEnrichment ──→ 重连问候 + 心情更新
──→ 在线状态感知链路 (Gateway ↔ ai-core)
```
本次 Phase 2 未碰数据库 schema,所有新增状态均为内存态 (EmotionState / ProactiveGuard / pendingProactive),重启后从 YAML 配置恢复初始心情基线。
@@ -1,374 +0,0 @@
# Phase 3: 插件与工具系统 — 开发报告
> **报告日期**2026-05-23
> **分支**`dev`
> **阶段**:Phase 3 — 插件与工具系统
> **总计修改文件数**:40 个 (新增 39 个, 修改 1 个)
> **总代码行数**3293 行
> **编译状态**plugin-manager ✅ / ai-core ⚠️ 网络不可用 (golang.org/x/arch 未缓存)
---
## 一、背景
Phase 2 完成了人格与交互深化 (情感状态机 + 主动消息决策 + 离线自主思考)。Phase 3 的目标是建立标准化的插件开发框架,让社区开发者可以方便地为昔涟扩展能力。
### 核心目标
| 任务 | 说明 |
|------|------|
| Plugin SDK | 标准化接口 (Plugin / Tool / ComplexTool / HostAPI) |
| Plugin Manager | 插件生命周期管理、热加载、工具注册聚合 |
| 工具分级 | 简易工具 (Simple) vs 复杂工具 (Complex),异步执行支持 |
| 13 个内置插件 | 将原有硬编码工具迁移为标准插件格式 |
| REST API | 插件 CRUD + 工具发现 + 执行端点 |
| ai-core 集成 | PluginManagerClient 替换本地工具调用 |
---
## 二、架构概览
```
┌──────────────────────────────────────────────────────┐
│ Plugin Manager (port 8094) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Plugin A │ │ Plugin B │ │ Plugin C │ ... 13个 │
│ │calculator│ │ datetime │ │iot_query │ 内置插件 │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────▼──────────────▼──────────────▼────┐ │
│ │ Plugin Manager Core │ │
│ │ ┌───────────┐ ┌──────────────────┐ │ │
│ │ │ Lifecycle │ │ Tool Registry │ │ │
│ │ │ Manager │ │ (aggregated) │ │ │
│ │ └───────────┘ └────────┬─────────┘ │ │
│ │ Install/Enable/Disable │ Execute │ │
│ │ Start/Stop/Reload │ Validate │ │
│ └──────────────────────────┴─────────────┘ │
│ │ │
│ ┌─────────────────▼────────────────────────┐ │
│ │ REST API (net/http) │ │
│ │ GET/POST/DELETE /api/v1/plugins/** │ │
│ │ GET/POST /api/v1/tools/** │ │
│ └──────────────────────────────────────────┘ │
│ ▲ │
└────────────────────┼───────────────────────────────────┘
│ HTTP
┌────────────────────┼───────────────────────────────────┐
│ ai-core │ │
│ ┌─────────────────▼────────────────────────┐ │
│ │ PluginManagerClient │ │
│ │ GetToolDefinitions / ExecuteTool │ │
│ └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
```
---
## 三、Plugin SDK (插件开发工具包)
### 3.1 核心接口
#### `sdk/plugin.go` — Plugin + Tool + ComplexTool + HostAPI
```go
// Plugin — 所有插件必须实现的核心接口
type Plugin interface {
Metadata() PluginMetadata
Init(ctx context.Context, config PluginConfig) error
Start(ctx context.Context, host HostAPI) error
Stop(ctx context.Context) error
Health(ctx context.Context) error
Tools() []Tool
}
// Tool — 所有工具必须实现
type Tool interface {
Definition() ToolDefinition
Execute(ctx context.Context, args map[string]interface{}) (*ToolResult, error)
Validate(args map[string]interface{}) error
Complexity() ToolComplexity
}
// ComplexTool — 扩展异步多轮执行
type ComplexTool interface {
Tool
ExecuteAsync(ctx context.Context, args map[string]interface{}) (<-chan ToolProgress, error)
Cancel(ctx context.Context, executionID string) error
}
// HostAPI — 插件可调用的宿主能力
type HostAPI interface {
CallLLM(ctx context.Context, messages []LLMMessage) (*LLMResponse, error)
SearchMemory(ctx context.Context, userID, query string, limit int) ([]MemoryEntry, error)
StoreMemory(ctx context.Context, entry MemoryEntry) error
Logger() Logger
GetConfig(key string) (string, error)
SetConfig(key, value string) error
PublishEvent(ctx context.Context, event map[string]interface{}) error
HTTPClient() *http.Client
}
```
#### `sdk/types.go` — 共享类型
| 类型 | 用途 |
|------|------|
| `PluginMetadata` | 插件标识 (name/version/author/dependencies) |
| `PluginConfig` | 运行时配置 (map[string]interface{}) |
| `ToolDefinition` | 工具定义 (ID/Name/Parameters/Complexity/DangerLevel) |
| `ToolResult` | 执行结果 (Success/Output/Error/DurationMs) |
| `ToolProgress` | 异步进度 (Status/Progress/Result) |
| `ToolComplexity` | 工具分级: `simple` / `complex` |
| `PluginStatus` | 插件状态: installed/loaded/running/paused/error/disabled |
| `IoTDeviceState` | 共享 IoT 设备状态 |
#### `sdk/base.go` — 基类实现
- `BasePlugin` — 提供 Init/Start/Stop/Health 空实现
- `BaseTool` — 提供 Validate (required 参数检查) + Complexity 默认返回 simple
---
## 四、Plugin Manager 核心
### 4.1 文件清单
| 文件 | 行数 | 用途 |
|------|------|------|
| `internal/manager/manager.go` | 200 | Plugin Manager 核心:Install/Enable/Disable/Uninstall/Reload/Shutdown |
| `internal/manager/registry.go` | 80 | 聚合工具注册表:Register/Unregister/Get/List/Definitions/Execute |
### 4.2 插件生命周期
```
Install → [installed]
→ Enable → Init() → Start() → Register tools → [running]
→ Disable → Unregister tools → Stop() → [disabled]
→ Reload → Disable → Enable
→ Uninstall → Disable (if running) → 移除注册
```
- 所有状态为内存态,重启后从 main.go 重新安装内置插件
- 每个插件启动时获得独立 `context.WithCancel`panic 恢复防护
### 4.3 EnableAll 执行流程
```
1. 遍历已安装插件
2. 对每个插件: Init(config) → Start(ctx, host) → Register tools
3. 任何插件失败不影响其他插件继续启动
4. 返回所有错误列表
```
---
## 五、13 个内置插件
每个插件目录包含 `plugin.json` (元数据) + `plugin.go` (实现)。
### 5.1 无状态工具插件 (7个)
| 插件名 | 目录 | 工具ID | 主要功能 |
|--------|------|--------|---------|
| Calculator | `plugins/calculator/` | `calculator` | 安全数学表达式解析器 (递归下降) |
| DateTime | `plugins/datetime/` | `datetime` | 日期时间 (now/format/add/diff/timezone) |
| Text | `plugins/text/` | `text` | 文本处理 (count/summarize/translate/extract) |
| Crypto | `plugins/crypto/` | `crypto` | 哈希/编码 (MD5/SHA/Base64/URL) |
| Random | `plugins/random/` | `random` | 随机生成 (number/uuid/password/pick/shuffle) |
| Markdown | `plugins/markdown/` | `markdown` | Markdown 处理 (to_html/to_text/extract/TOC) |
| JSON | `plugins/json/` | `json_ops` | JSON 处理 (parse/query/validate) |
### 5.2 带依赖插件 (4个)
| 插件名 | 目录 | 工具ID | 依赖 |
|--------|------|--------|------|
| File | `plugins/file/` | `file_ops` | dataDir (沙盒路径) |
| HTTP | `plugins/http/` | `http_request` | http.Client |
| WebSearch | `plugins/web_search/` | `web_search` | http.Client (DuckDuckGo API) |
| WebFetch | `plugins/web_fetch/` | `web_fetch` | http.Client |
### 5.3 IoT 插件 (2个)
| 插件名 | 目录 | 工具ID | 依赖 | DangerLevel |
|--------|------|--------|------|-------------|
| IoT Query | `plugins/iot_query/` | `iot_query` | IoTClient (查询设备状态) | low |
| IoT Control | `plugins/iot_control/` | `iot_control` | IoTController (控制设备) | medium |
IoT 插件通过 `IOT_SERVICE_URL` 环境变量配置,适配器通过 HTTP 调用 iot-debug-service。
---
## 六、REST API
### 6.1 端点列表
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/v1/plugins` | 列出所有已安装插件 |
| GET | `/api/v1/plugins/{id}` | 获取单个插件详情 |
| POST | `/api/v1/plugins/{id}/enable` | 启用插件 |
| POST | `/api/v1/plugins/{id}/disable` | 禁用插件 |
| POST | `/api/v1/plugins/{id}/reload` | 热重载插件 |
| DELETE | `/api/v1/plugins/{id}` | 卸载插件 |
| GET | `/api/v1/plugins/{id}/tools` | 列出插件的工具 |
| GET | `/api/v1/tools` | 列出所有已注册工具 |
| GET | `/api/v1/tools/{id}` | 获取单个工具定义 |
| POST | `/api/v1/tools/{id}/execute` | 执行工具 |
| GET | `/health` | 健康检查 |
### 6.2 使用 net/http
Plugin Manager 不依赖任何外部 HTTP 框架,使用 Go 标准库 `net/http``ServeMux`
- 零外部依赖:只有 Go 标准库
- 路由解析:手动解析路径参数
- JSON 序列化:`encoding/json`
---
## 七、ai-core 集成
### 7.1 PluginManagerClient
新增文件 `backend/ai-core/internal/tools/plugin_manager_client.go`
```go
type PluginManagerClient struct {
baseURL string // http://localhost:8094
httpClient *http.Client
}
```
方法:
- `GetToolDefinitions(ctx)` — GET /api/v1/tools → []PMToolDefinition
- `ExecuteTool(ctx, toolID, args)` — POST /api/v1/tools/{id}/execute → *PMToolResult
- `ListPlugins(ctx)` — GET /api/v1/plugins → []PMPluginInfo
- `AdaptDefinitions(ctx)` — 将 PM 工具定义转换为 ai-core ToolDefinition 格式
---
## 八、完整文件清单
### 新增文件 (39)
| 目录/文件 | 用途 |
|-----------|------|
| `backend/plugin-manager/go.mod` | Go 模块定义 (零外部依赖) |
| `backend/plugin-manager/cmd/main.go` | 服务入口:注册内置插件、启动 HTTP |
| `backend/plugin-manager/cmd/host_api.go` | HostAPI 实现 (stub) |
| `backend/plugin-manager/cmd/iot_adapter.go` | IoT 客户端适配器 (HTTP → IoT service) |
| `backend/plugin-manager/internal/config/config.go` | 配置加载 |
| `backend/plugin-manager/internal/handler/plugin_handler.go` | REST API 处理函数 (net/http) |
| `backend/plugin-manager/internal/manager/manager.go` | Plugin Manager 核心 |
| `backend/plugin-manager/internal/manager/registry.go` | 工具注册表 |
| `backend/plugin-manager/internal/sdk/base.go` | BasePlugin + BaseTool |
| `backend/plugin-manager/internal/sdk/plugin.go` | 核心接口 (Plugin/Tool/ComplexTool/HostAPI) |
| `backend/plugin-manager/internal/sdk/permissions.go` | 权限模型 |
| `backend/plugin-manager/internal/sdk/types.go` | 共享类型定义 |
| `backend/plugin-manager/plugins/{13个}/plugin.json` | 各插件元数据 |
| `backend/plugin-manager/plugins/{13个}/plugin.go` | 各插件实现 |
| `backend/ai-core/internal/tools/plugin_manager_client.go` | ai-core Plugin Manager 客户端 |
### 修改文件 (1)
| 文件 | 修改内容 |
|------|---------|
| `backend/go.work` | 添加 `./plugin-manager` 到 workspace |
---
## 九、验证指南
### 编译验证
```bash
# Plugin Manager (零外部依赖 — 即使离线也可编译)
cd backend/plugin-manager && GOWORK=off go build ./...
# ai-core / gateway (需要网络下载 golang.org/x/arch)
# 如果模块缓存可用: go build ./...
```
### 运行时验证
```bash
# 启动 Plugin Manager
cd backend/plugin-manager && GOWORK=off go run ./cmd/
# 测试端点
curl http://localhost:8094/health
# → {"status":"ok","service":"plugin-manager"}
curl http://localhost:8094/api/v1/plugins
# → {"plugins":[...13个插件...], "total": 13}
curl http://localhost:8094/api/v1/tools
# → {"tools":[...13个工具定义...], "total": 13}
# 执行计算器工具
curl -X POST http://localhost:8094/api/v1/tools/calculator/execute \
-H "Content-Type: application/json" \
-d '{"arguments":{"expression":"2+3*4"}}'
# → {"tool_name":"calculator","success":true,"output":"14"}
# 获取单个插件
curl http://localhost:8094/api/v1/plugins/calculator
# → 显示 Calculator 插件详情
# 禁用/启用插件
curl -X POST http://localhost:8094/api/v1/plugins/calculator/disable
curl -X POST http://localhost:8094/api/v1/plugins/calculator/enable
```
---
## 十、与设计文档的对应
| 设计文档要求 | 实现状态 |
|-------------|---------|
| Plugin 接口 (Metadata/Init/Start/Stop/Health/Tools) | ✅ 完整实现 |
| Tool 接口 (Definition/Execute/Validate) | ✅ 完整实现 |
| ComplexTool 接口 (ExecuteAsync/Cancel) | ✅ 已定义 (待异步场景实现) |
| HostAPI 接口 (CallLLM/SearchMemory/StoreMemory/Logger等) | ✅ 已定义 + stub 实现 |
| 插件生命周期 (Install→Enable→Start→Running→Stop→Disable→Uninstall) | ✅ 完整实现 |
| 工具注册表 (聚合所有插件工具) | ✅ 完整实现 |
| plugin.json 元数据格式 | ✅ 13 个插件均含完整 metadata |
| tool.json 定义 | 🔧 内嵌在 Go Definition() 方法中 (后续分离) |
| 插件目录结构 (tools/config/resources) | 🔧 基础结构已建 (config/resources 待补充) |
| Plugin Manager REST API (10+ 端点) | ✅ 11 个端点实现 |
| 权限模型 (PluginPermissions) | ✅ 类型已定义 |
| 工具分级 (simple/complex) | ✅ 已实现,所有内置工具标记为 simple |
| gRPC 通信 | 🔧 当前使用 HTTP (gRPC 后续迁移) |
| Marketplace 框架 | 🔧 基础 ListPlugins/GetPlugin 已实现 (search/install 待扩展) |
| 13 个工具迁移 | ✅ 全部迁移为标准插件 |
| ai-core PluginManagerClient | ✅ 已创建 |
---
## 十一、后续计划
1. **Phase 3.2**: 将 tool.json 从 Go 代码中分离为独立 JSON 文件,实现配置驱动
2. **Phase 3.3**: 完善 HostAPI 实现 (连接 ai-core LLM/记忆/事件总线)
3. **Phase 3.3**: 补充单元测试 (Plugin Manager 生命周期、工具执行、REST API)
4. **Phase 3.4**: gRPC 替换 HTTP (降低调用延迟)
5. **Phase 3.5**: Marketplace 安装/搜索/评分
---
## 十二、架构演进
```
Phase 1 (基础设施) Phase 2 (人格交互) Phase 3 (插件工具)
───────────────────── ───────────────────── ─────────────────────
ThinkChain EmotionState Plugin SDK
MessageScheduler ProactiveGuard Plugin Manager
AutonomousToolPolicy Presence Bridge Tool Registry
SessionEnrichment Offline Thinking Tool Grading (Simple/Complex)
──→ ──→
硬编码 13 工具 (ai-core) 13 内置插件
重复实现 (ai-core+tool-engine) 统一插件格式
无插件扩展机制 标准化扩展框架
```
Phase 3 建立了可扩展的插件基础设施。所有 13 个工具从硬编码迁移到标准化插件格式,Plugin Manager 通过 REST API 对外暴露工具发现和执行能力。ai-core 通过 PluginManagerClient 接入,替代原有的本地工具调用模式。
@@ -1,233 +0,0 @@
# Phase 4: 多平台接入 — 开发报告
> **报告日期**2026-05-23
> **分支**`dev`
> **阶段**Phase 4 — 多平台接入
> **总计修改文件数**:18 个 (新增 15 个, 修改 3 个)
> **总代码行数**1852 行 (Go) + 配置文件
> **编译状态**platform-bridge ✅ / ai-core ⚠️ 网络不可用 (预存在问题)
---
## 一、背景
Phase 3 完成了插件与工具系统。Phase 4 的目标是建立统一的多平台消息接入层,让昔涟能够通过 QQ、Telegram、Webhook 等多种渠道与用户交互。
### 核心目标
| 任务 | 说明 |
|------|------|
| 统一消息模型 | 所有平台消息转换为统一的 UnifiedMessage/UnifiedResponse |
| 身份映射 | 平台用户 UID → Cyrene 用户的身份映射与权限管理 |
| QQ OBv11 适配器 | 反向 WebSocket 连接,QQ 机器人协议适配 |
| Telegram 适配器 | Bot API Webhook 模式,消息收发 + 输入状态 |
| 通用 Webhook | 标准 HTTP POST 接收任意平台消息 |
| 权限检查器 | admin/full/basic/restricted 四级权限,工具/设备白名单 |
| WeChat/Feishu/Discord 桩 | 预留接口,待后续接入外部 SDK |
---
## 二、架构概览
```
┌──────────────────────────────────────────────────────┐
│ Platform Bridge (port 8095) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ QQ OBv11 │ │ Telegram │ │ Webhook │ ... 6 适配器│
│ │ (WS 8096)│ │ (Bot API)│ │ (通用) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────▼──────────────▼──────────────▼────┐ │
│ │ Platform Router │ │
│ │ ┌────────────┐ ┌─────────────────┐ │ │
│ │ │ Identity │ │ Permission │ │ │
│ │ │ Mapper │ │ Checker │ │ │
│ │ └────────────┘ └────────┬────────┘ │ │
│ └───────────────────────────┴────────────┘ │
│ │ │
│ forwardToAICore() │
│ │ │
│ ┌───────▼────────┐ │
│ │ AI-Core │ │
│ │ (port 8081) │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────────┘
```
## 消息流程
```
外部平台消息 → Adapter.ToUnified() → 统一消息 → IdentityMapper.Resolve()
→ Permission Check → forwardToAICore() → 统一响应 → Adapter.FromUnified()
→ 平台消息发送
```
---
## 三、文件清单
### 3.1 核心类型 (internal/bridge/)
| 文件 | 行数 | 说明 |
|------|------|------|
| `unified.go` | 76 | UnifiedMessage, UnifiedResponse, ResponseMessage, PlatformCapabilities, PlatformMessage, Attachment, PlatformHints |
| `adapter.go` | 24 | PlatformAdapter 接口 (PlatformName, ToUnified, FromUnified, Capabilities, Connect, Disconnect, IsConnected, HealthCheck) + MessageHandler 类型 |
| `mapper.go` | 73 | IdentityMapper — platform:platformUID → CyreneUser 映射注册/查询/列表 |
| `router.go` | 168 | PlatformRouter — 适配器注册、消息路由、通道上下文管理、响应发送 |
### 3.2 权限系统 (internal/permissions/)
| 文件 | 行数 | 说明 |
|------|------|------|
| `permissions.go` | 115 | PlatformIdentity 结构体 + Checker 权限检查器 (四级权限: admin/full/basic/restricted)、工具白名单、IoT 设备白名单 |
### 3.3 平台适配器 (internal/adapter/)
| 文件 | 行数 | 说明 |
|------|------|------|
| `qq/protocol.go` | 75 | OBv11 协议类型 (OBv11Message, OBv11Sender, OBv11SendMsg 等) |
| `qq/adapter.go` | 394 | QQ 适配器 — 反向 WebSocket 服务器, ToUnified/FromUnified, SendMessage, ReadMessages 事件循环, markdown→QQ 格式转换 |
| `telegram/adapter.go` | 251 | Telegram 适配器 — Bot API Webhook, ToUnified 支持结构化/map 两种输入, SendMessage, SendChatAction (typing 指示器) |
| `webhook/adapter.go` | 121 | 通用 Webhook 适配器 — WebhookPayload/WebhookResponse 标准格式 |
| `wechat/stub.go` | 50 | 微信桩 (待 WeChatFerry / 企业微信 SDK) |
| `feishu/stub.go` | 50 | 飞书桩 (待 Lark Go SDK) |
| `discord/stub.go` | 50 | Discord 桩 (待 discordgo) |
### 3.4 REST API (internal/handler/)
| 文件 | 行数 | 说明 |
|------|------|------|
| `bridge_handler.go` | 149 | 路由注册, /health, /api/v1/platforms, /api/v1/identities, /api/v1/webhook/telegram, /api/v1/webhook/{platform} |
### 3.5 配置与入口
| 文件 | 行数 | 说明 |
|------|------|------|
| `config/config.go` | 52 | 环境变量加载 (PORT, AI_CORE_URL, QQ_BOT_PORT, TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_URL) |
| `cmd/main.go` | 204 | 服务入口 — 创建 mapper/checker/router, 注册 6 个适配器, 设置消息处理器, QQ 消息读取循环, 身份种子数据 |
### 3.6 DevTools 集成 (修改)
| 文件 | 变更 | 说明 |
|------|------|------|
| `devtools/src/config.js` | +33 行 | 添加 plugin-manager 和 platform-bridge 服务配置 |
| `devtools/src/process-manager.js` | +2/-2 | 添加新服务到启动顺序和 DB 检查列表 |
| `backend/go.work` | +1 | 添加 platform-bridge 模块 |
---
## 四、关键设计决策
### 4.1 导入循环解决
原始设计中 `adapter` 包定义了 `PlatformAdapter` 接口,`bridge` 包的 `router.go` 引用它,但 `adapter` 实现文件又引用 `bridge` 的类型。产生了循环依赖:
```
adapter ↔ bridge (import cycle)
```
**解决方案**:将 `PlatformAdapter` 接口和 `MessageHandler` 类型从 `internal/adapter/` 移到 `internal/bridge/adapter.go`,与它们引用的类型 (`UnifiedMessage`, `UnifiedResponse`) 放在同一包中。
同理,`permissions ↔ bridge` 循环通过将 `PlatformIdentity``bridge/mapper.go` 移到 `permissions/permissions.go` 解决。
### 4.2 QQ 适配器的反向 WebSocket
QQ 使用 OneBot v11 协议,采用反向 WebSocket 模式:
- **平台桥接启动 WebSocket 服务器** (端口 8096)
- **QQ 机器人框架主动连接到此端口**
- 消息通过 WebSocket 双向传递
```go
// Connect 非阻塞:在 goroutine 中启动 HTTP Server
mux.HandleFunc("/ws/qq", func(w, r) { upgrade(w, r) })
go srv.ListenAndServe()
```
### 4.3 Telegram Webhook 模式
Telegram 适配器使用 Bot API webhook
- **启动时**调用 `setWebhook` 注册回调 URL
- **运行时**被动接收 Telegram 服务器推送的 Update
- **ToUnified** 同时支持结构化 `*TelegramUpdate` 和原始 `map[string]interface{}`,避免 HTTP handler 与适配器类型耦合
### 4.4 桩适配器策略
WeChat、Feishu、Discord 目前为桩实现。它们实现了完整的 `PlatformAdapter` 接口但返回错误信息,提示需要哪个 SDK。这样做的好处:
- 路由注册和平台列表正常工作
- 当获取到对应的 SDK 和凭据后,只需替换桩实现即可
- Auth 系统可通过 API 查看所有平台状态
### 4.5 权限模型
```
admin → 所有权限 (聊天/IoT操控/查询/记忆管理/系统管理/全部工具/全部设备)
full → 聊天 + IoT操控 + IoT查询 + 记忆访问
basic → 聊天 + IoT查询
restricted → 仅聊天
```
权限检查器支持细粒度的工具白名单和设备白名单,admin 自动拥有所有权限。
---
## 五、API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/health` | 健康检查,返回平台列表 |
| GET | `/api/v1/platforms` | 列出所有平台及其能力 |
| GET | `/api/v1/platforms/{name}` | 单个平台详情 |
| GET | `/api/v1/identities` | 列出所有身份映射 |
| POST | `/api/v1/webhook/telegram` | 接收 Telegram Bot API Update |
| POST | `/api/v1/webhook/{platform}` | 接收通用 Webhook 消息 |
---
## 六、环境变量
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `PORT` | 8095 | 桥接服务 HTTP 端口 |
| `AI_CORE_URL` | http://localhost:8081 | AI-Core 地址 |
| `QQ_BOT_PORT` | 8096 | QQ OBv11 WebSocket 监听端口 |
| `TELEGRAM_BOT_TOKEN` | (空) | Telegram Bot API Token |
| `TELEGRAM_WEBHOOK_URL` | (空) | Telegram Webhook 回调 URL |
| `QQ_ADMIN_UID` | (空) | QQ 管理员 UID (种子身份) |
| `TELEGRAM_ADMIN_UID` | (空) | Telegram 管理员 UID (种子身份) |
---
## 七、已知限制
1. **WeChat/Feishu/Discord 为桩** — 需要对应的 SDK 和凭据后才能启用
2. **QQ 适配器未实现心跳** — OBv11 协议中 bot 需要定期发送心跳包维持连接
3. **Telegram 未处理 callback_query** — 目前仅处理 message 类型的 Update
4. **平台桥接与 Gateway 之间无内部认证**`forwardToAICore` 直接调用 AI-Core,绕过 Gateway。后续若需要 Gateway 统一管理会话,应通过 Gateway 的 HTTP API 而非 WebSocket
---
## 八、验证指南
```bash
# 1. 编译 platform-bridge
cd backend/platform-bridge
GOWORK=off go build ./cmd/main.go
# 2. 启动服务
./main
# 3. 健康检查
curl http://localhost:8095/health
# 4. 查看注册的平台
curl http://localhost:8095/api/v1/platforms
# 5. 测试通用 Webhook
curl -X POST http://localhost:8095/api/v1/webhook/webhook \
-H "Content-Type: application/json" \
-d '{"user_id":"test_user","user_name":"测试用户","content":"你好昔涟","channel_id":"test_channel"}'
# 6. 查看身份映射
curl http://localhost:8095/api/v1/identities
```
-47
View File
@@ -1,47 +0,0 @@
# Cyrene 调试日志索引
本目录包含 Cyrene 项目开发过程中的阶段性调试总结日志。
## 2026-05-19
- [持续性调试报告 (10轮汇总)](2026-05-19-round7-continuous-debugging-report.md)
## 2026-05-20 (Round 1~10)
- [Round 1 - 回归验证](2026-05-20-round1-regression-verification.md)
- [Round 2 - 认证集成](2026-05-20-round2-auth-integration.md)
- [Round 3 - 面板前端](2026-05-20-round3-panels-frontend.md)
- [Round 4 - 子服务数据库](2026-05-20-round4-subservices-database.md)
- [Round 5 - 安全边界](2026-05-20-round5-security-boundary.md)
- [Round 6 - 性能代码质量](2026-05-20-round6-performance-code-quality.md)
- [Round 7 - E2E 跨服务](2026-05-20-round7-e2e-cross-service.md)
- [Round 8 - Docker PWA WebSocket](2026-05-20-round8-docker-pwa-websocket.md)
- [Round 9 - 配置汇总](2026-05-20-round9-config-summary.md)
- [Round 10 - 关键修复](2026-05-20-round10-critical-fixes.md)
## 2026-05-21
- [崩溃取消修复](2026-05-21-crash-cancel.md)
- [Round 2 修复](2026-05-21-round2-fixes.md)
- [Round 11 - API 契约](2026-05-21-round11-api-contract.md)
## 2026-05-22
- [最终汇总报告](2026-05-22-final-summary.md)
- [Round 4 - IoT 审查管道](2026-05-22-round4-iot-review-pipeline.md)
- [Round 5 - IoT 边界情况](2026-05-22-round5-iot-edge-cases.md)
- [Round 6 - 最终汇总 (Round 4~6)](2026-05-22-round6-final-summary.md)
## 2026-05-23
- [Phase 2 - 人格与交互深化](2026-05-23-phase2-personality-interaction.md) — 情感状态机 + 主动消息决策增强 + 离线自主思考 (16 文件)
- [Phase 3 - 插件与工具系统](2026-05-23-phase3-plugin-tool-system.md) — Plugin SDK + Plugin Manager + 13 内置插件 + 工具分级 (40 文件)
- [Phase 4 - 多平台接入](2026-05-23-phase4-multi-platform.md) — 统一消息模型 + QQ/Telegram/Webhook 适配器 + 身份权限系统 (18 文件)
## 2026-05-24
- [Phase 6 - 多模型配置系统 + 视觉集成](2026-05-24-phase6-model-config-vision.md) — ModelSelector 路由 + LLM 调用日志 + Vision/OCR 集成 + Bug 修复 (9 文件)
## 2026-05-25
- [思考调度 + 多端广播 + 插件管理器](2026-05-25-thinking-schedule-broadcast-plugins.md) — JSON 动态间隔调度 + 消息全端广播 + 【主动消息】解析修复 + 时间感知思考 + DevTools 插件管理面板 (12 文件)