diff --git a/crates/erp-health/src/service/consent_service.rs b/crates/erp-health/src/service/consent_service.rs index e1ad24f..6c913af 100644 --- a/crates/erp-health/src/service/consent_service.rs +++ b/crates/erp-health/src/service/consent_service.rs @@ -23,7 +23,7 @@ pub async fn list_consents( let limit = page_size.min(100); let offset = page.saturating_sub(1) * limit; - let mut query = consent::Entity::find() + let query = consent::Entity::find() .filter(consent::Column::TenantId.eq(tenant_id)) .filter(consent::Column::PatientId.eq(patient_id)) .filter(consent::Column::DeletedAt.is_null()); diff --git a/crates/erp-health/src/service/health_data_service.rs b/crates/erp-health/src/service/health_data_service.rs index 8c76b92..017e411 100644 --- a/crates/erp-health/src/service/health_data_service.rs +++ b/crates/erp-health/src/service/health_data_service.rs @@ -86,7 +86,7 @@ pub async fn create_vital_signs( .ok_or(HealthError::PatientNotFound)?; let now = Utc::now(); - check_vital_signs_alert(state, tenant_id, patient_id, operator_id, req.clone()).await; + let alert_req = req.clone(); let active = vital_signs::ActiveModel { id: Set(Uuid::now_v7()), tenant_id: Set(tenant_id), @@ -112,11 +112,15 @@ pub async fn create_vital_signs( }; let m = active.insert(&state.db).await?; + // 数据持久化成功后再触发危急值检测 + check_vital_signs_alert(state, tenant_id, patient_id, operator_id, alert_req).await; + audit_service::record( AuditLog::new(tenant_id, operator_id, "vital_signs.created", "vital_signs") .with_resource_id(m.id), &state.db, - ).await; + ) + .await; Ok(VitalSignsResp { id: m.id, patient_id: m.patient_id, record_date: m.record_date, diff --git a/crates/erp-message/src/service/message_service.rs b/crates/erp-message/src/service/message_service.rs index 88f890f..c3212e7 100644 --- a/crates/erp-message/src/service/message_service.rs +++ b/crates/erp-message/src/service/message_service.rs @@ -148,6 +148,9 @@ impl MessageService { } /// 系统发送消息(由事件处理器调用)。 + /// + /// 幂等保证:当 `business_id` 存在时,若同 tenant + recipient + business_id 的消息已存在, + /// 直接返回已有消息,避免 outbox relay 重放导致重复通知。 #[allow(clippy::too_many_arguments)] pub async fn send_system( tenant_id: Uuid, @@ -160,6 +163,27 @@ impl MessageService { db: &sea_orm::DatabaseConnection, event_bus: &EventBus, ) -> MessageResult { + // 幂等检查:防止 outbox relay 重放导致重复消息 + if let Some(bid) = business_id { + let existing = message::Entity::find() + .filter(message::Column::TenantId.eq(tenant_id)) + .filter(message::Column::RecipientId.eq(recipient_id)) + .filter(message::Column::BusinessId.eq(bid)) + .filter(message::Column::DeletedAt.is_null()) + .one(db) + .await + .map_err(|e| MessageError::Validation(e.to_string()))?; + + if let Some(m) = existing { + tracing::debug!( + message_id = %m.id, + business_id = %bid, + "消息已存在,跳过重复创建(幂等保护)" + ); + return Ok(Self::model_to_resp(&m)); + } + } + let id = Uuid::now_v7(); let now = Utc::now(); let system_user = Uuid::nil();