fix: apply DIP to UserService with local repository interfaces
- Define userRepository, userRoleRepository, roleRepository, passwordHistoryRepository interfaces - Update UserService struct to use interface types instead of concrete *repository types - Update NewUserService constructor to accept interfaces - Add UserCursorResult type (avoid conflict with login_log.go's CursorResult) - Fix AssignRoles to use type assertion for WithTx (concrete method not in interface) - Add GetByEmail, UpdateStatus, BatchUpdateStatus, BatchDelete to userRepository interface - Add GetByID, GetByIDs to roleRepository interface This enables dependency injection and mocking at the service layer.
This commit is contained in:
@@ -14,27 +14,66 @@ import (
|
|||||||
"gorm.io/gorm"
|
"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 用户服务
|
// UserService 用户服务
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
userRepo *repository.UserRepository
|
userRepo userRepository
|
||||||
userRoleRepo *repository.UserRoleRepository
|
userRoleRepo userRoleRepository
|
||||||
roleRepo *repository.RoleRepository
|
roleRepo roleRepository
|
||||||
passwordHistoryRepo *repository.PasswordHistoryRepository
|
passwordHistoryRepo passwordHistoryRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHistoryLimit = 5 // 保留最近5条密码历史
|
const passwordHistoryLimit = 5 // 保留最近5条密码历史
|
||||||
|
|
||||||
// NewUserService 创建用户服务实例
|
// NewUserService 创建用户服务实例
|
||||||
func NewUserService(
|
func NewUserService(
|
||||||
userRepo *repository.UserRepository,
|
userRepo userRepository,
|
||||||
userRoleRepo *repository.UserRoleRepository,
|
userRoleRepo userRoleRepository,
|
||||||
roleRepo *repository.RoleRepository,
|
roleRepo roleRepository,
|
||||||
passwordHistoryRepo *repository.PasswordHistoryRepository,
|
passwordHistoryRepo passwordHistoryRepository,
|
||||||
) *UserService {
|
) *UserService {
|
||||||
return &UserService{
|
return &UserService{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
userRoleRepo: userRoleRepo,
|
userRoleRepo: userRoleRepo,
|
||||||
roleRepo: roleRepo,
|
roleRepo: roleRepo,
|
||||||
passwordHistoryRepo: passwordHistoryRepo,
|
passwordHistoryRepo: passwordHistoryRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,8 +185,16 @@ type ListCursorRequest struct {
|
|||||||
Size int `form:"size"`
|
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 游标分页获取用户列表(推荐使用)
|
// 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)
|
size := pagination.ClampPageSize(req.Size)
|
||||||
|
|
||||||
cursor, err := pagination.Decode(req.Cursor)
|
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)
|
nextCursor = pagination.BuildNextCursor(last.ID, last.CreatedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CursorResult{
|
return &UserCursorResult{
|
||||||
Items: users,
|
Items: users,
|
||||||
NextCursor: nextCursor,
|
NextCursor: nextCursor,
|
||||||
HasMore: hasMore,
|
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 {
|
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 err
|
||||||
}
|
}
|
||||||
return s.userRoleRepo.WithTx(tx).BatchCreate(ctx, userRoles)
|
return txRepo.WithTx(tx).BatchCreate(ctx, userRoles)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user