Files
EchoChat/backend/go-service/app/admin/service/user_manage_service.go
bujinyuan c8ae70ef97 fix(phase2a): handleError 增强 + 代码格式化 + 测试报告归档
- auth/contact Controller handleError 增加 fallbackMsg 可变参数,未知错误可返回更有语义的降级提示
- import 排序、变量对齐等 gofmt 格式规范化
- 新增管理端测试报告和路由验证报告

Made-with: Cursor
2026-03-03 09:51:43 +08:00

374 lines
11 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 service 提供 admin 模块的核心业务逻辑
package service
import (
"context"
"errors"
"math"
adminDAO "github.com/echochat/backend/app/admin/dao"
authDAO "github.com/echochat/backend/app/auth/dao"
"github.com/echochat/backend/app/auth/model"
"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
}