feature/base-build #32
@@ -213,11 +213,73 @@ CREATE TABLE usage_daily (
|
||||
## 部署架构
|
||||
|
||||
```
|
||||
CDN(静态资源) ← 用户浏览器
|
||||
Nginx 负载均衡(sticky session for WebSocket)
|
||||
├── Gateway-1 ──→ Redis
|
||||
├── Gateway-2 ──→ Redis
|
||||
└── Gateway-N ──→ AI Services(外部 API)
|
||||
用户浏览器
|
||||
↓
|
||||
Nginx(同源反代 + 负载均衡)
|
||||
├── / → 前端静态资源(CDN 或本地 dist)
|
||||
├── /api/* → Go Gateway(REST API)
|
||||
└── /ws → Go Gateway(WebSocket)
|
||||
├── Gateway-1 ──→ Redis
|
||||
├── Gateway-2 ──→ Redis
|
||||
└── Gateway-N ──→ AI Services(外部 API)
|
||||
```
|
||||
|
||||
WebSocket 是长连接,Nginx 需要配置 `proxy_set_header Upgrade` 和 sticky session,确保同一用户的请求始终路由到同一个 Gateway 实例。
|
||||
**跨域策略**:Nginx 将前端和后端统一到同一域名下,浏览器无跨域问题。
|
||||
|
||||
### Nginx 配置
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name camtalk.example.com;
|
||||
|
||||
# 前端静态资源
|
||||
location / {
|
||||
root /var/www/camtalk/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# REST API 反代
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# WebSocket 反代
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 86400s; # 长连接超时 24h
|
||||
proxy_send_timeout 86400s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> WebSocket 是长连接,Nginx 必须配置 `Upgrade` 和 `Connection` 头。`proxy_read_timeout` 需要覆盖心跳间隔(客户端 30s ping),否则 Nginx 会主动断开空闲连接。
|
||||
|
||||
### 开发环境(Vite proxy)
|
||||
|
||||
开发时前端(Vite :5173)和后端(Gin :8080)不同端口,用 Vite 内置代理解决跨域:
|
||||
|
||||
```typescript
|
||||
// frontend/vite.config.ts
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": "http://localhost:8080",
|
||||
"/ws": {
|
||||
target: "ws://localhost:8080",
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
前端代码中 WebSocket 地址改为相对路径 `ws://localhost:5173/ws`,Vite 自动代理到后端。部署时 Nginx 同理,前端无需区分开发/生产地址。
|
||||
|
||||
@@ -1085,3 +1085,25 @@ function reconnect(attempt: number) {
|
||||
}
|
||||
// attempt: 0 → 1s, 1 → 2s, 2 → 4s, 3 → 8s, ... 最大 30s
|
||||
```
|
||||
|
||||
### 跨域处理
|
||||
|
||||
采用 **Nginx 同源反代**方案,前后端统一到同一域名,浏览器层面不存在跨域问题。
|
||||
|
||||
**生产环境**:Nginx 将 `/`(前端)、`/api/*`(REST)、`/ws`(WebSocket)统一反代到同一域名,详见 `02-系统架构.md` 部署架构章节。
|
||||
|
||||
**开发环境**:Vite 内置代理,前端 :5173 的 `/api` 和 `/ws` 请求代理到后端 :8080:
|
||||
|
||||
```typescript
|
||||
// frontend/vite.config.ts
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": "http://localhost:8080",
|
||||
"/ws": { target: "ws://localhost:8080", ws: true },
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
**Go 后端 WebSocket CheckOrigin**:生产环境 Nginx 同源,`CheckOrigin` 可保持默认(拒绝跨域)。开发环境由 Vite proxy 转发,不存在跨域。因此后端无需配置 CORS 中间件,`CheckOrigin` 保持 gorilla/websocket 默认值即可。
|
||||
|
||||
> 如果未来需要支持第三方客户端直连(如移动端),再按需添加 CORS 中间件和 `CheckOrigin` 白名单。
|
||||
|
||||
220
docs/PLAN_BACKEND.md
Normal file
220
docs/PLAN_BACKEND.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# CamTalk 后端完善计划
|
||||
|
||||
## Context
|
||||
|
||||
后端当前是一个骨架:`main.go` 启动 Gin 服务器,`ws/handler.go` 实现了 WebSocket 连接生命周期和消息分发,`models/models.go` 定义了所有协议消息类型,`config/config.go` 实现了 Viper 配置加载。但所有业务逻辑都是 TODO 桩——没有 Session Manager、没有 AI 服务客户端、没有编排层、没有日志/错误工具、没有测试。前端已基本完成,正在等待后端提供真实的 AI 管道。
|
||||
|
||||
**目标**:按设计文档(`docs/03-接口文档.md` 为最高依据)逐步填充所有业务模块,使端到端的 STT → LLM → TTS 流式管道可用。
|
||||
|
||||
---
|
||||
|
||||
## 分阶段实施
|
||||
|
||||
### Phase 1:基础设施(logger、errors、config 接入、graceful shutdown)
|
||||
|
||||
**目标**:为后续模块提供日志、错误码、配置等基础能力,替换 `main.go` 中的硬编码值。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1.1 | 实现 Zap 日志封装 | `internal/logger/logger.go` | 提供 `Init(level, format)` 和全局 `*zap.SugaredLogger`,替换所有 `log.Printf` |
|
||||
| 1.2 | 实现错误码常量 + WS 错误发送工具 | `internal/errors/codes.go` | 10 个错误码常量 + `SendWSError(client, code, requestID, err)` |
|
||||
| 1.3 | main.go 接入 config.Load() | `cmd/server/main.go` | 用 `cfg.Server.Host:Port` 替换硬编码 `:8080`,初始化 logger |
|
||||
| 1.4 | 添加 graceful shutdown | `cmd/server/main.go` | `signal.NotifyContext` + `http.Server.Shutdown`,10s drain |
|
||||
| 1.5 | 添加 CORS 中间件 | `cmd/server/main.go` | 开发阶段允许所有来源,生产走 Nginx 同源 |
|
||||
| 1.6 | 添加 .gitignore | `backend/.gitignore` | 排除 `server` 二进制、`.env`、`tmp/` |
|
||||
|
||||
---
|
||||
|
||||
### Phase 2:Session Manager
|
||||
|
||||
**目标**:实现会话生命周期管理,让 WS handler 能追踪会话、存储对话历史。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 2.1 | 定义 SessionManager 接口 | `internal/session/manager.go` | 方法:`Create`, `Get`, `UpdateConfig`, `GetHistory`, `AppendMessage`, `SetActiveRequest`, `ClearActiveRequest`, `Touch`, `Destroy` |
|
||||
| 2.2 | 实现内存版 SessionManager | `internal/session/memory.go` | `sync.RWMutex` + `map[string]*sessionEntry`,TTL 30 分钟,历史上限 20 条 |
|
||||
| 2.3 | 实现 Redis 版 SessionManager | `internal/session/redis.go` | `session:{id}:meta` Hash + `session:{id}:history` List,TTL 刷新,选配 |
|
||||
| 2.4 | 编写 Session Manager 测试 | `internal/session/memory_test.go` | 覆盖 Create/Get/Expire/Destroy/AppendMessage/History 上限 |
|
||||
| 2.5 | WS handler 接入 SessionManager | `internal/ws/handler.go` | `ServeWS` 接收 `session.Manager` 参数;`connected` 消息后创建会话;`query` 时 Touch + SetActiveRequest;`config` 时 UpdateConfig;断开时不销毁(自然过期) |
|
||||
|
||||
---
|
||||
|
||||
### Phase 3:AI 服务层接口 + 实现
|
||||
|
||||
**目标**:定义并实现三个 AI 服务客户端,每个服务一个独立包。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| **3a. STT** | | | |
|
||||
| 3.1 | STT 接口定义 | `internal/ai/stt/stt.go` | `Service` 接口:`Recognize(ctx, audio []byte, opts Options) (string, error)`。`Options`: Encoding, SampleRate, Language |
|
||||
| 3.2 | Deepgram 实现 | `internal/ai/stt/deepgram.go` | WebSocket 连接 `wss://api.deepgram.com/v1/listen`,发送 PCM 音频,接收转录结果,5s 超时 |
|
||||
| 3.3 | STT 测试(mock) | `internal/ai/stt/deepgram_test.go` | httptest/WebSocket mock,验证连接、发送、超时 |
|
||||
| **3b. LLM** | | | |
|
||||
| 3.4 | LLM 接口定义 | `internal/ai/llm/llm.go` | `Service` 接口:`ChatStream(ctx, req Request) (<-chan Chunk, error)`。`Request`: Image, Text, History, Language。`Chunk`: Delta, Done, TokensUsed, Model |
|
||||
| 3.5 | OpenAI 实现 | `internal/ai/llm/openai.go` | `POST /v1/chat/completions` + `stream: true`,SSE 解析,10s 超时,image 以 `data:image/jpeg;base64,...` 传入 |
|
||||
| 3.6 | System Prompt 定义 | `internal/ai/llm/prompt.go` | 中文视觉助手提示词,根据 Language/DetailLevel 动态构建 |
|
||||
| 3.7 | LLM 测试(mock) | `internal/ai/llm/openai_test.go` | httptest mock SSE 流,验证流式解析、超时、错误处理 |
|
||||
| **3c. TTS** | | | |
|
||||
| 3.8 | TTS 接口定义 | `internal/ai/tts/tts.go` | `Service` 接口:`SynthesizeStream(ctx, textStream <-chan string, opts Options) (<-chan Chunk, error)`。`Chunk`: Audio []byte, IsLast |
|
||||
| 3.9 | OpenAI 实现 | `internal/ai/tts/openai.go` | `POST /v1/audio/speech` 模型 `tts-1`,逐句发送,返回 MP3 流,5s/句超时 |
|
||||
| 3.10 | TTS 测试(mock) | `internal/ai/tts/openai_test.go` | httptest mock,验证逐句合成、超时 |
|
||||
|
||||
---
|
||||
|
||||
### Phase 4:AI Orchestrator(核心编排)
|
||||
|
||||
**目标**:实现 STT → LLM → TTS 流式并行管道,这是后端最关键的业务逻辑。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 4.1 | Orchestrator 接口 | `internal/orchestrator/orchestrator.go` | `ProcessQuery(ctx, sessionID, req, history, sender)` — 接收查询并执行管道 |
|
||||
| 4.2 | Sender 接口 | `internal/orchestrator/sender.go` | 抽象 WS 推送:`SendSTTResult`, `SendLLMChunk`, `SendLLMDone`, `SendTTSAudio`, `SendError`,便于测试 |
|
||||
| 4.3 | 管道实现 | `internal/orchestrator/pipeline.go` | ① `stt.Recognize()` → 发送 `stt_result` ② `llm.ChatStream()` 并行消费 token → 发送 `llm_chunk` + 句子切分 → channel ③ `tts.SynthesizeStream()` 从 channel 读取 → 发送 `tts_audio` ④ 流结束 → 发送 `llm_done` |
|
||||
| 4.4 | 句子切分器 | `internal/orchestrator/splitter.go` | 按 `。!?\n.!?` 切分,buffer size 4 channel |
|
||||
| 4.5 | 错误降级 | 同上文件 | STT 失败→STT_ERROR+abort;LLM 超时→LLM_TIMEOUT;TTS 失败→静默跳过 |
|
||||
| 4.6 | Interrupt 支持 | 同上文件 | context cancel 触发所有流中止 |
|
||||
| 4.7 | Orchestrator 测试 | `internal/orchestrator/pipeline_test.go` | mock 三个 AI service + mock sender,验证完整流程、中断、错误降级 |
|
||||
|
||||
---
|
||||
|
||||
### Phase 5:WS Handler 完整接入
|
||||
|
||||
**目标**:将 Session Manager + Orchestrator 串入 WebSocket handler,实现端到端消息处理。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 5.1 | Client 扩展 | `internal/ws/handler.go` | 添加 `session.Manager`、`orchestrator.Orchestrator`、`context.CancelFunc`(用于 interrupt) |
|
||||
| 5.2 | query 处理 | 同上 | 解码 audio Base64 → `stt.Recognize` 的输入;Touch 会话;设置 active request;启动 `orchestrator.ProcessQuery` goroutine |
|
||||
| 5.3 | config 处理 | 同上 | 调用 `session.UpdateConfig()` |
|
||||
| 5.4 | interrupt 处理 | 同上 | 查找 active request 的 cancel func,调用 `cancel()`,ClearActiveRequest |
|
||||
| 5.5 | Disconnect 处理 | 同上 | 取消当前活跃请求(如有),不销毁会话 |
|
||||
|
||||
---
|
||||
|
||||
### Phase 6:REST API 补全
|
||||
|
||||
**目标**:补全设计文档中的 REST 端点。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 6.1 | Session 路由 | `internal/api/session.go` | `POST /api/sessions` 创建会话,`DELETE /api/sessions/:id` 销毁会话 |
|
||||
| 6.2 | Health 更新 | `cmd/server/main.go` | 从 SessionManager 获取 `active_sessions` 真实值 |
|
||||
| 6.3 | 路由注册 | `cmd/server/main.go` | 统一注册 REST + WS 路由,注入依赖 |
|
||||
|
||||
---
|
||||
|
||||
### Phase 7:Rate Limiter + Model Router(可选/MVP 后)
|
||||
|
||||
**目标**:防止滥用 + 智能模型选择,MVP 可简化或跳过。
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 7.1 | 令牌桶 Rate Limiter | `internal/middleware/ratelimit.go` | `golang.org/x/time/rate` 或自实现,按 session ID 限流 |
|
||||
| 7.2 | Rate Limiter 中间件 | `internal/middleware/ratelimit.go` | 在 WS query 路径上检查,超限返回 `RATE_LIMITED` |
|
||||
| 7.3 | Model Router | `internal/ai/router.go` | 规则引擎:简单识别→GPT-4o-mini,深度分析→GPT-4o,暂不实现 o1 |
|
||||
|
||||
---
|
||||
|
||||
### Phase 8:集成测试 + 文档同步
|
||||
|
||||
| # | 任务 | 文件 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 8.1 | WS 集成测试 | `internal/ws/handler_test.go` | 启动 Gin test server + gorilla websocket client,验证完整 query→stt_result→llm_chunk→llm_done→tts_audio 流程 |
|
||||
| 8.2 | 文档同步 | `docs/03-接口文档.md` | 代码实现与文档有偏差时更新文档 |
|
||||
| 8.3 | go.sum 清理 | `backend/` | `go mod tidy` 清理无用依赖 |
|
||||
|
||||
---
|
||||
|
||||
## 关键文件清单
|
||||
|
||||
```
|
||||
backend/
|
||||
cmd/server/main.go ← Phase 1.3, 1.4, 1.5, 6.2, 6.3
|
||||
internal/
|
||||
config/config.go ← 已完成,Phase 1.3 接入
|
||||
logger/logger.go ← Phase 1.1(新建)
|
||||
errors/codes.go ← Phase 1.2(新建)
|
||||
models/models.go ← 已完成,可能小幅扩展
|
||||
session/
|
||||
manager.go ← Phase 2.1(新建)
|
||||
memory.go ← Phase 2.2(新建)
|
||||
redis.go ← Phase 2.3(新建)
|
||||
memory_test.go ← Phase 2.4(新建)
|
||||
ai/
|
||||
stt/
|
||||
stt.go ← Phase 3.1(新建)
|
||||
deepgram.go ← Phase 3.2(新建)
|
||||
deepgram_test.go ← Phase 3.3(新建)
|
||||
llm/
|
||||
llm.go ← Phase 3.4(新建)
|
||||
openai.go ← Phase 3.5(新建)
|
||||
prompt.go ← Phase 3.6(新建)
|
||||
openai_test.go ← Phase 3.7(新建)
|
||||
tts/
|
||||
tts.go ← Phase 3.8(新建)
|
||||
openai.go ← Phase 3.9(新建)
|
||||
openai_test.go ← Phase 3.10(新建)
|
||||
router.go ← Phase 7.3(新建)
|
||||
orchestrator/
|
||||
orchestrator.go ← Phase 4.1(新建)
|
||||
sender.go ← Phase 4.2(新建)
|
||||
pipeline.go ← Phase 4.3, 4.4, 4.5, 4.6(新建)
|
||||
pipeline_test.go ← Phase 4.7(新建)
|
||||
api/
|
||||
session.go ← Phase 6.1(新建)
|
||||
middleware/
|
||||
ratelimit.go ← Phase 7.1, 7.2(新建)
|
||||
ws/
|
||||
handler.go ← Phase 5.1-5.5(修改)
|
||||
handler_test.go ← Phase 8.1(新建)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新增依赖
|
||||
|
||||
| 包 | 用途 | Phase |
|
||||
|----|------|-------|
|
||||
| `go.uber.org/zap` | 结构化日志 | 1 |
|
||||
| `github.com/redis/go-redis/v9` | Redis 客户端 | 2.3 |
|
||||
| `github.com/gorilla/websocket` | 已有,Deepgram WS 也复用 | 3.2 |
|
||||
|
||||
---
|
||||
|
||||
## 执行顺序与依赖关系
|
||||
|
||||
```
|
||||
Phase 1 (基础设施)
|
||||
↓
|
||||
Phase 2 (Session Manager)
|
||||
↓
|
||||
Phase 3 (AI 服务层) ← 可与 Phase 2 并行开发
|
||||
↓
|
||||
Phase 4 (Orchestrator) ← 依赖 Phase 2 + 3
|
||||
↓
|
||||
Phase 5 (WS Handler 接入) ← 依赖 Phase 4
|
||||
↓
|
||||
Phase 6 (REST API) ← 依赖 Phase 2
|
||||
↓
|
||||
Phase 7 (Rate Limiter + Router) ← 独立,可推后
|
||||
↓
|
||||
Phase 8 (集成测试 + 文档)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证方案
|
||||
|
||||
1. **单元测试**:每个模块独立测试,mock 外部依赖(AI API、Redis)
|
||||
2. **集成测试**:`httptest` 启动 Gin server,用 gorilla/websocket 客户端模拟完整 query 流程
|
||||
3. **端到端手动测试**:启动后端 → 打开前端 → 摄像头+麦克风对话 → 验证 stt_result / llm_chunk / tts_audio 消息流
|
||||
4. **go vet + go test ./...** 通过
|
||||
|
||||
---
|
||||
|
||||
## 设计文档参考
|
||||
|
||||
- 接口规范(最高优先级):`docs/03-接口文档.md`
|
||||
- 系统架构:`docs/02-系统架构.md`
|
||||
- 技术选型:`docs/04-技术选型.md`
|
||||
- 成本控制:`docs/08-成本控制.md`
|
||||
Reference in New Issue
Block a user