From 50d6245829632b7d87eeb0f3006a04c17089c35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 2 Sep 2026 19:59:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=94=99=E8=AF=AF=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E5=B9=82=E7=AD=89=E4=BA=8B=E4=BB=B6=E9=9B=86=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 幂等命中时读取原归档并返回原事件 ID 相同 submissionId 重放事件集合不一致时返回 409 --- .../crates/api-server/src/error_reports.rs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/server-rs/crates/api-server/src/error_reports.rs b/server-rs/crates/api-server/src/error_reports.rs index 93dcf974c..7613dda39 100644 --- a/server-rs/crates/api-server/src/error_reports.rs +++ b/server-rs/crates/api-server/src/error_reports.rs @@ -192,21 +192,62 @@ pub async fn create_error_report( .with_message("报告元数据写入失败")); } }; - if record.batch_id != batch_id { + let accepted_event_ids = if record.batch_id != batch_id { + let existing_archive = oss + .get_object( + state.editor_oss_http_client(), + OssGetObjectRequest { + object_key: record.object_key.clone(), + max_bytes: MAX_BATCH_BYTES, + }, + ) + .await + .map_err(map_error_report_oss_error)?; + let (existing_events, _, _) = + parse_archive(&existing_archive).map_err(|e| internal(format!("报告归档损坏:{e}")))?; + let mut requested_event_ids = payload + .events + .iter() + .map(|event| event.event_id.clone()) + .collect::>(); + let existing_event_ids = existing_events + .iter() + .map(|event| event.event_id.clone()) + .collect::>(); + let mut existing_event_ids_sorted = existing_event_ids.clone(); + requested_event_ids.sort_unstable(); + existing_event_ids_sorted.sort_unstable(); + if requested_event_ids != existing_event_ids_sorted { + let _ = oss + .delete_object( + state.editor_oss_http_client(), + OssDeleteObjectRequest { object_key }, + ) + .await; + return Err(AppError::from_status(StatusCode::CONFLICT) + .with_message("相同 submissionId 的错误报告事件集合不一致")); + } let _ = oss .delete_object( state.editor_oss_http_client(), OssDeleteObjectRequest { object_key }, ) .await; - } + existing_event_ids + } else { + payload + .events + .into_iter() + .map(|event| event.event_id) + .collect() + }; Ok(json_success_body( Some(&ctx), CreateErrorReportBatchResponse { batch_id: record.batch_id, submission_id: record.submission_id, status: "ready".into(), - accepted_event_ids: payload.events.into_iter().map(|e| e.event_id).collect(), + accepted_event_ids, created_at: record.created_at, }, ))