feat(health): 日聚合查询 API — GET /health/vital-signs/daily

- 新增 DailyAggQuery DTO(patient_id/device_type/start_date/end_date)
- 新增 get_daily_aggregations handler(需 health.device-readings.list 权限)
- 路由注册到 protected_routes
This commit is contained in:
iven
2026-05-04 02:54:13 +08:00
parent 4c1d98116a
commit 3a14b7efe3
3 changed files with 92 additions and 1 deletions

View File

@@ -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;

View File

@@ -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<uuid::Uuid>,
pub device_type: Option<String>,
pub start_date: String,
pub end_date: String,
}
pub async fn get_daily_aggregations<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Query(query): Query<DailyAggQuery>,
) -> Result<impl IntoResponse, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.device-readings.list")?;
let start = query.start_date.parse::<chrono::NaiveDate>().map_err(|_| {
AppError::Validation("Invalid start_date format, expected YYYY-MM-DD".into())
})?;
let end = query.end_date.parse::<chrono::NaiveDate>().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)))
}

View File

@@ -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<S>() -> Router<S>
where
crate::state::HealthState: axum::extract::FromRef<S>,
@@ -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<S>() -> Router<S>
@@ -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(),
},
]
}