diff --git a/.claude/plans/main-plan.md b/.claude/plans/main-plan.md new file mode 100644 index 0000000..5a568fa --- /dev/null +++ b/.claude/plans/main-plan.md @@ -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` 一键启动 diff --git a/.gitignore b/.gitignore index 5eec986..d48c759 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.claude +.idea +.vscode \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7efbfe2 --- /dev/null +++ b/CLAUDE.md @@ -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` | `:` | + +--- + +## 项目目录结构 + +``` +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 里是什么" — 我用对照表解释 +- 用户说"我写好了,帮我看一下" — 我会做代码审查 +- 用户遇到编译/运行错误 — 我帮助分析错误信息 diff --git a/go.mod b/go.mod index e69de29..74d6fcf 100644 --- a/go.mod +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum index e69de29..5d7061b 100644 --- a/go.sum +++ b/go.sum @@ -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=