453 lines
12 KiB
Markdown
453 lines
12 KiB
Markdown
|
|
# Sub2API 模块分析报告:账户管理模块
|
|||
|
|
|
|||
|
|
## 1. 模块概述
|
|||
|
|
|
|||
|
|
### 1.1 模块定位
|
|||
|
|
账户管理模块是 Sub2API 的上游资源管理核心,负责管理连接到 AI 服务提供商的账号(Account)。这些账号是系统转发请求的"上游凭证",包括 OAuth 授权账号、API Key 账号等。
|
|||
|
|
|
|||
|
|
### 1.2 核心职责
|
|||
|
|
- **账号 CRUD**:创建、读取、更新、删除上游账号
|
|||
|
|
- **账号状态管理**:监控账号健康、处理限流和过期
|
|||
|
|
- **分组管理**:将账号分组以实现资源隔离和配额控制
|
|||
|
|
- **账号测试**:验证账号有效性(TestConnection)
|
|||
|
|
|
|||
|
|
## 2. 代码结构分析
|
|||
|
|
|
|||
|
|
### 2.1 核心文件
|
|||
|
|
|
|||
|
|
| 文件路径 | 职责 | 代码行数 |
|
|||
|
|
|---------|------|----------|
|
|||
|
|
| `service/account.go` | 账号实体定义和服务基础 | ~800 行 |
|
|||
|
|
| `service/account_service.go` | 账号管理核心逻辑 | ~1200 行 |
|
|||
|
|
| `service/account_group.go` | 分组管理服务 | ~500 行 |
|
|||
|
|
| `service/account_usage_service.go` | 账号用量监控 | ~400 行 |
|
|||
|
|
| `service/account_expiry_service.go` | 账号过期管理 | ~300 行 |
|
|||
|
|
| `handler/admin/account_handler.go` | 账号管理 API | ~1900 行 |
|
|||
|
|
| `repository/account_repo.go` | 账号数据访问层 | ~1800 行 |
|
|||
|
|
|
|||
|
|
### 2.2 数据模型
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// ent/schema/account.go
|
|||
|
|
type Account struct {
|
|||
|
|
ID int64 // 主键
|
|||
|
|
Platform string // 平台:anthropic/openai/gemini/antigravity/bedrock
|
|||
|
|
Type string // 类型:oauth/apikey
|
|||
|
|
Name string // 账号名称(显示用)
|
|||
|
|
Credentials string // 加密凭证(JSON)
|
|||
|
|
Extra string // 额外配置(JSON)
|
|||
|
|
Status string // 状态:active/error/disabled/expired
|
|||
|
|
GroupID int64 // 所属分组
|
|||
|
|
RateMultiplier float64 // 计费倍率
|
|||
|
|
MaxConcurrency int // 最大并发
|
|||
|
|
BaseURL string // 自定义上游地址
|
|||
|
|
CreatedAt time.Time
|
|||
|
|
UpdatedAt time.Time
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 功能详细分析
|
|||
|
|
|
|||
|
|
### 3.1 账号类型支持
|
|||
|
|
|
|||
|
|
| 平台 | 类型 | 认证方式 | 特性 |
|
|||
|
|
|------|------|----------|------|
|
|||
|
|
| **Anthropic** | OAuth / API Key | Bearer Token | Claude 模型支持 |
|
|||
|
|
| **OpenAI** | API Key | Bearer Token | ChatGPT、GPT 模型 |
|
|||
|
|
| **Google Gemini** | API Key | Bearer Token | 多模态支持 |
|
|||
|
|
| **Antigravity** | OAuth | 独立认证系统 | 独立配额 |
|
|||
|
|
| **AWS Bedrock** | API Key | AWS 签名 v4 | Claude on Bedrock |
|
|||
|
|
|
|||
|
|
### 3.2 账号创建流程
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_service.go - CreateAccount
|
|||
|
|
func (s *AccountService) CreateAccount(ctx context.Context, req CreateAccountRequest) (*Account, error) {
|
|||
|
|
// 1. 验证请求参数
|
|||
|
|
if err := validateAccountRequest(req); err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 加密凭证
|
|||
|
|
encryptedCreds, err := s.encryptCredentials(req.Credentials)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 创建账号
|
|||
|
|
account := &Account{
|
|||
|
|
Platform: req.Platform,
|
|||
|
|
Type: req.Type,
|
|||
|
|
Name: req.Name,
|
|||
|
|
Credentials: encryptedCreds,
|
|||
|
|
GroupID: req.GroupID,
|
|||
|
|
Status: StatusActive,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 保存到数据库
|
|||
|
|
return s.accountRepo.Create(ctx, account)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 账号验证 (TestConnection)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_test_service.go - TestAccount
|
|||
|
|
func (s *AccountTestService) TestAccount(ctx context.Context, account *Account) (*TestResult, error) {
|
|||
|
|
// 1. 构建测试请求
|
|||
|
|
testReq := buildTestRequest(account)
|
|||
|
|
|
|||
|
|
// 2. 发送请求
|
|||
|
|
resp, err := s.sendRequest(ctx, account, testReq)
|
|||
|
|
if err != nil {
|
|||
|
|
return &TestResult{
|
|||
|
|
Success: false,
|
|||
|
|
Error: err.Error(),
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 检查响应
|
|||
|
|
if !isSuccessResponse(resp) {
|
|||
|
|
return &TestResult{
|
|||
|
|
Success: false,
|
|||
|
|
StatusCode: resp.StatusCode,
|
|||
|
|
Error: parseError(resp.Body),
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 解析响应(获取配额信息)
|
|||
|
|
quota := parseQuotaInfo(resp.Body)
|
|||
|
|
return &TestResult{
|
|||
|
|
Success: true,
|
|||
|
|
QuotaInfo: quota,
|
|||
|
|
StatusCode: 200,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**验证端点:**
|
|||
|
|
| 平台 | 测试端点 | 验证内容 |
|
|||
|
|
|------|----------|----------|
|
|||
|
|
| Anthropic | `/v1/messages` | 基本连通性 + 配额 |
|
|||
|
|
| OpenAI | `/v1/models` | 模型列表 |
|
|||
|
|
| Gemini | `/v1/models` | 模型列表 |
|
|||
|
|
| Antigravity | `/v1/models` | 模型列表 |
|
|||
|
|
| Bedrock | `/invocations` | 基本连通性 |
|
|||
|
|
|
|||
|
|
### 3.4 账号状态管理
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 账号状态转换
|
|||
|
|
const (
|
|||
|
|
StatusActive = "active" // 正常可用
|
|||
|
|
StatusError = "error" // 出错(临时)
|
|||
|
|
StatusDisabled = "disabled" // 管理员禁用
|
|||
|
|
StatusExpired = "expired" // 已过期
|
|||
|
|
StatusRateLimited = "rate_limited" // 被限流
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 状态检查
|
|||
|
|
func (a *Account) CanUse() bool {
|
|||
|
|
return a.Status == StatusActive || a.Status == StatusRateLimited
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**状态变更触发:**
|
|||
|
|
- **Active → Error**:TestConnection 失败、请求返回 5xx
|
|||
|
|
- **Active → RateLimited**:上游返回 429
|
|||
|
|
- **Active → Expired**:检测到过期时间
|
|||
|
|
- **Any → Disabled**:管理员手动操作
|
|||
|
|
|
|||
|
|
### 3.5 账号分组管理
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_group.go
|
|||
|
|
type Group struct {
|
|||
|
|
ID int64
|
|||
|
|
Name string
|
|||
|
|
Platform string // 平台类型
|
|||
|
|
Status string
|
|||
|
|
RateMultiplier float64 // 计费倍率
|
|||
|
|
MaxConcurrency int // 分组最大并发
|
|||
|
|
Models []string // 允许的模型列表
|
|||
|
|
IsExclusive bool // 独占模式(只能被单个用户使用)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**分组特性:**
|
|||
|
|
- 按平台隔离:不同平台使用不同分组
|
|||
|
|
- 模型限制:分组可限定可用模型
|
|||
|
|
- 独占模式:某些分组只允许单个用户访问
|
|||
|
|
- 计费倍率:不同分组可有不同计费策略
|
|||
|
|
|
|||
|
|
### 3.6 账号用量追踪
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_usage_service.go
|
|||
|
|
func (s *AccountUsageService) RecordUsage(ctx context.Context, accountID int64, tokens int, cost float64) error {
|
|||
|
|
// 1. 更新内存统计
|
|||
|
|
s.localStats.Add(accountID, tokens, cost)
|
|||
|
|
|
|||
|
|
// 2. 更新 Redis 统计
|
|||
|
|
s.redisStats.Incr(accountID, tokens, cost)
|
|||
|
|
|
|||
|
|
// 3. 定期同步到数据库
|
|||
|
|
if s.shouldSync(accountID) {
|
|||
|
|
s.syncToDB(accountID)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前负载
|
|||
|
|
func (s *AccountUsageService) GetLoadFactor(accountID int64) float64 {
|
|||
|
|
activeConns := s.GetActiveConnections(accountID)
|
|||
|
|
maxConns := s.GetMaxConcurrency(accountID)
|
|||
|
|
return float64(activeConns) / float64(maxConns)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 高级功能
|
|||
|
|
|
|||
|
|
### 4.1 账号预热
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_intercept_warmup.go
|
|||
|
|
// 新账号首次使用前进行预热请求
|
|||
|
|
func WarmupAccount(account *Account) error {
|
|||
|
|
// 发送轻量级请求建立连接
|
|||
|
|
req := &Request{
|
|||
|
|
Model: "claude-3-haiku-20240307", // 最小的模型
|
|||
|
|
MaxTokens: 1,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
resp, err := sendRequest(account, req)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 预热后更新状态
|
|||
|
|
account.Status = StatusActive
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 账号健康检查
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_health_check.go
|
|||
|
|
func (s *HealthCheckService) CheckAccount(accountID int64) {
|
|||
|
|
account := s.getAccount(accountID)
|
|||
|
|
|
|||
|
|
// 检查最后活跃时间
|
|||
|
|
if time.Since(account.LastUsedAt) > 24*time.Hour {
|
|||
|
|
// 超过 24 小时未使用,发送探测请求
|
|||
|
|
s.testAccount(account)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查过期时间
|
|||
|
|
if account.ExpiresAt != nil && time.Now().After(*account.ExpiresAt) {
|
|||
|
|
s.updateStatus(accountID, StatusExpired)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 账号配额重置
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_quota_reset.go
|
|||
|
|
func (s *QuotaResetService) ResetDailyQuota() {
|
|||
|
|
// 每天 UTC 0 点重置
|
|||
|
|
for _, account := range s.getAllAccounts() {
|
|||
|
|
account.UsageToday = 0
|
|||
|
|
account.UpdatedAt = time.Now()
|
|||
|
|
s.accountRepo.Update(account)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 数据访问层
|
|||
|
|
|
|||
|
|
### 5.1 Repository 接口
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// repository/account_repo.go
|
|||
|
|
type AccountRepository interface {
|
|||
|
|
Create(ctx context.Context, account *Account) (*Account, error)
|
|||
|
|
GetByID(ctx context.Context, id int64) (*Account, error)
|
|||
|
|
GetByGroupID(ctx context.Context, groupID int64) ([]*Account, error)
|
|||
|
|
Update(ctx context.Context, account *Account) error
|
|||
|
|
Delete(ctx context.Context, id int64) error
|
|||
|
|
|
|||
|
|
// 高级查询
|
|||
|
|
GetAvailableAccounts(ctx context.Context, groupID int64) ([]*Account, error)
|
|||
|
|
Search(ctx context.Context, filters AccountFilters) ([]*Account, error)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 查询优化
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 常用查询优化
|
|||
|
|
func (r *accountRepository) GetAvailableAccounts(ctx context.Context, groupID int64) ([]*Account, error) {
|
|||
|
|
return r.client.Account.Query().
|
|||
|
|
Where(
|
|||
|
|
account.GroupID(groupID),
|
|||
|
|
account.StatusEQ("active"),
|
|||
|
|
account.DeletedAtIsNil(),
|
|||
|
|
).
|
|||
|
|
All(ctx)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 6. 配置参数
|
|||
|
|
|
|||
|
|
### 6.1 账号配置(config.yaml)
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
account:
|
|||
|
|
# 账号健康检查
|
|||
|
|
health_check:
|
|||
|
|
enabled: true
|
|||
|
|
interval: 5m
|
|||
|
|
timeout: 30s
|
|||
|
|
|
|||
|
|
# 账号预热
|
|||
|
|
warmup:
|
|||
|
|
enabled: true
|
|||
|
|
model: "claude-3-haiku-20240307"
|
|||
|
|
|
|||
|
|
# 配额重置
|
|||
|
|
quota_reset:
|
|||
|
|
timezone: "UTC"
|
|||
|
|
hour: 0
|
|||
|
|
|
|||
|
|
# 限流配置
|
|||
|
|
rate_limit:
|
|||
|
|
window: 60s
|
|||
|
|
max_errors: 3
|
|||
|
|
reset_after: 300s
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 环境变量
|
|||
|
|
|
|||
|
|
| 变量 | 说明 | 默认值 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| `ACCOUNT_HEALTH_CHECK_ENABLED` | 启用健康检查 | true |
|
|||
|
|
| `ACCOUNT_WARMUP_ENABLED` | 启用预热 | true |
|
|||
|
|
| `ACCOUNT_MAX_CONCURRENCY` | 默认最大并发 | 10 |
|
|||
|
|
|
|||
|
|
## 7. 修改和扩展指南
|
|||
|
|
|
|||
|
|
### 7.1 常见修改场景
|
|||
|
|
|
|||
|
|
**场景 1:添加新的账号类型**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 1. 在 domain/constants.go 添加类型
|
|||
|
|
const AccountTypeCustom = "custom"
|
|||
|
|
|
|||
|
|
// 2. 在 service/account.go 添加验证
|
|||
|
|
func (a *Account) ValidateCustom() error {
|
|||
|
|
// 自定义验证逻辑
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 在 handler 中添加创建接口
|
|||
|
|
router.POST("/accounts/custom", createCustomAccount)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**场景 2:调整账号选择策略**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_service.go - SelectAccount
|
|||
|
|
func (s *AccountService) SelectAccount(ctx context.Context, groupID int64, model string) (*Account, error) {
|
|||
|
|
// 调整选择逻辑
|
|||
|
|
accounts := s.getAvailableAccounts(groupID)
|
|||
|
|
|
|||
|
|
// 按负载排序
|
|||
|
|
sort.Slice(accounts, func(i, j int) bool {
|
|||
|
|
return accounts[i].LoadFactor < accounts[j].LoadFactor
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return accounts[0], nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**场景 3:修改限流行为**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// service/account_service.go - HandleRateLimit
|
|||
|
|
func (s *AccountService) HandleRateLimit(accountID int64, resetAfter time.Duration) error {
|
|||
|
|
// 修改限流持续时间
|
|||
|
|
account, _ := s.GetByID(accountID)
|
|||
|
|
account.Status = StatusRateLimited
|
|||
|
|
account.RateLimitedAt = time.Now()
|
|||
|
|
account.RateLimitResetAt = time.Now().Add(resetAfter * 2) // 倍增
|
|||
|
|
|
|||
|
|
return s.Update(account)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 注意事项
|
|||
|
|
|
|||
|
|
1. **凭证安全**:账号凭证必须加密存储,密钥独立管理
|
|||
|
|
2. **并发安全**:账号选择需要考虑并发场景
|
|||
|
|
3. **状态一致性**:账号状态变更需要同步到缓存
|
|||
|
|
|
|||
|
|
## 8. 测试覆盖
|
|||
|
|
|
|||
|
|
### 8.1 单元测试
|
|||
|
|
|
|||
|
|
| 测试文件 | 覆盖范围 |
|
|||
|
|
|----------|----------|
|
|||
|
|
| `account_service_test.go` | 账号 CRUD |
|
|||
|
|
| `account_load_factor_test.go` | 负载计算 |
|
|||
|
|
| `account_expiry_service_test.go` | 过期处理 |
|
|||
|
|
| `account_test_service_openai_test.go` | OpenAI 测试 |
|
|||
|
|
|
|||
|
|
### 8.2 集成测试
|
|||
|
|
|
|||
|
|
| 测试文件 | 场景 |
|
|||
|
|
|----------|------|
|
|||
|
|
| `e2e_gateway_test.go` | 账号选择和请求转发 |
|
|||
|
|
|
|||
|
|
## 9. 监控与运维
|
|||
|
|
|
|||
|
|
### 9.1 关键指标
|
|||
|
|
|
|||
|
|
| 指标 | 告警阈值 | 说明 |
|
|||
|
|
|------|----------|------|
|
|||
|
|
| `account_error_count` | > 10 | 账号错误数 |
|
|||
|
|
| `account_rate_limited` | > 20% | 账号限流比例 |
|
|||
|
|
| `account_expired` | > 5% | 账号过期比例 |
|
|||
|
|
| `account_test_success_rate` | < 90% | 测试成功率 |
|
|||
|
|
|
|||
|
|
### 9.2 运维任务
|
|||
|
|
|
|||
|
|
| 任务 | 频率 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 健康检查 | 5 分钟 | 检查账号可用性 |
|
|||
|
|
| 配额重置 | 每天 | 重置每日用量 |
|
|||
|
|
| 过期检查 | 每天 | 检查账号过期时间 |
|
|||
|
|
| 统计同步 | 每小时 | 同步用量到数据库 |
|
|||
|
|
|
|||
|
|
## 10. 总结
|
|||
|
|
|
|||
|
|
账户管理模块特点:
|
|||
|
|
|
|||
|
|
- **多平台支持**:统一接口管理多种 AI 服务账号
|
|||
|
|
- **状态自动化**:自动处理限流、过期等状态
|
|||
|
|
- **分组隔离**:通过分组实现资源隔离和配额控制
|
|||
|
|
- **健康监控**:持续的账号健康检查和预热
|
|||
|
|
|
|||
|
|
**潜在改进点:**
|
|||
|
|
1. 账号测试可以更全面(模拟实际请求)
|
|||
|
|
2. 支持更多账号配置选项(代理、超时等)
|
|||
|
|
|
|||
|
|
**修改建议:**
|
|||
|
|
- 账号类型扩展相对简单,风险较低
|
|||
|
|
- 状态管理逻辑修改需充分测试
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
*文档版本:1.0*
|
|||
|
|
*最后更新:2025-01*
|
|||
|
|
*分析基于:Sub2API v0.1.104*
|