From 490dc6293d9f9bee53def7a05bcecb86876a1932 Mon Sep 17 00:00:00 2001 From: lili Date: Sun, 21 Jun 2026 10:50:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(saa):=20=E5=8C=85=E7=BA=A7=20assets=20?= =?UTF-8?q?=E6=8E=A5=E9=80=9A=E6=BA=90=20sourceProject.assets[]=E2=80=94?= =?UTF-8?q?=E2=80=94=E9=97=AD=E5=90=88=E4=BA=A7=E6=B6=88=E6=96=AD=E9=93=BE?= =?UTF-8?q?(=E5=85=91=E7=8E=B0=20engine-E2/E3=20=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E4=BE=A7=20TODO)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen-done 出包历史上把包级 assets 硬置空(setAssets(emptyList)),丢掉源 gameDefinition 已落库的六类源资产(game_source_project.source_json 顶层 assets[6]),造成产(asset 节点写源)→消(组包)断链。 改 SaaGraphDispatcher.buildCallbackReqVO(in-JVM SAA 路,即 generic/engine 任务真实落包路):新增 extractSourceAssets(s,traceId) 从图终态 K_SOURCE_PROJECT 抽顶层 assets[] 忠实映射进 reqVO.assets;DifyCallbackTxService.buildGamePackage 读 reqVO.getAssets() 即自然带进 GamePackage.assets。 - 形态:源 assetSpec={id,category,ref,provider}(source-project.schema.json #/$defs/assetSpec),运行时 gd-runtime 经 gdef.assets 同形消费;geom: ref 系几何占位资产引用,原样透传,不伪造包契约 strict AssetSpec 的 type/url/hash/bytes/mime(无真图/真 sha256,伪造即脏数据)。 - best-effort 非阻断(严格 additive):源缺失/空白/非 JSON/无 assets 键/非数组/ 异常一律回退空集,绝不外抛、绝不打断 SAA 生成;存量无源回调字节零变化。 - 复用既有 parseTrace + TRACE_MAPPER(只读,无共享可变状态),不引新依赖。 不动 AigcGenerateExecutor:440:该处为进程内 clicker/merge 旧路(generic/engine 经 dispatchGeneric 早返,不达此行;worker 回调由独立 reqVO 驱动),作用域无 sourceProject,改它即凭空伪造,故按现状保留(对齐 run_batch.py assets:[])。 验证:mvn -o clean install -pl game-module-aigc-server -am 编译 BUILD SUCCESS; DifyCallbackServiceImplTest+AigcGenerateExecutorTest 41 tests 0 fail(含 in-process 路 getAssets().isEmpty() 断言仍过,确认未回归)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../module/aigc/saa/SaaGraphDispatcher.java | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/saa/SaaGraphDispatcher.java b/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/saa/SaaGraphDispatcher.java index 08481ee3..71888931 100644 --- a/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/saa/SaaGraphDispatcher.java +++ b/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/saa/SaaGraphDispatcher.java @@ -609,7 +609,14 @@ public class SaaGraphDispatcher implements GenerationDispatcher { DifyCallbackReqVO reqVO = new DifyCallbackReqVO(); reqVO.setTraceId(traceId); reqVO.setTemplateId(templateId); - reqVO.setAssets(java.util.Collections.emptyList()); + // ── U2/B8 消费侧(§5.2 产消接通):忠实取已落库源 assets[](六类源资产)填充包级 assets,替代历史硬置空。 ── + // 兑现 SaaStudioNodes 处自标的「engine-E2/E3 消费侧本期不做」TODO:源 asset 节点已产 assets[6] + // (schemaVersion+gameDefinition+顶层 assets,落 game_source_project.source_json),此处把同批源 assets + // 忠实带进 GamePackage.assets(DifyCallbackTxService.buildGamePackage 读 reqVO.getAssets()),闭合产消断链。 + // 形态注记:源 assetSpec={id,category,ref,provider}(source-project.schema.json #/$defs/assetSpec,运行时 + // gd-runtime 经 gdef.assets 同形消费);geom: ref 为几何占位资产引用,原样透传不伪造 type/hash/bytes/mime。 + // 空/缺/非数组源 assets → 回退空集(不抛):严格非破坏,存量无源回调字节零变。 + reqVO.setAssets(extractSourceAssets(s, traceId)); // gameConfig 最小合法占位(engine 路游戏逻辑全在 engineBundle,gameConfig 仍不可空:GamePackage required)。 Map gameConfig = new LinkedHashMap<>(); gameConfig.put("templateId", templateId); // 须与包级 templateId 一致(一致性校验) @@ -763,6 +770,55 @@ public class SaaGraphDispatcher implements GenerationDispatcher { } } + /** + * U2/B8 消费侧(§5.2 产消接通):从图终态 {@code K_SOURCE_PROJECT} 抽顶层 {@code assets[]} 映射为包级 assets。 + * + *

兑现 TODO:源 asset 节点已把六类源资产写入 {@code sourceProject} 顶层 {@code assets} + * ({@code source-project.schema.json #/$defs/assetSpec},形态 {@code {id,category,ref,provider}}),历史上组包侧 + * 硬置空集导致产消断链;本方法把同一批源 assets 忠实搬进 {@link DifyCallbackReqVO#getAssets()}, + * 由 {@code DifyCallbackTxService.buildGamePackage} 落进 {@code GamePackage.assets}。运行时 {@code gd-runtime} + * 经 {@code gdef.assets}(同 source assetSpec 形态)消费,故原样透传不重塑:{@code geom:} 为几何占位 + * 资产引用,不伪造包契约 strict AssetSpec 的 {@code type/url/hash/bytes/mime}(无真图、无真 sha256,伪造即脏数据)。 + * + *

best-effort 非阻断(严格 additive):源缺失/空白/非 JSON/无 assets 键/assets 非数组/任意异常 → + * 一律返回空集(绝不外抛、绝不打断 SAA 生成),与历史「恒空集」对存量无源回调字节零变化。 + * + * @param s 图终态(可能 null) + * @param traceId 贯穿键(仅日志) + * @return 源 assets 映射出的 {@code List>}(忠实透传字段);无/异常时空集(非 null) + */ + private static java.util.List> extractSourceAssets(OverAllState s, String traceId) { + if (s == null) { + return java.util.Collections.emptyList(); + } + try { + // 复用 best-effort 宽松解析(null/空白/非法 JSON → null);源串即 §5.2 落库的 source_json。 + JsonNode root = parseTrace(s.value(SaaStudioNodes.K_SOURCE_PROJECT, String.class).orElse("")); + if (root == null) { + return java.util.Collections.emptyList(); + } + JsonNode assets = root.path("assets"); + if (!assets.isArray() || assets.isEmpty()) { + // 无 assets 键 / 非数组 / 空数组 → 回退空集(不抛,存量无源回调字节零变)。 + return java.util.Collections.emptyList(); + } + // 逐项忠实映射为 Map(保 {id,category,ref,provider} 等全部源字段;geom: ref 原样);非对象项跳过(防御脏数据)。 + java.util.List> out = new java.util.ArrayList<>(assets.size()); + for (JsonNode a : assets) { + if (a != null && a.isObject()) { + out.add(TRACE_MAPPER.convertValue(a, new com.fasterxml.jackson.core.type.TypeReference>() {})); + } + } + // 可追溯日志:接通了几个源 assets(0 表示源里确无对象项,等价历史空集,不告警)。 + log.info("[saa-dispatch] 包级 assets 接通源 assets[] 共 {} 个(产消接通,兑现 engine-E2/E3 消费侧 TODO)traceId={}", out.size(), traceId); + return out; + } catch (Exception e) { + // 命门:抽取/映射异常一律吞 + warn 返空集,绝不打断 SAA 生成(生成成败由 status/engineBundle 既有逻辑决定)。 + log.warn("[saa-dispatch] 源 assets 抽取失败,包级 assets 回退空集(不阻断生成)traceId={}", traceId, e); + return java.util.Collections.emptyList(); + } + } + /** * 派生 models 并集:{@code K_TOKENS_BY_MODEL} 的 model 名键集 ∪ {@code K_ATTEMPTS[].{role:model}},组同形 {role:model} map。 *