mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
724 lines
21 KiB
Markdown
724 lines
21 KiB
Markdown
|
|
# 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 <fix-commit-hash>
|
|||
|
|
git push origin main
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 服务器部署流程
|
|||
|
|
|
|||
|
|
### 前置条件
|
|||
|
|
|
|||
|
|
- 本地已配置 SSH 别名 `clicodeplus` 连接到服务器
|
|||
|
|
- 服务器部署目录:`/root/sub2api`(正式)、`/root/sub2api-beta`(测试)
|
|||
|
|
- 服务器使用 Docker Compose 部署
|
|||
|
|
|
|||
|
|
### 部署环境说明
|
|||
|
|
|
|||
|
|
| 环境 | 目录 | 端口 | 数据库 | 容器名 |
|
|||
|
|
|------|------|------|--------|--------|
|
|||
|
|
| 正式 | `/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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1. 服务器拉取代码
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh clicodeplus "cd /root/sub2api && git fetch fork && git checkout -B release/custom-0.1.69 fork/release/custom-0.1.69"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 服务器构建镜像
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh clicodeplus "cd /root/sub2api && docker build --no-cache -t sub2api:latest -f Dockerfile ."
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 更新镜像标签并重启服务
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh clicodeplus "docker tag sub2api:latest weishaw/sub2api:latest"
|
|||
|
|
ssh clicodeplus "cd /root/sub2api/deploy && docker compose up -d --force-recreate sub2api"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. 验证部署
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看启动日志
|
|||
|
|
ssh clicodeplus "docker logs sub2api --tail 20"
|
|||
|
|
|
|||
|
|
# 确认版本号(必须与步骤 0 中设置的版本号一致)
|
|||
|
|
ssh clicodeplus "cat /root/sub2api/backend/cmd/server/VERSION"
|
|||
|
|
|
|||
|
|
# 检查容器状态
|
|||
|
|
ssh clicodeplus "docker ps | grep sub2api"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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'"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 首次部署步骤
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 0) 进入服务器
|
|||
|
|
ssh clicodeplus
|
|||
|
|
|
|||
|
|
# 1) 克隆代码到新目录(示例使用你的 fork)
|
|||
|
|
cd /root
|
|||
|
|
git clone https://github.com/touwaeriol/sub2api.git sub2api-beta
|
|||
|
|
cd /root/sub2api-beta
|
|||
|
|
git checkout release/custom-0.1.71
|
|||
|
|
|
|||
|
|
# 2) 准备 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
|
|||
|
|
|
|||
|
|
# 3) 写 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
|
|||
|
|
|
|||
|
|
# 4) 构建 beta 镜像(基于当前代码)
|
|||
|
|
cd /root/sub2api-beta
|
|||
|
|
docker build -t sub2api:beta -f Dockerfile .
|
|||
|
|
|
|||
|
|
# 5) 启动 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
|
|||
|
|
|
|||
|
|
# 6) 验证 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
|
|||
|
|
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"
|
|||
|
|
ssh clicodeplus "cd /root/sub2api-beta && docker build -t sub2api:beta -f Dockerfile ."
|
|||
|
|
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 "curl -fsS http://127.0.0.1:8084/health"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 停止/回滚 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 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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 切换到定制分支并配置环境
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
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 等
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 构建并启动
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /root/sub2api
|
|||
|
|
docker build -t sub2api:latest -f Dockerfile .
|
|||
|
|
docker tag sub2api:latest weishaw/sub2api:latest
|
|||
|
|
cd deploy && docker compose up -d
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. 启动服务
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 进入 deploy 目录
|
|||
|
|
cd deploy
|
|||
|
|
|
|||
|
|
# 启动所有服务(PostgreSQL、Redis、sub2api)
|
|||
|
|
docker compose up -d
|
|||
|
|
|
|||
|
|
# 查看服务状态
|
|||
|
|
docker compose ps
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7. 验证部署
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看应用日志
|
|||
|
|
docker logs sub2api --tail 50
|
|||
|
|
|
|||
|
|
# 检查健康状态
|
|||
|
|
curl http://localhost:8080/health
|
|||
|
|
|
|||
|
|
# 确认版本号
|
|||
|
|
cat /root/sub2api/backend/cmd/server/VERSION
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8. 常用运维命令
|
|||
|
|
|
|||
|
|
```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` 在服务器上构建,Dockerfile 会自动编译前端并 embed 到后端二进制中
|
|||
|
|
|
|||
|
|
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 <branch>` 检查状态
|
|||
|
|
|
|||
|
|
### 自有分支推送(develop / main)
|
|||
|
|
|
|||
|
|
推送到我们自己的 `develop` 或 `main` 分支时,包含所有改动(定制化 + 通用功能)。
|
|||
|
|
|
|||
|
|
**推送流程**:
|
|||
|
|
1. 本地运行 `cd backend && make test-unit` 确保单元测试通过
|
|||
|
|
2. 本地运行 `cd backend && gofmt -l ./...` 确保格式正确
|
|||
|
|
3. 推送后确认 CI 和 Security Scan 两个 workflow 的 4 个 job 全部绿色 ✅
|
|||
|
|
4. 任何 job 失败必须立即修复,**禁止在 CI 未通过的状态下继续后续操作**
|
|||
|
|
|
|||
|
|
### 发布版本
|
|||
|
|
|
|||
|
|
1. 确保 `main` 分支最新提交的 4 个 CI job 全部通过
|
|||
|
|
2. 递增 `backend/cmd/server/VERSION`,提交并推送
|
|||
|
|
3. 打 tag 推送后,确认 tag 触发的 3 个 workflow(CI、Security Scan、Release)全部通过
|
|||
|
|
4. **Release workflow 失败时禁止部署** — 必须先修复问题,删除旧 tag,重新打 tag
|
|||
|
|
5. 使用 `gh run list --repo touwaeriol/sub2api --limit 10` 确认状态
|
|||
|
|
|
|||
|
|
### 常见 CI 失败原因及修复
|
|||
|
|
- **gofmt**:struct 字段对齐不一致 → 运行 `gofmt -w <file>` 修复
|
|||
|
|
- **golangci-lint**:未使用的变量/导入 → 删除或使用 `_` 忽略
|
|||
|
|
- **test 失败**:mock 函数签名不一致 → 同步更新 mock
|
|||
|
|
- **gosec**:安全漏洞 → 根据提示修复或添加例外
|