修复抠图失败审计元数据

区分抠图传输失败与上游 HTTP 状态

补齐 BgFilter 与阿里云抠图失败 latencyMs

同步阿里云抠图配置与角色动作背景色文档口径
This commit is contained in:
2026-07-09 08:32:44 +00:00
parent 1aeb905e80
commit 2971352cfc
6 changed files with 91 additions and 8 deletions
@@ -158,6 +158,7 @@ pub(crate) async fn record_matting_external_api_failure(
failure_stage: &'static str,
status_code: Option<u16>,
timeout: bool,
latency_ms: Option<u64>,
error_message: String,
raw_excerpt: Option<String>,
) {
@@ -171,6 +172,7 @@ pub(crate) async fn record_matting_external_api_failure(
timeout,
false,
))
.with_latency_ms(latency_ms)
.with_raw_excerpt(raw_excerpt)
.with_audit_context(context);
record_external_api_failure(state, draft).await;
@@ -183,7 +185,7 @@ pub(crate) fn matting_failure_audit_status_code(error: &AppError) -> Option<u16>
.and_then(Value::as_u64)
.and_then(|value| u16::try_from(value).ok())
.or_else(|| {
if matting_failure_audit_timeout(error) {
if matting_failure_audit_is_transport(error) {
None
} else {
Some(error.status_code().as_u16())
@@ -191,6 +193,15 @@ pub(crate) fn matting_failure_audit_status_code(error: &AppError) -> Option<u16>
})
}
fn matting_failure_audit_is_transport(error: &AppError) -> bool {
matting_failure_audit_timeout(error)
|| error
.details()
.and_then(|details| details.get("transport"))
.and_then(Value::as_bool)
.unwrap_or(false)
}
pub(crate) fn matting_failure_audit_timeout(error: &AppError) -> bool {
error
.details()
@@ -199,6 +210,13 @@ pub(crate) fn matting_failure_audit_timeout(error: &AppError) -> bool {
.unwrap_or(false)
}
pub(crate) fn matting_failure_audit_latency_ms(error: &AppError) -> Option<u64> {
error
.details()
.and_then(|details| details.get("latencyMs"))
.and_then(Value::as_u64)
}
pub(crate) fn matting_failure_audit_raw_excerpt(error: &AppError) -> Option<String> {
error
.details()
@@ -541,6 +559,7 @@ mod tests {
"message": "BgFilter 服务返回非成功状态",
"upstreamStatus": 429,
"upstreamMessage": "too many requests",
"latencyMs": 345,
}));
let status_code = matting_failure_audit_status_code(&error);
@@ -556,6 +575,7 @@ mod tests {
.with_status_code(status_code)
.with_optional_status_class(Some(status_class(status_code)))
.with_timeout(timeout)
.with_latency_ms(matting_failure_audit_latency_ms(&error))
.with_retryable(is_retryable_external_api_failure(
status_code,
timeout,
@@ -568,6 +588,7 @@ mod tests {
assert_eq!(tracking.metadata["statusClass"], "4xx");
assert_eq!(tracking.metadata["timeout"], false);
assert_eq!(tracking.metadata["retryable"], true);
assert_eq!(tracking.metadata["latencyMs"], 345);
assert_eq!(tracking.metadata["rawExcerpt"], "too many requests");
}
@@ -604,4 +625,42 @@ mod tests {
assert_eq!(tracking.metadata["timeout"], true);
assert_eq!(tracking.metadata["retryable"], true);
}
#[test]
fn matting_failure_audit_keeps_non_timeout_transport_classification() {
let error = AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "bgfilter",
"message": "请求 BgFilter 服务失败:dns error",
"timeout": false,
"transport": true,
"latencyMs": 67,
}));
let status_code = matting_failure_audit_status_code(&error);
let timeout = matting_failure_audit_timeout(&error);
let tracking = build_external_api_failure_tracking_draft(
&ExternalApiFailureDraft::new(
"bgfilter",
"https://bgfilter.example/remove-background",
"editor-screen-background-removal",
"bgfilter_segment",
error.message(),
)
.with_status_code(status_code)
.with_optional_status_class(Some(status_class(status_code)))
.with_timeout(timeout)
.with_latency_ms(matting_failure_audit_latency_ms(&error))
.with_retryable(is_retryable_external_api_failure(
status_code,
timeout,
false,
)),
);
assert_eq!(tracking.metadata["statusCode"], Value::Null);
assert_eq!(tracking.metadata["statusClass"], "transport");
assert_eq!(tracking.metadata["timeout"], false);
assert_eq!(tracking.metadata["retryable"], false);
assert_eq!(tracking.metadata["latencyMs"], 67);
}
}