feat: Iteration 3 — 咨询轮询、统计概览、埋点后端
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- consultation_service 支持 after_id 增量消息查询
- 小程序咨询详情页 8 秒轮询新消息
- 新增 DashboardStatsResp 综合统计端点 (/statistics/dashboard)
- 新增 /analytics/batch 埋点接收端点(日志记录模式)
This commit is contained in:
iven
2026-04-26 13:54:21 +08:00
parent 0cf69815d9
commit f0076aa240
10 changed files with 125 additions and 4 deletions

View File

@@ -25,3 +25,11 @@ pub struct FollowUpStatisticsResp {
pub overdue: i64,
pub completion_rate: f64,
}
/// 综合统计概览(一次调用返回全部关键指标)。
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct DashboardStatsResp {
pub patients: PatientStatisticsResp,
pub consultations: ConsultationStatisticsResp,
pub follow_ups: FollowUpStatisticsResp,
}

View File

@@ -26,6 +26,7 @@ pub struct SessionListParams {
pub struct MessageListParams {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub after_id: Option<Uuid>,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
@@ -114,7 +115,7 @@ where
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = consultation_service::list_messages(
&state, ctx.tenant_id, session_id, page, page_size,
&state, ctx.tenant_id, session_id, page, page_size, params.after_id,
)
.await?;
Ok(Json(ApiResponse::ok(result)))

View File

@@ -46,3 +46,22 @@ where
let result = stats_service::get_follow_up_statistics(&state, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_dashboard_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<DashboardStatsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let patients = stats_service::get_patient_statistics(&state, ctx.tenant_id).await?;
let consultations = stats_service::get_consultation_statistics(&state, ctx.tenant_id).await?;
let follow_ups = stats_service::get_follow_up_statistics(&state, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(DashboardStatsResp {
patients,
consultations,
follow_ups,
})))
}

View File

@@ -467,6 +467,10 @@ impl HealthModule {
"/health/admin/statistics/follow-ups",
axum::routing::get(stats_handler::get_follow_up_stats),
)
.route(
"/health/admin/statistics/dashboard",
axum::routing::get(stats_handler::get_dashboard_stats),
)
// 危急值阈值配置
.route(
"/health/critical-value-thresholds",

View File

@@ -232,15 +232,23 @@ pub async fn list_messages(
session_id: Uuid,
page: u64,
page_size: u64,
after_id: Option<Uuid>,
) -> HealthResult<PaginatedResponse<MessageResp>> {
let limit = page_size.min(100);
let offset = page.saturating_sub(1) * limit;
let query = consultation_message::Entity::find()
let mut query = consultation_message::Entity::find()
.filter(consultation_message::Column::TenantId.eq(tenant_id))
.filter(consultation_message::Column::SessionId.eq(session_id))
.filter(consultation_message::Column::DeletedAt.is_null());
// after_id 模式:返回该 ID 之后的所有消息(用于轮询增量拉取)
if let Some(aid) = after_id {
query = query.filter(consultation_message::Column::Id.gt(aid));
}
let offset = page.saturating_sub(1) * limit;
let total = query.clone().count(&state.db).await?;
let total = query.clone().count(&state.db).await?;
let models = query
.order_by_asc(consultation_message::Column::CreatedAt)

View File

@@ -0,0 +1,34 @@
use axum::Json;
use serde::Deserialize;
use tracing;
use erp_core::types::ApiResponse;
#[derive(Debug, Deserialize)]
pub struct AnalyticsEvent {
pub event: String,
pub properties: Option<serde_json::Value>,
pub timestamp: Option<String>,
pub page: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct BatchRequest {
pub events: Vec<AnalyticsEvent>,
}
/// 接收小程序批量埋点事件。
/// 当前为日志记录模式 — 后续可接入 ClickHouse/PostgreSQL 分析表。
pub async fn batch(
Json(req): Json<BatchRequest>,
) -> Json<ApiResponse<()>> {
for evt in &req.events {
tracing::info!(
event = %evt.event,
page = ?evt.page,
properties = ?evt.properties,
"Analytics event received"
);
}
Json(ApiResponse::ok(()))
}

View File

@@ -1,3 +1,4 @@
pub mod analytics;
pub mod audit_log;
pub mod crypto_admin;
pub mod health;

View File

@@ -494,6 +494,10 @@ async fn main() -> anyhow::Result<()> {
"/docs/openapi.json",
axum::routing::get(handlers::openapi::openapi_spec),
)
.route(
"/analytics/batch",
axum::routing::post(handlers::analytics::batch),
)
.layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::rate_limit::rate_limit_by_ip,