feat(ai): Phase 1C 管理看板 — 用量/成本/功能开关三合一
- UsageService 新增 get_daily_usage + aggregate_daily 日聚合能力 - 新增 3 个管理端点: /ai/admin/daily-usage, /ai/admin/flags (GET+POST) - AiUsageDashboard 扩展为三 Tab: 用量概览/成本分析/功能开关 - 功能开关支持 Switch 实时切换,权限码 ai.admin.flags - 日聚合用量 30 天趋势表,含 Token/成本汇总统计
This commit is contained in:
@@ -832,6 +832,107 @@ where
|
||||
Ok(Json(ApiResponse::ok(estimate)))
|
||||
}
|
||||
|
||||
// === AI 管理看板 ===
|
||||
|
||||
#[derive(Debug, Deserialize, utoipa::IntoParams)]
|
||||
pub struct DailyUsageQuery {
|
||||
pub start_date: String,
|
||||
pub end_date: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/admin/daily-usage",
|
||||
params(DailyUsageQuery),
|
||||
responses((status = 200, description = "按日聚合用量")),
|
||||
tag = "AI 管理",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn admin_daily_usage<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Query(params): Query<DailyUsageQuery>,
|
||||
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||
where
|
||||
AiState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "ai.admin.dashboard")?;
|
||||
|
||||
let start_date = chrono::NaiveDate::parse_from_str(¶ms.start_date, "%Y-%m-%d")
|
||||
.map_err(|_| erp_core::error::AppError::Validation("start_date 格式错误".into()))?;
|
||||
let end_date = chrono::NaiveDate::parse_from_str(¶ms.end_date, "%Y-%m-%d")
|
||||
.map_err(|_| erp_core::error::AppError::Validation("end_date 格式错误".into()))?;
|
||||
|
||||
let rows = state
|
||||
.usage
|
||||
.get_daily_usage(ctx.tenant_id, start_date, end_date)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(serde_json::json!({
|
||||
"data": rows,
|
||||
"start_date": params.start_date,
|
||||
"end_date": params.end_date,
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/admin/flags",
|
||||
responses((status = 200, description = "功能开关列表")),
|
||||
tag = "AI 管理",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn admin_list_flags<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
) -> Result<
|
||||
Json<ApiResponse<Vec<crate::service::feature_flag_service::FeatureFlag>>>,
|
||||
erp_core::error::AppError,
|
||||
>
|
||||
where
|
||||
AiState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "ai.admin.flags")?;
|
||||
let flags = state.feature_flags.get_all(ctx.tenant_id).await?;
|
||||
Ok(Json(ApiResponse::ok(flags)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateFlagBody {
|
||||
pub feature: String,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/admin/flags",
|
||||
request_body = UpdateFlagBody,
|
||||
responses((status = 200, description = "更新功能开关")),
|
||||
tag = "AI 管理",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn admin_update_flag<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Json(body): Json<UpdateFlagBody>,
|
||||
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||
where
|
||||
AiState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "ai.admin.flags")?;
|
||||
state
|
||||
.feature_flags
|
||||
.set_enabled(ctx.tenant_id, &body.feature, body.enabled, ctx.user_id)
|
||||
.await?;
|
||||
Ok(Json(ApiResponse::ok(serde_json::json!({
|
||||
"feature": body.feature,
|
||||
"enabled": body.enabled,
|
||||
}))))
|
||||
}
|
||||
|
||||
// === SSE 流构建辅助 ===
|
||||
|
||||
fn build_sse_stream(
|
||||
|
||||
Reference in New Issue
Block a user