Files
EchoChat/backend/go-service/app/admin/service/user_manage_service.go
bujinyuan 4d03215fe4 feat: Phase 1 完成 — 基础设施 + 用户认证 + 管理端 + 端到端验证
Phase 1 (基础设施与用户认证) 全部 11 个 Task 开发完成:

后端 (Go):
- Auth 模块: 注册/登录/JWT(有状态)/Profile/密码修改
- Admin 模块: 用户列表/详情/禁用/启用/角色分配/创建用户
- 中间件: JWT认证 + RBAC角色权限 + 请求日志 + CORS + Panic恢复
- Dockerfile 多阶段构建 + Docker Compose 全栈部署

前台 (uni-app):
- 登录/注册页面 + 自定义 TabBar + 个人中心
- 请求封装 + 状态管理 (Pinia)

管理端 (Vue 3 + Element Plus):
- 登录/仪表盘/用户列表/用户详情
- Axios 封装 + 路由守卫 + Pinia 状态管理

验证:
- 全流程 API 端到端测试通过
- Playwright 页面自动化验证通过
- code-reviewer 代码审查通过

Made-with: Cursor
2026-03-02 11:31:24 +08:00

273 lines
7.6 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"
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("不能禁用自己的账号")
)
// 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.GetUserRoleCodes(ctx, user.ID)
if roleErr != nil {
logs.Warn(ctx, funcName, "获取用户角色失败,降级为空角色列表",
zap.Int64("user_id", user.ID), zap.Error(roleErr))
roles = []string{}
}
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.GetUserRoleCodes(ctx, user.ID)
if roleErr != nil {
logs.Warn(ctx, funcName, "获取用户角色失败,降级为空角色列表",
zap.Int64("user_id", user.ID), zap.Error(roleErr))
roles = []string{}
}
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.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
}
// 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),
)
// 验证用户存在
_, err := s.userDAO.FindByID(ctx, userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrUserNotFound
}
return err
}
// 查找角色
role, err := s.roleDAO.FindByCode(ctx, roleCode)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrInvalidRole
}
return err
}
// 分配角色RoleDAO.AssignRole 内部会处理重复分配)
if err := s.roleDAO.AssignRole(ctx, userID, role.ID); err != nil {
return err
}
logs.Info(ctx, funcName, "角色分配成功",
zap.Int64("user_id", userID),
zap.String("role_code", roleCode),
)
return nil
}
// CreateUser 管理员手动创建用户
func (s *UserManageService) CreateUser(ctx context.Context, req *dto.AdminCreateUserRequest) (*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)),
)
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
}
// 加密密码
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
}
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))
}
}
roles, rolesErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
if rolesErr != nil {
logs.Warn(ctx, funcName, "获取新建用户角色失败",
zap.Int64("user_id", user.ID), zap.Error(rolesErr))
roles = []string{}
}
logs.Info(ctx, funcName, "管理员创建用户成功", zap.Int64("user_id", user.ID))
return s.buildAdminUserInfo(user, roles), nil
}
// buildAdminUserInfo 从 model.User 构建 dto.AdminUserInfo
func (s *UserManageService) buildAdminUserInfo(user *model.User, roles []string) *dto.AdminUserInfo {
if roles == nil {
roles = []string{}
}
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: roles,
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
}