Files
janus/.claude/plans/sequential-juggling-lecun.md
T
yangzhaohan 0794474c04 feat(model): 新增基础数据模型与路由配置
- 新增 BaseModel 结构体,包含统一主键和时间字段
- 定义 JanusProvider、JanusModel、Channel、APIKey 四个业务模型结构
- 引入 Status 枚举表示启用/禁用状态
- 完成对应数据库建表 SQL 迁移脚本
- 新增 router 包及 API 健康检查路由注册实现
- 优化 main.go,使用 router 包统一路由注册管理
- 补充模型中字段的 GORM 和 JSON 标签规范
2026-07-14 23:36:41 +08:00

250 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Janus - 完整开发计划
> AI API 网关(中转站),Go 语言学习项目。
---
## 总览(8 阶段)
| Phase | 主题 | 核心 Go 知识点 |
|-------|------|---------------|
| 1 | 项目骨架 & 基础设施 | go mod、package、Gin、Viper、指针 vs 值 |
| **2** | **数据模型 & Repository** | **struct、GORM tags、interface 隐式满足** |
| 3 | 管理 API CRUD | gin.Context、JSON、binding 验证、统一错误响应 |
| 4 | 代理引擎 | http.Client、io.Reader/Writer、SSE streaming |
| 5 | 负载均衡 | sync.Mutex、RWMutex、加权轮询算法 |
| 6 | 健康检查 & 故障转移 | goroutine、channel、context、熔断器 |
| 7 | 速率限制 & 可观测性 | Gin 中间件链、slog、Token bucket |
| 8 | Dashboard & 容器化 | Vue 3、Docker 多阶段构建、graceful shutdown |
---
# Phase 2:数据模型 & Repository 层
## 学习目标
与 Java 对照理解:
| Go 概念 | Java 对照 | 关键差异 |
|---------|-----------|---------|
| struct + GORM tag | `@Entity` + JPA annotation | 无继承,字段是直接嵌入而非注解驱动 |
| interface 隐式满足 | `implements` 关键字 | 不需要显式声明,只要方法签名匹配就自动满足 |
| repository interface + impl | DAO 接口 + 实现类 | 模式相似,但 Go 用隐式满足,无 `@Autowired` |
| `*gorm.DB` | `EntityManager` / `JpaRepository` | GORM 更接近 MyBatis 风格的链式 API |
## 表结构设计
### providers(供应商)
AI 服务供应商(OpenAI、Anthropic、Azure 等)。
```sql
CREATE TABLE providers (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, -- openai / anthropic / azure
base_url VARCHAR(255) NOT NULL, -- API 基础地址
status SMALLINT NOT NULL DEFAULT 1, -- 1=启用 0=停用
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON COLUMN providers.status IS '1: enabled, 0: disabled';
```
### models(模型)
每个供应商下可用的 AI 模型。
```sql
CREATE TABLE models (
id BIGSERIAL PRIMARY KEY,
provider_id BIGINT NOT NULL REFERENCES providers(id),
name VARCHAR(100) NOT NULL, -- gpt-4 / claude-3-opus / text-embedding-3
model_type VARCHAR(50) NOT NULL, -- chat / completion / embedding / image
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(provider_id, name)
);
COMMENT ON COLUMN models.model_type IS 'chat|completion|embedding|image';
```
### channels(通道)
路由转发通道,连接 provider + model,支持权重和优先级(Phase 5 负载均衡使用)。
```sql
CREATE TABLE channels (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL, -- 通道名称(如 "主通道-OpenAI"
provider_id BIGINT NOT NULL REFERENCES providers(id),
model_id BIGINT REFERENCES models(id), -- NULL 表示供应商级别通道(透传)
weight INT NOT NULL DEFAULT 1, -- 负载均衡权重
priority INT NOT NULL DEFAULT 1, -- 优先级(数字越小越高)
rate_limit INT NOT NULL DEFAULT 60, -- 每分钟请求数上限
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### api_keysAPI 密钥)
调用方认证密钥。
```sql
CREATE TABLE api_keys (
id BIGSERIAL PRIMARY KEY,
key VARCHAR(64) NOT NULL UNIQUE, -- 实际密钥字符串
name VARCHAR(100) NOT NULL, -- 密钥名称/用途描述
rate_limit INT NOT NULL DEFAULT 60, -- 每分钟请求数上限
status SMALLINT NOT NULL DEFAULT 1,
expired_at TIMESTAMPTZ, -- NULL = 永不过期
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
---
## Go 代码组织
### 新增目录结构
```
internal/
├── model/ # ← 新增:数据模型
│ ├── provider.go
│ ├── model.go (模型表 struct)
│ ├── channel.go
│ └── api_key.go
├── repository/ # ← 新增:数据访问层
│ ├── interfaces.go (所有 repository interface 定义)
│ ├── provider_repo.go (GORM 实现)
│ ├── model_repo.go
│ ├── channel_repo.go
│ └── api_key_repo.go
└── database/
└── database.go # 已有,需增加自动迁移
```
### model 包设计要点
每个文件一个 struct,包含:
- GORM tags`gorm:"column:xxx;type:xxx"`
- JSON tags`json:"xxx"`
- GORM 约定:`ID` 为主键,`CreatedAt`/`UpdatedAt` 自动管理
- 嵌入 `gorm.Model` 还是手写 → 建议**手写**(学习目的,显式看到字段)
示例思路(非完整代码):
```go
type Provider struct {
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"uniqueIndex;size:100;not null" json:"name"`
BaseURL string `gorm:"size:255;not null" json:"base_url"`
Status int16 `gorm:"default:1;not null" json:"status"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
```
### repository 包设计要点
**interface 定义**`interfaces.go`)— 类比 Java 的 DAO 接口:
```go
type ProviderRepository interface {
Create(ctx context.Context, provider *model.Provider) error
GetByID(ctx context.Context, id int64) (*model.Provider, error)
GetAll(ctx context.Context) ([]*model.Provider, error)
Update(ctx context.Context, provider *model.Provider) error
Delete(ctx context.Context, id int64) error
}
```
**GORM 实现**`provider_repo.go`)— 类比 Java 的 DAO Impl
```go
type providerRepository struct {
db *gorm.DB
}
func NewProviderRepository(db *gorm.DB) ProviderRepository {
return &providerRepository{db: db}
}
```
**关键 Go 知识点(与 Java 对比):**
- `providerRepository` 没有 `implements ProviderRepository` 关键字 — 只要方法签名匹配就自动满足 interface,这叫**隐式满足**
- 构造函数 `NewProviderRepository` 返回 `ProviderRepository`(interface 类型),而不是具体类型 — 调用方只依赖 interface,不依赖具体实现
- 接口定义在哪里?**在 repository 包(使用方附近)**,而不是像 Java 那样单独抽一个 api 模块
---
## GORM 关联关系
- **Model → Provider**:多对一(`BelongsTo`
- **Channel → Provider**:多对一
- **Channel → Model**:多对一(可空)
Phase 2 先用手写外键 + 手动关联查询来理解,后续可以引入 GORM 的 `Preload` / `Association`
---
## 数据库迁移
`internal/database/database.go``Connect` 函数后增加自动迁移:
```go
func AutoMigrate(db *gorm.DB) error {
return db.AutoMigrate(
&model.Provider{},
&model.Model{}, // 注意命名冲突,Go 里可以用 ModelName
&model.Channel{},
&model.APIKey{},
)
}
```
同时创建 `migrations/` 目录下的 SQL 文件作为正式迁移脚本(与自动迁移并存,生产环境用 SQL)。
---
## 实施步骤
按顺序完成,每一步都可以编译验证:
### Step 1:创建 SQL 迁移脚本
-`migrations/` 下创建 `001_init.sql`,包含 4 张建表语句
### Step 2:创建 model struct
- `internal/model/provider.go`
- `internal/model/model.go`(注意 Go 标准库 `builtin` 没有 model,但包名 model + 类型名 Model 会造成 `model.Model` 有点别扭,可以考虑命名为 `AiModel` 或在调用时用别名,由你决定)
- `internal/model/channel.go`
- `internal/model/api_key.go`
### Step 3:创建 repository interface
- `internal/repository/interfaces.go` — 4 个 interface 定义
### Step 4:创建 repository 实现
- 每个 repository 一个文件,GORM CRUD 实现
### Step 5:集成自动迁移
- 修改 `database/database.go` 增加 `AutoMigrate`
- 修改 `cmd/server/main.go` 在连接后调用迁移
---
## 设计决策说明
| 决策 | 选择 | 理由 |
|------|------|------|
| 主键类型 | `int64` (BIGSERIAL) | 适合中小规模,比 UUID 性能好,比自增 int 容量大 |
| 时间字段 | `time.Time` + `TIMESTAMPTZ` | GORM 自动管理,带时区避免歧义 |
| 状态字段 | `int16` (SMALLINT) | 扩展性强,可后续加更多状态值 |
| 软删除 | 暂不使用 | GORM 默认支持 `gorm.DeletedAt`,但 Phase 2 先学基础,不引入 |
| model_type | varchar | 比 enum 灵活,Go 端用 const 常量约束 |
| 外键约束 | 数据库级 REFERENCES | GORM 不强制外键,但数据库层保证完整性 |