"frontend/src/components/vscode:/vscode.git/clone" did not exist on "669bff78c490085ee97579dd37fcca8ef806d80a"
Commit a4953785 authored by IanShaw027's avatar IanShaw027
Browse files

fix(lint): 修复所有 Go 命名规范问题

- 全局替换 ApiKey → APIKey(类型、字段、方法、变量)
- 修复所有 initialism 命名(API, SMTP, HTML, URL 等)
- 添加所有缺失的包注释
- 修复导出符号的注释格式

主要修改:
- ApiKey → APIKey(所有出现的地方)
- ApiKeyID → APIKeyID
- ApiKeyIDs → APIKeyIDs
- TestSmtpConnection → TestSMTPConnection
- HtmlURL → HTMLURL
- 添加 20+ 个包注释
- 修复 10+ 个导出符号注释格式

验证结果:
- ✓ golangci-lint: 0 issues
- ✓ 单元测试: 通过
- ✓ 集成测试: 通过
parent d92e71a1
...@@ -445,7 +445,7 @@ func (s *BillingCacheService) InvalidateSubscription(ctx context.Context, userID ...@@ -445,7 +445,7 @@ func (s *BillingCacheService) InvalidateSubscription(ctx context.Context, userID
// CheckBillingEligibility 检查用户是否有资格发起请求 // CheckBillingEligibility 检查用户是否有资格发起请求
// 余额模式:检查缓存余额 > 0 // 余额模式:检查缓存余额 > 0
// 订阅模式:检查缓存用量未超过限额(Group限额从参数传入) // 订阅模式:检查缓存用量未超过限额(Group限额从参数传入)
func (s *BillingCacheService) CheckBillingEligibility(ctx context.Context, user *User, apiKey *ApiKey, group *Group, subscription *UserSubscription) error { func (s *BillingCacheService) CheckBillingEligibility(ctx context.Context, user *User, apiKey *APIKey, group *Group, subscription *UserSubscription) error {
// 简易模式:跳过所有计费检查 // 简易模式:跳过所有计费检查
if s.cfg.RunMode == config.RunModeSimple { if s.cfg.RunMode == config.RunModeSimple {
return nil return nil
......
...@@ -430,7 +430,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput ...@@ -430,7 +430,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
account := &Account{ account := &Account{
Name: defaultName(src.Name, src.ID), Name: defaultName(src.Name, src.ID),
Platform: PlatformAnthropic, Platform: PlatformAnthropic,
Type: AccountTypeApiKey, Type: AccountTypeAPIKey,
Credentials: credentials, Credentials: credentials,
Extra: extra, Extra: extra,
ProxyID: proxyID, ProxyID: proxyID,
...@@ -455,7 +455,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput ...@@ -455,7 +455,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
existing.Extra = mergeMap(existing.Extra, extra) existing.Extra = mergeMap(existing.Extra, extra)
existing.Name = defaultName(src.Name, src.ID) existing.Name = defaultName(src.Name, src.ID)
existing.Platform = PlatformAnthropic existing.Platform = PlatformAnthropic
existing.Type = AccountTypeApiKey existing.Type = AccountTypeAPIKey
existing.Credentials = mergeMap(existing.Credentials, credentials) existing.Credentials = mergeMap(existing.Credentials, credentials)
if proxyID != nil { if proxyID != nil {
existing.ProxyID = proxyID existing.ProxyID = proxyID
...@@ -674,7 +674,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput ...@@ -674,7 +674,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
account := &Account{ account := &Account{
Name: defaultName(src.Name, src.ID), Name: defaultName(src.Name, src.ID),
Platform: PlatformOpenAI, Platform: PlatformOpenAI,
Type: AccountTypeApiKey, Type: AccountTypeAPIKey,
Credentials: credentials, Credentials: credentials,
Extra: extra, Extra: extra,
ProxyID: proxyID, ProxyID: proxyID,
...@@ -699,7 +699,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput ...@@ -699,7 +699,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
existing.Extra = mergeMap(existing.Extra, extra) existing.Extra = mergeMap(existing.Extra, extra)
existing.Name = defaultName(src.Name, src.ID) existing.Name = defaultName(src.Name, src.ID)
existing.Platform = PlatformOpenAI existing.Platform = PlatformOpenAI
existing.Type = AccountTypeApiKey existing.Type = AccountTypeAPIKey
existing.Credentials = mergeMap(existing.Credentials, credentials) existing.Credentials = mergeMap(existing.Credentials, credentials)
if proxyID != nil { if proxyID != nil {
existing.ProxyID = proxyID existing.ProxyID = proxyID
...@@ -893,7 +893,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput ...@@ -893,7 +893,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
account := &Account{ account := &Account{
Name: defaultName(src.Name, src.ID), Name: defaultName(src.Name, src.ID),
Platform: PlatformGemini, Platform: PlatformGemini,
Type: AccountTypeApiKey, Type: AccountTypeAPIKey,
Credentials: credentials, Credentials: credentials,
Extra: extra, Extra: extra,
ProxyID: proxyID, ProxyID: proxyID,
...@@ -918,7 +918,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput ...@@ -918,7 +918,7 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
existing.Extra = mergeMap(existing.Extra, extra) existing.Extra = mergeMap(existing.Extra, extra)
existing.Name = defaultName(src.Name, src.ID) existing.Name = defaultName(src.Name, src.ID)
existing.Platform = PlatformGemini existing.Platform = PlatformGemini
existing.Type = AccountTypeApiKey existing.Type = AccountTypeAPIKey
existing.Credentials = mergeMap(existing.Credentials, credentials) existing.Credentials = mergeMap(existing.Credentials, credentials)
if proxyID != nil { if proxyID != nil {
existing.ProxyID = proxyID existing.ProxyID = proxyID
......
...@@ -43,8 +43,8 @@ func (s *DashboardService) GetModelStatsWithFilters(ctx context.Context, startTi ...@@ -43,8 +43,8 @@ func (s *DashboardService) GetModelStatsWithFilters(ctx context.Context, startTi
return stats, nil return stats, nil
} }
func (s *DashboardService) GetApiKeyUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.ApiKeyUsageTrendPoint, error) { func (s *DashboardService) GetAPIKeyUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.APIKeyUsageTrendPoint, error) {
trend, err := s.usageRepo.GetApiKeyUsageTrend(ctx, startTime, endTime, granularity, limit) trend, err := s.usageRepo.GetAPIKeyUsageTrend(ctx, startTime, endTime, granularity, limit)
if err != nil { if err != nil {
return nil, fmt.Errorf("get api key usage trend: %w", err) return nil, fmt.Errorf("get api key usage trend: %w", err)
} }
...@@ -67,8 +67,8 @@ func (s *DashboardService) GetBatchUserUsageStats(ctx context.Context, userIDs [ ...@@ -67,8 +67,8 @@ func (s *DashboardService) GetBatchUserUsageStats(ctx context.Context, userIDs [
return stats, nil return stats, nil
} }
func (s *DashboardService) GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchApiKeyUsageStats, error) { func (s *DashboardService) GetBatchAPIKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchAPIKeyUsageStats, error) {
stats, err := s.usageRepo.GetBatchApiKeyUsageStats(ctx, apiKeyIDs) stats, err := s.usageRepo.GetBatchAPIKeyUsageStats(ctx, apiKeyIDs)
if err != nil { if err != nil {
return nil, fmt.Errorf("get batch api key usage stats: %w", err) return nil, fmt.Errorf("get batch api key usage stats: %w", err)
} }
......
...@@ -28,7 +28,7 @@ const ( ...@@ -28,7 +28,7 @@ const (
const ( const (
AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference) AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference)
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope) AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
AccountTypeApiKey = "apikey" // API Key类型账号 AccountTypeAPIKey = "apikey" // API Key类型账号
) )
// Redeem type constants // Redeem type constants
...@@ -64,13 +64,13 @@ const ( ...@@ -64,13 +64,13 @@ const (
SettingKeyEmailVerifyEnabled = "email_verify_enabled" // 是否开启邮件验证 SettingKeyEmailVerifyEnabled = "email_verify_enabled" // 是否开启邮件验证
// 邮件服务设置 // 邮件服务设置
SettingKeySmtpHost = "smtp_host" // SMTP服务器地址 SettingKeySMTPHost = "smtp_host" // SMTP服务器地址
SettingKeySmtpPort = "smtp_port" // SMTP端口 SettingKeySMTPPort = "smtp_port" // SMTP端口
SettingKeySmtpUsername = "smtp_username" // SMTP用户名 SettingKeySMTPUsername = "smtp_username" // SMTP用户名
SettingKeySmtpPassword = "smtp_password" // SMTP密码(加密存储) SettingKeySMTPPassword = "smtp_password" // SMTP密码(加密存储)
SettingKeySmtpFrom = "smtp_from" // 发件人地址 SettingKeySMTPFrom = "smtp_from" // 发件人地址
SettingKeySmtpFromName = "smtp_from_name" // 发件人名称 SettingKeySMTPFromName = "smtp_from_name" // 发件人名称
SettingKeySmtpUseTLS = "smtp_use_tls" // 是否使用TLS SettingKeySMTPUseTLS = "smtp_use_tls" // 是否使用TLS
// Cloudflare Turnstile 设置 // Cloudflare Turnstile 设置
SettingKeyTurnstileEnabled = "turnstile_enabled" // 是否启用 Turnstile 验证 SettingKeyTurnstileEnabled = "turnstile_enabled" // 是否启用 Turnstile 验证
...@@ -81,16 +81,16 @@ const ( ...@@ -81,16 +81,16 @@ const (
SettingKeySiteName = "site_name" // 网站名称 SettingKeySiteName = "site_name" // 网站名称
SettingKeySiteLogo = "site_logo" // 网站Logo (base64) SettingKeySiteLogo = "site_logo" // 网站Logo (base64)
SettingKeySiteSubtitle = "site_subtitle" // 网站副标题 SettingKeySiteSubtitle = "site_subtitle" // 网站副标题
SettingKeyApiBaseUrl = "api_base_url" // API端点地址(用于客户端配置和导入) SettingKeyAPIBaseURL = "api_base_url" // API端点地址(用于客户端配置和导入)
SettingKeyContactInfo = "contact_info" // 客服联系方式 SettingKeyContactInfo = "contact_info" // 客服联系方式
SettingKeyDocUrl = "doc_url" // 文档链接 SettingKeyDocURL = "doc_url" // 文档链接
// 默认配置 // 默认配置
SettingKeyDefaultConcurrency = "default_concurrency" // 新用户默认并发量 SettingKeyDefaultConcurrency = "default_concurrency" // 新用户默认并发量
SettingKeyDefaultBalance = "default_balance" // 新用户默认余额 SettingKeyDefaultBalance = "default_balance" // 新用户默认余额
// 管理员 API Key // 管理员 API Key
SettingKeyAdminApiKey = "admin_api_key" // 全局管理员 API Key(用于外部系统集成) SettingKeyAdminAPIKey = "admin_api_key" // 全局管理员 API Key(用于外部系统集成)
// Gemini 配额策略(JSON) // Gemini 配额策略(JSON)
SettingKeyGeminiQuotaPolicy = "gemini_quota_policy" SettingKeyGeminiQuotaPolicy = "gemini_quota_policy"
...@@ -103,5 +103,5 @@ const ( ...@@ -103,5 +103,5 @@ const (
SettingKeyFallbackModelAntigravity = "fallback_model_antigravity" SettingKeyFallbackModelAntigravity = "fallback_model_antigravity"
) )
// Admin API Key prefix (distinct from user "sk-" keys) // AdminAPIKeyPrefix is the prefix for admin API keys (distinct from user "sk-" keys).
const AdminApiKeyPrefix = "admin-" const AdminAPIKeyPrefix = "admin-"
...@@ -40,8 +40,8 @@ const ( ...@@ -40,8 +40,8 @@ const (
maxVerifyCodeAttempts = 5 maxVerifyCodeAttempts = 5
) )
// SmtpConfig SMTP配置 // SMTPConfig SMTP配置
type SmtpConfig struct { type SMTPConfig struct {
Host string Host string
Port int Port int
Username string Username string
...@@ -65,16 +65,16 @@ func NewEmailService(settingRepo SettingRepository, cache EmailCache) *EmailServ ...@@ -65,16 +65,16 @@ func NewEmailService(settingRepo SettingRepository, cache EmailCache) *EmailServ
} }
} }
// GetSmtpConfig 从数据库获取SMTP配置 // GetSMTPConfig 从数据库获取SMTP配置
func (s *EmailService) GetSmtpConfig(ctx context.Context) (*SmtpConfig, error) { func (s *EmailService) GetSMTPConfig(ctx context.Context) (*SMTPConfig, error) {
keys := []string{ keys := []string{
SettingKeySmtpHost, SettingKeySMTPHost,
SettingKeySmtpPort, SettingKeySMTPPort,
SettingKeySmtpUsername, SettingKeySMTPUsername,
SettingKeySmtpPassword, SettingKeySMTPPassword,
SettingKeySmtpFrom, SettingKeySMTPFrom,
SettingKeySmtpFromName, SettingKeySMTPFromName,
SettingKeySmtpUseTLS, SettingKeySMTPUseTLS,
} }
settings, err := s.settingRepo.GetMultiple(ctx, keys) settings, err := s.settingRepo.GetMultiple(ctx, keys)
...@@ -82,34 +82,34 @@ func (s *EmailService) GetSmtpConfig(ctx context.Context) (*SmtpConfig, error) { ...@@ -82,34 +82,34 @@ func (s *EmailService) GetSmtpConfig(ctx context.Context) (*SmtpConfig, error) {
return nil, fmt.Errorf("get smtp settings: %w", err) return nil, fmt.Errorf("get smtp settings: %w", err)
} }
host := settings[SettingKeySmtpHost] host := settings[SettingKeySMTPHost]
if host == "" { if host == "" {
return nil, ErrEmailNotConfigured return nil, ErrEmailNotConfigured
} }
port := 587 // 默认端口 port := 587 // 默认端口
if portStr := settings[SettingKeySmtpPort]; portStr != "" { if portStr := settings[SettingKeySMTPPort]; portStr != "" {
if p, err := strconv.Atoi(portStr); err == nil { if p, err := strconv.Atoi(portStr); err == nil {
port = p port = p
} }
} }
useTLS := settings[SettingKeySmtpUseTLS] == "true" useTLS := settings[SettingKeySMTPUseTLS] == "true"
return &SmtpConfig{ return &SMTPConfig{
Host: host, Host: host,
Port: port, Port: port,
Username: settings[SettingKeySmtpUsername], Username: settings[SettingKeySMTPUsername],
Password: settings[SettingKeySmtpPassword], Password: settings[SettingKeySMTPPassword],
From: settings[SettingKeySmtpFrom], From: settings[SettingKeySMTPFrom],
FromName: settings[SettingKeySmtpFromName], FromName: settings[SettingKeySMTPFromName],
UseTLS: useTLS, UseTLS: useTLS,
}, nil }, nil
} }
// SendEmail 发送邮件(使用数据库中保存的配置) // SendEmail 发送邮件(使用数据库中保存的配置)
func (s *EmailService) SendEmail(ctx context.Context, to, subject, body string) error { func (s *EmailService) SendEmail(ctx context.Context, to, subject, body string) error {
config, err := s.GetSmtpConfig(ctx) config, err := s.GetSMTPConfig(ctx)
if err != nil { if err != nil {
return err return err
} }
...@@ -117,7 +117,7 @@ func (s *EmailService) SendEmail(ctx context.Context, to, subject, body string) ...@@ -117,7 +117,7 @@ func (s *EmailService) SendEmail(ctx context.Context, to, subject, body string)
} }
// SendEmailWithConfig 使用指定配置发送邮件 // SendEmailWithConfig 使用指定配置发送邮件
func (s *EmailService) SendEmailWithConfig(config *SmtpConfig, to, subject, body string) error { func (s *EmailService) SendEmailWithConfig(config *SMTPConfig, to, subject, body string) error {
from := config.From from := config.From
if config.FromName != "" { if config.FromName != "" {
from = fmt.Sprintf("%s <%s>", config.FromName, config.From) from = fmt.Sprintf("%s <%s>", config.FromName, config.From)
...@@ -306,8 +306,8 @@ func (s *EmailService) buildVerifyCodeEmailBody(code, siteName string) string { ...@@ -306,8 +306,8 @@ func (s *EmailService) buildVerifyCodeEmailBody(code, siteName string) string {
`, siteName, code) `, siteName, code)
} }
// TestSmtpConnectionWithConfig 使用指定配置测试SMTP连接 // TestSMTPConnectionWithConfig 使用指定配置测试SMTP连接
func (s *EmailService) TestSmtpConnectionWithConfig(config *SmtpConfig) error { func (s *EmailService) TestSMTPConnectionWithConfig(config *SMTPConfig) error {
addr := fmt.Sprintf("%s:%d", config.Host, config.Port) addr := fmt.Sprintf("%s:%d", config.Host, config.Port)
if config.UseTLS { if config.UseTLS {
......
...@@ -282,7 +282,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_GeminiOAuthPreference( ...@@ -282,7 +282,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_GeminiOAuthPreference(
repo := &mockAccountRepoForPlatform{ repo := &mockAccountRepoForPlatform{
accounts: []Account{ accounts: []Account{
{ID: 1, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeApiKey}, {ID: 1, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeAPIKey},
{ID: 2, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeOAuth}, {ID: 2, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeOAuth},
}, },
accountsByID: map[int64]*Account{}, accountsByID: map[int64]*Account{},
...@@ -623,7 +623,7 @@ func TestGatewayService_selectAccountWithMixedScheduling(t *testing.T) { ...@@ -623,7 +623,7 @@ func TestGatewayService_selectAccountWithMixedScheduling(t *testing.T) {
t.Run("混合调度-Gemini优先选择OAuth账户", func(t *testing.T) { t.Run("混合调度-Gemini优先选择OAuth账户", func(t *testing.T) {
repo := &mockAccountRepoForPlatform{ repo := &mockAccountRepoForPlatform{
accounts: []Account{ accounts: []Account{
{ID: 1, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeApiKey}, {ID: 1, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeAPIKey},
{ID: 2, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeOAuth}, {ID: 2, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true, Type: AccountTypeOAuth},
}, },
accountsByID: map[int64]*Account{}, accountsByID: map[int64]*Account{},
......
...@@ -907,7 +907,7 @@ func (s *GatewayService) GetAccessToken(ctx context.Context, account *Account) ( ...@@ -907,7 +907,7 @@ func (s *GatewayService) GetAccessToken(ctx context.Context, account *Account) (
case AccountTypeOAuth, AccountTypeSetupToken: case AccountTypeOAuth, AccountTypeSetupToken:
// Both oauth and setup-token use OAuth token flow // Both oauth and setup-token use OAuth token flow
return s.getOAuthToken(ctx, account) return s.getOAuthToken(ctx, account)
case AccountTypeApiKey: case AccountTypeAPIKey:
apiKey := account.GetCredential("api_key") apiKey := account.GetCredential("api_key")
if apiKey == "" { if apiKey == "" {
return "", "", errors.New("api_key not found in credentials") return "", "", errors.New("api_key not found in credentials")
...@@ -1045,7 +1045,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -1045,7 +1045,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// 应用模型映射(仅对apikey类型账号) // 应用模型映射(仅对apikey类型账号)
originalModel := reqModel originalModel := reqModel
if account.Type == AccountTypeApiKey { if account.Type == AccountTypeAPIKey {
mappedModel := account.GetMappedModel(reqModel) mappedModel := account.GetMappedModel(reqModel)
if mappedModel != reqModel { if mappedModel != reqModel {
// 替换请求体中的模型名 // 替换请求体中的模型名
...@@ -1223,7 +1223,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A ...@@ -1223,7 +1223,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Context, account *Account, body []byte, token, tokenType, modelID string) (*http.Request, error) { func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Context, account *Account, body []byte, token, tokenType, modelID string) (*http.Request, error) {
// 确定目标URL // 确定目标URL
targetURL := claudeAPIURL targetURL := claudeAPIURL
if account.Type == AccountTypeApiKey { if account.Type == AccountTypeAPIKey {
baseURL := account.GetBaseURL() baseURL := account.GetBaseURL()
targetURL = baseURL + "/v1/messages" targetURL = baseURL + "/v1/messages"
} }
...@@ -1287,10 +1287,10 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex ...@@ -1287,10 +1287,10 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
// 处理anthropic-beta header(OAuth账号需要特殊处理) // 处理anthropic-beta header(OAuth账号需要特殊处理)
if tokenType == "oauth" { if tokenType == "oauth" {
req.Header.Set("anthropic-beta", s.getBetaHeader(modelID, c.GetHeader("anthropic-beta"))) req.Header.Set("anthropic-beta", s.getBetaHeader(modelID, c.GetHeader("anthropic-beta")))
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForApiKey && req.Header.Get("anthropic-beta") == "" { } else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
// API-key:仅在请求显式使用 beta 特性且客户端未提供时,按需补齐(默认关闭) // API-key:仅在请求显式使用 beta 特性且客户端未提供时,按需补齐(默认关闭)
if requestNeedsBetaFeatures(body) { if requestNeedsBetaFeatures(body) {
if beta := defaultApiKeyBetaHeader(body); beta != "" { if beta := defaultAPIKeyBetaHeader(body); beta != "" {
req.Header.Set("anthropic-beta", beta) req.Header.Set("anthropic-beta", beta)
} }
} }
...@@ -1357,12 +1357,12 @@ func requestNeedsBetaFeatures(body []byte) bool { ...@@ -1357,12 +1357,12 @@ func requestNeedsBetaFeatures(body []byte) bool {
return false return false
} }
func defaultApiKeyBetaHeader(body []byte) string { func defaultAPIKeyBetaHeader(body []byte) string {
modelID := gjson.GetBytes(body, "model").String() modelID := gjson.GetBytes(body, "model").String()
if strings.Contains(strings.ToLower(modelID), "haiku") { if strings.Contains(strings.ToLower(modelID), "haiku") {
return claude.ApiKeyHaikuBetaHeader return claude.APIKeyHaikuBetaHeader
} }
return claude.ApiKeyBetaHeader return claude.APIKeyBetaHeader
} }
func truncateForLog(b []byte, maxBytes int) string { func truncateForLog(b []byte, maxBytes int) string {
...@@ -1780,7 +1780,7 @@ func (s *GatewayService) replaceModelInResponseBody(body []byte, fromModel, toMo ...@@ -1780,7 +1780,7 @@ func (s *GatewayService) replaceModelInResponseBody(body []byte, fromModel, toMo
// RecordUsageInput 记录使用量的输入参数 // RecordUsageInput 记录使用量的输入参数
type RecordUsageInput struct { type RecordUsageInput struct {
Result *ForwardResult Result *ForwardResult
ApiKey *ApiKey APIKey *APIKey
User *User User *User
Account *Account Account *Account
Subscription *UserSubscription // 可选:订阅信息 Subscription *UserSubscription // 可选:订阅信息
...@@ -1789,7 +1789,7 @@ type RecordUsageInput struct { ...@@ -1789,7 +1789,7 @@ type RecordUsageInput struct {
// RecordUsage 记录使用量并扣费(或更新订阅用量) // RecordUsage 记录使用量并扣费(或更新订阅用量)
func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInput) error { func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInput) error {
result := input.Result result := input.Result
apiKey := input.ApiKey apiKey := input.APIKey
user := input.User user := input.User
account := input.Account account := input.Account
subscription := input.Subscription subscription := input.Subscription
...@@ -1826,7 +1826,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -1826,7 +1826,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
durationMs := int(result.Duration.Milliseconds()) durationMs := int(result.Duration.Milliseconds())
usageLog := &UsageLog{ usageLog := &UsageLog{
UserID: user.ID, UserID: user.ID,
ApiKeyID: apiKey.ID, APIKeyID: apiKey.ID,
AccountID: account.ID, AccountID: account.ID,
RequestID: result.RequestID, RequestID: result.RequestID,
Model: result.Model, Model: result.Model,
...@@ -1914,7 +1914,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, ...@@ -1914,7 +1914,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
} }
// 应用模型映射(仅对 apikey 类型账号) // 应用模型映射(仅对 apikey 类型账号)
if account.Type == AccountTypeApiKey { if account.Type == AccountTypeAPIKey {
if reqModel != "" { if reqModel != "" {
mappedModel := account.GetMappedModel(reqModel) mappedModel := account.GetMappedModel(reqModel)
if mappedModel != reqModel { if mappedModel != reqModel {
...@@ -2018,7 +2018,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context, ...@@ -2018,7 +2018,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Context, account *Account, body []byte, token, tokenType, modelID string) (*http.Request, error) { func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Context, account *Account, body []byte, token, tokenType, modelID string) (*http.Request, error) {
// 确定目标 URL // 确定目标 URL
targetURL := claudeAPICountTokensURL targetURL := claudeAPICountTokensURL
if account.Type == AccountTypeApiKey { if account.Type == AccountTypeAPIKey {
baseURL := account.GetBaseURL() baseURL := account.GetBaseURL()
targetURL = baseURL + "/v1/messages/count_tokens" targetURL = baseURL + "/v1/messages/count_tokens"
} }
...@@ -2077,10 +2077,10 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con ...@@ -2077,10 +2077,10 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
// OAuth 账号:处理 anthropic-beta header // OAuth 账号:处理 anthropic-beta header
if tokenType == "oauth" { if tokenType == "oauth" {
req.Header.Set("anthropic-beta", s.getBetaHeader(modelID, c.GetHeader("anthropic-beta"))) req.Header.Set("anthropic-beta", s.getBetaHeader(modelID, c.GetHeader("anthropic-beta")))
} else if s.cfg != nil && s.cfg.Gateway.InjectBetaForApiKey && req.Header.Get("anthropic-beta") == "" { } else if s.cfg != nil && s.cfg.Gateway.InjectBetaForAPIKey && req.Header.Get("anthropic-beta") == "" {
// API-key:与 messages 同步的按需 beta 注入(默认关闭) // API-key:与 messages 同步的按需 beta 注入(默认关闭)
if requestNeedsBetaFeatures(body) { if requestNeedsBetaFeatures(body) {
if beta := defaultApiKeyBetaHeader(body); beta != "" { if beta := defaultAPIKeyBetaHeader(body); beta != "" {
req.Header.Set("anthropic-beta", beta) req.Header.Set("anthropic-beta", beta)
} }
} }
......
...@@ -273,7 +273,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForAIStudioEndpoints(ctx cont ...@@ -273,7 +273,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForAIStudioEndpoints(ctx cont
return 999 return 999
} }
switch a.Type { switch a.Type {
case AccountTypeApiKey: case AccountTypeAPIKey:
if strings.TrimSpace(a.GetCredential("api_key")) != "" { if strings.TrimSpace(a.GetCredential("api_key")) != "" {
return 0 return 0
} }
...@@ -351,7 +351,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex ...@@ -351,7 +351,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
originalModel := req.Model originalModel := req.Model
mappedModel := req.Model mappedModel := req.Model
if account.Type == AccountTypeApiKey { if account.Type == AccountTypeAPIKey {
mappedModel = account.GetMappedModel(req.Model) mappedModel = account.GetMappedModel(req.Model)
} }
...@@ -374,7 +374,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex ...@@ -374,7 +374,7 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
} }
switch account.Type { switch account.Type {
case AccountTypeApiKey: case AccountTypeAPIKey:
buildReq = func(ctx context.Context) (*http.Request, string, error) { buildReq = func(ctx context.Context) (*http.Request, string, error) {
apiKey := account.GetCredential("api_key") apiKey := account.GetCredential("api_key")
if strings.TrimSpace(apiKey) == "" { if strings.TrimSpace(apiKey) == "" {
...@@ -621,7 +621,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin. ...@@ -621,7 +621,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
} }
mappedModel := originalModel mappedModel := originalModel
if account.Type == AccountTypeApiKey { if account.Type == AccountTypeAPIKey {
mappedModel = account.GetMappedModel(originalModel) mappedModel = account.GetMappedModel(originalModel)
} }
...@@ -643,7 +643,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin. ...@@ -643,7 +643,7 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
var buildReq func(ctx context.Context) (*http.Request, string, error) var buildReq func(ctx context.Context) (*http.Request, string, error)
switch account.Type { switch account.Type {
case AccountTypeApiKey: case AccountTypeAPIKey:
buildReq = func(ctx context.Context) (*http.Request, string, error) { buildReq = func(ctx context.Context) (*http.Request, string, error) {
apiKey := account.GetCredential("api_key") apiKey := account.GetCredential("api_key")
if strings.TrimSpace(apiKey) == "" { if strings.TrimSpace(apiKey) == "" {
...@@ -1790,7 +1790,7 @@ func (s *GeminiMessagesCompatService) ForwardAIStudioGET(ctx context.Context, ac ...@@ -1790,7 +1790,7 @@ func (s *GeminiMessagesCompatService) ForwardAIStudioGET(ctx context.Context, ac
} }
switch account.Type { switch account.Type {
case AccountTypeApiKey: case AccountTypeAPIKey:
apiKey := strings.TrimSpace(account.GetCredential("api_key")) apiKey := strings.TrimSpace(account.GetCredential("api_key"))
if apiKey == "" { if apiKey == "" {
return nil, errors.New("gemini api_key not configured") return nil, errors.New("gemini api_key not configured")
......
...@@ -281,7 +281,7 @@ func TestGeminiMessagesCompatService_SelectAccountForModelWithExclusions_OAuthPr ...@@ -281,7 +281,7 @@ func TestGeminiMessagesCompatService_SelectAccountForModelWithExclusions_OAuthPr
repo := &mockAccountRepoForGemini{ repo := &mockAccountRepoForGemini{
accounts: []Account{ accounts: []Account{
{ID: 1, Platform: PlatformGemini, Type: AccountTypeApiKey, Priority: 1, Status: StatusActive, Schedulable: true, LastUsedAt: nil}, {ID: 1, Platform: PlatformGemini, Type: AccountTypeAPIKey, Priority: 1, Status: StatusActive, Schedulable: true, LastUsedAt: nil},
{ID: 2, Platform: PlatformGemini, Type: AccountTypeOAuth, Priority: 1, Status: StatusActive, Schedulable: true, LastUsedAt: nil}, {ID: 2, Platform: PlatformGemini, Type: AccountTypeOAuth, Priority: 1, Status: StatusActive, Schedulable: true, LastUsedAt: nil},
}, },
accountsByID: map[int64]*Account{}, accountsByID: map[int64]*Account{},
......
...@@ -353,7 +353,7 @@ func inferGoogleOneTier(storageBytes int64) string { ...@@ -353,7 +353,7 @@ func inferGoogleOneTier(storageBytes int64) string {
return GeminiTierGoogleOneUnknown return GeminiTierGoogleOneUnknown
} }
// fetchGoogleOneTier fetches Google One tier from Drive API // FetchGoogleOneTier fetches Google One tier from Drive API.
// Note: LoadCodeAssist API is NOT called for Google One accounts because: // Note: LoadCodeAssist API is NOT called for Google One accounts because:
// 1. It's designed for GCP IAM (enterprise), not personal Google accounts // 1. It's designed for GCP IAM (enterprise), not personal Google accounts
// 2. Personal accounts will get 403/404 from cloudaicompanion.googleapis.com // 2. Personal accounts will get 403/404 from cloudaicompanion.googleapis.com
......
...@@ -487,7 +487,7 @@ func (s *OpenAIGatewayService) GetAccessToken(ctx context.Context, account *Acco ...@@ -487,7 +487,7 @@ func (s *OpenAIGatewayService) GetAccessToken(ctx context.Context, account *Acco
return "", "", errors.New("access_token not found in credentials") return "", "", errors.New("access_token not found in credentials")
} }
return accessToken, "oauth", nil return accessToken, "oauth", nil
case AccountTypeApiKey: case AccountTypeAPIKey:
apiKey := account.GetOpenAIApiKey() apiKey := account.GetOpenAIApiKey()
if apiKey == "" { if apiKey == "" {
return "", "", errors.New("api_key not found in credentials") return "", "", errors.New("api_key not found in credentials")
...@@ -627,7 +627,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin. ...@@ -627,7 +627,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
case AccountTypeOAuth: case AccountTypeOAuth:
// OAuth accounts use ChatGPT internal API // OAuth accounts use ChatGPT internal API
targetURL = chatgptCodexURL targetURL = chatgptCodexURL
case AccountTypeApiKey: case AccountTypeAPIKey:
// API Key accounts use Platform API or custom base URL // API Key accounts use Platform API or custom base URL
baseURL := account.GetOpenAIBaseURL() baseURL := account.GetOpenAIBaseURL()
if baseURL != "" { if baseURL != "" {
...@@ -946,7 +946,7 @@ func (s *OpenAIGatewayService) replaceModelInResponseBody(body []byte, fromModel ...@@ -946,7 +946,7 @@ func (s *OpenAIGatewayService) replaceModelInResponseBody(body []byte, fromModel
// OpenAIRecordUsageInput input for recording usage // OpenAIRecordUsageInput input for recording usage
type OpenAIRecordUsageInput struct { type OpenAIRecordUsageInput struct {
Result *OpenAIForwardResult Result *OpenAIForwardResult
ApiKey *ApiKey APIKey *APIKey
User *User User *User
Account *Account Account *Account
Subscription *UserSubscription Subscription *UserSubscription
...@@ -955,7 +955,7 @@ type OpenAIRecordUsageInput struct { ...@@ -955,7 +955,7 @@ type OpenAIRecordUsageInput struct {
// RecordUsage records usage and deducts balance // RecordUsage records usage and deducts balance
func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRecordUsageInput) error { func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRecordUsageInput) error {
result := input.Result result := input.Result
apiKey := input.ApiKey apiKey := input.APIKey
user := input.User user := input.User
account := input.Account account := input.Account
subscription := input.Subscription subscription := input.Subscription
...@@ -997,7 +997,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec ...@@ -997,7 +997,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
durationMs := int(result.Duration.Milliseconds()) durationMs := int(result.Duration.Milliseconds())
usageLog := &UsageLog{ usageLog := &UsageLog{
UserID: user.ID, UserID: user.ID,
ApiKeyID: apiKey.ID, APIKeyID: apiKey.ID,
AccountID: account.ID, AccountID: account.ID,
RequestID: result.RequestID, RequestID: result.RequestID,
Model: result.Model, Model: result.Model,
......
...@@ -61,9 +61,9 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings ...@@ -61,9 +61,9 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
SettingKeySiteName, SettingKeySiteName,
SettingKeySiteLogo, SettingKeySiteLogo,
SettingKeySiteSubtitle, SettingKeySiteSubtitle,
SettingKeyApiBaseUrl, SettingKeyAPIBaseURL,
SettingKeyContactInfo, SettingKeyContactInfo,
SettingKeyDocUrl, SettingKeyDocURL,
} }
settings, err := s.settingRepo.GetMultiple(ctx, keys) settings, err := s.settingRepo.GetMultiple(ctx, keys)
...@@ -79,9 +79,9 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings ...@@ -79,9 +79,9 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"), SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"),
SiteLogo: settings[SettingKeySiteLogo], SiteLogo: settings[SettingKeySiteLogo],
SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"), SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"),
APIBaseURL: settings[SettingKeyApiBaseUrl], APIBaseURL: settings[SettingKeyAPIBaseURL],
ContactInfo: settings[SettingKeyContactInfo], ContactInfo: settings[SettingKeyContactInfo],
DocURL: settings[SettingKeyDocUrl], DocURL: settings[SettingKeyDocURL],
}, nil }, nil
} }
...@@ -94,15 +94,15 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet ...@@ -94,15 +94,15 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates[SettingKeyEmailVerifyEnabled] = strconv.FormatBool(settings.EmailVerifyEnabled) updates[SettingKeyEmailVerifyEnabled] = strconv.FormatBool(settings.EmailVerifyEnabled)
// 邮件服务设置(只有非空才更新密码) // 邮件服务设置(只有非空才更新密码)
updates[SettingKeySmtpHost] = settings.SMTPHost updates[SettingKeySMTPHost] = settings.SMTPHost
updates[SettingKeySmtpPort] = strconv.Itoa(settings.SMTPPort) updates[SettingKeySMTPPort] = strconv.Itoa(settings.SMTPPort)
updates[SettingKeySmtpUsername] = settings.SMTPUsername updates[SettingKeySMTPUsername] = settings.SMTPUsername
if settings.SMTPPassword != "" { if settings.SMTPPassword != "" {
updates[SettingKeySmtpPassword] = settings.SMTPPassword updates[SettingKeySMTPPassword] = settings.SMTPPassword
} }
updates[SettingKeySmtpFrom] = settings.SMTPFrom updates[SettingKeySMTPFrom] = settings.SMTPFrom
updates[SettingKeySmtpFromName] = settings.SMTPFromName updates[SettingKeySMTPFromName] = settings.SMTPFromName
updates[SettingKeySmtpUseTLS] = strconv.FormatBool(settings.SMTPUseTLS) updates[SettingKeySMTPUseTLS] = strconv.FormatBool(settings.SMTPUseTLS)
// Cloudflare Turnstile 设置(只有非空才更新密钥) // Cloudflare Turnstile 设置(只有非空才更新密钥)
updates[SettingKeyTurnstileEnabled] = strconv.FormatBool(settings.TurnstileEnabled) updates[SettingKeyTurnstileEnabled] = strconv.FormatBool(settings.TurnstileEnabled)
...@@ -115,9 +115,9 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet ...@@ -115,9 +115,9 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates[SettingKeySiteName] = settings.SiteName updates[SettingKeySiteName] = settings.SiteName
updates[SettingKeySiteLogo] = settings.SiteLogo updates[SettingKeySiteLogo] = settings.SiteLogo
updates[SettingKeySiteSubtitle] = settings.SiteSubtitle updates[SettingKeySiteSubtitle] = settings.SiteSubtitle
updates[SettingKeyApiBaseUrl] = settings.APIBaseURL updates[SettingKeyAPIBaseURL] = settings.APIBaseURL
updates[SettingKeyContactInfo] = settings.ContactInfo updates[SettingKeyContactInfo] = settings.ContactInfo
updates[SettingKeyDocUrl] = settings.DocURL updates[SettingKeyDocURL] = settings.DocURL
// 默认配置 // 默认配置
updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency) updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency)
...@@ -205,8 +205,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { ...@@ -205,8 +205,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeySiteLogo: "", SettingKeySiteLogo: "",
SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency), SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency),
SettingKeyDefaultBalance: strconv.FormatFloat(s.cfg.Default.UserBalance, 'f', 8, 64), SettingKeyDefaultBalance: strconv.FormatFloat(s.cfg.Default.UserBalance, 'f', 8, 64),
SettingKeySmtpPort: "587", SettingKeySMTPPort: "587",
SettingKeySmtpUseTLS: "false", SettingKeySMTPUseTLS: "false",
// Model fallback defaults // Model fallback defaults
SettingKeyEnableModelFallback: "false", SettingKeyEnableModelFallback: "false",
SettingKeyFallbackModelAnthropic: "claude-3-5-sonnet-20241022", SettingKeyFallbackModelAnthropic: "claude-3-5-sonnet-20241022",
...@@ -223,23 +223,23 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin ...@@ -223,23 +223,23 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
result := &SystemSettings{ result := &SystemSettings{
RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true", RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true",
EmailVerifyEnabled: settings[SettingKeyEmailVerifyEnabled] == "true", EmailVerifyEnabled: settings[SettingKeyEmailVerifyEnabled] == "true",
SMTPHost: settings[SettingKeySmtpHost], SMTPHost: settings[SettingKeySMTPHost],
SMTPUsername: settings[SettingKeySmtpUsername], SMTPUsername: settings[SettingKeySMTPUsername],
SMTPFrom: settings[SettingKeySmtpFrom], SMTPFrom: settings[SettingKeySMTPFrom],
SMTPFromName: settings[SettingKeySmtpFromName], SMTPFromName: settings[SettingKeySMTPFromName],
SMTPUseTLS: settings[SettingKeySmtpUseTLS] == "true", SMTPUseTLS: settings[SettingKeySMTPUseTLS] == "true",
TurnstileEnabled: settings[SettingKeyTurnstileEnabled] == "true", TurnstileEnabled: settings[SettingKeyTurnstileEnabled] == "true",
TurnstileSiteKey: settings[SettingKeyTurnstileSiteKey], TurnstileSiteKey: settings[SettingKeyTurnstileSiteKey],
SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"), SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"),
SiteLogo: settings[SettingKeySiteLogo], SiteLogo: settings[SettingKeySiteLogo],
SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"), SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"),
APIBaseURL: settings[SettingKeyApiBaseUrl], APIBaseURL: settings[SettingKeyAPIBaseURL],
ContactInfo: settings[SettingKeyContactInfo], ContactInfo: settings[SettingKeyContactInfo],
DocURL: settings[SettingKeyDocUrl], DocURL: settings[SettingKeyDocURL],
} }
// 解析整数类型 // 解析整数类型
if port, err := strconv.Atoi(settings[SettingKeySmtpPort]); err == nil { if port, err := strconv.Atoi(settings[SettingKeySMTPPort]); err == nil {
result.SMTPPort = port result.SMTPPort = port
} else { } else {
result.SMTPPort = 587 result.SMTPPort = 587
...@@ -259,7 +259,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin ...@@ -259,7 +259,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
} }
// 敏感信息直接返回,方便测试连接时使用 // 敏感信息直接返回,方便测试连接时使用
result.SMTPPassword = settings[SettingKeySmtpPassword] result.SMTPPassword = settings[SettingKeySMTPPassword]
result.TurnstileSecretKey = settings[SettingKeyTurnstileSecretKey] result.TurnstileSecretKey = settings[SettingKeyTurnstileSecretKey]
// Model fallback settings // Model fallback settings
...@@ -298,28 +298,28 @@ func (s *SettingService) GetTurnstileSecretKey(ctx context.Context) string { ...@@ -298,28 +298,28 @@ func (s *SettingService) GetTurnstileSecretKey(ctx context.Context) string {
return value return value
} }
// GenerateAdminApiKey 生成新的管理员 API Key // GenerateAdminAPIKey 生成新的管理员 API Key
func (s *SettingService) GenerateAdminApiKey(ctx context.Context) (string, error) { func (s *SettingService) GenerateAdminAPIKey(ctx context.Context) (string, error) {
// 生成 32 字节随机数 = 64 位十六进制字符 // 生成 32 字节随机数 = 64 位十六进制字符
bytes := make([]byte, 32) bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil { if _, err := rand.Read(bytes); err != nil {
return "", fmt.Errorf("generate random bytes: %w", err) return "", fmt.Errorf("generate random bytes: %w", err)
} }
key := AdminApiKeyPrefix + hex.EncodeToString(bytes) key := AdminAPIKeyPrefix + hex.EncodeToString(bytes)
// 存储到 settings 表 // 存储到 settings 表
if err := s.settingRepo.Set(ctx, SettingKeyAdminApiKey, key); err != nil { if err := s.settingRepo.Set(ctx, SettingKeyAdminAPIKey, key); err != nil {
return "", fmt.Errorf("save admin api key: %w", err) return "", fmt.Errorf("save admin api key: %w", err)
} }
return key, nil return key, nil
} }
// GetAdminApiKeyStatus 获取管理员 API Key 状态 // GetAdminAPIKeyStatus 获取管理员 API Key 状态
// 返回脱敏的 key、是否存在、错误 // 返回脱敏的 key、是否存在、错误
func (s *SettingService) GetAdminApiKeyStatus(ctx context.Context) (maskedKey string, exists bool, err error) { func (s *SettingService) GetAdminAPIKeyStatus(ctx context.Context) (maskedKey string, exists bool, err error) {
key, err := s.settingRepo.GetValue(ctx, SettingKeyAdminApiKey) key, err := s.settingRepo.GetValue(ctx, SettingKeyAdminAPIKey)
if err != nil { if err != nil {
if errors.Is(err, ErrSettingNotFound) { if errors.Is(err, ErrSettingNotFound) {
return "", false, nil return "", false, nil
...@@ -340,10 +340,10 @@ func (s *SettingService) GetAdminApiKeyStatus(ctx context.Context) (maskedKey st ...@@ -340,10 +340,10 @@ func (s *SettingService) GetAdminApiKeyStatus(ctx context.Context) (maskedKey st
return maskedKey, true, nil return maskedKey, true, nil
} }
// GetAdminApiKey 获取完整的管理员 API Key(仅供内部验证使用) // GetAdminAPIKey 获取完整的管理员 API Key(仅供内部验证使用)
// 如果未配置返回空字符串和 nil 错误,只有数据库错误时才返回 error // 如果未配置返回空字符串和 nil 错误,只有数据库错误时才返回 error
func (s *SettingService) GetAdminApiKey(ctx context.Context) (string, error) { func (s *SettingService) GetAdminAPIKey(ctx context.Context) (string, error) {
key, err := s.settingRepo.GetValue(ctx, SettingKeyAdminApiKey) key, err := s.settingRepo.GetValue(ctx, SettingKeyAdminAPIKey)
if err != nil { if err != nil {
if errors.Is(err, ErrSettingNotFound) { if errors.Is(err, ErrSettingNotFound) {
return "", nil // 未配置,返回空字符串 return "", nil // 未配置,返回空字符串
...@@ -353,9 +353,9 @@ func (s *SettingService) GetAdminApiKey(ctx context.Context) (string, error) { ...@@ -353,9 +353,9 @@ func (s *SettingService) GetAdminApiKey(ctx context.Context) (string, error) {
return key, nil return key, nil
} }
// DeleteAdminApiKey 删除管理员 API Key // DeleteAdminAPIKey 删除管理员 API Key
func (s *SettingService) DeleteAdminApiKey(ctx context.Context) error { func (s *SettingService) DeleteAdminAPIKey(ctx context.Context) error {
return s.settingRepo.Delete(ctx, SettingKeyAdminApiKey) return s.settingRepo.Delete(ctx, SettingKeyAdminAPIKey)
} }
// IsModelFallbackEnabled 检查是否启用模型兜底机制 // IsModelFallbackEnabled 检查是否启用模型兜底机制
......
...@@ -197,7 +197,7 @@ func TestClaudeTokenRefresher_CanRefresh(t *testing.T) { ...@@ -197,7 +197,7 @@ func TestClaudeTokenRefresher_CanRefresh(t *testing.T) {
{ {
name: "anthropic api-key - cannot refresh", name: "anthropic api-key - cannot refresh",
platform: PlatformAnthropic, platform: PlatformAnthropic,
accType: AccountTypeApiKey, accType: AccountTypeAPIKey,
want: false, want: false,
}, },
{ {
......
...@@ -79,7 +79,7 @@ type ReleaseInfo struct { ...@@ -79,7 +79,7 @@ type ReleaseInfo struct {
Name string `json:"name"` Name string `json:"name"`
Body string `json:"body"` Body string `json:"body"`
PublishedAt string `json:"published_at"` PublishedAt string `json:"published_at"`
HtmlURL string `json:"html_url"` HTMLURL string `json:"html_url"`
Assets []Asset `json:"assets,omitempty"` Assets []Asset `json:"assets,omitempty"`
} }
...@@ -96,13 +96,13 @@ type GitHubRelease struct { ...@@ -96,13 +96,13 @@ type GitHubRelease struct {
Name string `json:"name"` Name string `json:"name"`
Body string `json:"body"` Body string `json:"body"`
PublishedAt string `json:"published_at"` PublishedAt string `json:"published_at"`
HtmlUrl string `json:"html_url"` HTMLURL string `json:"html_url"`
Assets []GitHubAsset `json:"assets"` Assets []GitHubAsset `json:"assets"`
} }
type GitHubAsset struct { type GitHubAsset struct {
Name string `json:"name"` Name string `json:"name"`
BrowserDownloadUrl string `json:"browser_download_url"` BrowserDownloadURL string `json:"browser_download_url"`
Size int64 `json:"size"` Size int64 `json:"size"`
} }
...@@ -285,7 +285,7 @@ func (s *UpdateService) fetchLatestRelease(ctx context.Context) (*UpdateInfo, er ...@@ -285,7 +285,7 @@ func (s *UpdateService) fetchLatestRelease(ctx context.Context) (*UpdateInfo, er
for i, a := range release.Assets { for i, a := range release.Assets {
assets[i] = Asset{ assets[i] = Asset{
Name: a.Name, Name: a.Name,
DownloadURL: a.BrowserDownloadUrl, DownloadURL: a.BrowserDownloadURL,
Size: a.Size, Size: a.Size,
} }
} }
...@@ -298,7 +298,7 @@ func (s *UpdateService) fetchLatestRelease(ctx context.Context) (*UpdateInfo, er ...@@ -298,7 +298,7 @@ func (s *UpdateService) fetchLatestRelease(ctx context.Context) (*UpdateInfo, er
Name: release.Name, Name: release.Name,
Body: release.Body, Body: release.Body,
PublishedAt: release.PublishedAt, PublishedAt: release.PublishedAt,
HtmlURL: release.HtmlUrl, HTMLURL: release.HTMLURL,
Assets: assets, Assets: assets,
}, },
Cached: false, Cached: false,
......
...@@ -10,7 +10,7 @@ const ( ...@@ -10,7 +10,7 @@ const (
type UsageLog struct { type UsageLog struct {
ID int64 ID int64
UserID int64 UserID int64
ApiKeyID int64 APIKeyID int64
AccountID int64 AccountID int64
RequestID string RequestID string
Model string Model string
...@@ -42,7 +42,7 @@ type UsageLog struct { ...@@ -42,7 +42,7 @@ type UsageLog struct {
CreatedAt time.Time CreatedAt time.Time
User *User User *User
ApiKey *ApiKey APIKey *APIKey
Account *Account Account *Account
Group *Group Group *Group
Subscription *UserSubscription Subscription *UserSubscription
......
...@@ -19,7 +19,7 @@ var ( ...@@ -19,7 +19,7 @@ var (
// CreateUsageLogRequest 创建使用日志请求 // CreateUsageLogRequest 创建使用日志请求
type CreateUsageLogRequest struct { type CreateUsageLogRequest struct {
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
ApiKeyID int64 `json:"api_key_id"` APIKeyID int64 `json:"api_key_id"`
AccountID int64 `json:"account_id"` AccountID int64 `json:"account_id"`
RequestID string `json:"request_id"` RequestID string `json:"request_id"`
Model string `json:"model"` Model string `json:"model"`
...@@ -91,7 +91,7 @@ func (s *UsageService) Create(ctx context.Context, req CreateUsageLogRequest) (* ...@@ -91,7 +91,7 @@ func (s *UsageService) Create(ctx context.Context, req CreateUsageLogRequest) (*
// 创建使用日志 // 创建使用日志
usageLog := &UsageLog{ usageLog := &UsageLog{
UserID: req.UserID, UserID: req.UserID,
ApiKeyID: req.ApiKeyID, APIKeyID: req.APIKeyID,
AccountID: req.AccountID, AccountID: req.AccountID,
RequestID: req.RequestID, RequestID: req.RequestID,
Model: req.Model, Model: req.Model,
...@@ -151,9 +151,9 @@ func (s *UsageService) ListByUser(ctx context.Context, userID int64, params pagi ...@@ -151,9 +151,9 @@ func (s *UsageService) ListByUser(ctx context.Context, userID int64, params pagi
return logs, pagination, nil return logs, pagination, nil
} }
// ListByApiKey 获取API Key的使用日志列表 // ListByAPIKey 获取API Key的使用日志列表
func (s *UsageService) ListByApiKey(ctx context.Context, apiKeyID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) { func (s *UsageService) ListByAPIKey(ctx context.Context, apiKeyID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
logs, pagination, err := s.usageRepo.ListByApiKey(ctx, apiKeyID, params) logs, pagination, err := s.usageRepo.ListByAPIKey(ctx, apiKeyID, params)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("list usage logs: %w", err) return nil, nil, fmt.Errorf("list usage logs: %w", err)
} }
...@@ -188,9 +188,9 @@ func (s *UsageService) GetStatsByUser(ctx context.Context, userID int64, startTi ...@@ -188,9 +188,9 @@ func (s *UsageService) GetStatsByUser(ctx context.Context, userID int64, startTi
}, nil }, nil
} }
// GetStatsByApiKey 获取API Key的使用统计 // GetStatsByAPIKey 获取API Key的使用统计
func (s *UsageService) GetStatsByApiKey(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) (*UsageStats, error) { func (s *UsageService) GetStatsByAPIKey(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) (*UsageStats, error) {
stats, err := s.usageRepo.GetApiKeyStatsAggregated(ctx, apiKeyID, startTime, endTime) stats, err := s.usageRepo.GetAPIKeyStatsAggregated(ctx, apiKeyID, startTime, endTime)
if err != nil { if err != nil {
return nil, fmt.Errorf("get api key stats: %w", err) return nil, fmt.Errorf("get api key stats: %w", err)
} }
...@@ -293,9 +293,9 @@ func (s *UsageService) GetUserModelStats(ctx context.Context, userID int64, star ...@@ -293,9 +293,9 @@ func (s *UsageService) GetUserModelStats(ctx context.Context, userID int64, star
return stats, nil return stats, nil
} }
// GetBatchApiKeyUsageStats returns today/total actual_cost for given api keys. // GetBatchAPIKeyUsageStats returns today/total actual_cost for given api keys.
func (s *UsageService) GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchApiKeyUsageStats, error) { func (s *UsageService) GetBatchAPIKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchAPIKeyUsageStats, error) {
stats, err := s.usageRepo.GetBatchApiKeyUsageStats(ctx, apiKeyIDs) stats, err := s.usageRepo.GetBatchAPIKeyUsageStats(ctx, apiKeyIDs)
if err != nil { if err != nil {
return nil, fmt.Errorf("get batch api key usage stats: %w", err) return nil, fmt.Errorf("get batch api key usage stats: %w", err)
} }
......
...@@ -21,7 +21,7 @@ type User struct { ...@@ -21,7 +21,7 @@ type User struct {
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
ApiKeys []ApiKey APIKeys []APIKey
Subscriptions []UserSubscription Subscriptions []UserSubscription
} }
......
...@@ -75,7 +75,7 @@ var ProviderSet = wire.NewSet( ...@@ -75,7 +75,7 @@ var ProviderSet = wire.NewSet(
// Core services // Core services
NewAuthService, NewAuthService,
NewUserService, NewUserService,
NewApiKeyService, NewAPIKeyService,
NewGroupService, NewGroupService,
NewAccountService, NewAccountService,
NewProxyService, NewProxyService,
......
// Package setup provides CLI commands and application initialization helpers.
package setup package setup
import ( import (
......
...@@ -345,7 +345,7 @@ func writeConfigFile(cfg *SetupConfig) error { ...@@ -345,7 +345,7 @@ func writeConfigFile(cfg *SetupConfig) error {
Default struct { Default struct {
UserConcurrency int `yaml:"user_concurrency"` UserConcurrency int `yaml:"user_concurrency"`
UserBalance float64 `yaml:"user_balance"` UserBalance float64 `yaml:"user_balance"`
ApiKeyPrefix string `yaml:"api_key_prefix"` APIKeyPrefix string `yaml:"api_key_prefix"`
RateMultiplier float64 `yaml:"rate_multiplier"` RateMultiplier float64 `yaml:"rate_multiplier"`
} `yaml:"default"` } `yaml:"default"`
RateLimit struct { RateLimit struct {
...@@ -367,12 +367,12 @@ func writeConfigFile(cfg *SetupConfig) error { ...@@ -367,12 +367,12 @@ func writeConfigFile(cfg *SetupConfig) error {
Default: struct { Default: struct {
UserConcurrency int `yaml:"user_concurrency"` UserConcurrency int `yaml:"user_concurrency"`
UserBalance float64 `yaml:"user_balance"` UserBalance float64 `yaml:"user_balance"`
ApiKeyPrefix string `yaml:"api_key_prefix"` APIKeyPrefix string `yaml:"api_key_prefix"`
RateMultiplier float64 `yaml:"rate_multiplier"` RateMultiplier float64 `yaml:"rate_multiplier"`
}{ }{
UserConcurrency: 5, UserConcurrency: 5,
UserBalance: 0, UserBalance: 0,
ApiKeyPrefix: "sk-", APIKeyPrefix: "sk-",
RateMultiplier: 1.0, RateMultiplier: 1.0,
}, },
RateLimit: struct { RateLimit: struct {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment