# Sub2API 开发说明 ## 版本管理策略 ### 版本号规则 我们在官方版本号后面添加自己的小版本号: - 官方版本:`v0.1.68` - 我们的版本:`v0.1.68.1`、`v0.1.68.2`(递增) ### 分支策略 | 分支 | 说明 | |------|------| | `main` | 我们的主分支,包含所有定制功能 | | `release/custom-X.Y.Z` | 基于官方 `vX.Y.Z` 的发布分支 | | `upstream/main` | 上游官方仓库 | --- ## 发布流程(基于新官方版本) 当官方发布新版本(如 `v0.1.69`)时: ### 1. 同步上游并创建发布分支 ```bash # 获取上游最新代码 git fetch upstream --tags # 基于官方标签创建新的发布分支 git checkout v0.1.69 -b release/custom-0.1.69 # 合并我们的 main 分支(包含所有定制功能) git merge main --no-edit # 解决可能的冲突后继续 ``` ### 2. 更新版本号并打标签 ```bash # 更新版本号文件 echo "0.1.69.1" > backend/cmd/server/VERSION git add backend/cmd/server/VERSION git commit -m "chore: bump version to 0.1.69.1" # 打上我们自己的标签 git tag v0.1.69.1 # 推送分支和标签 git push origin release/custom-0.1.69 git push origin v0.1.69.1 ``` ### 3. 更新 main 分支 ```bash # 将发布分支合并回 main,保持 main 包含最新定制功能 git checkout main git merge release/custom-0.1.69 git push origin main ``` --- ## 热修复发布(在现有版本上修复) 当需要在当前版本上发布修复时: ```bash # 在当前发布分支上修复 git checkout release/custom-0.1.68 # ... 进行修复 ... git commit -m "fix: 修复描述" # 递增小版本号 echo "0.1.68.2" > backend/cmd/server/VERSION git add backend/cmd/server/VERSION git commit -m "chore: bump version to 0.1.68.2" # 打标签并推送 git tag v0.1.68.2 git push origin release/custom-0.1.68 git push origin v0.1.68.2 # 同步修复到 main git checkout main git cherry-pick git push origin main ``` --- ## 服务器部署流程 ### 前置条件 - 本地已配置 SSH 别名 `clicodeplus` 连接到生产服务器(运行服务) - 本地已配置 SSH 别名 `us-asaki-root` 连接到构建服务器(拉取代码、构建镜像) - 生产服务器部署目录:`/root/sub2api`(正式)、`/root/sub2api-beta`(测试) - 生产服务器使用 Docker Compose 部署 - **镜像统一在构建服务器上构建**,避免生产服务器因编译占用 CPU/内存影响线上服务 ### 服务器角色说明 | 服务器 | SSH 别名 | 职责 | |--------|----------|------| | 构建服务器 | `us-asaki-root` | 拉取代码、`docker build` 构建镜像 | | 生产服务器 | `clicodeplus` | 加载镜像、运行服务、部署验证 | ### 部署环境说明 | 环境 | 目录(生产服务器) | 端口 | 数据库 | 容器名 | |------|------|------|--------|--------| | 正式 | `/root/sub2api` | 8080 | `sub2api` | `sub2api` | | Beta | `/root/sub2api-beta` | 8084 | `beta` | `sub2api-beta` | ### 外部数据库 正式和 Beta 环境**共用外部 PostgreSQL 数据库**(非容器内数据库),配置在 `.env` 文件中: - `DATABASE_HOST`:外部数据库地址 - `DATABASE_SSLMODE`:SSL 模式(通常为 `require`) - `POSTGRES_USER` / `POSTGRES_DB`:用户名和数据库名 #### 数据库操作命令 通过 SSH 在服务器上执行数据库操作: ```bash # 正式环境 - 查询迁移记录 ssh clicodeplus "source /root/sub2api/deploy/.env && PGPASSWORD=\"\$POSTGRES_PASSWORD\" psql -h \$DATABASE_HOST -U \$POSTGRES_USER -d \$POSTGRES_DB -c 'SELECT * FROM schema_migrations ORDER BY applied_at DESC LIMIT 5;'" # Beta 环境 - 查询迁移记录 ssh clicodeplus "source /root/sub2api-beta/deploy/.env && PGPASSWORD=\"\$POSTGRES_PASSWORD\" psql -h \$DATABASE_HOST -U \$POSTGRES_USER -d \$POSTGRES_DB -c 'SELECT * FROM schema_migrations ORDER BY applied_at DESC LIMIT 5;'" # Beta 环境 - 清除指定迁移记录(重新执行迁移) ssh clicodeplus "source /root/sub2api-beta/deploy/.env && PGPASSWORD=\"\$POSTGRES_PASSWORD\" psql -h \$DATABASE_HOST -U \$POSTGRES_USER -d \$POSTGRES_DB -c \"DELETE FROM schema_migrations WHERE filename LIKE '%049%';\"" # Beta 环境 - 更新账号数据 ssh clicodeplus "source /root/sub2api-beta/deploy/.env && PGPASSWORD=\"\$POSTGRES_PASSWORD\" psql -h \$DATABASE_HOST -U \$POSTGRES_USER -d \$POSTGRES_DB -c \"UPDATE accounts SET credentials = credentials - 'model_mapping' WHERE platform = 'antigravity';\"" ``` > **注意**:使用 `source .env` 加载环境变量,避免在命令行中暴露密码。 ### 部署步骤 **重要:每次部署都必须递增版本号!** #### 0. 递增版本号并推送(本地操作) 每次部署前,先在本地递增小版本号并确保推送成功: ```bash # 查看当前版本号 cat backend/cmd/server/VERSION # 假设当前是 0.1.69.1 # 递增版本号 echo "0.1.69.2" > backend/cmd/server/VERSION git add backend/cmd/server/VERSION git commit -m "chore: bump version to 0.1.69.2" git push origin release/custom-0.1.69 # ⚠️ 确认推送成功(必须看到分支更新输出,不能有 rejected 错误) ``` > **检查点**:如果有其他未提交的改动,应先 commit 并 push,确保 release 分支上的所有代码都已推送到远程。 #### 1. 构建服务器拉取代码 ```bash # 拉取最新代码并切换分支 ssh us-asaki-root "cd /root/sub2api && git fetch origin && git checkout -B release/custom-0.1.69 origin/release/custom-0.1.69" # ⚠️ 验证版本号与步骤 0 一致 ssh us-asaki-root "cat /root/sub2api/backend/cmd/server/VERSION" ``` > **首次使用构建服务器?** 需要先初始化仓库,参见下方「构建服务器首次初始化」章节。 #### 2. 构建服务器构建镜像 ```bash ssh us-asaki-root "cd /root/sub2api && docker build --no-cache -t sub2api:latest -f Dockerfile ." # ⚠️ 必须看到构建成功输出,如果失败需要先排查问题 ``` > **常见构建问题**: > - `buildx` 版本过旧导致 API 版本不兼容 → 更新 buildx:`curl -fsSL "https://github.com/docker/buildx/releases/latest/download/buildx-$(curl -fsSL https://api.github.com/repos/docker/buildx/releases/latest | grep tag_name | cut -d'"' -f4).linux-amd64" -o ~/.docker/cli-plugins/docker-buildx && chmod +x ~/.docker/cli-plugins/docker-buildx` > - 磁盘空间不足 → `docker system prune -f` 清理无用镜像 #### 3. 传输镜像到生产服务器并加载 ```bash # 导出镜像 → 通过管道传输 → 生产服务器加载 ssh us-asaki-root "docker save sub2api:latest" | ssh clicodeplus "docker load" # ⚠️ 必须看到 "Loaded image: sub2api:latest" 输出 ``` #### 4. 生产服务器同步代码、更新标签并重启 ```bash # 同步代码(用于版本号确认和 deploy 配置) ssh clicodeplus "cd /root/sub2api && git fetch fork && git checkout -B release/custom-0.1.69 fork/release/custom-0.1.69" # 更新镜像标签并重启 ssh clicodeplus "docker tag sub2api:latest weishaw/sub2api:latest" ssh clicodeplus "cd /root/sub2api/deploy && docker compose up -d --force-recreate sub2api" ``` #### 5. 验证部署 ```bash # 查看启动日志 ssh clicodeplus "docker logs sub2api --tail 20" # 确认版本号(必须与步骤 0 中设置的版本号一致) ssh clicodeplus "cat /root/sub2api/backend/cmd/server/VERSION" # 检查容器状态(必须显示 healthy) ssh clicodeplus "docker ps | grep sub2api" ``` --- ### 构建服务器首次初始化 首次使用 `us-asaki-root` 作为构建服务器时,需要执行以下一次性操作: ```bash ssh us-asaki-root # 1) 克隆仓库 cd /root git clone https://github.com/touwaeriol/sub2api.git sub2api cd sub2api # 2) 验证 Docker 和 buildx 版本 docker version docker buildx version # 如果 buildx 版本过旧(< v0.14),执行更新: # LATEST=$(curl -fsSL https://api.github.com/repos/docker/buildx/releases/latest | grep tag_name | cut -d'"' -f4) # curl -fsSL "https://github.com/docker/buildx/releases/download/${LATEST}/buildx-${LATEST}.linux-amd64" -o ~/.docker/cli-plugins/docker-buildx # chmod +x ~/.docker/cli-plugins/docker-buildx # 3) 验证构建能力 docker build --no-cache -t sub2api:test -f Dockerfile . docker rmi sub2api:test ``` --- ## Beta 并行部署(不影响现网) 目标:在同一台服务器上并行启动一个 beta 实例(例如端口 `8084`),**严禁改动/重启**现网实例(默认目录 `/root/sub2api`)。 ### 设计原则 - **新目录**:beta 使用独立目录,例如 `/root/sub2api-beta`。 - **敏感信息只放 `.env`**:beta 的数据库密码、JWT_SECRET 等只写入 `/root/sub2api-beta/deploy/.env`,不要提交到 git。 - **独立 Compose Project**:通过 `docker compose -p sub2api-beta ...` 启动,确保 network/volume 隔离。 - **独立端口**:通过 `.env` 的 `SERVER_PORT` 映射宿主机端口(例如 `8084:8080`)。 ### 前置检查 ```bash # 1) 确保 8084 未被占用 ssh clicodeplus "ss -ltnp | grep :8084 || echo '8084 is free'" # 2) 确认现网容器还在(只读检查) ssh clicodeplus "docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}' | sed -n '1,200p'" ``` ### 首次部署步骤 > **构建服务器说明**:正式和 beta 共用构建服务器上的 `/root/sub2api` 仓库,通过不同的镜像标签区分(`sub2api:latest` 用于正式,`sub2api:beta` 用于测试)。 ```bash # 1) 构建服务器构建 beta 镜像(共用 /root/sub2api 仓库,切到目标分支后打 beta 标签) ssh us-asaki-root "cd /root/sub2api && git fetch origin && git checkout -B release/custom-0.1.71 origin/release/custom-0.1.71" ssh us-asaki-root "cd /root/sub2api && docker build --no-cache -t sub2api:beta -f Dockerfile ." # ⚠️ 构建完成后如需恢复正式分支: # ssh us-asaki-root "cd /root/sub2api && git checkout release/custom-<正式版本>" # 2) 传输镜像到生产服务器 ssh us-asaki-root "docker save sub2api:beta" | ssh clicodeplus "docker load" # ⚠️ 必须看到 "Loaded image: sub2api:beta" 输出 # 3) 在生产服务器上准备 beta 环境 ssh clicodeplus # 克隆代码(仅用于 deploy 配置和版本号确认,不在此构建) cd /root git clone https://github.com/touwaeriol/sub2api.git sub2api-beta cd /root/sub2api-beta git checkout release/custom-0.1.71 # 4) 准备 beta 的 .env(敏感信息只写这里) cd /root/sub2api-beta/deploy # 推荐:从现网 .env 复制,保证除 DB 名/用户/端口外完全一致 cp -f /root/sub2api/deploy/.env ./.env # 仅修改以下三项(其他保持不变) perl -pi -e 's/^SERVER_PORT=.*/SERVER_PORT=8084/' ./.env perl -pi -e 's/^POSTGRES_USER=.*/POSTGRES_USER=beta/' ./.env perl -pi -e 's/^POSTGRES_DB=.*/POSTGRES_DB=beta/' ./.env # 5) 写 compose override(避免与现网容器名冲突,镜像使用构建服务器传输的 sub2api:beta) cat > docker-compose.override.yml <<'YAML' services: sub2api: image: sub2api:beta container_name: sub2api-beta redis: container_name: sub2api-beta-redis YAML # 6) 启动 beta(独立 project,确保不影响现网) cd /root/sub2api-beta/deploy docker compose -p sub2api-beta --env-file .env -f docker-compose.yml -f docker-compose.override.yml up -d # 7) 验证 beta curl -fsS http://127.0.0.1:8084/health docker logs sub2api-beta --tail 50 ``` ### 数据库配置约定(beta) - 数据库地址/SSL/密码:与现网一致(从现网 `.env` 复制即可)。 - 仅修改: - `POSTGRES_USER=beta` - `POSTGRES_DB=beta` 注意:需要数据库侧已存在 `beta` 用户与 `beta` 数据库,并授予权限;否则容器会启动失败并不断重启。 ### 更新 beta(构建服务器构建 + 传输 + 仅重启 beta 容器) ```bash # 1) 构建服务器拉取代码并构建镜像(共用 /root/sub2api 仓库) ssh us-asaki-root "cd /root/sub2api && git fetch origin && git checkout -B release/custom-0.1.71 origin/release/custom-0.1.71" ssh us-asaki-root "cd /root/sub2api && docker build --no-cache -t sub2api:beta -f Dockerfile ." # ⚠️ 必须看到构建成功输出 # 2) 传输镜像到生产服务器 ssh us-asaki-root "docker save sub2api:beta" | ssh clicodeplus "docker load" # ⚠️ 必须看到 "Loaded image: sub2api:beta" 输出 # 3) 生产服务器同步代码(用于版本号确认和 deploy 配置) ssh clicodeplus "set -e; cd /root/sub2api-beta && git fetch --all --tags && git checkout -f release/custom-0.1.71 && git reset --hard origin/release/custom-0.1.71" # 4) 重启 beta 容器并验证 ssh clicodeplus "cd /root/sub2api-beta/deploy && docker compose -p sub2api-beta --env-file .env -f docker-compose.yml -f docker-compose.override.yml up -d --no-deps --force-recreate sub2api" ssh clicodeplus "sleep 5 && curl -fsS http://127.0.0.1:8084/health" ssh clicodeplus "cat /root/sub2api-beta/backend/cmd/server/VERSION" ``` ### 停止/回滚 beta(只影响 beta) ```bash ssh clicodeplus "cd /root/sub2api-beta/deploy && docker compose -p sub2api-beta -f docker-compose.yml -f docker-compose.override.yml down" ``` --- ## 服务器首次部署 ### 1. 构建服务器:克隆代码并配置远程仓库 ```bash ssh us-asaki-root cd /root git clone https://github.com/Wei-Shaw/sub2api.git cd sub2api # 添加 fork 仓库 git remote add fork https://github.com/touwaeriol/sub2api.git ``` ### 2. 构建服务器:切换到定制分支并构建镜像 ```bash git fetch fork git checkout -B release/custom-0.1.69 fork/release/custom-0.1.69 cd /root/sub2api docker build -t sub2api:latest -f Dockerfile . exit ``` ### 3. 传输镜像到生产服务器 ```bash ssh us-asaki-root "docker save sub2api:latest" | ssh clicodeplus "docker load" ``` ### 4. 生产服务器:克隆代码并配置环境 ```bash ssh clicodeplus cd /root git clone https://github.com/Wei-Shaw/sub2api.git cd sub2api # 添加 fork 仓库 git remote add fork https://github.com/touwaeriol/sub2api.git git fetch fork git checkout -B release/custom-0.1.69 fork/release/custom-0.1.69 # 配置环境变量 cd deploy cp .env.example .env vim .env # 配置 DATABASE_URL, REDIS_URL, JWT_SECRET 等 ``` ### 5. 生产服务器:更新镜像标签并启动服务 ```bash docker tag sub2api:latest weishaw/sub2api:latest cd /root/sub2api/deploy && docker compose up -d ``` ### 6. 验证部署 ```bash # 查看应用日志 docker logs sub2api --tail 50 # 检查健康状态 curl http://localhost:8080/health # 确认版本号 cat /root/sub2api/backend/cmd/server/VERSION ``` ### 7. 常用运维命令 ```bash # 查看实时日志 docker logs -f sub2api # 重启服务 docker compose restart sub2api # 停止所有服务 docker compose down # 停止并删除数据卷(慎用!会删除数据库数据) docker compose down -v # 查看资源使用情况 docker stats sub2api ``` --- ## 定制功能说明 当前定制分支包含以下功能(相对于官方版本): ### UI/UX 定制 | 功能 | 说明 | |------|------| | 首页优化 | 面向用户的价值主张设计 | | 移除 GitHub 链接 | 用户菜单中不显示 GitHub 导航 | | 微信客服按钮 | 首页悬浮微信客服入口 | | 限流时间精确显示 | 账号限流时间显示精确到秒 | ### Antigravity 平台增强 | 功能 | 说明 | |------|------| | Scope 级别限流 | 按配额域(claude/gemini_text/gemini_image)独立限流,避免整个账号被锁定 | | 模型级别限流 | 按具体模型(如 claude-opus-4-5)独立限流,更精细的限流控制 | | 限流预检查 | 调度时预检查账号/模型限流状态,避免选中已限流账号 | | 秒级冷却时间 | 支持 429 响应的秒级精确冷却时间 | | 身份注入优化 | 模型身份信息注入 + 静默边界防止身份泄露 | | thoughtSignature 修复 | Gemini 3 函数调用 400 错误修复 | | max_tokens 自动修正 | 自动修正 max_tokens <= budget_tokens 导致的 400 错误 | ### 调度算法优化 | 功能 | 说明 | |------|------| | 分层过滤选择 | 调度算法从全排序改为分层过滤,提升性能 | | LRU 随机选择 | 相同 LRU 时间时随机选择,避免账号集中 | | 限流等待阈值配置化 | 可配置的限流等待阈值 | ### 运维增强 | 功能 | 说明 | |------|------| | Scope 限流统计 | 运维界面展示 Antigravity 账号 scope 级别限流统计 | | 账号限流状态显示 | 账号列表显示 scope 和模型级别限流状态 | | 清除限流按钮增强 | 有 scope/模型限流时也显示清除限流按钮 | ### 其他修复 | 功能 | 说明 | |------|------| | .gitattributes | 确保迁移文件使用 LF 换行符(解决 Windows 下 SQL 摘要不一致) | | 部署配置优化 | DATABASE_HOST 和 DATABASE_SSLMODE 可通过 .env 配置 | --- ## 注意事项 1. **前端必须打包进镜像**:使用 `docker build` 在构建服务器(`us-asaki-root`)上构建,Dockerfile 会自动编译前端并 embed 到后端二进制中,构建完成后通过 `docker save | docker load` 传输到生产服务器(`clicodeplus`) 2. **镜像标签**:docker-compose.yml 使用 `weishaw/sub2api:latest`,本地构建后需要 `docker tag` 覆盖 3. **Windows 换行符问题**:已通过 `.gitattributes` 解决,确保 `*.sql` 文件始终使用 LF 4. **版本号管理**:每次发布必须更新 `backend/cmd/server/VERSION` 并打标签 5. **合并冲突**:合并上游新版本时,重点关注以下文件可能的冲突: - `backend/internal/service/antigravity_gateway_service.go` - `backend/internal/service/gateway_service.go` - `backend/internal/pkg/antigravity/request_transformer.go` --- ## Go 代码规范 ### 1. 函数设计 #### 单一职责原则 - **函数行数**:单个函数常规不应超过 **30 行**,超过时应拆分为子函数。若某段逻辑确实不可拆分(如复杂的状态机、协议解析等),可以例外,但需添加注释说明原因 - **嵌套层级**:避免超过 3 层嵌套,使用 early return 减少嵌套 ```go // ❌ 不推荐:深层嵌套 func process(data []Item) { for _, item := range data { if item.Valid { if item.Type == "A" { if item.Status == "active" { // 业务逻辑... } } } } } // ✅ 推荐:early return func process(data []Item) { for _, item := range data { if !item.Valid { continue } if item.Type != "A" { continue } if item.Status != "active" { continue } // 业务逻辑... } } ``` #### 复杂逻辑提取 将复杂的条件判断或处理逻辑提取为独立函数: ```go // ❌ 不推荐:内联复杂逻辑 if resp.StatusCode == 429 || resp.StatusCode == 503 { // 80+ 行处理逻辑... } // ✅ 推荐:提取为独立函数 result := handleRateLimitResponse(resp, params) switch result.action { case actionRetry: continue case actionBreak: return result.resp, nil } ``` ### 2. 重复代码消除 #### 配置获取模式 将重复的配置获取逻辑提取为方法: ```go // ❌ 不推荐:重复代码 logBody := s.settingService != nil && s.settingService.cfg != nil && s.settingService.cfg.Gateway.LogUpstreamErrorBody maxBytes := 2048 if s.settingService != nil && s.settingService.cfg != nil && s.settingService.cfg.Gateway.LogUpstreamErrorBodyMaxBytes > 0 { maxBytes = s.settingService.cfg.Gateway.LogUpstreamErrorBodyMaxBytes } // ✅ 推荐:提取为方法 func (s *Service) getLogConfig() (logBody bool, maxBytes int) { maxBytes = 2048 if s.settingService == nil || s.settingService.cfg == nil { return false, maxBytes } cfg := s.settingService.cfg.Gateway if cfg.LogUpstreamErrorBodyMaxBytes > 0 { maxBytes = cfg.LogUpstreamErrorBodyMaxBytes } return cfg.LogUpstreamErrorBody, maxBytes } ``` ### 3. 常量管理 #### 避免魔法数字 所有硬编码的数值都应定义为常量: ```go // ❌ 不推荐 if retryDelay >= 10*time.Second { resetAt := time.Now().Add(30 * time.Second) } // ✅ 推荐 const ( rateLimitThreshold = 10 * time.Second defaultRateLimitDuration = 30 * time.Second ) if retryDelay >= rateLimitThreshold { resetAt := time.Now().Add(defaultRateLimitDuration) } ``` #### 注释引用常量名 在注释中引用常量名而非硬编码值: ```go // ❌ 不推荐 // < 10s: 等待后重试 // ✅ 推荐 // < rateLimitThreshold: 等待后重试 ``` ### 4. 错误处理 #### 使用结构化日志 优先使用 `slog` 进行结构化日志记录: ```go // ❌ 不推荐 log.Printf("%s status=%d model_rate_limit_failed model=%s error=%v", prefix, statusCode, modelName, err) // ✅ 推荐 slog.Error("failed to set model rate limit", "prefix", prefix, "status_code", statusCode, "model", modelName, "error", err, ) ``` ### 5. 测试规范 #### Mock 函数签名同步 修改函数签名时,必须同步更新所有测试中的 mock 函数: ```go // 如果修改了 handleError 签名 handleError func(..., groupID int64, sessionHash string) *Result // 必须同步更新测试中的 mock handleError: func(..., groupID int64, sessionHash string) *Result { return nil }, ``` #### 测试构建标签 统一使用测试构建标签: ```go //go:build unit package service ``` ### 6. 时间格式解析 #### 使用标准库 优先使用 `time.ParseDuration`,支持所有 Go duration 格式: ```go // ❌ 不推荐:手动限制格式 if !strings.HasSuffix(delay, "s") || strings.Contains(delay, "m") { continue } // ✅ 推荐:使用标准库 dur, err := time.ParseDuration(delay) // 支持 "0.5s", "4m50s", "1h30m" 等 ``` ### 7. 接口设计 #### 接口隔离原则 定义最小化接口,只包含必需的方法: ```go // ❌ 不推荐:使用过于宽泛的接口 type AccountRepository interface { // 20+ 个方法... } // ✅ 推荐:定义最小化接口 type ModelRateLimiter interface { SetModelRateLimit(ctx context.Context, id int64, modelKey string, resetAt time.Time) error } ``` ### 8. 并发安全 #### 共享数据保护 访问可能被并发修改的数据时,确保线程安全: ```go // 如果 Account.Extra 可能被并发修改 // 需要使用互斥锁或原子操作保护读取 func (a *Account) GetRateLimitRemainingTime(model string) time.Duration { a.mu.RLock() defer a.mu.RUnlock() // 读取 Extra 字段... } ``` ### 9. 命名规范 #### 一致的命名风格 - 常量使用 camelCase:`rateLimitThreshold` - 类型使用 PascalCase:`AntigravityQuotaScope` - 同一概念使用统一命名:`Threshold` 或 `Limit`,不要混用 ```go // ❌ 不推荐:命名不一致 antigravitySmartRetryMinWait // 使用 Min antigravityRateLimitThreshold // 使用 Threshold // ✅ 推荐:统一风格 antigravityMinRetryWait antigravityRateLimitThreshold ``` ### 10. 代码审查清单 在提交代码前,检查以下项目: - [ ] 函数是否超过 30 行?(不可拆分的逻辑除外,需注释说明) - [ ] 嵌套是否超过 3 层? - [ ] 是否有重复代码可以提取? - [ ] 是否使用了魔法数字? - [ ] Mock 函数签名是否与实际函数一致? - [ ] 测试是否覆盖了新增逻辑? - [ ] 日志是否包含足够的上下文信息? - [ ] 是否考虑了并发安全? --- ## CI 检查与发布门禁 ### GitHub Actions 检查项 本项目有 4 个 CI 任务,**任何代码推送或发布前都必须全部通过**: | Workflow | Job | 说明 | 本地验证命令 | |----------|-----|------|-------------| | CI | `test` | 单元测试 + 集成测试 | `cd backend && make test-unit && make test-integration` | | CI | `golangci-lint` | Go 代码静态检查(golangci-lint v2.7) | `cd backend && golangci-lint run --timeout=5m` | | Security Scan | `backend-security` | govulncheck + gosec 安全扫描 | `cd backend && govulncheck ./... && gosec -severity high -confidence high ./...` | | Security Scan | `frontend-security` | pnpm audit 前端依赖安全检查 | `cd frontend && pnpm audit --prod --audit-level=high` | ### 向上游提交 PR PR 目标是上游官方仓库,**只包含通用功能改动**(bug fix、新功能、性能优化等)。 **以下文件禁止出现在 PR 中**(属于我们 fork 的定制化内容): - `CLAUDE.md`、`AGENTS.md` — 我们的开发文档 - `backend/cmd/server/VERSION` — 我们的版本号文件 - UI 定制改动(GitHub 链接移除、微信客服按钮、首页定制等) - 部署配置(`deploy/` 目录下的定制修改) **PR 流程**: 1. 从 `develop` 创建功能分支,只包含要提交给上游的改动 2. 推送分支后,**等待 4 个 CI job 全部通过** 3. 确认通过后再创建 PR 4. 使用 `gh run list --repo touwaeriol/sub2api --branch ` 检查状态 ### 自有分支推送(develop / main) 推送到我们自己的 `develop` 或 `main` 分支时,包含所有改动(定制化 + 通用功能)。 **推送前必须在本地执行全部 CI 检查**(不要等 GitHub Actions): ```bash # 确保 Go 工具链可用(macOS homebrew) export PATH="/opt/homebrew/bin:$HOME/go/bin:$PATH" # 1. 单元测试(必须) cd backend && make test-unit # 2. 集成测试(推荐,需要 Docker) make test-integration # 3. golangci-lint 静态检查(必须) golangci-lint run --timeout=5m # 4. gofmt 格式检查(必须) gofmt -l ./... # 如果有输出,运行 gofmt -w 修复 ``` **推送后确认**: 1. 使用 `gh run list --repo touwaeriol/sub2api --branch ` 检查 GitHub Actions 状态 2. 确认 CI 和 Security Scan 两个 workflow 的 4 个 job 全部绿色 ✅ 3. 任何 job 失败必须立即修复,**禁止在 CI 未通过的状态下继续后续操作** ### 发布版本 1. 本地执行上述全部 CI 检查通过 2. 递增 `backend/cmd/server/VERSION`,提交并推送 3. 推送后确认 GitHub Actions 的 4 个 CI job 全部通过 4. **CI 未通过时禁止部署** — 必须先修复问题 5. 使用 `gh run list --repo touwaeriol/sub2api --limit 10` 确认状态 ### 常见 CI 失败原因及修复 - **gofmt**:struct 字段对齐不一致 → 运行 `gofmt -w ` 修复 - **golangci-lint**:未使用的变量/导入 → 删除或使用 `_` 忽略 - **test 失败**:mock 函数签名不一致 → 同步更新 mock - **gosec**:安全漏洞 → 根据提示修复或添加例外