package admin import ( "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" ) // GetAdminAPIKey 获取管理员 API Key 状态 // GET /api/v1/admin/settings/admin-api-key func (h *SettingHandler) GetAdminAPIKey(c *gin.Context) { maskedKey, exists, err := h.settingService.GetAdminAPIKeyStatus(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, gin.H{ "exists": exists, "masked_key": maskedKey, }) } // RegenerateAdminAPIKey 生成/重新生成管理员 API Key // POST /api/v1/admin/settings/admin-api-key/regenerate func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) { key, err := h.settingService.GenerateAdminAPIKey(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, gin.H{ "key": key, // 完整 key 只在生成时返回一次 }) } // DeleteAdminAPIKey 删除管理员 API Key // DELETE /api/v1/admin/settings/admin-api-key func (h *SettingHandler) DeleteAdminAPIKey(c *gin.Context) { if err := h.settingService.DeleteAdminAPIKey(c.Request.Context()); err != nil { response.ErrorFrom(c, err) return } response.Success(c, gin.H{"message": "Admin API key deleted"}) } // GetOverloadCooldownSettings 获取529过载冷却配置 // GET /api/v1/admin/settings/overload-cooldown func (h *SettingHandler) GetOverloadCooldownSettings(c *gin.Context) { settings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.OverloadCooldownSettings{ Enabled: settings.Enabled, CooldownMinutes: settings.CooldownMinutes, }) } // UpdateOverloadCooldownSettingsRequest 更新529过载冷却配置请求 type UpdateOverloadCooldownSettingsRequest struct { Enabled bool `json:"enabled"` CooldownMinutes int `json:"cooldown_minutes"` } // UpdateOverloadCooldownSettings 更新529过载冷却配置 // PUT /api/v1/admin/settings/overload-cooldown func (h *SettingHandler) UpdateOverloadCooldownSettings(c *gin.Context) { var req UpdateOverloadCooldownSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } settings := &service.OverloadCooldownSettings{ Enabled: req.Enabled, CooldownMinutes: req.CooldownMinutes, } if err := h.settingService.SetOverloadCooldownSettings(c.Request.Context(), settings); err != nil { response.BadRequest(c, err.Error()) return } updatedSettings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.OverloadCooldownSettings{ Enabled: updatedSettings.Enabled, CooldownMinutes: updatedSettings.CooldownMinutes, }) } // GetRateLimit429CooldownSettings 获取429默认回避配置 // GET /api/v1/admin/settings/rate-limit-429-cooldown func (h *SettingHandler) GetRateLimit429CooldownSettings(c *gin.Context) { settings, err := h.settingService.GetRateLimit429CooldownSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.RateLimit429CooldownSettings{ Enabled: settings.Enabled, CooldownSeconds: settings.CooldownSeconds, }) } // UpdateRateLimit429CooldownSettingsRequest 更新429默认回避配置请求 type UpdateRateLimit429CooldownSettingsRequest struct { Enabled bool `json:"enabled"` CooldownSeconds int `json:"cooldown_seconds"` } // UpdateRateLimit429CooldownSettings 更新429默认回避配置 // PUT /api/v1/admin/settings/rate-limit-429-cooldown func (h *SettingHandler) UpdateRateLimit429CooldownSettings(c *gin.Context) { var req UpdateRateLimit429CooldownSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } settings := &service.RateLimit429CooldownSettings{ Enabled: req.Enabled, CooldownSeconds: req.CooldownSeconds, } if err := h.settingService.SetRateLimit429CooldownSettings(c.Request.Context(), settings); err != nil { response.BadRequest(c, err.Error()) return } updatedSettings, err := h.settingService.GetRateLimit429CooldownSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.RateLimit429CooldownSettings{ Enabled: updatedSettings.Enabled, CooldownSeconds: updatedSettings.CooldownSeconds, }) } // GetPanelRateLimitSettings 获取面板 API 限流配置 // GET /api/v1/admin/settings/panel-rate-limit func (h *SettingHandler) GetPanelRateLimitSettings(c *gin.Context) { settings, err := h.settingService.GetPanelRateLimitSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.PanelRateLimitSettings{ Enabled: settings.Enabled, UserRPM: settings.UserRPM, HeavyRPM: settings.HeavyRPM, ExemptAdmin: settings.ExemptAdmin, PublicIPRPM: settings.PublicIPRPM, }) } // UpdatePanelRateLimitSettingsRequest 更新面板 API 限流配置请求 type UpdatePanelRateLimitSettingsRequest struct { Enabled bool `json:"enabled"` UserRPM int `json:"user_rpm"` HeavyRPM int `json:"heavy_rpm"` ExemptAdmin bool `json:"exempt_admin"` PublicIPRPM int `json:"public_ip_rpm"` } // UpdatePanelRateLimitSettings 更新面板 API 限流配置 // PUT /api/v1/admin/settings/panel-rate-limit func (h *SettingHandler) UpdatePanelRateLimitSettings(c *gin.Context) { var req UpdatePanelRateLimitSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } settings := &service.PanelRateLimitSettings{ Enabled: req.Enabled, UserRPM: req.UserRPM, HeavyRPM: req.HeavyRPM, ExemptAdmin: req.ExemptAdmin, PublicIPRPM: req.PublicIPRPM, } if err := h.settingService.SetPanelRateLimitSettings(c.Request.Context(), settings); err != nil { response.BadRequest(c, err.Error()) return } updatedSettings, err := h.settingService.GetPanelRateLimitSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.PanelRateLimitSettings{ Enabled: updatedSettings.Enabled, UserRPM: updatedSettings.UserRPM, HeavyRPM: updatedSettings.HeavyRPM, ExemptAdmin: updatedSettings.ExemptAdmin, PublicIPRPM: updatedSettings.PublicIPRPM, }) } // GetStreamTimeoutSettings 获取流超时处理配置 // GET /api/v1/admin/settings/stream-timeout func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) { settings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.StreamTimeoutSettings{ Enabled: settings.Enabled, Action: settings.Action, TempUnschedMinutes: settings.TempUnschedMinutes, ThresholdCount: settings.ThresholdCount, ThresholdWindowMinutes: settings.ThresholdWindowMinutes, }) } // GetRectifierSettings 获取请求整流器配置 // GET /api/v1/admin/settings/rectifier func (h *SettingHandler) GetRectifierSettings(c *gin.Context) { settings, err := h.settingService.GetRectifierSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } patterns := settings.APIKeySignaturePatterns if patterns == nil { patterns = []string{} } response.Success(c, dto.RectifierSettings{ Enabled: settings.Enabled, ThinkingSignatureEnabled: settings.ThinkingSignatureEnabled, ThinkingBudgetEnabled: settings.ThinkingBudgetEnabled, APIKeySignatureEnabled: settings.APIKeySignatureEnabled, APIKeySignaturePatterns: patterns, }) } // UpdateRectifierSettingsRequest 更新整流器配置请求 type UpdateRectifierSettingsRequest struct { Enabled bool `json:"enabled"` ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"` ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"` APIKeySignatureEnabled bool `json:"apikey_signature_enabled"` APIKeySignaturePatterns []string `json:"apikey_signature_patterns"` } // UpdateRectifierSettings 更新请求整流器配置 // PUT /api/v1/admin/settings/rectifier func (h *SettingHandler) UpdateRectifierSettings(c *gin.Context) { var req UpdateRectifierSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } // 校验并清理自定义匹配关键词 const maxPatterns = 50 const maxPatternLen = 500 if len(req.APIKeySignaturePatterns) > maxPatterns { response.BadRequest(c, "Too many signature patterns (max 50)") return } var cleanedPatterns []string for _, p := range req.APIKeySignaturePatterns { p = strings.TrimSpace(p) if p == "" { continue } if len(p) > maxPatternLen { response.BadRequest(c, "Signature pattern too long (max 500 characters)") return } cleanedPatterns = append(cleanedPatterns, p) } settings := &service.RectifierSettings{ Enabled: req.Enabled, ThinkingSignatureEnabled: req.ThinkingSignatureEnabled, ThinkingBudgetEnabled: req.ThinkingBudgetEnabled, APIKeySignatureEnabled: req.APIKeySignatureEnabled, APIKeySignaturePatterns: cleanedPatterns, } if err := h.settingService.SetRectifierSettings(c.Request.Context(), settings); err != nil { response.BadRequest(c, err.Error()) return } // 重新获取设置返回 updatedSettings, err := h.settingService.GetRectifierSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } updatedPatterns := updatedSettings.APIKeySignaturePatterns if updatedPatterns == nil { updatedPatterns = []string{} } response.Success(c, dto.RectifierSettings{ Enabled: updatedSettings.Enabled, ThinkingSignatureEnabled: updatedSettings.ThinkingSignatureEnabled, ThinkingBudgetEnabled: updatedSettings.ThinkingBudgetEnabled, APIKeySignatureEnabled: updatedSettings.APIKeySignatureEnabled, APIKeySignaturePatterns: updatedPatterns, }) } // GetBetaPolicySettings 获取 Beta 策略配置 // GET /api/v1/admin/settings/beta-policy func (h *SettingHandler) GetBetaPolicySettings(c *gin.Context) { settings, err := h.settingService.GetBetaPolicySettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } rules := make([]dto.BetaPolicyRule, len(settings.Rules)) for i, r := range settings.Rules { rules[i] = dto.BetaPolicyRule(r) } response.Success(c, dto.BetaPolicySettings{Rules: rules}) } // UpdateBetaPolicySettingsRequest 更新 Beta 策略配置请求 type UpdateBetaPolicySettingsRequest struct { Rules []dto.BetaPolicyRule `json:"rules"` } // UpdateBetaPolicySettings 更新 Beta 策略配置 // PUT /api/v1/admin/settings/beta-policy func (h *SettingHandler) UpdateBetaPolicySettings(c *gin.Context) { var req UpdateBetaPolicySettingsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } rules := make([]service.BetaPolicyRule, len(req.Rules)) for i, r := range req.Rules { rules[i] = service.BetaPolicyRule(r) } settings := &service.BetaPolicySettings{Rules: rules} if err := h.settingService.SetBetaPolicySettings(c.Request.Context(), settings); err != nil { response.BadRequest(c, err.Error()) return } // Re-fetch to return updated settings updated, err := h.settingService.GetBetaPolicySettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } outRules := make([]dto.BetaPolicyRule, len(updated.Rules)) for i, r := range updated.Rules { outRules[i] = dto.BetaPolicyRule(r) } response.Success(c, dto.BetaPolicySettings{Rules: outRules}) } // UpdateStreamTimeoutSettingsRequest 更新流超时配置请求 type UpdateStreamTimeoutSettingsRequest struct { Enabled bool `json:"enabled"` Action string `json:"action"` TempUnschedMinutes int `json:"temp_unsched_minutes"` ThresholdCount int `json:"threshold_count"` ThresholdWindowMinutes int `json:"threshold_window_minutes"` } // UpdateStreamTimeoutSettings 更新流超时处理配置 // PUT /api/v1/admin/settings/stream-timeout func (h *SettingHandler) UpdateStreamTimeoutSettings(c *gin.Context) { var req UpdateStreamTimeoutSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } settings := &service.StreamTimeoutSettings{ Enabled: req.Enabled, Action: req.Action, TempUnschedMinutes: req.TempUnschedMinutes, ThresholdCount: req.ThresholdCount, ThresholdWindowMinutes: req.ThresholdWindowMinutes, } if err := h.settingService.SetStreamTimeoutSettings(c.Request.Context(), settings); err != nil { response.BadRequest(c, err.Error()) return } // 重新获取设置返回 updatedSettings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, dto.StreamTimeoutSettings{ Enabled: updatedSettings.Enabled, Action: updatedSettings.Action, TempUnschedMinutes: updatedSettings.TempUnschedMinutes, ThresholdCount: updatedSettings.ThresholdCount, ThresholdWindowMinutes: updatedSettings.ThresholdWindowMinutes, }) } // GetWebSearchEmulationConfig 获取 Web Search 模拟配置 // GET /api/v1/admin/settings/web-search-emulation func (h *SettingHandler) GetWebSearchEmulationConfig(c *gin.Context) { cfg, err := h.settingService.GetWebSearchEmulationConfig(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), cfg)) } // UpdateWebSearchEmulationConfig 更新 Web Search 模拟配置 // PUT /api/v1/admin/settings/web-search-emulation func (h *SettingHandler) UpdateWebSearchEmulationConfig(c *gin.Context) { var cfg service.WebSearchEmulationConfig if err := c.ShouldBindJSON(&cfg); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } if err := h.settingService.SaveWebSearchEmulationConfig(c.Request.Context(), &cfg); err != nil { response.ErrorFrom(c, err) return } // Re-read (with sanitized api keys) to return current state updated, err := h.settingService.GetWebSearchEmulationConfig(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), updated)) } // ResetWebSearchUsage 重置指定 provider 的配额用量 // POST /api/v1/admin/settings/web-search-emulation/reset-usage func (h *SettingHandler) ResetWebSearchUsage(c *gin.Context) { var req struct { ProviderType string `json:"provider_type"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } if req.ProviderType == "" { response.BadRequest(c, "provider_type is required") return } if err := service.ResetWebSearchUsage(c.Request.Context(), req.ProviderType); err != nil { response.ErrorFrom(c, err) return } response.Success(c, nil) } // TestWebSearchEmulation 测试 Web Search 搜索 // POST /api/v1/admin/settings/web-search-emulation/test func (h *SettingHandler) TestWebSearchEmulation(c *gin.Context) { var req struct { Query string `json:"query"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) return } if strings.TrimSpace(req.Query) == "" { req.Query = "搜索今年世界大事件" } result, err := service.TestWebSearch(c.Request.Context(), req.Query) if err != nil { response.ErrorFrom(c, err) return } response.Success(c, result) }