Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,530 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// semverPattern 预编译 semver 格式校验正则
|
||||
var semverPattern = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
|
||||
|
||||
// menuItemIDPattern validates custom menu item IDs: alphanumeric, hyphens, underscores only.
|
||||
var menuItemIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
||||
|
||||
// generateMenuItemID generates a short random hex ID for a custom menu item.
|
||||
func generateMenuItemID() (string, error) {
|
||||
b := make([]byte, 8)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("generate menu item ID: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func scopesContainOpenID(scopes string) bool {
|
||||
for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
|
||||
if scope == "openid" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SettingHandler 系统设置处理器
|
||||
type SettingHandler struct {
|
||||
settingService *service.SettingService
|
||||
emailService *service.EmailService
|
||||
turnstileService *service.TurnstileService
|
||||
aliyunCaptchaService *service.AliyunCaptchaService
|
||||
opsService *service.OpsService
|
||||
paymentConfigService *service.PaymentConfigService
|
||||
paymentService *service.PaymentService
|
||||
userAttributeService *service.UserAttributeService
|
||||
notificationEmailService *service.NotificationEmailService
|
||||
totpService *service.TotpService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewSettingHandler 创建系统设置处理器
|
||||
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService, paymentConfigService *service.PaymentConfigService, paymentService *service.PaymentService, userAttributeService *service.UserAttributeService) *SettingHandler {
|
||||
return &SettingHandler{
|
||||
settingService: settingService,
|
||||
emailService: emailService,
|
||||
turnstileService: turnstileService,
|
||||
opsService: opsService,
|
||||
paymentConfigService: paymentConfigService,
|
||||
paymentService: paymentService,
|
||||
userAttributeService: userAttributeService,
|
||||
}
|
||||
}
|
||||
|
||||
// SetNotificationEmailService attaches the notification template service without changing
|
||||
// the constructor signature used by existing unit tests.
|
||||
func (h *SettingHandler) SetNotificationEmailService(notificationEmailService *service.NotificationEmailService) {
|
||||
h.notificationEmailService = notificationEmailService
|
||||
}
|
||||
|
||||
// SetAliyunCaptchaService attaches the Aliyun captcha credential validator without
|
||||
// changing the constructor signature used by existing unit tests.
|
||||
func (h *SettingHandler) SetAliyunCaptchaService(aliyunCaptchaService *service.AliyunCaptchaService) {
|
||||
h.aliyunCaptchaService = aliyunCaptchaService
|
||||
}
|
||||
|
||||
// SetStepUpDeps attaches the services backing the step-up switch preconditions
|
||||
// (enable requires the acting admin to have TOTP enabled; disable is itself a
|
||||
// step-up gated operation), without changing the constructor signature used by
|
||||
// existing unit tests.
|
||||
func (h *SettingHandler) SetStepUpDeps(totpService *service.TotpService, userService *service.UserService) {
|
||||
h.totpService = totpService
|
||||
h.userService = userService
|
||||
}
|
||||
|
||||
// GetSettings 获取所有系统设置
|
||||
// GET /api/v1/admin/settings
|
||||
func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
settings, err := h.settingService.GetAllSettings(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
authSourceDefaults, err := h.settingService.GetAuthSourceDefaultSettings(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if ops monitoring is enabled (respects config.ops.enabled)
|
||||
opsEnabled := h.opsService != nil && h.opsService.IsMonitoringEnabled(c.Request.Context())
|
||||
defaultSubscriptions := make([]dto.DefaultSubscriptionSetting, 0, len(settings.DefaultSubscriptions))
|
||||
for _, sub := range settings.DefaultSubscriptions {
|
||||
defaultSubscriptions = append(defaultSubscriptions, dto.DefaultSubscriptionSetting{
|
||||
GroupID: sub.GroupID,
|
||||
ValidityDays: sub.ValidityDays,
|
||||
})
|
||||
}
|
||||
|
||||
// Load payment config
|
||||
var paymentCfg *service.PaymentConfig
|
||||
if h.paymentConfigService != nil {
|
||||
paymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context())
|
||||
}
|
||||
if paymentCfg == nil {
|
||||
paymentCfg = &service.PaymentConfig{}
|
||||
}
|
||||
passkeyConfigured, passkeyRPID, passkeyRPOrigins := h.settingService.PasskeyConfiguration()
|
||||
|
||||
payload := dto.SystemSettings{
|
||||
RegistrationEnabled: settings.RegistrationEnabled,
|
||||
EmailVerifyEnabled: settings.EmailVerifyEnabled,
|
||||
RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
|
||||
RegistrationEmailDomainQuotaEnabled: settings.RegistrationEmailDomainQuotaEnabled,
|
||||
PromoCodeEnabled: settings.PromoCodeEnabled,
|
||||
PasswordResetEnabled: settings.PasswordResetEnabled,
|
||||
FrontendURL: settings.FrontendURL,
|
||||
InvitationCodeEnabled: settings.InvitationCodeEnabled,
|
||||
TotpEnabled: settings.TotpEnabled,
|
||||
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
||||
PasskeyEnabled: settings.PasskeyEnabled,
|
||||
PasskeyConfigured: passkeyConfigured,
|
||||
PasskeyRPID: passkeyRPID,
|
||||
PasskeyRPOrigins: passkeyRPOrigins,
|
||||
SessionBindingEnabled: settings.SessionBindingEnabled,
|
||||
StepUpEnabled: settings.StepUpEnabled,
|
||||
AuditLogRetentionDays: settings.AuditLogRetentionDays,
|
||||
LoginAgreementEnabled: settings.LoginAgreementEnabled,
|
||||
LoginAgreementMode: settings.LoginAgreementMode,
|
||||
LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt,
|
||||
LoginAgreementDocuments: loginAgreementDocumentsToDTO(settings.LoginAgreementDocuments),
|
||||
SMTPHost: settings.SMTPHost,
|
||||
SMTPPort: settings.SMTPPort,
|
||||
SMTPUsername: settings.SMTPUsername,
|
||||
SMTPPasswordConfigured: settings.SMTPPasswordConfigured,
|
||||
SMTPFrom: settings.SMTPFrom,
|
||||
SMTPFromName: settings.SMTPFromName,
|
||||
SMTPUseTLS: settings.SMTPUseTLS,
|
||||
TurnstileEnabled: settings.TurnstileEnabled,
|
||||
TurnstileSiteKey: settings.TurnstileSiteKey,
|
||||
TurnstileSecretKeyConfigured: settings.TurnstileSecretKeyConfigured,
|
||||
TencentCaptchaEnabled: settings.TencentCaptchaEnabled,
|
||||
TencentCaptchaAppID: settings.TencentCaptchaAppID,
|
||||
TencentCaptchaAppSecretKeyConfigured: settings.TencentCaptchaAppSecretKeyConfigured,
|
||||
TencentCaptchaCloudSecretIDConfigured: settings.TencentCaptchaCloudSecretIDConfigured,
|
||||
TencentCaptchaCloudSecretKeyConfigured: settings.TencentCaptchaCloudSecretKeyConfigured,
|
||||
TencentCaptchaRegion: settings.TencentCaptchaRegion,
|
||||
AliyunCaptchaEnabled: settings.AliyunCaptchaEnabled,
|
||||
AliyunCaptchaAccessKeyID: settings.AliyunCaptchaAccessKeyID,
|
||||
AliyunCaptchaAccessKeySecretConfigured: settings.AliyunCaptchaAccessKeySecretConfigured,
|
||||
AliyunCaptchaSceneID: settings.AliyunCaptchaSceneID,
|
||||
AliyunCaptchaPrefix: settings.AliyunCaptchaPrefix,
|
||||
AliyunCaptchaRegion: settings.AliyunCaptchaRegion,
|
||||
APIKeyACLTrustForwardedIP: settings.APIKeyACLTrustForwardedIP,
|
||||
ForwardedClientIPHeaders: settings.ForwardedClientIPHeaders,
|
||||
LinuxDoConnectEnabled: settings.LinuxDoConnectEnabled,
|
||||
LinuxDoConnectClientID: settings.LinuxDoConnectClientID,
|
||||
LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured,
|
||||
LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL,
|
||||
DingTalkConnectEnabled: settings.DingTalkConnectEnabled,
|
||||
DingTalkConnectClientID: settings.DingTalkConnectClientID,
|
||||
DingTalkConnectClientSecretConfigured: settings.DingTalkConnectClientSecretConfigured,
|
||||
DingTalkConnectRedirectURL: settings.DingTalkConnectRedirectURL,
|
||||
DingTalkConnectCorpRestrictionPolicy: settings.DingTalkConnectCorpRestrictionPolicy,
|
||||
DingTalkConnectInternalCorpID: settings.DingTalkConnectInternalCorpID,
|
||||
DingTalkConnectBypassRegistration: settings.DingTalkConnectBypassRegistration,
|
||||
DingTalkConnectSyncCorpEmail: settings.DingTalkConnectSyncCorpEmail,
|
||||
DingTalkConnectSyncDisplayName: settings.DingTalkConnectSyncDisplayName,
|
||||
DingTalkConnectSyncDept: settings.DingTalkConnectSyncDept,
|
||||
DingTalkConnectSyncCorpEmailAttrKey: settings.DingTalkConnectSyncCorpEmailAttrKey,
|
||||
DingTalkConnectSyncDisplayNameAttrKey: settings.DingTalkConnectSyncDisplayNameAttrKey,
|
||||
DingTalkConnectSyncDeptAttrKey: settings.DingTalkConnectSyncDeptAttrKey,
|
||||
DingTalkConnectSyncCorpEmailAttrName: settings.DingTalkConnectSyncCorpEmailAttrName,
|
||||
DingTalkConnectSyncDisplayNameAttrName: settings.DingTalkConnectSyncDisplayNameAttrName,
|
||||
DingTalkConnectSyncDeptAttrName: settings.DingTalkConnectSyncDeptAttrName,
|
||||
WeChatConnectEnabled: settings.WeChatConnectEnabled,
|
||||
WeChatConnectAppID: settings.WeChatConnectAppID,
|
||||
WeChatConnectAppSecretConfigured: settings.WeChatConnectAppSecretConfigured,
|
||||
WeChatConnectOpenAppID: settings.WeChatConnectOpenAppID,
|
||||
WeChatConnectOpenAppSecretConfigured: settings.WeChatConnectOpenAppSecretConfigured,
|
||||
WeChatConnectMPAppID: settings.WeChatConnectMPAppID,
|
||||
WeChatConnectMPAppSecretConfigured: settings.WeChatConnectMPAppSecretConfigured,
|
||||
WeChatConnectMobileAppID: settings.WeChatConnectMobileAppID,
|
||||
WeChatConnectMobileAppSecretConfigured: settings.WeChatConnectMobileAppSecretConfigured,
|
||||
WeChatConnectOpenEnabled: settings.WeChatConnectOpenEnabled,
|
||||
WeChatConnectMPEnabled: settings.WeChatConnectMPEnabled,
|
||||
WeChatConnectMobileEnabled: settings.WeChatConnectMobileEnabled,
|
||||
WeChatConnectMode: settings.WeChatConnectMode,
|
||||
WeChatConnectScopes: settings.WeChatConnectScopes,
|
||||
WeChatConnectRedirectURL: settings.WeChatConnectRedirectURL,
|
||||
WeChatConnectFrontendRedirectURL: settings.WeChatConnectFrontendRedirectURL,
|
||||
OIDCConnectEnabled: settings.OIDCConnectEnabled,
|
||||
OIDCConnectProviderName: settings.OIDCConnectProviderName,
|
||||
OIDCConnectClientID: settings.OIDCConnectClientID,
|
||||
OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured,
|
||||
OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL,
|
||||
OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL,
|
||||
OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL,
|
||||
OIDCConnectTokenURL: settings.OIDCConnectTokenURL,
|
||||
OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL,
|
||||
OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL,
|
||||
OIDCConnectScopes: settings.OIDCConnectScopes,
|
||||
OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL,
|
||||
OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL,
|
||||
OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod,
|
||||
OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE,
|
||||
OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken,
|
||||
OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs,
|
||||
OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds,
|
||||
OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified,
|
||||
OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath,
|
||||
OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath,
|
||||
OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath,
|
||||
GitHubOAuthEnabled: settings.GitHubOAuthEnabled,
|
||||
GitHubOAuthClientID: settings.GitHubOAuthClientID,
|
||||
GitHubOAuthClientSecretConfigured: settings.GitHubOAuthClientSecretConfigured,
|
||||
GitHubOAuthRedirectURL: settings.GitHubOAuthRedirectURL,
|
||||
GitHubOAuthFrontendRedirectURL: settings.GitHubOAuthFrontendRedirectURL,
|
||||
GoogleOAuthEnabled: settings.GoogleOAuthEnabled,
|
||||
GoogleOAuthClientID: settings.GoogleOAuthClientID,
|
||||
GoogleOAuthClientSecretConfigured: settings.GoogleOAuthClientSecretConfigured,
|
||||
GoogleOAuthRedirectURL: settings.GoogleOAuthRedirectURL,
|
||||
GoogleOAuthFrontendRedirectURL: settings.GoogleOAuthFrontendRedirectURL,
|
||||
SiteName: settings.SiteName,
|
||||
SiteLogo: settings.SiteLogo,
|
||||
SiteSubtitle: settings.SiteSubtitle,
|
||||
APIBaseURL: settings.APIBaseURL,
|
||||
ContactInfo: settings.ContactInfo,
|
||||
DocURL: settings.DocURL,
|
||||
HomeContent: settings.HomeContent,
|
||||
CompactHomeEnabled: settings.CompactHomeEnabled,
|
||||
HideCcsImportButton: settings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||
TableDefaultPageSize: settings.TableDefaultPageSize,
|
||||
TablePageSizeOptions: settings.TablePageSizeOptions,
|
||||
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
|
||||
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
|
||||
DefaultConcurrency: settings.DefaultConcurrency,
|
||||
DefaultBalance: settings.DefaultBalance,
|
||||
RiskControlEnabled: settings.RiskControlEnabled,
|
||||
CyberSessionBlockEnabled: settings.CyberSessionBlockEnabled,
|
||||
CyberSessionBlockTTLSeconds: settings.CyberSessionBlockTTLSeconds,
|
||||
AffiliateRebateRate: settings.AffiliateRebateRate,
|
||||
AffiliateRebateFreezeHours: settings.AffiliateRebateFreezeHours,
|
||||
AffiliateRebateDurationDays: settings.AffiliateRebateDurationDays,
|
||||
AffiliateRebatePerInviteeCap: settings.AffiliateRebatePerInviteeCap,
|
||||
AdminRechargeRebateEnabled: settings.AdminRechargeRebateEnabled,
|
||||
DefaultUserRPMLimit: settings.DefaultUserRPMLimit,
|
||||
DefaultSubscriptions: defaultSubscriptions,
|
||||
EnableModelFallback: settings.EnableModelFallback,
|
||||
FallbackModelAnthropic: settings.FallbackModelAnthropic,
|
||||
FallbackModelOpenAI: settings.FallbackModelOpenAI,
|
||||
FallbackModelGemini: settings.FallbackModelGemini,
|
||||
FallbackModelAntigravity: settings.FallbackModelAntigravity,
|
||||
EnableIdentityPatch: settings.EnableIdentityPatch,
|
||||
IdentityPatchPrompt: settings.IdentityPatchPrompt,
|
||||
OpsMonitoringEnabled: opsEnabled && settings.OpsMonitoringEnabled,
|
||||
OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
|
||||
OpsQueryModeDefault: settings.OpsQueryModeDefault,
|
||||
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
|
||||
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
|
||||
MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
|
||||
BackendModeEnabled: settings.BackendModeEnabled,
|
||||
EnableFingerprintUnification: settings.EnableFingerprintUnification,
|
||||
EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
|
||||
EnableCCHSigning: settings.EnableCCHSigning,
|
||||
EnableClaudeOAuthSystemPromptInjection: settings.EnableClaudeOAuthSystemPromptInjection,
|
||||
ClaudeOAuthSystemPrompt: settings.ClaudeOAuthSystemPrompt,
|
||||
ClaudeOAuthSystemPromptBlocks: settings.ClaudeOAuthSystemPromptBlocks,
|
||||
EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
|
||||
RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
|
||||
EnableClientDatelineNormalization: settings.EnableClientDatelineNormalization,
|
||||
AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
|
||||
OpenAICodexUserAgent: settings.OpenAICodexUserAgent,
|
||||
OpenAICodexClientVersion: settings.OpenAICodexClientVersion,
|
||||
OpenAICodexClientVersionSynced: settings.OpenAICodexClientVersionSynced,
|
||||
OpenAICodexVersionAutoSyncEnabled: settings.OpenAICodexVersionAutoSyncEnabled,
|
||||
MinCodexVersion: settings.MinCodexVersion,
|
||||
MaxCodexVersion: settings.MaxCodexVersion,
|
||||
CodexCLIOnlyBlacklist: settings.CodexCLIOnlyBlacklist,
|
||||
CodexCLIOnlyWhitelist: settings.CodexCLIOnlyWhitelist,
|
||||
CodexCLIOnlyAllowAppServerClients: settings.CodexCLIOnlyAllowAppServerClients,
|
||||
CodexCLIOnlyEngineFingerprintSignals: settings.CodexCLIOnlyEngineFingerprintSignals,
|
||||
WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
|
||||
PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
|
||||
PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
|
||||
PaymentVisibleMethodAlipayEnabled: settings.PaymentVisibleMethodAlipayEnabled,
|
||||
PaymentVisibleMethodWxpayEnabled: settings.PaymentVisibleMethodWxpayEnabled,
|
||||
OpenAILowUpstreamRatePriorityEnabled: settings.OpenAILowUpstreamRatePriorityEnabled,
|
||||
OpenAIOAuthSchedulingRateMultiplier: settings.OpenAIOAuthSchedulingRateMultiplier,
|
||||
OpenAIAdvancedSchedulerEnabled: settings.OpenAIAdvancedSchedulerEnabled,
|
||||
OpenAIAdvancedSchedulerStickyWeightedEnabled: settings.OpenAIAdvancedSchedulerStickyWeightedEnabled,
|
||||
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: settings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled,
|
||||
OpenAIAdvancedSchedulerLBTopK: settings.OpenAIAdvancedSchedulerLBTopK,
|
||||
OpenAIAdvancedSchedulerWeightPriority: settings.OpenAIAdvancedSchedulerWeightPriority,
|
||||
OpenAIAdvancedSchedulerWeightLoad: settings.OpenAIAdvancedSchedulerWeightLoad,
|
||||
OpenAIAdvancedSchedulerWeightQueue: settings.OpenAIAdvancedSchedulerWeightQueue,
|
||||
OpenAIAdvancedSchedulerWeightErrorRate: settings.OpenAIAdvancedSchedulerWeightErrorRate,
|
||||
OpenAIAdvancedSchedulerWeightTTFT: settings.OpenAIAdvancedSchedulerWeightTTFT,
|
||||
OpenAIAdvancedSchedulerWeightReset: settings.OpenAIAdvancedSchedulerWeightReset,
|
||||
OpenAIAdvancedSchedulerWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerWeightQuotaHeadroom,
|
||||
OpenAIAdvancedSchedulerWeightUpstreamCost: settings.OpenAIAdvancedSchedulerWeightUpstreamCost,
|
||||
OpenAIAdvancedSchedulerWeightPreviousResponse: settings.OpenAIAdvancedSchedulerWeightPreviousResponse,
|
||||
OpenAIAdvancedSchedulerWeightSessionSticky: settings.OpenAIAdvancedSchedulerWeightSessionSticky,
|
||||
OpenAIAdvancedSchedulerEffectiveLBTopK: settings.OpenAIAdvancedSchedulerEffectiveLBTopK,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightPriority: settings.OpenAIAdvancedSchedulerEffectiveWeightPriority,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightLoad: settings.OpenAIAdvancedSchedulerEffectiveWeightLoad,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightQueue: settings.OpenAIAdvancedSchedulerEffectiveWeightQueue,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightErrorRate: settings.OpenAIAdvancedSchedulerEffectiveWeightErrorRate,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightTTFT: settings.OpenAIAdvancedSchedulerEffectiveWeightTTFT,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightReset: settings.OpenAIAdvancedSchedulerEffectiveWeightReset,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost: settings.OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse: settings.OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse,
|
||||
OpenAIAdvancedSchedulerEffectiveWeightSessionSticky: settings.OpenAIAdvancedSchedulerEffectiveWeightSessionSticky,
|
||||
BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled,
|
||||
BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold,
|
||||
BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL,
|
||||
SubscriptionExpiryNotifyEnabled: settings.SubscriptionExpiryNotifyEnabled,
|
||||
AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled,
|
||||
AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(settings.AccountQuotaNotifyEmails),
|
||||
PaymentEnabled: paymentCfg.Enabled,
|
||||
PaymentMinAmount: paymentCfg.MinAmount,
|
||||
PaymentMaxAmount: paymentCfg.MaxAmount,
|
||||
PaymentDailyLimit: paymentCfg.DailyLimit,
|
||||
PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin,
|
||||
PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders,
|
||||
PaymentEnabledTypes: paymentCfg.EnabledTypes,
|
||||
PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
|
||||
PaymentBalanceRechargeMultiplier: paymentCfg.BalanceRechargeMultiplier,
|
||||
PaymentSubscriptionUSDToCNYRate: paymentCfg.SubscriptionUSDToCNYRate,
|
||||
PaymentRechargeFeeRate: paymentCfg.RechargeFeeRate,
|
||||
PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
|
||||
PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
|
||||
PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
|
||||
PaymentHelpImageURL: paymentCfg.HelpImageURL,
|
||||
PaymentHelpText: paymentCfg.HelpText,
|
||||
PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled,
|
||||
PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax,
|
||||
PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
|
||||
PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
|
||||
PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
|
||||
PaymentAlipayForceQRCode: paymentCfg.AlipayForceQRCode,
|
||||
PaymentAlipayMobilePrecreateDeepLink: paymentCfg.AlipayMobilePrecreateDeepLink,
|
||||
|
||||
ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
|
||||
ChannelMonitorMode: settings.ChannelMonitorMode,
|
||||
ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
|
||||
ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput,
|
||||
ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota,
|
||||
|
||||
GrokDefaultTextModel: settings.GrokDefaultTextModel,
|
||||
GrokCrossClientModelMapEnabled: settings.GrokCrossClientModelMapEnabled,
|
||||
GrokDefaultBaseURLMode: settings.GrokDefaultBaseURLMode,
|
||||
|
||||
AvailableChannelsEnabled: settings.AvailableChannelsEnabled,
|
||||
|
||||
ModelPlazaEnabled: settings.ModelPlazaEnabled,
|
||||
ModelPlazaRequireAuth: settings.ModelPlazaRequireAuth,
|
||||
ModelPlazaDescription: settings.ModelPlazaDescription,
|
||||
|
||||
AffiliateEnabled: settings.AffiliateEnabled,
|
||||
|
||||
AccountSchedulingThresholds: settings.AccountSchedulingThresholds,
|
||||
AllowUserViewErrorRequests: settings.AllowUserViewErrorRequests,
|
||||
}
|
||||
|
||||
// OpenAI fast policy (stored under a dedicated setting key)
|
||||
if fastPolicy, err := h.settingService.GetOpenAIFastPolicySettings(c.Request.Context()); err != nil {
|
||||
slog.Error("openai_fast_policy_settings_get_failed", "error", err)
|
||||
} else if fastPolicy != nil {
|
||||
payload.OpenAIFastPolicySettings = openaiFastPolicySettingsToDTO(fastPolicy)
|
||||
}
|
||||
|
||||
// Default platform quotas(JSON map)
|
||||
if platformQuotas, err := h.settingService.GetDefaultPlatformQuotas(c.Request.Context()); err != nil {
|
||||
slog.Error("default_platform_quotas_get_failed", "error", err)
|
||||
} else {
|
||||
payload.DefaultPlatformQuotas = platformQuotas
|
||||
}
|
||||
|
||||
response.Success(c, systemSettingsResponseData(payload, authSourceDefaults))
|
||||
}
|
||||
|
||||
// openaiFastPolicySettingsToDTO converts service -> dto for OpenAI fast policy.
|
||||
func openaiFastPolicySettingsToDTO(s *service.OpenAIFastPolicySettings) *dto.OpenAIFastPolicySettings {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
rules := make([]dto.OpenAIFastPolicyRule, len(s.Rules))
|
||||
for i, r := range s.Rules {
|
||||
rules[i] = dto.OpenAIFastPolicyRule(r)
|
||||
}
|
||||
return &dto.OpenAIFastPolicySettings{Rules: rules}
|
||||
}
|
||||
|
||||
// openaiFastPolicySettingsFromDTO converts dto -> service for OpenAI fast policy.
|
||||
//
|
||||
// 规范化 ServiceTier:在 DTO 进入 service 层之前统一把空字符串归一为
|
||||
// service.OpenAIFastTierAny ("all"),避免管理员保存时空串与 "all" 同时
|
||||
// 表达"匹配任意 tier"造成数据库取值的二义性。其它非空值原样透传,由
|
||||
// service.SetOpenAIFastPolicySettings 负责合法值校验。
|
||||
func openaiFastPolicySettingsFromDTO(s *dto.OpenAIFastPolicySettings) *service.OpenAIFastPolicySettings {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
rules := make([]service.OpenAIFastPolicyRule, len(s.Rules))
|
||||
for i, r := range s.Rules {
|
||||
rules[i] = service.OpenAIFastPolicyRule(r)
|
||||
tier := strings.ToLower(strings.TrimSpace(rules[i].ServiceTier))
|
||||
if tier == "" {
|
||||
tier = service.OpenAIFastTierAny
|
||||
}
|
||||
rules[i].ServiceTier = tier
|
||||
}
|
||||
return &service.OpenAIFastPolicySettings{Rules: rules}
|
||||
}
|
||||
|
||||
func loginAgreementDocumentsToDTO(items []service.LoginAgreementDocument) []dto.LoginAgreementDocument {
|
||||
result := make([]dto.LoginAgreementDocument, 0, len(items))
|
||||
for _, item := range items {
|
||||
result = append(result, dto.LoginAgreementDocument{
|
||||
ID: item.ID,
|
||||
Title: item.Title,
|
||||
ContentMD: item.ContentMD,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loginAgreementDocumentsToService(items []dto.LoginAgreementDocument) []service.LoginAgreementDocument {
|
||||
result := make([]service.LoginAgreementDocument, 0, len(items))
|
||||
for _, item := range items {
|
||||
title := strings.TrimSpace(item.Title)
|
||||
content := strings.TrimSpace(item.ContentMD)
|
||||
if title == "" && content == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, service.LoginAgreementDocument{
|
||||
ID: strings.TrimSpace(item.ID),
|
||||
Title: title,
|
||||
ContentMD: content,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func systemSettingsResponseData(settings dto.SystemSettings, authSourceDefaults *service.AuthSourceDefaultSettings) map[string]any {
|
||||
data := make(map[string]any)
|
||||
raw, err := json.Marshal(settings)
|
||||
if err == nil {
|
||||
_ = json.Unmarshal(raw, &data)
|
||||
}
|
||||
if authSourceDefaults == nil {
|
||||
authSourceDefaults = &service.AuthSourceDefaultSettings{}
|
||||
}
|
||||
|
||||
data["auth_source_default_email_balance"] = authSourceDefaults.Email.Balance
|
||||
data["auth_source_default_email_concurrency"] = authSourceDefaults.Email.Concurrency
|
||||
data["auth_source_default_email_subscriptions"] = authSourceDefaults.Email.Subscriptions
|
||||
data["auth_source_default_email_grant_on_signup"] = authSourceDefaults.Email.GrantOnSignup
|
||||
data["auth_source_default_email_grant_on_first_bind"] = authSourceDefaults.Email.GrantOnFirstBind
|
||||
data["auth_source_default_linuxdo_balance"] = authSourceDefaults.LinuxDo.Balance
|
||||
data["auth_source_default_linuxdo_concurrency"] = authSourceDefaults.LinuxDo.Concurrency
|
||||
data["auth_source_default_linuxdo_subscriptions"] = authSourceDefaults.LinuxDo.Subscriptions
|
||||
data["auth_source_default_linuxdo_grant_on_signup"] = authSourceDefaults.LinuxDo.GrantOnSignup
|
||||
data["auth_source_default_linuxdo_grant_on_first_bind"] = authSourceDefaults.LinuxDo.GrantOnFirstBind
|
||||
data["auth_source_default_dingtalk_balance"] = authSourceDefaults.DingTalk.Balance
|
||||
data["auth_source_default_dingtalk_concurrency"] = authSourceDefaults.DingTalk.Concurrency
|
||||
data["auth_source_default_dingtalk_subscriptions"] = authSourceDefaults.DingTalk.Subscriptions
|
||||
data["auth_source_default_dingtalk_grant_on_signup"] = authSourceDefaults.DingTalk.GrantOnSignup
|
||||
data["auth_source_default_dingtalk_grant_on_first_bind"] = authSourceDefaults.DingTalk.GrantOnFirstBind
|
||||
data["auth_source_default_oidc_balance"] = authSourceDefaults.OIDC.Balance
|
||||
data["auth_source_default_oidc_concurrency"] = authSourceDefaults.OIDC.Concurrency
|
||||
data["auth_source_default_oidc_subscriptions"] = authSourceDefaults.OIDC.Subscriptions
|
||||
data["auth_source_default_oidc_grant_on_signup"] = authSourceDefaults.OIDC.GrantOnSignup
|
||||
data["auth_source_default_oidc_grant_on_first_bind"] = authSourceDefaults.OIDC.GrantOnFirstBind
|
||||
data["auth_source_default_wechat_balance"] = authSourceDefaults.WeChat.Balance
|
||||
data["auth_source_default_wechat_concurrency"] = authSourceDefaults.WeChat.Concurrency
|
||||
data["auth_source_default_wechat_subscriptions"] = authSourceDefaults.WeChat.Subscriptions
|
||||
data["auth_source_default_wechat_grant_on_signup"] = authSourceDefaults.WeChat.GrantOnSignup
|
||||
data["auth_source_default_wechat_grant_on_first_bind"] = authSourceDefaults.WeChat.GrantOnFirstBind
|
||||
data["auth_source_default_github_balance"] = authSourceDefaults.GitHub.Balance
|
||||
data["auth_source_default_github_concurrency"] = authSourceDefaults.GitHub.Concurrency
|
||||
data["auth_source_default_github_subscriptions"] = authSourceDefaults.GitHub.Subscriptions
|
||||
data["auth_source_default_github_grant_on_signup"] = authSourceDefaults.GitHub.GrantOnSignup
|
||||
data["auth_source_default_github_grant_on_first_bind"] = authSourceDefaults.GitHub.GrantOnFirstBind
|
||||
data["auth_source_default_google_balance"] = authSourceDefaults.Google.Balance
|
||||
data["auth_source_default_google_concurrency"] = authSourceDefaults.Google.Concurrency
|
||||
data["auth_source_default_google_subscriptions"] = authSourceDefaults.Google.Subscriptions
|
||||
data["auth_source_default_google_grant_on_signup"] = authSourceDefaults.Google.GrantOnSignup
|
||||
data["auth_source_default_google_grant_on_first_bind"] = authSourceDefaults.Google.GrantOnFirstBind
|
||||
data["auth_source_default_email_platform_quotas"] = authSourceDefaults.Email.PlatformQuotas
|
||||
data["auth_source_default_linuxdo_platform_quotas"] = authSourceDefaults.LinuxDo.PlatformQuotas
|
||||
data["auth_source_default_oidc_platform_quotas"] = authSourceDefaults.OIDC.PlatformQuotas
|
||||
data["auth_source_default_wechat_platform_quotas"] = authSourceDefaults.WeChat.PlatformQuotas
|
||||
data["auth_source_default_github_platform_quotas"] = authSourceDefaults.GitHub.PlatformQuotas
|
||||
data["auth_source_default_google_platform_quotas"] = authSourceDefaults.Google.PlatformQuotas
|
||||
data["auth_source_default_dingtalk_platform_quotas"] = authSourceDefaults.DingTalk.PlatformQuotas
|
||||
data["force_email_on_third_party_signup"] = authSourceDefaults.ForceEmailOnThirdPartySignup
|
||||
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user