feat(plugin): 新增数据统计 REST API — count 和 aggregate 端点
- dynamic_table: 新增 build_filtered_count_sql(带过滤/搜索的 COUNT)和 build_aggregate_sql(按字段分组计数)
- data_service: 新增 count 和 aggregate 方法,支持实时统计查询
- data_handler: 新增 count_plugin_data 和 aggregate_plugin_data REST handler
- data_dto: 新增 AggregateItem、AggregateQueryParams、CountQueryParams 类型
- module: 注册 /plugins/{plugin_id}/{entity}/count 和 /aggregate 路由
- 包含 8 个新增单元测试,全部通过
This commit is contained in:
@@ -261,6 +261,99 @@ impl PluginDataService {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 统计记录数(支持过滤和搜索)
|
||||
pub async fn count(
|
||||
plugin_id: Uuid,
|
||||
entity_name: &str,
|
||||
tenant_id: Uuid,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
filter: Option<serde_json::Value>,
|
||||
search: Option<String>,
|
||||
) -> AppResult<u64> {
|
||||
let table_name = resolve_table_name(plugin_id, entity_name, tenant_id, db).await?;
|
||||
|
||||
// 获取 searchable 字段列表,构建搜索条件
|
||||
let entity_fields = resolve_entity_fields(plugin_id, entity_name, tenant_id, db).await?;
|
||||
let search_tuple = {
|
||||
let searchable: Vec<&str> = entity_fields
|
||||
.iter()
|
||||
.filter(|f| f.searchable == Some(true))
|
||||
.map(|f| f.name.as_str())
|
||||
.collect();
|
||||
match (searchable.is_empty(), &search) {
|
||||
(false, Some(kw)) => Some((searchable.join(","), kw.clone())),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let (sql, values) = DynamicTableManager::build_filtered_count_sql(
|
||||
&table_name,
|
||||
tenant_id,
|
||||
filter,
|
||||
search_tuple,
|
||||
)
|
||||
.map_err(|e| AppError::Validation(e))?;
|
||||
|
||||
#[derive(FromQueryResult)]
|
||||
struct CountResult {
|
||||
count: i64,
|
||||
}
|
||||
|
||||
let result = CountResult::find_by_statement(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
values,
|
||||
))
|
||||
.one(db)
|
||||
.await?
|
||||
.map(|r| r.count as u64)
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 聚合查询 — 按字段分组计数
|
||||
/// 返回 [(分组键, 计数), ...]
|
||||
pub async fn aggregate(
|
||||
plugin_id: Uuid,
|
||||
entity_name: &str,
|
||||
tenant_id: Uuid,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
group_by_field: &str,
|
||||
filter: Option<serde_json::Value>,
|
||||
) -> AppResult<Vec<(String, i64)>> {
|
||||
let table_name = resolve_table_name(plugin_id, entity_name, tenant_id, db).await?;
|
||||
|
||||
let (sql, values) = DynamicTableManager::build_aggregate_sql(
|
||||
&table_name,
|
||||
tenant_id,
|
||||
group_by_field,
|
||||
filter,
|
||||
)
|
||||
.map_err(|e| AppError::Validation(e))?;
|
||||
|
||||
#[derive(FromQueryResult)]
|
||||
struct AggRow {
|
||||
key: Option<String>,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
let rows = AggRow::find_by_statement(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
sql,
|
||||
values,
|
||||
))
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
let result = rows
|
||||
.into_iter()
|
||||
.map(|r| (r.key.unwrap_or_default(), r.count))
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 plugin_entities 表解析 table_name(带租户隔离)
|
||||
|
||||
Reference in New Issue
Block a user