diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 7c8ae8e..597130f 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -14,27 +14,66 @@ import ( "gorm.io/gorm" ) +// Repository interfaces for dependency inversion (DIP) — service layer depends on these abstractions, not concrete types. +type userRepository interface { + GetByID(ctx context.Context, id int64) (*domain.User, error) + GetByUsername(ctx context.Context, username string) (*domain.User, error) + GetByEmail(ctx context.Context, email string) (*domain.User, error) + Create(ctx context.Context, user *domain.User) error + Update(ctx context.Context, user *domain.User) error + Delete(ctx context.Context, id int64) error + List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error) + ListCursor(ctx context.Context, filter *repository.AdvancedFilter, limit int, cursor *pagination.Cursor) ([]*domain.User, bool, error) + GetByIDs(ctx context.Context, ids []int64) ([]*domain.User, error) + UpdateStatus(ctx context.Context, id int64, status domain.UserStatus) error + BatchUpdateStatus(ctx context.Context, ids []int64, status domain.UserStatus) error + BatchDelete(ctx context.Context, ids []int64) error + DB() *gorm.DB +} + +type userRoleRepository interface { + GetByUserID(ctx context.Context, userID int64) ([]*domain.UserRole, error) + DeleteByUserID(ctx context.Context, userID int64) error + DeleteByUserAndRole(ctx context.Context, userID, roleID int64) error + GetByRoleID(ctx context.Context, roleID int64) ([]*domain.UserRole, error) + GetUserIDByRoleID(ctx context.Context, roleID int64) ([]int64, error) + BatchCreate(ctx context.Context, userRoles []*domain.UserRole) error + DB() *gorm.DB +} + +type roleRepository interface { + GetByCode(ctx context.Context, code string) (*domain.Role, error) + GetByID(ctx context.Context, id int64) (*domain.Role, error) + GetByIDs(ctx context.Context, ids []int64) ([]*domain.Role, error) +} + +type passwordHistoryRepository interface { + GetByUserID(ctx context.Context, userID int64, limit int) ([]*domain.PasswordHistory, error) + Create(ctx context.Context, history *domain.PasswordHistory) error + DeleteOldRecords(ctx context.Context, userID int64, keep int) error +} + // UserService 用户服务 type UserService struct { - userRepo *repository.UserRepository - userRoleRepo *repository.UserRoleRepository - roleRepo *repository.RoleRepository - passwordHistoryRepo *repository.PasswordHistoryRepository + userRepo userRepository + userRoleRepo userRoleRepository + roleRepo roleRepository + passwordHistoryRepo passwordHistoryRepository } const passwordHistoryLimit = 5 // 保留最近5条密码历史 // NewUserService 创建用户服务实例 func NewUserService( - userRepo *repository.UserRepository, - userRoleRepo *repository.UserRoleRepository, - roleRepo *repository.RoleRepository, - passwordHistoryRepo *repository.PasswordHistoryRepository, + userRepo userRepository, + userRoleRepo userRoleRepository, + roleRepo roleRepository, + passwordHistoryRepo passwordHistoryRepository, ) *UserService { return &UserService{ userRepo: userRepo, userRoleRepo: userRoleRepo, - roleRepo: roleRepo, + roleRepo: roleRepo, passwordHistoryRepo: passwordHistoryRepo, } } @@ -146,8 +185,16 @@ type ListCursorRequest struct { Size int `form:"size"` } +// UserCursorResult wraps cursor-based pagination response for users +type UserCursorResult struct { + Items []*domain.User `json:"items"` + NextCursor string `json:"next_cursor"` + HasMore bool `json:"has_more"` + PageSize int `json:"page_size"` +} + // ListCursor 游标分页获取用户列表(推荐使用) -func (s *UserService) ListCursor(ctx context.Context, req *ListCursorRequest) (*CursorResult, error) { +func (s *UserService) ListCursor(ctx context.Context, req *ListCursorRequest) (*UserCursorResult, error) { size := pagination.ClampPageSize(req.Size) cursor, err := pagination.Decode(req.Cursor) @@ -176,7 +223,7 @@ func (s *UserService) ListCursor(ctx context.Context, req *ListCursorRequest) (* nextCursor = pagination.BuildNextCursor(last.ID, last.CreatedAt) } - return &CursorResult{ + return &UserCursorResult{ Items: users, NextCursor: nextCursor, HasMore: hasMore, @@ -268,11 +315,16 @@ func (s *UserService) AssignRoles(ctx context.Context, userID int64, roleIDs []i } // 使用事务包装删旧建新操作,确保原子性 + // Note: WithTx is on concrete type, requires type assertion + txRepo, ok := s.userRoleRepo.(*repository.UserRoleRepository) + if !ok { + return errors.New("userRoleRepo does not support transactions") + } return s.userRoleRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { - if err := s.userRoleRepo.WithTx(tx).DeleteByUserID(ctx, userID); err != nil { + if err := txRepo.WithTx(tx).DeleteByUserID(ctx, userID); err != nil { return err } - return s.userRoleRepo.WithTx(tx).BatchCreate(ctx, userRoles) + return txRepo.WithTx(tx).BatchCreate(ctx, userRoles) }) }