docs: 补充配置管理规范(Viper + 环境变量)
All checks were successful
Backend CI / ci (pull_request) Successful in 1m5s
Frontend CI / ci (pull_request) Successful in 1m40s

- 03-接口文档: 新增第六章配置管理(Go 配置结构体、YAML 配置示例、环境变量覆盖规则、Viper 加载代码、启动命令示例)
- 03-接口文档: 原七~九章重新编号为八~十章
- 02-系统架构: 配置管理行补充交叉引用
- README.md: 索引补充配置管理关键词
This commit is contained in:
hhs
2026-06-13 13:31:48 +08:00
parent ee557eaa3d
commit 360b2a4f50
3 changed files with 235 additions and 6 deletions

View File

@@ -35,7 +35,7 @@
| WebSocket | gorilla/websocket | Go 生态最成熟的 WebSocket 库 | | WebSocket | gorilla/websocket | Go 生态最成熟的 WebSocket 库 |
| 会话存储 | Redis | 高速 KV 存储,适合会话状态和上下文缓存 | | 会话存储 | Redis | 高速 KV 存储,适合会话状态和上下文缓存 |
| 持久化存储 | PostgreSQL | 对话历史、用量统计、用户偏好MVP 阶段可选) | | 持久化存储 | PostgreSQL | 对话历史、用量统计、用户偏好MVP 阶段可选) |
| 配置管理 | Viper | 支持多格式配置,环境变量覆盖 | | 配置管理 | Viper | 支持 YAML + 环境变量覆盖,详见 `03-接口文档.md` 第六章 |
| 日志 | Zap | 高性能结构化日志 | | 日志 | Zap | 高性能结构化日志 |
### AI 服务 ### AI 服务

View File

@@ -673,7 +673,236 @@ if cfg.Redis.Addr != "" {
--- ---
## 六、数据模型 ## 六、配置管理
使用 Viper 加载配置,支持 YAML 文件 + 环境变量覆盖。**环境变量优先级高于配置文件**。
### 配置文件位置
```
backend/config.yaml # 默认加载
backend/config.dev.yaml # 开发环境go run 时使用)
backend/config.prod.yaml # 生产环境
```
Viper 加载顺序:先读 `config.yaml`,再根据 `APP_ENV` 环境变量尝试读 `config.{env}.yaml` 覆盖,最后所有环境变量自动覆盖对应字段。
### Go 配置结构体
```go
// Config 应用配置。
type Config struct {
App AppConfig `mapstructure:"app"`
Server ServerConfig `mapstructure:"server"`
Redis RedisConfig `mapstructure:"redis"`
AI AIConfig `mapstructure:"ai"`
Storage StorageConfig `mapstructure:"storage"`
Log LogConfig `mapstructure:"log"`
}
type AppConfig struct {
Env string `mapstructure:"env"` // "dev" | "prod",默认 "dev"
Version string `mapstructure:"version"` // 由编译时注入
}
type ServerConfig struct {
Host string `mapstructure:"host"` // 默认 "0.0.0.0"
Port int `mapstructure:"port"` // 默认 8080
ReadTimeout int `mapstructure:"read_timeout"` // 秒,默认 30
WriteTimeout int `mapstructure:"write_timeout"` // 秒,默认 30
}
type RedisConfig struct {
Addr string `mapstructure:"addr"` // "localhost:6379"
Password string `mapstructure:"password"` // 无密码留空
DB int `mapstructure:"db"` // 默认 0
}
type AIConfig struct {
STT STTConfig `mapstructure:"stt"`
LLM LLMConfig `mapstructure:"llm"`
TTS TTSConfig `mapstructure:"tts"`
}
type STTConfig struct {
Provider string `mapstructure:"provider"` // "deepgram"
APIKey string `mapstructure:"api_key"`
Endpoint string `mapstructure:"endpoint"` // 默认 "wss://api.deepgram.com/v1/listen"
}
type LLMConfig struct {
Provider string `mapstructure:"provider"` // "openai"
APIKey string `mapstructure:"api_key"`
Model string `mapstructure:"model"` // 默认 "gpt-4o"
Endpoint string `mapstructure:"endpoint"` // 默认 "https://api.openai.com/v1"
Timeout int `mapstructure:"timeout"` // 秒,默认 10
}
type TTSConfig struct {
Provider string `mapstructure:"provider"` // "openai"
APIKey string `mapstructure:"api_key"`
Voice string `mapstructure:"voice"` // 默认 "alloy"
Speed float64 `mapstructure:"speed"` // 默认 1.0
Endpoint string `mapstructure:"endpoint"` // 默认 "https://api.openai.com/v1"
Timeout int `mapstructure:"timeout"` // 秒,默认 5
}
type StorageConfig struct {
Driver string `mapstructure:"driver"` // "memory" | "postgres"
DSN string `mapstructure:"dsn"` // PostgreSQL 连接串driver=postgres 时必填
}
type LogConfig struct {
Level string `mapstructure:"level"` // "debug" | "info" | "warn" | "error",默认 "info"
Format string `mapstructure:"format"` // "json" | "console",生产用 json
}
```
### 配置文件示例
```yaml
# config.yaml — 所有环境共享的默认值
app:
env: dev
server:
host: "0.0.0.0"
port: 8080
read_timeout: 30
write_timeout: 30
redis:
addr: "localhost:6379"
password: ""
db: 0
ai:
stt:
provider: deepgram
endpoint: "wss://api.deepgram.com/v1/listen"
llm:
provider: openai
model: gpt-4o
endpoint: "https://api.openai.com/v1"
timeout: 10
tts:
provider: openai
voice: alloy
speed: 1.0
endpoint: "https://api.openai.com/v1"
timeout: 5
storage:
driver: memory
log:
level: info
format: console
```
### 环境变量覆盖规则
Viper 自动将配置项映射为环境变量,规则:**前缀 `CAMTALK_` + 路径大写用 `_` 连接**。
| 配置项 | 环境变量 | 示例 |
|--------|---------|------|
| `server.port` | `CAMTALK_SERVER_PORT` | `8080` |
| `redis.addr` | `CAMTALK_REDIS_ADDR` | `redis:6379` |
| `redis.password` | `CAMTALK_REDIS_PASSWORD` | — |
| `ai.stt.api_key` | `CAMTALK_AI_STT_API_KEY` | — |
| `ai.llm.api_key` | `CAMTALK_AI_LLM_API_KEY` | — |
| `ai.tts.api_key` | `CAMTALK_AI_TTS_API_KEY` | — |
| `ai.llm.model` | `CAMTALK_AI_LLM_MODEL` | `gpt-4o` |
| `storage.driver` | `CAMTALK_STORAGE_DRIVER` | `postgres` |
| `storage.dsn` | `CAMTALK_STORAGE_DSN` | — |
| `app.env` | `CAMTALK_APP_ENV` | `prod` |
| `log.level` | `CAMTALK_LOG_LEVEL` | `warn` |
| `log.format` | `CAMTALK_LOG_FORMAT` | `json` |
> API Key 和密码**只通过环境变量注入**,不写入配置文件,避免泄露到版本控制。
### 配置加载代码
```go
// internal/config/config.go
func Load() (*Config, error) {
v := viper.New()
// 1. 读默认配置文件
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath("./config") // go run 时
v.AddConfigPath(".") // 二进制运行时
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("read config: %w", err)
}
}
// 2. 按环境覆盖
env := os.Getenv("CAMTALK_APP_ENV")
if env == "" {
env = "dev"
}
v.SetConfigName("config." + env)
v.MergeInConfig() // 忽略文件不存在
// 3. 环境变量覆盖
v.SetEnvPrefix("CAMTALK")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
// 4. 解析
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("unmarshal config: %w", err)
}
return &cfg, nil
}
```
### main.go 集成
```go
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("load config: %v", err)
}
r := gin.Default()
// 使用 cfg.Server.Port 替代硬编码 :8080
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
log.Printf("CamTalk gateway starting on %s (env=%s)", addr, cfg.App.Env)
r.Run(addr)
}
```
### 启动方式
```bash
# 开发环境(默认 config.yamlAPI Key 通过环境变量注入)
CAMTALK_AI_LLM_API_KEY=sk-xxx \
CAMTALK_AI_STT_API_KEY=xxx \
go run ./cmd/server
# 生产环境
CAMTALK_APP_ENV=prod \
CAMTALK_REDIS_ADDR=redis:6379 \
CAMTALK_AI_LLM_API_KEY=sk-xxx \
CAMTALK_AI_STT_API_KEY=xxx \
CAMTALK_AI_TTS_API_KEY=xxx \
CAMTALK_STORAGE_DRIVER=postgres \
CAMTALK_STORAGE_DSN="postgres://user:pass@db:5432/camtalk?sslmode=disable" \
CAMTALK_LOG_LEVEL=warn \
CAMTALK_LOG_FORMAT=json \
./bin/camtalk
```
---
## 七、数据模型
> Go 和 TypeScript 的数据模型定义见下方。AI 服务层的 Go 模型见上方"AI 服务层接口"章节。 > Go 和 TypeScript 的数据模型定义见下方。AI 服务层的 Go 模型见上方"AI 服务层接口"章节。
@@ -751,7 +980,7 @@ type ClientMessage =
--- ---
## 、扩展接口设计 ## 、扩展接口设计
通过 Repository 接口隔离存储层MVP 用内存实现,后续替换为数据库——业务逻辑零改动。 通过 Repository 接口隔离存储层MVP 用内存实现,后续替换为数据库——业务逻辑零改动。
@@ -827,7 +1056,7 @@ func NewApp(cfg *Config) *App {
--- ---
## 、错误码 ## 、错误码
| 错误码 | 含义 | 客户端处理建议 | | 错误码 | 含义 | 客户端处理建议 |
|--------|------|--------------| |--------|------|--------------|
@@ -842,7 +1071,7 @@ func NewApp(cfg *Config) *App {
| `TTS_ERROR` | 语音合成失败 | 静默回退到纯文本回复 | | `TTS_ERROR` | 语音合成失败 | 静默回退到纯文本回复 |
| `INTERNAL_ERROR` | 服务端内部错误 | 提示用户重试 | | `INTERNAL_ERROR` | 服务端内部错误 | 提示用户重试 |
## 、连接管理 ## 、连接管理
**心跳机制**:客户端每 30 秒发送 `ping`,服务端回复 `pong`。超过 60 秒无 `ping`,服务端判定连接断开并清理会话资源。 **心跳机制**:客户端每 30 秒发送 `ping`,服务端回复 `pong`。超过 60 秒无 `ping`,服务端判定连接断开并清理会话资源。

View File

@@ -8,7 +8,7 @@ CamTalk 是一款多模态实时 AI 视觉对话助手。用户通过摄像头
|------|------| |------|------|
| [01-项目概述](01-项目概述.md) | 项目目标、核心挑战、交付物 | | [01-项目概述](01-项目概述.md) | 项目目标、核心挑战、交付物 |
| [02-系统架构](02-系统架构.md) | 三层架构、技术栈、核心交互流程、前后端模块、存储策略、部署架构 | | [02-系统架构](02-系统架构.md) | 三层架构、技术栈、核心交互流程、前后端模块、存储策略、部署架构 |
| [03-接口文档](03-接口文档.md) | WebSocket 协议、REST API、**AI 服务层接口**、**编排器设计**、**Session ManagerRedis**、数据模型、错误码、连接管理(**实现时首先阅读** | | [03-接口文档](03-接口文档.md) | WebSocket 协议、REST API、**AI 服务层接口**、**编排器设计**、**Session Manager**、**配置管理Viper**、数据模型、错误码、连接管理(**实现时首先阅读** |
| [04-技术选型](04-技术选型.md) | 持久化层PostgreSQL和前端边缘处理层的选型对比与决策理由 | | [04-技术选型](04-技术选型.md) | 持久化层PostgreSQL和前端边缘处理层的选型对比与决策理由 |
| [05-用户故事](05-用户故事.md) | P0/P1/P2 用户故事、验收标准、优先级决策依据 | | [05-用户故事](05-用户故事.md) | P0/P1/P2 用户故事、验收标准、优先级决策依据 |
| [06-语音交互](06-语音交互.md) | VAD → STT → LLM → TTS 全链路、延迟优化 | | [06-语音交互](06-语音交互.md) | VAD → STT → LLM → TTS 全链路、延迟优化 |