修复AGC主站同名标记覆盖
增加统一请求终结器,覆盖调用方传入的同名客户端标记 补齐账号态、External Key态、生成轮询和资源编辑主站请求 保持OSS、签名下载、Provider、搜索、loopback和更新下载边界不变 补充同名Header覆盖回归测试并同步Issue226方案与验收文档
This commit is contained in:
@@ -2493,12 +2493,14 @@ async fn recover_direct_taonier_spritesheet_read_only_at(
|
||||
percent_encode_query_component(&remote_art_spec.canvas_project_id)
|
||||
));
|
||||
access.validate_frozen_session()?;
|
||||
let response = client
|
||||
.get(format!("{api_base_url}{route}"))
|
||||
.bearer_auth(&api_key)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("读取陶泥儿画布资源失败:{error}"))?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.get(format!("{api_base_url}{route}"))
|
||||
.bearer_auth(&api_key),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("读取陶泥儿画布资源失败:{error}"))?;
|
||||
access.validate_frozen_session()?;
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
|
||||
@@ -1818,21 +1818,23 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
|
||||
let fingerprint = format!("{}\0{}", source_asset_id, asset_name);
|
||||
let (_operation_id, idempotency_key) = state.resource_request_ids(&fingerprint)?;
|
||||
let route = "/api/external/v1/editor/images/background-removals";
|
||||
let response = client
|
||||
.post(format!("{}{}", api_base_url, route))
|
||||
.bearer_auth(api_key)
|
||||
.header("Idempotency-Key", idempotency_key)
|
||||
.json(&json!({
|
||||
"sourceImageSrc": source_resource_id,
|
||||
"projectId": manifest.project_id,
|
||||
"assetKind": source_asset.kind,
|
||||
"assetFolderId": context.asset_folder_id,
|
||||
"assetLabel": asset_name,
|
||||
"sourceResourceId": source_resource_id,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("抠图服务提交失败:{error}"))?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!("{}{}", api_base_url, route))
|
||||
.bearer_auth(api_key)
|
||||
.header("Idempotency-Key", idempotency_key)
|
||||
.json(&json!({
|
||||
"sourceImageSrc": source_resource_id,
|
||||
"projectId": manifest.project_id,
|
||||
"assetKind": source_asset.kind,
|
||||
"assetFolderId": context.asset_folder_id,
|
||||
"assetLabel": asset_name,
|
||||
"sourceResourceId": source_resource_id,
|
||||
})),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("抠图服务提交失败:{error}"))?;
|
||||
let status = response.status();
|
||||
let payload = response
|
||||
.json::<Value>()
|
||||
|
||||
@@ -685,7 +685,7 @@ pub(crate) async fn external_editor_json_request(
|
||||
request: reqwest::RequestBuilder,
|
||||
action: &str,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let response = request
|
||||
let response = crate::http_client::with_agc_main_site_marker(request)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("{action}失败:{error}"))?;
|
||||
@@ -963,22 +963,24 @@ pub(crate) async fn submit_external_generation_request(
|
||||
}
|
||||
let request_body_json = serde_json::to_string(&request_body)
|
||||
.map_err(|error| format!("序列化平台图片生成请求失败:{error}"))?;
|
||||
let response = client
|
||||
.post(format!(
|
||||
"{api_base_url}{}",
|
||||
resolve_platform_editor_api_route(endpoint)
|
||||
))
|
||||
.bearer_auth(api_key)
|
||||
.header("Idempotency-Key", idempotency_key)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.body(request_body_json)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 请求平台图片生成后未取得确定响应:{error}"
|
||||
)
|
||||
})?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!(
|
||||
"{api_base_url}{}",
|
||||
resolve_platform_editor_api_route(endpoint)
|
||||
))
|
||||
.bearer_auth(api_key)
|
||||
.header("Idempotency-Key", idempotency_key)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.body(request_body_json),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 请求平台图片生成后未取得确定响应:{error}"
|
||||
)
|
||||
})?;
|
||||
if response.status().is_server_error() {
|
||||
return Err(format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 请求平台图片生成后收到 HTTP {},服务端是否已产生副作用未知",
|
||||
|
||||
@@ -380,15 +380,15 @@ async fn create_private_external_editor_api_credentials_from_platform_session(
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
.map_err(|error| format!("创建本机开发者 Key 客户端失败:{error}"))?;
|
||||
let response = client
|
||||
.post(format!("{api_base_url}/api/profile/api-keys"))
|
||||
.bearer_auth(&session.access_token)
|
||||
.json(&serde_json::json!({ "name": DIRECT_EXTERNAL_EDITOR_API_KEY_NAME }))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
format!("创建本机陶泥儿开发者 Key 未取得确定响应;不会自动重试:{error}")
|
||||
})?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!("{api_base_url}/api/profile/api-keys"))
|
||||
.bearer_auth(&session.access_token)
|
||||
.json(&serde_json::json!({ "name": DIRECT_EXTERNAL_EDITOR_API_KEY_NAME })),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("创建本机陶泥儿开发者 Key 未取得确定响应;不会自动重试:{error}"))?;
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(match status {
|
||||
@@ -720,12 +720,12 @@ pub(crate) async fn sync_canvas_project_assets_at(
|
||||
percent_encode_query_component(canvas_project_id)
|
||||
))
|
||||
);
|
||||
let project_response = client
|
||||
.get(project_url)
|
||||
.bearer_auth(&api_key)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("读取画板项目失败:{error}"))?;
|
||||
let project_response = crate::http_client::with_agc_main_site_marker(
|
||||
client.get(project_url).bearer_auth(&api_key),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("读取画板项目失败:{error}"))?;
|
||||
let project_status = project_response.status();
|
||||
if !project_status.is_success() {
|
||||
return Err(format!(
|
||||
@@ -1243,12 +1243,11 @@ pub(crate) async fn resolve_external_asset_signed_url(
|
||||
api_key: &str,
|
||||
read_url: String,
|
||||
) -> Result<String, String> {
|
||||
let response = client
|
||||
.get(read_url)
|
||||
.bearer_auth(api_key)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("换签画板资产失败:{error}"))?;
|
||||
let response =
|
||||
crate::http_client::with_agc_main_site_marker(client.get(read_url).bearer_auth(api_key))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("换签画板资产失败:{error}"))?;
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(format!("换签画板资产失败:HTTP {}", status.as_u16()));
|
||||
|
||||
@@ -26,17 +26,33 @@ fn agc_main_site_redirect_policy() -> reqwest::redirect::Policy {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn agc_main_site_client_builder() -> reqwest::ClientBuilder {
|
||||
fn agc_main_site_marker_headers() -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
HeaderName::from_static(AGC_CLIENT_MARKER_HEADER),
|
||||
HeaderValue::from_static(AGC_CLIENT_MARKER_VALUE),
|
||||
);
|
||||
headers
|
||||
}
|
||||
|
||||
pub(crate) fn agc_main_site_client_builder() -> reqwest::ClientBuilder {
|
||||
reqwest::Client::builder()
|
||||
.default_headers(headers)
|
||||
.default_headers(agc_main_site_marker_headers())
|
||||
.redirect(agc_main_site_redirect_policy())
|
||||
}
|
||||
|
||||
/// Finalize a request sent through the AGC main-site client.
|
||||
///
|
||||
/// `ClientBuilder::default_headers` only fills a missing request header. A
|
||||
/// request-level header with the same name would otherwise win, so use
|
||||
/// `RequestBuilder::headers` here to replace any caller-provided value with
|
||||
/// the reserved AGC marker after all business headers have been configured.
|
||||
pub(crate) fn with_agc_main_site_marker(
|
||||
request: reqwest::RequestBuilder,
|
||||
) -> reqwest::RequestBuilder {
|
||||
request.headers(agc_main_site_marker_headers())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
@@ -157,6 +173,30 @@ mod tests {
|
||||
assert!(request_header(&request, "idempotency-key").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_finalizer_overrides_a_caller_provided_marker() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("bind HTTP fixture");
|
||||
let address = listener.local_addr().expect("read HTTP fixture address");
|
||||
let fixture = spawn_http_fixture(listener);
|
||||
let client = agc_main_site_client_builder()
|
||||
.build()
|
||||
.expect("build AGC main-site client");
|
||||
let request = client
|
||||
.get(format!("http://{address}/api/auth/me"))
|
||||
.header(AGC_CLIENT_MARKER_HEADER, "spoofed-value");
|
||||
let response = super::with_agc_main_site_marker(request)
|
||||
.send()
|
||||
.await
|
||||
.expect("send finalized request");
|
||||
let request = fixture.join().expect("join HTTP fixture");
|
||||
|
||||
assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT);
|
||||
assert_eq!(
|
||||
request_header(&request, AGC_CLIENT_MARKER_HEADER),
|
||||
Some(AGC_CLIENT_MARKER_VALUE.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn factory_keeps_request_headers_and_transport_options_configurable() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("bind HTTP fixture");
|
||||
|
||||
@@ -2089,7 +2089,7 @@ async fn try_confirm_uploaded_reference(
|
||||
asset_kind: &str,
|
||||
) -> Result<Option<String>, ReferencePreparationError> {
|
||||
let endpoint = resolve_platform_editor_api_route("/api/external/v1/assets/objects/confirm");
|
||||
let response = authorize_canvas_request(
|
||||
let response = crate::http_client::with_agc_main_site_marker(authorize_canvas_request(
|
||||
client
|
||||
.post(format!("{api_base_url}{endpoint}"))
|
||||
.json(&serde_json::json!({
|
||||
@@ -2102,7 +2102,7 @@ async fn try_confirm_uploaded_reference(
|
||||
"accessPolicy": "private",
|
||||
})),
|
||||
api_mode,
|
||||
)
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ReferencePreparationError::ConfirmFailed)?;
|
||||
|
||||
@@ -1619,22 +1619,24 @@ async fn request_resource_edit_upload_ticket(
|
||||
.as_ref()
|
||||
.ok_or_else(|| "源媒体上传缺少文件内容".to_string())?;
|
||||
access.validate_frozen_session()?;
|
||||
let response = client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route("/api/external/v1/assets/direct-upload-tickets")
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.json(&resource_edit_upload_ticket_payload(
|
||||
input,
|
||||
source,
|
||||
binding_key_sha256,
|
||||
bytes.len(),
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 创建源资源上传凭证未取得确定响应".to_string())?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route("/api/external/v1/assets/direct-upload-tickets")
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.json(&resource_edit_upload_ticket_payload(
|
||||
input,
|
||||
source,
|
||||
binding_key_sha256,
|
||||
bytes.len(),
|
||||
)),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 创建源资源上传凭证未取得确定响应".to_string())?;
|
||||
let mut post_response_session_error = access.validate_frozen_session().err();
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||
if let Some(error) = post_response_session_error {
|
||||
@@ -1801,25 +1803,27 @@ async fn confirm_resource_edit_source(
|
||||
.as_ref()
|
||||
.ok_or_else(|| "确认源媒体上传缺少文件内容".to_string())?;
|
||||
access.validate_frozen_session()?;
|
||||
let response = client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route("/api/external/v1/assets/objects/confirm")
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.json(&serde_json::json!({
|
||||
"bucket": bucket,
|
||||
"objectKey": object_key,
|
||||
"contentType": source.media_type,
|
||||
"contentLength": bytes.len(),
|
||||
"contentHash": source.source_sha256,
|
||||
"assetKind": source.asset_kind,
|
||||
"accessPolicy": "private",
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 确认源资源上传未取得确定响应".to_string())?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route("/api/external/v1/assets/objects/confirm")
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.json(&serde_json::json!({
|
||||
"bucket": bucket,
|
||||
"objectKey": object_key,
|
||||
"contentType": source.media_type,
|
||||
"contentLength": bytes.len(),
|
||||
"contentHash": source.source_sha256,
|
||||
"assetKind": source.asset_kind,
|
||||
"accessPolicy": "private",
|
||||
})),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 确认源资源上传未取得确定响应".to_string())?;
|
||||
let mut post_response_session_error = access.validate_frozen_session().err();
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||
if let Some(error) = post_response_session_error {
|
||||
@@ -1902,34 +1906,36 @@ async fn register_resource_edit_source_image(
|
||||
percent_encode_query_component(remote_project_id)
|
||||
);
|
||||
access.validate_frozen_session()?;
|
||||
let response = client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route(&endpoint)
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.header(
|
||||
"Idempotency-Key",
|
||||
format!("game-creator-resource-{binding_key}"),
|
||||
)
|
||||
.json(&serde_json::json!({
|
||||
"imageSrc": format!("/{}", object_key.trim_start_matches('/')),
|
||||
"objectKey": object_key,
|
||||
"assetObjectId": asset_object_id,
|
||||
"width": decoded.width(),
|
||||
"height": decoded.height(),
|
||||
"sourceType": "uploaded",
|
||||
"assetKind": source.asset_kind,
|
||||
"generationInputs": {
|
||||
"source": RESOURCE_EDIT_QUEUE_SOURCE,
|
||||
"localAssetId": local_asset_id,
|
||||
"sourceSha256": source.source_sha256,
|
||||
},
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 登记源图片项目资源未取得确定响应".to_string())?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route(&endpoint)
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.header(
|
||||
"Idempotency-Key",
|
||||
format!("game-creator-resource-{binding_key}"),
|
||||
)
|
||||
.json(&serde_json::json!({
|
||||
"imageSrc": format!("/{}", object_key.trim_start_matches('/')),
|
||||
"objectKey": object_key,
|
||||
"assetObjectId": asset_object_id,
|
||||
"width": decoded.width(),
|
||||
"height": decoded.height(),
|
||||
"sourceType": "uploaded",
|
||||
"assetKind": source.asset_kind,
|
||||
"generationInputs": {
|
||||
"source": RESOURCE_EDIT_QUEUE_SOURCE,
|
||||
"localAssetId": local_asset_id,
|
||||
"sourceSha256": source.source_sha256,
|
||||
},
|
||||
})),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 登记源图片项目资源未取得确定响应".to_string())?;
|
||||
let mut post_response_session_error = access.validate_frozen_session().err();
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||
if let Some(error) = post_response_session_error {
|
||||
@@ -2485,19 +2491,21 @@ async fn submit_resource_edit_remote(
|
||||
}
|
||||
}
|
||||
access.validate_frozen_session()?;
|
||||
let response = client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route(endpoint)
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.header("Idempotency-Key", &ledger.idempotency_key)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.body(body_value.to_string())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("result-unknown: 资源编辑请求已发出但未取得确定响应:{error}"))?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.api_route(endpoint)
|
||||
))
|
||||
.bearer_auth(access.bearer_token())
|
||||
.header("Idempotency-Key", &ledger.idempotency_key)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.body(body_value.to_string()),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("result-unknown: 资源编辑请求已发出但未取得确定响应:{error}"))?;
|
||||
let mut post_response_session_error = access.validate_frozen_session().err();
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::UNAUTHORIZED {
|
||||
@@ -2608,12 +2616,12 @@ async fn wait_for_resource_edit_remote(
|
||||
access.validate_frozen_session()?;
|
||||
tokio::time::sleep(Duration::from_millis(poll_after_ms)).await;
|
||||
access.validate_frozen_session()?;
|
||||
let response = client
|
||||
.get(&status_url)
|
||||
.bearer_auth(access.bearer_token())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 查询资源编辑任务失败".to_string())?;
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client.get(&status_url).bearer_auth(access.bearer_token()),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| "result-unknown: 查询资源编辑任务失败".to_string())?;
|
||||
access.validate_frozen_session()?;
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||
return Err(editor_api_authentication_error());
|
||||
|
||||
Reference in New Issue
Block a user