Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
f51ad2e1
"deploy/docker-compose-aicodex.yml" did not exist on "94749b12ac66e4b97c21c4d90bf57abc41046324"
Commit
f51ad2e1
authored
Dec 25, 2025
by
Forest
Browse files
refactor: 删除 ports 目录
parent
f57f12c6
Changes
66
Show whitespace changes
Inline
Side-by-side
backend/internal/service/account_usage_service.go
View file @
f51ad2e1
...
...
@@ -8,10 +8,49 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
)
type
UsageLogRepository
interface
{
Create
(
ctx
context
.
Context
,
log
*
model
.
UsageLog
)
error
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
model
.
UsageLog
,
error
)
Delete
(
ctx
context
.
Context
,
id
int64
)
error
ListByUser
(
ctx
context
.
Context
,
userID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
ListByApiKey
(
ctx
context
.
Context
,
apiKeyID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
ListByAccount
(
ctx
context
.
Context
,
accountID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
ListByUserAndTimeRange
(
ctx
context
.
Context
,
userID
int64
,
startTime
,
endTime
time
.
Time
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
ListByApiKeyAndTimeRange
(
ctx
context
.
Context
,
apiKeyID
int64
,
startTime
,
endTime
time
.
Time
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
ListByAccountAndTimeRange
(
ctx
context
.
Context
,
accountID
int64
,
startTime
,
endTime
time
.
Time
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
ListByModelAndTimeRange
(
ctx
context
.
Context
,
modelName
string
,
startTime
,
endTime
time
.
Time
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
GetAccountWindowStats
(
ctx
context
.
Context
,
accountID
int64
,
startTime
time
.
Time
)
(
*
usagestats
.
AccountStats
,
error
)
GetAccountTodayStats
(
ctx
context
.
Context
,
accountID
int64
)
(
*
usagestats
.
AccountStats
,
error
)
// Admin dashboard stats
GetDashboardStats
(
ctx
context
.
Context
)
(
*
usagestats
.
DashboardStats
,
error
)
GetUsageTrendWithFilters
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
userID
,
apiKeyID
int64
)
([]
usagestats
.
TrendDataPoint
,
error
)
GetModelStatsWithFilters
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
userID
,
apiKeyID
,
accountID
int64
)
([]
usagestats
.
ModelStat
,
error
)
GetApiKeyUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
ApiKeyUsageTrendPoint
,
error
)
GetUserUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
UserUsageTrendPoint
,
error
)
GetBatchUserUsageStats
(
ctx
context
.
Context
,
userIDs
[]
int64
)
(
map
[
int64
]
*
usagestats
.
BatchUserUsageStats
,
error
)
GetBatchApiKeyUsageStats
(
ctx
context
.
Context
,
apiKeyIDs
[]
int64
)
(
map
[
int64
]
*
usagestats
.
BatchApiKeyUsageStats
,
error
)
// User dashboard stats
GetUserDashboardStats
(
ctx
context
.
Context
,
userID
int64
)
(
*
usagestats
.
UserDashboardStats
,
error
)
GetUserUsageTrendByUserID
(
ctx
context
.
Context
,
userID
int64
,
startTime
,
endTime
time
.
Time
,
granularity
string
)
([]
usagestats
.
TrendDataPoint
,
error
)
GetUserModelStats
(
ctx
context
.
Context
,
userID
int64
,
startTime
,
endTime
time
.
Time
)
([]
usagestats
.
ModelStat
,
error
)
// Admin usage listing/stats
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
filters
usagestats
.
UsageLogFilters
)
([]
model
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
GetGlobalStats
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
)
(
*
usagestats
.
UsageStats
,
error
)
// Account stats
GetAccountUsageStats
(
ctx
context
.
Context
,
accountID
int64
,
startTime
,
endTime
time
.
Time
)
(
*
usagestats
.
AccountUsageStatsResponse
,
error
)
}
// usageCache 用于缓存usage数据
type
usageCache
struct
{
data
*
UsageInfo
...
...
@@ -69,13 +108,13 @@ type ClaudeUsageFetcher interface {
// AccountUsageService 账号使用量查询服务
type
AccountUsageService
struct
{
accountRepo
ports
.
AccountRepository
usageLogRepo
ports
.
UsageLogRepository
accountRepo
AccountRepository
usageLogRepo
UsageLogRepository
usageFetcher
ClaudeUsageFetcher
}
// NewAccountUsageService 创建AccountUsageService实例
func
NewAccountUsageService
(
accountRepo
ports
.
AccountRepository
,
usageLogRepo
ports
.
UsageLogRepository
,
usageFetcher
ClaudeUsageFetcher
)
*
AccountUsageService
{
func
NewAccountUsageService
(
accountRepo
AccountRepository
,
usageLogRepo
UsageLogRepository
,
usageFetcher
ClaudeUsageFetcher
)
*
AccountUsageService
{
return
&
AccountUsageService
{
accountRepo
:
accountRepo
,
usageLogRepo
:
usageLogRepo
,
...
...
backend/internal/service/admin_service.go
View file @
f51ad2e1
...
...
@@ -9,8 +9,6 @@ import (
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"gorm.io/gorm"
)
...
...
@@ -221,24 +219,24 @@ type ProxyExitInfoProber interface {
// adminServiceImpl implements AdminService
type
adminServiceImpl
struct
{
userRepo
ports
.
UserRepository
groupRepo
ports
.
GroupRepository
accountRepo
ports
.
AccountRepository
proxyRepo
ports
.
ProxyRepository
apiKeyRepo
ports
.
ApiKeyRepository
redeemCodeRepo
ports
.
RedeemCodeRepository
userRepo
UserRepository
groupRepo
GroupRepository
accountRepo
AccountRepository
proxyRepo
ProxyRepository
apiKeyRepo
ApiKeyRepository
redeemCodeRepo
RedeemCodeRepository
billingCacheService
*
BillingCacheService
proxyProber
ProxyExitInfoProber
}
// NewAdminService creates a new AdminService
func
NewAdminService
(
userRepo
ports
.
UserRepository
,
groupRepo
ports
.
GroupRepository
,
accountRepo
ports
.
AccountRepository
,
proxyRepo
ports
.
ProxyRepository
,
apiKeyRepo
ports
.
ApiKeyRepository
,
redeemCodeRepo
ports
.
RedeemCodeRepository
,
userRepo
UserRepository
,
groupRepo
GroupRepository
,
accountRepo
AccountRepository
,
proxyRepo
ProxyRepository
,
apiKeyRepo
ApiKeyRepository
,
redeemCodeRepo
RedeemCodeRepository
,
billingCacheService
*
BillingCacheService
,
proxyProber
ProxyExitInfoProber
,
)
AdminService
{
...
...
@@ -734,7 +732,7 @@ func (s *adminServiceImpl) BulkUpdateAccounts(ctx context.Context, input *BulkUp
}
// Prepare bulk updates for columns and JSONB fields.
repoUpdates
:=
ports
.
AccountBulkUpdate
{
repoUpdates
:=
AccountBulkUpdate
{
Credentials
:
input
.
Credentials
,
Extra
:
input
.
Extra
,
}
...
...
backend/internal/service/api_key_service.go
View file @
f51ad2e1
...
...
@@ -6,13 +6,12 @@ import (
"encoding/hex"
"errors"
"fmt"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"time"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
...
...
@@ -30,6 +29,32 @@ const (
apiKeyMaxErrorsPerHour
=
20
)
type
ApiKeyRepository
interface
{
Create
(
ctx
context
.
Context
,
key
*
model
.
ApiKey
)
error
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
model
.
ApiKey
,
error
)
GetByKey
(
ctx
context
.
Context
,
key
string
)
(
*
model
.
ApiKey
,
error
)
Update
(
ctx
context
.
Context
,
key
*
model
.
ApiKey
)
error
Delete
(
ctx
context
.
Context
,
id
int64
)
error
ListByUserID
(
ctx
context
.
Context
,
userID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
ApiKey
,
*
pagination
.
PaginationResult
,
error
)
CountByUserID
(
ctx
context
.
Context
,
userID
int64
)
(
int64
,
error
)
ExistsByKey
(
ctx
context
.
Context
,
key
string
)
(
bool
,
error
)
ListByGroupID
(
ctx
context
.
Context
,
groupID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
ApiKey
,
*
pagination
.
PaginationResult
,
error
)
SearchApiKeys
(
ctx
context
.
Context
,
userID
int64
,
keyword
string
,
limit
int
)
([]
model
.
ApiKey
,
error
)
ClearGroupIDByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
CountByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
}
// ApiKeyCache defines cache operations for API key service
type
ApiKeyCache
interface
{
GetCreateAttemptCount
(
ctx
context
.
Context
,
userID
int64
)
(
int
,
error
)
IncrementCreateAttemptCount
(
ctx
context
.
Context
,
userID
int64
)
error
DeleteCreateAttemptCount
(
ctx
context
.
Context
,
userID
int64
)
error
IncrementDailyUsage
(
ctx
context
.
Context
,
apiKey
string
)
error
SetDailyUsageExpiry
(
ctx
context
.
Context
,
apiKey
string
,
ttl
time
.
Duration
)
error
}
// CreateApiKeyRequest 创建API Key请求
type
CreateApiKeyRequest
struct
{
Name
string
`json:"name"`
...
...
@@ -46,21 +71,21 @@ type UpdateApiKeyRequest struct {
// ApiKeyService API Key服务
type
ApiKeyService
struct
{
apiKeyRepo
ports
.
ApiKeyRepository
userRepo
ports
.
UserRepository
groupRepo
ports
.
GroupRepository
userSubRepo
ports
.
UserSubscriptionRepository
cache
ports
.
ApiKeyCache
apiKeyRepo
ApiKeyRepository
userRepo
UserRepository
groupRepo
GroupRepository
userSubRepo
UserSubscriptionRepository
cache
ApiKeyCache
cfg
*
config
.
Config
}
// NewApiKeyService 创建API Key服务实例
func
NewApiKeyService
(
apiKeyRepo
ports
.
ApiKeyRepository
,
userRepo
ports
.
UserRepository
,
groupRepo
ports
.
GroupRepository
,
userSubRepo
ports
.
UserSubscriptionRepository
,
cache
ports
.
ApiKeyCache
,
apiKeyRepo
ApiKeyRepository
,
userRepo
UserRepository
,
groupRepo
GroupRepository
,
userSubRepo
UserSubscriptionRepository
,
cache
ApiKeyCache
,
cfg
*
config
.
Config
,
)
*
ApiKeyService
{
return
&
ApiKeyService
{
...
...
backend/internal/service/auth_service.go
View file @
f51ad2e1
...
...
@@ -4,12 +4,12 @@ import (
"context"
"errors"
"fmt"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"log"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
...
...
@@ -36,7 +36,7 @@ type JWTClaims struct {
// AuthService 认证服务
type
AuthService
struct
{
userRepo
ports
.
UserRepository
userRepo
UserRepository
cfg
*
config
.
Config
settingService
*
SettingService
emailService
*
EmailService
...
...
@@ -46,7 +46,7 @@ type AuthService struct {
// NewAuthService 创建认证服务实例
func
NewAuthService
(
userRepo
ports
.
UserRepository
,
userRepo
UserRepository
,
cfg
*
config
.
Config
,
settingService
*
SettingService
,
emailService
*
EmailService
,
...
...
backend/internal/service/billing_cache_port.go
0 → 100644
View file @
f51ad2e1
package
service
import
(
"time"
)
// SubscriptionCacheData represents cached subscription data
type
SubscriptionCacheData
struct
{
Status
string
ExpiresAt
time
.
Time
DailyUsage
float64
WeeklyUsage
float64
MonthlyUsage
float64
Version
int64
}
backend/internal/service/billing_cache_service.go
View file @
f51ad2e1
...
...
@@ -8,7 +8,6 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
)
// 错误定义
...
...
@@ -31,13 +30,13 @@ type subscriptionCacheData struct {
// BillingCacheService 计费缓存服务
// 负责余额和订阅数据的缓存管理,提供高性能的计费资格检查
type
BillingCacheService
struct
{
cache
ports
.
BillingCache
userRepo
ports
.
UserRepository
subRepo
ports
.
UserSubscriptionRepository
cache
BillingCache
userRepo
UserRepository
subRepo
UserSubscriptionRepository
}
// NewBillingCacheService 创建计费缓存服务
func
NewBillingCacheService
(
cache
ports
.
BillingCache
,
userRepo
ports
.
UserRepository
,
subRepo
ports
.
UserSubscriptionRepository
)
*
BillingCacheService
{
func
NewBillingCacheService
(
cache
BillingCache
,
userRepo
UserRepository
,
subRepo
UserSubscriptionRepository
)
*
BillingCacheService
{
return
&
BillingCacheService
{
cache
:
cache
,
userRepo
:
userRepo
,
...
...
@@ -149,7 +148,7 @@ func (s *BillingCacheService) GetSubscriptionStatus(ctx context.Context, userID,
return
data
,
nil
}
func
(
s
*
BillingCacheService
)
convertFromPortsData
(
data
*
ports
.
SubscriptionCacheData
)
*
subscriptionCacheData
{
func
(
s
*
BillingCacheService
)
convertFromPortsData
(
data
*
SubscriptionCacheData
)
*
subscriptionCacheData
{
return
&
subscriptionCacheData
{
Status
:
data
.
Status
,
ExpiresAt
:
data
.
ExpiresAt
,
...
...
@@ -160,8 +159,8 @@ func (s *BillingCacheService) convertFromPortsData(data *ports.SubscriptionCache
}
}
func
(
s
*
BillingCacheService
)
convertToPortsData
(
data
*
subscriptionCacheData
)
*
ports
.
SubscriptionCacheData
{
return
&
ports
.
SubscriptionCacheData
{
func
(
s
*
BillingCacheService
)
convertToPortsData
(
data
*
subscriptionCacheData
)
*
SubscriptionCacheData
{
return
&
SubscriptionCacheData
{
Status
:
data
.
Status
,
ExpiresAt
:
data
.
ExpiresAt
,
DailyUsage
:
data
.
DailyUsage
,
...
...
backend/internal/service/billing_service.go
View file @
f51ad2e1
package
service
import
(
"context"
"fmt"
"github.com/Wei-Shaw/sub2api/internal/config"
"log"
"strings"
"github.com/Wei-Shaw/sub2api/internal/config"
)
// BillingCache defines cache operations for billing service
type
BillingCache
interface
{
// Balance operations
GetUserBalance
(
ctx
context
.
Context
,
userID
int64
)
(
float64
,
error
)
SetUserBalance
(
ctx
context
.
Context
,
userID
int64
,
balance
float64
)
error
DeductUserBalance
(
ctx
context
.
Context
,
userID
int64
,
amount
float64
)
error
InvalidateUserBalance
(
ctx
context
.
Context
,
userID
int64
)
error
// Subscription operations
GetSubscriptionCache
(
ctx
context
.
Context
,
userID
,
groupID
int64
)
(
*
SubscriptionCacheData
,
error
)
SetSubscriptionCache
(
ctx
context
.
Context
,
userID
,
groupID
int64
,
data
*
SubscriptionCacheData
)
error
UpdateSubscriptionUsage
(
ctx
context
.
Context
,
userID
,
groupID
int64
,
cost
float64
)
error
InvalidateSubscriptionCache
(
ctx
context
.
Context
,
userID
,
groupID
int64
)
error
}
// ModelPricing 模型价格配置(per-token价格,与LiteLLM格式一致)
type
ModelPricing
struct
{
InputPricePerToken
float64
// 每token输入价格 (USD)
...
...
backend/internal/service/concurrency_service.go
View file @
f51ad2e1
...
...
@@ -7,10 +7,28 @@ import (
"fmt"
"log"
"time"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
)
// ConcurrencyCache defines cache operations for concurrency service
// Uses independent keys per request slot with native Redis TTL for automatic cleanup
type
ConcurrencyCache
interface
{
// Account slot management - each slot is a separate key with independent TTL
// Key format: concurrency:account:{accountID}:{requestID}
AcquireAccountSlot
(
ctx
context
.
Context
,
accountID
int64
,
maxConcurrency
int
,
requestID
string
)
(
bool
,
error
)
ReleaseAccountSlot
(
ctx
context
.
Context
,
accountID
int64
,
requestID
string
)
error
GetAccountConcurrency
(
ctx
context
.
Context
,
accountID
int64
)
(
int
,
error
)
// User slot management - each slot is a separate key with independent TTL
// Key format: concurrency:user:{userID}:{requestID}
AcquireUserSlot
(
ctx
context
.
Context
,
userID
int64
,
maxConcurrency
int
,
requestID
string
)
(
bool
,
error
)
ReleaseUserSlot
(
ctx
context
.
Context
,
userID
int64
,
requestID
string
)
error
GetUserConcurrency
(
ctx
context
.
Context
,
userID
int64
)
(
int
,
error
)
// Wait queue - uses counter with TTL set only on creation
IncrementWaitCount
(
ctx
context
.
Context
,
userID
int64
,
maxWait
int
)
(
bool
,
error
)
DecrementWaitCount
(
ctx
context
.
Context
,
userID
int64
)
error
}
// generateRequestID generates a unique request ID for concurrency slot tracking
// Uses 8 random bytes (16 hex chars) for uniqueness
func
generateRequestID
()
string
{
...
...
@@ -29,11 +47,11 @@ const (
// ConcurrencyService manages concurrent request limiting for accounts and users
type
ConcurrencyService
struct
{
cache
ports
.
ConcurrencyCache
cache
ConcurrencyCache
}
// NewConcurrencyService creates a new ConcurrencyService
func
NewConcurrencyService
(
cache
ports
.
ConcurrencyCache
)
*
ConcurrencyService
{
func
NewConcurrencyService
(
cache
ConcurrencyCache
)
*
ConcurrencyService
{
return
&
ConcurrencyService
{
cache
:
cache
}
}
...
...
backend/internal/service/crs_sync_service.go
View file @
f51ad2e1
...
...
@@ -13,19 +13,18 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
)
type
CRSSyncService
struct
{
accountRepo
ports
.
AccountRepository
proxyRepo
ports
.
ProxyRepository
accountRepo
AccountRepository
proxyRepo
ProxyRepository
oauthService
*
OAuthService
openaiOAuthService
*
OpenAIOAuthService
}
func
NewCRSSyncService
(
accountRepo
ports
.
AccountRepository
,
proxyRepo
ports
.
ProxyRepository
,
accountRepo
AccountRepository
,
proxyRepo
ProxyRepository
,
oauthService
*
OAuthService
,
openaiOAuthService
*
OpenAIOAuthService
,
)
*
CRSSyncService
{
...
...
backend/internal/service/dashboard_service.go
View file @
f51ad2e1
...
...
@@ -6,15 +6,14 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
)
// DashboardService provides aggregated statistics for admin dashboard.
type
DashboardService
struct
{
usageRepo
ports
.
UsageLogRepository
usageRepo
UsageLogRepository
}
func
NewDashboardService
(
usageRepo
ports
.
UsageLogRepository
)
*
DashboardService
{
func
NewDashboardService
(
usageRepo
UsageLogRepository
)
*
DashboardService
{
return
&
DashboardService
{
usageRepo
:
usageRepo
,
}
...
...
backend/internal/service/email_service.go
View file @
f51ad2e1
...
...
@@ -6,12 +6,12 @@ import (
"crypto/tls"
"errors"
"fmt"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"math/big"
"net/smtp"
"strconv"
"time"
"github.com/Wei-Shaw/sub2api/internal/model"
)
var
(
...
...
@@ -21,6 +21,20 @@ var (
ErrVerifyCodeMaxAttempts
=
errors
.
New
(
"too many failed attempts, please request a new code"
)
)
// EmailCache defines cache operations for email service
type
EmailCache
interface
{
GetVerificationCode
(
ctx
context
.
Context
,
email
string
)
(
*
VerificationCodeData
,
error
)
SetVerificationCode
(
ctx
context
.
Context
,
email
string
,
data
*
VerificationCodeData
,
ttl
time
.
Duration
)
error
DeleteVerificationCode
(
ctx
context
.
Context
,
email
string
)
error
}
// VerificationCodeData represents verification code data
type
VerificationCodeData
struct
{
Code
string
Attempts
int
CreatedAt
time
.
Time
}
const
(
verifyCodeTTL
=
15
*
time
.
Minute
verifyCodeCooldown
=
1
*
time
.
Minute
...
...
@@ -40,12 +54,12 @@ type SmtpConfig struct {
// EmailService 邮件服务
type
EmailService
struct
{
settingRepo
ports
.
SettingRepository
cache
ports
.
EmailCache
settingRepo
SettingRepository
cache
EmailCache
}
// NewEmailService 创建邮件服务实例
func
NewEmailService
(
settingRepo
ports
.
SettingRepository
,
cache
ports
.
EmailCache
)
*
EmailService
{
func
NewEmailService
(
settingRepo
SettingRepository
,
cache
EmailCache
)
*
EmailService
{
return
&
EmailService
{
settingRepo
:
settingRepo
,
cache
:
cache
,
...
...
@@ -205,7 +219,7 @@ func (s *EmailService) SendVerifyCode(ctx context.Context, email, siteName strin
}
// 保存验证码到 Redis
data
:=
&
ports
.
VerificationCodeData
{
data
:=
&
VerificationCodeData
{
Code
:
code
,
Attempts
:
0
,
CreatedAt
:
time
.
Now
(),
...
...
backend/internal/service/gateway_service.go
View file @
f51ad2e1
...
...
@@ -19,7 +19,6 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
...
...
@@ -54,6 +53,13 @@ var allowedHeaders = map[string]bool{
"content-type"
:
true
,
}
// GatewayCache defines cache operations for gateway service
type
GatewayCache
interface
{
GetSessionAccountID
(
ctx
context
.
Context
,
sessionHash
string
)
(
int64
,
error
)
SetSessionAccountID
(
ctx
context
.
Context
,
sessionHash
string
,
accountID
int64
,
ttl
time
.
Duration
)
error
RefreshSessionTTL
(
ctx
context
.
Context
,
sessionHash
string
,
ttl
time
.
Duration
)
error
}
// ClaudeUsage 表示Claude API返回的usage信息
type
ClaudeUsage
struct
{
InputTokens
int
`json:"input_tokens"`
...
...
@@ -74,32 +80,32 @@ type ForwardResult struct {
// GatewayService handles API gateway operations
type
GatewayService
struct
{
accountRepo
ports
.
AccountRepository
usageLogRepo
ports
.
UsageLogRepository
userRepo
ports
.
UserRepository
userSubRepo
ports
.
UserSubscriptionRepository
cache
ports
.
GatewayCache
accountRepo
AccountRepository
usageLogRepo
UsageLogRepository
userRepo
UserRepository
userSubRepo
UserSubscriptionRepository
cache
GatewayCache
cfg
*
config
.
Config
billingService
*
BillingService
rateLimitService
*
RateLimitService
billingCacheService
*
BillingCacheService
identityService
*
IdentityService
httpUpstream
ports
.
HTTPUpstream
httpUpstream
HTTPUpstream
}
// NewGatewayService creates a new GatewayService
func
NewGatewayService
(
accountRepo
ports
.
AccountRepository
,
usageLogRepo
ports
.
UsageLogRepository
,
userRepo
ports
.
UserRepository
,
userSubRepo
ports
.
UserSubscriptionRepository
,
cache
ports
.
GatewayCache
,
accountRepo
AccountRepository
,
usageLogRepo
UsageLogRepository
,
userRepo
UserRepository
,
userSubRepo
UserSubscriptionRepository
,
cache
GatewayCache
,
cfg
*
config
.
Config
,
billingService
*
BillingService
,
rateLimitService
*
RateLimitService
,
billingCacheService
*
BillingCacheService
,
identityService
*
IdentityService
,
httpUpstream
ports
.
HTTPUpstream
,
httpUpstream
HTTPUpstream
,
)
*
GatewayService
{
return
&
GatewayService
{
accountRepo
:
accountRepo
,
...
...
@@ -507,7 +513,7 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
}
// OAuth账号:应用统一指纹
var
fingerprint
*
ports
.
Fingerprint
var
fingerprint
*
Fingerprint
if
account
.
IsOAuth
()
&&
s
.
identityService
!=
nil
{
// 1. 获取或创建指纹(包含随机生成的ClientID)
fp
,
err
:=
s
.
identityService
.
GetOrCreateFingerprint
(
ctx
,
account
.
ID
,
c
.
Request
.
Header
)
...
...
backend/internal/service/group_service.go
View file @
f51ad2e1
...
...
@@ -4,10 +4,9 @@ import (
"context"
"errors"
"fmt"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"gorm.io/gorm"
)
...
...
@@ -16,6 +15,24 @@ var (
ErrGroupExists
=
errors
.
New
(
"group name already exists"
)
)
type
GroupRepository
interface
{
Create
(
ctx
context
.
Context
,
group
*
model
.
Group
)
error
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
model
.
Group
,
error
)
Update
(
ctx
context
.
Context
,
group
*
model
.
Group
)
error
Delete
(
ctx
context
.
Context
,
id
int64
)
error
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
)
([]
model
.
Group
,
*
pagination
.
PaginationResult
,
error
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
model
.
Group
,
*
pagination
.
PaginationResult
,
error
)
ListActive
(
ctx
context
.
Context
)
([]
model
.
Group
,
error
)
ListActiveByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
model
.
Group
,
error
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
DB
()
*
gorm
.
DB
}
// CreateGroupRequest 创建分组请求
type
CreateGroupRequest
struct
{
Name
string
`json:"name"`
...
...
@@ -35,11 +52,11 @@ type UpdateGroupRequest struct {
// GroupService 分组管理服务
type
GroupService
struct
{
groupRepo
ports
.
GroupRepository
groupRepo
GroupRepository
}
// NewGroupService 创建分组服务实例
func
NewGroupService
(
groupRepo
ports
.
GroupRepository
)
*
GroupService
{
func
NewGroupService
(
groupRepo
GroupRepository
)
*
GroupService
{
return
&
GroupService
{
groupRepo
:
groupRepo
,
}
...
...
backend/internal/service/
ports/
http_upstream.go
→
backend/internal/service/http_upstream
_port
.go
View file @
f51ad2e1
package
ports
package
service
import
"net/http"
...
...
backend/internal/service/identity_service.go
View file @
f51ad2e1
...
...
@@ -7,7 +7,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"log"
"net/http"
"regexp"
...
...
@@ -24,7 +23,7 @@ var (
)
// 默认指纹值(当客户端未提供时使用)
var
defaultFingerprint
=
ports
.
Fingerprint
{
var
defaultFingerprint
=
Fingerprint
{
UserAgent
:
"claude-cli/2.0.62 (external, cli)"
,
StainlessLang
:
"js"
,
StainlessPackageVersion
:
"0.52.0"
,
...
...
@@ -34,20 +33,38 @@ var defaultFingerprint = ports.Fingerprint{
StainlessRuntimeVersion
:
"v22.14.0"
,
}
// Fingerprint represents account fingerprint data
type
Fingerprint
struct
{
ClientID
string
UserAgent
string
StainlessLang
string
StainlessPackageVersion
string
StainlessOS
string
StainlessArch
string
StainlessRuntime
string
StainlessRuntimeVersion
string
}
// IdentityCache defines cache operations for identity service
type
IdentityCache
interface
{
GetFingerprint
(
ctx
context
.
Context
,
accountID
int64
)
(
*
Fingerprint
,
error
)
SetFingerprint
(
ctx
context
.
Context
,
accountID
int64
,
fp
*
Fingerprint
)
error
}
// IdentityService 管理OAuth账号的请求身份指纹
type
IdentityService
struct
{
cache
ports
.
IdentityCache
cache
IdentityCache
}
// NewIdentityService 创建新的IdentityService
func
NewIdentityService
(
cache
ports
.
IdentityCache
)
*
IdentityService
{
func
NewIdentityService
(
cache
IdentityCache
)
*
IdentityService
{
return
&
IdentityService
{
cache
:
cache
}
}
// GetOrCreateFingerprint 获取或创建账号的指纹
// 如果缓存存在,检测user-agent版本,新版本则更新
// 如果缓存不存在,生成随机ClientID并从请求头创建指纹,然后缓存
func
(
s
*
IdentityService
)
GetOrCreateFingerprint
(
ctx
context
.
Context
,
accountID
int64
,
headers
http
.
Header
)
(
*
ports
.
Fingerprint
,
error
)
{
func
(
s
*
IdentityService
)
GetOrCreateFingerprint
(
ctx
context
.
Context
,
accountID
int64
,
headers
http
.
Header
)
(
*
Fingerprint
,
error
)
{
// 尝试从缓存获取指纹
cached
,
err
:=
s
.
cache
.
GetFingerprint
(
ctx
,
accountID
)
if
err
==
nil
&&
cached
!=
nil
{
...
...
@@ -79,8 +96,8 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
}
// createFingerprintFromHeaders 从请求头创建指纹
func
(
s
*
IdentityService
)
createFingerprintFromHeaders
(
headers
http
.
Header
)
*
ports
.
Fingerprint
{
fp
:=
&
ports
.
Fingerprint
{}
func
(
s
*
IdentityService
)
createFingerprintFromHeaders
(
headers
http
.
Header
)
*
Fingerprint
{
fp
:=
&
Fingerprint
{}
// 获取User-Agent
if
ua
:=
headers
.
Get
(
"User-Agent"
);
ua
!=
""
{
...
...
@@ -109,7 +126,7 @@ func getHeaderOrDefault(headers http.Header, key, defaultValue string) string {
}
// ApplyFingerprint 将指纹应用到请求头(覆盖原有的x-stainless-*头)
func
(
s
*
IdentityService
)
ApplyFingerprint
(
req
*
http
.
Request
,
fp
*
ports
.
Fingerprint
)
{
func
(
s
*
IdentityService
)
ApplyFingerprint
(
req
*
http
.
Request
,
fp
*
Fingerprint
)
{
if
fp
==
nil
{
return
}
...
...
backend/internal/service/oauth_service.go
View file @
f51ad2e1
...
...
@@ -8,9 +8,15 @@ import (
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/oauth"
"github.com/Wei-Shaw/sub2api/internal/
service/ports
"
"github.com/Wei-Shaw/sub2api/internal/
pkg/openai
"
)
// OpenAIOAuthClient interface for OpenAI OAuth operations
type
OpenAIOAuthClient
interface
{
ExchangeCode
(
ctx
context
.
Context
,
code
,
codeVerifier
,
redirectURI
,
proxyURL
string
)
(
*
openai
.
TokenResponse
,
error
)
RefreshToken
(
ctx
context
.
Context
,
refreshToken
,
proxyURL
string
)
(
*
openai
.
TokenResponse
,
error
)
}
// ClaudeOAuthClient handles HTTP requests for Claude OAuth flows
type
ClaudeOAuthClient
interface
{
GetOrganizationUUID
(
ctx
context
.
Context
,
sessionKey
,
proxyURL
string
)
(
string
,
error
)
...
...
@@ -22,12 +28,12 @@ type ClaudeOAuthClient interface {
// OAuthService handles OAuth authentication flows
type
OAuthService
struct
{
sessionStore
*
oauth
.
SessionStore
proxyRepo
ports
.
ProxyRepository
proxyRepo
ProxyRepository
oauthClient
ClaudeOAuthClient
}
// NewOAuthService creates a new OAuth service
func
NewOAuthService
(
proxyRepo
ports
.
ProxyRepository
,
oauthClient
ClaudeOAuthClient
)
*
OAuthService
{
func
NewOAuthService
(
proxyRepo
ProxyRepository
,
oauthClient
ClaudeOAuthClient
)
*
OAuthService
{
return
&
OAuthService
{
sessionStore
:
oauth
.
NewSessionStore
(),
proxyRepo
:
proxyRepo
,
...
...
backend/internal/service/openai_gateway_service.go
View file @
f51ad2e1
...
...
@@ -17,8 +17,6 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
"github.com/gin-gonic/gin"
)
...
...
@@ -71,30 +69,30 @@ type OpenAIForwardResult struct {
// OpenAIGatewayService handles OpenAI API gateway operations
type
OpenAIGatewayService
struct
{
accountRepo
ports
.
AccountRepository
usageLogRepo
ports
.
UsageLogRepository
userRepo
ports
.
UserRepository
userSubRepo
ports
.
UserSubscriptionRepository
cache
ports
.
GatewayCache
accountRepo
AccountRepository
usageLogRepo
UsageLogRepository
userRepo
UserRepository
userSubRepo
UserSubscriptionRepository
cache
GatewayCache
cfg
*
config
.
Config
billingService
*
BillingService
rateLimitService
*
RateLimitService
billingCacheService
*
BillingCacheService
httpUpstream
ports
.
HTTPUpstream
httpUpstream
HTTPUpstream
}
// NewOpenAIGatewayService creates a new OpenAIGatewayService
func
NewOpenAIGatewayService
(
accountRepo
ports
.
AccountRepository
,
usageLogRepo
ports
.
UsageLogRepository
,
userRepo
ports
.
UserRepository
,
userSubRepo
ports
.
UserSubscriptionRepository
,
cache
ports
.
GatewayCache
,
accountRepo
AccountRepository
,
usageLogRepo
UsageLogRepository
,
userRepo
UserRepository
,
userSubRepo
UserSubscriptionRepository
,
cache
GatewayCache
,
cfg
*
config
.
Config
,
billingService
*
BillingService
,
rateLimitService
*
RateLimitService
,
billingCacheService
*
BillingCacheService
,
httpUpstream
ports
.
HTTPUpstream
,
httpUpstream
HTTPUpstream
,
)
*
OpenAIGatewayService
{
return
&
OpenAIGatewayService
{
accountRepo
:
accountRepo
,
...
...
backend/internal/service/openai_oauth_service.go
View file @
f51ad2e1
...
...
@@ -7,18 +7,17 @@ import (
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
"github.com/Wei-Shaw/sub2api/internal/service/ports"
)
// OpenAIOAuthService handles OpenAI OAuth authentication flows
type
OpenAIOAuthService
struct
{
sessionStore
*
openai
.
SessionStore
proxyRepo
ports
.
ProxyRepository
oauthClient
ports
.
OpenAIOAuthClient
proxyRepo
ProxyRepository
oauthClient
OpenAIOAuthClient
}
// NewOpenAIOAuthService creates a new OpenAI OAuth service
func
NewOpenAIOAuthService
(
proxyRepo
ports
.
ProxyRepository
,
oauthClient
ports
.
OpenAIOAuthClient
)
*
OpenAIOAuthService
{
func
NewOpenAIOAuthService
(
proxyRepo
ProxyRepository
,
oauthClient
OpenAIOAuthClient
)
*
OpenAIOAuthService
{
return
&
OpenAIOAuthService
{
sessionStore
:
openai
.
NewSessionStore
(),
proxyRepo
:
proxyRepo
,
...
...
backend/internal/service/ports/account.go
deleted
100644 → 0
View file @
f57f12c6
package
ports
import
(
"context"
"time"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
)
type
AccountRepository
interface
{
Create
(
ctx
context
.
Context
,
account
*
model
.
Account
)
error
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
model
.
Account
,
error
)
// GetByCRSAccountID finds an account previously synced from CRS.
// Returns (nil, nil) if not found.
GetByCRSAccountID
(
ctx
context
.
Context
,
crsAccountID
string
)
(
*
model
.
Account
,
error
)
Update
(
ctx
context
.
Context
,
account
*
model
.
Account
)
error
Delete
(
ctx
context
.
Context
,
id
int64
)
error
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
)
([]
model
.
Account
,
*
pagination
.
PaginationResult
,
error
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
accountType
,
status
,
search
string
)
([]
model
.
Account
,
*
pagination
.
PaginationResult
,
error
)
ListByGroup
(
ctx
context
.
Context
,
groupID
int64
)
([]
model
.
Account
,
error
)
ListActive
(
ctx
context
.
Context
)
([]
model
.
Account
,
error
)
ListByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
model
.
Account
,
error
)
UpdateLastUsed
(
ctx
context
.
Context
,
id
int64
)
error
SetError
(
ctx
context
.
Context
,
id
int64
,
errorMsg
string
)
error
SetSchedulable
(
ctx
context
.
Context
,
id
int64
,
schedulable
bool
)
error
BindGroups
(
ctx
context
.
Context
,
accountID
int64
,
groupIDs
[]
int64
)
error
ListSchedulable
(
ctx
context
.
Context
)
([]
model
.
Account
,
error
)
ListSchedulableByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
([]
model
.
Account
,
error
)
ListSchedulableByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
model
.
Account
,
error
)
ListSchedulableByGroupIDAndPlatform
(
ctx
context
.
Context
,
groupID
int64
,
platform
string
)
([]
model
.
Account
,
error
)
SetRateLimited
(
ctx
context
.
Context
,
id
int64
,
resetAt
time
.
Time
)
error
SetOverloaded
(
ctx
context
.
Context
,
id
int64
,
until
time
.
Time
)
error
ClearRateLimit
(
ctx
context
.
Context
,
id
int64
)
error
UpdateSessionWindow
(
ctx
context
.
Context
,
id
int64
,
start
,
end
*
time
.
Time
,
status
string
)
error
UpdateExtra
(
ctx
context
.
Context
,
id
int64
,
updates
map
[
string
]
any
)
error
BulkUpdate
(
ctx
context
.
Context
,
ids
[]
int64
,
updates
AccountBulkUpdate
)
(
int64
,
error
)
}
// AccountBulkUpdate describes the fields that can be updated in a bulk operation.
// Nil pointers mean "do not change".
type
AccountBulkUpdate
struct
{
Name
*
string
ProxyID
*
int64
Concurrency
*
int
Priority
*
int
Status
*
string
Credentials
map
[
string
]
any
Extra
map
[
string
]
any
}
backend/internal/service/ports/api_key.go
deleted
100644 → 0
View file @
f57f12c6
package
ports
import
(
"context"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
)
type
ApiKeyRepository
interface
{
Create
(
ctx
context
.
Context
,
key
*
model
.
ApiKey
)
error
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
model
.
ApiKey
,
error
)
GetByKey
(
ctx
context
.
Context
,
key
string
)
(
*
model
.
ApiKey
,
error
)
Update
(
ctx
context
.
Context
,
key
*
model
.
ApiKey
)
error
Delete
(
ctx
context
.
Context
,
id
int64
)
error
ListByUserID
(
ctx
context
.
Context
,
userID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
ApiKey
,
*
pagination
.
PaginationResult
,
error
)
CountByUserID
(
ctx
context
.
Context
,
userID
int64
)
(
int64
,
error
)
ExistsByKey
(
ctx
context
.
Context
,
key
string
)
(
bool
,
error
)
ListByGroupID
(
ctx
context
.
Context
,
groupID
int64
,
params
pagination
.
PaginationParams
)
([]
model
.
ApiKey
,
*
pagination
.
PaginationResult
,
error
)
SearchApiKeys
(
ctx
context
.
Context
,
userID
int64
,
keyword
string
,
limit
int
)
([]
model
.
ApiKey
,
error
)
ClearGroupIDByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
CountByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
}
Prev
1
2
3
4
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment