## 高优先级修复 - stats_service_impl: 使用 strings.SplitN 替代错误的索引分割 - provider_handler: 使用 errors.Is(err, gorm.ErrDuplicatedKey) 替代字符串匹配 - client: 重写 isNetworkError 使用 errors.As/Is 类型安全判断 - proxy_handler: 使用 encoding/json 标准库解析 JSON(extractModelName、isStreamRequest) ## 中优先级修复 - stats_handler: 添加 parseDateParam 辅助函数消除重复日期解析 - pkg/errors: 新增 ErrRequestCreate/Send/ResponseRead 错误类型和 WithCause 方法 - client: 使用结构化错误替代 fmt.Errorf - ConversionEngine: logger 依赖注入,替换所有 zap.L() 调用 ## 低优先级修复 - encoder: 删除 joinStrings,使用 strings.Join - adapter: 删除 modelInfoRegex 正则,使用 isModelInfoPath 字符串函数 ## 文档更新 - README.md: 添加公共库使用指南和编码规范章节 - specs: 同步 delta specs 到 main specs(error-handling、structured-logging、request-validation) ## 归档 - openspec/changes/archive/2026-04-20-refactor-backend-code-quality/
390 lines
10 KiB
Markdown
390 lines
10 KiB
Markdown
# AI Gateway Backend
|
||
|
||
AI 网关后端服务,提供统一的大模型 API 代理接口。
|
||
|
||
## 功能特性
|
||
|
||
- 支持 OpenAI 协议(`/openai/v1/...`)
|
||
- 支持 Anthropic 协议(`/anthropic/v1/...`)
|
||
- 支持 Hub-and-Spoke 跨协议双向转换(OpenAI ↔ Anthropic)
|
||
- 同协议透传(零语义损失、零序列化开销)
|
||
- 支持流式响应(SSE)
|
||
- 支持 Function Calling / Tools
|
||
- 支持 Thinking / Reasoning
|
||
- 支持扩展层接口(Models、Embeddings、Rerank)
|
||
- 多供应商配置和路由
|
||
- 用量统计
|
||
- 结构化日志(zap + lumberjack)
|
||
- YAML 配置管理
|
||
- 请求验证
|
||
- 中间件支持(请求 ID、日志、恢复、CORS)
|
||
|
||
## 技术栈
|
||
|
||
- **语言**: Go 1.26+
|
||
- **HTTP 框架**: Gin
|
||
- **ORM**: GORM
|
||
- **数据库**: SQLite
|
||
- **日志**: zap + lumberjack
|
||
- **配置**: gopkg.in/yaml.v3
|
||
- **验证**: go-playground/validator/v10
|
||
- **迁移**: goose
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
backend/
|
||
├── cmd/
|
||
│ └── server/
|
||
│ └── main.go # 主程序入口(依赖注入)
|
||
├── internal/
|
||
│ ├── config/ # 配置管理
|
||
│ │ ├── config.go # 配置加载/保存/验证
|
||
│ │ └── models.go # GORM 数据模型
|
||
│ ├── domain/ # 领域模型
|
||
│ │ ├── provider.go
|
||
│ │ ├── model.go
|
||
│ │ ├── stats.go
|
||
│ │ └── route.go
|
||
│ ├── handler/ # HTTP 处理器
|
||
│ │ ├── middleware/ # 中间件
|
||
│ │ │ ├── request_id.go
|
||
│ │ │ ├── logging.go
|
||
│ │ │ ├── recovery.go
|
||
│ │ │ └── cors.go
|
||
│ │ ├── proxy_handler.go # 统一代理处理器
|
||
│ │ ├── provider_handler.go
|
||
│ │ ├── model_handler.go
|
||
│ │ └── stats_handler.go
|
||
│ ├── conversion/ # 协议转换引擎
|
||
│ │ ├── canonical/ # Canonical Model
|
||
│ │ │ ├── types.go # 核心请求/响应类型
|
||
│ │ │ ├── stream.go # 流式事件类型
|
||
│ │ │ └── extended.go # 扩展层 Models
|
||
│ │ ├── openai/ # OpenAI 协议适配器
|
||
│ │ │ ├── types.go
|
||
│ │ │ ├── adapter.go
|
||
│ │ │ ├── decoder.go
|
||
│ │ │ ├── encoder.go
|
||
│ │ │ ├── stream_decoder.go
|
||
│ │ │ └── stream_encoder.go
|
||
│ │ ├── anthropic/ # Anthropic 协议适配器
|
||
│ │ │ ├── types.go
|
||
│ │ │ ├── adapter.go
|
||
│ │ │ ├── decoder.go
|
||
│ │ │ ├── encoder.go
|
||
│ │ │ ├── stream_decoder.go
|
||
│ │ │ └── stream_encoder.go
|
||
│ │ ├── adapter.go # ProtocolAdapter 接口 + Registry
|
||
│ │ ├── stream.go # StreamDecoder/Encoder/Converter
|
||
│ │ ├── middleware.go # Middleware 接口和 Chain
|
||
│ │ ├── engine.go # ConversionEngine 门面
|
||
│ │ ├── errors.go # ConversionError
|
||
│ │ ├── interface.go # InterfaceType 枚举
|
||
│ │ └── provider.go # TargetProvider
|
||
│ ├── provider/ # 供应商客户端
|
||
│ │ └── client.go
|
||
│ ├── repository/ # 数据访问层
|
||
│ │ ├── provider_repo.go # 接口定义
|
||
│ │ ├── provider_repo_impl.go
|
||
│ │ ├── model_repo.go
|
||
│ │ ├── model_repo_impl.go
|
||
│ │ ├── stats_repo.go
|
||
│ │ └── stats_repo_impl.go
|
||
│ └── service/ # 业务逻辑层
|
||
│ ├── provider_service.go # 接口定义
|
||
│ ├── provider_service_impl.go
|
||
│ ├── model_service.go
|
||
│ ├── model_service_impl.go
|
||
│ ├── routing_service.go
|
||
│ ├── routing_service_impl.go
|
||
│ ├── stats_service.go
|
||
│ └── stats_service_impl.go
|
||
├── pkg/ # 公共包
|
||
│ ├── errors/ # 结构化错误
|
||
│ │ ├── errors.go
|
||
│ │ └── wrap.go
|
||
│ ├── logger/ # 日志系统
|
||
│ │ ├── logger.go
|
||
│ │ ├── rotate.go
|
||
│ │ └── context.go
|
||
│ └── validator/ # 验证器
|
||
│ └── validator.go
|
||
├── migrations/ # 数据库迁移
|
||
│ ├── 001_initial_schema.sql
|
||
│ └── 002_add_indexes.sql
|
||
├── tests/ # 测试
|
||
│ ├── helpers.go
|
||
│ ├── integration/
|
||
│ ├── unit/
|
||
│ └── testdata/
|
||
├── Makefile
|
||
├── go.mod
|
||
└── README.md
|
||
```
|
||
|
||
## 架构
|
||
|
||
采用三层架构(handler → service → repository),通过依赖注入连接:
|
||
|
||
```
|
||
handler(HTTP 请求处理)
|
||
→ service(业务逻辑)
|
||
→ repository(数据访问)
|
||
```
|
||
|
||
## 运行方式
|
||
|
||
### 安装依赖
|
||
|
||
```bash
|
||
go mod download
|
||
```
|
||
|
||
### 启动服务
|
||
|
||
```bash
|
||
go run cmd/server/main.go
|
||
```
|
||
|
||
服务将在端口 9826 启动。首次启动会自动创建配置文件和运行数据库迁移。
|
||
|
||
## 配置
|
||
|
||
配置文件位于 `~/.nex/config.yaml`,首次启动自动生成。
|
||
|
||
```yaml
|
||
server:
|
||
port: 9826
|
||
read_timeout: 30s
|
||
write_timeout: 30s
|
||
|
||
database:
|
||
path: ~/.nex/config.db
|
||
max_idle_conns: 10
|
||
max_open_conns: 100
|
||
conn_max_lifetime: 1h
|
||
|
||
log:
|
||
level: info
|
||
path: ~/.nex/log
|
||
max_size: 100 # MB
|
||
max_backups: 10
|
||
max_age: 30 # 天
|
||
compress: true
|
||
```
|
||
|
||
数据文件:
|
||
- `~/.nex/config.yaml` - 配置文件
|
||
- `~/.nex/config.db` - SQLite 数据库
|
||
- `~/.nex/log/` - 日志目录
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
# 运行所有测试
|
||
make test
|
||
|
||
# 生成覆盖率报告
|
||
make test-coverage
|
||
```
|
||
|
||
## 数据库迁移
|
||
|
||
```bash
|
||
# 使用 Makefile
|
||
make migrate-up DB_PATH=~/.nex/config.db
|
||
make migrate-down DB_PATH=~/.nex/config.db
|
||
make migrate-status DB_PATH=~/.nex/config.db
|
||
|
||
# 或直接使用 goose
|
||
goose -dir migrations sqlite3 ~/.nex/config.db up
|
||
```
|
||
|
||
## API 文档
|
||
|
||
### 代理接口
|
||
|
||
使用 `/{protocol}/v1/{path}` URL 前缀路由:
|
||
|
||
#### OpenAI 协议代理
|
||
|
||
```
|
||
POST /openai/v1/chat/completions
|
||
GET /openai/v1/models
|
||
POST /openai/v1/embeddings
|
||
POST /openai/v1/rerank
|
||
```
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"model": "gpt-4",
|
||
"messages": [
|
||
{"role": "user", "content": "Hello"}
|
||
],
|
||
"stream": false
|
||
}
|
||
```
|
||
|
||
#### Anthropic 协议代理
|
||
|
||
```
|
||
POST /anthropic/v1/messages
|
||
GET /anthropic/v1/models
|
||
```
|
||
|
||
请求示例:
|
||
|
||
```json
|
||
{
|
||
"model": "claude-3-opus",
|
||
"max_tokens": 1024,
|
||
"messages": [
|
||
{"role": "user", "content": [{"type": "text", "text": "Hello"}]}
|
||
]
|
||
}
|
||
```
|
||
|
||
**协议转换**:网关支持任意协议间的双向转换。客户端使用 OpenAI 协议请求,上游供应商可以是 Anthropic 协议(反之亦然)。同协议时自动透传,零序列化开销。
|
||
|
||
### 管理接口
|
||
|
||
#### 供应商管理
|
||
|
||
- `GET /api/providers` - 列出所有供应商
|
||
- `POST /api/providers` - 创建供应商
|
||
- `GET /api/providers/:id` - 获取供应商
|
||
- `PUT /api/providers/:id` - 更新供应商
|
||
- `DELETE /api/providers/:id` - 删除供应商
|
||
|
||
创建供应商示例:
|
||
|
||
```json
|
||
{
|
||
"id": "openai",
|
||
"name": "OpenAI",
|
||
"api_key": "sk-...",
|
||
"base_url": "https://api.openai.com/v1",
|
||
"protocol": "openai"
|
||
}
|
||
```
|
||
|
||
**Protocol 字段说明:**
|
||
- `protocol` 标识上游供应商使用的协议类型,可选值:`"openai"`(默认)、`"anthropic"`
|
||
- 同协议透传时,请求体和响应体原样转发,零序列化开销
|
||
|
||
**重要说明:**
|
||
- `base_url` 应配置到 API 版本路径,不包含具体端点
|
||
- OpenAI: `https://api.openai.com/v1`
|
||
- GLM: `https://open.bigmodel.cn/api/paas/v4`
|
||
- 其他 OpenAI 兼容供应商根据其文档配置版本路径
|
||
|
||
#### 模型管理
|
||
|
||
- `GET /api/models` - 列出模型(支持 `?provider_id=xxx` 过滤)
|
||
- `POST /api/models` - 创建模型
|
||
- `GET /api/models/:id` - 获取模型
|
||
- `PUT /api/models/:id` - 更新模型
|
||
- `DELETE /api/models/:id` - 删除模型
|
||
|
||
创建模型示例:
|
||
|
||
```json
|
||
{
|
||
"id": "gpt-4",
|
||
"provider_id": "openai",
|
||
"model_name": "gpt-4"
|
||
}
|
||
```
|
||
|
||
#### 统计查询
|
||
|
||
- `GET /api/stats` - 查询统计
|
||
- `GET /api/stats/aggregate` - 聚合统计
|
||
|
||
查询参数:
|
||
|
||
- `provider_id` - 供应商 ID
|
||
- `model_name` - 模型名称
|
||
- `start_date` - 开始日期(YYYY-MM-DD)
|
||
- `end_date` - 结束日期(YYYY-MM-DD)
|
||
- `group_by` - 聚合维度(provider/model/date)
|
||
|
||
## 开发
|
||
|
||
### 构建
|
||
|
||
```bash
|
||
make build
|
||
```
|
||
|
||
### 代码检查
|
||
|
||
```bash
|
||
make lint
|
||
```
|
||
|
||
### 环境要求
|
||
|
||
- Go 1.26 或更高版本
|
||
|
||
## 公共库使用指南
|
||
|
||
### pkg/errors — 结构化错误
|
||
|
||
使用预定义的错误类型,配合 `errors.Is` / `errors.As` 判断错误:
|
||
|
||
```go
|
||
import (
|
||
"errors"
|
||
pkgErrors "nex/backend/pkg/errors"
|
||
)
|
||
|
||
// 使用预定义错误
|
||
return pkgErrors.ErrRequestSend.WithCause(err)
|
||
|
||
// 判断错误类型
|
||
var appErr *pkgErrors.AppError
|
||
if errors.As(err, &appErr) {
|
||
// appErr.Code, appErr.HTTPStatus, appErr.Message
|
||
}
|
||
```
|
||
|
||
可用函数:`NewAppError`、`Wrap`、`WithContext`、`WithMessage`、`AsAppError`
|
||
|
||
预定义错误:`ErrModelNotFound`、`ErrProviderNotFound`、`ErrInvalidRequest`、`ErrRequestCreate`、`ErrRequestSend`、`ErrResponseRead` 等
|
||
|
||
### pkg/logger — 日志系统
|
||
|
||
使用依赖注入模式,构造函数接受 `*zap.Logger` 参数,nil 时回退到 `zap.L()`:
|
||
|
||
```go
|
||
func NewMyService(repo Repository, logger *zap.Logger) *MyService {
|
||
if logger == nil {
|
||
logger = zap.L()
|
||
}
|
||
return &MyService{repo: repo, logger: logger}
|
||
}
|
||
```
|
||
|
||
禁止直接在业务代码中使用 `zap.L()` 全局 logger,应通过构造函数注入。
|
||
|
||
### pkg/validator — 请求验证
|
||
|
||
```go
|
||
import "nex/backend/pkg/validator"
|
||
|
||
v := validator.Get()
|
||
err := v.Validate(myStruct)
|
||
```
|
||
|
||
## 编码规范
|
||
|
||
- **JSON 解析**:使用 `encoding/json` 标准库(`json.Unmarshal` / `json.Marshal`),不手动扫描字节
|
||
- **字符串拼接**:使用 `strings.Join`,不手写循环拼接
|
||
- **错误判断**:使用 `errors.Is` / `errors.As`,不使用字符串匹配(`strings.Contains(err.Error(), ...)`)
|
||
- **日志使用**:通过依赖注入 `*zap.Logger`,不直接调用 `zap.L()`
|
||
- **字符串分割**:使用 `strings.SplitN(key, "/", 2)` 等精确分割,不使用索引切片
|