Files
jiang13-bbs/backend/handler/admin_user.go
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

255 lines
7.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"errors"
"net/http"
"strconv"
"strings"
"github.com/freefire/jiang13-bbs/middleware"
"github.com/freefire/jiang13-bbs/model"
"github.com/freefire/jiang13-bbs/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// ===== 用户管理(RequireStaff + PermUsers 兜底,前端不做权限判定) =====
// AdminListUsers 用户分页列表(搜索/角色/状态筛选 + 全站汇总)
func (h *Handlers) AdminListUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
res, err := h.AdminUser.List(service.AdminUserListQuery{
Page: page,
Size: size,
Keyword: strings.TrimSpace(c.Query("q")),
Role: c.Query("role"),
Status: c.Query("status"),
Sort: c.Query("sort"),
Dir: c.Query("order"),
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户列表失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"users": res.Users,
"total": res.Total,
"page": res.Page,
"size": res.Size,
"summary": res.Summary,
})
}
// adminStaffBody 角色与板块授权变更请求体
type adminStaffBody struct {
Role string `json:"role"`
BoardIDs []uint `json:"board_ids"`
}
type adminUserBanBody struct {
Banned bool `json:"banned"`
}
// AdminUpdateUserRole 设置管理角色与板块授权
func (h *Handlers) AdminUpdateUserRole(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
var body adminStaffBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
user, err := h.AdminUser.SetStaff(
middleware.CurrentActor(c), id, model.Role(body.Role), body.BoardIDs,
)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": user})
}
// AdminSetUserMessages 站长授予/撤销站点消息管理
func (h *Handlers) AdminSetUserMessages(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
var body struct {
CanManageMessages bool `json:"can_manage_messages"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
user, err := h.AdminUser.SetCanManageMessages(middleware.CurrentActor(c), id, body.CanManageMessages)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": user})
}
// AdminGetUserPerms 读取账号级渠道权限(超管/站长,授权弹窗回显用)
func (h *Handlers) AdminGetUserPerms(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
perms, err := h.AdminUser.GetPermOverrides(middleware.CurrentActor(c), id)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"perm_overrides": perms})
}
// AdminSetUserPerms 授予/撤销账号级渠道权限(超管/站长)
func (h *Handlers) AdminSetUserPerms(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
var body struct {
Perms []string `json:"perms"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
user, err := h.AdminUser.SetPermOverrides(middleware.CurrentActor(c), id, body.Perms)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": user})
}
// AdminSetUserBan 封禁 / 解封用户
func (h *Handlers) AdminSetUserBan(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
var body adminUserBanBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
user, err := h.AdminUser.SetBanned(middleware.CurrentActor(c), id, body.Banned)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": user})
}
// AdminUserLoginLogs 某用户的登录历史(IP/UA/成败)
func (h *Handlers) AdminUserLoginLogs(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
logs, total, err := h.AdminUser.ListLoginLogs(id, page, 15)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取登录历史失败"})
return
}
c.JSON(http.StatusOK, gin.H{"logs": nonNilSlice(logs), "total": total, "page": page})
}
// AdminGetUserAudit 站长:用户内容档案摘要
func (h *Handlers) AdminGetUserAudit(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
profile, err := h.AdminUser.GetAuditProfile(middleware.CurrentActor(c), id)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, profile)
}
// AdminUserAuditPosts 站长:用户帖子(含驳回/软删)
func (h *Handlers) AdminUserAuditPosts(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
items, total, page, err := h.AdminUser.ListAuditPosts(middleware.CurrentActor(c), id, page, 20)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total, "page": page})
}
// AdminUserAuditComments 站长:用户评论(含驳回/软删)
func (h *Handlers) AdminUserAuditComments(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
items, total, page, err := h.AdminUser.ListAuditComments(middleware.CurrentActor(c), id, page, 20)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total, "page": page})
}
// AdminUserAuditMessages 站长:用户聊天消息(含撤回/软删)
func (h *Handlers) AdminUserAuditMessages(c *gin.Context) {
id, ok := parseAdminUserID(c)
if !ok {
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
items, total, page, err := h.AdminUser.ListAuditMessages(middleware.CurrentActor(c), id, page, 20)
if err != nil {
respondAdminUserError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total, "page": page})
}
// parseAdminUserID 解析路径中的用户 ID,失败时直接写出 400
func parseAdminUserID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil || id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户 ID"})
return 0, false
}
return uint(id), true
}
// respondAdminUserError 统一映射用户管理业务错误到 HTTP 状态码
func respondAdminUserError(c *gin.Context, err error) {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
case errors.Is(err, service.ErrProtectedOwner),
errors.Is(err, service.ErrOwnerOnly):
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
case errors.Is(err, service.ErrAdminSelfAction),
errors.Is(err, service.ErrInvalidRole),
errors.Is(err, service.ErrCannotAssignRole),
errors.Is(err, service.ErrBoardRequired),
errors.Is(err, service.ErrBoardNotFound),
errors.Is(err, service.ErrInsufficientPoints),
errors.Is(err, service.ErrInvalidPoints):
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "操作失败"})
}
}