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
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
// Package controller 提供 admin 模块的 HTTP 请求处理
|
||||
package controller
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/echochat/backend/app/admin/service"
|
||||
"github.com/echochat/backend/app/dto"
|
||||
"github.com/echochat/backend/pkg/logs"
|
||||
"github.com/echochat/backend/pkg/middleware"
|
||||
"github.com/echochat/backend/pkg/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UserManageController 管理端用户管理控制器
|
||||
type UserManageController struct {
|
||||
userManageService *service.UserManageService
|
||||
}
|
||||
|
||||
// NewUserManageController 创建用户管理控制器实例
|
||||
func NewUserManageController(svc *service.UserManageService) *UserManageController {
|
||||
return &UserManageController{userManageService: svc}
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
// GET /api/v1/admin/users?page=1&page_size=10&keyword=xxx&status=1
|
||||
func (ctl *UserManageController) GetUserList(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.GetUserList"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req dto.UserListRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
|
||||
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "获取用户列表",
|
||||
zap.Int("page", req.Page),
|
||||
zap.Int("page_size", req.PageSize),
|
||||
zap.String("keyword", req.Keyword),
|
||||
)
|
||||
|
||||
resp, err := ctl.userManageService.GetUserList(ctx, &req)
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "获取用户列表失败", zap.Error(err))
|
||||
utils.ResponseError(c, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.ResponseOK(c, resp)
|
||||
}
|
||||
|
||||
// GetUserDetail 获取用户详情
|
||||
// GET /api/v1/admin/users/:id
|
||||
func (ctl *UserManageController) GetUserDetail(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.GetUserDetail"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
utils.ResponseBadRequest(c, "无效的用户 ID")
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "获取用户详情", zap.Int64("user_id", userID))
|
||||
|
||||
detail, err := ctl.userManageService.GetUserDetail(ctx, userID)
|
||||
if err != nil {
|
||||
if err == service.ErrUserNotFound {
|
||||
utils.ResponseNotFound(c, "用户不存在")
|
||||
return
|
||||
}
|
||||
logs.Error(ctx, funcName, "获取用户详情失败", zap.Error(err))
|
||||
utils.ResponseError(c, "获取用户详情失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.ResponseOK(c, detail)
|
||||
}
|
||||
|
||||
// UpdateUserStatus 更新用户状态(启用/禁用)
|
||||
// PUT /api/v1/admin/users/:id/status
|
||||
func (ctl *UserManageController) UpdateUserStatus(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.UpdateUserStatus"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
utils.ResponseBadRequest(c, "无效的用户 ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.UpdateUserStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
|
||||
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
adminUserID, ok := middleware.GetCurrentUserID(c)
|
||||
if !ok {
|
||||
utils.ResponseUnauthorized(c, "无法获取当前用户信息")
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "更新用户状态",
|
||||
zap.Int64("target_user_id", userID),
|
||||
zap.Int("status", req.Status),
|
||||
zap.Int64("admin_user_id", adminUserID),
|
||||
)
|
||||
|
||||
if err := ctl.userManageService.UpdateUserStatus(ctx, userID, req.Status, adminUserID); err != nil {
|
||||
switch err {
|
||||
case service.ErrUserNotFound:
|
||||
utils.ResponseNotFound(c, "用户不存在")
|
||||
case service.ErrCannotDisableSelf:
|
||||
utils.ResponseBadRequest(c, "不能禁用自己的账号")
|
||||
case service.ErrInvalidStatus:
|
||||
utils.ResponseBadRequest(c, "无效的用户状态")
|
||||
default:
|
||||
logs.Error(ctx, funcName, "更新用户状态失败", zap.Error(err))
|
||||
utils.ResponseError(c, "更新用户状态失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
utils.ResponseBadRequest(c, "无效的用户 ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AssignRoleRequest
|
||||
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),
|
||||
)
|
||||
|
||||
if err := ctl.userManageService.AssignUserRole(ctx, userID, req.RoleCode); err != nil {
|
||||
switch err {
|
||||
case service.ErrUserNotFound:
|
||||
utils.ResponseNotFound(c, "用户不存在")
|
||||
case service.ErrInvalidRole:
|
||||
utils.ResponseBadRequest(c, "无效的角色代码")
|
||||
default:
|
||||
logs.Error(ctx, funcName, "分配角色失败", zap.Error(err))
|
||||
utils.ResponseError(c, "分配角色失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
utils.ResponseOK(c, nil)
|
||||
}
|
||||
|
||||
// CreateUser 管理员手动创建用户
|
||||
// POST /api/v1/admin/users
|
||||
func (ctl *UserManageController) CreateUser(c *gin.Context) {
|
||||
funcName := "controller.user_manage_controller.CreateUser"
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req dto.AdminCreateUserRequest
|
||||
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.String("username", req.Username),
|
||||
zap.String("email", logs.MaskEmail(req.Email)),
|
||||
)
|
||||
|
||||
userInfo, err := ctl.userManageService.CreateUser(ctx, &req)
|
||||
if err != nil {
|
||||
if err == service.ErrUserExists {
|
||||
utils.ResponseBadRequest(c, "用户名或邮箱已被注册")
|
||||
return
|
||||
}
|
||||
logs.Error(ctx, funcName, "创建用户失败", zap.Error(err))
|
||||
utils.ResponseError(c, "创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.ResponseOK(c, userInfo)
|
||||
}
|
||||
106
backend/go-service/app/admin/dao/user_manage_dao.go
Normal file
106
backend/go-service/app/admin/dao/user_manage_dao.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Package dao 提供 admin 模块的数据库访问操作
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/echochat/backend/app/auth/model"
|
||||
"github.com/echochat/backend/app/dto"
|
||||
"github.com/echochat/backend/pkg/logs"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserManageDAO 管理端用户数据访问对象
|
||||
// 提供面向管理后台的用户查询操作(分页、搜索、筛选)
|
||||
type UserManageDAO struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserManageDAO 创建 UserManageDAO 实例
|
||||
func NewUserManageDAO(db *gorm.DB) *UserManageDAO {
|
||||
return &UserManageDAO{db: db}
|
||||
}
|
||||
|
||||
// ListUsers 分页查询用户列表
|
||||
// 支持按用户名/邮箱关键词搜索 + 按状态筛选
|
||||
// SQL 执行两次查询:COUNT 总数 + LIMIT/OFFSET 分页数据
|
||||
func (d *UserManageDAO) ListUsers(ctx context.Context, req *dto.UserListRequest) ([]model.User, int64, error) {
|
||||
funcName := "dao.user_manage_dao.ListUsers"
|
||||
logs.Info(ctx, funcName, "查询用户列表",
|
||||
zap.Int("page", req.Page),
|
||||
zap.Int("page_size", req.PageSize),
|
||||
zap.String("keyword", req.Keyword),
|
||||
)
|
||||
|
||||
var users []model.User
|
||||
var total int64
|
||||
|
||||
query := d.db.WithContext(ctx).Model(&model.User{})
|
||||
|
||||
// 关键词搜索:模糊匹配用户名或邮箱
|
||||
if req.Keyword != "" {
|
||||
like := fmt.Sprintf("%%%s%%", req.Keyword)
|
||||
query = query.Where("username LIKE ? OR email LIKE ?", like, like)
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if req.Status != nil {
|
||||
query = query.Where("status = ?", *req.Status)
|
||||
}
|
||||
|
||||
// 先查询总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
logs.Error(ctx, funcName, "查询用户总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询数据,按创建时间倒序
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&users).Error; err != nil {
|
||||
logs.Error(ctx, funcName, "查询用户列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
logs.Info(ctx, funcName, "查询用户列表成功",
|
||||
zap.Int64("total", total),
|
||||
zap.Int("count", len(users)),
|
||||
)
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// CountUsers 统计用户总数(不含筛选条件)
|
||||
func (d *UserManageDAO) CountUsers(ctx context.Context) (int64, error) {
|
||||
funcName := "dao.user_manage_dao.CountUsers"
|
||||
|
||||
var count int64
|
||||
err := d.db.WithContext(ctx).Model(&model.User{}).Count(&count).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "统计用户总数失败", zap.Error(err))
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
// UpdateUserStatus 更新用户账号状态
|
||||
// 仅更新 status 字段,不触发其他字段的更新
|
||||
func (d *UserManageDAO) UpdateUserStatus(ctx context.Context, userID int64, status int) error {
|
||||
funcName := "dao.user_manage_dao.UpdateUserStatus"
|
||||
logs.Info(ctx, funcName, "更新用户状态",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int("status", status),
|
||||
)
|
||||
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("status", status).Error
|
||||
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "更新用户状态失败",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
return err
|
||||
}
|
||||
17
backend/go-service/app/admin/provider.go
Normal file
17
backend/go-service/app/admin/provider.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Package admin 提供管理后台功能模块
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/echochat/backend/app/admin/controller"
|
||||
"github.com/echochat/backend/app/admin/dao"
|
||||
"github.com/echochat/backend/app/admin/service"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// AdminSet Admin 模块依赖注入 Provider Set
|
||||
// 提供管理端的 DAO、Service 和 Controller 组件
|
||||
var AdminSet = wire.NewSet(
|
||||
dao.NewUserManageDAO,
|
||||
service.NewUserManageService,
|
||||
controller.NewUserManageController,
|
||||
)
|
||||
29
backend/go-service/app/admin/router.go
Normal file
29
backend/go-service/app/admin/router.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Package admin 提供管理后台功能模块
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/echochat/backend/app/admin/controller"
|
||||
"github.com/echochat/backend/app/constants"
|
||||
"github.com/echochat/backend/pkg/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterRoutes 注册 admin 模块的所有路由
|
||||
// 所有管理端 API 需要 JWT + admin 角色双重中间件
|
||||
func RegisterRoutes(
|
||||
r *gin.Engine,
|
||||
ctrl *controller.UserManageController,
|
||||
jwtAuth gin.HandlerFunc,
|
||||
) {
|
||||
// 管理端路由组:JWT 认证 + admin/super_admin 角色检查
|
||||
adminGroup := r.Group("/api/v1/admin")
|
||||
adminGroup.Use(jwtAuth, middleware.RequireRole(constants.RoleAdmin, constants.RoleSuperAdmin))
|
||||
{
|
||||
// 用户管理
|
||||
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.POST("/users", ctrl.CreateUser)
|
||||
}
|
||||
}
|
||||
272
backend/go-service/app/admin/service/user_manage_service.go
Normal file
272
backend/go-service/app/admin/service/user_manage_service.go
Normal file
@@ -0,0 +1,272 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user