feat: 角色等级体系与权限管控实施
- 数据库:auth_roles 表新增 level 字段(1=超管, 10=管理员, 100=普通用户) - 后端:新增 GetAllRoles/GetUserMaxLevel/SetUserRoles/FindByCodeList DAO 方法 - 后端:SetRolesRequest 替换 AssignRoleRequest,AdminUserInfo.Roles 改为 []RoleInfo - 后端:所有管理操作(禁用/启用/角色分配/创建用户)强制层级权限校验 - 后端:PUT /users/:id/roles(批量设置角色)+ GET /roles(角色列表) - 前端:角色管理改为 Checkbox Group 多选,高等级角色禁用 - 前端:禁用/启用按钮受角色层级约束,列表页操作按钮权限管控 - 前端:创建用户对话框角色选项根据操作者等级动态过滤 - 文档:API 文档、设计方案、集成规范、进度文档同步更新 Made-with: Cursor
This commit is contained in:
@@ -119,6 +119,8 @@ func (ctl *UserManageController) UpdateUserStatus(c *gin.Context) {
|
||||
utils.ResponseBadRequest(c, "不能禁用自己的账号")
|
||||
case service.ErrInvalidStatus:
|
||||
utils.ResponseBadRequest(c, "无效的用户状态")
|
||||
case service.ErrInsufficientPermission:
|
||||
utils.ResponseForbidden(c, "权限不足,无法操作更高等级的用户")
|
||||
default:
|
||||
logs.Error(ctx, funcName, "更新用户状态失败", zap.Error(err))
|
||||
utils.ResponseError(c, "更新用户状态失败")
|
||||
@@ -129,10 +131,10 @@ func (ctl *UserManageController) UpdateUserStatus(c *gin.Context) {
|
||||
utils.ResponseOK(c, nil)
|
||||
}
|
||||
|
||||
// AssignRole 分配角色
|
||||
// PUT /api/v1/admin/users/:id/role
|
||||
func (ctl *UserManageController) AssignRole(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.AssignRole"
|
||||
// SetRoles 批量设置用户角色
|
||||
// PUT /api/v1/admin/users/:id/roles
|
||||
func (ctl *UserManageController) SetRoles(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.SetRoles"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
@@ -141,27 +143,38 @@ func (ctl *UserManageController) AssignRole(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AssignRoleRequest
|
||||
var req dto.SetRolesRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
|
||||
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "分配角色",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.String("role_code", req.RoleCode),
|
||||
adminUserID, ok := middleware.GetCurrentUserID(c)
|
||||
if !ok {
|
||||
utils.ResponseUnauthorized(c, "无法获取当前用户信息")
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "设置用户角色",
|
||||
zap.Int64("target_user_id", userID),
|
||||
zap.Strings("role_codes", req.RoleCodes),
|
||||
zap.Int64("admin_user_id", adminUserID),
|
||||
)
|
||||
|
||||
if err := ctl.userManageService.AssignUserRole(ctx, userID, req.RoleCode); err != nil {
|
||||
if err := ctl.userManageService.SetUserRoles(ctx, userID, req.RoleCodes, adminUserID); err != nil {
|
||||
switch err {
|
||||
case service.ErrUserNotFound:
|
||||
utils.ResponseNotFound(c, "用户不存在")
|
||||
case service.ErrInvalidRole:
|
||||
utils.ResponseBadRequest(c, "无效的角色代码")
|
||||
utils.ResponseBadRequest(c, "包含无效的角色代码")
|
||||
case service.ErrInsufficientPermission:
|
||||
utils.ResponseForbidden(c, "权限不足,无法操作更高等级的用户")
|
||||
case service.ErrCannotAssignHigherRole:
|
||||
utils.ResponseForbidden(c, "不能分配高于自身等级的角色")
|
||||
default:
|
||||
logs.Error(ctx, funcName, "分配角色失败", zap.Error(err))
|
||||
utils.ResponseError(c, "分配角色失败")
|
||||
logs.Error(ctx, funcName, "设置用户角色失败", zap.Error(err))
|
||||
utils.ResponseError(c, "设置用户角色失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -169,6 +182,24 @@ func (ctl *UserManageController) AssignRole(c *gin.Context) {
|
||||
utils.ResponseOK(c, nil)
|
||||
}
|
||||
|
||||
// GetAllRoles 获取所有角色列表
|
||||
// GET /api/v1/admin/roles
|
||||
func (ctl *UserManageController) GetAllRoles(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.GetAllRoles"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
logs.Info(ctx, funcName, "获取所有角色列表")
|
||||
|
||||
roles, err := ctl.userManageService.GetAllRoles(ctx)
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "获取角色列表失败", zap.Error(err))
|
||||
utils.ResponseError(c, "获取角色列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.ResponseOK(c, roles)
|
||||
}
|
||||
|
||||
// CreateUser 管理员手动创建用户
|
||||
// POST /api/v1/admin/users
|
||||
func (ctl *UserManageController) CreateUser(c *gin.Context) {
|
||||
@@ -182,19 +213,31 @@ func (ctl *UserManageController) CreateUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
adminUserID, ok := middleware.GetCurrentUserID(c)
|
||||
if !ok {
|
||||
utils.ResponseUnauthorized(c, "无法获取当前用户信息")
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "管理员创建用户",
|
||||
zap.String("username", req.Username),
|
||||
zap.String("email", logs.MaskEmail(req.Email)),
|
||||
zap.Int64("admin_user_id", adminUserID),
|
||||
)
|
||||
|
||||
userInfo, err := ctl.userManageService.CreateUser(ctx, &req)
|
||||
userInfo, err := ctl.userManageService.CreateUser(ctx, &req, adminUserID)
|
||||
if err != nil {
|
||||
if err == service.ErrUserExists {
|
||||
switch err {
|
||||
case service.ErrUserExists:
|
||||
utils.ResponseBadRequest(c, "用户名或邮箱已被注册")
|
||||
return
|
||||
case service.ErrInvalidRole:
|
||||
utils.ResponseBadRequest(c, "无效的角色代码")
|
||||
case service.ErrCannotAssignHigherRole:
|
||||
utils.ResponseForbidden(c, "不能分配高于自身等级的角色")
|
||||
default:
|
||||
logs.Error(ctx, funcName, "创建用户失败", zap.Error(err))
|
||||
utils.ResponseError(c, "创建用户失败")
|
||||
}
|
||||
logs.Error(ctx, funcName, "创建用户失败", zap.Error(err))
|
||||
utils.ResponseError(c, "创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ func RegisterRoutes(
|
||||
adminGroup.GET("/users", ctrl.GetUserList)
|
||||
adminGroup.GET("/users/:id", ctrl.GetUserDetail)
|
||||
adminGroup.PUT("/users/:id/status", ctrl.UpdateUserStatus)
|
||||
adminGroup.PUT("/users/:id/role", ctrl.AssignRole)
|
||||
adminGroup.PUT("/users/:id/roles", ctrl.SetRoles)
|
||||
adminGroup.POST("/users", ctrl.CreateUser)
|
||||
|
||||
// 角色管理
|
||||
adminGroup.GET("/roles", ctrl.GetAllRoles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
authDAO "github.com/echochat/backend/app/auth/dao"
|
||||
"github.com/echochat/backend/app/auth/model"
|
||||
@@ -17,11 +18,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
ErrUserExists = errors.New("用户名或邮箱已被注册")
|
||||
ErrInvalidStatus = errors.New("无效的用户状态")
|
||||
ErrInvalidRole = errors.New("无效的角色代码")
|
||||
ErrCannotDisableSelf = errors.New("不能禁用自己的账号")
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
ErrUserExists = errors.New("用户名或邮箱已被注册")
|
||||
ErrInvalidStatus = errors.New("无效的用户状态")
|
||||
ErrInvalidRole = errors.New("无效的角色代码")
|
||||
ErrCannotDisableSelf = errors.New("不能禁用自己的账号")
|
||||
ErrInsufficientPermission = errors.New("权限不足,无法操作更高等级的用户")
|
||||
ErrCannotAssignHigherRole = errors.New("不能分配高于自身等级的角色")
|
||||
)
|
||||
|
||||
// UserManageService 管理端用户管理服务
|
||||
@@ -56,11 +59,11 @@ func (s *UserManageService) GetUserList(ctx context.Context, req *dto.UserListRe
|
||||
|
||||
list := make([]dto.AdminUserInfo, 0, len(users))
|
||||
for _, user := range users {
|
||||
roles, roleErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
|
||||
roles, roleErr := s.roleDAO.GetUserRoles(ctx, user.ID)
|
||||
if roleErr != nil {
|
||||
logs.Warn(ctx, funcName, "获取用户角色失败,降级为空角色列表",
|
||||
zap.Int64("user_id", user.ID), zap.Error(roleErr))
|
||||
roles = []string{}
|
||||
roles = []model.Role{}
|
||||
}
|
||||
list = append(list, *s.buildAdminUserInfo(&user, roles))
|
||||
}
|
||||
@@ -84,17 +87,17 @@ func (s *UserManageService) GetUserDetail(ctx context.Context, userID int64) (*d
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles, roleErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
|
||||
roles, roleErr := s.roleDAO.GetUserRoles(ctx, user.ID)
|
||||
if roleErr != nil {
|
||||
logs.Warn(ctx, funcName, "获取用户角色失败,降级为空角色列表",
|
||||
zap.Int64("user_id", user.ID), zap.Error(roleErr))
|
||||
roles = []string{}
|
||||
roles = []model.Role{}
|
||||
}
|
||||
return s.buildAdminUserInfo(user, roles), nil
|
||||
}
|
||||
|
||||
// UpdateUserStatus 启用/禁用用户
|
||||
// adminUserID 用于防止管理员禁用自己
|
||||
// adminUserID 用于防止管理员禁用自己;同时校验操作者权限等级必须高于目标用户
|
||||
func (s *UserManageService) UpdateUserStatus(ctx context.Context, userID int64, status int, adminUserID int64) error {
|
||||
funcName := "service.user_manage_service.UpdateUserStatus"
|
||||
logs.Info(ctx, funcName, "更新用户状态",
|
||||
@@ -111,7 +114,6 @@ func (s *UserManageService) UpdateUserStatus(ctx context.Context, userID int64,
|
||||
return ErrInvalidStatus
|
||||
}
|
||||
|
||||
// 确认用户存在
|
||||
_, err := s.userDAO.FindByID(ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -120,6 +122,10 @@ func (s *UserManageService) UpdateUserStatus(ctx context.Context, userID int64,
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.checkPermissionLevel(ctx, adminUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userManageDAO.UpdateUserStatus(ctx, userID, status); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -131,15 +137,16 @@ func (s *UserManageService) UpdateUserStatus(ctx context.Context, userID int64,
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssignUserRole 分配角色给用户
|
||||
func (s *UserManageService) AssignUserRole(ctx context.Context, userID int64, roleCode string) error {
|
||||
funcName := "service.user_manage_service.AssignUserRole"
|
||||
logs.Info(ctx, funcName, "分配角色",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.String("role_code", roleCode),
|
||||
// SetUserRoles 批量设置用户角色(事务内先清后设)
|
||||
// adminUserID 用于校验操作者等级,防止越权分配高等级角色
|
||||
func (s *UserManageService) SetUserRoles(ctx context.Context, userID int64, roleCodes []string, adminUserID int64) error {
|
||||
funcName := "service.user_manage_service.SetUserRoles"
|
||||
logs.Info(ctx, funcName, "设置用户角色",
|
||||
zap.Int64("target_user_id", userID),
|
||||
zap.Strings("role_codes", roleCodes),
|
||||
zap.Int64("admin_user_id", adminUserID),
|
||||
)
|
||||
|
||||
// 验证用户存在
|
||||
_, err := s.userDAO.FindByID(ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -148,33 +155,106 @@ func (s *UserManageService) AssignUserRole(ctx context.Context, userID int64, ro
|
||||
return err
|
||||
}
|
||||
|
||||
// 查找角色
|
||||
role, err := s.roleDAO.FindByCode(ctx, roleCode)
|
||||
if err := s.checkPermissionLevel(ctx, adminUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
roles, err := s.roleDAO.FindByCodeList(ctx, roleCodes)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrInvalidRole
|
||||
return err
|
||||
}
|
||||
if len(roles) != len(roleCodes) {
|
||||
return ErrInvalidRole
|
||||
}
|
||||
|
||||
adminLevel, err := s.roleDAO.GetUserMaxLevel(ctx, adminUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range roles {
|
||||
if r.Level <= adminLevel {
|
||||
logs.Warn(ctx, funcName, "尝试分配高于自身等级的角色",
|
||||
zap.String("role_code", r.Code),
|
||||
zap.Int("role_level", r.Level),
|
||||
zap.Int("admin_level", adminLevel),
|
||||
)
|
||||
return ErrCannotAssignHigherRole
|
||||
}
|
||||
}
|
||||
|
||||
roleIDs := make([]int, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
roleIDs = append(roleIDs, r.ID)
|
||||
}
|
||||
if err := s.roleDAO.SetUserRoles(ctx, userID, roleIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 分配角色(RoleDAO.AssignRole 内部会处理重复分配)
|
||||
if err := s.roleDAO.AssignRole(ctx, userID, role.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "角色分配成功",
|
||||
logs.Info(ctx, funcName, "用户角色设置成功",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.String("role_code", roleCode),
|
||||
zap.Strings("role_codes", roleCodes),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllRoles 获取所有角色列表
|
||||
func (s *UserManageService) GetAllRoles(ctx context.Context) ([]dto.RoleInfo, error) {
|
||||
funcName := "service.user_manage_service.GetAllRoles"
|
||||
logs.Info(ctx, funcName, "获取所有角色列表")
|
||||
|
||||
roles, err := s.roleDAO.GetAllRoles(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]dto.RoleInfo, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
result = append(result, dto.RoleInfo{
|
||||
Code: r.Code,
|
||||
Name: r.Name,
|
||||
Level: r.Level,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// checkPermissionLevel 校验操作者的权限等级是否高于目标用户
|
||||
// 操作者 level 必须 < 目标用户 level(数值更小 = 权限更高)
|
||||
func (s *UserManageService) checkPermissionLevel(ctx context.Context, adminUserID, targetUserID int64) error {
|
||||
funcName := "service.user_manage_service.checkPermissionLevel"
|
||||
|
||||
adminLevel, err := s.roleDAO.GetUserMaxLevel(ctx, adminUserID)
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "获取操作者权限等级失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
targetLevel, err := s.roleDAO.GetUserMaxLevel(ctx, targetUserID)
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "获取目标用户权限等级失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if adminLevel >= targetLevel {
|
||||
logs.Warn(ctx, funcName, "权限不足",
|
||||
zap.Int64("admin_user_id", adminUserID),
|
||||
zap.Int("admin_level", adminLevel),
|
||||
zap.Int64("target_user_id", targetUserID),
|
||||
zap.Int("target_level", targetLevel),
|
||||
)
|
||||
return ErrInsufficientPermission
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateUser 管理员手动创建用户
|
||||
func (s *UserManageService) CreateUser(ctx context.Context, req *dto.AdminCreateUserRequest) (*dto.AdminUserInfo, error) {
|
||||
// adminUserID 用于校验操作者权限等级,防止越权分配高等级角色
|
||||
func (s *UserManageService) CreateUser(ctx context.Context, req *dto.AdminCreateUserRequest, adminUserID int64) (*dto.AdminUserInfo, error) {
|
||||
funcName := "service.user_manage_service.CreateUser"
|
||||
logs.Info(ctx, funcName, "管理员创建用户",
|
||||
zap.String("username", req.Username),
|
||||
zap.String("email", logs.MaskEmail(req.Email)),
|
||||
zap.Int64("admin_user_id", adminUserID),
|
||||
)
|
||||
|
||||
existing, findErr := s.userDAO.FindByUsername(ctx, req.Username)
|
||||
@@ -192,7 +272,28 @@ func (s *UserManageService) CreateUser(ctx context.Context, req *dto.AdminCreate
|
||||
return nil, ErrUserExists
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
roleCode := req.RoleCode
|
||||
if roleCode == "" {
|
||||
roleCode = constants.RoleUser
|
||||
}
|
||||
role, roleErr := s.roleDAO.FindByCode(ctx, roleCode)
|
||||
if roleErr != nil {
|
||||
return nil, ErrInvalidRole
|
||||
}
|
||||
|
||||
adminLevel, err := s.roleDAO.GetUserMaxLevel(ctx, adminUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role.Level <= adminLevel {
|
||||
logs.Warn(ctx, funcName, "尝试为新用户分配高于自身等级的角色",
|
||||
zap.String("role_code", roleCode),
|
||||
zap.Int("role_level", role.Level),
|
||||
zap.Int("admin_level", adminLevel),
|
||||
)
|
||||
return nil, ErrCannotAssignHigherRole
|
||||
}
|
||||
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -214,35 +315,34 @@ func (s *UserManageService) CreateUser(ctx context.Context, req *dto.AdminCreate
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleCode := req.RoleCode
|
||||
if roleCode == "" {
|
||||
roleCode = constants.RoleUser
|
||||
}
|
||||
role, roleErr := s.roleDAO.FindByCode(ctx, roleCode)
|
||||
if roleErr != nil {
|
||||
logs.Warn(ctx, funcName, "角色查找失败,将跳过角色分配",
|
||||
zap.String("role_code", roleCode), zap.Error(roleErr))
|
||||
} else {
|
||||
if assignErr := s.roleDAO.AssignRole(ctx, user.ID, role.ID); assignErr != nil {
|
||||
logs.Warn(ctx, funcName, "角色分配失败",
|
||||
zap.Int64("user_id", user.ID), zap.Error(assignErr))
|
||||
}
|
||||
if assignErr := s.roleDAO.AssignRole(ctx, user.ID, role.ID); assignErr != nil {
|
||||
logs.Warn(ctx, funcName, "角色分配失败",
|
||||
zap.Int64("user_id", user.ID), zap.Error(assignErr))
|
||||
}
|
||||
|
||||
roles, rolesErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
|
||||
userRoles, rolesErr := s.roleDAO.GetUserRoles(ctx, user.ID)
|
||||
if rolesErr != nil {
|
||||
logs.Warn(ctx, funcName, "获取新建用户角色失败",
|
||||
zap.Int64("user_id", user.ID), zap.Error(rolesErr))
|
||||
roles = []string{}
|
||||
userRoles = []model.Role{}
|
||||
}
|
||||
logs.Info(ctx, funcName, "管理员创建用户成功", zap.Int64("user_id", user.ID))
|
||||
return s.buildAdminUserInfo(user, roles), nil
|
||||
return s.buildAdminUserInfo(user, userRoles), nil
|
||||
}
|
||||
|
||||
// buildAdminUserInfo 从 model.User 构建 dto.AdminUserInfo
|
||||
func (s *UserManageService) buildAdminUserInfo(user *model.User, roles []string) *dto.AdminUserInfo {
|
||||
if roles == nil {
|
||||
roles = []string{}
|
||||
// buildAdminUserInfo 从 model.User + []model.Role 构建 dto.AdminUserInfo
|
||||
func (s *UserManageService) buildAdminUserInfo(user *model.User, roles []model.Role) *dto.AdminUserInfo {
|
||||
roleInfos := make([]dto.RoleInfo, 0, len(roles))
|
||||
minLevel := math.MaxInt32
|
||||
for _, r := range roles {
|
||||
roleInfos = append(roleInfos, dto.RoleInfo{
|
||||
Code: r.Code,
|
||||
Name: r.Name,
|
||||
Level: r.Level,
|
||||
})
|
||||
if r.Level < minLevel {
|
||||
minLevel = r.Level
|
||||
}
|
||||
}
|
||||
|
||||
info := &dto.AdminUserInfo{
|
||||
@@ -254,7 +354,8 @@ func (s *UserManageService) buildAdminUserInfo(user *model.User, roles []string)
|
||||
Gender: user.Gender,
|
||||
Status: user.Status,
|
||||
StatusText: constants.UserStatusMap[user.Status],
|
||||
Roles: roles,
|
||||
Roles: roleInfos,
|
||||
MaxLevel: minLevel,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/echochat/backend/app/auth/model"
|
||||
"github.com/echochat/backend/pkg/logs"
|
||||
@@ -117,3 +118,87 @@ func (d *RoleDAO) RemoveUserRoles(ctx context.Context, userID int64) error {
|
||||
Where("user_id = ?", userID).
|
||||
Delete(&model.UserRole{}).Error
|
||||
}
|
||||
|
||||
// GetAllRoles 获取所有角色列表(按 level 升序排列)
|
||||
func (d *RoleDAO) GetAllRoles(ctx context.Context) ([]model.Role, error) {
|
||||
funcName := "dao.role_dao.GetAllRoles"
|
||||
logs.Debug(ctx, funcName, "获取所有角色列表")
|
||||
|
||||
var roles []model.Role
|
||||
err := d.db.WithContext(ctx).Order("level ASC").Find(&roles).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "获取所有角色失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
// GetUserMaxLevel 获取用户的最高权限等级(最小 level 值)
|
||||
// 如果用户无角色,返回 math.MaxInt32 表示无权限
|
||||
func (d *RoleDAO) GetUserMaxLevel(ctx context.Context, userID int64) (int, error) {
|
||||
funcName := "dao.role_dao.GetUserMaxLevel"
|
||||
logs.Debug(ctx, funcName, "获取用户最高权限等级", zap.Int64("user_id", userID))
|
||||
|
||||
var minLevel *int
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.UserRole{}).
|
||||
Joins("JOIN auth_roles ON auth_roles.id = auth_user_roles.role_id").
|
||||
Where("auth_user_roles.user_id = ?", userID).
|
||||
Select("MIN(auth_roles.level)").
|
||||
Scan(&minLevel).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "获取用户权限等级失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
if minLevel == nil {
|
||||
return math.MaxInt32, nil
|
||||
}
|
||||
return *minLevel, nil
|
||||
}
|
||||
|
||||
// SetUserRoles 事务内先清除再批量设置用户角色
|
||||
func (d *RoleDAO) SetUserRoles(ctx context.Context, userID int64, roleIDs []int) error {
|
||||
funcName := "dao.role_dao.SetUserRoles"
|
||||
logs.Info(ctx, funcName, "设置用户角色",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Ints("role_ids", roleIDs),
|
||||
)
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("user_id = ?", userID).Delete(&model.UserRole{}).Error; err != nil {
|
||||
logs.Error(ctx, funcName, "清除用户角色失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(roleIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
userRoles := make([]model.UserRole, 0, len(roleIDs))
|
||||
for _, rid := range roleIDs {
|
||||
userRoles = append(userRoles, model.UserRole{
|
||||
UserID: userID,
|
||||
RoleID: rid,
|
||||
})
|
||||
}
|
||||
if err := tx.Create(&userRoles).Error; err != nil {
|
||||
logs.Error(ctx, funcName, "批量设置用户角色失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// FindByCodeList 按角色代码列表批量查询角色
|
||||
func (d *RoleDAO) FindByCodeList(ctx context.Context, codes []string) ([]model.Role, error) {
|
||||
funcName := "dao.role_dao.FindByCodeList"
|
||||
logs.Debug(ctx, funcName, "批量查询角色", zap.Strings("codes", codes))
|
||||
|
||||
var roles []model.Role
|
||||
err := d.db.WithContext(ctx).Where("code IN ?", codes).Find(&roles).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "批量查询角色失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type Role struct {
|
||||
ID int `json:"id" gorm:"primaryKey;autoIncrement"` // 角色唯一标识,自增主键
|
||||
Code string `json:"code" gorm:"uniqueIndex;size:50;not null"` // 角色代码,全局唯一,如 user/admin/super_admin(对应 constants.RoleCode*)
|
||||
Name string `json:"name" gorm:"size:50;not null"` // 角色中文显示名称,如「普通用户」「管理员」
|
||||
Level int `json:"level" gorm:"not null;default:100"` // 角色等级,值越小权限越高:1=超管, 10=管理员, 100=普通用户
|
||||
Description string `json:"description" gorm:"size:200;default:''"` // 角色描述说明,用于后台管理界面展示
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null;autoCreateTime;type:timestamp(0)"` // 创建时间,由 GORM 自动填充,精确到秒
|
||||
}
|
||||
|
||||
@@ -17,20 +17,28 @@ type UserListResponse struct {
|
||||
|
||||
// AdminUserInfo 管理端用户信息(比 UserInfo 更详细,包含管理字段)
|
||||
type AdminUserInfo struct {
|
||||
ID int64 `json:"id"` // 用户 ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Email string `json:"email"` // 邮箱
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像 URL
|
||||
Gender int `json:"gender"` // 性别:0=未知, 1=男, 2=女
|
||||
Phone string `json:"phone,omitempty"` // 手机号
|
||||
Status int `json:"status"` // 账号状态
|
||||
StatusText string `json:"status_text"` // 状态中文描述
|
||||
Roles []string `json:"roles"` // 角色代码列表
|
||||
LastLoginAt string `json:"last_login_at,omitempty"` // 最后登录时间
|
||||
LastLoginIP string `json:"last_login_ip,omitempty"` // 最后登录 IP
|
||||
CreatedAt string `json:"created_at"` // 注册时间
|
||||
UpdatedAt string `json:"updated_at"` // 更新时间
|
||||
ID int64 `json:"id"` // 用户 ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Email string `json:"email"` // 邮箱
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像 URL
|
||||
Gender int `json:"gender"` // 性别:0=未知, 1=男, 2=女
|
||||
Phone string `json:"phone,omitempty"` // 手机号
|
||||
Status int `json:"status"` // 账号状态
|
||||
StatusText string `json:"status_text"` // 状态中文描述
|
||||
Roles []RoleInfo `json:"roles"` // 角色详情列表(含 code/name/level)
|
||||
MaxLevel int `json:"max_level"` // 用户最高权限等级(最小 level 值)
|
||||
LastLoginAt string `json:"last_login_at,omitempty"` // 最后登录时间
|
||||
LastLoginIP string `json:"last_login_ip,omitempty"` // 最后登录 IP
|
||||
CreatedAt string `json:"created_at"` // 注册时间
|
||||
UpdatedAt string `json:"updated_at"` // 更新时间
|
||||
}
|
||||
|
||||
// RoleInfo 角色信息(用于前端角色列表展示和角色分配)
|
||||
type RoleInfo struct {
|
||||
Code string `json:"code"` // 角色代码
|
||||
Name string `json:"name"` // 角色中文名称
|
||||
Level int `json:"level"` // 角色等级,值越小权限越高
|
||||
}
|
||||
|
||||
// UpdateUserStatusRequest 更新用户状态请求
|
||||
@@ -38,9 +46,9 @@ type UpdateUserStatusRequest struct {
|
||||
Status int `json:"status" binding:"required,oneof=1 2"` // 目标状态:1=正常, 2=禁用
|
||||
}
|
||||
|
||||
// AssignRoleRequest 分配角色请求
|
||||
type AssignRoleRequest struct {
|
||||
RoleCode string `json:"role_code" binding:"required"` // 角色代码:user/admin/super_admin
|
||||
// SetRolesRequest 批量设置用户角色请求(替换原有的单角色分配)
|
||||
type SetRolesRequest struct {
|
||||
RoleCodes []string `json:"role_codes" binding:"required,min=1"` // 角色代码列表
|
||||
}
|
||||
|
||||
// AdminCreateUserRequest 管理员手动创建用户请求
|
||||
|
||||
Reference in New Issue
Block a user