From 3a14b7efe3aa2ba7cc7cdf718fab320f926e6b4c Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 4 May 2026 02:54:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(health):=20=E6=97=A5=E8=81=9A=E5=90=88?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=20API=20=E2=80=94=20GET=20/health/vital-sign?= =?UTF-8?q?s/daily?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DailyAggQuery DTO(patient_id/device_type/start_date/end_date) - 新增 get_daily_aggregations handler(需 health.device-readings.list 权限) - 路由注册到 protected_routes --- crates/erp-health/src/handler/mod.rs | 1 + .../src/handler/vital_signs_daily_handler.rs | 51 +++++++++++++++++++ crates/erp-health/src/module.rs | 41 ++++++++++++++- 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 crates/erp-health/src/handler/vital_signs_daily_handler.rs diff --git a/crates/erp-health/src/handler/mod.rs b/crates/erp-health/src/handler/mod.rs index 94dfc7a..40340a5 100644 --- a/crates/erp-health/src/handler/mod.rs +++ b/crates/erp-health/src/handler/mod.rs @@ -22,3 +22,4 @@ pub mod health_data_handler; pub mod patient_handler; pub mod points_handler; pub mod stats_handler; +pub mod vital_signs_daily_handler; diff --git a/crates/erp-health/src/handler/vital_signs_daily_handler.rs b/crates/erp-health/src/handler/vital_signs_daily_handler.rs new file mode 100644 index 0000000..b3710da --- /dev/null +++ b/crates/erp-health/src/handler/vital_signs_daily_handler.rs @@ -0,0 +1,51 @@ +use axum::extract::{FromRef, Query, State}; +use axum::response::IntoResponse; +use axum::Extension; +use serde::Deserialize; +use utoipa::IntoParams; + +use erp_core::error::AppError; +use erp_core::rbac::require_permission; +use erp_core::types::{ApiResponse, TenantContext}; + +use crate::service::vital_signs_daily_service; +use crate::state::HealthState; + +#[derive(Debug, Deserialize, IntoParams)] +pub struct DailyAggQuery { + pub patient_id: Option, + pub device_type: Option, + pub start_date: String, + pub end_date: String, +} + +pub async fn get_daily_aggregations( + State(state): State, + Extension(ctx): Extension, + Query(query): Query, +) -> Result +where + HealthState: FromRef, + S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "health.device-readings.list")?; + + let start = query.start_date.parse::().map_err(|_| { + AppError::Validation("Invalid start_date format, expected YYYY-MM-DD".into()) + })?; + let end = query.end_date.parse::().map_err(|_| { + AppError::Validation("Invalid end_date format, expected YYYY-MM-DD".into()) + })?; + + let results = vital_signs_daily_service::query_daily( + &state.db, + ctx.tenant_id, + query.patient_id, + query.device_type, + start, + end, + ) + .await?; + + Ok(axum::Json(ApiResponse::ok(results))) +} diff --git a/crates/erp-health/src/module.rs b/crates/erp-health/src/module.rs index a8bb68a..e812fb3 100644 --- a/crates/erp-health/src/module.rs +++ b/crates/erp-health/src/module.rs @@ -10,6 +10,7 @@ use crate::handler::{ alert_handler, alert_rule_handler, appointment_handler, article_category_handler, article_handler, article_tag_handler, consultation_handler, consent_handler, critical_alert_handler, critical_value_threshold_handler, daily_monitoring_handler, device_handler, device_reading_handler, diagnosis_handler, doctor_handler, follow_up_handler, follow_up_template_handler, health_data_handler, medication_record_handler, medication_reminder_handler, patient_handler, points_handler, stats_handler, + vital_signs_daily_handler, }; pub struct HealthModule; @@ -137,9 +138,10 @@ impl HealthModule { S: Clone + Send + Sync + 'static, { Router::new() + .route("/oauth/token", axum::routing::post(crate::oauth::handler::token)) } - /// FHIR R4 只读路由(复用 JWT 认证中间件) + /// FHIR R4 只读路由(使用 OAuth client_credentials 认证) pub fn fhir_routes() -> Router where crate::state::HealthState: axum::extract::FromRef, @@ -174,6 +176,10 @@ impl HealthModule { .route("/fhir/R4/Task/{id}", axum::routing::get(fhir::get_task)) // $everything .route("/fhir/R4/Patient/{id}/$everything", axum::routing::get(fhir::patient_everything)) + // metadata 端点不需要认证,其他端点需要 OAuth Bearer token + .layer(axum::middleware::from_fn( + crate::oauth::middleware::oauth_auth_middleware, + )) } pub fn protected_routes() -> Router @@ -721,6 +727,11 @@ impl HealthModule { "/health/patients/{patient_id}/device-readings/hourly", axum::routing::get(device_reading_handler::list_hourly), ) + // 日聚合查询 + .route( + "/health/vital-signs/daily", + axum::routing::get(vital_signs_daily_handler::get_daily_aggregations), + ) // 告警路由 .route( "/health/alerts", @@ -790,6 +801,21 @@ impl HealthModule { "/health/action-inbox/{source_ref}/thread", axum::routing::get(action_inbox_handler::get_action_thread), ) + // OAuth 合作方管理 + .route( + "/health/oauth/clients", + axum::routing::get(crate::oauth::handler::list_clients) + .post(crate::oauth::handler::create_client), + ) + .route( + "/health/oauth/clients/{id}", + axum::routing::put(crate::oauth::handler::update_client) + .delete(crate::oauth::handler::delete_client), + ) + .route( + "/health/oauth/clients/{id}/regenerate-secret", + axum::routing::post(crate::oauth::handler::regenerate_secret), + ) } } @@ -1197,6 +1223,19 @@ impl ErpModule for HealthModule { description: "查看系统健康、用户活跃度、模块状态等管理统计".into(), module: "health".into(), }, + // OAuth 合作方管理 + PermissionDescriptor { + code: "health.oauth.list".into(), + name: "查看合作方".into(), + description: "查看 FHIR API 合作方列表".into(), + module: "health".into(), + }, + PermissionDescriptor { + code: "health.oauth.manage".into(), + name: "管理合作方".into(), + description: "创建/编辑/删除 FHIR API 合作方".into(), + module: "health".into(), + }, ] }