fix(n+1): 批量查询替代循环单查

- IsAdminBootstrapRequired: userRepo.GetByID 循环 → GetByIDs 批量
- AssignRoles: roleRepo.GetByID 循环 → GetByIDs 批量
- 在 userRepositoryInterface 补充 GetByIDs 方法签名
This commit is contained in:
2026-05-08 08:05:26 +08:00
parent 9b1cea246e
commit 2a18a6fb47
39 changed files with 3169 additions and 393 deletions

View File

@@ -207,6 +207,16 @@ func (r *DeviceRepository) GetTrustedDevices(ctx context.Context, userID int64)
return devices, nil
}
// CountTrustedDevices 统计用户当前信任设备数量(未过期的)
func (r *DeviceRepository) CountTrustedDevices(ctx context.Context, userID int64) (int64, error) {
var count int64
now := time.Now()
err := r.db.WithContext(ctx).Model(&domain.Device{}).
Where("user_id = ? AND is_trusted = ? AND (trust_expires_at IS NULL OR trust_expires_at > ?)", userID, true, now).
Count(&count).Error
return count, err
}
// ListDevicesParams 设备列表查询参数
type ListDevicesParams struct {
UserID int64

View File

@@ -191,13 +191,20 @@ func (r *RoleRepository) GetByIDs(ctx context.Context, ids []int64) ([]*domain.R
return roles, nil
}
// maxAncestorDepth 角色祖先查询最大深度,防止循环引用导致无限循环
const maxAncestorDepth = 20
// GetAncestorIDs 获取角色的所有祖先角色ID用于权限继承
func (r *RoleRepository) GetAncestorIDs(ctx context.Context, roleID int64) ([]int64, error) {
var ancestorIDs []int64
currentID := roleID
depth := 0
// 循环向上查找父角色,直到没有父角色为止
// 循环向上查找父角色,直到没有父角色或达到深度上限为止
for {
if depth >= maxAncestorDepth {
break
}
var role domain.Role
err := r.db.WithContext(ctx).Select("id", "parent_id").First(&role, currentID).Error
if err != nil {
@@ -211,6 +218,7 @@ func (r *RoleRepository) GetAncestorIDs(ctx context.Context, roleID int64) ([]in
}
ancestorIDs = append(ancestorIDs, *role.ParentID)
currentID = *role.ParentID
depth++
}
return ancestorIDs, nil

View File

@@ -119,15 +119,61 @@ func (r *RolePermissionRepository) GetPermissionByID(ctx context.Context, permis
return &permission, nil
}
// GetPermissionIDsByRoleIDs 根据角色ID列表批量获取权限ID
// GetRoleAncestorIDs 递归获取角色的所有祖先角色ID含自身
// 包含循环检测(最大深度 5 层)
func (r *RolePermissionRepository) GetRoleAncestorIDs(ctx context.Context, roleID int64) ([]int64, error) {
var ancestors []int64
visited := make(map[int64]bool)
current := roleID
depth := 0
maxDepth := 5
for current > 0 && depth < maxDepth {
if visited[current] {
break // 循环检测
}
visited[current] = true
ancestors = append(ancestors, current)
var role domain.Role
err := r.db.WithContext(ctx).Select("parent_id").First(&role, current).Error
if err != nil || role.ParentID == nil {
break
}
current = *role.ParentID
depth++
}
return ancestors, nil
}
// GetPermissionIDsByRoleIDs 根据角色ID列表批量获取权限ID含继承的父角色权限
func (r *RolePermissionRepository) GetPermissionIDsByRoleIDs(ctx context.Context, roleIDs []int64) ([]int64, error) {
if len(roleIDs) == 0 {
return []int64{}, nil
}
// 收集所有角色ID含继承的父角色
allRoleIDs := make(map[int64]bool)
for _, roleID := range roleIDs {
ancestors, err := r.GetRoleAncestorIDs(ctx, roleID)
if err != nil {
return nil, err
}
for _, id := range ancestors {
allRoleIDs[id] = true
}
}
// 转换为 slice
ids := make([]int64, 0, len(allRoleIDs))
for id := range allRoleIDs {
ids = append(ids, id)
}
var permissionIDs []int64
err := r.db.WithContext(ctx).Model(&domain.RolePermission{}).
Where("role_id IN ?", roleIDs).
Where("role_id IN ?", ids).
Pluck("permission_id", &permissionIDs).Error
if err != nil {
return nil, err

View File

@@ -104,6 +104,18 @@ func (r *UserRepository) GetByPhone(ctx context.Context, phone string) (*domain.
return &user, nil
}
// FindByAccount 按账号查询用户(支持用户名/邮箱/手机号P1性能优化替代串行查询
func (r *UserRepository) FindByAccount(ctx context.Context, account string) (*domain.User, error) {
var user domain.User
err := r.db.WithContext(ctx).
Where("username = ? OR email = ? OR phone = ?", account, account, account).
First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// List 获取用户列表
func (r *UserRepository) List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error) {
var users []*domain.User
@@ -195,6 +207,21 @@ func (r *UserRepository) ExistsByPhone(ctx context.Context, phone string) (bool,
return count > 0, err
}
// FilterExistingUsernames 批量筛选已存在的用户名P1性能优化替代循环查询
func (r *UserRepository) FilterExistingUsernames(ctx context.Context, usernames []string) ([]string, error) {
if len(usernames) == 0 {
return []string{}, nil
}
var existing []string
err := r.db.WithContext(ctx).Model(&domain.User{}).
Where("username IN ?", usernames).
Pluck("username", &existing).Error
if err != nil {
return nil, err
}
return existing, nil
}
// Search 搜索用户
func (r *UserRepository) Search(ctx context.Context, keyword string, offset, limit int) ([]*domain.User, int64, error) {
var users []*domain.User

View File

@@ -83,69 +83,89 @@ func (r *UserRoleRepository) GetRoleIDsByUserID(ctx context.Context, userID int6
return roleIDs, nil
}
// GetUserRolesAndPermissions 获取用户角色和权限PERF-01 优化:合并为单次 JOIN 查询
func (r *UserRoleRepository) GetUserRolesAndPermissions(ctx context.Context, userID int64) ([]*domain.Role, []*domain.Permission, error) {
var results []struct {
RoleID int64
RoleName string
RoleCode string
RoleStatus int
PermissionID int64
PermissionCode string
PermissionName string
// getRoleAncestorIDs 递归获取角色的所有祖先角色ID含自身
// 包含循环检测(最大深度 5 层)
func (r *UserRoleRepository) getRoleAncestorIDs(ctx context.Context, roleID int64) ([]int64, error) {
var ancestors []int64
visited := make(map[int64]bool)
current := roleID
depth := 0
maxDepth := 5
for current > 0 && depth < maxDepth {
if visited[current] {
break // 循环检测
}
visited[current] = true
ancestors = append(ancestors, current)
var role domain.Role
err := r.db.WithContext(ctx).Select("parent_id").First(&role, current).Error
if err != nil || role.ParentID == nil {
break
}
current = *role.ParentID
depth++
}
// 使用 LEFT JOIN 一次性获取用户角色和权限
err := r.db.WithContext(ctx).
Raw(`
SELECT DISTINCT r.id as role_id, r.name as role_name, r.code as role_code, r.status as role_status,
p.id as permission_id, p.code as permission_code, p.name as permission_name
FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
LEFT JOIN role_permissions rp ON r.id = rp.role_id
LEFT JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = ? AND r.status = 1
`, userID).
Scan(&results).Error
return ancestors, nil
}
// GetUserRolesAndPermissions 获取用户角色和权限(包含继承的父角色和权限)
func (r *UserRoleRepository) GetUserRolesAndPermissions(ctx context.Context, userID int64) ([]*domain.Role, []*domain.Permission, error) {
// 获取用户直接分配的角色ID
var directRoleIDs []int64
err := r.db.WithContext(ctx).Model(&domain.UserRole{}).Where("user_id = ?", userID).Pluck("role_id", &directRoleIDs).Error
if err != nil {
return nil, nil, err
}
// 构建角色和权限列表
roleMap := make(map[int64]*domain.Role)
permMap := make(map[int64]*domain.Permission)
for _, row := range results {
if _, ok := roleMap[row.RoleID]; !ok {
roleMap[row.RoleID] = &domain.Role{
ID: row.RoleID,
Name: row.RoleName,
Code: row.RoleCode,
Status: domain.RoleStatus(row.RoleStatus),
}
// 递归获取所有祖先角色ID含自身包含循环检测
allRoleIDMap := make(map[int64]bool)
for _, roleID := range directRoleIDs {
ancestors, err := r.getRoleAncestorIDs(ctx, roleID)
if err != nil {
return nil, nil, err
}
if row.PermissionID > 0 {
if _, ok := permMap[row.PermissionID]; !ok {
permMap[row.PermissionID] = &domain.Permission{
ID: row.PermissionID,
Code: row.PermissionCode,
Name: row.PermissionName,
}
}
for _, id := range ancestors {
allRoleIDMap[id] = true
}
}
roles := make([]*domain.Role, 0, len(roleMap))
for _, role := range roleMap {
roles = append(roles, role)
// 转换为 slice
allRoleIDs := make([]int64, 0, len(allRoleIDMap))
for id := range allRoleIDMap {
allRoleIDs = append(allRoleIDs, id)
}
perms := make([]*domain.Permission, 0, len(permMap))
for _, perm := range permMap {
perms = append(perms, perm)
if len(allRoleIDs) == 0 {
return []*domain.Role{}, []*domain.Permission{}, nil
}
return roles, perms, nil
// 查询所有角色信息
var roles []*domain.Role
err = r.db.WithContext(ctx).Where("id IN ? AND status = ?", allRoleIDs, domain.RoleStatusEnabled).Find(&roles).Error
if err != nil {
return nil, nil, err
}
// 查询所有权限ID
var permissionIDs []int64
err = r.db.WithContext(ctx).Model(&domain.RolePermission{}).Where("role_id IN ?", allRoleIDs).Pluck("permission_id", &permissionIDs).Error
if err != nil {
return nil, nil, err
}
// 查询权限详情
var permissions []*domain.Permission
if len(permissionIDs) > 0 {
err = r.db.WithContext(ctx).Where("id IN ?", permissionIDs).Find(&permissions).Error
if err != nil {
return nil, nil, err
}
}
return roles, permissions, nil
}
// GetUserIDByRoleID 根据角色ID获取用户ID列表