chore: 从仓库移除 docs/debug_log/ — 调试日志不进版本管理
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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服务均有Dockerfile,voice-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**: ✅ 运行中,端口 8080,0 个活跃 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 |
|
||||
|
||||
> **分析**:所有服务的健康检查端点响应时间均 < 7ms,Gateway 表现最优 (~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 优势总结
|
||||
|
||||
| 维度 | 评级 | 说明 |
|
||||
|------|------|------|
|
||||
| 响应性能 | ⭐⭐⭐⭐⭐ | 健康检查 < 1ms,50 并发仅 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 token(query 参数或 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 用户、时区设置、ldflags;Redis 生产密码;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、部分健康检查 |
|
||||
| 性能 | ⭐⭐⭐⭐⭐ | 健康检查 <7ms,50 并发仅 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*
|
||||
*审计工具: 静态代码分析 + 运行时健康检查 + 冒烟测试*
|
||||
@@ -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天内的所有源码更改。
|
||||
@@ -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 设备广播、记忆存储链路完整。
|
||||
@@ -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 H2:WS 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 response(action 消息 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 ✅
|
||||
```
|
||||
|
||||
### 测试场景 2:Review 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/chat(role="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. **在线通知** — 连接/断开 WebSocket,ai-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
|
||||
```
|
||||
@@ -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 文件)
|
||||
Reference in New Issue
Block a user