From 8324911bbbe0cc62bb208de5ebaaf4b99b7ca1a2 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 27 Jun 2026 17:24:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E4=BD=9C=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E7=BC=93=E5=AD=98=E7=BC=BA=E5=A4=B1=E9=99=8D=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 角色资产工作流解析在 OSS 缓存未配置时返回默认提示词 补充无 OSS 缓存的 workflow 路由回归测试 同步后端契约文档说明 --- ...】server-rs与SpacetimeDB数据契约-2026-05-15.md | 2 +- server-rs/crates/api-server/src/app.rs | 39 +++++++++++++++++++ .../src/character_animation_assets.rs | 16 +++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md index 67d49adc6..3c23c8a43 100644 --- a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md +++ b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md @@ -60,7 +60,7 @@ npm run check:server-rs-ddd - 平台基础能力:`/api/llm/*`、`/api/speech/volcengine/*`,只保留通用 LLM 和语音代理。 - 资产基础能力:`/api/assets/direct-upload-tickets`、`/api/assets/sts-upload-credentials`、`/api/assets/objects/*`、`/api/assets/read-*`,负责直传、确认、绑定和读取。 - 外部 OpenAPI:`/api/external/v1/openapi.json`、`/api/external/v1/assets/direct-upload-tickets`、`/api/external/v1/assets/objects/confirm`、`/api/external/v1/assets/read-url`、`/api/external/v1/editor/*`,使用 Bearer API Key 鉴权;API Key 管理仍在登录态 `/api/profile/api-keys`,不进入外部 OpenAPI JSON。 -- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`、`/api/editor/projects*`。 +- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`、`/api/editor/projects*`。`/api/runtime/custom-world/asset-studio/*` 解析默认角色形象 / 动作提示词时可以在 OSS 缓存不可用或未配置时按无缓存返回默认提示;保存 workflow 缓存和真实素材读写仍必须要求 OSS 正常可用。 - 后台入口配置:`/admin/api/creation-entry/config`、`/admin/api/creation-entry/config/banners` 和 `/admin/api/creation-entry/config/interactions`。 - 自定义世界 / RPG:`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*`。 - 拼图:`/api/runtime/puzzle/*`。 diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index e86aabf9a..fc7dd84fc 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -690,6 +690,45 @@ mod tests { assert_eq!(body["error"]["details"]["creationTypeId"], "rpg"); } + #[tokio::test] + async fn role_asset_workflow_returns_default_prompts_without_oss_cache() { + let app = build_router(AppState::new(AppConfig::default()).expect("state should build")); + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/runtime/custom-world/asset-studio/role/hero-01/workflow") + .header("content-type", "application/json") + .body(Body::from( + serde_json::json!({ + "role": { + "id": "hero-01", + "name": "沈砺", + "title": "灰炬向导", + "role": "边路同行者", + "actionDescription": "持灯快步穿过灰烬街巷" + } + }) + .to_string(), + )) + .expect("workflow request should build"), + ) + .await + .expect("workflow request should succeed"); + + assert_eq!(response.status(), StatusCode::OK); + let body = read_json_response(response).await; + assert_eq!(body["ok"], Value::Bool(true)); + assert_eq!(body["cache"], Value::Null); + assert!( + body["workflow"]["animationPromptText"] + .as_str() + .unwrap_or_default() + .contains("持灯快步穿过灰烬街巷") + ); + } + #[tokio::test] async fn healthz_returns_standard_envelope_when_requested() { let app = build_router(AppState::new(AppConfig::default()).expect("state should build")); 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 f13006a39..996a5a507 100644 --- a/server-rs/crates/api-server/src/character_animation_assets.rs +++ b/server-rs/crates/api-server/src/character_animation_assets.rs @@ -1124,9 +1124,13 @@ pub async fn resolve_role_asset_workflow( })?; let cache_scope_id = trim_optional_text(payload.cache_scope_id.as_deref()); - let cache = load_workflow_cache(&state, character_id.as_str(), cache_scope_id.as_deref()) + let cache = match load_workflow_cache(&state, character_id.as_str(), cache_scope_id.as_deref()) .await - .map_err(|error| character_animation_error_response(&request_context, error))?; + { + Ok(cache) => cache, + Err(error) if is_missing_oss_config_error(&error) => None, + Err(error) => return Err(character_animation_error_response(&request_context, error)), + }; let workflow = build_role_asset_workflow(payload.role, cache.as_ref()); Ok(json_success_body( @@ -4133,6 +4137,14 @@ fn require_oss_client(state: &AppState) -> Result<&platform_oss::OssClient, AppE }) } +fn is_missing_oss_config_error(error: &AppError) -> bool { + error.status_code() == StatusCode::SERVICE_UNAVAILABLE + && error.details().is_some_and(|details| { + details.get("provider").and_then(Value::as_str) == Some("aliyun-oss") + && details.get("reason").and_then(Value::as_str) == Some("OSS 未完成环境变量配置") + }) +} + fn normalize_required_text(value: &str, fallback: &str) -> String { let normalized = value .trim()