setting_handler_runtime.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. package admin
  2. import (
  3. "strings"
  4. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  5. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "github.com/gin-gonic/gin"
  8. )
  9. // GetAdminAPIKey 获取管理员 API Key 状态
  10. // GET /api/v1/admin/settings/admin-api-key
  11. func (h *SettingHandler) GetAdminAPIKey(c *gin.Context) {
  12. maskedKey, exists, err := h.settingService.GetAdminAPIKeyStatus(c.Request.Context())
  13. if err != nil {
  14. response.ErrorFrom(c, err)
  15. return
  16. }
  17. response.Success(c, gin.H{
  18. "exists": exists,
  19. "masked_key": maskedKey,
  20. })
  21. }
  22. // RegenerateAdminAPIKey 生成/重新生成管理员 API Key
  23. // POST /api/v1/admin/settings/admin-api-key/regenerate
  24. func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) {
  25. key, err := h.settingService.GenerateAdminAPIKey(c.Request.Context())
  26. if err != nil {
  27. response.ErrorFrom(c, err)
  28. return
  29. }
  30. response.Success(c, gin.H{
  31. "key": key, // 完整 key 只在生成时返回一次
  32. })
  33. }
  34. // DeleteAdminAPIKey 删除管理员 API Key
  35. // DELETE /api/v1/admin/settings/admin-api-key
  36. func (h *SettingHandler) DeleteAdminAPIKey(c *gin.Context) {
  37. if err := h.settingService.DeleteAdminAPIKey(c.Request.Context()); err != nil {
  38. response.ErrorFrom(c, err)
  39. return
  40. }
  41. response.Success(c, gin.H{"message": "Admin API key deleted"})
  42. }
  43. // GetOverloadCooldownSettings 获取529过载冷却配置
  44. // GET /api/v1/admin/settings/overload-cooldown
  45. func (h *SettingHandler) GetOverloadCooldownSettings(c *gin.Context) {
  46. settings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context())
  47. if err != nil {
  48. response.ErrorFrom(c, err)
  49. return
  50. }
  51. response.Success(c, dto.OverloadCooldownSettings{
  52. Enabled: settings.Enabled,
  53. CooldownMinutes: settings.CooldownMinutes,
  54. })
  55. }
  56. // UpdateOverloadCooldownSettingsRequest 更新529过载冷却配置请求
  57. type UpdateOverloadCooldownSettingsRequest struct {
  58. Enabled bool `json:"enabled"`
  59. CooldownMinutes int `json:"cooldown_minutes"`
  60. }
  61. // UpdateOverloadCooldownSettings 更新529过载冷却配置
  62. // PUT /api/v1/admin/settings/overload-cooldown
  63. func (h *SettingHandler) UpdateOverloadCooldownSettings(c *gin.Context) {
  64. var req UpdateOverloadCooldownSettingsRequest
  65. if err := c.ShouldBindJSON(&req); err != nil {
  66. response.BadRequest(c, "Invalid request: "+err.Error())
  67. return
  68. }
  69. settings := &service.OverloadCooldownSettings{
  70. Enabled: req.Enabled,
  71. CooldownMinutes: req.CooldownMinutes,
  72. }
  73. if err := h.settingService.SetOverloadCooldownSettings(c.Request.Context(), settings); err != nil {
  74. response.BadRequest(c, err.Error())
  75. return
  76. }
  77. updatedSettings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context())
  78. if err != nil {
  79. response.ErrorFrom(c, err)
  80. return
  81. }
  82. response.Success(c, dto.OverloadCooldownSettings{
  83. Enabled: updatedSettings.Enabled,
  84. CooldownMinutes: updatedSettings.CooldownMinutes,
  85. })
  86. }
  87. // GetRateLimit429CooldownSettings 获取429默认回避配置
  88. // GET /api/v1/admin/settings/rate-limit-429-cooldown
  89. func (h *SettingHandler) GetRateLimit429CooldownSettings(c *gin.Context) {
  90. settings, err := h.settingService.GetRateLimit429CooldownSettings(c.Request.Context())
  91. if err != nil {
  92. response.ErrorFrom(c, err)
  93. return
  94. }
  95. response.Success(c, dto.RateLimit429CooldownSettings{
  96. Enabled: settings.Enabled,
  97. CooldownSeconds: settings.CooldownSeconds,
  98. })
  99. }
  100. // UpdateRateLimit429CooldownSettingsRequest 更新429默认回避配置请求
  101. type UpdateRateLimit429CooldownSettingsRequest struct {
  102. Enabled bool `json:"enabled"`
  103. CooldownSeconds int `json:"cooldown_seconds"`
  104. }
  105. // UpdateRateLimit429CooldownSettings 更新429默认回避配置
  106. // PUT /api/v1/admin/settings/rate-limit-429-cooldown
  107. func (h *SettingHandler) UpdateRateLimit429CooldownSettings(c *gin.Context) {
  108. var req UpdateRateLimit429CooldownSettingsRequest
  109. if err := c.ShouldBindJSON(&req); err != nil {
  110. response.BadRequest(c, "Invalid request: "+err.Error())
  111. return
  112. }
  113. settings := &service.RateLimit429CooldownSettings{
  114. Enabled: req.Enabled,
  115. CooldownSeconds: req.CooldownSeconds,
  116. }
  117. if err := h.settingService.SetRateLimit429CooldownSettings(c.Request.Context(), settings); err != nil {
  118. response.BadRequest(c, err.Error())
  119. return
  120. }
  121. updatedSettings, err := h.settingService.GetRateLimit429CooldownSettings(c.Request.Context())
  122. if err != nil {
  123. response.ErrorFrom(c, err)
  124. return
  125. }
  126. response.Success(c, dto.RateLimit429CooldownSettings{
  127. Enabled: updatedSettings.Enabled,
  128. CooldownSeconds: updatedSettings.CooldownSeconds,
  129. })
  130. }
  131. func (h *SettingHandler) GetOpenAIImagesOAuthUnavailableCooldownSettings(c *gin.Context) {
  132. settings, err := h.settingService.GetOpenAIImagesOAuthUnavailableCooldownSettings(c.Request.Context())
  133. if err != nil {
  134. response.ErrorFrom(c, err)
  135. return
  136. }
  137. response.Success(c, dto.OpenAIImagesOAuthUnavailableCooldownSettings{CooldownMinutes: settings.CooldownMinutes})
  138. }
  139. type UpdateOpenAIImagesOAuthUnavailableCooldownSettingsRequest struct {
  140. CooldownMinutes int `json:"cooldown_minutes"`
  141. }
  142. func (h *SettingHandler) UpdateOpenAIImagesOAuthUnavailableCooldownSettings(c *gin.Context) {
  143. var req UpdateOpenAIImagesOAuthUnavailableCooldownSettingsRequest
  144. if err := c.ShouldBindJSON(&req); err != nil {
  145. response.BadRequest(c, "Invalid request: "+err.Error())
  146. return
  147. }
  148. settings := &service.OpenAIImagesOAuthUnavailableCooldownSettings{CooldownMinutes: req.CooldownMinutes}
  149. if err := h.settingService.SetOpenAIImagesOAuthUnavailableCooldownSettings(c.Request.Context(), settings); err != nil {
  150. response.BadRequest(c, err.Error())
  151. return
  152. }
  153. response.Success(c, dto.OpenAIImagesOAuthUnavailableCooldownSettings{CooldownMinutes: settings.CooldownMinutes})
  154. }
  155. // GetPanelRateLimitSettings 获取面板 API 限流配置
  156. // GET /api/v1/admin/settings/panel-rate-limit
  157. func (h *SettingHandler) GetPanelRateLimitSettings(c *gin.Context) {
  158. settings, err := h.settingService.GetPanelRateLimitSettings(c.Request.Context())
  159. if err != nil {
  160. response.ErrorFrom(c, err)
  161. return
  162. }
  163. response.Success(c, dto.PanelRateLimitSettings{
  164. Enabled: settings.Enabled,
  165. UserRPM: settings.UserRPM,
  166. HeavyRPM: settings.HeavyRPM,
  167. ExemptAdmin: settings.ExemptAdmin,
  168. PublicIPRPM: settings.PublicIPRPM,
  169. })
  170. }
  171. // UpdatePanelRateLimitSettingsRequest 更新面板 API 限流配置请求
  172. type UpdatePanelRateLimitSettingsRequest struct {
  173. Enabled bool `json:"enabled"`
  174. UserRPM int `json:"user_rpm"`
  175. HeavyRPM int `json:"heavy_rpm"`
  176. ExemptAdmin bool `json:"exempt_admin"`
  177. PublicIPRPM int `json:"public_ip_rpm"`
  178. }
  179. // UpdatePanelRateLimitSettings 更新面板 API 限流配置
  180. // PUT /api/v1/admin/settings/panel-rate-limit
  181. func (h *SettingHandler) UpdatePanelRateLimitSettings(c *gin.Context) {
  182. var req UpdatePanelRateLimitSettingsRequest
  183. if err := c.ShouldBindJSON(&req); err != nil {
  184. response.BadRequest(c, "Invalid request: "+err.Error())
  185. return
  186. }
  187. settings := &service.PanelRateLimitSettings{
  188. Enabled: req.Enabled,
  189. UserRPM: req.UserRPM,
  190. HeavyRPM: req.HeavyRPM,
  191. ExemptAdmin: req.ExemptAdmin,
  192. PublicIPRPM: req.PublicIPRPM,
  193. }
  194. if err := h.settingService.SetPanelRateLimitSettings(c.Request.Context(), settings); err != nil {
  195. response.BadRequest(c, err.Error())
  196. return
  197. }
  198. updatedSettings, err := h.settingService.GetPanelRateLimitSettings(c.Request.Context())
  199. if err != nil {
  200. response.ErrorFrom(c, err)
  201. return
  202. }
  203. response.Success(c, dto.PanelRateLimitSettings{
  204. Enabled: updatedSettings.Enabled,
  205. UserRPM: updatedSettings.UserRPM,
  206. HeavyRPM: updatedSettings.HeavyRPM,
  207. ExemptAdmin: updatedSettings.ExemptAdmin,
  208. PublicIPRPM: updatedSettings.PublicIPRPM,
  209. })
  210. }
  211. // GetStreamTimeoutSettings 获取流超时处理配置
  212. // GET /api/v1/admin/settings/stream-timeout
  213. func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) {
  214. settings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context())
  215. if err != nil {
  216. response.ErrorFrom(c, err)
  217. return
  218. }
  219. response.Success(c, dto.StreamTimeoutSettings{
  220. Enabled: settings.Enabled,
  221. Action: settings.Action,
  222. TempUnschedMinutes: settings.TempUnschedMinutes,
  223. ThresholdCount: settings.ThresholdCount,
  224. ThresholdWindowMinutes: settings.ThresholdWindowMinutes,
  225. })
  226. }
  227. // GetRectifierSettings 获取请求整流器配置
  228. // GET /api/v1/admin/settings/rectifier
  229. func (h *SettingHandler) GetRectifierSettings(c *gin.Context) {
  230. settings, err := h.settingService.GetRectifierSettings(c.Request.Context())
  231. if err != nil {
  232. response.ErrorFrom(c, err)
  233. return
  234. }
  235. patterns := settings.APIKeySignaturePatterns
  236. if patterns == nil {
  237. patterns = []string{}
  238. }
  239. response.Success(c, dto.RectifierSettings{
  240. Enabled: settings.Enabled,
  241. ThinkingSignatureEnabled: settings.ThinkingSignatureEnabled,
  242. ThinkingBudgetEnabled: settings.ThinkingBudgetEnabled,
  243. APIKeySignatureEnabled: settings.APIKeySignatureEnabled,
  244. APIKeySignaturePatterns: patterns,
  245. })
  246. }
  247. // UpdateRectifierSettingsRequest 更新整流器配置请求
  248. type UpdateRectifierSettingsRequest struct {
  249. Enabled bool `json:"enabled"`
  250. ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
  251. ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
  252. APIKeySignatureEnabled bool `json:"apikey_signature_enabled"`
  253. APIKeySignaturePatterns []string `json:"apikey_signature_patterns"`
  254. }
  255. // UpdateRectifierSettings 更新请求整流器配置
  256. // PUT /api/v1/admin/settings/rectifier
  257. func (h *SettingHandler) UpdateRectifierSettings(c *gin.Context) {
  258. var req UpdateRectifierSettingsRequest
  259. if err := c.ShouldBindJSON(&req); err != nil {
  260. response.BadRequest(c, "Invalid request: "+err.Error())
  261. return
  262. }
  263. // 校验并清理自定义匹配关键词
  264. const maxPatterns = 50
  265. const maxPatternLen = 500
  266. if len(req.APIKeySignaturePatterns) > maxPatterns {
  267. response.BadRequest(c, "Too many signature patterns (max 50)")
  268. return
  269. }
  270. var cleanedPatterns []string
  271. for _, p := range req.APIKeySignaturePatterns {
  272. p = strings.TrimSpace(p)
  273. if p == "" {
  274. continue
  275. }
  276. if len(p) > maxPatternLen {
  277. response.BadRequest(c, "Signature pattern too long (max 500 characters)")
  278. return
  279. }
  280. cleanedPatterns = append(cleanedPatterns, p)
  281. }
  282. settings := &service.RectifierSettings{
  283. Enabled: req.Enabled,
  284. ThinkingSignatureEnabled: req.ThinkingSignatureEnabled,
  285. ThinkingBudgetEnabled: req.ThinkingBudgetEnabled,
  286. APIKeySignatureEnabled: req.APIKeySignatureEnabled,
  287. APIKeySignaturePatterns: cleanedPatterns,
  288. }
  289. if err := h.settingService.SetRectifierSettings(c.Request.Context(), settings); err != nil {
  290. response.BadRequest(c, err.Error())
  291. return
  292. }
  293. // 重新获取设置返回
  294. updatedSettings, err := h.settingService.GetRectifierSettings(c.Request.Context())
  295. if err != nil {
  296. response.ErrorFrom(c, err)
  297. return
  298. }
  299. updatedPatterns := updatedSettings.APIKeySignaturePatterns
  300. if updatedPatterns == nil {
  301. updatedPatterns = []string{}
  302. }
  303. response.Success(c, dto.RectifierSettings{
  304. Enabled: updatedSettings.Enabled,
  305. ThinkingSignatureEnabled: updatedSettings.ThinkingSignatureEnabled,
  306. ThinkingBudgetEnabled: updatedSettings.ThinkingBudgetEnabled,
  307. APIKeySignatureEnabled: updatedSettings.APIKeySignatureEnabled,
  308. APIKeySignaturePatterns: updatedPatterns,
  309. })
  310. }
  311. // GetBetaPolicySettings 获取 Beta 策略配置
  312. // GET /api/v1/admin/settings/beta-policy
  313. func (h *SettingHandler) GetBetaPolicySettings(c *gin.Context) {
  314. settings, err := h.settingService.GetBetaPolicySettings(c.Request.Context())
  315. if err != nil {
  316. response.ErrorFrom(c, err)
  317. return
  318. }
  319. rules := make([]dto.BetaPolicyRule, len(settings.Rules))
  320. for i, r := range settings.Rules {
  321. rules[i] = dto.BetaPolicyRule(r)
  322. }
  323. response.Success(c, dto.BetaPolicySettings{Rules: rules})
  324. }
  325. // UpdateBetaPolicySettingsRequest 更新 Beta 策略配置请求
  326. type UpdateBetaPolicySettingsRequest struct {
  327. Rules []dto.BetaPolicyRule `json:"rules"`
  328. }
  329. // UpdateBetaPolicySettings 更新 Beta 策略配置
  330. // PUT /api/v1/admin/settings/beta-policy
  331. func (h *SettingHandler) UpdateBetaPolicySettings(c *gin.Context) {
  332. var req UpdateBetaPolicySettingsRequest
  333. if err := c.ShouldBindJSON(&req); err != nil {
  334. response.BadRequest(c, "Invalid request: "+err.Error())
  335. return
  336. }
  337. rules := make([]service.BetaPolicyRule, len(req.Rules))
  338. for i, r := range req.Rules {
  339. rules[i] = service.BetaPolicyRule(r)
  340. }
  341. settings := &service.BetaPolicySettings{Rules: rules}
  342. if err := h.settingService.SetBetaPolicySettings(c.Request.Context(), settings); err != nil {
  343. response.BadRequest(c, err.Error())
  344. return
  345. }
  346. // Re-fetch to return updated settings
  347. updated, err := h.settingService.GetBetaPolicySettings(c.Request.Context())
  348. if err != nil {
  349. response.ErrorFrom(c, err)
  350. return
  351. }
  352. outRules := make([]dto.BetaPolicyRule, len(updated.Rules))
  353. for i, r := range updated.Rules {
  354. outRules[i] = dto.BetaPolicyRule(r)
  355. }
  356. response.Success(c, dto.BetaPolicySettings{Rules: outRules})
  357. }
  358. // UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
  359. type UpdateStreamTimeoutSettingsRequest struct {
  360. Enabled bool `json:"enabled"`
  361. Action string `json:"action"`
  362. TempUnschedMinutes int `json:"temp_unsched_minutes"`
  363. ThresholdCount int `json:"threshold_count"`
  364. ThresholdWindowMinutes int `json:"threshold_window_minutes"`
  365. }
  366. // UpdateStreamTimeoutSettings 更新流超时处理配置
  367. // PUT /api/v1/admin/settings/stream-timeout
  368. func (h *SettingHandler) UpdateStreamTimeoutSettings(c *gin.Context) {
  369. var req UpdateStreamTimeoutSettingsRequest
  370. if err := c.ShouldBindJSON(&req); err != nil {
  371. response.BadRequest(c, "Invalid request: "+err.Error())
  372. return
  373. }
  374. settings := &service.StreamTimeoutSettings{
  375. Enabled: req.Enabled,
  376. Action: req.Action,
  377. TempUnschedMinutes: req.TempUnschedMinutes,
  378. ThresholdCount: req.ThresholdCount,
  379. ThresholdWindowMinutes: req.ThresholdWindowMinutes,
  380. }
  381. if err := h.settingService.SetStreamTimeoutSettings(c.Request.Context(), settings); err != nil {
  382. response.BadRequest(c, err.Error())
  383. return
  384. }
  385. // 重新获取设置返回
  386. updatedSettings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context())
  387. if err != nil {
  388. response.ErrorFrom(c, err)
  389. return
  390. }
  391. response.Success(c, dto.StreamTimeoutSettings{
  392. Enabled: updatedSettings.Enabled,
  393. Action: updatedSettings.Action,
  394. TempUnschedMinutes: updatedSettings.TempUnschedMinutes,
  395. ThresholdCount: updatedSettings.ThresholdCount,
  396. ThresholdWindowMinutes: updatedSettings.ThresholdWindowMinutes,
  397. })
  398. }
  399. // GetWebSearchEmulationConfig 获取 Web Search 模拟配置
  400. // GET /api/v1/admin/settings/web-search-emulation
  401. func (h *SettingHandler) GetWebSearchEmulationConfig(c *gin.Context) {
  402. cfg, err := h.settingService.GetWebSearchEmulationConfig(c.Request.Context())
  403. if err != nil {
  404. response.ErrorFrom(c, err)
  405. return
  406. }
  407. response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), cfg))
  408. }
  409. // UpdateWebSearchEmulationConfig 更新 Web Search 模拟配置
  410. // PUT /api/v1/admin/settings/web-search-emulation
  411. func (h *SettingHandler) UpdateWebSearchEmulationConfig(c *gin.Context) {
  412. var cfg service.WebSearchEmulationConfig
  413. if err := c.ShouldBindJSON(&cfg); err != nil {
  414. response.BadRequest(c, "Invalid request: "+err.Error())
  415. return
  416. }
  417. if err := h.settingService.SaveWebSearchEmulationConfig(c.Request.Context(), &cfg); err != nil {
  418. response.ErrorFrom(c, err)
  419. return
  420. }
  421. // Re-read (with sanitized api keys) to return current state
  422. updated, err := h.settingService.GetWebSearchEmulationConfig(c.Request.Context())
  423. if err != nil {
  424. response.ErrorFrom(c, err)
  425. return
  426. }
  427. response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), updated))
  428. }
  429. // ResetWebSearchUsage 重置指定 provider 的配额用量
  430. // POST /api/v1/admin/settings/web-search-emulation/reset-usage
  431. func (h *SettingHandler) ResetWebSearchUsage(c *gin.Context) {
  432. var req struct {
  433. ProviderType string `json:"provider_type"`
  434. }
  435. if err := c.ShouldBindJSON(&req); err != nil {
  436. response.BadRequest(c, "Invalid request: "+err.Error())
  437. return
  438. }
  439. if req.ProviderType == "" {
  440. response.BadRequest(c, "provider_type is required")
  441. return
  442. }
  443. if err := service.ResetWebSearchUsage(c.Request.Context(), req.ProviderType); err != nil {
  444. response.ErrorFrom(c, err)
  445. return
  446. }
  447. response.Success(c, nil)
  448. }
  449. // TestWebSearchEmulation 测试 Web Search 搜索
  450. // POST /api/v1/admin/settings/web-search-emulation/test
  451. func (h *SettingHandler) TestWebSearchEmulation(c *gin.Context) {
  452. var req struct {
  453. Query string `json:"query"`
  454. }
  455. if err := c.ShouldBindJSON(&req); err != nil {
  456. response.BadRequest(c, "Invalid request: "+err.Error())
  457. return
  458. }
  459. if strings.TrimSpace(req.Query) == "" {
  460. req.Query = "搜索今年世界大事件"
  461. }
  462. result, err := service.TestWebSearch(c.Request.Context(), req.Query)
  463. if err != nil {
  464. response.ErrorFrom(c, err)
  465. return
  466. }
  467. response.Success(c, result)
  468. }