package handler import ( "net/http" "strconv" "strings" "unicode/utf8" "github.com/gin-gonic/gin" ) // ===== 积分管理(usersAPI 组:RequireStaff + CSRF + PermUsers,超管/站长) ===== // adminAdjustMaxDelta 单次调整量的绝对值上界(与积分规则值上界同级) const adminAdjustMaxDelta = 100000 // AdminAdjustUserPoints 管理员手动调整用户积分: // 加分走 CreditTx(同步累加累计获得→可升级),扣分走 DebitTx(仅扣余额、不降级); // 理由必填,写入流水 note(reason=admin_adjust),响应返回调整后余额/累计/等级。 func (h *Handlers) AdminAdjustUserPoints(c *gin.Context) { id, ok := parseAdminUserID(c) if !ok { return } var body struct { Delta int `json:"delta"` Note string `json:"note"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } if body.Delta == 0 || body.Delta > adminAdjustMaxDelta || body.Delta < -adminAdjustMaxDelta { c.JSON(http.StatusBadRequest, gin.H{"error": "积分数量无效"}) return } note := strings.TrimSpace(body.Note) if note == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "调整理由必填"}) return } if utf8.RuneCountInString(note) > 200 { c.JSON(http.StatusBadRequest, gin.H{"error": "调整理由过长(≤200 字)"}) return } bal, total, level, err := h.Points.AdminAdjustPoints(id, body.Delta, note) if err != nil { respondAdminUserError(c, err) return } c.JSON(http.StatusOK, gin.H{"balance": bal, "total_points": total, "level": level}) } // AdminUserPointLedger 某用户的积分流水(分页,id DESC) func (h *Handlers) AdminUserPointLedger(c *gin.Context) { id, ok := parseAdminUserID(c) if !ok { return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) items, total, err := h.Points.AdminUserLedger(id, page, 20) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取积分流水失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total, "page": page, "size": 20}) }