feat: Iteration 3 — 咨询轮询、统计概览、埋点后端
- consultation_service 支持 after_id 增量消息查询 - 小程序咨询详情页 8 秒轮询新消息 - 新增 DashboardStatsResp 综合统计端点 (/statistics/dashboard) - 新增 /analytics/batch 埋点接收端点(日志记录模式)
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user