Files
mce/internal/controller/admin/admin_controller.go
Victor_Jay 2449a27eb5
Some checks failed
CI / Lint + Build (push) Has been cancelled
refactor: CSP nonce替代unsafe-inline, HSTS始终启用, router/api拆分, 管理后台+用户设置页拆分, DB健康降级, 时区配置, UID统一解析, CI接入
审计修复:
- CSP: unsafe-inline移除, nonce替代; unsafe-eval保留供Vditor使用
- HSTS: 始终启用(不再依赖release模式)
- Controller Exp: common.GetGinExp包装, 不再直接读context
- UID解析: common.ParseUIDParam统一, follow/admin controller改用

重构:
- router/api.go(198行)拆为7个域文件: auth/settings/posts/comments/reactions/social/studio
- 管理后台站点设置: 单页拆为4个子页(brand/security/registration/content)+子菜单
- 前台用户设置页: 1201行拆为6个独立模板+6个独立JS文件

新增:
- DB健康检测中间件(middleware/db_health.go): 5s ping+降级页面
- 时区配置: config.yaml server.timezone→time.Local初始化
- .gitea/workflows/ci.yml: strict模式CI流水线
2026-06-22 03:06:24 +08:00

152 lines
4.2 KiB
Go
Raw 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 admin 提供管理后台的 HTTP 控制器:用户管理、审核、站点设置等。
package admin
import (
"errors"
"net/http"
"strconv"
"metazone.cc/mce/internal/common"
"metazone.cc/mce/internal/model"
"metazone.cc/mce/internal/service"
"github.com/gin-gonic/gin"
)
// Note: service import kept for ListUsersParams / ListUsersResult DTOs
// AdminController 管理后台控制器
type AdminController struct {
adminService adminUseCase
}
// NewAdminController 构造函数
func NewAdminController(adminService adminUseCase) *AdminController {
return &AdminController{adminService: adminService}
}
// Dashboard 管理首页(欢迎页)
func (ac *AdminController) Dashboard(c *gin.Context) {
totalUsers, err := ac.adminService.CountUsers()
if err != nil {
totalUsers = 0
}
postStats, err := ac.adminService.GetPostStats()
if err != nil {
postStats = &service.PostStats{}
}
storeMetrics := ac.adminService.GetStoreMetrics()
c.HTML(http.StatusOK, "admin/dashboard/index.html", common.BuildAdminPageData(c, gin.H{
"Title": "管理首页",
"TotalUsers": totalUsers,
"PostTotal": postStats.Total,
"PostApproved": postStats.Approved,
"PostPending": postStats.Pending,
"StoreType": storeMetrics.Type,
"StoreHealthy": storeMetrics.Healthy,
"StoreDegraded": storeMetrics.Degraded,
"DegradedNote": storeMetrics.DegradedNote,
"ActiveSessions": storeMetrics.ActiveCount,
}))
}
// UsersPage 用户管理页面
func (ac *AdminController) UsersPage(c *gin.Context) {
c.HTML(http.StatusOK, "admin/users/index.html", common.BuildAdminPageData(c, gin.H{
"Title": "用户管理",
"ExtraCSS": "/admin/static/css/users.css",
"ExtraJS": "/admin/static/js/users.js",
"RoleNames": model.RoleDisplayNames,
"StatusNames": model.StatusDisplayNames,
}))
}
// ListUsers 用户列表 API分页 + 搜索 + 筛选)
func (ac *AdminController) ListUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
params := service.ListUsersParams{
Keyword: c.Query("keyword"),
Role: c.Query("role"),
Status: c.Query("status"),
Page: page,
PageSize: pageSize,
}
result, err := ac.adminService.ListUsers(params)
if err != nil {
common.Error(c, http.StatusInternalServerError, "查询失败")
return
}
common.Ok(c, result)
}
// UpdateUserStatus 封禁/解封用户
func (ac *AdminController) UpdateUserStatus(c *gin.Context) {
targetUID, err := parseUIDParam(c)
if err != nil {
common.Error(c, http.StatusBadRequest, "无效的用户 ID")
return
}
var req struct {
Status string `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "参数错误")
return
}
operatorUID := c.GetUint("uid")
if err := ac.adminService.UpdateUserStatus(operatorUID, targetUID, req.Status); err != nil {
handleServiceError(c, err)
return
}
action := "封禁"
switch req.Status {
case model.StatusActive:
action = "解封"
case model.StatusLocked:
action = "已锁定"
}
common.OkMessage(c, action+"成功")
}
// ResetToken 强制用户下线
func (ac *AdminController) ResetToken(c *gin.Context) {
targetUID, err := parseUIDParam(c)
if err != nil {
common.Error(c, http.StatusBadRequest, "无效的用户 ID")
return
}
operatorUID := c.GetUint("uid")
if err := ac.adminService.ResetUserToken(operatorUID, targetUID); err != nil {
handleServiceError(c, err)
return
}
common.OkMessage(c, "已强制下线")
}
// parseUIDParam 从 URL 路径参数解析 uid
func parseUIDParam(c *gin.Context) (uint, error) {
return common.ParseUIDParam(c, "uid")
}
// handleServiceError 统一处理 service 层返回的错误
func handleServiceError(c *gin.Context, err error) {
if errors.Is(err, common.ErrPermissionDenied) {
common.Error(c, http.StatusForbidden, "权限不足")
} else if errors.Is(err, common.ErrUserNotFound) {
common.Error(c, http.StatusNotFound, "用户不存在")
} else if errors.Is(err, common.ErrEmailExists) {
common.Error(c, http.StatusConflict, "该邮箱已被其他用户注册,无法解锁")
} else {
common.Error(c, http.StatusInternalServerError, "操作失败")
}
}