- 数据库: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
374 lines
11 KiB
Go
374 lines
11 KiB
Go
// Package service 提供 admin 模块的核心业务逻辑
|
||
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"math"
|
||
|
||
authDAO "github.com/echochat/backend/app/auth/dao"
|
||
"github.com/echochat/backend/app/auth/model"
|
||
adminDAO "github.com/echochat/backend/app/admin/dao"
|
||
"github.com/echochat/backend/app/constants"
|
||
"github.com/echochat/backend/app/dto"
|
||
"github.com/echochat/backend/pkg/logs"
|
||
"github.com/echochat/backend/pkg/utils"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
var (
|
||
ErrUserNotFound = errors.New("用户不存在")
|
||
ErrUserExists = errors.New("用户名或邮箱已被注册")
|
||
ErrInvalidStatus = errors.New("无效的用户状态")
|
||
ErrInvalidRole = errors.New("无效的角色代码")
|
||
ErrCannotDisableSelf = errors.New("不能禁用自己的账号")
|
||
ErrInsufficientPermission = errors.New("权限不足,无法操作更高等级的用户")
|
||
ErrCannotAssignHigherRole = errors.New("不能分配高于自身等级的角色")
|
||
)
|
||
|
||
// UserManageService 管理端用户管理服务
|
||
type UserManageService struct {
|
||
userManageDAO *adminDAO.UserManageDAO
|
||
userDAO *authDAO.UserDAO
|
||
roleDAO *authDAO.RoleDAO
|
||
}
|
||
|
||
// NewUserManageService 创建用户管理服务实例
|
||
func NewUserManageService(
|
||
userManageDAO *adminDAO.UserManageDAO,
|
||
userDAO *authDAO.UserDAO,
|
||
roleDAO *authDAO.RoleDAO,
|
||
) *UserManageService {
|
||
return &UserManageService{
|
||
userManageDAO: userManageDAO,
|
||
userDAO: userDAO,
|
||
roleDAO: roleDAO,
|
||
}
|
||
}
|
||
|
||
// GetUserList 获取用户列表(分页 + 搜索 + 筛选)
|
||
func (s *UserManageService) GetUserList(ctx context.Context, req *dto.UserListRequest) (*dto.UserListResponse, error) {
|
||
funcName := "service.user_manage_service.GetUserList"
|
||
logs.Info(ctx, funcName, "获取用户列表")
|
||
|
||
users, total, err := s.userManageDAO.ListUsers(ctx, req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
list := make([]dto.AdminUserInfo, 0, len(users))
|
||
for _, user := range users {
|
||
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 = []model.Role{}
|
||
}
|
||
list = append(list, *s.buildAdminUserInfo(&user, roles))
|
||
}
|
||
|
||
return &dto.UserListResponse{
|
||
Total: total,
|
||
List: list,
|
||
}, nil
|
||
}
|
||
|
||
// GetUserDetail 获取单个用户详情(含角色信息)
|
||
func (s *UserManageService) GetUserDetail(ctx context.Context, userID int64) (*dto.AdminUserInfo, error) {
|
||
funcName := "service.user_manage_service.GetUserDetail"
|
||
logs.Info(ctx, funcName, "获取用户详情", zap.Int64("user_id", userID))
|
||
|
||
user, err := s.userDAO.FindByID(ctx, userID)
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, ErrUserNotFound
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
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 = []model.Role{}
|
||
}
|
||
return s.buildAdminUserInfo(user, roles), nil
|
||
}
|
||
|
||
// UpdateUserStatus 启用/禁用用户
|
||
// 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, "更新用户状态",
|
||
zap.Int64("target_user_id", userID),
|
||
zap.Int("status", status),
|
||
zap.Int64("admin_user_id", adminUserID),
|
||
)
|
||
|
||
if userID == adminUserID {
|
||
return ErrCannotDisableSelf
|
||
}
|
||
|
||
if status != constants.UserStatusActive && status != constants.UserStatusDisabled {
|
||
return ErrInvalidStatus
|
||
}
|
||
|
||
_, err := s.userDAO.FindByID(ctx, userID)
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return ErrUserNotFound
|
||
}
|
||
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
|
||
}
|
||
|
||
logs.Info(ctx, funcName, "用户状态更新成功",
|
||
zap.Int64("user_id", userID),
|
||
zap.Int("new_status", status),
|
||
)
|
||
return nil
|
||
}
|
||
|
||
// 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) {
|
||
return ErrUserNotFound
|
||
}
|
||
return err
|
||
}
|
||
|
||
if err := s.checkPermissionLevel(ctx, adminUserID, userID); err != nil {
|
||
return err
|
||
}
|
||
|
||
roles, err := s.roleDAO.FindByCodeList(ctx, roleCodes)
|
||
if err != nil {
|
||
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
|
||
}
|
||
|
||
logs.Info(ctx, funcName, "用户角色设置成功",
|
||
zap.Int64("user_id", userID),
|
||
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 管理员手动创建用户
|
||
// 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)
|
||
if findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
|
||
return nil, findErr
|
||
}
|
||
if existing != nil {
|
||
return nil, ErrUserExists
|
||
}
|
||
existing, findErr = s.userDAO.FindByEmail(ctx, req.Email)
|
||
if findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
|
||
return nil, findErr
|
||
}
|
||
if existing != nil {
|
||
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
|
||
}
|
||
|
||
nickname := req.Nickname
|
||
if nickname == "" {
|
||
nickname = req.Username
|
||
}
|
||
|
||
user := &model.User{
|
||
Username: req.Username,
|
||
Email: req.Email,
|
||
PasswordHash: hashedPassword,
|
||
Nickname: nickname,
|
||
Status: constants.UserStatusActive,
|
||
}
|
||
if err := s.userDAO.Create(ctx, user); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
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))
|
||
}
|
||
|
||
userRoles, rolesErr := s.roleDAO.GetUserRoles(ctx, user.ID)
|
||
if rolesErr != nil {
|
||
logs.Warn(ctx, funcName, "获取新建用户角色失败",
|
||
zap.Int64("user_id", user.ID), zap.Error(rolesErr))
|
||
userRoles = []model.Role{}
|
||
}
|
||
logs.Info(ctx, funcName, "管理员创建用户成功", zap.Int64("user_id", user.ID))
|
||
return s.buildAdminUserInfo(user, userRoles), nil
|
||
}
|
||
|
||
// 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{
|
||
ID: user.ID,
|
||
Username: user.Username,
|
||
Email: user.Email,
|
||
Nickname: user.Nickname,
|
||
Avatar: user.Avatar,
|
||
Gender: user.Gender,
|
||
Status: user.Status,
|
||
StatusText: constants.UserStatusMap[user.Status],
|
||
Roles: roleInfos,
|
||
MaxLevel: minLevel,
|
||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||
}
|
||
|
||
if user.Phone != nil {
|
||
info.Phone = *user.Phone
|
||
}
|
||
if user.LastLoginAt != nil {
|
||
info.LastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
|
||
}
|
||
if user.LastLoginIP != nil {
|
||
info.LastLoginIP = *user.LastLoginIP
|
||
}
|
||
return info
|
||
}
|