fix(n+1): 批量查询替代循环单查
- IsAdminBootstrapRequired: userRepo.GetByID 循环 → GetByIDs 批量 - AssignRoles: roleRepo.GetByID 循环 → GetByIDs 批量 - 在 userRepositoryInterface 补充 GetByIDs 方法签名
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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列表
|
||||
|
||||
Reference in New Issue
Block a user