M3a 退役机制 plan 经 Codex+Opus 双评审(needs-rework)→ 按创始人 A(全局 dispatcher flag 整体翻转、不做 per-request 灰度)重写 → Opus 复审 minor-fixes → 发现项全数修复。 U1 Python §6.1 HTTP worker(cheap-worker/worker_service.py + result_out.py): 202 投递握手 + 有界串行队列(治串行 worker 忙 409 误判)+ 三字段 result-out (gameConfig 占位 / engineBundle 承重含 __GameBundle / sourceProject best-effort 2.0) + HMAC 回调。27 单测 + 真生成 e2e 过九门 + 真网络回调。 U2 跨语言契约 slice test(CheapWorkerResultOutContractTest):反射调真 validateSucceededPayload 过薄校验 + 真 CallbackSignatureVerifier HMAC。 U3 A2A 状态(additive):AigcTaskStatusEnum.toA2aStatus + app VO/RPC DTO a2aStatus 字段(保留数字 status 不破坏调用方)+ Convert/ApiImpl backfill + 契约 aigc.yaml。 U4 创作请求级幂等(行业标准 idempotency):DO idempotencyKey + Flyway V26 (列+唯一索引,NULL 多值=不去重)+ Mapper selectByIdempotency + submitGenerate dedup。 真验证抓到并修 2 个真 bug:① worker 回调走系统代理被 fake-ip 拦 502 → 禁代理 opener; ② result-out 顶层 costRmb 不在 DifyCallbackReqVO 字段内、后端严格拒未知 → 落 trace.cost。 验证:Python 27 测 + Java 21 测全绿(mvn BUILD SUCCESS)。 剩余 U2 真派发 / U4 真 DB / U5 待 mini-desktop 真部署。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
297 lines
18 KiB
YAML
297 lines
18 KiB
YAML
openapi: 3.0.3
|
||
# 契约 #1 API | 模块:aigc(game-module-aigc,"做得出"生成入口)| owner:WS2 主笔,全员 review
|
||
# 职责:生成入口——异步提交生成任务(prompt+templateId)+ 轮询状态;产物=GamePackage(契约#4),落到 project 的 game_version(经 game_id 关联);对接 Dify(契约#6) 仅留对接点。
|
||
# 端:/app-api(产品端 game-studio,用户 Token+DataPermission,创作者只见自己任务) /admin-api(管理端 game-admin,RBAC);前缀由 huijing 框架按 controller.app/admin 包名自动添加。
|
||
# 错误码段:aigc = 1-101-***-***(本模块独占,禁止与他模块重叠)
|
||
# 响应统一 Huijing CommonResult 信封:{ code, data, msg };code=0 成功
|
||
# 范围纪律:只做 MVP 核心闭环(异步提交+轮询+取消+重试+模板列表);LLM 实名充值为人工闸门,Dify/MQ/LLM 只定义对接点不实现。
|
||
info:
|
||
title: 绘境AI aigc 模块 API
|
||
version: 1.0.0
|
||
description: >-
|
||
AI 生成("做得出"入口)。一句话 Prompt + 模板 → 异步生成任务 → 轮询状态 → 产物 GamePackage(契约#4)。
|
||
任务状态机对齐 Dify(契约#6) 输出 + RocketMQ 队列态(queued/running/succeeded/failed/timed_out/canceled)。
|
||
成功产物经 game_id 关联落到 project.game_version;前端据此 vite-plugin-mock 自动生成 mock。
|
||
|
||
servers:
|
||
- url: http://localhost:48080
|
||
description: 本地(Swagger/Knife4j http://localhost:48080/doc.html)
|
||
|
||
paths:
|
||
# ===================== 产品端 /app-api(创作者)=====================
|
||
/app-api/aigc/template/list:
|
||
get:
|
||
tags: [app-aigc]
|
||
summary: 玩法模板列表(P-TPL-01;提交生成前选模板,MVP 3-5 个 P0 模板)
|
||
description: 返回 aigc 模板注册表中启用的模板(T-AGC-03)。templateId 取值如 dodge/runner/clicker/puzzle/shooter(最终由产品拍板)。
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultTemplateList' }
|
||
|
||
/app-api/aigc/generate:
|
||
post:
|
||
tags: [app-aigc]
|
||
summary: 提交生成任务(P-CRT-01 自然语言生成;异步入队,立即返回 taskId)
|
||
description: >-
|
||
校验 prompt 非空 + templateId 存在;落库 game_aigc_task(status=0 queued),生成 traceId,投递 RocketMQ 异步队列(T-AGC-07,仅对接点)。
|
||
不同步等待生成结果;客户端据 taskId 轮询 GET /app-api/aigc/task/{id}。
|
||
关联的 gameId/versionId 由调用方(studio 草稿)传入。
|
||
写权属(决策5):aigc 成功后只回填 game_version.id 关联 + 生成元数据(gen_task_id);不写 game_version 的 package_url/checksum/bundle_size——这些的权威写者=runtime 编译成功后回写(见 runtime V3.0.0 / runtime.yaml)。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/AigcGenerateReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 返回新建生成任务(含 taskId + 初始状态 queued + traceId)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultGenerateResp' }
|
||
|
||
/app-api/aigc/task/{id}:
|
||
get:
|
||
tags: [app-aigc]
|
||
summary: 轮询生成任务状态(P-CRT-12/13;前端轮询直至终态)
|
||
description: >-
|
||
返回任务当前状态机 + 进度。终态 succeeded 携带产物引用(gameId/versionId + qualityScore;packageUrl 由 runtime 编译成功后回写,aigc 仅透传只读);
|
||
终态 failed/timed_out 携带 failureReason(结构化错误分类,映射用户可读提示)。DataPermission:创作者只见自己任务。
|
||
注:qualityScore=生成质量分(0-1),对齐契约#6 Dify output;≠ telemetry/feed 的运营质量分(0-100),两者口径不同、不可互相回灌。
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: 生成任务 ID(taskId) }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultTask' }
|
||
|
||
/app-api/aigc/task/{id}/cancel:
|
||
post:
|
||
tags: [app-aigc]
|
||
summary: 取消生成任务(仅 queued/running 可取消 → canceled)
|
||
description: 服务端校验状态机,仅非终态可取消;终态拒绝(错误码 1-101-002-001)。
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: 生成任务 ID }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
/app-api/aigc/task/{id}/retry:
|
||
post:
|
||
tags: [app-aigc]
|
||
summary: 重试生成任务(P-CRT-09 重生成;仅 failed/timed_out 可重试,新建任务)
|
||
description: >-
|
||
基于原任务 prompt+templateId 新建一条 game_aigc_task 重新入队(retry_of 指向原任务),返回新 taskId。
|
||
仅终态失败可重试;成功/进行中拒绝(错误码 1-101-002-002)。
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: 原生成任务 ID }
|
||
responses:
|
||
'200':
|
||
description: 返回新建重试任务
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultGenerateResp' }
|
||
|
||
/app-api/aigc/task/my:
|
||
get:
|
||
tags: [app-aigc]
|
||
summary: 我的生成任务列表(创作者只见自己数据)
|
||
parameters:
|
||
- { name: status, in: query, required: false, schema: { type: integer }, description: 按状态机筛选(见 status 枚举) }
|
||
- { name: gameId, in: query, required: false, schema: { type: integer, format: int64 }, description: 按所属游戏筛选 }
|
||
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
|
||
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultTaskPage' }
|
||
|
||
# ===================== 管理端 /admin-api(运营/排障)=====================
|
||
/admin-api/aigc/task/page:
|
||
get:
|
||
tags: [admin-aigc]
|
||
summary: 生成任务全量分页(管理端 RBAC,排障/健康监控 T-TEL-14)
|
||
description: 跨创作者查看生成任务,支持按状态/模板/traceId 检索,用于生成成功率统计与失败排障。
|
||
parameters:
|
||
- { name: status, in: query, required: false, schema: { type: integer }, description: 按状态机筛选 }
|
||
- { name: templateId, in: query, required: false, schema: { type: string }, description: 按模板筛选 }
|
||
- { name: traceId, in: query, required: false, schema: { type: string }, description: 按全链路 traceId 精确检索 }
|
||
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
|
||
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultTaskPage' }
|
||
|
||
# ===================== Dify 回调对接点(仅契约,不实现)=====================
|
||
/admin-api/aigc/dify/callback:
|
||
post:
|
||
tags: [admin-aigc]
|
||
summary: '[对接点] Dify workflow 完成回调(契约#6 output → 驱动任务状态机;不在 MVP 实现)'
|
||
description: >-
|
||
【外部依赖对接点,仅定义契约,不实现、不 mock】Dify 工作流(6 节点)完成后回调本端点,
|
||
以契约#6 output 形态(status/traceId/gameConfig/assets/qualityScore/failureReason)驱动 game_aigc_task 状态机:
|
||
succeeded → 只回填 game_version.id 关联 + 生成元数据(gen_task_id)并置 status=2,不写 game_version 的 package_url/checksum/bundle_size(权威写者=runtime 编译成功后回写,见 runtime V3.0.0 / runtime.yaml);failed → 落 failure_reason 置 status=3。
|
||
端鉴权:内网/服务间调用,MVP 阶段经 admin-api 受 RBAC 保护;正式实现需加签名校验。LLM 实名充值为人工闸门,本端点不触发计费。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/DifyCallbackReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 回调受理(幂等:同一 traceId 重复回调只生效一次)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
components:
|
||
schemas:
|
||
# ---- Huijing CommonResult 信封 ----
|
||
CommonResultBoolean:
|
||
type: object
|
||
properties:
|
||
code: { type: integer, description: '0=成功,非0=错误码 1-101-***-***' }
|
||
data: { type: boolean }
|
||
msg: { type: string }
|
||
CommonResultGenerateResp:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/GenerateRespVO' }
|
||
msg: { type: string }
|
||
CommonResultTask:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/AigcTaskRespVO' }
|
||
msg: { type: string }
|
||
CommonResultTaskPage:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data:
|
||
type: object
|
||
properties:
|
||
list: { type: array, items: { $ref: '#/components/schemas/AigcTaskRespVO' } }
|
||
total: { type: integer, format: int64 }
|
||
msg: { type: string }
|
||
CommonResultTemplateList:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { type: array, items: { $ref: '#/components/schemas/TemplateRespVO' } }
|
||
msg: { type: string }
|
||
|
||
# ---- 请求 VO ----
|
||
AigcGenerateReqVO:
|
||
type: object
|
||
required: [prompt]
|
||
properties:
|
||
prompt: { type: string, minLength: 1, maxLength: 1000, description: 创作者一句话自然语言 Prompt(→ 契约#6 input.prompt) }
|
||
templateId: { type: string, description: '玩法模板 ID(→ 契约#6 input.templateId;可选,缺省=generic:W-CLEAN 废玩法模板层后,一句话创作无需选模板,入口将缺省 templateId 归一为 generic 单一通用口径,品类区分留 prompt 层;前端亦可显式从模板列表选)' }
|
||
gameId: { type: integer, format: int64, description: '关联游戏 ID(project.game_project.id)。产物 GamePackage 经此落 game_version;studio 草稿场景传入' }
|
||
versionId: { type: integer, format: int64, description: '关联版本 ID(project.game_version.id)。指定则产物写入该版本;为空时由生成流程新建版本' }
|
||
attachments:
|
||
type: array
|
||
description: 附件驱动创作上下文(图片/参考,可空;→ 契约#6 input.attachments)
|
||
items:
|
||
type: object
|
||
properties:
|
||
type: { type: string, description: 附件类型 }
|
||
url: { type: string, format: uri, description: 附件 URL }
|
||
DifyCallbackReqVO:
|
||
type: object
|
||
description: '[对接点] 形态对齐契约#6 dify-workflow-io.json 的 output。仅契约,不实现。'
|
||
required: [traceId, status]
|
||
properties:
|
||
traceId: { type: string, description: 贯穿全链路 traceId(与 game_aigc_task.trace_id 匹配定位任务) }
|
||
status: { type: string, enum: [succeeded, failed], description: 'Dify 生成结果状态(→ 驱动任务状态机 2/3)' }
|
||
templateId: { type: string, description: 最终匹配/使用的模板 ID }
|
||
gameConfig: { type: object, description: '生成的可玩配置(注入 GamePackage.gameConfig)' }
|
||
assets:
|
||
type: array
|
||
description: 生成资源引用(合入 GamePackage.assets)
|
||
items:
|
||
type: object
|
||
properties:
|
||
type: { type: string }
|
||
url: { type: string, format: uri }
|
||
qualityScore: { type: number, minimum: 0, maximum: 1, description: '生成质量分(0-1),对齐契约#6 Dify output 0-1,质量门禁参考;≠ telemetry/feed 的运营质量分(0-100),两者不可互相回灌' }
|
||
failureReason: { $ref: '#/components/schemas/FailureReason' }
|
||
# ↓↓ P3 生成主线 additive 字段(Java DifyCallbackReqVO 已加,此处补 yaml 漂移对齐;契约先行收口)↓↓
|
||
engineBundle:
|
||
type: string
|
||
nullable: true
|
||
description: >-
|
||
引擎包路 bundle 文本(P3 additive;worker 产 __GameBundle iife bundle 全文,
|
||
→ DifyCallbackTxService.resolveEngineBundleText 取值写入 GamePackage.engineBundle 落包)。
|
||
与 GamePackage.engineBundle(契约#4 落库侧产物)分清:本字段是回调入参侧载体。
|
||
存量 M-b 回调不带本字段时为 null,组包链省略该键、字节零变化。
|
||
# ↓↓ W-G1 组B 新增(additive 可选):9d 富生成轨迹账本 ↓↓
|
||
trace:
|
||
type: object
|
||
nullable: true
|
||
description: >-
|
||
9d 生成轨迹账本(W-G1 组B,additive 可选):worker 已产出的富生成数据
|
||
(guards 九门逐门/cost/models/attempts/gatespec/verdict/repairs/wall/similarity)。
|
||
字段全可选(worker 路径未定,按必填子集统计完整率);后端 best-effort 落
|
||
game_aigc_task.trace_json,落库失败不阻断主回调链。存量回调不带本字段时为 null,trace_json 列 NULL,字节零变化。
|
||
|
||
# ---- 响应 VO ----
|
||
GenerateRespVO:
|
||
type: object
|
||
description: 提交/重试生成任务的返回(异步,立即返回任务句柄)
|
||
properties:
|
||
taskId: { type: integer, format: int64, description: 生成任务 ID(轮询用) }
|
||
status: { type: integer, description: '初始状态:0 queued', example: 0 }
|
||
traceId: { type: string, description: 贯穿全链路 traceId(透传给前端/日志关联) }
|
||
AigcTaskRespVO:
|
||
type: object
|
||
description: 生成任务详情(轮询返回)。终态 succeeded 带产物引用;失败带 failureReason。
|
||
properties:
|
||
id: { type: integer, format: int64, description: 生成任务 ID(taskId) }
|
||
prompt: { type: string, description: 创作者 Prompt }
|
||
templateId: { type: string, description: 模板 ID }
|
||
gameId: { type: integer, format: int64, description: '关联游戏 ID(project.game_project.id)' }
|
||
versionId: { type: integer, format: int64, description: '产物落地的版本 ID(project.game_version.id);succeeded 后回填' }
|
||
status: { type: integer, description: '状态机:0 queued 排队 / 1 running 生成中 / 2 succeeded 成功 / 3 failed 失败 / 4 timed_out 超时 / 5 canceled 已取消' }
|
||
a2aStatus: { type: string, enum: [submitted, working, input-required, completed, failed, canceled], description: 'A2A 语义状态(M3a U3 additive):由 status 映射(queued→submitted / running→working / succeeded→completed / failed,timed_out→failed / canceled→canceled);前端可选消费、底层换实现不感知;input-required 为协议占位(内部六态无对应、M3a 不可触发,交互待切片三 A11)。保留上方 integer status 不破坏既有调用方。' }
|
||
progress: { type: integer, minimum: 0, maximum: 100, description: 进度百分比(0-100,前端进度条 P-CRT-12) }
|
||
qualityScore: { type: number, minimum: 0, maximum: 1, description: '生成质量分(0-1),对齐契约#6 Dify output 0-1(succeeded 后回填);≠ telemetry/feed 的运营质量分(0-100),两者不可互相回灌' }
|
||
packageUrl: { type: string, description: '产物 GamePackage(manifest) OSS URL(= game_version.package_url,只读透传)。aigc 不写此字段,权威写者=runtime 编译成功后回写(见 runtime V3.0.0 / runtime.yaml)' }
|
||
failureReason: { $ref: '#/components/schemas/FailureReason' }
|
||
retryOf: { type: integer, format: int64, description: 若本任务为重试,指向原任务 ID }
|
||
traceId: { type: string, description: 全链路 traceId }
|
||
createTime: { type: string, format: date-time, description: 创建时间 }
|
||
finishTime: { type: string, format: date-time, description: 终态完成时间 }
|
||
TemplateRespVO:
|
||
type: object
|
||
description: 玩法模板(aigc 模板注册表 T-AGC-03)
|
||
properties:
|
||
templateId: { type: string, description: '模板 ID(如 dodge/runner/clicker/puzzle/shooter)' }
|
||
name: { type: string, description: 模板名称 }
|
||
description: { type: string, description: 模板说明 }
|
||
coverUrl: { type: string, description: 模板封面/示意图 URL }
|
||
examplePrompt: { type: string, description: 示例 Prompt(P-CRT-11 示例 Prompt 库) }
|
||
|
||
# ---- 共享枚举:失败原因分类(T-AGC-10;取值对齐契约#6 output.failureReason)----
|
||
FailureReason:
|
||
type: string
|
||
description: >-
|
||
生成失败原因分类(结构化错误码,映射用户可读提示)。取值与契约#6 dify-workflow-io.json output.failureReason 一致。
|
||
unsafe_prompt=Prompt 不安全 / intent_unclear=意图不清 / no_template_match=无匹配模板 /
|
||
config_invalid=配置校验失败 / llm_error=LLM 异常 / timeout=超时 / asset_gen_failed=素材生成失败。
|
||
enum: [unsafe_prompt, intent_unclear, no_template_match, config_invalid, llm_error, timeout, asset_gen_failed]
|