anti_degrade_handler.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package admin
  2. import (
  3. "errors"
  4. "strconv"
  5. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  6. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  7. "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. "github.com/gin-gonic/gin"
  10. )
  11. // AntiDegradeHandler 一键防降智:预览 / 应用 / 还原账号保护组合。
  12. type AntiDegradeHandler struct {
  13. service *service.AntiDegradeService
  14. }
  15. func NewAntiDegradeHandler(svc *service.AntiDegradeService) *AntiDegradeHandler {
  16. return &AntiDegradeHandler{service: svc}
  17. }
  18. func (h *AntiDegradeHandler) requireService(c *gin.Context) bool {
  19. role, ok := middleware.GetUserRoleFromContext(c)
  20. if !ok || !service.IsAdminRole(role) {
  21. response.Forbidden(c, "Admin access required")
  22. return false
  23. }
  24. if h == nil || h.service == nil {
  25. response.ErrorFrom(c, errors.New("anti-degrade service unavailable"))
  26. return false
  27. }
  28. return true
  29. }
  30. func antiDegradeID(c *gin.Context) (int64, bool) {
  31. id, err := strconv.ParseInt(c.Param("id"), 10, 64)
  32. if err != nil || id <= 0 {
  33. response.BadRequest(c, "Invalid account ID")
  34. return 0, false
  35. }
  36. return id, true
  37. }
  38. // Strategies returns the server-owned, data-driven strategy registry. It is
  39. // read-only and intentionally available only to administrators.
  40. func (h *AntiDegradeHandler) Strategies(c *gin.Context) {
  41. if !h.requireService(c) {
  42. return
  43. }
  44. response.Success(c, gin.H{"strategies": service.ListAntiDegradeStrategyProfiles()})
  45. }
  46. // Preview 预览改动集(不写库)。
  47. // GET /api/v1/admin/accounts/:id/anti-degrade
  48. func (h *AntiDegradeHandler) Preview(c *gin.Context) {
  49. if !h.requireService(c) {
  50. return
  51. }
  52. id, ok := antiDegradeID(c)
  53. if !ok {
  54. return
  55. }
  56. mode := service.AntiDegradeMode(c.Query("mode"))
  57. account, err := h.service.PreviewMode(c.Request.Context(), id, mode)
  58. if err != nil {
  59. response.ErrorFrom(c, err)
  60. return
  61. }
  62. response.Success(c, account)
  63. }
  64. // Apply 应用一键防降智并快照旧值。
  65. // POST /api/v1/admin/accounts/:id/anti-degrade/apply
  66. func (h *AntiDegradeHandler) Apply(c *gin.Context) {
  67. if !h.requireService(c) {
  68. return
  69. }
  70. id, ok := antiDegradeID(c)
  71. if !ok {
  72. return
  73. }
  74. mode := service.AntiDegradeMode(c.Query("mode"))
  75. account, err := h.service.ApplyAntiDegradeMode(c.Request.Context(), id, mode)
  76. if err != nil {
  77. response.ErrorFrom(c, err)
  78. return
  79. }
  80. response.Success(c, dto.AccountFromService(account))
  81. }
  82. // Revert 一键还原快照旧值。
  83. // POST /api/v1/admin/accounts/:id/anti-degrade/revert
  84. func (h *AntiDegradeHandler) Revert(c *gin.Context) {
  85. if !h.requireService(c) {
  86. return
  87. }
  88. id, ok := antiDegradeID(c)
  89. if !ok {
  90. return
  91. }
  92. var req struct {
  93. ConfirmDisable bool `json:"confirm_disable"`
  94. }
  95. if err := c.ShouldBindJSON(&req); err != nil || !req.ConfirmDisable {
  96. response.BadRequest(c, "关闭防降智模式需要管理员明确确认")
  97. return
  98. }
  99. middleware.SetAuditAction(c, "account.protection.disable")
  100. middleware.SetAuditExtra(c, map[string]any{"enabled": false, "confirm": true})
  101. account, err := h.service.Revert(c.Request.Context(), id)
  102. if err != nil {
  103. response.ErrorFrom(c, err)
  104. return
  105. }
  106. response.Success(c, dto.AccountFromService(account))
  107. }
  108. // SetProtection handles the explicit, administrator-confirmed ON/OFF control.
  109. func (h *AntiDegradeHandler) SetProtection(c *gin.Context) {
  110. if !h.requireService(c) {
  111. return
  112. }
  113. id, ok := antiDegradeID(c)
  114. if !ok {
  115. return
  116. }
  117. var req struct {
  118. Enabled *bool `json:"enabled" binding:"required"`
  119. ConfirmDisable bool `json:"confirm_disable"`
  120. }
  121. if err := c.ShouldBindJSON(&req); err != nil {
  122. response.BadRequest(c, "Invalid protection request")
  123. return
  124. }
  125. middleware.SetAuditAction(c, "account.protection.update")
  126. middleware.SetAuditExtra(c, map[string]any{"enabled": *req.Enabled, "confirm": req.ConfirmDisable})
  127. account, err := h.service.SetProtection(c.Request.Context(), id, *req.Enabled, req.ConfirmDisable)
  128. if err != nil {
  129. response.ErrorFrom(c, err)
  130. return
  131. }
  132. response.Success(c, dto.AccountFromService(account))
  133. }
  134. // EnableBatch deliberately has no disable branch.
  135. func (h *AntiDegradeHandler) EnableBatch(c *gin.Context) {
  136. if !h.requireService(c) {
  137. return
  138. }
  139. var req struct {
  140. AccountIDs []int64 `json:"account_ids" binding:"required,min=1,max=500,dive,gt=0"`
  141. }
  142. if err := c.ShouldBindJSON(&req); err != nil {
  143. response.BadRequest(c, "Select 1–500 valid account IDs")
  144. return
  145. }
  146. middleware.SetAuditAction(c, "account.protection.enable_batch")
  147. middleware.SetAuditExtra(c, map[string]any{"enabled": true, "requested_count": len(req.AccountIDs)})
  148. success := []int64{}
  149. failures := map[int64]string{}
  150. seen := map[int64]bool{}
  151. for _, id := range req.AccountIDs {
  152. if seen[id] {
  153. continue
  154. }
  155. seen[id] = true
  156. if _, err := h.service.SetProtection(c.Request.Context(), id, true, false); err != nil {
  157. failures[id] = err.Error()
  158. } else {
  159. success = append(success, id)
  160. }
  161. }
  162. response.Success(c, gin.H{"success_ids": success, "failures": failures})
  163. }