diff --git a/server-rs/crates/api-server/src/aliyun_matting.rs b/server-rs/crates/api-server/src/aliyun_matting.rs index 2bccca389..cf2e9a4e0 100644 --- a/server-rs/crates/api-server/src/aliyun_matting.rs +++ b/server-rs/crates/api-server/src/aliyun_matting.rs @@ -17,12 +17,9 @@ pub(crate) async fn segment_image_with_aliyun_matting( image: &DownloadedOpenAiImage, log_label: &str, ) -> Result { - let matting_client = state.matting_client().ok_or_else(|| { - AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ - "provider": "aliyun-matting", - "message": "阿里云抠图客户端未配置或未启用。", - })) - })?; + let matting_client = state + .matting_client() + .ok_or_else(aliyun_matting_unconfigured_error)?; let file_name = format!("{log_label}.png"); let started_at = std::time::Instant::now(); @@ -81,6 +78,14 @@ fn aliyun_matting_error_is_transport(message: &str) -> bool { aliyun_matting_error_http_status(message).is_none() } +fn aliyun_matting_unconfigured_error() -> AppError { + AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ + "provider": "aliyun-matting", + "message": "阿里云抠图客户端未配置或未启用。", + "externalCallAttempted": false, + })) +} + #[cfg(test)] mod tests { use super::*; @@ -122,4 +127,11 @@ mod tests { "通用抠图接口返回失败(HTTP 429,Code=Throttled):QPS exceeded" )); } + + #[test] + fn aliyun_matting_unconfigured_error_marks_no_external_call_attempt() { + let error = aliyun_matting_unconfigured_error(); + + assert!(!crate::external_api_audit::matting_failure_external_call_attempted(&error)); + } } diff --git a/server-rs/crates/api-server/src/character_animation_assets.rs b/server-rs/crates/api-server/src/character_animation_assets.rs index 03e83157c..deb266331 100644 --- a/server-rs/crates/api-server/src/character_animation_assets.rs +++ b/server-rs/crates/api-server/src/character_animation_assets.rs @@ -2238,21 +2238,23 @@ async fn remove_editor_character_animation_frame_backgrounds( error_details = ?error.details(), "editor_animation_frame_aliyun_matting_fallback_to_local" ); - crate::external_api_audit::record_matting_external_api_failure( - state, - audit, - "aliyun-matting", - state.config.aliyun_matting_endpoint.clone(), - "editor-character-animation-frame-matting", - "aliyun_segment", - crate::external_api_audit::matting_failure_audit_status_code(&error), - crate::external_api_audit::matting_failure_audit_timeout(&error), - crate::external_api_audit::matting_failure_audit_latency_ms(&error), - error.message().to_string(), - crate::external_api_audit::matting_failure_audit_raw_excerpt(&error) - .or_else(|| Some(format!("frame_index={frame_index}"))), - ) - .await; + if crate::external_api_audit::matting_failure_external_call_attempted(&error) { + crate::external_api_audit::record_matting_external_api_failure( + state, + audit, + "aliyun-matting", + state.config.aliyun_matting_endpoint.clone(), + "editor-character-animation-frame-matting", + "aliyun_segment", + crate::external_api_audit::matting_failure_audit_status_code(&error), + crate::external_api_audit::matting_failure_audit_timeout(&error), + crate::external_api_audit::matting_failure_audit_latency_ms(&error), + error.message().to_string(), + crate::external_api_audit::matting_failure_audit_raw_excerpt(&error) + .or_else(|| Some(format!("frame_index={frame_index}"))), + ) + .await; + } remove_editor_generated_green_screen_background(&image, screen_color)? } }; diff --git a/server-rs/crates/api-server/src/editor_project.rs b/server-rs/crates/api-server/src/editor_project.rs index c4576087b..ab683f709 100644 --- a/server-rs/crates/api-server/src/editor_project.rs +++ b/server-rs/crates/api-server/src/editor_project.rs @@ -2304,20 +2304,22 @@ async fn fallback_editor_screen_background_removal( error_details = ?error.details(), "editor_aliyun_matting_fallback_to_local_screen_background_removal" ); - crate::external_api_audit::record_matting_external_api_failure( - state, - audit, - "aliyun-matting", - state.config.aliyun_matting_endpoint.clone(), - "editor-screen-background-removal", - "aliyun_segment", - crate::external_api_audit::matting_failure_audit_status_code(&error), - crate::external_api_audit::matting_failure_audit_timeout(&error), - crate::external_api_audit::matting_failure_audit_latency_ms(&error), - error.message().to_string(), - crate::external_api_audit::matting_failure_audit_raw_excerpt(&error), - ) - .await; + if crate::external_api_audit::matting_failure_external_call_attempted(&error) { + crate::external_api_audit::record_matting_external_api_failure( + state, + audit, + "aliyun-matting", + state.config.aliyun_matting_endpoint.clone(), + "editor-screen-background-removal", + "aliyun_segment", + crate::external_api_audit::matting_failure_audit_status_code(&error), + crate::external_api_audit::matting_failure_audit_timeout(&error), + crate::external_api_audit::matting_failure_audit_latency_ms(&error), + error.message().to_string(), + crate::external_api_audit::matting_failure_audit_raw_excerpt(&error), + ) + .await; + } remove_editor_generated_green_screen_background(image, screen_color) } } diff --git a/server-rs/crates/api-server/src/external_api_audit.rs b/server-rs/crates/api-server/src/external_api_audit.rs index 9f9417e49..e62d3a0fb 100644 --- a/server-rs/crates/api-server/src/external_api_audit.rs +++ b/server-rs/crates/api-server/src/external_api_audit.rs @@ -248,6 +248,14 @@ pub(crate) fn matting_failure_audit_latency_ms(error: &AppError) -> Option .and_then(Value::as_u64) } +pub(crate) fn matting_failure_external_call_attempted(error: &AppError) -> bool { + error + .details() + .and_then(|details| details.get("externalCallAttempted")) + .and_then(Value::as_bool) + .unwrap_or(true) +} + pub(crate) fn matting_failure_audit_raw_excerpt(error: &AppError) -> Option { error .details() @@ -741,4 +749,20 @@ mod tests { assert_eq!(tracking.metadata["retryable"], false); assert_eq!(tracking.metadata["latencyMs"], 67); } + + #[test] + fn matting_failure_external_call_attempted_defaults_to_true() { + let attempted = AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "aliyun-matting", + "message": "真实上游失败", + })); + let skipped = AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ + "provider": "aliyun-matting", + "message": "阿里云抠图客户端未配置或未启用。", + "externalCallAttempted": false, + })); + + assert!(matting_failure_external_call_attempted(&attempted)); + assert!(!matting_failure_external_call_attempted(&skipped)); + } }