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:
@@ -7,7 +7,10 @@ use erp_core::error::AppError;
|
||||
use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::data_dto::{CreatePluginDataReq, PluginDataListParams, PluginDataResp, UpdatePluginDataReq};
|
||||
use crate::data_dto::{
|
||||
AggregateItem, AggregateQueryParams, CountQueryParams, CreatePluginDataReq,
|
||||
PluginDataListParams, PluginDataResp, UpdatePluginDataReq,
|
||||
};
|
||||
use crate::data_service::PluginDataService;
|
||||
use crate::state::PluginState;
|
||||
|
||||
@@ -228,3 +231,98 @@ where
|
||||
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/plugins/{plugin_id}/{entity}/count",
|
||||
params(CountQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "成功", body = ApiResponse<u64>),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "插件数据"
|
||||
)]
|
||||
/// GET /api/v1/plugins/{plugin_id}/{entity}/count — 统计计数
|
||||
pub async fn count_plugin_data<S>(
|
||||
State(state): State<PluginState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path((plugin_id, entity)): Path<(Uuid, String)>,
|
||||
Query(params): Query<CountQueryParams>,
|
||||
) -> Result<Json<ApiResponse<u64>>, AppError>
|
||||
where
|
||||
PluginState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let fine_perm = compute_permission_code(&plugin_id.to_string(), &entity, "list");
|
||||
if require_permission(&ctx, &fine_perm).is_err() {
|
||||
require_permission(&ctx, "plugin.list")?;
|
||||
}
|
||||
|
||||
// 解析 filter JSON
|
||||
let filter: Option<serde_json::Value> = params
|
||||
.filter
|
||||
.as_ref()
|
||||
.and_then(|f| serde_json::from_str(f).ok());
|
||||
|
||||
let total = PluginDataService::count(
|
||||
plugin_id,
|
||||
&entity,
|
||||
ctx.tenant_id,
|
||||
&state.db,
|
||||
filter,
|
||||
params.search,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(total)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/plugins/{plugin_id}/{entity}/aggregate",
|
||||
params(AggregateQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "成功", body = ApiResponse<Vec<AggregateItem>>),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "插件数据"
|
||||
)]
|
||||
/// GET /api/v1/plugins/{plugin_id}/{entity}/aggregate — 聚合查询
|
||||
pub async fn aggregate_plugin_data<S>(
|
||||
State(state): State<PluginState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path((plugin_id, entity)): Path<(Uuid, String)>,
|
||||
Query(params): Query<AggregateQueryParams>,
|
||||
) -> Result<Json<ApiResponse<Vec<AggregateItem>>>, AppError>
|
||||
where
|
||||
PluginState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let fine_perm = compute_permission_code(&plugin_id.to_string(), &entity, "list");
|
||||
if require_permission(&ctx, &fine_perm).is_err() {
|
||||
require_permission(&ctx, "plugin.list")?;
|
||||
}
|
||||
|
||||
// 解析 filter JSON
|
||||
let filter: Option<serde_json::Value> = params
|
||||
.filter
|
||||
.as_ref()
|
||||
.and_then(|f| serde_json::from_str(f).ok());
|
||||
|
||||
let rows = PluginDataService::aggregate(
|
||||
plugin_id,
|
||||
&entity,
|
||||
ctx.tenant_id,
|
||||
&state.db,
|
||||
¶ms.group_by,
|
||||
filter,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let items = rows
|
||||
.into_iter()
|
||||
.map(|(key, count)| AggregateItem { key, count })
|
||||
.collect();
|
||||
|
||||
Ok(Json(ApiResponse::ok(items)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user