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

@@ -25,7 +25,7 @@ pub struct PluginResp {
}
/// 插件实体信息
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct PluginEntityResp {
pub name: String,
pub display_name: String,

View File

@@ -1,5 +1,6 @@
use chrono::Utc;
use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, PaginatorTrait, QueryFilter, Set};
use std::collections::HashMap;
use uuid::Uuid;
use sha2::{Sha256, Digest};
@@ -212,11 +213,19 @@ impl PluginService {
.await?;
}
// 初始化
engine.initialize(plugin_manifest_id).await?;
// 初始化非致命WASM 插件可能不包含 initialize 逻辑,失败不阻塞启用)
let init_error = match engine.initialize(plugin_manifest_id).await {
Ok(()) => None,
Err(e) => {
tracing::warn!(plugin = %plugin_manifest_id, error = %e, "插件初始化失败(非致命,继续启用)");
Some(format!("初始化警告: {}", e))
}
};
// 启动事件监听
engine.start_event_listener(plugin_manifest_id).await?;
// 启动事件监听(非致命)
if let Err(e) = engine.start_event_listener(plugin_manifest_id).await {
tracing::warn!(plugin = %plugin_manifest_id, error = %e, "事件监听启动失败(非致命,继续启用)");
}
let now = Utc::now();
let mut active: plugin::ActiveModel = model.into();
@@ -224,7 +233,7 @@ impl PluginService {
active.enabled_at = Set(Some(now));
active.updated_at = Set(now);
active.updated_by = Set(Some(operator_id));
active.error_message = Set(None);
active.error_message = Set(init_error);
let model = active.update(db).await?;
let entity_resps = find_plugin_entities(plugin_id, tenant_id, db).await?;
@@ -363,6 +372,11 @@ impl PluginService {
.await?;
let mut resps = Vec::with_capacity(models.len());
// 批量查询所有插件的 entitiesN+1 → 2 固定查询)
let plugin_ids: Vec<Uuid> = models.iter().map(|m| m.id).collect();
let entities_map = find_batch_plugin_entities(&plugin_ids, tenant_id, db).await;
for model in models {
let manifest: PluginManifest =
serde_json::from_value(model.manifest_json.clone()).unwrap_or_else(|_| {
@@ -382,7 +396,7 @@ impl PluginService {
permissions: None,
}
});
let entities = find_plugin_entities(model.id, tenant_id, db).await.unwrap_or_default();
let entities = entities_map.get(&model.id).cloned().unwrap_or_default();
resps.push(plugin_model_to_resp(&model, &manifest, entities));
}
@@ -520,6 +534,35 @@ fn find_plugin(
}
}
/// 批量查询多插件的 entities返回 plugin_id → Vec<PluginEntityResp> 映射。
async fn find_batch_plugin_entities(
plugin_ids: &[Uuid],
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
) -> HashMap<Uuid, Vec<PluginEntityResp>> {
if plugin_ids.is_empty() {
return HashMap::new();
}
let entities = plugin_entity::Entity::find()
.filter(plugin_entity::Column::PluginId.is_in(plugin_ids.iter().copied()))
.filter(plugin_entity::Column::TenantId.eq(tenant_id))
.filter(plugin_entity::Column::DeletedAt.is_null())
.all(db)
.await
.unwrap_or_default();
let mut result: HashMap<Uuid, Vec<PluginEntityResp>> = HashMap::new();
for e in entities {
result.entry(e.plugin_id).or_default().push(PluginEntityResp {
name: e.entity_name.clone(),
display_name: e.entity_name,
table_name: e.table_name,
});
}
result
}
async fn find_plugin_entities(
plugin_id: Uuid,
tenant_id: Uuid,