Files
user-system/internal/service/user_service.go

374 lines
10 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
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/user-management-system/internal/auth"
"github.com/user-management-system/internal/domain"
"github.com/user-management-system/internal/pagination"
"github.com/user-management-system/internal/repository"
)
// UserService 用户服务
type UserService struct {
userRepo *repository.UserRepository
userRoleRepo *repository.UserRoleRepository
roleRepo *repository.RoleRepository
passwordHistoryRepo *repository.PasswordHistoryRepository
}
const passwordHistoryLimit = 5 // 保留最近5条密码历史
// NewUserService 创建用户服务实例
func NewUserService(
userRepo *repository.UserRepository,
userRoleRepo *repository.UserRoleRepository,
roleRepo *repository.RoleRepository,
passwordHistoryRepo *repository.PasswordHistoryRepository,
) *UserService {
return &UserService{
userRepo: userRepo,
userRoleRepo: userRoleRepo,
roleRepo: roleRepo,
passwordHistoryRepo: passwordHistoryRepo,
}
}
// ChangePassword 修改用户密码(含历史记录检查)
func (s *UserService) ChangePassword(ctx context.Context, userID int64, oldPassword, newPassword string) error {
if s.userRepo == nil {
return errors.New("user repository is not configured")
}
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return errors.New("用户不存在")
}
// 验证旧密码
if strings.TrimSpace(oldPassword) == "" {
return errors.New("请输入当前密码")
}
if !auth.VerifyPassword(user.Password, oldPassword) {
return errors.New("当前密码不正确")
}
// 检查新密码强度
if strings.TrimSpace(newPassword) == "" {
return errors.New("新密码不能为空")
}
if err := validatePasswordStrength(newPassword, 8, false); err != nil {
return err
}
// 检查密码历史
if s.passwordHistoryRepo != nil {
histories, err := s.passwordHistoryRepo.GetByUserID(ctx, userID, passwordHistoryLimit)
if err == nil && len(histories) > 0 {
for _, h := range histories {
if auth.VerifyPassword(h.PasswordHash, newPassword) {
return errors.New("新密码不能与最近5次密码相同")
}
}
}
// 保存新密码到历史记录
newHashedPassword, hashErr := auth.HashPassword(newPassword)
if hashErr != nil {
return errors.New("密码哈希失败")
}
// #nosec G118 - 使用带超时的独立 context不能使用请求 ctx该 goroutine 在请求完成后仍可能运行)
go func() { // #nosec G118
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = s.passwordHistoryRepo.Create(bgCtx, &domain.PasswordHistory{
UserID: userID,
PasswordHash: newHashedPassword,
})
_ = s.passwordHistoryRepo.DeleteOldRecords(bgCtx, userID, passwordHistoryLimit)
}()
}
// 更新密码
newHashedPassword, err := auth.HashPassword(newPassword)
if err != nil {
return errors.New("密码哈希失败")
}
user.Password = newHashedPassword
return s.userRepo.Update(ctx, user)
}
// GetByID 根据ID获取用户
func (s *UserService) GetByID(ctx context.Context, id int64) (*domain.User, error) {
return s.userRepo.GetByID(ctx, id)
}
// GetByEmail 根据邮箱获取用户
func (s *UserService) GetByEmail(ctx context.Context, email string) (*domain.User, error) {
return s.userRepo.GetByEmail(ctx, email)
}
// Create 创建用户
func (s *UserService) Create(ctx context.Context, user *domain.User) error {
return s.userRepo.Create(ctx, user)
}
// Update 更新用户
func (s *UserService) Update(ctx context.Context, user *domain.User) error {
return s.userRepo.Update(ctx, user)
}
// Delete 删除用户
func (s *UserService) Delete(ctx context.Context, id int64) error {
return s.userRepo.Delete(ctx, id)
}
// List 获取用户列表
func (s *UserService) List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error) {
return s.userRepo.List(ctx, offset, limit)
}
// ListCursorRequest 用户游标分页请求
type ListCursorRequest struct {
Keyword string `form:"keyword"`
Status int `form:"status"` // -1=全部
RoleIDs []int64
CreatedFrom *time.Time
CreatedTo *time.Time
SortBy string // created_at, last_login_time, username
SortOrder string // asc, desc
Cursor string `form:"cursor"`
Size int `form:"size"`
}
// ListCursor 游标分页获取用户列表(推荐使用)
func (s *UserService) ListCursor(ctx context.Context, req *ListCursorRequest) (*CursorResult, error) {
size := pagination.ClampPageSize(req.Size)
cursor, err := pagination.Decode(req.Cursor)
if err != nil {
return nil, fmt.Errorf("invalid cursor: %w", err)
}
filter := &repository.AdvancedFilter{
Keyword: req.Keyword,
Status: req.Status,
RoleIDs: req.RoleIDs,
CreatedFrom: req.CreatedFrom,
CreatedTo: req.CreatedTo,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
users, hasMore, err := s.userRepo.ListCursor(ctx, filter, size, cursor)
if err != nil {
return nil, err
}
nextCursor := ""
if len(users) > 0 {
last := users[len(users)-1]
nextCursor = pagination.BuildNextCursor(last.ID, last.CreatedAt)
}
return &CursorResult{
Items: users,
NextCursor: nextCursor,
HasMore: hasMore,
PageSize: size,
}, nil
}
// UpdateStatus 更新用户状态
func (s *UserService) UpdateStatus(ctx context.Context, id int64, status domain.UserStatus) error {
return s.userRepo.UpdateStatus(ctx, id, status)
}
// BatchUpdateStatusRequest 批量更新状态请求
type BatchUpdateStatusRequest struct {
IDs []int64 `json:"ids" binding:"required,min=1"`
Status domain.UserStatus `json:"status" binding:"required"`
}
// BatchDeleteRequest 批量删除请求
type BatchDeleteRequest struct {
IDs []int64 `json:"ids" binding:"required,min=1"`
}
// BatchUpdateStatus 批量更新用户状态
func (s *UserService) BatchUpdateStatus(ctx context.Context, req *BatchUpdateStatusRequest) (int64, error) {
err := s.userRepo.BatchUpdateStatus(ctx, req.IDs, req.Status)
return int64(len(req.IDs)), err
}
// BatchDelete 批量删除用户
func (s *UserService) BatchDelete(ctx context.Context, req *BatchDeleteRequest) (int64, error) {
err := s.userRepo.BatchDelete(ctx, req.IDs)
return int64(len(req.IDs)), err
}
// GetUserRoles 获取用户的所有角色
func (s *UserService) GetUserRoles(ctx context.Context, userID int64) ([]*domain.Role, error) {
// 检查用户是否存在
if _, err := s.userRepo.GetByID(ctx, userID); err != nil {
return nil, err
}
// 获取用户角色关联
userRoles, err := s.userRoleRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, err
}
if len(userRoles) == 0 {
return []*domain.Role{}, nil
}
// 获取角色ID列表
roleIDs := make([]int64, len(userRoles))
for i, ur := range userRoles {
roleIDs[i] = ur.RoleID
}
// 批量获取角色详情
var roles []*domain.Role
for _, roleID := range roleIDs {
role, err := s.roleRepo.GetByID(ctx, roleID)
if err != nil {
continue // 跳过不存在的角色
}
roles = append(roles, role)
}
return roles, nil
}
// AssignRoles 分配用户角色
func (s *UserService) AssignRoles(ctx context.Context, userID int64, roleIDs []int64) error {
// 检查用户是否存在
if _, err := s.userRepo.GetByID(ctx, userID); err != nil {
return err
}
// 验证所有角色存在
for _, roleID := range roleIDs {
if _, err := s.roleRepo.GetByID(ctx, roleID); err != nil {
return fmt.Errorf("角色 %d 不存在", roleID)
}
}
// 删除用户现有角色
if err := s.userRoleRepo.DeleteByUserID(ctx, userID); err != nil {
return err
}
// 创建新的用户角色关联
var userRoles []*domain.UserRole
for _, roleID := range roleIDs {
userRoles = append(userRoles, &domain.UserRole{
UserID: userID,
RoleID: roleID,
})
}
return s.userRoleRepo.BatchCreate(ctx, userRoles)
}
// AdminRoleID is the ID of the admin role
const AdminRoleID = 1
// ListAdmins 获取所有管理员
func (s *UserService) ListAdmins(ctx context.Context) ([]*domain.User, error) {
// 获取管理员角色ID列表
adminUserIDs, err := s.userRoleRepo.GetUserIDByRoleID(ctx, AdminRoleID)
if err != nil {
return nil, err
}
if len(adminUserIDs) == 0 {
return []*domain.User{}, nil
}
// 获取所有管理员用户
var admins []*domain.User
for _, adminID := range adminUserIDs {
user, err := s.userRepo.GetByID(ctx, adminID)
if err != nil {
continue // 跳过不存在的用户
}
admins = append(admins, user)
}
return admins, nil
}
// CreateAdmin 创建管理员
func (s *UserService) CreateAdmin(ctx context.Context, req *CreateAdminRequest) (*domain.User, error) {
// 检查用户名是否已存在
existingUser, err := s.userRepo.GetByUsername(ctx, req.Username)
if err == nil && existingUser != nil {
return nil, errors.New("用户名已存在")
}
// 创建用户
hashedPassword, err := auth.HashPassword(req.Password)
if err != nil {
return nil, errors.New("密码哈希失败")
}
user := &domain.User{
Username: req.Username,
Password: hashedPassword,
Status: domain.UserStatusActive,
}
if req.Email != "" {
user.Email = &req.Email
}
if req.Nickname != "" {
user.Nickname = req.Nickname
}
if err := s.userRepo.Create(ctx, user); err != nil {
return nil, err
}
// 分配管理员角色
userRole := &domain.UserRole{
UserID: user.ID,
RoleID: AdminRoleID,
}
if err := s.userRoleRepo.Create(ctx, userRole); err != nil {
return nil, err
}
return user, nil
}
// DeleteAdmin 删除管理员(移除管理员角色)
func (s *UserService) DeleteAdmin(ctx context.Context, userID int64) error {
// 检查用户是否存在
if _, err := s.userRepo.GetByID(ctx, userID); err != nil {
return err
}
// 不能删除自己
// 注意这里需要从handler传入当前用户ID进行校验
// 删除用户的管理员角色
return s.userRoleRepo.DeleteByUserAndRole(ctx, userID, AdminRoleID)
}
// CreateAdminRequest 创建管理员请求
type CreateAdminRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Email string `json:"email"`
Nickname string `json:"nickname"`
}