chore(project): 初始化项目基础配置和开发计划
This commit is contained in:
@@ -0,0 +1,466 @@
|
|||||||
|
# Janus - AI 中转站 完整开发计划
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
构建一个 AI API 中转/网关服务(Janus),使用 Go + Gin + PostgreSQL(GORM),前端采用前后端分离架构(Vue 3)。
|
||||||
|
|
||||||
|
用户背景:Java 转 Go,项目兼顾学习目的。计划按阶段递增复杂度,每个阶段引入新的 Go 语言特性:
|
||||||
|
- 早期:struct、interface、error handling、package 组织
|
||||||
|
- 中期:goroutine、channel、sync 包(并发安全)
|
||||||
|
- 后期:middleware 模式、context、observability
|
||||||
|
|
||||||
|
## 架构总览
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Clients │
|
||||||
|
│ (OpenAI SDK / Anthropic SDK / curl) │
|
||||||
|
└─────────────────┬───────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Janus Gateway │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
|
||||||
|
│ │ Gin │ │ Proxy │ │ Management │ │
|
||||||
|
│ │ Router │ │ Engine │ │ API (CRUD) │ │
|
||||||
|
│ └────┬─────┘ └────┬─────┘ └───────┬───────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ┌────┴─────────────┴───────────────┴───────┐ │
|
||||||
|
│ │ Service Layer │ │
|
||||||
|
│ │ ┌──────────┐ ┌────────┐ ┌────────────┐ │ │
|
||||||
|
│ │ │ Balancer │ │ Health │ │ Rate Limiter│ │ │
|
||||||
|
│ │ │ Service │ │Checker │ │ Service │ │ │
|
||||||
|
│ │ └──────────┘ └────────┘ └────────────┘ │ │
|
||||||
|
│ └────────────────┬──────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌────────────────┴──────────────────────────┐ │
|
||||||
|
│ │ Repository Layer (GORM) │ │
|
||||||
|
│ └────────────────┬──────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌────────────────┴──────────────────────────┐ │
|
||||||
|
│ │ PostgreSQL │ │
|
||||||
|
│ └────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────────────────┐ │
|
||||||
|
│ │ Vue 3 Dashboard (SPA) │ │
|
||||||
|
│ └────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Upstream AI Providers │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||||
|
│ │ OpenAI │ │Anthropic │ │ Compatible │ │
|
||||||
|
│ │ API │ │ API │ │ (DS, QWen) │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
janus/
|
||||||
|
├── cmd/
|
||||||
|
│ └── server/
|
||||||
|
│ └── main.go # 应用入口,依赖组装
|
||||||
|
├── internal/
|
||||||
|
│ ├── config/
|
||||||
|
│ │ └── config.go # YAML 配置加载(viper)
|
||||||
|
│ ├── model/
|
||||||
|
│ │ ├── provider.go # Provider 模型
|
||||||
|
│ │ ├── api_key.go # ApiKey 模型
|
||||||
|
│ │ ├── model.go # AIModel 模型
|
||||||
|
│ │ └── request_log.go # RequestLog 模型
|
||||||
|
│ ├── repository/
|
||||||
|
│ │ ├── provider_repo.go # Provider CRUD
|
||||||
|
│ │ ├── api_key_repo.go # ApiKey CRUD
|
||||||
|
│ │ ├── model_repo.go # AIModel CRUD
|
||||||
|
│ │ └── request_log_repo.go # Log 查询
|
||||||
|
│ ├── service/
|
||||||
|
│ │ ├── provider_svc.go # Provider 业务逻辑
|
||||||
|
│ │ ├── api_key_svc.go # ApiKey 管理
|
||||||
|
│ │ ├── balancer_svc.go # 负载均衡核心
|
||||||
|
│ │ ├── proxy_svc.go # 请求代理引擎
|
||||||
|
│ │ ├── health_svc.go # 健康检查 & 故障转移
|
||||||
|
│ │ └── rate_limit_svc.go # 速率限制
|
||||||
|
│ ├── handler/
|
||||||
|
│ │ ├── proxy_handler.go # /v1/* 代理端点
|
||||||
|
│ │ ├── provider_handler.go # Provider 管理 API
|
||||||
|
│ │ ├── api_key_handler.go # ApiKey 管理 API
|
||||||
|
│ │ ├── model_handler.go # Model 管理 API
|
||||||
|
│ │ ├── stats_handler.go # 统计 API
|
||||||
|
│ │ └── health_handler.go # 健康检查
|
||||||
|
│ ├── middleware/
|
||||||
|
│ │ ├── auth.go # API Key 认证
|
||||||
|
│ │ ├── ratelimit.go # 速率限制中间件
|
||||||
|
│ │ ├── logging.go # 请求日志中间件
|
||||||
|
│ │ ├── cors.go # CORS
|
||||||
|
│ │ └── recovery.go # Panic 恢复
|
||||||
|
│ ├── proxy/
|
||||||
|
│ │ ├── openai.go # OpenAI 协议适配器
|
||||||
|
│ │ ├── anthropic.go # Anthropic 协议适配器
|
||||||
|
│ │ └── adapter.go # 适配器接口定义
|
||||||
|
│ ├── balancer/
|
||||||
|
│ │ ├── strategy.go # 策略接口
|
||||||
|
│ │ ├── round_robin.go # 轮询实现
|
||||||
|
│ │ └── weighted.go # 加权实现
|
||||||
|
│ ├── health/
|
||||||
|
│ │ ├── checker.go # 健康检查 Worker
|
||||||
|
│ │ └── circuit_breaker.go # 熔断器
|
||||||
|
│ └── router/
|
||||||
|
│ └── router.go # 路由注册
|
||||||
|
├── pkg/
|
||||||
|
│ ├── response/
|
||||||
|
│ │ └── response.go # 统一响应格式
|
||||||
|
│ ├── errors/
|
||||||
|
│ │ └── errors.go # 自定义错误类型
|
||||||
|
│ └── utils/
|
||||||
|
│ └── utils.go # 工具函数
|
||||||
|
├── web/ # Vue 3 前端项目
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── views/
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ ├── api/
|
||||||
|
│ │ └── router/
|
||||||
|
│ ├── package.json
|
||||||
|
│ └── vite.config.ts
|
||||||
|
├── migrations/ # 数据库迁移 SQL
|
||||||
|
│ ├── 001_init.up.sql
|
||||||
|
│ └── 001_init.down.sql
|
||||||
|
├── config.yaml # 默认配置
|
||||||
|
└── go.mod
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据库设计
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- providers: AI 服务提供商
|
||||||
|
CREATE TABLE providers (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL UNIQUE, -- openai / anthropic / deepseek
|
||||||
|
base_url VARCHAR(255) NOT NULL, -- https://api.openai.com
|
||||||
|
protocol VARCHAR(20) NOT NULL DEFAULT 'openai', -- openai / anthropic
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active / disabled
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- api_keys: API 密钥池
|
||||||
|
CREATE TABLE api_keys (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
provider_id BIGINT NOT NULL REFERENCES providers(id),
|
||||||
|
key_value TEXT NOT NULL, -- 加密存储
|
||||||
|
name VARCHAR(100), -- 别名/备注
|
||||||
|
weight INT NOT NULL DEFAULT 1, -- 负载权重
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled/depleted
|
||||||
|
fail_count INT NOT NULL DEFAULT 0, -- 连续失败计数
|
||||||
|
last_used_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- models: 可用模型列表
|
||||||
|
CREATE TABLE models (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
provider_id BIGINT NOT NULL REFERENCES providers(id),
|
||||||
|
name VARCHAR(100) NOT NULL, -- gpt-4o / claude-sonnet-4-5
|
||||||
|
input_price DECIMAL(10,8) DEFAULT 0, -- 每 1K token 价格
|
||||||
|
output_price DECIMAL(10,8) DEFAULT 0,
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(provider_id, name)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- request_logs: 请求日志
|
||||||
|
CREATE TABLE request_logs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
api_key_id BIGINT,
|
||||||
|
model_id BIGINT,
|
||||||
|
provider_id BIGINT,
|
||||||
|
request_method VARCHAR(10),
|
||||||
|
request_path VARCHAR(500),
|
||||||
|
request_body TEXT,
|
||||||
|
response_body TEXT,
|
||||||
|
status_code INT,
|
||||||
|
latency_ms INT,
|
||||||
|
tokens_in INT DEFAULT 0,
|
||||||
|
tokens_out INT DEFAULT 0,
|
||||||
|
cost DECIMAL(10,6) DEFAULT 0,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
CREATE INDEX idx_api_keys_provider_id ON api_keys(provider_id);
|
||||||
|
CREATE INDEX idx_api_keys_status ON api_keys(status);
|
||||||
|
CREATE INDEX idx_models_provider_id ON models(provider_id);
|
||||||
|
CREATE INDEX idx_request_logs_created_at ON request_logs(created_at DESC);
|
||||||
|
CREATE INDEX idx_request_logs_provider_id ON request_logs(provider_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 设计
|
||||||
|
|
||||||
|
### 代理端点(OpenAI 兼容 & Anthropic 兼容)
|
||||||
|
```
|
||||||
|
POST /v1/chat/completions # OpenAI 聊天补全
|
||||||
|
POST /v1/embeddings # OpenAI 嵌入
|
||||||
|
POST /v1/messages # Anthropic Messages API
|
||||||
|
GET /v1/models # 列出可用模型(聚合所有 provider)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 管理 API(Dashboard 用)
|
||||||
|
```
|
||||||
|
# Providers
|
||||||
|
GET /api/admin/providers
|
||||||
|
POST /api/admin/providers
|
||||||
|
GET /api/admin/providers/:id
|
||||||
|
PUT /api/admin/providers/:id
|
||||||
|
DELETE /api/admin/providers/:id
|
||||||
|
|
||||||
|
# API Keys
|
||||||
|
GET /api/admin/api-keys?provider_id=
|
||||||
|
POST /api/admin/api-keys
|
||||||
|
PUT /api/admin/api-keys/:id
|
||||||
|
DELETE /api/admin/api-keys/:id
|
||||||
|
POST /api/admin/api-keys/:id/toggle # 启用/禁用
|
||||||
|
|
||||||
|
# Models
|
||||||
|
GET /api/admin/models?provider_id=
|
||||||
|
POST /api/admin/models
|
||||||
|
PUT /api/admin/models/:id
|
||||||
|
DELETE /api/admin/models/:id
|
||||||
|
|
||||||
|
# Stats
|
||||||
|
GET /api/admin/stats/overview # 总览统计
|
||||||
|
GET /api/admin/stats/requests # 请求明细
|
||||||
|
```
|
||||||
|
|
||||||
|
## 负载均衡 & 故障转移设计
|
||||||
|
|
||||||
|
### 负载策略接口
|
||||||
|
```go
|
||||||
|
type Strategy interface {
|
||||||
|
Next(keys []*model.ApiKey) *model.ApiKey
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 策略实现
|
||||||
|
1. **RoundRobin** - 无状态轮询,适合均匀分布
|
||||||
|
2. **WeightedRoundRobin** - 按 weight 字段加权分配
|
||||||
|
|
||||||
|
### 健康检查
|
||||||
|
- 每个 ApiKey 维护 `fail_count` 连续失败计数
|
||||||
|
- 失败超过阈值(默认 3 次)自动标记为 `disabled`
|
||||||
|
- 后台 goroutine 定期重试 disabled 的 key(30s 间隔)
|
||||||
|
- 正常请求也作为隐式健康检查
|
||||||
|
|
||||||
|
### 故障转移流程
|
||||||
|
1. Balancer 选取一个 ApiKey
|
||||||
|
2. 代理请求到上游
|
||||||
|
3. 如果失败(5xx / 超时 / 网络错误):
|
||||||
|
- 递增 `fail_count`
|
||||||
|
- 达到阈值标记 `disabled`
|
||||||
|
- 立即从剩余 key 中选择重试(最多 3 次)
|
||||||
|
4. 如果成功:重置 `fail_count` 为 0
|
||||||
|
|
||||||
|
### 熔断器(单例 per ApiKey)
|
||||||
|
```
|
||||||
|
状态机: CLOSED → (连续失败) → OPEN → (冷却时间) → HALF_OPEN → (成功) → CLOSED
|
||||||
|
→ (失败) → OPEN
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分阶段实施计划
|
||||||
|
|
||||||
|
### Phase 1: 项目骨架 & 基础设施(第 1-2 天)
|
||||||
|
**学习目标**: Go 项目组织、package 管理、Gin 基础
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [-] 初始化 `go.mod`(go mod init github.com/synoth/janus)
|
||||||
|
- [ ] 安装核心依赖:gin, gorm, viper, pgx 驱动
|
||||||
|
- [ ] 实现 `internal/config/config.go`:Viper 加载 YAML 配置
|
||||||
|
- [ ] 编写 `config.yaml`:数据库连接、服务器端口等
|
||||||
|
- [ ] 实现 `cmd/server/main.go`:组装依赖,启动 Gin
|
||||||
|
- [ ] 实现 `internal/router/router.go`:基础路由
|
||||||
|
- [ ] 实现 `internal/handler/health_handler.go`:GET /health
|
||||||
|
- [ ] 实现 GORM 数据库连接 & AutoMigrate
|
||||||
|
- [ ] 添加 `.env` 支持敏感配置
|
||||||
|
|
||||||
|
**交付物**: 可运行的服务,响应 `/health`,连接数据库
|
||||||
|
|
||||||
|
### Phase 2: 数据模型 & Repository 层(第 2-3 天)
|
||||||
|
**学习目标**: Go struct、GORM 关系映射、interface 定义
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 定义 `internal/model/provider.go`(struct + GORM 标签)
|
||||||
|
- [ ] 定义 `internal/model/api_key.go`
|
||||||
|
- [ ] 定义 `internal/model/model.go`(AIModel)
|
||||||
|
- [ ] 定义 `internal/model/request_log.go`
|
||||||
|
- [ ] 编写 SQL 迁移脚本 `migrations/001_init.up.sql`
|
||||||
|
- [ ] 实现 `internal/repository/provider_repo.go`(CRUD)
|
||||||
|
- [ ] 实现 `internal/repository/api_key_repo.go`
|
||||||
|
- [ ] 实现 `internal/repository/model_repo.go`
|
||||||
|
- [ ] 编写 Repository 层的单元测试
|
||||||
|
|
||||||
|
**交付物**: 完整的数据访问层,可通过测试验证
|
||||||
|
|
||||||
|
### Phase 3: Provider & API Key 管理 API(第 3-4 天)
|
||||||
|
**学习目标**: Gin handler、请求验证、JSON 序列化、统一错误处理
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 实现 `pkg/response/response.go`:统一 JSON 响应格式
|
||||||
|
- [ ] 实现 `pkg/errors/errors.go`:业务错误码定义
|
||||||
|
- [ ] 实现 `internal/service/provider_svc.go`
|
||||||
|
- [ ] 实现 `internal/service/api_key_svc.go`
|
||||||
|
- [ ] 实现 `internal/handler/provider_handler.go`:CRUD API
|
||||||
|
- [ ] 实现 `internal/handler/api_key_handler.go`:CRUD API
|
||||||
|
- [ ] 实现 `internal/handler/model_handler.go`:模型管理 API
|
||||||
|
- [ ] 添加请求验证(binding tags + validator)
|
||||||
|
- [ ] Postman/curl 可测试的完整管理 API
|
||||||
|
|
||||||
|
**交付物**: 可通过 REST API 管理 Provider、ApiKey、Model
|
||||||
|
|
||||||
|
### Phase 4: 代理引擎(第 4-6 天)
|
||||||
|
**学习目标**: HTTP 反向代理、streaming、context、Go http.Client
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 实现 `internal/proxy/adapter.go`:协议适配器接口
|
||||||
|
```go
|
||||||
|
type Adapter interface {
|
||||||
|
Forward(ctx context.Context, key *model.ApiKey, body []byte) (*ProxyResult, error)
|
||||||
|
Protocol() string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [ ] 实现 `internal/proxy/openai.go`:OpenAI 请求转发
|
||||||
|
- 支持 `/v1/chat/completions`(含 streaming)
|
||||||
|
- 支持 `/v1/embeddings`
|
||||||
|
- [ ] 实现 `internal/proxy/anthropic.go`:Anthropic 请求转发
|
||||||
|
- 支持 `/v1/messages`(含 streaming)
|
||||||
|
- [ ] 实现 `internal/service/proxy_svc.go`:编排代理流程
|
||||||
|
- [ ] 实现 `internal/handler/proxy_handler.go`:
|
||||||
|
- 解析请求路径确定目标 protocol
|
||||||
|
- 从 header 获取 Janus API Key → 查找 upstream key 池
|
||||||
|
- 调用代理服务
|
||||||
|
- [ ] 实现 `internal/middleware/auth.go`:Janus 自身的 API Key 认证
|
||||||
|
- [ ] 添加请求日志记录(写入 request_logs)
|
||||||
|
- [ ] 处理 streaming 响应(SSE)
|
||||||
|
|
||||||
|
**交付物**: 可以用 curl 通过 Janus 调用 OpenAI/Anthropic
|
||||||
|
|
||||||
|
### Phase 5: 负载均衡(第 6-7 天)
|
||||||
|
**学习目标**: Go interface 多态、sync.Mutex、原子操作
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 实现 `internal/balancer/strategy.go`:Strategy 接口
|
||||||
|
- [ ] 实现 `internal/balancer/round_robin.go`:
|
||||||
|
- 线程安全的轮询计数器(sync.Mutex 或 atomic)
|
||||||
|
- 跳过 disabled key
|
||||||
|
- [ ] 实现 `internal/balancer/weighted.go`:
|
||||||
|
- 加权轮询算法
|
||||||
|
- 平滑加权轮询(Nginx 算法)
|
||||||
|
- [ ] 实现 `internal/service/balancer_svc.go`:
|
||||||
|
- 集成策略选择(可通过 API 或配置切换)
|
||||||
|
- 从 DB 加载可用 key 列表(带缓存)
|
||||||
|
- [ ] 修改 ProxyService 集成 BalancerService
|
||||||
|
- [ ] 编写负载均衡的单元测试(验证分布均匀性)
|
||||||
|
|
||||||
|
**交付物**: 请求自动在多个 ApiKey 间负载均衡
|
||||||
|
|
||||||
|
### Phase 6: 健康检查 & 故障转移(第 7-9 天)
|
||||||
|
**学习目标**: Goroutine、Channel、context 取消、并发模式
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 实现 `internal/health/circuit_breaker.go`:
|
||||||
|
- 三态状态机(Closed/Open/HalfOpen)
|
||||||
|
- 可配置阈值和冷却时间
|
||||||
|
- [ ] 实现 `internal/health/checker.go`:
|
||||||
|
- 后台 goroutine 定期检查 disabled key
|
||||||
|
- 向每个 provider 发送轻量健康检查请求
|
||||||
|
- 通过 channel 接收检查结果
|
||||||
|
- [ ] 实现 `internal/service/health_svc.go`:
|
||||||
|
- 管理所有 key 的健康状态
|
||||||
|
- 优雅关闭(context 取消 → 停止所有 goroutine)
|
||||||
|
- [ ] 修改 ProxyService 添加故障转移逻辑:
|
||||||
|
- 请求失败 → 标记 key → 从剩余池中重试
|
||||||
|
- 最大重试次数 3
|
||||||
|
- 熔断器介入
|
||||||
|
- [ ] 添加健康状态 API:GET /api/admin/keys/:id/health
|
||||||
|
|
||||||
|
**交付物**: 单个 key 故障时自动切换,故障 key 自动恢复
|
||||||
|
|
||||||
|
### Phase 7: 速率限制 & 可观测性(第 9-10 天)
|
||||||
|
**学习目标**: Middleware 模式、token bucket 算法、结构化日志
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 实现 `internal/service/rate_limit_svc.go`:
|
||||||
|
- Token bucket 算法(golang.org/x/time/rate)
|
||||||
|
- 支持按 Janus API Key 限流
|
||||||
|
- 支持按上游模型限流
|
||||||
|
- [ ] 实现 `internal/middleware/ratelimit.go`:
|
||||||
|
- 从 Gin context 提取 key
|
||||||
|
- 返回 429 Too Many Requests
|
||||||
|
- 添加 Retry-After header
|
||||||
|
- [ ] 实现 `internal/middleware/logging.go`:
|
||||||
|
- 结构化日志(使用 slog 或 zerolog)
|
||||||
|
- 记录 latency、status、path、tokens
|
||||||
|
- [ ] 实现 `internal/middleware/recovery.go`:Panic 恢复
|
||||||
|
- [ ] 实现 `internal/handler/stats_handler.go`:
|
||||||
|
- 总请求数、成功率、平均延迟
|
||||||
|
- 按 Provider/Model 分组统计
|
||||||
|
- 费用汇总
|
||||||
|
- [ ] 添加 `internal/middleware/cors.go`
|
||||||
|
|
||||||
|
**交付物**: 完整的中间件链,可观测的统计数据
|
||||||
|
|
||||||
|
### Phase 8: Dashboard 前端 & 容器化(第 10-14 天)
|
||||||
|
**学习目标**: 前后端集成、Docker、项目收尾
|
||||||
|
|
||||||
|
具体任务:
|
||||||
|
- [ ] 初始化 Vue 3 + Vite 项目(`web/`)
|
||||||
|
- [ ] 安装 UI 组件库(Element Plus 或 Naive UI)
|
||||||
|
- [ ] 实现 Providers 管理页面
|
||||||
|
- [ ] 实现 API Keys 管理页面
|
||||||
|
- [ ] 实现 Models 管理页面
|
||||||
|
- [ ] 实现统计仪表盘页面(图表)
|
||||||
|
- [ ] 前端构建产物由 Gin 静态文件服务托管
|
||||||
|
- [ ] 编写 Dockerfile(多阶段构建)
|
||||||
|
- [ ] 编写 docker-compose.yml(Janus + PostgreSQL)
|
||||||
|
- [ ] 编写 README.md(中英文)
|
||||||
|
|
||||||
|
**交付物**: 完整的可部署项目
|
||||||
|
|
||||||
|
## Go 学习路径映射
|
||||||
|
|
||||||
|
| Phase | Go 特性 | Java 对比 |
|
||||||
|
|-------|---------|-----------|
|
||||||
|
| 1 | package, import, func, defer | package ≈ package, defer ≈ finally |
|
||||||
|
| 2 | struct, interface, 指针 | struct ≈ class(无继承), interface 隐式满足 |
|
||||||
|
| 3 | error handling, type assertion | error 返回值 ≠ exception |
|
||||||
|
| 4 | context, http.Client, io.Reader | context ≈ 请求上下文传递 |
|
||||||
|
| 5 | sync.Mutex, atomic, slice | 无内置锁语法,需显式使用 |
|
||||||
|
| 6 | goroutine, channel, select | ≈ ThreadPool + BlockingQueue |
|
||||||
|
| 7 | middleware 闭包, functional options | ≈ Filter chain / Builder pattern |
|
||||||
|
| 8 | 前后端集成, Docker | 通用技能 |
|
||||||
|
|
||||||
|
## 关键技术决策
|
||||||
|
|
||||||
|
1. **配置管理**: Viper(支持 YAML + 环境变量覆盖)
|
||||||
|
2. **ORM**: GORM v2(AutoMigrate 用于开发,生产用迁移脚本)
|
||||||
|
3. **API Key 加密**: 使用 AES-256-GCM 加密存储 key_value
|
||||||
|
4. **Streaming**: 使用 `http.Flusher` + SSE 协议
|
||||||
|
5. **日志**: slog(Go 1.21+ 内置结构化日志)
|
||||||
|
6. **前端**: Vue 3 + Vite + Element Plus(轻量、国内文档丰富)
|
||||||
|
7. **容器化**: 多阶段 Docker 构建,docker-compose 一键部署
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
|
||||||
|
每个 Phase 完成后的验证方法:
|
||||||
|
1. `go build ./...` 编译通过
|
||||||
|
2. `go vet ./...` 静态分析通过
|
||||||
|
3. `go test ./...` 单元测试通过
|
||||||
|
4. curl / Postman 手动测试关键 API
|
||||||
|
5. Phase 4+ 可用 OpenAI SDK 指向 Janus 实际调用
|
||||||
|
6. Phase 6 手动停用一个 key 验证故障转移
|
||||||
|
7. Phase 8 `docker-compose up` 一键启动
|
||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
.claude
|
.idea
|
||||||
|
.vscode
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
## 项目:Janus - AI 中转站
|
||||||
|
|
||||||
|
这是一个 **Go 语言学习项目**,用户是 Java 开发者转 Go。项目目标是用 Go + Gin + PostgreSQL 构建一个 AI API 网关。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 我的角色定位
|
||||||
|
|
||||||
|
我是 **Go 技术导师和代码审查员**,不是主力开发者。
|
||||||
|
|
||||||
|
### 核心原则
|
||||||
|
- **绝不直接写业务代码**——用户要通过亲手编码学习 Go
|
||||||
|
- 我的职责:解释概念、审查代码、提供建议、解答疑问、帮助调试
|
||||||
|
- 代码必须由用户亲自编写完成
|
||||||
|
|
||||||
|
### 我需要做的
|
||||||
|
1. **解释 Go 概念**——用 Java 作为对照("Go 的 interface ≈ Java interface 但隐式满足")
|
||||||
|
2. **审查代码**——用户写完代码后,审查 Go 惯用法、错误处理、并发安全
|
||||||
|
3. **提供建议**——当用户问"这里怎么做"时,给出 Go 惯用方案和理由
|
||||||
|
4. **帮助调试**——遇到错误时,帮助分析原因
|
||||||
|
5. **对比分析**——帮用户理解 Go 的方式 vs Java 的方式的区别
|
||||||
|
|
||||||
|
### 我绝不做的
|
||||||
|
- **绝不直接写业务 `.go` 文件内容**——每个文件都由用户亲手编写
|
||||||
|
- **绝不替用户执行 `go` 命令**——用户自己操作,我只讲解
|
||||||
|
- **绝不一次性把所有做法全讲完**——用户问什么讲什么,保持学习节奏
|
||||||
|
|
||||||
|
### 可以例外的情况
|
||||||
|
- 可以写示例代码片段(上下文片段,不是完整文件)来解释某个概念
|
||||||
|
- 可以写 `config.yaml`、SQL 迁移脚本等**非业务代码**
|
||||||
|
- 可以创建项目目录结构(`mkdir`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目技术栈
|
||||||
|
|
||||||
|
| 组件 | 技术选型 | Java 对照 |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| 语言 | Go 1.24+ | Java 21 |
|
||||||
|
| Web 框架 | Gin | Spring MVC |
|
||||||
|
| 数据库 | PostgreSQL | - |
|
||||||
|
| ORM | GORM v2 | Hibernate / MyBatis |
|
||||||
|
| 配置 | Viper | Spring Config |
|
||||||
|
| 前端 | Vue 3 + Element Plus | - |
|
||||||
|
| 模块路径 | `synoth.com/janus` | `<groupId>:<artifactId>` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
janus/
|
||||||
|
├── cmd/server/main.go # 入口
|
||||||
|
├── internal/ # 内部包(不对外暴露)
|
||||||
|
│ ├── config/ # 配置
|
||||||
|
│ ├── model/ # 数据模型(GORM)
|
||||||
|
│ ├── repository/ # 数据访问层
|
||||||
|
│ ├── service/ # 业务逻辑层
|
||||||
|
│ ├── handler/ # HTTP 处理器
|
||||||
|
│ ├── middleware/ # 中间件
|
||||||
|
│ ├── proxy/ # 协议适配器
|
||||||
|
│ ├── balancer/ # 负载均衡
|
||||||
|
│ └── health/ # 健康检查
|
||||||
|
├── pkg/ # 可对外暴露的公共包
|
||||||
|
├── migrations/ # SQL 迁移脚本
|
||||||
|
├── web/ # Vue 3 前端
|
||||||
|
└── config.yaml # 配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发计划(8 阶段)
|
||||||
|
|
||||||
|
按 `.claude/plans/sequential-juggling-lecun.md` 中的完整计划执行:
|
||||||
|
1. 项目骨架 & 基础设施(Go 包管理、Gin 启动、配置加载)
|
||||||
|
2. 数据模型 & Repository 层(struct、GORM、interface)
|
||||||
|
3. 管理 API CRUD(Gin handler、JSON、验证)
|
||||||
|
4. 代理引擎(HTTP 反向代理、SSE streaming)
|
||||||
|
5. 负载均衡(sync.Mutex、轮询/加权算法)
|
||||||
|
6. 健康检查 & 故障转移(goroutine、channel、熔断器)
|
||||||
|
7. 速率限制 & 可观测性(中间件、slog、统计)
|
||||||
|
8. Dashboard & 容器化(Vue 3、Docker)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Go 学习要点(按阶段)
|
||||||
|
|
||||||
|
### Phase 1
|
||||||
|
- `go mod init` / `go get` — 依赖管理
|
||||||
|
- package 组织、`main` 函数、`init()`
|
||||||
|
- 指针 vs 值、`defer`、error 返回值
|
||||||
|
|
||||||
|
### Phase 2
|
||||||
|
- struct 定义和 GORM 标签
|
||||||
|
- interface 隐式满足(Java 同学最容易困惑的点)
|
||||||
|
- `*sql.DB` 连接池
|
||||||
|
|
||||||
|
### Phase 3
|
||||||
|
- `gin.Context`、JSON 序列化/反序列化
|
||||||
|
- binding tags 和验证器
|
||||||
|
- 统一错误响应格式
|
||||||
|
|
||||||
|
### Phase 4
|
||||||
|
- `http.Client`、`http.Transport`
|
||||||
|
- `io.Reader`/`io.Writer` 接口
|
||||||
|
- SSE 流处理(`http.Flusher`)
|
||||||
|
|
||||||
|
### Phase 5
|
||||||
|
- `sync.Mutex`、`sync.RWMutex`、`atomic`
|
||||||
|
- 方法接收者(value/pointer receiver)
|
||||||
|
- 接口工厂模式
|
||||||
|
|
||||||
|
### Phase 6
|
||||||
|
- goroutine 生命周期管理
|
||||||
|
- channel(buffered/unbuffered)、`select`
|
||||||
|
- `context.Context` 取消传播
|
||||||
|
- `sync.WaitGroup`
|
||||||
|
|
||||||
|
### Phase 7
|
||||||
|
- Gin 中间件链模式
|
||||||
|
- `slog` 结构化日志
|
||||||
|
- Token bucket 限流
|
||||||
|
|
||||||
|
### Phase 8
|
||||||
|
- Docker 多阶段构建
|
||||||
|
- Go embed 静态文件
|
||||||
|
- 优雅关闭
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 交互约定
|
||||||
|
|
||||||
|
- 用户说"帮我写一个..." — 我会解释思路、给出伪代码或示例片段,但不会写完整文件
|
||||||
|
- 用户贴代码问"这里有问题吗" — 我会审查并给出改进建议
|
||||||
|
- 用户问"Java 的 X 在 Go 里是什么" — 我用对照表解释
|
||||||
|
- 用户说"我写好了,帮我看一下" — 我会做代码审查
|
||||||
|
- 用户遇到编译/运行错误 — 我帮助分析错误信息
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
module synoth.com/janus
|
||||||
|
|
||||||
|
go 1.26.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.12.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.10.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/spf13/viper v1.21.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
|
golang.org/x/net v0.51.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
gorm.io/gorm v1.31.2 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
||||||
|
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
|
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
|
||||||
|
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
|
|||||||
Reference in New Issue
Block a user