fix(health+ai+dialysis): 审计 P1 批次修复 — EventBus接入/盲索引去重/事件消费者补全
P1-2: erp-ai EventBus 接入 - handler 层 SSE 流完成/失败时发布 ai.analysis.completed/failed 事件 - build_sse_stream 新增 tenant_id 参数 P1-2: erp-dialysis EventBus 接入 - create_dialysis_record 审计后发布 dialysis.record.created 事件 P1-5: message.sent 消费者改进 - 从占位 tracing::info 升级为带 payload 详情的结构化日志 P1-7: 盲索引去重 - create_patient 中新增 id_number HMAC 去重检查(查 blind_indexes 表) - 患者创建成功后写入 blind_indexes 表(id_number + phone) - 防止同租户重复建档 P1-1: 事件消费者补全 - 新增 ai.analysis.completed 消费者(幂等处理 + 日志) - 新增 dialysis.record.created 消费者(幂等处理 + 日志)
This commit is contained in:
@@ -72,7 +72,7 @@ where
|
||||
let analysis_id_clone = analysis_id;
|
||||
let state_clone = state.clone();
|
||||
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "lab_report");
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "lab_report", ctx.tenant_id);
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ where
|
||||
let analysis_id_clone = analysis_id;
|
||||
let state_clone = state.clone();
|
||||
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "trend");
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "trend", ctx.tenant_id);
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ where
|
||||
let analysis_id_clone = analysis_id;
|
||||
let state_clone = state.clone();
|
||||
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "checkup_plan");
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "checkup_plan", ctx.tenant_id);
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ where
|
||||
let analysis_id_clone = analysis_id;
|
||||
let state_clone = state.clone();
|
||||
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "report_summary");
|
||||
let sse_stream = build_sse_stream(stream, analysis_id_clone, state_clone, "report_summary", ctx.tenant_id);
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
@@ -452,6 +452,7 @@ fn build_sse_stream(
|
||||
analysis_id: uuid::Uuid,
|
||||
state: AiState,
|
||||
analysis_type: &'static str,
|
||||
tenant_id: uuid::Uuid,
|
||||
) -> impl futures::Stream<Item = Result<Event, Infallible>> {
|
||||
async_stream::stream! {
|
||||
let mut full_content = String::new();
|
||||
@@ -472,13 +473,34 @@ fn build_sse_stream(
|
||||
let data = serde_json::to_string(&event).unwrap_or_default();
|
||||
yield Ok(Event::default().event("error").data(data));
|
||||
let _ = state.analysis.fail_analysis(analysis_id, e.to_string()).await;
|
||||
// 发布 AI 分析失败事件
|
||||
let fail_event = erp_core::events::DomainEvent::new(
|
||||
"ai.analysis.failed",
|
||||
tenant_id,
|
||||
erp_core::events::build_event_payload(serde_json::json!({
|
||||
"analysis_id": analysis_id,
|
||||
"error": e.to_string(),
|
||||
})),
|
||||
);
|
||||
state.event_bus.publish(fail_event, &state.db).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = serde_json::json!({"analysis_type": analysis_type});
|
||||
let _ = state.analysis.complete_analysis(analysis_id, full_content, metadata).await;
|
||||
let _ = state.analysis.complete_analysis(analysis_id, full_content.clone(), metadata).await;
|
||||
|
||||
// 发布 AI 分析完成事件
|
||||
let event = erp_core::events::DomainEvent::new(
|
||||
"ai.analysis.completed",
|
||||
tenant_id,
|
||||
erp_core::events::build_event_payload(serde_json::json!({
|
||||
"analysis_id": analysis_id,
|
||||
"analysis_type": analysis_type,
|
||||
})),
|
||||
);
|
||||
state.event_bus.publish(event, &state.db).await;
|
||||
|
||||
let done_event = AnalysisSseEvent::Done {
|
||||
analysis_id,
|
||||
|
||||
Reference in New Issue
Block a user