admin_service.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. package service
  2. import (
  3. "context"
  4. "net/http"
  5. "time"
  6. dbent "github.com/Wei-Shaw/sub2api/ent"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  9. )
  10. // AdminService interface defines admin management operations
  11. type AdminService interface {
  12. // User management
  13. ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters, sortBy, sortOrder string) ([]User, int64, error)
  14. GetUser(ctx context.Context, id int64) (*User, error)
  15. GetUserIncludeDeleted(ctx context.Context, id int64) (*User, error)
  16. CreateUser(ctx context.Context, input *CreateUserInput) (*User, error)
  17. UpdateUser(ctx context.Context, id int64, input *UpdateUserInput) (*User, error)
  18. DeleteUser(ctx context.Context, id int64) error
  19. UpdateUserBalance(ctx context.Context, userID int64, balance float64, operation string, notes string) (*User, error)
  20. BatchUpdateConcurrency(ctx context.Context, userIDs []int64, value int, mode string) (int, error)
  21. BatchUpdateLimits(ctx context.Context, userIDs []int64, concurrency, rpmLimit *int) (int, error)
  22. GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]APIKey, int64, error)
  23. GetUserUsageStats(ctx context.Context, userID int64, period string) (any, error)
  24. GetUserRPMStatus(ctx context.Context, userID int64) (*UserRPMStatus, error)
  25. // GetUserBalanceHistory returns paginated balance/concurrency change records for a user.
  26. // codeType is optional - pass empty string to return all types.
  27. // Also returns totalRecharged (sum of all positive balance top-ups).
  28. GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error)
  29. BindUserAuthIdentity(ctx context.Context, userID int64, input AdminBindAuthIdentityInput) (*AdminBoundAuthIdentity, error)
  30. // Group management
  31. ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error)
  32. GetAllGroups(ctx context.Context) ([]Group, error)
  33. GetAllGroupsByPlatform(ctx context.Context, platform string) ([]Group, error)
  34. // GetAllGroupsIncludingInactive returns all groups regardless of status (active + disabled),
  35. // ordered by sort_order then id. Used by the API Key group filter dropdown.
  36. GetAllGroupsIncludingInactive(ctx context.Context) ([]Group, error)
  37. GetGroup(ctx context.Context, id int64) (*Group, error)
  38. GetGroupModelsListCandidates(ctx context.Context, id int64, platform string) ([]string, error)
  39. CreateGroup(ctx context.Context, input *CreateGroupInput) (*Group, error)
  40. // DuplicateGroup creates an inactive independent copy of a group's configuration
  41. // and account bindings while preserving each binding's priority.
  42. DuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*Group, error)
  43. // RecoverDuplicateGroup returns a previously committed copy for an ambiguous retry.
  44. // It never creates a group.
  45. RecoverDuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*Group, error)
  46. UpdateGroup(ctx context.Context, id int64, input *UpdateGroupInput) (*Group, error)
  47. DeleteGroup(ctx context.Context, id int64) error
  48. DeleteGroupIfEmpty(ctx context.Context, id int64) error
  49. ListCompositeRoutes(ctx context.Context, groupID int64) ([]CompositeModelRoute, error)
  50. CreateCompositeRoute(ctx context.Context, groupID int64, input CompositeRouteInput) (*CompositeModelRoute, error)
  51. UpdateCompositeRoute(ctx context.Context, groupID, routeID int64, input CompositeRouteInput) (*CompositeModelRoute, error)
  52. DeleteCompositeRoute(ctx context.Context, groupID, routeID int64) error
  53. PreviewCompositeRoute(ctx context.Context, groupID int64, input CompositeRoutePreviewRequest) (*CompositeRouteDecision, error)
  54. GetGroupAPIKeys(ctx context.Context, groupID int64, page, pageSize int) ([]APIKey, int64, error)
  55. GetGroupRateMultipliers(ctx context.Context, groupID int64) ([]UserGroupRateEntry, error)
  56. ClearGroupRateMultipliers(ctx context.Context, groupID int64) error
  57. BatchSetGroupRateMultipliers(ctx context.Context, groupID int64, entries []GroupRateMultiplierInput) error
  58. ClearGroupRPMOverrides(ctx context.Context, groupID int64) error
  59. BatchSetGroupRPMOverrides(ctx context.Context, groupID int64, entries []GroupRPMOverrideInput) error
  60. UpdateGroupSortOrders(ctx context.Context, updates []GroupSortOrderUpdate) error
  61. // API Key management (admin)
  62. AdminUpdateAPIKeyGroupID(ctx context.Context, keyID int64, groupID *int64) (*AdminUpdateAPIKeyGroupIDResult, error)
  63. AdminResetAPIKeyRateLimitUsage(ctx context.Context, keyID int64) (*APIKey, error)
  64. // ReplaceUserGroup 替换用户的专属分组:授予新分组权限、迁移 Key、移除旧分组权限
  65. ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*ReplaceUserGroupResult, error)
  66. // Account management
  67. ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]Account, int64, error)
  68. // ListAccountsForSchedulerScoreFilter 返回符合过滤条件的全部账号(不分页),
  69. // 作为账号列表页计算 OpenAI 调度分数的过滤范围池。
  70. ListAccountsForSchedulerScoreFilter(ctx context.Context, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, error)
  71. // ListOpenAISchedulableAccountsForSchedulerScore 返回指定分组(nil 为未分组)内
  72. // 可调度的 OpenAI 账号,用于按组计算调度分数。
  73. ListOpenAISchedulableAccountsForSchedulerScore(ctx context.Context, groupID *int64) ([]Account, error)
  74. GetAccount(ctx context.Context, id int64) (*Account, error)
  75. GetAccountsByIDs(ctx context.Context, ids []int64) ([]*Account, error)
  76. CreateAccount(ctx context.Context, input *CreateAccountInput) (*Account, error)
  77. ValidateAccountGroupBindings(ctx context.Context, groupIDs []int64) error
  78. // DuplicateAccount creates an independent account from an existing account's configuration.
  79. // First-class runtime columns are intentionally reset by the normal account creation path.
  80. DuplicateAccount(ctx context.Context, id int64, actorScope, operationKey string) (*Account, error)
  81. // RecoverDuplicateAccount returns a previously committed duplicate for an ambiguous retry.
  82. // It never creates an account.
  83. RecoverDuplicateAccount(ctx context.Context, id int64, actorScope, operationKey string) (*Account, error)
  84. UpdateAccount(ctx context.Context, id int64, input *UpdateAccountInput) (*Account, error)
  85. // UpdateAccountExtra 仅对 Extra 做 JSONB 增量合并(key 级覆盖),不会影响其它字段或运行态键。
  86. // 用于刷新流程持久化 account_uuid / org_uuid 等少量键,避免被全量快照覆盖。
  87. UpdateAccountExtra(ctx context.Context, id int64, updates map[string]any) error
  88. DeleteAccount(ctx context.Context, id int64) error
  89. RefreshAccountCredentials(ctx context.Context, id int64) (*Account, error)
  90. ClearAccountError(ctx context.Context, id int64) (*Account, error)
  91. SetAccountError(ctx context.Context, id int64, errorMsg string) error
  92. // EnsureOpenAIPrivacy 检查 OpenAI OAuth 账号 privacy_mode,未设置则尝试关闭训练数据共享并持久化。
  93. EnsureOpenAIPrivacy(ctx context.Context, account *Account) string
  94. // EnsureAntigravityPrivacy 检查 Antigravity OAuth 账号 privacy_mode,未设置则调用 setUserSettings 并持久化。
  95. EnsureAntigravityPrivacy(ctx context.Context, account *Account) string
  96. // ForceOpenAIPrivacy 强制重新设置 OpenAI OAuth 账号隐私,无论当前状态。
  97. ForceOpenAIPrivacy(ctx context.Context, account *Account) string
  98. // ForceAntigravityPrivacy 强制重新设置 Antigravity OAuth 账号隐私,无论当前状态。
  99. ForceAntigravityPrivacy(ctx context.Context, account *Account) string
  100. SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*Account, error)
  101. BulkUpdateAccounts(ctx context.Context, input *BulkUpdateAccountsInput) (*BulkUpdateAccountsResult, error)
  102. CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error
  103. // RevertAccountProxyFallback 将账号的 proxy_id 切回 proxy_fallback_origin_id,并清空 origin 字段。
  104. // 若账号不存在返回 ErrAccountNotFound;若账号存在但不在 fallback 状态,返回 ErrAccountNotInFallback。
  105. RevertAccountProxyFallback(ctx context.Context, id int64) error
  106. // CreateShadow 为指定 OpenAI OAuth 母账号创建 spark 维度影子账号(一母一影)。
  107. // 影子账号不持凭据(Credentials 恒为空),透传母账号凭据;继承母账号的 ProxyID。
  108. CreateShadow(ctx context.Context, parentID int64, opts ShadowOptions) (*Account, error)
  109. // Proxy management
  110. ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error)
  111. ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]ProxyWithAccountCount, int64, error)
  112. GetAllProxies(ctx context.Context) ([]Proxy, error)
  113. GetAllProxiesWithAccountCount(ctx context.Context) ([]ProxyWithAccountCount, error)
  114. GetProxy(ctx context.Context, id int64) (*Proxy, error)
  115. GetProxiesByIDs(ctx context.Context, ids []int64) ([]Proxy, error)
  116. CreateProxy(ctx context.Context, input *CreateProxyInput) (*Proxy, error)
  117. UpdateProxy(ctx context.Context, id int64, input *UpdateProxyInput) (*Proxy, error)
  118. DeleteProxy(ctx context.Context, id int64) error
  119. BatchDeleteProxies(ctx context.Context, ids []int64) (*ProxyBatchDeleteResult, error)
  120. GetProxyAccounts(ctx context.Context, proxyID int64) ([]ProxyAccountSummary, error)
  121. CheckProxyExists(ctx context.Context, host string, port int, username, password string) (bool, error)
  122. TestProxy(ctx context.Context, id int64) (*ProxyTestResult, error)
  123. CheckProxyQuality(ctx context.Context, id int64) (*ProxyQualityCheckResult, error)
  124. // Redeem code management
  125. ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]RedeemCode, int64, error)
  126. GetRedeemCode(ctx context.Context, id int64) (*RedeemCode, error)
  127. GenerateRedeemCodes(ctx context.Context, input *GenerateRedeemCodesInput) ([]RedeemCode, error)
  128. DeleteRedeemCode(ctx context.Context, id int64) error
  129. BatchDeleteRedeemCodes(ctx context.Context, ids []int64) (int64, error)
  130. ExpireRedeemCode(ctx context.Context, id int64) (*RedeemCode, error)
  131. ResetAccountQuota(ctx context.Context, id int64) error
  132. }
  133. type AdminGroupOperation string
  134. const (
  135. AdminGroupOperationBasic AdminGroupOperation = "basic"
  136. AdminGroupOperationDuplicate AdminGroupOperation = "duplicate"
  137. AdminGroupOperationCompositeRoute AdminGroupOperation = "composite_route"
  138. AdminGroupOperationMultiplier AdminGroupOperation = "multiplier"
  139. AdminGroupOperationRPMOverride AdminGroupOperation = "rpm_override"
  140. AdminGroupOperationSort AdminGroupOperation = "sort"
  141. )
  142. func ValidateSimpleModeGroupOperation(cfg *config.Config, operation AdminGroupOperation) error {
  143. if cfg != nil && cfg.RunMode == config.RunModeSimple && operation != AdminGroupOperationBasic {
  144. return infraerrors.New(http.StatusForbidden, "SIMPLE_MODE_OPERATION_UNSUPPORTED", "This operation is not supported in simple mode")
  145. }
  146. return nil
  147. }
  148. // CreateUserInput represents input for creating a new user via admin operations.
  149. type CreateUserInput struct {
  150. Email string
  151. Password string
  152. Username string
  153. Notes string
  154. Role string // 空字符串表示使用默认角色(user);合法值 admin/user
  155. Balance *float64
  156. Concurrency int
  157. RPMLimit int
  158. AllowedGroups []int64
  159. RestrictPublicGroups bool
  160. // ActorAdminID 执行本次操作的管理员ID(来自JWT),仅用于权限敏感操作的审计日志。
  161. ActorAdminID int64
  162. }
  163. type UpdateUserInput struct {
  164. Email string
  165. Password string
  166. Username *string
  167. Notes *string
  168. Role string // 空字符串表示"未提供"(不修改);合法值 admin/user
  169. Balance *float64 // 使用指针区分"未提供"和"设置为0"
  170. Concurrency *int // 使用指针区分"未提供"和"设置为0"
  171. RPMLimit *int // 使用指针区分"未提供"和"设置为0"
  172. Status string
  173. AllowedGroups *[]int64 // 使用指针区分"未提供"和"设置为空数组"
  174. // RestrictPublicGroups 指针区分"未提供"和"显式开关"。
  175. RestrictPublicGroups *bool
  176. // GroupRates 用户专属分组倍率配置
  177. // map[groupID]*rate,nil 表示删除该分组的专属倍率
  178. GroupRates map[int64]*float64
  179. // ActorAdminID 执行本次操作的管理员ID(来自JWT),仅用于权限敏感操作的审计日志。
  180. ActorAdminID int64
  181. }
  182. type AdminBindAuthIdentityInput struct {
  183. ProviderType string
  184. ProviderKey string
  185. ProviderSubject string
  186. Issuer *string
  187. Metadata map[string]any
  188. Channel *AdminBindAuthIdentityChannelInput
  189. }
  190. type AdminBindAuthIdentityChannelInput struct {
  191. Channel string
  192. ChannelAppID string
  193. ChannelSubject string
  194. Metadata map[string]any
  195. }
  196. type AdminBoundAuthIdentity struct {
  197. UserID int64 `json:"user_id"`
  198. ProviderType string `json:"provider_type"`
  199. ProviderKey string `json:"provider_key"`
  200. ProviderSubject string `json:"provider_subject"`
  201. VerifiedAt *time.Time `json:"verified_at,omitempty"`
  202. Issuer *string `json:"issuer,omitempty"`
  203. Metadata map[string]any `json:"metadata"`
  204. CreatedAt time.Time `json:"created_at"`
  205. UpdatedAt time.Time `json:"updated_at"`
  206. Channel *AdminBoundAuthIdentityChannel `json:"channel,omitempty"`
  207. }
  208. type AdminBoundAuthIdentityChannel struct {
  209. Channel string `json:"channel"`
  210. ChannelAppID string `json:"channel_app_id"`
  211. ChannelSubject string `json:"channel_subject"`
  212. Metadata map[string]any `json:"metadata"`
  213. CreatedAt time.Time `json:"created_at"`
  214. UpdatedAt time.Time `json:"updated_at"`
  215. }
  216. type CreateGroupInput struct {
  217. SecurityPolicyEnabled bool
  218. SecurityPolicyMode string
  219. SecurityPolicyEmailEnabled *bool
  220. Name string
  221. Description string
  222. Platform string
  223. RateMultiplier float64
  224. IsExclusive bool
  225. SubscriptionType string // standard/subscription
  226. DailyLimitUSD *float64 // 日限额 (USD)
  227. WeeklyLimitUSD *float64 // 周限额 (USD)
  228. MonthlyLimitUSD *float64 // 月限额 (USD)
  229. LongContextPricingEnabled bool
  230. ModelPricing []ChannelModelPricing
  231. // 图片生成计费配置(仅 antigravity 平台使用)
  232. AllowImageGeneration bool
  233. AllowBatchImageGeneration bool
  234. ImageRateIndependent bool
  235. ImageRateMultiplier *float64
  236. BatchImageDiscountMultiplier *float64
  237. BatchImageHoldMultiplier *float64
  238. VideoRateIndependent bool
  239. VideoRateMultiplier *float64
  240. // 高峰时段倍率配置(PeakRateMultiplier 为 nil 时按 1.0 处理)
  241. PeakRateEnabled bool
  242. PeakStart string
  243. PeakEnd string
  244. PeakRateMultiplier *float64
  245. ImagePrice1K *float64
  246. ImagePrice2K *float64
  247. ImagePrice4K *float64
  248. VideoPrice480P *float64
  249. VideoPrice720P *float64
  250. VideoPrice1080P *float64
  251. // VideoModelPrices 可选按模型族×分辨率覆盖视频每秒单价。
  252. VideoModelPrices map[string]map[string]float64
  253. // Codex alpha/search 网页搜索单次价格(USD/次,仅 openai 平台使用);nil/负数按默认价 0.01 处理
  254. WebSearchPricePerCall *float64
  255. // 搜索工具单价 per 1k
  256. SearchPricePer1k *float64
  257. // Grok Voice 显式定价(分组级)
  258. AudioRealtimePricePerMin *float64
  259. AudioTTSPricePerMillionChars *float64
  260. AudioSTTPricePerHour *float64
  261. ClaudeCodeOnly bool // 仅允许 Claude Code 客户端
  262. FallbackGroupID *int64 // 降级分组 ID
  263. // 无效请求兜底分组 ID(仅 anthropic 平台使用)
  264. FallbackGroupIDOnInvalidRequest *int64
  265. // 模型路由配置(仅 anthropic 平台使用)
  266. ModelRouting map[string][]int64
  267. ModelRoutingEnabled bool // 是否启用模型路由
  268. MCPXMLInject *bool
  269. // 支持的模型系列(仅 antigravity 平台使用)
  270. SupportedModelScopes []string
  271. // OpenAI Messages 调度配置(仅 openai 平台使用)
  272. AllowMessagesDispatch bool
  273. AllowLive bool
  274. ForceOpenAIFast bool
  275. FreeOpenAIFast bool
  276. DefaultMappedModel string
  277. RequireOAuthOnly bool
  278. RequirePrivacySet bool
  279. MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig
  280. ModelAllowlist GroupModelAllowlist
  281. // CodexModelsManifestConfig 固定账号 manifest 配置;创建路径禁止开启,仅编辑可配置。
  282. CodexModelsManifestConfig GroupCodexModelsManifestConfig
  283. // RPMLimit 分组 RPM 上限(0 = 不限制)
  284. RPMLimit int
  285. // MaxReasoningEffort Anthropic/OpenAI 请求的推理强度上限,空字符串表示不限制。
  286. MaxReasoningEffort string
  287. // MaxReasoningEffortOverLimit 超过上限时的访问控制:downgrade(默认)或 deny。
  288. MaxReasoningEffortOverLimit string
  289. // ReasoningEffortMappings Anthropic/OpenAI 推理强度映射,可按模型精确名、前缀或后缀限定。
  290. ReasoningEffortMappings []ReasoningEffortMapping
  291. // 分组利润控制(五个 token 平台分组可启用;margin/buffer 为小数,nil 按 0 处理)
  292. ProfitControlEnabled bool
  293. ProfitMinMargin *float64
  294. ProfitSafetyBuffer *float64
  295. // 从指定分组复制账号(创建分组后在同一事务内绑定)
  296. CopyAccountsFromGroupIDs []int64
  297. }
  298. type UpdateGroupInput struct {
  299. SecurityPolicyEnabled *bool
  300. SecurityPolicyMode *string
  301. SecurityPolicyEmailEnabled *bool
  302. Name string
  303. Description *string
  304. Platform string
  305. RateMultiplier *float64 // 使用指针以支持设置为0
  306. IsExclusive *bool
  307. Status string
  308. SubscriptionType string // standard/subscription
  309. DailyLimitUSD *float64 // 日限额 (USD)
  310. WeeklyLimitUSD *float64 // 周限额 (USD)
  311. MonthlyLimitUSD *float64 // 月限额 (USD)
  312. LongContextPricingEnabled *bool
  313. ModelPricing *[]ChannelModelPricing
  314. // 图片生成计费配置(仅 antigravity 平台使用)
  315. AllowImageGeneration *bool
  316. AllowBatchImageGeneration *bool
  317. ImageRateIndependent *bool
  318. ImageRateMultiplier *float64
  319. BatchImageDiscountMultiplier *float64
  320. BatchImageHoldMultiplier *float64
  321. VideoRateIndependent *bool
  322. VideoRateMultiplier *float64
  323. // 高峰时段倍率配置(nil 表示不修改)
  324. PeakRateEnabled *bool
  325. PeakStart *string
  326. PeakEnd *string
  327. PeakRateMultiplier *float64
  328. ImagePrice1K *float64
  329. ImagePrice2K *float64
  330. ImagePrice4K *float64
  331. VideoPrice480P *float64
  332. VideoPrice720P *float64
  333. VideoPrice1080P *float64
  334. // VideoModelPrices 可选按模型族×分辨率覆盖;nil 表示不修改,空 map 表示清除。
  335. VideoModelPrices map[string]map[string]float64
  336. // Codex alpha/search 网页搜索单次价格(USD/次);nil 表示不修改,负数表示清除回默认价 0.01
  337. WebSearchPricePerCall *float64
  338. // 搜索工具单价;nil 不修改,负数清除
  339. SearchPricePer1k *float64
  340. // Grok Voice 显式定价;nil 表示不修改,负数表示清除
  341. AudioRealtimePricePerMin *float64
  342. AudioTTSPricePerMillionChars *float64
  343. AudioSTTPricePerHour *float64
  344. ClaudeCodeOnly *bool // 仅允许 Claude Code 客户端
  345. FallbackGroupID *int64 // 降级分组 ID
  346. // 无效请求兜底分组 ID(仅 anthropic 平台使用)
  347. FallbackGroupIDOnInvalidRequest *int64
  348. // 模型路由配置(仅 anthropic 平台使用)
  349. ModelRouting map[string][]int64
  350. ModelRoutingEnabled *bool // 是否启用模型路由
  351. MCPXMLInject *bool
  352. // 支持的模型系列(仅 antigravity 平台使用)
  353. SupportedModelScopes *[]string
  354. // OpenAI Messages 调度配置(仅 openai 平台使用)
  355. AllowMessagesDispatch *bool
  356. AllowLive *bool
  357. ForceOpenAIFast *bool
  358. FreeOpenAIFast *bool
  359. DefaultMappedModel *string
  360. RequireOAuthOnly *bool
  361. RequirePrivacySet *bool
  362. MessagesDispatchModelConfig *OpenAIMessagesDispatchModelConfig
  363. ModelAllowlist *GroupModelAllowlist
  364. // CodexModelsManifestConfig nil 表示不修改;非 openai 平台会被归一化为关闭。
  365. CodexModelsManifestConfig *GroupCodexModelsManifestConfig
  366. // RPMLimit 分组 RPM 上限(0 = 不限制),nil 表示未提供不改动。
  367. RPMLimit *int
  368. // MaxReasoningEffort 空字符串表示清除上限;nil 表示未提供不改动。
  369. MaxReasoningEffort *string
  370. // MaxReasoningEffortOverLimit 空字符串视为 downgrade;nil 表示未提供不改动。
  371. MaxReasoningEffortOverLimit *string
  372. // ReasoningEffortMappings nil 表示不修改,空数组表示清空,非空数组表示替换。
  373. ReasoningEffortMappings *[]ReasoningEffortMapping
  374. // 分组利润控制(nil 表示不修改;margin/buffer 为小数)
  375. ProfitControlEnabled *bool
  376. ProfitMinMargin *float64
  377. ProfitSafetyBuffer *float64
  378. // 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
  379. CopyAccountsFromGroupIDs []int64
  380. }
  381. type CreateAccountInput struct {
  382. Name string
  383. Notes *string
  384. Platform string
  385. Type string
  386. Credentials map[string]any
  387. Extra map[string]any
  388. ProxyID *int64
  389. Concurrency int
  390. Priority int
  391. RateMultiplier *float64 // 账号计费倍率(>=0,允许 0)
  392. LoadFactor *int
  393. GroupIDs []int64
  394. ExpiresAt *int64
  395. AutoPauseOnExpired *bool
  396. ProbeEnabled *bool
  397. // SkipDefaultGroupBind prevents auto-binding to platform default group when GroupIDs is empty.
  398. SkipDefaultGroupBind bool
  399. // SkipMixedChannelCheck skips the mixed channel risk check when binding groups.
  400. // This should only be set when the caller has explicitly confirmed the risk.
  401. SkipMixedChannelCheck bool
  402. }
  403. // ShadowOptions is the input for CreateShadow.
  404. // The shadow holds no credentials — the scheduler transparently delegates to the parent account's tokens.
  405. type ShadowOptions struct {
  406. Name string
  407. Priority int
  408. Concurrency int
  409. GroupIDs []int64
  410. }
  411. type UpdateAccountInput struct {
  412. Name string
  413. Notes *string
  414. Type string // Account type: oauth, setup-token, apikey
  415. Credentials map[string]any
  416. Extra map[string]any
  417. ProxyID *int64
  418. Concurrency *int // 使用指针区分"未提供"和"设置为0"
  419. Priority *int // 使用指针区分"未提供"和"设置为0"
  420. RateMultiplier *float64 // 账号计费倍率(>=0,允许 0)
  421. LoadFactor *int
  422. Status string
  423. GroupIDs *[]int64
  424. ExpiresAt *int64
  425. AutoPauseOnExpired *bool
  426. ProbeEnabled *bool
  427. RateSyncEnabled *bool
  428. SkipMixedChannelCheck bool // 跳过混合渠道检查(用户已确认风险)
  429. }
  430. // BulkUpdateAccountsInput describes the payload for bulk updating accounts.
  431. type BulkUpdateAccountsInput struct {
  432. AccountIDs []int64
  433. Filters *BulkUpdateAccountFilters
  434. Name string
  435. ProxyID *int64
  436. Concurrency *int
  437. Priority *int
  438. RateMultiplier *float64 // 账号计费倍率(>=0,允许 0)
  439. LoadFactor *int
  440. Status string
  441. Schedulable *bool
  442. GroupIDs *[]int64
  443. Credentials map[string]any
  444. Extra map[string]any
  445. ProbeEnabled *bool
  446. // SkipMixedChannelCheck skips the mixed channel risk check when binding groups.
  447. // This should only be set when the caller has explicitly confirmed the risk.
  448. SkipMixedChannelCheck bool
  449. }
  450. type BulkUpdateAccountFilters struct {
  451. Platform string
  452. Type string
  453. Status string
  454. Group string
  455. Search string
  456. PrivacyMode string
  457. }
  458. // BulkUpdateAccountResult captures the result for a single account update.
  459. type BulkUpdateAccountResult struct {
  460. AccountID int64 `json:"account_id"`
  461. Success bool `json:"success"`
  462. Error string `json:"error,omitempty"`
  463. }
  464. // AdminUpdateAPIKeyGroupIDResult is the result of AdminUpdateAPIKeyGroupID.
  465. type AdminUpdateAPIKeyGroupIDResult struct {
  466. APIKey *APIKey
  467. AutoGrantedGroupAccess bool // true if a new exclusive group permission was auto-added
  468. GrantedGroupID *int64 // the group ID that was auto-granted
  469. GrantedGroupName string // the group name that was auto-granted
  470. }
  471. // ReplaceUserGroupResult 分组替换操作的结果
  472. type ReplaceUserGroupResult struct {
  473. MigratedKeys int64 // 迁移的 Key 数量
  474. }
  475. // UserRPMStatus describes a user's current per-minute RPM usage.
  476. type UserRPMStatus struct {
  477. UserRPMUsed int `json:"user_rpm_used"`
  478. UserRPMLimit int `json:"user_rpm_limit"`
  479. PerGroup []UserGroupRPMStatus `json:"per_group"`
  480. }
  481. // UserGroupRPMStatus describes current per-minute RPM usage for one user/group pair.
  482. type UserGroupRPMStatus struct {
  483. GroupID int64 `json:"group_id"`
  484. GroupName string `json:"group_name"`
  485. Used int `json:"used"`
  486. Limit int `json:"limit"`
  487. Source string `json:"source"` // "group" | "override"
  488. }
  489. // BulkUpdateAccountsResult is the aggregated response for bulk updates.
  490. type BulkUpdateAccountsResult struct {
  491. Success int `json:"success"`
  492. Failed int `json:"failed"`
  493. SuccessIDs []int64 `json:"success_ids"`
  494. FailedIDs []int64 `json:"failed_ids"`
  495. Results []BulkUpdateAccountResult `json:"results"`
  496. LongContextInheritedCount int `json:"long_context_inherited_count,omitempty"`
  497. }
  498. type CreateProxyInput struct {
  499. Name string
  500. Protocol string
  501. Host string
  502. Port int
  503. Username string
  504. Password string
  505. ExpiresAt *time.Time
  506. FallbackMode string
  507. BackupProxyID *int64
  508. ExpiryWarnDays int
  509. }
  510. // UpdateProxyInput preserves omitted expiry/backup values; Clear flags explicitly
  511. // remove them. A nil ExpiryWarnDays preserves the current warning period.
  512. type UpdateProxyInput struct {
  513. Name string
  514. Protocol string
  515. Host string
  516. Port int
  517. Username string
  518. Password string
  519. Status string
  520. ExpiresAt *time.Time
  521. ClearExpiresAt bool
  522. FallbackMode string
  523. BackupProxyID *int64
  524. ClearBackupID bool
  525. ExpiryWarnDays *int
  526. }
  527. type GenerateRedeemCodesInput struct {
  528. Count int
  529. Type string
  530. Value float64
  531. GroupID *int64 // 订阅类型专用:关联的分组ID
  532. ValidityDays int // 订阅类型专用:有效天数
  533. ExpiresAt *time.Time
  534. }
  535. type ProxyBatchDeleteResult struct {
  536. DeletedIDs []int64 `json:"deleted_ids"`
  537. Skipped []ProxyBatchDeleteSkipped `json:"skipped"`
  538. }
  539. type ProxyBatchDeleteSkipped struct {
  540. ID int64 `json:"id"`
  541. Reason string `json:"reason"`
  542. }
  543. // ProxyTestResult represents the result of testing a proxy
  544. type ProxyTestResult struct {
  545. Success bool `json:"success"`
  546. Message string `json:"message"`
  547. LatencyMs int64 `json:"latency_ms,omitempty"`
  548. IPAddress string `json:"ip_address,omitempty"`
  549. City string `json:"city,omitempty"`
  550. Region string `json:"region,omitempty"`
  551. Country string `json:"country,omitempty"`
  552. CountryCode string `json:"country_code,omitempty"`
  553. }
  554. type ProxyQualityCheckResult struct {
  555. ProxyID int64 `json:"proxy_id"`
  556. Score int `json:"score"`
  557. Grade string `json:"grade"`
  558. Summary string `json:"summary"`
  559. ExitIP string `json:"exit_ip,omitempty"`
  560. Country string `json:"country,omitempty"`
  561. CountryCode string `json:"country_code,omitempty"`
  562. BaseLatencyMs int64 `json:"base_latency_ms,omitempty"`
  563. PassedCount int `json:"passed_count"`
  564. WarnCount int `json:"warn_count"`
  565. FailedCount int `json:"failed_count"`
  566. ChallengeCount int `json:"challenge_count"`
  567. CheckedAt int64 `json:"checked_at"`
  568. Items []ProxyQualityCheckItem `json:"items"`
  569. }
  570. type ProxyQualityCheckItem struct {
  571. Target string `json:"target"`
  572. Status string `json:"status"` // pass/warn/fail/challenge
  573. HTTPStatus int `json:"http_status,omitempty"`
  574. LatencyMs int64 `json:"latency_ms,omitempty"`
  575. Message string `json:"message,omitempty"`
  576. CFRay string `json:"cf_ray,omitempty"`
  577. }
  578. // ProxyExitInfo represents proxy exit information from ip-api.com
  579. type ProxyExitInfo struct {
  580. IP string
  581. City string
  582. Region string
  583. Country string
  584. CountryCode string
  585. }
  586. // ProxyExitInfoProber tests proxy connectivity and retrieves exit information
  587. type ProxyExitInfoProber interface {
  588. ProbeProxy(ctx context.Context, proxyURL string) (*ProxyExitInfo, int64, error)
  589. }
  590. type groupExistenceBatchReader interface {
  591. ExistsByIDs(ctx context.Context, ids []int64) (map[int64]bool, error)
  592. }
  593. type proxyQualityTarget struct {
  594. Target string
  595. URL string
  596. Method string
  597. AllowedStatuses map[int]struct{}
  598. }
  599. var proxyQualityTargets = []proxyQualityTarget{
  600. {
  601. Target: "openai",
  602. URL: "https://api.openai.com/v1/models",
  603. Method: http.MethodGet,
  604. AllowedStatuses: map[int]struct{}{
  605. http.StatusUnauthorized: {},
  606. },
  607. },
  608. {
  609. Target: "anthropic",
  610. URL: "https://api.anthropic.com/v1/messages",
  611. Method: http.MethodGet,
  612. AllowedStatuses: map[int]struct{}{
  613. http.StatusUnauthorized: {},
  614. http.StatusMethodNotAllowed: {},
  615. http.StatusNotFound: {},
  616. http.StatusBadRequest: {},
  617. },
  618. },
  619. {
  620. Target: "gemini",
  621. URL: "https://generativelanguage.googleapis.com/$discovery/rest?version=v1beta",
  622. Method: http.MethodGet,
  623. AllowedStatuses: map[int]struct{}{
  624. http.StatusOK: {},
  625. },
  626. },
  627. {
  628. Target: "grok",
  629. URL: "https://api.x.ai/v1/models",
  630. Method: http.MethodGet,
  631. AllowedStatuses: map[int]struct{}{
  632. http.StatusUnauthorized: {},
  633. },
  634. },
  635. }
  636. const (
  637. proxyQualityRequestTimeout = 15 * time.Second
  638. proxyQualityResponseHeaderTimeout = 10 * time.Second
  639. proxyQualityMaxBodyBytes = int64(8 * 1024)
  640. proxyQualityClientUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
  641. )
  642. var ErrRPMStatusUnavailable = infraerrors.New(http.StatusNotImplemented, "RPM_STATUS_UNAVAILABLE", "RPM cache not available")
  643. // adminServiceImpl implements AdminService
  644. type adminServiceImpl struct {
  645. cfg *config.Config
  646. userRepo UserRepository
  647. groupRepo GroupRepository
  648. groupDuplicateRepo GroupDuplicateRepository
  649. emptyGroupDeleteRepo EmptyGroupDeleteRepository
  650. accountRepo AccountRepository
  651. accountDuplicateRepo AccountDuplicateRepository
  652. accountBillingRepo AccountBillingSettingsRepository
  653. proxyRepo ProxyRepository
  654. apiKeyRepo APIKeyRepository
  655. redeemCodeRepo RedeemCodeRepository
  656. userGroupRateRepo UserGroupRateRepository
  657. userRPMCache UserRPMCache
  658. billingCacheService *BillingCacheService
  659. proxyProber ProxyExitInfoProber
  660. proxyLatencyCache ProxyLatencyCache
  661. authCacheInvalidator APIKeyAuthCacheInvalidator
  662. entClient *dbent.Client // 用于开启数据库事务
  663. settingService *SettingService
  664. defaultSubAssigner DefaultSubscriptionAssigner
  665. userSubRepo UserSubscriptionRepository
  666. privacyClientFactory PrivacyClientFactory
  667. runtimeBlocker AccountRuntimeBlocker
  668. affiliateService adminRechargeAffiliateAccruer
  669. compositeRouteRepo CompositeModelRouteRepository
  670. compositeResolver *CompositeRouteResolver
  671. // 分组平台变更后用来失效渠道缓存;可为 nil(缓存会在 TTL 到期后自然重建)
  672. channelCacheInvalidator ChannelCacheInvalidator
  673. }
  674. // ChannelCacheInvalidator 失效渠道缓存。
  675. // 窄接口,避免 admin 服务依赖整个 ChannelService——与 APIKeyAuthCacheInvalidator 同一思路。
  676. type ChannelCacheInvalidator interface {
  677. InvalidateCache()
  678. }
  679. type adminRechargeAffiliateAccruer interface {
  680. AccrueInviteRebate(ctx context.Context, inviteeUserID int64, baseRechargeAmount float64) (float64, error)
  681. }
  682. type userGroupRateBatchReader interface {
  683. GetByUserIDs(ctx context.Context, userIDs []int64) (map[int64]map[int64]float64, error)
  684. }
  685. // NewAdminService creates a new AdminService
  686. func NewAdminService(
  687. cfg *config.Config,
  688. userRepo UserRepository,
  689. groupRepo AdminGroupRepository,
  690. accountRepo AdminAccountRepository,
  691. proxyRepo ProxyRepository,
  692. apiKeyRepo APIKeyRepository,
  693. redeemCodeRepo RedeemCodeRepository,
  694. userGroupRateRepo UserGroupRateRepository,
  695. userRPMCache UserRPMCache,
  696. billingCacheService *BillingCacheService,
  697. proxyProber ProxyExitInfoProber,
  698. proxyLatencyCache ProxyLatencyCache,
  699. authCacheInvalidator APIKeyAuthCacheInvalidator,
  700. entClient *dbent.Client,
  701. settingService *SettingService,
  702. defaultSubAssigner DefaultSubscriptionAssigner,
  703. userSubRepo UserSubscriptionRepository,
  704. privacyClientFactory PrivacyClientFactory,
  705. runtimeBlocker AccountRuntimeBlocker,
  706. affiliateService *AffiliateService,
  707. compositeRouteRepo CompositeModelRouteRepository,
  708. compositeResolver *CompositeRouteResolver,
  709. channelCacheInvalidator ChannelCacheInvalidator,
  710. ) AdminService {
  711. return &adminServiceImpl{
  712. cfg: cfg,
  713. userRepo: userRepo,
  714. groupRepo: groupRepo,
  715. groupDuplicateRepo: groupRepo,
  716. emptyGroupDeleteRepo: groupRepo,
  717. accountRepo: accountRepo,
  718. accountDuplicateRepo: accountRepo,
  719. accountBillingRepo: accountRepo,
  720. proxyRepo: proxyRepo,
  721. apiKeyRepo: apiKeyRepo,
  722. redeemCodeRepo: redeemCodeRepo,
  723. userGroupRateRepo: userGroupRateRepo,
  724. userRPMCache: userRPMCache,
  725. billingCacheService: billingCacheService,
  726. proxyProber: proxyProber,
  727. proxyLatencyCache: proxyLatencyCache,
  728. authCacheInvalidator: authCacheInvalidator,
  729. entClient: entClient,
  730. settingService: settingService,
  731. defaultSubAssigner: defaultSubAssigner,
  732. userSubRepo: userSubRepo,
  733. privacyClientFactory: privacyClientFactory,
  734. runtimeBlocker: runtimeBlocker,
  735. affiliateService: affiliateService,
  736. compositeRouteRepo: compositeRouteRepo,
  737. compositeResolver: compositeResolver,
  738. channelCacheInvalidator: channelCacheInvalidator,
  739. }
  740. }