| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- 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,
- })
- }
- func (h *SettingHandler) GetOpenAIImagesOAuthUnavailableCooldownSettings(c *gin.Context) {
- settings, err := h.settingService.GetOpenAIImagesOAuthUnavailableCooldownSettings(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, dto.OpenAIImagesOAuthUnavailableCooldownSettings{CooldownMinutes: settings.CooldownMinutes})
- }
- type UpdateOpenAIImagesOAuthUnavailableCooldownSettingsRequest struct {
- CooldownMinutes int `json:"cooldown_minutes"`
- }
- func (h *SettingHandler) UpdateOpenAIImagesOAuthUnavailableCooldownSettings(c *gin.Context) {
- var req UpdateOpenAIImagesOAuthUnavailableCooldownSettingsRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- settings := &service.OpenAIImagesOAuthUnavailableCooldownSettings{CooldownMinutes: req.CooldownMinutes}
- if err := h.settingService.SetOpenAIImagesOAuthUnavailableCooldownSettings(c.Request.Context(), settings); err != nil {
- response.BadRequest(c, err.Error())
- return
- }
- response.Success(c, dto.OpenAIImagesOAuthUnavailableCooldownSettings{CooldownMinutes: settings.CooldownMinutes})
- }
- // 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)
- }
|