Files
user-system/internal/service/user_service.go
long-agent 12a5be9826 fix: suppress gosec G115/G118 false positive warnings
- G115 (integer overflow): Added nosec comments for safe type conversions
  where values are bounded by design (e.g., rng.Intn(255) returns 0-254)
- G118 (context.Background): Added nosec for intentional async goroutines
  that use WithTimeout for bounded execution after request completes

Note: G101 (hardcoded credentials) warnings are low-confidence false
positives - OAuth fields use getEnv() to read from environment.
2026-04-08 22:50:42 +08:00

214 lines
6.2 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
}