Files
backend/internal/handler/role_handler.go
lan ae5b997924 feat(auth): add Casbin RBAC and user activity tracking
Integrate Casbin for role-based access control with admin routes for role management. Add user activity logging service to track login and refresh events. Refactor audit service to use AI-based content moderation.
2026-03-14 02:09:38 +08:00

205 lines
4.8 KiB
Go

package handler
import (
"errors"
"net/http"
apperrors "carrot_bbs/internal/errors"
"carrot_bbs/internal/model"
"carrot_bbs/internal/service"
"github.com/gin-gonic/gin"
)
// RoleHandler 角色管理处理器
type RoleHandler struct {
roleService service.RoleService
}
// NewRoleHandler 创建角色处理器
func NewRoleHandler(roleService service.RoleService) *RoleHandler {
return &RoleHandler{
roleService: roleService,
}
}
// GetAllRoles 获取所有角色
// GET /api/v1/admin/roles
func (h *RoleHandler) GetAllRoles(c *gin.Context) {
roles, err := h.roleService.GetAllRoles(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": "INTERNAL_ERROR",
"message": "获取角色列表失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"data": h.toRoleListResponse(roles),
})
}
// GetUserRoles 获取用户的角色
// GET /api/v1/admin/users/:id/roles
func (h *RoleHandler) GetUserRoles(c *gin.Context) {
userID := c.Param("id")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": "BAD_REQUEST",
"message": "缺少用户ID",
})
return
}
roles, err := h.roleService.GetUserRoles(c.Request.Context(), userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": "INTERNAL_ERROR",
"message": "获取用户角色失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"data": h.toRoleListResponse(roles),
})
}
// AssignRoleRequest 分配角色请求
type AssignRoleRequest struct {
Role string `json:"role" binding:"required"`
}
// AssignRole 为用户分配角色
// POST /api/v1/admin/users/:id/roles
func (h *RoleHandler) AssignRole(c *gin.Context) {
targetUserID := c.Param("id")
if targetUserID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": "BAD_REQUEST",
"message": "缺少用户ID",
})
return
}
var req AssignRoleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "BAD_REQUEST",
"message": "请求参数错误",
})
return
}
// 获取操作者ID
operatorID, _ := c.Get("user_id")
err := h.roleService.AssignRole(c.Request.Context(), operatorID.(string), targetUserID, req.Role)
if err != nil {
h.handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "角色分配成功",
})
}
// RemoveRole 移除用户角色
// DELETE /api/v1/admin/users/:id/roles/:role
func (h *RoleHandler) RemoveRole(c *gin.Context) {
targetUserID := c.Param("id")
role := c.Param("role")
if targetUserID == "" || role == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": "BAD_REQUEST",
"message": "缺少用户ID或角色名",
})
return
}
// 获取操作者ID
operatorID, _ := c.Get("user_id")
err := h.roleService.RemoveRole(c.Request.Context(), operatorID.(string), targetUserID, role)
if err != nil {
h.handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "角色移除成功",
})
}
// GetMyRoles 获取当前用户的角色
// GET /api/v1/users/me/roles
func (h *RoleHandler) GetMyRoles(c *gin.Context) {
userID, _ := c.Get("user_id")
roles, err := h.roleService.GetUserRoles(c.Request.Context(), userID.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": "INTERNAL_ERROR",
"message": "获取用户角色失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"data": h.toRoleListResponse(roles),
})
}
// toRoleListResponse 转换为响应格式
func (h *RoleHandler) toRoleListResponse(roles []model.Role) []gin.H {
result := make([]gin.H, len(roles))
for i, role := range roles {
result[i] = gin.H{
"name": role.Name,
"display_name": role.DisplayName,
"description": role.Description,
"priority": role.Priority,
}
}
return result
}
// handleError 处理错误
func (h *RoleHandler) handleError(c *gin.Context, err error) {
switch {
case errors.Is(err, apperrors.ErrCannotModifyOwnRole):
c.JSON(http.StatusForbidden, gin.H{
"code": "CANNOT_MODIFY_OWN_ROLE",
"message": "不能修改自己的角色",
})
case errors.Is(err, apperrors.ErrCannotModifySuperAdmin):
c.JSON(http.StatusForbidden, gin.H{
"code": "CANNOT_MODIFY_SUPER_ADMIN",
"message": "不能修改超级管理员角色",
})
case errors.Is(err, apperrors.ErrRoleAlreadyAssigned):
c.JSON(http.StatusConflict, gin.H{
"code": "ROLE_ALREADY_ASSIGNED",
"message": "用户已拥有该角色",
})
case errors.Is(err, apperrors.ErrRoleNotAssigned):
c.JSON(http.StatusConflict, gin.H{
"code": "ROLE_NOT_ASSIGNED",
"message": "用户未拥有该角色",
})
case errors.Is(err, apperrors.ErrRoleNotFound):
c.JSON(http.StatusNotFound, gin.H{
"code": "ROLE_NOT_FOUND",
"message": "角色不存在",
})
default:
c.JSON(http.StatusInternalServerError, gin.H{
"code": "INTERNAL_ERROR",
"message": "操作失败",
})
}
}