From 326729a5ac7bd0b53cb39c1233ba674b98fd893a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 22 Sep 2026 13:42:34 +0800 Subject: [PATCH] =?UTF-8?q?Idempotency-Key=20=E6=A0=A1=E9=AA=8C=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E4=BF=9D=E7=95=99=E8=AF=B7=E6=B1=82=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server-rs/crates/api-server/src/tripo3d/routes.rs:require_idempotency_key 增加 request_context 参数,缺失 / 格式非法的两个 400 改用 into_response_with_context(Some(..)),与同处理器其它校验、定价错误一致;此前传 None,客户端拿不到请求 ID / 关联标识 验证:cargo test -p api-server --locked idempotency_key(相关 6 个用例全绿) --- .../crates/api-server/src/tripo3d/routes.rs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/server-rs/crates/api-server/src/tripo3d/routes.rs b/server-rs/crates/api-server/src/tripo3d/routes.rs index be6f6f83e..9d7eac162 100644 --- a/server-rs/crates/api-server/src/tripo3d/routes.rs +++ b/server-rs/crates/api-server/src/tripo3d/routes.rs @@ -58,7 +58,7 @@ pub(crate) async fn submit_text_to_model( payload: Result, JsonRejection>, ) -> Result<(StatusCode, Json), Response> { let payload = parse_json_payload(&request_context, payload)?; - let idempotency_key = require_idempotency_key(&headers)?; + let idempotency_key = require_idempotency_key(&headers, &request_context)?; let query = validate_text_to_model_request(&payload) .map_err(map_request_error) .map_err(|error| error.into_response_with_context(Some(&request_context)))?; @@ -86,7 +86,7 @@ pub(crate) async fn submit_image_to_model( payload: Result, JsonRejection>, ) -> Result<(StatusCode, Json), Response> { let payload = parse_json_payload(&request_context, payload)?; - let idempotency_key = require_idempotency_key(&headers)?; + let idempotency_key = require_idempotency_key(&headers, &request_context)?; let query = validate_image_to_model_request(&payload) .map_err(map_request_error) .map_err(|error| error.into_response_with_context(Some(&request_context)))?; @@ -172,7 +172,10 @@ fn target_source_entity_id( } /// 两个提交都必须显式给出 `Idempotency-Key`;缺失或格式非法一律 400,不静默生成键。 -fn require_idempotency_key(headers: &HeaderMap) -> Result<&str, Response> { +fn require_idempotency_key<'a>( + headers: &'a HeaderMap, + request_context: &RequestContext, +) -> Result<&'a str, Response> { let value = headers .get("idempotency-key") .and_then(|value| value.to_str().ok()) @@ -184,7 +187,7 @@ fn require_idempotency_key(headers: &HeaderMap) -> Result<&str, Response> { "provider": super::provider::TRIPO_PROVIDER, "message": "3D 生成提交必须携带 Idempotency-Key 请求头。", })) - .into_response_with_context(None) + .into_response_with_context(Some(request_context)) })?; if value.len() > 128 || !value.bytes().all(|byte| (0x21..=0x7e).contains(&byte)) { return Err(AppError::from_status(StatusCode::BAD_REQUEST) @@ -192,7 +195,7 @@ fn require_idempotency_key(headers: &HeaderMap) -> Result<&str, Response> { "provider": super::provider::TRIPO_PROVIDER, "message": "Idempotency-Key 必须是 1-128 个可打印 ASCII 字符,且不能包含空格。", })) - .into_response_with_context(None)); + .into_response_with_context(Some(request_context))); } Ok(value) } @@ -215,23 +218,29 @@ mod tests { #[test] fn idempotency_key_is_required_and_validated() { + let request_context = RequestContext::new( + "request-idempotency-key".to_string(), + "test".to_string(), + std::time::Duration::ZERO, + false, + ); let missing = HeaderMap::new(); - assert!(require_idempotency_key(&missing).is_err()); + assert!(require_idempotency_key(&missing, &request_context).is_err()); let mut headers = HeaderMap::new(); headers.insert("idempotency-key", HeaderValue::from_static("issue-1")); assert_eq!( - require_idempotency_key(&headers).expect("合法键应通过"), + require_idempotency_key(&headers, &request_context).expect("合法键应通过"), "issue-1" ); let mut blank = HeaderMap::new(); blank.insert("idempotency-key", HeaderValue::from_static(" ")); - assert!(require_idempotency_key(&blank).is_err()); + assert!(require_idempotency_key(&blank, &request_context).is_err()); let mut spaced = HeaderMap::new(); spaced.insert("idempotency-key", HeaderValue::from_static("a b")); - assert!(require_idempotency_key(&spaced).is_err()); + assert!(require_idempotency_key(&spaced, &request_context).is_err()); } /// 两个路由必须真的挂在应用路由上并走统一鉴权:路径写错或漏 merge 会退化成 404。