setting_handler.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. package admin
  2. import (
  3. "crypto/rand"
  4. "encoding/hex"
  5. "encoding/json"
  6. "fmt"
  7. "log/slog"
  8. "regexp"
  9. "strings"
  10. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  11. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  12. "github.com/Wei-Shaw/sub2api/internal/service"
  13. "github.com/gin-gonic/gin"
  14. )
  15. // semverPattern 预编译 semver 格式校验正则
  16. var semverPattern = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
  17. // menuItemIDPattern validates custom menu item IDs: alphanumeric, hyphens, underscores only.
  18. var menuItemIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
  19. // generateMenuItemID generates a short random hex ID for a custom menu item.
  20. func generateMenuItemID() (string, error) {
  21. b := make([]byte, 8)
  22. if _, err := rand.Read(b); err != nil {
  23. return "", fmt.Errorf("generate menu item ID: %w", err)
  24. }
  25. return hex.EncodeToString(b), nil
  26. }
  27. func scopesContainOpenID(scopes string) bool {
  28. for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
  29. if scope == "openid" {
  30. return true
  31. }
  32. }
  33. return false
  34. }
  35. func firstNonEmpty(values ...string) string {
  36. for _, value := range values {
  37. if trimmed := strings.TrimSpace(value); trimmed != "" {
  38. return trimmed
  39. }
  40. }
  41. return ""
  42. }
  43. // SettingHandler 系统设置处理器
  44. type SettingHandler struct {
  45. settingService *service.SettingService
  46. emailService *service.EmailService
  47. turnstileService *service.TurnstileService
  48. aliyunCaptchaService *service.AliyunCaptchaService
  49. opsService *service.OpsService
  50. paymentConfigService *service.PaymentConfigService
  51. paymentService *service.PaymentService
  52. userAttributeService *service.UserAttributeService
  53. notificationEmailService *service.NotificationEmailService
  54. totpService *service.TotpService
  55. userService *service.UserService
  56. }
  57. // NewSettingHandler 创建系统设置处理器
  58. func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService, paymentConfigService *service.PaymentConfigService, paymentService *service.PaymentService, userAttributeService *service.UserAttributeService) *SettingHandler {
  59. return &SettingHandler{
  60. settingService: settingService,
  61. emailService: emailService,
  62. turnstileService: turnstileService,
  63. opsService: opsService,
  64. paymentConfigService: paymentConfigService,
  65. paymentService: paymentService,
  66. userAttributeService: userAttributeService,
  67. }
  68. }
  69. // SetNotificationEmailService attaches the notification template service without changing
  70. // the constructor signature used by existing unit tests.
  71. func (h *SettingHandler) SetNotificationEmailService(notificationEmailService *service.NotificationEmailService) {
  72. h.notificationEmailService = notificationEmailService
  73. }
  74. // SetAliyunCaptchaService attaches the Aliyun captcha credential validator without
  75. // changing the constructor signature used by existing unit tests.
  76. func (h *SettingHandler) SetAliyunCaptchaService(aliyunCaptchaService *service.AliyunCaptchaService) {
  77. h.aliyunCaptchaService = aliyunCaptchaService
  78. }
  79. // SetStepUpDeps attaches the services backing the step-up switch preconditions
  80. // (enable requires the acting admin to have TOTP enabled; disable is itself a
  81. // step-up gated operation), without changing the constructor signature used by
  82. // existing unit tests.
  83. func (h *SettingHandler) SetStepUpDeps(totpService *service.TotpService, userService *service.UserService) {
  84. h.totpService = totpService
  85. h.userService = userService
  86. }
  87. // GetSettings 获取所有系统设置
  88. // GET /api/v1/admin/settings
  89. func (h *SettingHandler) GetSettings(c *gin.Context) {
  90. settings, err := h.settingService.GetAllSettings(c.Request.Context())
  91. if err != nil {
  92. response.ErrorFrom(c, err)
  93. return
  94. }
  95. authSourceDefaults, err := h.settingService.GetAuthSourceDefaultSettings(c.Request.Context())
  96. if err != nil {
  97. response.ErrorFrom(c, err)
  98. return
  99. }
  100. // Check if ops monitoring is enabled (respects config.ops.enabled)
  101. opsEnabled := h.opsService != nil && h.opsService.IsMonitoringEnabled(c.Request.Context())
  102. defaultSubscriptions := make([]dto.DefaultSubscriptionSetting, 0, len(settings.DefaultSubscriptions))
  103. for _, sub := range settings.DefaultSubscriptions {
  104. defaultSubscriptions = append(defaultSubscriptions, dto.DefaultSubscriptionSetting{
  105. GroupID: sub.GroupID,
  106. ValidityDays: sub.ValidityDays,
  107. })
  108. }
  109. // Load payment config
  110. var paymentCfg *service.PaymentConfig
  111. if h.paymentConfigService != nil {
  112. paymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context())
  113. }
  114. if paymentCfg == nil {
  115. paymentCfg = &service.PaymentConfig{}
  116. }
  117. passkeyConfigured, passkeyRPID, passkeyRPOrigins := h.settingService.PasskeyConfiguration()
  118. payload := dto.SystemSettings{
  119. RegistrationEnabled: settings.RegistrationEnabled,
  120. EmailVerifyEnabled: settings.EmailVerifyEnabled,
  121. RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
  122. RegistrationEmailDomainQuotaEnabled: settings.RegistrationEmailDomainQuotaEnabled,
  123. PromoCodeEnabled: settings.PromoCodeEnabled,
  124. PasswordResetEnabled: settings.PasswordResetEnabled,
  125. FrontendURL: settings.FrontendURL,
  126. InvitationCodeEnabled: settings.InvitationCodeEnabled,
  127. TotpEnabled: settings.TotpEnabled,
  128. TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
  129. PasskeyEnabled: settings.PasskeyEnabled,
  130. PasskeyConfigured: passkeyConfigured,
  131. PasskeyRPID: passkeyRPID,
  132. PasskeyRPOrigins: passkeyRPOrigins,
  133. SessionBindingEnabled: settings.SessionBindingEnabled,
  134. StepUpEnabled: settings.StepUpEnabled,
  135. AuditLogRetentionDays: settings.AuditLogRetentionDays,
  136. LoginAgreementEnabled: settings.LoginAgreementEnabled,
  137. LoginAgreementMode: settings.LoginAgreementMode,
  138. LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt,
  139. LoginAgreementDocuments: loginAgreementDocumentsToDTO(settings.LoginAgreementDocuments),
  140. SMTPHost: settings.SMTPHost,
  141. SMTPPort: settings.SMTPPort,
  142. SMTPUsername: settings.SMTPUsername,
  143. SMTPPasswordConfigured: settings.SMTPPasswordConfigured,
  144. SMTPFrom: settings.SMTPFrom,
  145. SMTPFromName: settings.SMTPFromName,
  146. SMTPUseTLS: settings.SMTPUseTLS,
  147. TurnstileEnabled: settings.TurnstileEnabled,
  148. TurnstileSiteKey: settings.TurnstileSiteKey,
  149. TurnstileSecretKeyConfigured: settings.TurnstileSecretKeyConfigured,
  150. TencentCaptchaEnabled: settings.TencentCaptchaEnabled,
  151. TencentCaptchaAppID: settings.TencentCaptchaAppID,
  152. TencentCaptchaAppSecretKeyConfigured: settings.TencentCaptchaAppSecretKeyConfigured,
  153. TencentCaptchaCloudSecretIDConfigured: settings.TencentCaptchaCloudSecretIDConfigured,
  154. TencentCaptchaCloudSecretKeyConfigured: settings.TencentCaptchaCloudSecretKeyConfigured,
  155. TencentCaptchaRegion: settings.TencentCaptchaRegion,
  156. AliyunCaptchaEnabled: settings.AliyunCaptchaEnabled,
  157. AliyunCaptchaAccessKeyID: settings.AliyunCaptchaAccessKeyID,
  158. AliyunCaptchaAccessKeySecretConfigured: settings.AliyunCaptchaAccessKeySecretConfigured,
  159. AliyunCaptchaSceneID: settings.AliyunCaptchaSceneID,
  160. AliyunCaptchaPrefix: settings.AliyunCaptchaPrefix,
  161. AliyunCaptchaRegion: settings.AliyunCaptchaRegion,
  162. APIKeyACLTrustForwardedIP: settings.APIKeyACLTrustForwardedIP,
  163. ForwardedClientIPHeaders: settings.ForwardedClientIPHeaders,
  164. LinuxDoConnectEnabled: settings.LinuxDoConnectEnabled,
  165. LinuxDoConnectClientID: settings.LinuxDoConnectClientID,
  166. LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured,
  167. LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL,
  168. DingTalkConnectEnabled: settings.DingTalkConnectEnabled,
  169. DingTalkConnectClientID: settings.DingTalkConnectClientID,
  170. DingTalkConnectClientSecretConfigured: settings.DingTalkConnectClientSecretConfigured,
  171. DingTalkConnectRedirectURL: settings.DingTalkConnectRedirectURL,
  172. DingTalkConnectCorpRestrictionPolicy: settings.DingTalkConnectCorpRestrictionPolicy,
  173. DingTalkConnectInternalCorpID: settings.DingTalkConnectInternalCorpID,
  174. DingTalkConnectBypassRegistration: settings.DingTalkConnectBypassRegistration,
  175. DingTalkConnectSyncCorpEmail: settings.DingTalkConnectSyncCorpEmail,
  176. DingTalkConnectSyncDisplayName: settings.DingTalkConnectSyncDisplayName,
  177. DingTalkConnectSyncDept: settings.DingTalkConnectSyncDept,
  178. DingTalkConnectSyncCorpEmailAttrKey: settings.DingTalkConnectSyncCorpEmailAttrKey,
  179. DingTalkConnectSyncDisplayNameAttrKey: settings.DingTalkConnectSyncDisplayNameAttrKey,
  180. DingTalkConnectSyncDeptAttrKey: settings.DingTalkConnectSyncDeptAttrKey,
  181. DingTalkConnectSyncCorpEmailAttrName: settings.DingTalkConnectSyncCorpEmailAttrName,
  182. DingTalkConnectSyncDisplayNameAttrName: settings.DingTalkConnectSyncDisplayNameAttrName,
  183. DingTalkConnectSyncDeptAttrName: settings.DingTalkConnectSyncDeptAttrName,
  184. WeChatConnectEnabled: settings.WeChatConnectEnabled,
  185. WeChatConnectAppID: settings.WeChatConnectAppID,
  186. WeChatConnectAppSecretConfigured: settings.WeChatConnectAppSecretConfigured,
  187. WeChatConnectOpenAppID: settings.WeChatConnectOpenAppID,
  188. WeChatConnectOpenAppSecretConfigured: settings.WeChatConnectOpenAppSecretConfigured,
  189. WeChatConnectMPAppID: settings.WeChatConnectMPAppID,
  190. WeChatConnectMPAppSecretConfigured: settings.WeChatConnectMPAppSecretConfigured,
  191. WeChatConnectMobileAppID: settings.WeChatConnectMobileAppID,
  192. WeChatConnectMobileAppSecretConfigured: settings.WeChatConnectMobileAppSecretConfigured,
  193. WeChatConnectOpenEnabled: settings.WeChatConnectOpenEnabled,
  194. WeChatConnectMPEnabled: settings.WeChatConnectMPEnabled,
  195. WeChatConnectMobileEnabled: settings.WeChatConnectMobileEnabled,
  196. WeChatConnectMode: settings.WeChatConnectMode,
  197. WeChatConnectScopes: settings.WeChatConnectScopes,
  198. WeChatConnectRedirectURL: settings.WeChatConnectRedirectURL,
  199. WeChatConnectFrontendRedirectURL: settings.WeChatConnectFrontendRedirectURL,
  200. OIDCConnectEnabled: settings.OIDCConnectEnabled,
  201. OIDCConnectProviderName: settings.OIDCConnectProviderName,
  202. OIDCConnectClientID: settings.OIDCConnectClientID,
  203. OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured,
  204. OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL,
  205. OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL,
  206. OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL,
  207. OIDCConnectTokenURL: settings.OIDCConnectTokenURL,
  208. OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL,
  209. OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL,
  210. OIDCConnectScopes: settings.OIDCConnectScopes,
  211. OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL,
  212. OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL,
  213. OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod,
  214. OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE,
  215. OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken,
  216. OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs,
  217. OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds,
  218. OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified,
  219. OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath,
  220. OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath,
  221. OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath,
  222. GitHubOAuthEnabled: settings.GitHubOAuthEnabled,
  223. GitHubOAuthClientID: settings.GitHubOAuthClientID,
  224. GitHubOAuthClientSecretConfigured: settings.GitHubOAuthClientSecretConfigured,
  225. GitHubOAuthRedirectURL: settings.GitHubOAuthRedirectURL,
  226. GitHubOAuthFrontendRedirectURL: settings.GitHubOAuthFrontendRedirectURL,
  227. GoogleOAuthEnabled: settings.GoogleOAuthEnabled,
  228. GoogleOAuthClientID: settings.GoogleOAuthClientID,
  229. GoogleOAuthClientSecretConfigured: settings.GoogleOAuthClientSecretConfigured,
  230. GoogleOAuthRedirectURL: settings.GoogleOAuthRedirectURL,
  231. GoogleOAuthFrontendRedirectURL: settings.GoogleOAuthFrontendRedirectURL,
  232. SiteName: settings.SiteName,
  233. SiteLogo: settings.SiteLogo,
  234. SiteSubtitle: settings.SiteSubtitle,
  235. APIBaseURL: settings.APIBaseURL,
  236. ContactInfo: settings.ContactInfo,
  237. DocURL: settings.DocURL,
  238. HomeContent: settings.HomeContent,
  239. CompactHomeEnabled: settings.CompactHomeEnabled,
  240. HideCcsImportButton: settings.HideCcsImportButton,
  241. PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
  242. PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
  243. TableDefaultPageSize: settings.TableDefaultPageSize,
  244. TablePageSizeOptions: settings.TablePageSizeOptions,
  245. CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
  246. CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
  247. DefaultConcurrency: settings.DefaultConcurrency,
  248. DefaultBalance: settings.DefaultBalance,
  249. RiskControlEnabled: settings.RiskControlEnabled,
  250. CyberSessionBlockEnabled: settings.CyberSessionBlockEnabled,
  251. CyberSessionBlockTTLSeconds: settings.CyberSessionBlockTTLSeconds,
  252. AffiliateRebateRate: settings.AffiliateRebateRate,
  253. AffiliateRebateFreezeHours: settings.AffiliateRebateFreezeHours,
  254. AffiliateRebateDurationDays: settings.AffiliateRebateDurationDays,
  255. AffiliateRebatePerInviteeCap: settings.AffiliateRebatePerInviteeCap,
  256. AdminRechargeRebateEnabled: settings.AdminRechargeRebateEnabled,
  257. DefaultUserRPMLimit: settings.DefaultUserRPMLimit,
  258. DefaultSubscriptions: defaultSubscriptions,
  259. EnableModelFallback: settings.EnableModelFallback,
  260. FallbackModelAnthropic: settings.FallbackModelAnthropic,
  261. FallbackModelOpenAI: settings.FallbackModelOpenAI,
  262. FallbackModelGemini: settings.FallbackModelGemini,
  263. FallbackModelAntigravity: settings.FallbackModelAntigravity,
  264. EnableIdentityPatch: settings.EnableIdentityPatch,
  265. IdentityPatchPrompt: settings.IdentityPatchPrompt,
  266. OpsMonitoringEnabled: opsEnabled && settings.OpsMonitoringEnabled,
  267. OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
  268. OpsQueryModeDefault: settings.OpsQueryModeDefault,
  269. OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
  270. MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
  271. MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
  272. AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
  273. BackendModeEnabled: settings.BackendModeEnabled,
  274. OpenAITTFTMode: settings.OpenAITTFTMode,
  275. EnableFingerprintUnification: settings.EnableFingerprintUnification,
  276. EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
  277. EnableCCHSigning: settings.EnableCCHSigning,
  278. EnableClaudeOAuthSystemPromptInjection: settings.EnableClaudeOAuthSystemPromptInjection,
  279. ClaudeOAuthSystemPrompt: settings.ClaudeOAuthSystemPrompt,
  280. ClaudeOAuthSystemPromptBlocks: settings.ClaudeOAuthSystemPromptBlocks,
  281. EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
  282. RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
  283. EnableClientDatelineNormalization: settings.EnableClientDatelineNormalization,
  284. AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
  285. OpenAICodexUserAgent: settings.OpenAICodexUserAgent,
  286. OpenAICodexClientVersion: settings.OpenAICodexClientVersion,
  287. OpenAICodexClientVersionSynced: settings.OpenAICodexClientVersionSynced,
  288. OpenAICodexVersionAutoSyncEnabled: settings.OpenAICodexVersionAutoSyncEnabled,
  289. MinCodexVersion: settings.MinCodexVersion,
  290. MaxCodexVersion: settings.MaxCodexVersion,
  291. CodexCLIOnlyBlacklist: settings.CodexCLIOnlyBlacklist,
  292. CodexCLIOnlyWhitelist: settings.CodexCLIOnlyWhitelist,
  293. CodexCLIOnlyAllowAppServerClients: settings.CodexCLIOnlyAllowAppServerClients,
  294. CodexCLIOnlyEngineFingerprintSignals: settings.CodexCLIOnlyEngineFingerprintSignals,
  295. WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
  296. PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
  297. PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
  298. PaymentVisibleMethodAlipayEnabled: settings.PaymentVisibleMethodAlipayEnabled,
  299. PaymentVisibleMethodWxpayEnabled: settings.PaymentVisibleMethodWxpayEnabled,
  300. OpenAILowUpstreamRatePriorityEnabled: settings.OpenAILowUpstreamRatePriorityEnabled,
  301. OpenAIOAuthSchedulingRateMultiplier: settings.OpenAIOAuthSchedulingRateMultiplier,
  302. OpenAIAdvancedSchedulerEnabled: settings.OpenAIAdvancedSchedulerEnabled,
  303. OpenAIAdvancedSchedulerStickyWeightedEnabled: settings.OpenAIAdvancedSchedulerStickyWeightedEnabled,
  304. OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: settings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled,
  305. OpenAIAdvancedSchedulerLBTopK: settings.OpenAIAdvancedSchedulerLBTopK,
  306. OpenAIAdvancedSchedulerWeightPriority: settings.OpenAIAdvancedSchedulerWeightPriority,
  307. OpenAIAdvancedSchedulerWeightLoad: settings.OpenAIAdvancedSchedulerWeightLoad,
  308. OpenAIAdvancedSchedulerWeightQueue: settings.OpenAIAdvancedSchedulerWeightQueue,
  309. OpenAIAdvancedSchedulerWeightErrorRate: settings.OpenAIAdvancedSchedulerWeightErrorRate,
  310. OpenAIAdvancedSchedulerWeightTTFT: settings.OpenAIAdvancedSchedulerWeightTTFT,
  311. OpenAIAdvancedSchedulerWeightReset: settings.OpenAIAdvancedSchedulerWeightReset,
  312. OpenAIAdvancedSchedulerWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerWeightQuotaHeadroom,
  313. OpenAIAdvancedSchedulerWeightUpstreamCost: settings.OpenAIAdvancedSchedulerWeightUpstreamCost,
  314. OpenAIAdvancedSchedulerWeightPreviousResponse: settings.OpenAIAdvancedSchedulerWeightPreviousResponse,
  315. OpenAIAdvancedSchedulerWeightSessionSticky: settings.OpenAIAdvancedSchedulerWeightSessionSticky,
  316. OpenAIAdvancedSchedulerEffectiveLBTopK: settings.OpenAIAdvancedSchedulerEffectiveLBTopK,
  317. OpenAIAdvancedSchedulerEffectiveWeightPriority: settings.OpenAIAdvancedSchedulerEffectiveWeightPriority,
  318. OpenAIAdvancedSchedulerEffectiveWeightLoad: settings.OpenAIAdvancedSchedulerEffectiveWeightLoad,
  319. OpenAIAdvancedSchedulerEffectiveWeightQueue: settings.OpenAIAdvancedSchedulerEffectiveWeightQueue,
  320. OpenAIAdvancedSchedulerEffectiveWeightErrorRate: settings.OpenAIAdvancedSchedulerEffectiveWeightErrorRate,
  321. OpenAIAdvancedSchedulerEffectiveWeightTTFT: settings.OpenAIAdvancedSchedulerEffectiveWeightTTFT,
  322. OpenAIAdvancedSchedulerEffectiveWeightReset: settings.OpenAIAdvancedSchedulerEffectiveWeightReset,
  323. OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom,
  324. OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost: settings.OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost,
  325. OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse: settings.OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse,
  326. OpenAIAdvancedSchedulerEffectiveWeightSessionSticky: settings.OpenAIAdvancedSchedulerEffectiveWeightSessionSticky,
  327. BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled,
  328. BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold,
  329. BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL,
  330. SubscriptionExpiryNotifyEnabled: settings.SubscriptionExpiryNotifyEnabled,
  331. AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled,
  332. AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(settings.AccountQuotaNotifyEmails),
  333. PaymentEnabled: paymentCfg.Enabled,
  334. PaymentMinAmount: paymentCfg.MinAmount,
  335. PaymentMaxAmount: paymentCfg.MaxAmount,
  336. PaymentDailyLimit: paymentCfg.DailyLimit,
  337. PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin,
  338. PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders,
  339. PaymentEnabledTypes: paymentCfg.EnabledTypes,
  340. PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
  341. PaymentBalanceRechargeMultiplier: paymentCfg.BalanceRechargeMultiplier,
  342. PaymentSubscriptionUSDToCNYRate: paymentCfg.SubscriptionUSDToCNYRate,
  343. PaymentRechargeFeeRate: paymentCfg.RechargeFeeRate,
  344. PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
  345. PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
  346. PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
  347. PaymentHelpImageURL: paymentCfg.HelpImageURL,
  348. PaymentHelpText: paymentCfg.HelpText,
  349. PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled,
  350. PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax,
  351. PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
  352. PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
  353. PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
  354. PaymentAlipayForceQRCode: paymentCfg.AlipayForceQRCode,
  355. PaymentAlipayMobilePrecreateDeepLink: paymentCfg.AlipayMobilePrecreateDeepLink,
  356. ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
  357. ChannelMonitorMode: settings.ChannelMonitorMode,
  358. ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
  359. ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput,
  360. ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota,
  361. ChannelMonitorHideUserRanking: settings.ChannelMonitorHideUserRanking,
  362. GrokDefaultTextModel: settings.GrokDefaultTextModel,
  363. GrokCrossClientModelMapEnabled: settings.GrokCrossClientModelMapEnabled,
  364. GrokDefaultBaseURLMode: settings.GrokDefaultBaseURLMode,
  365. AvailableChannelsEnabled: settings.AvailableChannelsEnabled,
  366. ModelPlazaEnabled: settings.ModelPlazaEnabled,
  367. ModelPlazaRequireAuth: settings.ModelPlazaRequireAuth,
  368. PluginManagementEnabled: settings.PluginManagementEnabled,
  369. ModelPlazaDescription: settings.ModelPlazaDescription,
  370. AffiliateEnabled: settings.AffiliateEnabled,
  371. AccountSchedulingThresholds: settings.AccountSchedulingThresholds,
  372. AllowUserViewErrorRequests: settings.AllowUserViewErrorRequests,
  373. }
  374. // OpenAI fast policy (stored under a dedicated setting key)
  375. if fastPolicy, err := h.settingService.GetOpenAIFastPolicySettings(c.Request.Context()); err != nil {
  376. slog.Error("openai_fast_policy_settings_get_failed", "error", err)
  377. } else if fastPolicy != nil {
  378. payload.OpenAIFastPolicySettings = openaiFastPolicySettingsToDTO(fastPolicy)
  379. }
  380. // Default platform quotas(JSON map)
  381. if platformQuotas, err := h.settingService.GetDefaultPlatformQuotas(c.Request.Context()); err != nil {
  382. slog.Error("default_platform_quotas_get_failed", "error", err)
  383. } else {
  384. payload.DefaultPlatformQuotas = platformQuotas
  385. }
  386. response.Success(c, systemSettingsResponseData(payload, authSourceDefaults))
  387. }
  388. // openaiFastPolicySettingsToDTO converts service -> dto for OpenAI fast policy.
  389. func openaiFastPolicySettingsToDTO(s *service.OpenAIFastPolicySettings) *dto.OpenAIFastPolicySettings {
  390. if s == nil {
  391. return nil
  392. }
  393. rules := make([]dto.OpenAIFastPolicyRule, len(s.Rules))
  394. for i, r := range s.Rules {
  395. rules[i] = dto.OpenAIFastPolicyRule(r)
  396. }
  397. return &dto.OpenAIFastPolicySettings{Rules: rules}
  398. }
  399. // openaiFastPolicySettingsFromDTO converts dto -> service for OpenAI fast policy.
  400. //
  401. // 规范化 ServiceTier:在 DTO 进入 service 层之前统一把空字符串归一为
  402. // service.OpenAIFastTierAny ("all"),避免管理员保存时空串与 "all" 同时
  403. // 表达"匹配任意 tier"造成数据库取值的二义性。其它非空值原样透传,由
  404. // service.SetOpenAIFastPolicySettings 负责合法值校验。
  405. func openaiFastPolicySettingsFromDTO(s *dto.OpenAIFastPolicySettings) *service.OpenAIFastPolicySettings {
  406. if s == nil {
  407. return nil
  408. }
  409. rules := make([]service.OpenAIFastPolicyRule, len(s.Rules))
  410. for i, r := range s.Rules {
  411. rules[i] = service.OpenAIFastPolicyRule(r)
  412. tier := strings.ToLower(strings.TrimSpace(rules[i].ServiceTier))
  413. if tier == "" {
  414. tier = service.OpenAIFastTierAny
  415. }
  416. rules[i].ServiceTier = tier
  417. }
  418. return &service.OpenAIFastPolicySettings{Rules: rules}
  419. }
  420. func loginAgreementDocumentsToDTO(items []service.LoginAgreementDocument) []dto.LoginAgreementDocument {
  421. result := make([]dto.LoginAgreementDocument, 0, len(items))
  422. for _, item := range items {
  423. result = append(result, dto.LoginAgreementDocument{
  424. ID: item.ID,
  425. Title: item.Title,
  426. ContentMD: item.ContentMD,
  427. })
  428. }
  429. return result
  430. }
  431. func loginAgreementDocumentsToService(items []dto.LoginAgreementDocument) []service.LoginAgreementDocument {
  432. result := make([]service.LoginAgreementDocument, 0, len(items))
  433. for _, item := range items {
  434. title := strings.TrimSpace(item.Title)
  435. content := strings.TrimSpace(item.ContentMD)
  436. if title == "" && content == "" {
  437. continue
  438. }
  439. result = append(result, service.LoginAgreementDocument{
  440. ID: strings.TrimSpace(item.ID),
  441. Title: title,
  442. ContentMD: content,
  443. })
  444. }
  445. return result
  446. }
  447. func systemSettingsResponseData(settings dto.SystemSettings, authSourceDefaults *service.AuthSourceDefaultSettings) map[string]any {
  448. data := make(map[string]any)
  449. raw, err := json.Marshal(settings)
  450. if err == nil {
  451. _ = json.Unmarshal(raw, &data)
  452. }
  453. if authSourceDefaults == nil {
  454. authSourceDefaults = &service.AuthSourceDefaultSettings{}
  455. }
  456. data["auth_source_default_email_balance"] = authSourceDefaults.Email.Balance
  457. data["auth_source_default_email_concurrency"] = authSourceDefaults.Email.Concurrency
  458. data["auth_source_default_email_subscriptions"] = authSourceDefaults.Email.Subscriptions
  459. data["auth_source_default_email_grant_on_signup"] = authSourceDefaults.Email.GrantOnSignup
  460. data["auth_source_default_email_grant_on_first_bind"] = authSourceDefaults.Email.GrantOnFirstBind
  461. data["auth_source_default_linuxdo_balance"] = authSourceDefaults.LinuxDo.Balance
  462. data["auth_source_default_linuxdo_concurrency"] = authSourceDefaults.LinuxDo.Concurrency
  463. data["auth_source_default_linuxdo_subscriptions"] = authSourceDefaults.LinuxDo.Subscriptions
  464. data["auth_source_default_linuxdo_grant_on_signup"] = authSourceDefaults.LinuxDo.GrantOnSignup
  465. data["auth_source_default_linuxdo_grant_on_first_bind"] = authSourceDefaults.LinuxDo.GrantOnFirstBind
  466. data["auth_source_default_dingtalk_balance"] = authSourceDefaults.DingTalk.Balance
  467. data["auth_source_default_dingtalk_concurrency"] = authSourceDefaults.DingTalk.Concurrency
  468. data["auth_source_default_dingtalk_subscriptions"] = authSourceDefaults.DingTalk.Subscriptions
  469. data["auth_source_default_dingtalk_grant_on_signup"] = authSourceDefaults.DingTalk.GrantOnSignup
  470. data["auth_source_default_dingtalk_grant_on_first_bind"] = authSourceDefaults.DingTalk.GrantOnFirstBind
  471. data["auth_source_default_oidc_balance"] = authSourceDefaults.OIDC.Balance
  472. data["auth_source_default_oidc_concurrency"] = authSourceDefaults.OIDC.Concurrency
  473. data["auth_source_default_oidc_subscriptions"] = authSourceDefaults.OIDC.Subscriptions
  474. data["auth_source_default_oidc_grant_on_signup"] = authSourceDefaults.OIDC.GrantOnSignup
  475. data["auth_source_default_oidc_grant_on_first_bind"] = authSourceDefaults.OIDC.GrantOnFirstBind
  476. data["auth_source_default_wechat_balance"] = authSourceDefaults.WeChat.Balance
  477. data["auth_source_default_wechat_concurrency"] = authSourceDefaults.WeChat.Concurrency
  478. data["auth_source_default_wechat_subscriptions"] = authSourceDefaults.WeChat.Subscriptions
  479. data["auth_source_default_wechat_grant_on_signup"] = authSourceDefaults.WeChat.GrantOnSignup
  480. data["auth_source_default_wechat_grant_on_first_bind"] = authSourceDefaults.WeChat.GrantOnFirstBind
  481. data["auth_source_default_github_balance"] = authSourceDefaults.GitHub.Balance
  482. data["auth_source_default_github_concurrency"] = authSourceDefaults.GitHub.Concurrency
  483. data["auth_source_default_github_subscriptions"] = authSourceDefaults.GitHub.Subscriptions
  484. data["auth_source_default_github_grant_on_signup"] = authSourceDefaults.GitHub.GrantOnSignup
  485. data["auth_source_default_github_grant_on_first_bind"] = authSourceDefaults.GitHub.GrantOnFirstBind
  486. data["auth_source_default_google_balance"] = authSourceDefaults.Google.Balance
  487. data["auth_source_default_google_concurrency"] = authSourceDefaults.Google.Concurrency
  488. data["auth_source_default_google_subscriptions"] = authSourceDefaults.Google.Subscriptions
  489. data["auth_source_default_google_grant_on_signup"] = authSourceDefaults.Google.GrantOnSignup
  490. data["auth_source_default_google_grant_on_first_bind"] = authSourceDefaults.Google.GrantOnFirstBind
  491. data["auth_source_default_email_platform_quotas"] = authSourceDefaults.Email.PlatformQuotas
  492. data["auth_source_default_linuxdo_platform_quotas"] = authSourceDefaults.LinuxDo.PlatformQuotas
  493. data["auth_source_default_oidc_platform_quotas"] = authSourceDefaults.OIDC.PlatformQuotas
  494. data["auth_source_default_wechat_platform_quotas"] = authSourceDefaults.WeChat.PlatformQuotas
  495. data["auth_source_default_github_platform_quotas"] = authSourceDefaults.GitHub.PlatformQuotas
  496. data["auth_source_default_google_platform_quotas"] = authSourceDefaults.Google.PlatformQuotas
  497. data["auth_source_default_dingtalk_platform_quotas"] = authSourceDefaults.DingTalk.PlatformQuotas
  498. data["force_email_on_third_party_signup"] = authSourceDefaults.ForceEmailOnThirdPartySignup
  499. return data
  500. }