# 架构设计 ## 项目概述 CamTalk 是一款**多模态实时 AI 视觉对话助手**。用户通过摄像头和麦克风与 AI 交互,AI 理解视觉场景和语音输入后,以文字和语音形式给出自然回应。 核心挑战在于三个维度之间的张力: | 维度 | 关键问题 | |------|---------| | 视觉理解 | 如何准确理解摄像头画面中的人物、物体、场景? | | 语音交互 | 如何让对话像真人交流一样自然、低延迟? | | 成本控制 | 实时视频流 + LLM 推理,如何避免账单爆炸? | ## 系统架构 三层架构:**前端做轻量预处理,后端做智能编排,云端 AI 服务按需调用**。 ```mermaid graph TB subgraph Browser["浏览器客户端"] UI["UI 渲染层
React 18 + TypeScript"] Edge["边缘预处理层
VAD / 关键帧检测"] Media["媒体采集层
Camera / Microphone"] end subgraph Gateway["Go 网关"] WS["WebSocket Handler
连接管理 / 消息分发"] Session["Session Manager
会话状态 / 对话历史"] Orch["AI Orchestrator
Eino Graph 声明式编排"] Auth["Auth 模块
JWT / bcrypt"] REST["REST API
健康检查 / 对话管理"] Store["Store 层
Repository 接口"] end subgraph AI["云端 AI 服务"] STT["STT
Deepgram / MiMo ASR"] LLM["LLM
GPT-4o / 通义千问"] TTS["TTS
OpenAI TTS / MiMo TTS"] end subgraph Storage["存储层"] Mem["Memory
进程内缓存"] Redis["Redis
会话状态"] PG["PostgreSQL
持久化存储"] end Media --> Edge Edge -->|"query (image+audio)"| WS UI <-->|"WebSocket"| WS WS --> Session WS --> Orch Orch --> STT Orch --> LLM Orch --> TTS Session --> Store Store --> Mem Store --> Redis Store --> PG REST --> Session WS --> Auth ``` > 为什么单独加一层 Go 网关,而不是让前端直连 AI API?1)API Key 安全性;2)统一的速率限制和成本管控;3)多模型路由逻辑集中在一处便于维护。 ## 核心交互流程 一次完整的"用户提问 → AI 回答"流程: ```mermaid sequenceDiagram participant B as 浏览器 participant G as Go 网关(Eino Graph) participant S as STT participant L as LLM(ChatModel) participant T as TTS B->>B: VAD 检测到语音结束 B->>G: query {image, audio} Note over G: EinoOrchestrator 启动 Graph.Stream() G->>S: STT Lambda:音频 → 文本 S-->>G: 识别文本 G-->>B: stt_result {text} G->>G: History Lambda:组装提示词 + 历史 + 多模态消息 G->>L: ChatModel Node:流式推理 loop LLM 流式输出(Callback OnEndWithStreamOutput) L-->>G: token delta G-->>B: llm_chunk {delta} end G->>G: Msg2Str + Splitter Lambda:句子切分 G->>T: TTS Lambda:逐句合成 T-->>G: 音频 chunk G-->>B: tts_audio {audio} G->>G: Done Lambda:发送完成通知 G-->>B: llm_done {full_text, tokens} G-->>B: tts_audio {final: true} ``` **关键优化**:Eino Graph 以 Stream 模式运行,ChatModel 的 token 流通过 Callback 的 `OnEndWithStreamOutput` 实时推送到客户端(`llm_chunk`),同时 Splitter 节点将 token 流切分为句子,TTS 节点逐句合成并推送音频。LLM 文本流和 TTS 音频流**并行推送**,用户感知延迟大幅降低。 ## 技术栈 ### 前端 | 技术 | 选型 | 选择理由 | |------|------|---------| | 框架 | React 18 + TypeScript | 组件化开发,类型安全,生态成熟 | | 构建 | Vite | 开发热更新快,构建产物小 | | 实时通信 | WebSocket(原生 API) + 自封装连接管理 | 浏览器原生支持,封装心跳/重连/消息分发 | | 语音检测 | @ricky0123/vad-web | 基于 WebRTC VAD,纯前端零延迟 | | 媒体采集 | MediaDevices API | 浏览器原生摄像头/麦克风访问 | ### 后端 | 技术 | 选型 | 选择理由 | |------|------|---------| | 语言 | Go | 高并发 goroutine 模型,适合长连接管理 | | HTTP 框架 | Gin | 高性能 HTTP 路由,中间件生态成熟 | | WebSocket | gorilla/websocket | Go 生态最成熟的 WebSocket 库 | | 会话存储 | Memory / Redis / PostgreSQL 三级存储 | 进程内存零依赖,Redis 支持多实例,PG 持久化。TieredManager 自动降级 | | AI 编排 | CloudWeGo Eino Graph | 声明式 DAG 编排,Stream 模式,Callback AOP | | 持久化存储 | PostgreSQL | 对话历史、用户数据、会话元数据 | | 配置管理 | Viper + godotenv | 支持 YAML + .env + 环境变量覆盖 | | 日志 | Zap | 高性能结构化日志 | ### AI 服务 | 能力 | 默认方案 | 备选方案 | |------|---------|---------| | 多模态 LLM | DashScope qwen3-vl-plus | GPT-4o 等 OpenAI 兼容模型 | | 语音识别 STT | MiMo ASR(小米) | Deepgram | | 语音合成 TTS | MiMo TTS(小米) | OpenAI TTS | > Go 网关的 AI 服务层统一封装不同服务商的调用接口,通过配置切换 provider。LLM 通过 Eino 框架的 `eino-ext/components/model/openai` 组件接入,支持任何 OpenAI 兼容接口。 ## 后端模块 ```mermaid graph LR subgraph Entry["入口层"] Main["main.go
依赖注入 / 启动"] end subgraph Transport["传输层"] WSH["WebSocket Handler
连接管理 / 认证"] APH["REST API Handlers
Auth / Conversation / Health"] end subgraph Business["业务层"] SM["Session Manager
会话生命周期"] ORCH["EinoOrchestrator
Eino Graph 编排"] AS["Auth Service
注册/登录/刷新/登出"] end subgraph Eino_Layer["Eino 编排层"] PG["PipelineGraph
7 节点 DAG"] CB["Callback Handler
LLM token 推送"] ST["PipelineState
跨节点状态"] end subgraph AI_Layer["AI 服务层"] STT_S["STT Service
MiMo / Deepgram"] LLM_S["ChatModel
eino-ext OpenAI 兼容"] TTS_S["TTS Service
MiMo / OpenAI"] end subgraph Data["数据层"] UR["UserRepository"] MR["MessageRepository"] SR["SessionRepository"] end Main --> WSH Main --> APH Main --> SM Main --> ORCH Main --> AS WSH --> SM WSH --> ORCH APH --> SM APH --> AS ORCH --> PG PG --> CB PG --> ST PG --> STT_S PG --> LLM_S PG --> TTS_S SM --> MR SM --> SR AS --> UR ``` | 模块 | 职责 | |------|------| | WebSocket Handler | 管理客户端连接生命周期,JWT 认证,conversation_id 恢复,单播消息推送 | | Session Manager | 维护用户会话状态、对话历史。三级存储(Memory → Redis → PostgreSQL),30 分钟 TTL,Write-Through 到 PG | | Eino 编排层 | 基于 CloudWeGo Eino Graph 的声明式 AI 编排。7 节点 DAG(STT→History→ChatModel→Msg2Str→Splitter→TTS→Done),Stream 模式调用,Callback 实现 LLM token 实时推送 | | AI Orchestrator | `EinoOrchestrator` 适配器,包装 Eino Graph 实现 `Orchestrator` 接口。context 取消 + 超时控制 | | AI Service Layer | AI 服务抽象层,多 provider 支持(Deepgram/MiMo/OpenAI 等) | | Auth | 用户认证与授权。JWT (HS256) 双 token 轮转,bcrypt 密码哈希,Gin 中间件 | | Store | 持久化存储层。UserRepository / MessageRepository / SessionRepository,内存 + PostgreSQL 双实现 | | REST API | 健康检查、认证、对话管理端点 | | Logger | Zap 结构化日志 | | Models | 数据模型定义 | | Migrations | 数据库版本化迁移,嵌入式 SQL 文件自动执行 | | Model Router | 根据请求类型选择 AI 模型(待实现) | | Rate Limiter | 令牌桶限流。详细设计见 [令牌桶限流设计](./13-令牌桶限流设计.md) | ## 前端组件 | 组件 | 职责 | |------|------| | LandingPage | 未登录时的着陆页(营销展示),内嵌 LoginModal 登录/注册弹窗 | | AuthPage | 登录/注册表单(备用,已被 LandingPage + LoginModal 替代) | | CameraManager | 摄像头流采集 | | MicManager | 麦克风音频采集 | | EdgeProcessor | VAD + 关键帧检测(Canvas 像素比较) | | WebSocketManager | WS 连接生命周期管理 | | ChatPanel | 消息展示、流式回复、文本输入、场景选择 | | VideoPreview | 摄像头画面预览 | | SessionSidebar | 左侧抽屉式对话列表(搜索、重命名、删除、时间分组) | | ConfigPanel | 右侧抽屉式配置面板(主题、TTS 开关、detail level、语言、场景、登出) | | Toast | 轻量通知提示(3 秒自动消失) | 核心 Hook:`useVisionSession()` 封装一次完整的视觉对话会话(摄像头、VAD、WebSocket、消息状态、认证、场景模式)。`useSessionList()` 通过 REST API 管理对话列表 CRUD(列表、创建、删除、重命名、加载消息)。 ### 前端会话状态模型(三态) 前端 UI 存在三个会话状态,由 `isConnected` 和 `isCameraOn` 联合决定: ``` ┌──────────┐ startSession() ┌──────────┐ │ initial │ ──────────────────→ │ video │ │ 初始态 │ │ 视频通话 │ └──────────┘ └──────────┘ ↑ │ │ stopSession() stopVideo() │ │ │ ▼ │ ┌──────────┐ └──────────────────────── │ textOnly │ │ 文字对话 │ └──────────┘ │ startSession() │ ▼ ┌──────────┐ │ video │ └──────────┘ ``` | 状态 | 条件 | WebSocket | 摄像头 | 消息 | 文字输入 | |------|------|-----------|--------|------|---------| | `initial` | `!isConnected && messages.length === 0` | 断开 | 关闭 | 空 | 可用(自动连接) | | `video` | `isConnected && isCameraOn` | 连接 | 开启 | 有 | 可用 | | `textOnly` | `isConnected && !isCameraOn` | 连接 | 关闭 | 保留 | 可用 | - **`stopVideo()`**:停止摄像头/麦克风/VAD,保持 WebSocket 连接和消息历史,用户可继续文字对话 - **`stopSession()`**:完全断开 WebSocket、清空消息、重置状态,回到初始态 ## 数据库设计 ### ER 关系 ```mermaid erDiagram users ||--o{ sessions : "1:N" users ||--o{ refresh_tokens : "1:N" sessions ||--o{ messages : "1:N" users { uuid id PK varchar username UK varchar password_hash timestamptz created_at timestamptz updated_at } sessions { uuid id PK uuid user_id FK varchar title jsonb config timestamptz created_at timestamptz updated_at } messages { bigserial id PK uuid session_id FK varchar role text content integer tokens_used timestamptz created_at } refresh_tokens { bigserial id PK uuid user_id FK varchar token_hash UK timestamptz expires_at timestamptz created_at } ``` ### 表结构 ```sql -- 用户表 CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR(64) NOT NULL UNIQUE, password_hash VARCHAR(256) NOT NULL, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); -- 会话表 CREATE TABLE sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, title VARCHAR(128) DEFAULT '新对话', config JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); -- 消息表 CREATE TABLE messages ( id BIGSERIAL PRIMARY KEY, session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, role VARCHAR(16) NOT NULL, content TEXT NOT NULL, tokens_used INTEGER DEFAULT 0, created_at TIMESTAMPTZ DEFAULT now() ); -- 刷新令牌表 CREATE TABLE refresh_tokens ( id BIGSERIAL PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(256) NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT now() ); ``` ### 存储策略 | 场景 | 存储方案 | 说明 | |------|---------|------| | 默认 | Memory(进程内) | 零依赖,快速启动。MemoryManager 支持 Write-Through 到 PG | | 持久化 | Memory + PostgreSQL | 通过 `storage.persistence.enabled: true` 启用,MemoryManager 注入 PG Repository | | 多实例 | Redis(独立) | 通过配置切换到 RedisManager,适合多实例部署 | | 三级存储 | TieredManager | L1 Memory → L2 Redis → L3 PostgreSQL,自动降级 | **三级存储架构**(`TieredManager`): ``` TieredManager ├── L1: Memory(进程内缓存,微秒级读写) ├── L2: Redis(分布式缓存,毫秒级读写) └── L3: PostgreSQL(持久化存储,冷数据) ``` - **读取路径**:L1 → L2 → L3,逐级回源,命中后向上回填 - **写入路径**:L1 → L2(同步) → L3(异步) - **健康检查**:后台 goroutine 每 30 秒 ping Redis,故障时自动降级为 L1+L3 模式 - **冷热分离**:L1/L2 存"热数据"(当前对话上下文),L3 存"冷数据"(历史记录) ## 认证设计 采用 **JWT 双 token 轮转认证机制**,结合 bcrypt 密码哈希和 Refresh Token Rotation 安全策略。详细设计见 [鉴权体系设计](./12-鉴权体系设计.md)。 ### 核心组件 | 组件 | 职责 | |------|------| | TokenManager | JWT 生成与验证(HS256 算法) | | AuthService | 认证业务逻辑(注册/登录/刷新/登出) | | AuthMiddleware | Gin 中间件,校验 access_token 并注入用户信息 | | PasswordUtil | bcrypt 密码哈希(cost=10) | ### 认证流程 ```mermaid sequenceDiagram participant C as 客户端 participant G as Go 网关 participant DB as PostgreSQL Note over C,DB: 注册流程 C->>G: POST /api/auth/register {username, password} G->>G: bcrypt hash 密码 G->>DB: INSERT users G->>G: 生成 access_token + refresh_token G->>DB: 存 SHA256(refresh_token) G-->>C: {user, access_token, refresh_token} Note over C,DB: 登录流程 C->>G: POST /api/auth/login {username, password} G->>DB: 查 users by username G->>G: bcrypt.CompareHashAndPassword G->>G: 生成 token pair G->>DB: 存 SHA256(refresh_token) G-->>C: {user, access_token, refresh_token} Note over C,DB: Token 刷新(轮转) C->>G: POST /api/auth/refresh {refresh_token} G->>G: 校验签名和过期 G->>DB: 验证 hash 存在 G->>DB: 撤销旧 refresh_token G->>G: 生成新 token pair G->>DB: 存新 refresh_token hash G-->>C: {access_token, refresh_token} ``` ### Token 策略 - **access_token**:15 分钟有效,用于 API 认证和 WebSocket 连接 - **refresh_token**:7 天有效,用于刷新 access_token - **Refresh Token Rotation**:每次 refresh 都生成新的 token pair,旧 refresh_token 立即失效 - **复用检测**:检测到已删除的 refresh_token 被复用时,吊销该用户的所有 refresh_token ### 安全机制 1. **密码安全**:bcrypt 算法(cost=10),自动生成盐值,防彩虹表攻击 2. **Token 安全**: - access_token 短有效期(15 分钟),降低泄露风险 - refresh_token 使用 SHA256 哈希存储,不存储原始 token - Refresh Token Rotation 防重放攻击 - 复用检测 + 自动吊销机制 3. **传输安全**:HTTPS 强制,CORS 限制,HttpOnly Cookie 存储 refresh_token 4. **防攻击策略**: - 防暴力破解:可选速率限制 - 防枚举攻击:统一错误信息 - 防 Token 泄露:复用检测 + 自动吊销 ### WebSocket 认证 连接地址:`ws://host/ws?token=&conversation_id=` - HTTP Upgrade 前校验 token - 校验失败返回 401 Unauthorized - 校验成功后,user_id 和 username 注入到连接上下文 ### 配置 ```yaml auth: jwt_secret: "" # JWT 签名密钥(必须通过 CAMTALK_AUTH_JWT_SECRET 环境变量设置) access_ttl: 15 # access_token 有效期(分钟) refresh_ttl: 10080 # refresh_token 有效期(分钟,7天) ``` > **安全要求**:`JWT_SECRET` **必须**通过环境变量设置,不能写入配置文件。生产环境使用 `openssl rand -hex 32` 生成随机密钥。 ## 部署架构 ```mermaid graph TB User["用户浏览器"] --> Nginx subgraph Nginx["Nginx 反向代理"] Static["/ → 前端静态资源"] API["/api/* → Go Gateway"] WS_Proxy["/ws → Go Gateway"] end subgraph Gateway_Pool["Go Gateway 实例"] G1["Gateway-1"] G2["Gateway-2"] GN["Gateway-N"] end Nginx --> G1 Nginx --> G2 Nginx --> GN G1 --> Redis G2 --> Redis GN --> Redis G1 --> PG_DB["PostgreSQL"] G2 --> PG_DB GN --> PG_DB G1 --> AI_Services["AI Services(外部 API)"] G2 --> AI_Services GN --> AI_Services ``` **跨域策略**:Nginx 将前端(`/`)、REST API(`/api/*`)、WebSocket(`/ws`)统一反代到同一域名,浏览器无跨域问题。 **开发环境**:前端 Vite :5173 通过 `server.proxy` 转发 `/ws` 和 `/api` 到后端 :8080,无需硬编码端口。