diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90f944b --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Go +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work + +# Node.js +node_modules/ +dist/ +build/ + +# IDE +.idea/ + +# Environment files with sensitive data +.env +.env.local +.env.*.local + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Temporary files +*.tmp +*.temp + +# 外部文件 +new-api/ +sub2api/ + +# 配置文件(含敏感信息,使用 .example 模板代替) +hack/config.yaml +manifest/config/config.yaml + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..77035a4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,415 @@ +# Team-API — 项目开发规范 + +## 项目概述 + +多租户 AI API 网关 SaaS 平台。租户注册后获取 API Key,通过统一接口调用 OpenAI/Claude/Gemini 等大模型,平台负责计费、限流、监控。 + +双控制台架构:管理后台(平台运营)+ 租户控制台(组织使用),两套完全独立的用户体系。 + +## 技术栈 + +- **后端**:Go + GoFrame (v2) + goose(数据库迁移) +- **数据库**:PostgreSQL(唯一,不用 MySQL/SQLite) +- **缓存**:Redis + 内存缓存(双层缓存) +- **前端**:Vue 3 + Vite + TailwindCSS + Naive UI(管理后台) +- **包管理**:pnpm +- **对象存储**:S3 / OSS / COS / MinIO(禁止纯本地磁盘作为生产存储) + +## 项目结构 + +项目基于 GoFrame v2 标准项目结构(`gf init` 生成),在此基础上扩展业务模块。 + +``` +team-api/ +├── api/ # API 定义(请求/响应结构体 + 路由注解) +│ ├── admin/v1/ # 管理后台 API(gf gen ctrl 源) +│ ├── tenant/v1/ # 租户控制台 API(gf gen ctrl 源) +│ ├── captcha/v1/ # 验证码 API(管理后台 + 租户控制台共用) +│ ├── docs/v1/ # OpenAPI 文档 API +│ ├── open/v1/ # 开放平台 API +│ ├── payment/v1/ # 支付回调 API +├── hack/ +│ └── config.yaml # gf CLI 脚手架配置(gen dao 等工具的数据库连接和生成规则) +├── internal/ +│ ├── cmd/ # 入口命令、服务启动、路由注册 +│ │ ├── cmd.go # main 包注册,路由分组定义 +│ │ └── cmd_reset_pwd.go # 密码重置命令 +│ ├── consts/ # 常量定义(业务状态码、枚举等) +│ ├── controller/ # 控制器(由 gf gen ctrl 从 api/ 自动生成,禁止手动修改) +│ │ ├── admin/ # 管理后台控制器 +│ │ ├── tenant/ # 租户控制台控制器 +│ │ ├── captcha/ # 验证码控制器 +│ │ ├── docs/ # 文档控制器 +│ │ ├── open/ # 开放平台控制器 +│ ├── handler/ # 特殊端点处理器(不走 Controller 链路,见下方说明) +│ │ ├── relay/ # AI 代理端点处理器 +│ │ ├── public/ # 支付回调端点处理器 +│ │ └── setup/ # 系统初始化向导处理器 +│ ├── logic/ # 业务逻辑实现(核心代码所在) +│ │ ├── admin/ # 管理后台业务逻辑 +│ │ ├── tenant/ # 租户控制台业务逻辑 +│ │ ├── billing/ # 计费引擎 +│ │ ├── common/ # 公共逻辑(缓存、配置、安全、邮件、验证码、JWT 等) +│ │ ├── docs/ # OpenAPI 文档生成 +│ │ ├── monitor/ # 监控告警引擎 +│ │ ├── notification/ # 通知服务 +│ │ ├── open/ # 开放平台业务逻辑 +│ │ ├── payment/ # 支付逻辑(回调处理、订单履约) +│ │ ├── relay/ # Relay 业务逻辑(亲和性、健康检查、缓存) +│ │ └── task/ # 异步任务框架 +│ ├── model/ # 数据模型(由 gf gen dao 自动生成,禁止手动修改) +│ │ ├── do/ # Domain Objects(查询条件、输入结构体) +│ │ └── entity/ # Entity(与数据库表 1:1 映射的数据实体) +│ ├── packed/ # 打包资源(内嵌静态文件、编译时生成) +│ ├── dao/ # 数据访问对象(由 gf gen dao 自动生成,禁止手动修改) +│ ├── response/ # 统一响应工具包(Success/Error 封装) +│ ├── service/ # 业务接口定义(由 gf gen service 从 logic/ 自动生成,禁止手动修改) +│ ├── middleware/ # 中间件 +│ └── utility/ # 工具函数 +│ ├── crypto/ # 加密工具 +│ ├── export/ # 数据导出 +│ ├── totp/ # TOTP 两步验证 +│ └── turnstile/ # Cloudflare Turnstile 验证 +├── relay/ # AI 模型代理层(顶级模块,独立于 GoFrame 脚手架生成目录) +│ ├── channel/ # 供应商适配器(registry.go + 24 个子目录,每个供应商一个) +│ │ └── openai/ claude/ gemini/ ... +│ ├── common/ # Relay 层共享类型和工具 +│ ├── constant/ # Relay 层常量(供应商类型、模式、错误码) +│ ├── dto/ # 数据传输对象(OpenAI/Claude/Gemini/Realtime/Task 等格式定义) +│ ├── handler/ # Relay 请求处理器(chat/claude/gemini/audio/rerank/task/realtime 等) +│ ├── helper/ # Relay 辅助函数(流式处理、状态码映射、系统提示词、thinking 处理) +│ ├── override/ # 请求/响应覆盖(Header 改写、参数覆盖) +│ ├── scheduler/ # 渠道调度器(调度、亲和性、重试) +│ └── taskchannel/ # 异步任务渠道适配器 +│ ├── registry.go # 任务渠道注册表 +│ ├── kling/ # 可灵视频生成 +│ ├── midjourney/ # Midjourney 图像生成 +│ ├── sora/ # Sora 视频生成 +│ └── suno/ # Suno 音乐生成 +├── manifest/ +│ ├── config/ # 应用运行时配置(GoFrame 标准路径) +│ ├── deploy/ # 部署配置(K8s/Compose 等) +│ ├── docker/ # Docker 构建配置 +│ ├── i18n/ # 国际化资源文件 +│ └── protobuf/ # Protobuf 定义文件 +├── migrations/ # goose 数据库迁移脚本(六位序号编号:000001_xxx.sql) +├── web/ # 前端 +│ ├── admin/ # 管理后台 +│ └── tenant/ # 租户控制台 +├── docs/ # 项目文档 +│ ├── 开发计划-v2/ # 分周期开发计划(周期一~六) +│ └── 协议文档/ # 协议相关文档 +├── new-api/ # 参考项目(只读,不修改) +├── sub2api/ # 参考项目(只读,不修改) +├── main.go # 应用入口 +├── go.mod +└── Makefile +``` + +### Relay 层设计说明 + +Relay 层作为顶级 `relay/` 模块独立于 GoFrame 脚手架生成目录(`internal/` 下的 `controller/service/dao/model`),原因: +- **文件量大**:24 个供应商适配器 + 4 个异步任务适配器 + 共享模块,放在 `internal/logic/` 下层级过深 +- **职责独立**:Relay 是纯粹的代理转发层,不依赖 GoFrame ORM/DAO,与 `internal/` 下的业务逻辑解耦 +- **参考对齐**:目录结构与 new-api 的 `relay/` 保持一致,降低移植和理解成本 + +`internal/logic/` 中的业务逻辑(如计费、渠道调度)通过 Go import 调用 `relay/` 包,而非将 relay 嵌入 logic 内部。 + +### GoFrame 代码生成与手动编写分界 + +| 目录 | 生成方式 | 能否手动修改 | +|------|---------|------------| +| `api/` | 手动编写 | 是(结构体定义和路由注解) | +| `hack/config.yaml` | 手动编写 | 是(CLI 工具配置) | +| `internal/controller/` | `gf gen ctrl` | 否(自动生成) | +| `internal/service/` | `gf gen service` | 否(自动生成接口) | +| `internal/model/do/` | `gf gen dao` | 否(自动生成) | +| `internal/model/entity/` | `gf gen dao` | 否(自动生成) | +| `internal/dao/` | `gf gen dao` | 否(自动生成) | +| `internal/logic/` | 手动编写 | 是(核心业务逻辑) | +| `internal/cmd/` | 手动编写 | 是(路由注册、服务启动) | +| `internal/consts/` | 手动编写 | 是(常量定义) | +| `internal/handler/` | 手动编写 | 是(特殊端点处理器,见下方说明) | +| `internal/response/` | 手动编写 | 是(统一响应工具包) | +| `internal/middleware/` | 手动编写 | 是(中间件) | +| `internal/utility/` | 手动编写 | 是(工具函数:加密、导出、TOTP、Turnstile) | +| `manifest/config/` | 手动编写 | 是(运行时配置) | +| `relay/` | 手动编写 | 是(AI 模型代理层,独立于 GoFrame 脚手架) | + +### internal/handler/ 目录说明 + +`internal/handler/` 目录包含**特殊端点的处理器**,这些端点不适合走 GoFrame 的标准 Controller 链路: + +| 子目录 | 用途 | +|--------|------| +| `handler/relay/` | `/v1/*` AI 代理端点(OpenAI/Claude/Gemini 兼容格式)- 需要直接操作 `http.ResponseWriter` 进行流式转发 | +| `handler/public/` | `/api/payment/*` 支付回调端点 - 需要返回非 JSON 格式的响应(如支付宝的表单重定向) | +| `handler/setup/` | `/api/setup/*` 系统初始化向导端点 - 首次部署时的系统配置页面和初始化接口 | + +**为什么不用 Controller**: +- **流式响应**:AI 代理端点需要实时转发上游的 SSE 流,Controller 的响应包装会破坏流式传输 +- **原生格式透传**:必须保持与 OpenAI/Claude/Gemini API 完全兼容的请求/响应格式 +- **自定义响应**:支付回调需要返回 HTML 表单或文本响应,而非统一的 JSON 格式 + +**与 Controller 的区别**: +- Controller:走 GoFrame 标准链路(`api → controller → service → logic → dao`),返回统一 JSON 响应 +- Handler:直接处理 HTTP 请求/响应,不走 GoFrame 的 `ghttp.Request` 包装,用于特殊场景 + +### GoFrame 脚手架生成顺序(重要) + +项目 `hack/config.yaml` 中 `gfcli.gen.ctrl` 已配置 `withService: true`,意味着 `gf gen ctrl` 会尝试自动将控制器连接到 service 方法。但前提是 **service 接口中已存在对应方法**,否则生成 `CodeNotImplemented` 桩代码。 + +**新增接口时必须按以下顺序执行,不可颠倒:** + +1. **编写 API 定义**(`api/xxx/v1/xxx.go`)— 添加 Req/Res 结构体 +2. **编写 Logic 实现**(`internal/logic/xxx/xxx.go`)— 实现业务逻辑 +3. **`gf gen service`** — 从 Logic 方法签名自动生成 Service 接口 +4. **`gf gen ctrl`** — 从 API 定义生成 Controller,此时 `withService: true` 会自动生成 `return service.Xxx().Method(ctx, req)` 调用 + +如果先执行 `gf gen ctrl` 再执行 `gf gen service`,控制器会是桩代码(返回 501 Not Implemented),需要手动修改。始终遵循 "logic → gen service → gen ctrl" 的顺序可避免此问题。 + +### GoFrame 请求处理链路 + +``` +cmd(路由注册) + → api(请求/响应结构体定义) + → controller(解析请求、校验参数、调用 service) + → service(接口定义,自动生成) + → logic(业务逻辑实现,使用 DO 构造查询) + → dao(ORM 操作,返回 Entity) + ← 返回 Entity + ← controller 格式化响应(Res 结构体) +``` + +## 参考项目(只读,禁止修改) + +项目根目录下有 `new-api/` 和 `sub2api/` 两个参考项目,它们是大模型网关领域的成熟实现。 +大多数情况下不需要查看这2个目录下的代码,除非我主动提及或者涉及到大模型相关的实现逻辑。 + +### new-api 重点参考 + +- **Relay 代理层架构**:`controller/relay/`,适配器模式、请求转发、响应解析 +- **供应商适配器**:`relay/channel/` 下 40+ 供应商的适配器实现,优先复用 +- **协议转换**:OpenAI ↔ Claude ↔ Gemini 消息格式互转 +- **渠道调度**:优先级/权重选择、自动重试、Key 轮询 +- **计费模型**:预扣/结算/退款、梯度定价、Token 计量 + +### sub2api 重点参考 + +- **渠道亲和性**:用户+模型→渠道映射缓存策略 +- **运维监控**:仪表盘设计、健康监控页面 +- **用户体系**:前端布局和组件设计 + +### 参考原则 + +参考不等于照搬。new-api 使用 Gin + GORM,我们需要适配到 GoFrame 框架。接口设计和核心算法可以参考,但代码需要重新组织以符合 GoFrame 标准结构(`api → controller → service → logic → dao` 分层)和项目的多租户架构。注意 GoFrame 的 `model/do/`、`model/entity/`、`dao/`、`service/`、`controller/` 均由脚手架自动生成,业务代码只写在 `api/`、`logic/`、`cmd/`、`handler/`、`middleware/`、`response/`、`utility/`、`consts/` 和顶级 `relay/` 中。 + +## 核心架构决策 + +### 多租户隔离 + +行级隔离:所有业务表包含 `tenant_id` 字段,GoFrame ORM 中间件全局注入 `WHERE tenant_id = ?`。请求链路通过 Context 传递 tenant_id。 + +### 路由前缀 + +``` +/api/admin/ 管理后台(JWT 认证,admin_users 表) +/api/tenant/ 租户控制台(JWT 认证,tenant_users 表,RAM 格式 用户名@租户代码) +/api/payment/ 支付回调(签名验证,无用户认证) +/api/captcha/ 验证码服务(无需认证,管理后台 + 租户控制台共用) +/api/docs/ OpenAPI 文档(无需认证) +/api/open/ 开放平台 API(HMAC-SHA256 认证) +/api/setup/ 系统初始化向导(首次部署,完成后禁用) +/v1/ AI 代理端点 — OpenAI 兼容格式(API Key 认证,Bearer Token) +/v1beta/ AI 代理端点 — Gemini 兼容格式(API Key 认证,Bearer Token) +/suno/ Suno 音乐生成端点(API Key 认证) +``` + +### RBAC 权限模型 + +三层:角色 + 权限点 + 数据范围。公式:`is_allowed = has_permission_point AND within_data_scope AND resource_state_allows`。 + +管理后台角色:`super_admin`(全权限)、`admin`(可配置权限集)。 +租户控制台角色:`owner`(全权限)、`admin`(管理权限)、`member`(个人数据)。 + +### 五层额度模型 + +``` +租户钱包(货币池)→ 套餐额度(资源池)→ 成员额度(控制线)→ 项目预算(控制线)→ Key 额度(控制线) +``` + +扣费优先级:先扣套餐资源量,不足部分扣钱包余额。免费版额度耗尽停止服务,钱包余额不兜底。 + +### 计费流程 + +``` +预扣(Redis 原子操作)→ 转发请求 → 结算(实际用量)→ 退款/补扣差额 +``` + +价格查询优先级:租户独立价 > 套餐价 > 模型基础价 > 硬编码默认。 + +最终价格公式:`最终价格 = 基础价格 × 模型乘数 × 租户乘数`。模型乘数根据模型稀有度/成本动态调整;租户乘数根据租户等级/用量等级设置(VIP 折扣等)。双层乘数替代单纯的"租户独立定价",提供更灵活的定价能力。 + +### 钱包冻结余额 + +wallets 表包含 `frozen_balance` 字段,用于支付中/退款中的金额冻结,防止并发超扣。可用余额 = `balance - frozen_balance`。预扣和支付操作需检查可用余额而非总余额。 + +### 系统货币规则 + +平台采用**内部统一 USD + 充值入口 CNY**策略,系统内除充值/支付外的所有金额均为 USD: + +| 业务场景 | 货币 | 说明 | +|----------|------|------| +| 大模型价格展示和计费 | **USD(美元)** | 模型定价、Token 单价、计费扣费统一使用美元,与上游供应商(OpenAI/Anthropic/Google)报价币种一致 | +| 用户钱包余额 | **USD(美元)** | 钱包存储 USD 余额,充值时 CNY 按汇率转换为 USD 入账 | +| 计费记录 / 交易流水 | **USD(美元)** | 所有 bil_records、bil_transactions 金额均为 USD | +| 套餐价格 | **CNY(人民币)** | 管理后台创建套餐时设置的价格为人民币,用户购买套餐使用人民币支付 | +| 充值/支付 | **CNY(人民币)** | 用户通过支付渠道(支付宝/微信/Stripe)充值为人民币,履约时转换为 USD 入账钱包 | + +**换算规则**: +- 管理后台支付设置中配置 CNY → USD 兑换比例(`payment_exchange_rate_cny_to_usd`,默认 0.14) +- 充值履约时调用 `billing.ConvertCNYToUSD(ctx, cnyAmount)` 将 CNY 金额转换为 USD 入账钱包 +- 计费引擎全程使用 USD,无需换算 +- 前端展示规则:钱包余额/交易记录/计费用 `$`(USD),套餐价格/充值金额/订单金额用 `¥`(CNY) + +## 数据库表前缀定义 + +按功能域划分,新建表时必须归入对应前缀,除非加入了新的功能模型,否则不得自行新增前缀。 + +| 前缀 | 功能域 | 周期 | 涉及表(示例) | +|------|--------|------|--------------| +| `sys_` | 平台系统 + 系统配置 | 一 | `sys_admin_users` `sys_admin_role_perms` `sys_admin_data_scopes` `sys_sessions` `sys_email_verify_codes` `sys_idempotency_records` `sys_options` | +| `tnt_` | 租户核心 + 项目管理 | 一 | `tnt_tenants` `tnt_users` `tnt_invitations` `tnt_member_imports` `tnt_member_model_scopes` `tnt_projects` | +| `mdl_` | AI 模型 | 一 | `mdl_models` `mdl_pricing_tiers` `mdl_tenant_models` | +| `chn_` | 渠道管理 | 一 | `chn_channels` `chn_abilities` `chn_health_scores` `chn_channel_keys` `chn_channel_affinities` | +| `api_` | API 密钥 | 一 | `api_keys` `api_key_model_scopes` | +| `bil_` | 计费账单 | 一 | `bil_records` `bil_wallets` `bil_transactions` `bil_usage_logs` `bil_daily_usage_summary` `bil_daily_revenue_summary` `bil_monthly_usage_summary` `bil_monthly_revenue_summary` | +| `ord_` | 订单支付 + 优惠券 | 二 | `ord_orders` `ord_refunds` `ord_payment_channels` `ord_promo_codes` `ord_promo_code_usages` `ord_redemptions` `ord_redemption_usages` | +| `pln_` | 套餐订阅 | 二 | `pln_plans` `pln_tenant_plans` `pln_feature_flags` | +| `ntf_` | 通知消息 | 三 | `ntf_templates` `ntf_send_log` `ntf_messages` `ntf_read_status` `ntf_preferences` `ntf_announcements` | +| `aud_` | 审计日志 | 三 | `aud_request_logs` `aud_operation_logs` `aud_sensitive_access_logs` `aud_login_history` | +| `spt_` | 客户支持(工单+帮助+反馈) | 三/六 | `spt_tickets` `spt_replies` `spt_attachments` `spt_categories` `spt_articles` `spt_feedbacks` | +| `ops_` | 运维监控 | 四 | `ops_alert_rules` `ops_alert_events` `ops_system_metrics` | +| `tsk_` | 异步任务 | 四 | `tsk_tasks` `tsk_task_logs` `tsk_async_tasks` | +| `fil_` | 文件存储 | 四 | `fil_files` | +| `opn_` | 开放平台 + Webhook | 五 | `opn_apps` `opn_webhook_configs` `opn_webhook_events` `opn_webhook_delivery_logs` | +| `clg_` | 更新日志 | 六 | `clg_changelogs` | + +### 前缀使用规则 + +- 新建表必须从上表选择前缀,不允许自行新增前缀 +- 如果一个表明显属于两个域,选择核心归属域的前缀 +- 通用/跨域表(如 `sys_options` 系统配置)放在最相关的域下 +- 迁移脚本文件名按现有文件顺序递增编号,格式:`{序号}_{变更内容描述}.sql`,例如 `000002_alter_mdl_pricing_cache_fields.sql` + +## 开发规范 + +### Go 后端 + +- **缩进统一使用 tab**:所有 Go 代码的缩进必须使用 tab,与 `go fmt` 自动格式化保持一致,禁止混用空格和 tab 缩进 +- GoFrame 标准项目结构,不要偏离。框架使用规范(时间处理、错误处理、ORM 模式、日志等)详见 [`docs/reference/goframe-conventions.md`](docs/reference/goframe-conventions.md),遇到不确定的 GoFrame 用法时善用 `/goframe-v2` skill 查询框架规范 +- **修复 GoFrame 框架使用 bug 时**,必须在 `docs/reference/goframe-conventions.md` 末尾的「已修复的框架使用错误记录」章节追加记录(问题描述、原因、修复方式),防止同类问题重现 +- 数据库迁移用 goose,脚本按六位序号递增编号(`migrations/000001_xxx.sql`) +- 所有业务表必须有 `id`, `created_at`, `updated_at` 字段 +- 审计相关表按月分区(audit_logs) +- 使用量大的写入操作(usage_logs)异步批量写入 +- 错误响应统一格式:`{"error": {"type": "...", "message": "...", "code": "...", "request_id": "..."}}` +- 每个 API 请求生成唯一 Request ID,贯穿全链路 +- 敏感字段(密码、API Key 原值)AES-256 加密存储 +- 缓存策略:API Key 60s、用户信息 300s、模型定价 600s、渠道信息 300s + +### 前端 + +- Vue 3 Composition API + ` + + + + + + +
+ +系统初始化设置
++ 首次使用,请创建管理员账号以开始配置系统 +
+ + + ++ 初始化完成后将自动跳转至登录页面 +
+