From 84b671d1e5831a5226ce818226920d1b0cff00e0 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 5 May 2026 11:56:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(server+health):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=20middleware=20=E6=B3=84=E6=BC=8F=20?= =?UTF-8?q?=E2=80=94=20FHIR/Gateway=20=E6=94=B9=E7=94=A8=20.nest()=20?= =?UTF-8?q?=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Axum 的 .merge() 会将子 Router 的 middleware 泄漏到整个路由树, 导致 FHIR OAuth middleware 和 Gateway auth middleware 拦截所有请求。 修复方式: - fhir_routes 内部路径去掉 /fhir 前缀,main.rs 用 .nest("/fhir", ...) 注册 - gateway_routes 内部路径去掉 /health/gateway 前缀,main.rs 用 .nest("/health/gateway", ...) 注册 - 透析患者查询表名 patients → patient(与 Entity 一致) Co-Authored-By: Claude Opus 4.7 --- .../src/service/dialysis_service.rs | 2 +- crates/erp-health/src/module.rs | 38 +++++++++---------- crates/erp-server/src/main.rs | 5 ++- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/crates/erp-dialysis/src/service/dialysis_service.rs b/crates/erp-dialysis/src/service/dialysis_service.rs index e4aa664..914f755 100644 --- a/crates/erp-dialysis/src/service/dialysis_service.rs +++ b/crates/erp-dialysis/src/service/dialysis_service.rs @@ -75,7 +75,7 @@ pub async fn create_dialysis_record( // 患者存在性校验 let patient_sql = sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, - "SELECT EXISTS(SELECT 1 FROM patients WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL)", + "SELECT EXISTS(SELECT 1 FROM patient WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL)", [req.patient_id.into(), tenant_id.into()], ); if let Ok(row) = state.db.query_one(patient_sql).await { diff --git a/crates/erp-health/src/module.rs b/crates/erp-health/src/module.rs index e73e60b..981a927 100644 --- a/crates/erp-health/src/module.rs +++ b/crates/erp-health/src/module.rs @@ -151,32 +151,32 @@ impl HealthModule { use crate::fhir::handler as fhir; Router::new() - .route("/fhir/R4/metadata", axum::routing::get(fhir::capability_statement)) + .route("/R4/metadata", axum::routing::get(fhir::capability_statement)) // Patient - .route("/fhir/R4/Patient", axum::routing::get(fhir::search_patients)) - .route("/fhir/R4/Patient/{id}", axum::routing::get(fhir::get_patient)) + .route("/R4/Patient", axum::routing::get(fhir::search_patients)) + .route("/R4/Patient/{id}", axum::routing::get(fhir::get_patient)) // Observation - .route("/fhir/R4/Observation", axum::routing::get(fhir::search_observations)) + .route("/R4/Observation", axum::routing::get(fhir::search_observations)) // Device - .route("/fhir/R4/Device", axum::routing::get(fhir::search_devices)) - .route("/fhir/R4/Device/{id}", axum::routing::get(fhir::get_device)) + .route("/R4/Device", axum::routing::get(fhir::search_devices)) + .route("/R4/Device/{id}", axum::routing::get(fhir::get_device)) // Practitioner - .route("/fhir/R4/Practitioner", axum::routing::get(fhir::search_practitioners)) - .route("/fhir/R4/Practitioner/{id}", axum::routing::get(fhir::get_practitioner)) + .route("/R4/Practitioner", axum::routing::get(fhir::search_practitioners)) + .route("/R4/Practitioner/{id}", axum::routing::get(fhir::get_practitioner)) // Appointment - .route("/fhir/R4/Appointment", axum::routing::get(fhir::search_appointments)) - .route("/fhir/R4/Appointment/{id}", axum::routing::get(fhir::get_appointment)) + .route("/R4/Appointment", axum::routing::get(fhir::search_appointments)) + .route("/R4/Appointment/{id}", axum::routing::get(fhir::get_appointment)) // DiagnosticReport - .route("/fhir/R4/DiagnosticReport", axum::routing::get(fhir::search_diagnostic_reports)) - .route("/fhir/R4/DiagnosticReport/{id}", axum::routing::get(fhir::get_diagnostic_report)) + .route("/R4/DiagnosticReport", axum::routing::get(fhir::search_diagnostic_reports)) + .route("/R4/DiagnosticReport/{id}", axum::routing::get(fhir::get_diagnostic_report)) // Encounter - .route("/fhir/R4/Encounter", axum::routing::get(fhir::search_encounters)) - .route("/fhir/R4/Encounter/{id}", axum::routing::get(fhir::get_encounter)) + .route("/R4/Encounter", axum::routing::get(fhir::search_encounters)) + .route("/R4/Encounter/{id}", axum::routing::get(fhir::get_encounter)) // Task - .route("/fhir/R4/Task", axum::routing::get(fhir::search_tasks)) - .route("/fhir/R4/Task/{id}", axum::routing::get(fhir::get_task)) + .route("/R4/Task", axum::routing::get(fhir::search_tasks)) + .route("/R4/Task/{id}", axum::routing::get(fhir::get_task)) // $everything - .route("/fhir/R4/Patient/{id}/$everything", axum::routing::get(fhir::patient_everything)) + .route("/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, @@ -945,8 +945,8 @@ impl HealthModule { S: Clone + Send + Sync + 'static, { Router::new() - .route("/health/gateway/upload", axum::routing::post(ble_gateway_handler::gateway_upload)) - .route("/health/gateway/heartbeat", axum::routing::post(ble_gateway_handler::gateway_heartbeat)) + .route("/upload", axum::routing::post(ble_gateway_handler::gateway_upload)) + .route("/heartbeat", axum::routing::post(ble_gateway_handler::gateway_heartbeat)) } } diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index 6a69eaf..80d6667 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -616,8 +616,9 @@ async fn main() -> anyhow::Result<()> { })); let app = Router::new() .nest("/api/v1", unthrottled_routes.merge(public_routes).merge(protected_routes)) - .merge(erp_health::HealthModule::fhir_routes().with_state(state.clone())) - .merge( + .nest("/fhir", erp_health::HealthModule::fhir_routes().with_state(state.clone())) + .nest( + "/health/gateway", erp_health::HealthModule::gateway_routes() .layer(axum::middleware::from_fn_with_state( state.clone(),