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("密码哈希失败") } go func() { // 使用带超时的独立 context(不能使用请求 ctx,该 goroutine 在请求完成后仍可能运行) 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) }