fix: wrap AssignRoles in transaction and eliminate N+1 queries
- AssignRoles: wrap DeleteByUserID + BatchCreate in DB transaction (P1) - GetUserRoles: use GetByIDs batch query instead of per-role GetByID loop (N+1 fix) - ListAdmins: use GetByIDs batch query instead of per-user GetByID loop (N+1 fix) - Add WithTx/DB methods to UserRoleRepository for transaction support - Add GetByIDs to UserRepository (batch user lookup) - Add .gitattributes to normalize line endings to LF (P2)
This commit is contained in:
@@ -235,14 +235,10 @@ func (s *UserService) GetUserRoles(ctx context.Context, userID int64) ([]*domain
|
||||
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)
|
||||
// 批量获取角色详情(消除 N+1 查询)
|
||||
roles, err := s.roleRepo.GetByIDs(ctx, roleIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch roles: %w", err)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
@@ -255,19 +251,14 @@ func (s *UserService) AssignRoles(ctx context.Context, userID int64, roleIDs []i
|
||||
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{
|
||||
@@ -276,7 +267,13 @@ func (s *UserService) AssignRoles(ctx context.Context, userID int64, roleIDs []i
|
||||
})
|
||||
}
|
||||
|
||||
return s.userRoleRepo.BatchCreate(ctx, userRoles)
|
||||
// 使用事务包装删旧建新操作,确保原子性
|
||||
return s.userRoleRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.userRoleRepo.WithTx(tx).DeleteByUserID(ctx, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.userRoleRepo.WithTx(tx).BatchCreate(ctx, userRoles)
|
||||
})
|
||||
}
|
||||
|
||||
// getAdminRoleID looks up the admin role ID by code to avoid hardcoded magic numbers.
|
||||
@@ -304,14 +301,10 @@ func (s *UserService) ListAdmins(ctx context.Context) ([]*domain.User, error) {
|
||||
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)
|
||||
// 批量获取所有管理员用户(消除 N+1 查询)
|
||||
admins, err := s.userRepo.GetByIDs(ctx, adminUserIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch admin users: %w", err)
|
||||
}
|
||||
|
||||
return admins, nil
|
||||
|
||||
Reference in New Issue
Block a user