perf: Q3 N+1 查询优化 — user_service 和 plugin_service
Some checks failed
CI / rust-check (push) Has been cancelled
CI / security-audit (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled

- user_service::list() 循环内单查询改为 fetch_batch_user_role_resps 批量查询
- plugin_service::list() 循环内单查询改为 find_batch_plugin_entities 批量查询
- RoleResp 和 PluginEntityResp 添加 Clone derive
This commit is contained in:
iven
2026-04-17 19:30:12 +08:00
parent eef264c72b
commit 6a44cbecf3
4 changed files with 122 additions and 12 deletions

View File

@@ -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,

View File

@@ -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,