perf: Q3 N+1 查询优化 — user_service 和 plugin_service
- user_service::list() 循环内单查询改为 fetch_batch_user_role_resps 批量查询 - plugin_service::list() 循环内单查询改为 find_batch_plugin_entities 批量查询 - RoleResp 和 PluginEntityResp 添加 Clone derive
This commit is contained in:
@@ -73,7 +73,7 @@ pub struct UpdateUserReq {
|
||||
|
||||
// --- Role DTOs ---
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct RoleResp {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use chrono::Utc;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::dto::{CreateUserReq, RoleResp, UpdateUserReq, UserResp};
|
||||
@@ -174,10 +175,12 @@ impl UserService {
|
||||
.map_err(|e| AuthError::Validation(e.to_string()))?;
|
||||
|
||||
let mut resps = Vec::with_capacity(models.len());
|
||||
// 批量查询所有用户的角色(N+1 → 3 固定查询)
|
||||
let user_ids: Vec<Uuid> = models.iter().map(|m| m.id).collect();
|
||||
let role_map = Self::fetch_batch_user_role_resps(&user_ids, tenant_id, db).await;
|
||||
|
||||
for m in models {
|
||||
let roles: Vec<RoleResp> = Self::fetch_user_role_resps(m.id, tenant_id, db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let roles = role_map.get(&m.id).cloned().unwrap_or_default();
|
||||
resps.push(model_to_resp(&m, roles));
|
||||
}
|
||||
|
||||
@@ -368,7 +371,71 @@ impl UserService {
|
||||
Self::fetch_user_role_resps(user_id, tenant_id, db).await
|
||||
}
|
||||
|
||||
/// Fetch RoleResp DTOs for a given user within a tenant.
|
||||
/// 批量查询多用户的角色,返回 user_id → RoleResp 映射。
|
||||
///
|
||||
/// 使用 3 次固定查询替代 N+1:用户角色关联 → 角色 → 分组组装。
|
||||
async fn fetch_batch_user_role_resps(
|
||||
user_ids: &[Uuid],
|
||||
tenant_id: Uuid,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> HashMap<Uuid, Vec<RoleResp>> {
|
||||
if user_ids.is_empty() {
|
||||
return HashMap::new();
|
||||
}
|
||||
|
||||
// 1. 批量查询 user_role 关联
|
||||
let user_roles: Vec<user_role::Model> = user_role::Entity::find()
|
||||
.filter(user_role::Column::UserId.is_in(user_ids.iter().copied()))
|
||||
.filter(user_role::Column::TenantId.eq(tenant_id))
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let role_ids: Vec<Uuid> = user_roles.iter().map(|ur| ur.role_id).collect();
|
||||
|
||||
// 2. 批量查询角色
|
||||
let roles: Vec<role::Model> = if role_ids.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
role::Entity::find()
|
||||
.filter(role::Column::Id.is_in(role_ids.iter().copied()))
|
||||
.filter(role::Column::TenantId.eq(tenant_id))
|
||||
.filter(role::Column::DeletedAt.is_null())
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let role_map: HashMap<Uuid, &role::Model> =
|
||||
roles.iter().map(|r| (r.id, r)).collect();
|
||||
|
||||
// 3. 按 user_id 分组
|
||||
let mut result: HashMap<Uuid, Vec<RoleResp>> = HashMap::new();
|
||||
for ur in &user_roles {
|
||||
let resp = role_map
|
||||
.get(&ur.role_id)
|
||||
.map(|r| RoleResp {
|
||||
id: r.id,
|
||||
name: r.name.clone(),
|
||||
code: r.code.clone(),
|
||||
description: r.description.clone(),
|
||||
is_system: r.is_system,
|
||||
version: r.version,
|
||||
})
|
||||
.unwrap_or_else(|| RoleResp {
|
||||
id: ur.role_id,
|
||||
name: "Unknown".into(),
|
||||
code: "unknown".into(),
|
||||
description: None,
|
||||
is_system: false,
|
||||
version: 0,
|
||||
});
|
||||
result.entry(ur.user_id).or_default().push(resp);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Fetch role details for a single user, returning RoleResp DTOs.
|
||||
async fn fetch_user_role_resps(
|
||||
user_id: Uuid,
|
||||
tenant_id: Uuid,
|
||||
|
||||
Reference in New Issue
Block a user