diff --git a/.agents/rules/engineering-conventions.md b/.agents/rules/engineering-conventions.md index 48dc4398..bdfa3767 100644 --- a/.agents/rules/engineering-conventions.md +++ b/.agents/rules/engineering-conventions.md @@ -64,6 +64,15 @@ > 源档明确给出 system/infra/project/aigc/runtime/feed 六段;其余模块按"每模块一段顺延"规则分配(推断),新增模块时在 `-api` 的错误码常量类登记,避免与既有段重叠。 +### 1.4 Spring bean 名消歧(admin/app 双包同名 controller 必显式命名) + +`controller/admin/` 与 `controller/app/` 下**同简单类名**的 controller(如两个都叫 `XxxController`)若都用裸 `@RestController`,Spring 组件扫描默认 bean 名都 = 首字母小写类名 → `ConflictingBeanDefinitionException` → **整个 context 启动失败**(路径靠包级 admin-api/app-api 前缀区分、本不冲突,但 bean 名先撞死)。硬要求: + +- 双包同名 controller **必须显式 bean 名**:`@RestController("xxxAdminController")` / `@RestController("xxxAppController")`; +- 其余 admin/app 对靠不同类名(如 app 端 `AppXxxController` vs 后台 `XxxController`)天然规避; +- 自检(合入前 / CI):`grep -rlE '^\s*@(RestController|Controller)\s*$' --include='*.java' game-cloud | xargs -n1 basename | sort | uniq -d` 应为空(裸注解重名 = 隐患);同机理也查 `@Service/@Component`。 +- 血泪:2026-06-19 dev/2.0.0 因 `ComplianceAppeal/PolicyController` 两对双包同名裸 `@RestController` 连崩两次启动(staging 重部署 §3 隔离门逐个逮、live 零破)。 + --- ## 2. 前端(TypeScript / Vue)编码规范 @@ -204,6 +213,7 @@ subject: 动词开头,简明描述(中英文均可) | DDL/DML 分开 | 结构变更与数据变更拆成不同迁移文件 | | CI 阻断 | CI 阶段执行 `flyway validate`,不通过则阻断合入 | | 大表变更 | 用 `gh-ost` / `pt-online-schema-change` **不锁表** | +| 单一事实源 | 迁移 SQL **只放聚合器 `huijing-server/src/main/resources/db/migration/`(全 V1-Vn)**;**禁**在 `game-module-*-server` 自带重复副本——Flyway 扫全 classpath(含各 module jar),同版本号出现 ≥2 处即 `Found more than one migration with version` **拒启**。自检:`find game-cloud -path '*/db/migration/V*.sql' \| xargs -n1 basename \| grep -oE '^V[0-9.]+' \| sort \| uniq -d` 应空。血泪:2026-06-19 V6/7/8/9/18 各 lane 在 module 自带副本致 5 版本重复、dev/2.0.0 拒启(§3 隔离门第 3 拦) | --- @@ -214,7 +224,7 @@ subject: 动词开头,简明描述(中英文均可) - [ ] 创建 `game-module-{name}-api` + `game-module-{name}-server` 两个 Maven 模块(结构对齐 huijing-cloud fork) - [ ] `-api` 中定义 VO / DTO / 枚举 / Feign 接口 / **错误码段** - [ ] `-server` 中创建 `controller/admin/` + `controller/app/` + `service/` + `dal/` + `convert/` -- [ ] 编写 Flyway 迁移脚本 `V{x.y.z}__{desc}.sql` +- [ ] 编写 Flyway 迁移脚本 `V{x.y.z}__{desc}.sql`——**置于聚合器 `huijing-server/.../db/migration/`,勿在 `module-server` 自带副本**(同版本号重复会拒启,见 §8 单一事实源) - [ ] `huijing-server`(聚合器,B 路线保留原名)的 `pom.xml` 引入新模块依赖;并在 `HuijingServerApplication` 扫描 + `application.yaml` 类型别名纳入 `com.wanxiang.huijing.game.module`;Nacos 添加模块配置(如有) - [ ] 编写单元测试(Service 层)+ 集成测试(Controller 层含 DB) - [ ] Swagger(Knife4j)验证 API 文档自动生成 diff --git a/.agents/skills/ui-walkthrough-cdp.md b/.agents/skills/ui-walkthrough-cdp.md index e9d972fe..607c59e9 100644 --- a/.agents/skills/ui-walkthrough-cdp.md +++ b/.agents/skills/ui-walkthrough-cdp.md @@ -9,6 +9,7 @@ - 浏览器内访问前端**优先 `http://localhost:4173`**(安全上下文,省心)。**经 Tailscale IP/明文 http 现也可用**:宿主 manifest 校验的 `crypto.subtle` 在非安全上下文缺失,已补纯 JS SHA-256 兜底(2026-06-17 `4e486f60`),不再「走 IP 永远 demo 兜底」。 - **红线(此坑血泪)**:上条「IP→demo 兜底」曾被当成「走查改用 localhost 规避」记着,却没意识到**创始人/真实用户正是经 IP 访问 → 线上游戏一直「加载失败」**(demo 退化后 W-CLEAN 占位发 game_error)。教训:**走查环境 localhost = 安全上下文,会系统性掩盖「上下文依赖缺陷」(`crypto.subtle`、`isSecureContext` 门控特性等);用户可见 UI/e2e 走查必须另跑一遍真实访问 origin(IP/明文 http),不能只在 localhost 取证。当走查需要一个 workaround 才能避开某失败时,先问「真实用户会不会也撞上同一失败」。** - chrome 起后必先 `curl -s localhost:9222/json/version` 验活再用。 +- **禁从 lili-mac 浏览器 / superpowers-chrome 走查 staging(2026-06-19 实证两次假 502)**:① lili-mac 有系统代理(`HTTPProxy 127.0.0.1` + WPAD `wpad.dat`),Chrome 走代理够不到 Tailscale `100.64.0.7` → 全端 **502 假象**;而 curl 直连绕过代理 → 200,故出现「curl/ssh 显 UP 但 Chrome 见 502」的矛盾,**判据=ssh mini-desktop localhost 200 + 进程活 即为真态**。② superpowers-chrome 持久实例会复用**陈旧会话**(把重部署前的旧 502 截图当现态报,认 `session-<旧id>` 复用即是)。**正解永远是本配方(mini-desktop localhost chrome,无代理、服务+浏览器同机)**;lili-mac CDP 仅个人快验、绝不作 e2e 证。 ## 2. CDP 七坑(Chrome 146 实证,逐个血泪) diff --git a/deploy/smoke-test.sh b/deploy/smoke-test.sh index 7b5c39b6..b2eef6ce 100755 --- a/deploy/smoke-test.sh +++ b/deploy/smoke-test.sh @@ -108,7 +108,7 @@ out="$(api GET /app-api/aigc/template/list)" tcount="$(printf '%s' "$out" | sed '$d' | python3 -c "import sys,json try: d=json.load(sys.stdin); data=d.get('data') or []; print(len(data) if isinstance(data,list) else 0) except: print(-1)" 2>/dev/null)" -if [ "${tcount:-0}" -ge 4 ] 2>/dev/null; then PASS=$((PASS+1)); _log " ${G}✓${N} aigc/template/list(四模板就绪 count=$tcount)"; else FAIL=$((FAIL+1)); FAILED_ITEMS+=("template/list count=$tcount"); _log " ${R}✗${N} aigc/template/list(期望≥4,实=$tcount)"; fi +if [ "${tcount:-0}" -ge 4 ] 2>/dev/null; then PASS=$((PASS+1)); _log " ${G}✓${N} aigc/template/list(四模板就绪 count=${tcount})"; else FAIL=$((FAIL+1)); FAILED_ITEMS+=("template/list count=$tcount"); _log " ${R}✗${N} aigc/template/list(期望≥4,实=${tcount})"; fi check "studio/draft(创建草稿拿 gameId)" POST /app-api/studio/draft envelope_nonempty '{"title":"冒烟门-clicker","templateId":"clicker"}' # ---------------- 链路② 发布 → 审核 → 游戏流 ---------------- @@ -128,7 +128,7 @@ try: print(lst[0].get('versionId','') if lst else '') except: print('')" 2>/dev/null)" if [ -n "$VID" ] && [ "$VID" != "None" ]; then - check "runtime/package/{versionId}(试玩取包 vid=$VID)" GET "/app-api/runtime/package/$VID" envelope + check "runtime/package/{versionId}(试玩取包 vid=${VID})" GET "/app-api/runtime/package/$VID" envelope else FAIL=$((FAIL+1)); FAILED_ITEMS+=("feed 首条无 versionId"); _log " ${R}✗${N} feed 首条无 versionId(无法验取包)"; fi # ---------------- 链路④ 广告 → 收益 → 钱包 ---------------- @@ -146,7 +146,7 @@ try: d=json.load(sys.stdin); lst=(d.get('data') or {}).get('list') or [] print(lst[0].get('qualityScore') if lst and lst[0].get('qualityScore') is not None else 'NONE') except: print('NONE')" 2>/dev/null)" -if [ "$QSCORE" != "NONE" ] && [ -n "$QSCORE" ]; then PASS=$((PASS+1)); _log " ${G}✓${N} feed 携带 qualityScore=$QSCORE(质量分回路已回灌)"; else FAIL=$((FAIL+1)); FAILED_ITEMS+=("feed 无 qualityScore"); _log " ${R}✗${N} feed 首条无 qualityScore"; fi +if [ "$QSCORE" != "NONE" ] && [ -n "$QSCORE" ]; then PASS=$((PASS+1)); _log " ${G}✓${N} feed 携带 qualityScore=${QSCORE}(质量分回路已回灌)"; else FAIL=$((FAIL+1)); FAILED_ITEMS+=("feed 无 qualityScore"); _log " ${R}✗${N} feed 首条无 qualityScore"; fi # ---------------- --deep:写路径(生成入队 + 遥测事件)---------------- if [ "$DEEP" = "1" ]; then @@ -160,7 +160,7 @@ if [ "$DEEP" = "1" ]; then try: print((json.load(sys.stdin).get('data') or {}).get('id','')) except: print('')" 2>/dev/null)" if [ -n "$SID" ] && [ "$SID" != "None" ]; then - check "studio/generate(入队 sessionId=$SID)" POST /app-api/studio/generate envelope \ + check "studio/generate(入队 sessionId=${SID})" POST /app-api/studio/generate envelope \ "{\"sessionId\":$SID,\"prompt\":\"一个挂机种树攒果实的放置小游戏\"}" else FAIL=$((FAIL+1)); FAILED_ITEMS+=("deep: draft 无 id"); _log " ${R}✗${N} deep: draft 未拿到 sessionId(id)"; fi fi diff --git a/game-cloud/game-module-ad/game-module-ad-server/src/main/resources/db/migration/V6.0.0__create_game_ad.sql b/game-cloud/game-module-ad/game-module-ad-server/src/main/resources/db/migration/V6.0.0__create_game_ad.sql deleted file mode 100644 index c97e1489..00000000 --- a/game-cloud/game-module-ad/game-module-ad-server/src/main/resources/db/migration/V6.0.0__create_game_ad.sql +++ /dev/null @@ -1,77 +0,0 @@ --- ============================================================================= --- 契约 #2 DB 迁移 | 模块:ad(game-module-ad,Wave2 变现域「广告引擎」)| owner:WS5 --- 文件:V6.0.0__create_game_ad.sql(Flyway,只新增;已合入禁止修改,回滚写新补偿迁移) --- 内容:ad 模块核心表 —— 广告位配置 game_ad_slot(对齐契约#7)+ 广告收入台账 game_ad_revenue(ad↔trade seam 数据源) --- 职责(架构 Doc B):联盟接入/广告位 AI 植入/曝光计费/eCPM/归因;不做结算提现(归 trade)。MVP provider 默认 mock。 --- 脊柱(钱财闭环上游):SDK 拉广告位 → 有效曝光/激励完成计费上报(幂等) → mock eCPM 计收入 → 落 game_ad_revenue(settle_status=0) --- → trade 经 AdRevenueApi.getUnsettledRevenue 拉取做 T+1 分账 → markSettled 回标 settle_status=1(seam 详见 ad.yaml x-feign-contracts) --- 计费 vs 分析埋点:本模块台账=计费级(决定收入),区别于 telemetry(#5 ad_impression/ad_reward) 分析埋点(仅统计),二者并存口径不同 --- 约定:InnoDB + utf8mb4;显式列;含 Yudao 审计列 creator/create_time/updater/update_time/deleted + tenant_id; --- 金额一律「分」(BIGINT),禁浮点;状态机用 tinyint,非法流转由 Service 校验,DO 层不承载;中文列注释;关键查询建索引、禁裸 select*。 --- 错误码段:ad = 1-111-***-*** --- ============================================================================= - --- ----------------------------------------------------------------------------- --- 表:game_ad_slot —— 广告位配置表(字段严格对齐契约#7 ad-slot.schema.json) --- 用途:admin CRUD 维护;SDK Plugin.Ad 拉 enabled=true 广告位渲染(/app-api/ad/slot/list-enabled) --- Provider SPI:provider=mock 走桩(MVP 默认);csj 穿山甲 / gdt 优量汇 走审核闸门后由人填 provider_slot_id 注入 --- 唯一键:slot_id 业务唯一(uk_slot),埋点 ad_impression.slot_id 与计费上报均引用此逻辑 ID --- compliance(未成年人保护)拆为显式列 block_minor / max_per_session,不用 JSON 整块存(便于查询与计费限流) --- ----------------------------------------------------------------------------- -CREATE TABLE `game_ad_slot` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '广告位主键 ID', - `slot_id` VARCHAR(64) NOT NULL COMMENT '广告位逻辑 ID(平台内唯一,对齐契约#7 slotId;埋点/计费上报均引用)', - `type` VARCHAR(16) NOT NULL DEFAULT 'rewarded' COMMENT '广告形式(对齐#7 type):rewarded 激励视频/interstitial 插屏/banner 横幅', - `provider` VARCHAR(16) NOT NULL DEFAULT 'mock' COMMENT '联盟(对齐#7 provider,SPI AdProvider):csj 穿山甲/gdt 优量汇/mock 桩(MVP 默认)', - `placement` VARCHAR(16) NOT NULL DEFAULT 'game_end' COMMENT '触发场景(对齐#7 placement):game_end 本局结束/pause 暂停/feed 游戏流间隙', - `provider_slot_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '联盟侧真实广告位 ID(对齐#7 providerSlotId;provider!=mock 时由人提供——审核闸门后注入;不下发前端明文)', - `ecpm_floor` BIGINT NOT NULL DEFAULT 0 COMMENT 'eCPM 底价(单位:分,对齐#7 ecpmFloor);低于此不展示;mock 计费亦用此算单次收入', - `enabled` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否启用(对齐#7 enabled):0停用 1启用;SDK 只拉启用项', - `block_minor` BIT(1) NOT NULL DEFAULT b'1' COMMENT '合规:识别为未成年则不展示/不计费(对齐#7 compliance.blockMinor,默认 1=拦截)', - `max_per_session` INT NOT NULL DEFAULT 0 COMMENT '合规:单会话最大展示次数(对齐#7 compliance.maxPerSession;0=不限;计费上报按此限流)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_slot` (`slot_id`, `deleted`, `tenant_id`) COMMENT '广告位逻辑 ID 业务唯一键(含 deleted/tenant_id 适配 Yudao 逻辑删除+多租户,避免软删后无法复用 slot_id)', - KEY `idx_enabled` (`enabled`, `placement`) COMMENT 'SDK 按启用+场景拉广告位(list-enabled)', - KEY `idx_provider` (`provider`, `type`) COMMENT '管理端按联盟+形式分页/筛选' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '广告位配置表(对齐契约#7;admin CRUD + SDK 拉取)'; - --- ----------------------------------------------------------------------------- --- 表:game_ad_revenue —— 广告收入台账(有效曝光/激励计费产出;ad↔trade seam 的数据源) --- 用途:计费上报(impression/reward)按 mock eCPM 算收入落账;trade 按 settle_status=0 拉取做 T+1 分账 --- 收入归因:creator_user_id = 由 game_id 反查 project.game_project.creator_user_id(收入归到游戏作者,trade 按此聚合分账) --- 幂等核心:uk_trace=(trace_id,event_type),同 (trace_id,event_type) 重复上报只计一条(计费幂等);同一 trace_id 的 impression 与 reward 各落一条、互不覆盖(防 reward 被 impression 静默吞掉=丢账) --- 收入口径(避免同一次展示重复计收入):type=rewarded 收入计在 reward(impression 若上报则 revenue_amount=0);type=interstitial/banner 收入计在 impression。event_type 仅区分计费事件,不对同一次展示重复计收入 --- 结算状态机 settle_status:0未结算 → 1已结算(trade 分账入账成功后经 AdRevenueApi.markSettled 回标,幂等;非法回退由 Service 拒绝) --- 扫描入口:trade 的 getUnsettledRevenue(statDate) 按 (settle_status=0, stat_date) 命中 idx_settle_date 扫描,禁裸 select* --- 金额单位:ecpm / revenue_amount 一律「分」(BIGINT),禁浮点 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_ad_revenue` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '广告收入台账记录 ID(= trade 侧 game_trade_income.source_ref 对账锚点)', - `slot_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '广告位逻辑 ID(= game_ad_slot.slot_id)', - `game_id` BIGINT NOT NULL COMMENT '产生收入的游戏 ID(project.game_project.id)', - `creator_user_id` BIGINT NOT NULL COMMENT '收入归因到的创作者用户 ID(由 game_id 反查 game_project.creator_user_id;trade 按此聚合分账)', - `event_type` TINYINT NOT NULL DEFAULT 1 COMMENT '计费事件类型:1 impression 有效曝光 / 2 reward 激励完成', - `provider` VARCHAR(16) NOT NULL DEFAULT 'mock' COMMENT '产生收入的联盟:csj/gdt/mock(与广告位 provider 一致)', - `ecpm` BIGINT NOT NULL DEFAULT 0 COMMENT 'eCPM(单位:分;mock 取广告位 ecpm_floor)', - `revenue_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '本条广告收入(单位:分,BIGINT;金额一律用分禁浮点;mock=ecpm/1000 向下取整)', - `trace_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '全链路追踪 ID(与 event_type 组成计费幂等键 uk_trace);透传到 trade.trace_id 供端到端对账', - `settle_status` TINYINT NOT NULL DEFAULT 0 COMMENT '结算状态机:0未结算 1已结算(trade 分账后经 markSettled 回标 1;非法回退由 Service 拒绝)', - `stat_date` DATE NOT NULL COMMENT '归集日(yyyy-MM-dd,按日归集供 trade 按日扫未结算做 T+1 分账)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列;计费上报为 system/上报方)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(计费入账时间)', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者(trade 回标时为结算 job)', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_trace` (`trace_id`, `event_type`, `tenant_id`) COMMENT '计费幂等键:(trace_id+event_type) 唯一——同 (trace_id,event_type) 重复上报只落一条(计费幂等);同一 trace_id 的 impression 与 reward 各计一条、不互相覆盖(防 reward 被 impression 静默吞掉)', - KEY `idx_settle_date` (`settle_status`, `stat_date`) COMMENT 'trade 扫未结算做 T+1 分账(getUnsettledRevenue 按 settle_status=0 + stat_date 扫描)', - KEY `idx_creator_date` (`creator_user_id`, `stat_date`) COMMENT '按创作者+归集日聚合分账/对账', - KEY `idx_game` (`game_id`, `stat_date`) COMMENT '按游戏查收入(归因/计费对账)' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '广告收入台账表(计费产出;ad↔trade seam 数据源,settle_status 供 trade 分账)'; diff --git a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/ComplianceAppealController.java b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/ComplianceAppealController.java index b5e8dca7..4774c7d0 100644 --- a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/ComplianceAppealController.java +++ b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/ComplianceAppealController.java @@ -29,7 +29,9 @@ import static com.wanxiang.huijing.framework.common.pojo.CommonResult.success; * @author 造梦AI */ @Tag(name = "管理后台 - 申诉") -@RestController +// 显式 bean 名消歧:admin 与 app 两包下同名类 ComplianceAppealController 若都用裸 @RestController, +// Spring 默认 bean 名均为 complianceAppealController → ConflictingBeanDefinitionException 启动失败。 +@RestController("complianceAppealAdminController") @RequestMapping("/compliance/appeal") @Validated public class ComplianceAppealController { diff --git a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/CompliancePolicyController.java b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/CompliancePolicyController.java index ae814115..5306455f 100644 --- a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/CompliancePolicyController.java +++ b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/admin/CompliancePolicyController.java @@ -29,7 +29,9 @@ import static com.wanxiang.huijing.framework.common.pojo.CommonResult.success; * @author 造梦AI */ @Tag(name = "管理后台 - 政策正文") -@RestController +// 显式 bean 名消歧:admin 与 app 两包下同名类 CompliancePolicyController 若都用裸 @RestController, +// Spring 默认 bean 名均为 compliancePolicyController → ConflictingBeanDefinitionException 启动失败。 +@RestController("compliancePolicyAdminController") @RequestMapping("/compliance/policy") @Validated public class CompliancePolicyController { diff --git a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/ComplianceAppealController.java b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/ComplianceAppealController.java index 7c4d08d6..48f03636 100644 --- a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/ComplianceAppealController.java +++ b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/ComplianceAppealController.java @@ -28,7 +28,8 @@ import static com.wanxiang.huijing.framework.common.pojo.CommonResult.success; * @author 造梦AI */ @Tag(name = "用户 App - 申诉") -@RestController +// 显式 bean 名消歧:与 admin 包下同名类 ComplianceAppealController 区分,避免默认 bean 名冲突导致启动失败。 +@RestController("complianceAppealAppController") @RequestMapping("/compliance/appeal") @Validated public class ComplianceAppealController { diff --git a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/CompliancePolicyController.java b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/CompliancePolicyController.java index 7e0b315d..18ccf871 100644 --- a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/CompliancePolicyController.java +++ b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/java/com/wanxiang/huijing/game/module/compliance/controller/app/CompliancePolicyController.java @@ -28,7 +28,8 @@ import static com.wanxiang.huijing.framework.common.pojo.CommonResult.success; * @author 造梦AI */ @Tag(name = "用户 App - 政策正文") -@RestController +// 显式 bean 名消歧:与 admin 包下同名类 CompliancePolicyController 区分,避免默认 bean 名冲突导致启动失败。 +@RestController("compliancePolicyAppController") @RequestMapping("/compliance/policy") @Validated public class CompliancePolicyController { diff --git a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/resources/db/migration/V9.0.0__create_game_compliance.sql b/game-cloud/game-module-compliance/game-module-compliance-server/src/main/resources/db/migration/V9.0.0__create_game_compliance.sql deleted file mode 100644 index 696a49dd..00000000 --- a/game-cloud/game-module-compliance/game-module-compliance-server/src/main/resources/db/migration/V9.0.0__create_game_compliance.sql +++ /dev/null @@ -1,84 +0,0 @@ --- ============================================================================= --- 契约 #2 DB 迁移 | 模块:compliance(game-module-compliance,Wave3 合规域「锁风门 Gate」)| owner:Wave3 compliance 子 agent --- 文件:V9.0.0__create_game_compliance.sql(Flyway,只新增;已合入禁止修改,回滚写新补偿迁移 V9.0.1 DROP TABLE) --- 内容:compliance 模块三张表 —— 锁风门评估台账 game_compliance_gate_result + 内容分级 game_content_rating + 封禁台账 game_user_ban --- 职责(架构 Doc B + D2 范围铁律):只做 Gate(聚合风格/IP 原子→verdict + 内容分级 + 封禁台账); --- 【明确不建】审核状态机表 / audit_record —— 审核状态机权威归 project(game_review_record),compliance 绝不重写。 --- 锁风门 seam(R1):project.submitPublish 同进程调 ComplianceGateApi.evaluate → 聚合裁决落 game_compliance_gate_result + 分级落 game_content_rating --- → 返回 verdict 供 project 据此置 admitted(block→admitted=false 不抛异常)。 --- 封禁联动(R3 状态分支):写 game_user_ban 台账(主操作恒执行);下架仅当 project 状态==PUBLISHED 时才调 project reviewProject(3); --- 非 PUBLISHED 只记封禁不调状态机。【本波说明】ProjectApi 暂无 reviewProject seam,下架联动标 TODO 留 Phase C 主 agent 收口,本波仅写台账。 --- 降权出流屏蔽:P1 本波不生效(R5,不建 FeedDownweightApi 孤儿 seam)。 --- 约定:InnoDB + utf8mb4;显式列;含 Yudao 审计列 creator/create_time/updater/update_time/deleted + tenant_id; --- 状态机用 tinyint,非法流转由 Service 校验,DO 层不承载;中文列注释;关键查询建索引、禁裸 select*。 --- 错误码段:compliance = 1-109-***-*** --- ============================================================================= - --- ----------------------------------------------------------------------------- --- 表:game_compliance_gate_result —— 锁风门评估台账(每次 publish 评估各落一条,审计/溯源用) --- 用途:project.publish 注入 evaluate 时落聚合裁决 + 各原子明细 JSON;admin 复核与合规溯源据此查 --- 写口径:非幂等(每次评估各落一条审计记录,不去重);verdict 为聚合结果 max(各原子) --- ----------------------------------------------------------------------------- -CREATE TABLE `game_compliance_gate_result` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '锁风门评估记录 ID', - `game_id` BIGINT NOT NULL COMMENT '评估的游戏 ID(project.game_project.id)', - `version_id` BIGINT NOT NULL DEFAULT 0 COMMENT '评估的版本 ID(待发布版本;无版本时为 0)', - `verdict` VARCHAR(16) NOT NULL DEFAULT 'pass' COMMENT '聚合裁决:pass 通过/review 需人工复核/block 拦截(= max(各原子))', - `rating` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '本次评估落定的内容分级:all/8+/12+/16+(可空)', - `details` VARCHAR(2048) NOT NULL DEFAULT '' COMMENT '各合规原子明细 JSON([{atom,verdict,reason}],便于复核溯源)', - `trace_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '全链路追踪 ID(与 publish 链路对账)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列;锁风门评估为 system/发布发起方)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(评估时间)', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - KEY `idx_game_version` (`game_id`, `version_id`) COMMENT '按游戏+版本查最近一次锁风门评估(复核/溯源)' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '锁风门评估台账(每次 publish 评估落一条,聚合裁决+原子明细;审计/溯源)'; - --- ----------------------------------------------------------------------------- --- 表:game_content_rating —— 内容分级表(按游戏+版本唯一;评估时落、admin 复核可改) --- 用途:/app-api/compliance/rating/get 读分级;分级写入仅由 Gate evaluate 落定 + admin 复核产生 --- 唯一键:(game_id,version_id) 业务唯一(含 deleted/tenant_id 适配逻辑删+多租户),同游戏同版本只存一条最新分级 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_content_rating` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '内容分级记录 ID', - `game_id` BIGINT NOT NULL COMMENT '游戏 ID(project.game_project.id)', - `version_id` BIGINT NOT NULL DEFAULT 0 COMMENT '版本 ID(无版本时为 0)', - `rating` VARCHAR(16) NOT NULL DEFAULT 'all' COMMENT '内容分级:all 全年龄/8+/12+/16+', - `rated_by` VARCHAR(32) NOT NULL DEFAULT 'gate' COMMENT '分级来源:gate 锁风门自动/admin 人工复核', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者(admin 复核改分级时记录)', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_game_version` (`game_id`, `version_id`, `deleted`, `tenant_id`) COMMENT '游戏+版本分级业务唯一键(含 deleted/tenant_id 适配逻辑删+多租户)' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '内容分级表(评估落定+admin 复核;/app-api 只读取)'; - --- ----------------------------------------------------------------------------- --- 表:game_user_ban —— 封禁台账(封禁/降权的权威台账;主操作恒执行) --- 用途:admin 封禁游戏/用户落台账(恒执行);R3 下架联动仅当游戏==PUBLISHED 时才调 project 状态机(本波标 TODO); --- R5 降权出流屏蔽 P1 本波不生效(不建 feed seam) --- 唯一键:(target_type,target_id,status) 生效中唯一(status=1 生效,含 deleted/tenant_id),同目标不可重复生效封禁;解封后(status=0)可再次封禁 --- 状态机 status:1 生效 → 0 已解封(解封由 admin 触发,Service 校验合法流转) --- ----------------------------------------------------------------------------- -CREATE TABLE `game_user_ban` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '封禁台账记录 ID', - `target_type` TINYINT NOT NULL COMMENT '封禁目标类型:1 游戏 / 2 用户', - `target_id` BIGINT NOT NULL COMMENT '封禁目标 ID(游戏=game_id / 用户=user_id)', - `ban_type` TINYINT NOT NULL DEFAULT 1 COMMENT '处置类型:1 封禁(下架/禁用)/ 2 降权(出流屏蔽,P1 本波不生效)', - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '封禁状态机:1 生效 / 0 已解封(合法流转由 Service 校验)', - `reason` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封禁/降权原因(合规处置说明)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列;封禁操作的 admin userId)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(封禁时间)', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者(解封操作的 admin userId)', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_target_active` (`target_type`, `target_id`, `status`, `deleted`, `tenant_id`) COMMENT '生效中封禁业务唯一键:同目标 status=1 不可重复(含 deleted/tenant_id 适配逻辑删+多租户)', - KEY `idx_type_status` (`target_type`, `status`) COMMENT 'admin 按目标类型+状态分页查封禁台账' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '封禁台账表(封禁/降权权威台账;主操作恒执行,下架联动按 project 状态分支)'; diff --git a/game-cloud/game-module-studio/game-module-studio-server/src/main/resources/db/migration/V18.0.0__create_game_source_project.sql b/game-cloud/game-module-studio/game-module-studio-server/src/main/resources/db/migration/V18.0.0__create_game_source_project.sql deleted file mode 100644 index e2f9e267..00000000 --- a/game-cloud/game-module-studio/game-module-studio-server/src/main/resources/db/migration/V18.0.0__create_game_source_project.sql +++ /dev/null @@ -1,49 +0,0 @@ --- ============================================================================= --- 契约 #2 DB 迁移 | 模块:studio(game-module-studio)| owner:后端线(6c6g 编辑 / mini-desktop 验迁移) --- 文件:V18.0.0__create_game_source_project.sql(Flyway,只新增;接 V17.0.0 之后; --- 已合入禁止修改,回滚写新补偿迁移 V18.0.1 DROP TABLE,遵 V2.0.0/V17 头注铁律) --- 背书:HJ 固定架构+SAA agentic studio execution §5.2(2026-06-17)——「源项目存储=独立 game_source_project 表,勿塞 game_version」。 --- 内容:新增源项目工件表 game_source_project —— 生成主线「改源不改打包产物」基座的源侧存储面(与 GamePackage 产物面解耦)。 --- 范式(生命周期项目模型):游戏 = 长生命周期源项目(LLM as 工作室);本表存「可维护的结构化源项目工件」 --- (SourceProject schema = contracts/agent-loop/source-project.schema.json);构建产物(bundle/manifest)仍存 --- game_version + game_runtime_package(GamePackage 产物 schema 不变)。 --- 边界纪律: --- - 源/产物物理解耦:源项目存本表(additive 新存储面),打包产物存 game_version(既冻 GamePackage schema 不变);勿把源字段塞进 game_version。 --- - 版本产物权威仍归 project(game_version);本表 version_id 仅持引用(构建成功建包后回填),不在本模块建版本表。 --- - 事务边界(化解孤儿,execution §5.2):源落库(status=0) → 触发确定性构建 → 成功建 preview package 回填 version_id+status=1; --- 失败 status=2(孤儿)、不建包、不动 currentVersion。源落库与建包非同一大事务(构建数十秒级,长事务=连接池杀手),用 status 机器态串联。 --- - modify「改源不改产物」:base_version_id 记血缘(create 时 NULL);modify 产新源行 → 新预览版,不动 currentVersion、不自动入 feed,走发布审核。 --- 约定:InnoDB + utf8mb4;显式列;含 Yudao 审计列 creator/create_time/updater/update_time/deleted + tenant_id; --- 状态机用 tinyint,非法流转由 Service 校验,DO 层不承载;中文列注释;关键查询建索引、禁裸 select*。 --- 错误码段:studio = 1-112-***-***(本迁移不引入新错误码)。 --- ============================================================================= - --- ----------------------------------------------------------------------------- --- 表:game_source_project —— 游戏源项目工件表(改源不改产物的源侧存储面) --- 用途:studio 工作室编排(create/modify/extend)产出的结构化源项目工件落库;构建从源重新产出字节等价 bundle(可寻址/幂等/可复现)。 --- 可寻址/幂等:source_hash = 源规范化 sha256(= SourceProject.sourceHash);按 game_id 查历史、按 version_id 反查源、按 source_hash 去重。 --- 源态 status:0 草稿 → 1 已构建(建包回填 version_id) / 2 孤儿(构建失败,不建包) / 3 已发布。 --- M0 存储:source_json 走 DB(LONGTEXT),大资产经 ref 外置;切对象存储后改用 source_url(与 source_json 二选一)。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_source_project` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '源项目工件 ID', - `game_id` BIGINT NOT NULL COMMENT '所属游戏 ID(project.game_project.id)', - `version_id` BIGINT NULL DEFAULT NULL COMMENT '关联产物版本 ID(project.game_version.id);构建成功建包后回填,失败留 NULL=孤儿', - `source_json` LONGTEXT NULL COMMENT '源项目工件 JSON(SourceProject schema;M0 走 DB,大资产经 ref 外置)', - `source_url` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '源项目对象存储 URL(切对象存储后用,与 source_json 二选一)', - `source_hash` CHAR(64) NOT NULL DEFAULT '' COMMENT '源规范化 sha256(可寻址/幂等键;= SourceProject.sourceHash)', - `schema_version` VARCHAR(16) NOT NULL DEFAULT '1.0' COMMENT '源项目契约版本(独立于 GamePackage 版本)', - `build_profile` VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '构建画像(目标引擎/优化档;= buildProfile,影响 buildInputHash)', - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '源态:0草稿 1已构建 2孤儿(构建失败) 3已发布(Service 校验流转)', - `base_version_id` BIGINT NULL DEFAULT NULL COMMENT 'modify 血缘:本源派生自的 base 版本 ID(create 时 NULL)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - KEY `idx_game` (`game_id`, `id`) COMMENT '按游戏查源项目历史(按 id 倒序)', - KEY `idx_version` (`version_id`) COMMENT '按产物版本反查源', - KEY `idx_source_hash` (`source_hash`) COMMENT '可寻址/幂等去重' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '游戏源项目工件表(改源不改产物;与 game_version 产物面解耦)'; diff --git a/game-cloud/game-module-studio/game-module-studio-server/src/main/resources/db/migration/V8.0.0__create_game_studio.sql b/game-cloud/game-module-studio/game-module-studio-server/src/main/resources/db/migration/V8.0.0__create_game_studio.sql deleted file mode 100644 index 9e2f2e0b..00000000 --- a/game-cloud/game-module-studio/game-module-studio-server/src/main/resources/db/migration/V8.0.0__create_game_studio.sql +++ /dev/null @@ -1,92 +0,0 @@ --- ============================================================================= --- 契约 #2 DB 迁移 | 模块:studio(game-module-studio,Wave3 创作主链路最小版)| owner:studio 路 --- 文件:V8.0.0__create_game_studio.sql(Flyway,只新增;已合入禁止修改,回滚写新补偿迁移 V8.0.1 DROP TABLE) --- 内容:studio 模块核心表 —— 创作会话 game_studio_session + 创作任务链 game_studio_taskchain + 资产槽位 game_studio_asset --- 职责(架构 Doc B §71-76):有状态创作工作台后端。编排「建草稿(委托 project createProject)→一句话生成(委托 aigc) --- →进度轮询→重生成→模板应用」。不跑 LLM(委托 aigc)、不持久化项目版本(交 project)、不做锁风裁决(调 compliance)。 --- 边界纪律: --- - 项目/版本权威归 project(game_project / game_version);studio 仅持 game_id/version_id 引用,不在本模块建版本表。 --- - 生成任务权威归 aigc(game_aigc_task);studio 任务链仅持 aigc_task_id 引用 + 自身链态,不在本模块重建生成状态机。 --- - attachments 本波仅持久化(R7),不透传 aigc(aigc reqDTO 无 attachments 字段)。 --- - MVP 进度推送走轮询(D4),非 MQ/SSE。 --- 约定:InnoDB + utf8mb4;显式列;含 Yudao 审计列 creator/create_time/updater/update_time/deleted + tenant_id; --- 状态机用 tinyint,非法流转由 Service 校验,DO 层不承载;中文列注释;关键查询建索引、禁裸 select*。 --- 错误码段:studio = 1-112-***-*** --- ============================================================================= - --- ----------------------------------------------------------------------------- --- 表:game_studio_session —— 创作会话表(有状态创作工作台主记录,对齐 P-CRT-10 进工作坊) --- 用途:创作者进工作坊 upsert 一条会话,承载草稿入参(title/prompt/templateId/attachments)+ 移交后的 game_id。 --- 移交:createDraft 调 project ProjectApi.createProject 拿 gameId 回写本表 game_id(草稿移交 project,P-TPL-03)。 --- attachments:JSON 持久化(本波仅持久化不透传 aigc,R7)。 --- 状态机 status:0 草稿 → 1 生成中 → 2 完成 / 3 失败(StudioSessionStatusEnum;由 generate/getTask 透传 aigc 终态映射)。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_studio_session` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '创作会话 ID', - `creator_user_id` BIGINT NOT NULL COMMENT '创作者用户 ID(业务字段;DataPermission:创作者只见自己会话。区别于审计列 creator)', - `game_id` BIGINT NULL DEFAULT NULL COMMENT '移交后的游戏 ID(project.game_project.id);createDraft 调 createProject 后回写', - `template_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '玩法模板 ID(透传 aigc,如 dodge/runner/clicker/puzzle/shooter)', - `title` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '作品标题(建草稿入参,透传 project createProject)', - `prompt` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '一句话自然语言 Prompt(透传 aigc submitGenerate)', - `attachments` JSON NULL DEFAULT NULL COMMENT '附件上下文(P-CRT-04,JSON 数组);本波仅持久化,P1 透传 aigc', - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '会话状态机:0草稿 1生成中 2完成 3失败(StudioSessionStatusEnum;Service 校验流转)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - KEY `idx_creator` (`creator_user_id`, `id`) COMMENT '创作者「我的创作会话」列表(DataPermission 边界 + 按 id 倒序)', - KEY `idx_game` (`game_id`) COMMENT '按移交后的游戏 ID 反查会话' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '创作会话表(有状态创作工作台主记录;草稿入参 + 移交 game_id)'; - --- ----------------------------------------------------------------------------- --- 表:game_studio_taskchain —— 创作任务链表(线性单步编排,对齐 P-CRT-01/09 一句话生成 + 重生成血缘) --- 用途:generate 时落一条任务链(status=1),回填 aigc_task_id;getTask 轮询透传 aigc 进度/状态映射链态。 --- 引用边界:aigc_task_id 引用 aigc.game_aigc_task.id(生成权威归 aigc);version_id 引用 project.game_version.id --- (aigc succeeded 后回填,版本权威归 project);本表只持引用 + 自身链态,不重建生成状态机。 --- 重生成血缘:retry_of 指向原任务链 id(P-CRT-09);regenerate 新建一条 retry_of=原 id 的链。 --- 链态 status:0 初始 → 1 生成中 → 2 完成(回填 version_id) / 3 失败(透传 aigc failed/timed_out)。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_studio_taskchain` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '创作任务链 ID', - `session_id` BIGINT NOT NULL COMMENT '所属创作会话 ID(= game_studio_session.id)', - `creator_user_id` BIGINT NOT NULL COMMENT '创作者用户 ID(DataPermission 边界,归属校验用)', - `aigc_task_id` BIGINT NULL DEFAULT NULL COMMENT '委托的 aigc 生成任务 ID(= aigc.game_aigc_task.id);generate 后回填,轮询据此查 aigc', - `game_id` BIGINT NULL DEFAULT NULL COMMENT '关联游戏 ID(project.game_project.id,透传 aigc 用)', - `version_id` BIGINT NULL DEFAULT NULL COMMENT '产物版本 ID(project.game_version.id);aigc succeeded 后由 getTask 回填', - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '任务链状态机:0初始 1生成中 2完成 3失败(Service 校验流转,透传 aigc 终态映射)', - `progress` INT NOT NULL DEFAULT 0 COMMENT '进度百分比 0-100(透传 aigc progress,供前端进度条 P-CRT-12)', - `retry_of` BIGINT NULL DEFAULT NULL COMMENT '若为重生成,指向原任务链 ID(P-CRT-09 重生成血缘)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - KEY `idx_session` (`session_id`, `id`) COMMENT '按会话查任务链(含重生成历史,按 id 倒序)', - KEY `idx_aigc` (`aigc_task_id`) COMMENT '按 aigc 任务 ID 反查任务链(轮询回写映射)' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '创作任务链表(线性单步编排;持 aigc_task_id/version_id 引用 + 链态)'; - --- ----------------------------------------------------------------------------- --- 表:game_studio_asset —— 创作资产槽位表(单步出整包,对齐 P-CRT-02;MVP 仅落槽位 seam,不六路并发) --- 用途:单步生成产物的资产槽位记录(asset_type 槽位 + asset_url + 槽位状态)。 --- 狠切(R7/草稿 scope):MVP 只建槽位表占位 seam,不实现六路并发资产生成;资产由 aigc 单步整包产出,studio 仅登记槽位。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_studio_asset` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '资产槽位 ID', - `session_id` BIGINT NOT NULL COMMENT '所属创作会话 ID(= game_studio_session.id)', - `asset_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '资产槽位类型(如 sprite/background/audio/icon;MVP 槽位占位)', - `asset_url` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '资产 URL(OSS/MinIO;单步整包产出后登记)', - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '槽位状态:0未生成 1已生成(Service 校验,MVP 单步直填)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - KEY `idx_session_type` (`session_id`, `asset_type`) COMMENT '按会话+槽位类型查资产' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '创作资产槽位表(单步出整包槽位 seam;MVP 不六路并发)'; diff --git a/game-cloud/game-module-trade/game-module-trade-server/src/main/resources/db/migration/V7.0.0__create_game_trade.sql b/game-cloud/game-module-trade/game-module-trade-server/src/main/resources/db/migration/V7.0.0__create_game_trade.sql deleted file mode 100644 index 91c705f3..00000000 --- a/game-cloud/game-module-trade/game-module-trade-server/src/main/resources/db/migration/V7.0.0__create_game_trade.sql +++ /dev/null @@ -1,118 +0,0 @@ --- ============================================================================= --- 契约 #2 DB 迁移 | 模块:trade(game-module-trade,Wave2 变现域「分账/结算/提现」)| owner:WS5 --- 文件:V7.0.0__create_game_trade.sql(Flyway,只新增;已合入禁止修改,回滚写新补偿迁移) --- 内容:trade 模块核心表 —— 收益账户 game_trade_account + 收益流水 game_trade_income + 提现申请 game_trade_withdraw --- 职责(架构 Doc B):多源收入分账/归集/结算/对账;钱包打款/提现门槛与打款执行属 trade(不属 pay);不做支付收单(归 pay)。 --- MVP 钱财闭环(决策 D3):广告收入 → trade 分账(创作者 80%) → 创作者收益账户 → 提现(满 5 元)。会员订阅/支付收单=P1,本轮不做。 --- 上游 seam:消费 ad 模块 AdRevenueApi.getUnsettledRevenue/markSettled(详见 ad.yaml x-feign-contracts),T+1 SettlementJob 据此分账。 --- --- 【配置化(决策 D3,全配置走 Nacos/DB,严禁硬编码)—— 本迁移不建死值,运行时读 Nacos】: --- - trade.creator-share = 0.80 (MVP 全档统一 80% 分账比例;分层结构 creator_share_tier 保留、值后期填,见下方占位说明) --- - trade.withdraw-min = 500 (单位:分,满 5 元可提现门槛) --- 分账规则不建表(走 Nacos 配置);分层占位说明(later):若引入分层分账,可加 game_trade_share_rule --- (tier 等级/min_income 门槛/share_rate 比例),按创作者等级取不同 share_rate;MVP 不建,统一读 trade.creator-share。 --- 落账定格:game_trade_income.share_rate 落账时定格当时的 Nacos 比例快照,不随后续配置变动重算(保证历史流水可对账、金额不漂移)。 --- --- 约定:InnoDB + utf8mb4;显式列;含 Yudao 审计列 creator/create_time/updater/update_time/deleted + tenant_id; --- 金额一律「分」(BIGINT) 禁浮点,比例用 DECIMAL;资金写入有幂等键(biz_no/source_ref + uk);状态机用 tinyint,非法流转 Service 拒绝; --- 账户表 = 流水的物化汇总,余额变更必有对应流水/单据;中文列注释;关键查询建索引、禁裸 select*。 --- 错误码段:trade = 1-106-***-*** --- ============================================================================= - --- ----------------------------------------------------------------------------- --- 表:game_trade_account —— 创作者收益账户(物化汇总;余额变更必有对应流水/提现单) --- 不变式:total_income = Σ game_trade_income.net_amount(本人分账)+ Σ game_trade_grant.amount(本人 admin 赋余额)—— --- 即「累计入账」= 分账入账 + admin 赋余额两类资金来源之和(U2 起 admin 赋余额也计入,维持下方账面恒等式); --- balance + frozen + total_withdraw = total_income(余额 + 冻结中 + 已提现 = 累计入账,恒等式始终成立) --- 写入点:① 分账入账(SettlementJob)→ total_income += net、balance += net; --- ②′ admin 赋余额(U2,AccountService.grant)→ total_income += amount、balance += amount(记 game_trade_grant 流水佐证); --- ② 提现申请 → balance -= amount、frozen += amount(冻结); --- ③ 打款成功 → frozen -= amount、total_withdraw += amount; --- ④ 驳回/打款失败 → frozen -= amount、balance += amount(退回)。 --- 并发:余额增减用行级原子更新(UPDATE ... SET balance = balance ± ? WHERE user_id=? AND balance >= ?),扣减带余额充足条件防超扣。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_trade_account` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '收益账户 ID', - `user_id` BIGINT NOT NULL COMMENT '创作者用户 ID(DataPermission:创作者只见自己账户)', - `balance` BIGINT NOT NULL DEFAULT 0 COMMENT '可提现余额(单位:分,BIGINT 禁浮点)', - `frozen` BIGINT NOT NULL DEFAULT 0 COMMENT '冻结中(提现处理中占用,单位:分;提现申请时从 balance 转入)', - `total_income` BIGINT NOT NULL DEFAULT 0 COMMENT '累计入账(单位:分;= Σ 本人收益流水 net_amount + Σ 本人 admin 赋余额 grant.amount;U2 起含赋余额,维持 balance+frozen+total_withdraw 恒等式)', - `total_withdraw` BIGINT NOT NULL DEFAULT 0 COMMENT '累计已提现(已打款成功总额,单位:分)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(账户初始化时间)', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_user` (`user_id`, `tenant_id`) COMMENT '一个创作者一个收益账户(幂等初始化,防并发重复建账户)' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '创作者收益账户表(流水物化汇总,余额变更必有流水)'; - --- ----------------------------------------------------------------------------- --- 表:game_trade_income —— 收益流水(分账后入账,每笔来源收入一条;账户余额的明细来源) --- 分账:net_amount = gross_amount × share_rate(创作者实得,落账时按分计算定格);平台分成 = gross - net(不单独存,报表算) --- 幂等核心:uk_source(source, source_ref) 防同一笔来源收入重复分账—— --- 广告:source=1(ad)、source_ref=game_ad_revenue.id;打赏:source=2(tip)、source_ref=打赏单 id。 --- SettlementJob 重跑/补偿时,命中 uk_source 即跳过该笔,保证不重复入账(不重复发钱)。 --- 对账锚点:source_ref + trace_id 与 ad 侧 game_ad_revenue.id + trace_id 对齐,端到端可对账。 --- 注:本流水即「income_settled 已落账」事实;契约#5 income_settled 事件(props:amount/source)为分析埋点镜像,不替代本表。 --- 金额单位:gross_amount / net_amount 一律「分」(BIGINT) 禁浮点;share_rate 用 DECIMAL(5,4)(如 0.8000)。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_trade_income` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '收益流水 ID', - `user_id` BIGINT NOT NULL COMMENT '创作者用户 ID(收入归属,DataPermission:只见自己)', - `source` TINYINT NOT NULL COMMENT '收入来源:1 ad 广告 / 2 tip 打赏(MVP 仅 ad;tip 留位)', - `source_ref` VARCHAR(64) NOT NULL COMMENT '来源单号(对账锚点):广告=game_ad_revenue.id;打赏=打赏单 id。与 source 组成幂等唯一键', - `gross_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '原始收入(分账前,单位:分)', - `share_rate` DECIMAL(5,4) NOT NULL DEFAULT 0.0000 COMMENT '分账比例(如 0.8000=创作者得 80%;落账时定格 Nacos trade.creator-share 快照,不随配置变动重算)', - `net_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '创作者实得 = gross_amount × share_rate(单位:分,落账定格;累加入账户 balance/total_income)', - `settle_date` DATE NOT NULL COMMENT 'T+1 结算日(yyyy-MM-dd;= 上游 game_ad_revenue.stat_date)', - `trace_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '全链路 traceId(广告来源透传自 game_ad_revenue.trace_id,供端到端对账)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列;分账 job 为 system)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(入账时间)', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_source` (`source`, `source_ref`, `tenant_id`) COMMENT '幂等核心:同一笔来源收入(source+source_ref)只分账入账一次,防重复发钱', - KEY `idx_user_settle` (`user_id`, `settle_date`) COMMENT '我的收益流水分页(创作者+结算日)', - KEY `idx_settle_date` (`settle_date`) COMMENT '平台营收报表按日聚合 gross/net' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '收益流水表(分账后入账;账户余额明细来源,uk 防重复分账)'; - --- ----------------------------------------------------------------------------- --- 表:game_trade_withdraw —— 提现申请(提现门槛/冻结/审核/打款执行,属 trade 不属 pay) --- 状态机 status:0待审核 →(1审核通过打款中 / 3驳回) ;1 →(2已打款 / 4打款失败) --- 合法流转(非法流转由 WithdrawService 拒绝,DO 层不承载校验): --- 0 → 1(审核通过) :管理端审核通过 --- 0 → 3(审核驳回) :冻结金额 frozen 退回 balance(资金原路退回,必填 reject_reason) --- 1 → 2(打款成功,终态) :frozen 扣减、total_withdraw 累加;MVP mock 渠道审核通过即置 2 --- 1 → 4(打款失败,终态) :frozen 退回 balance,可重新发起新单 --- 终态:2/3/4 不可再变;非法如 0→2(未审核直接打款)、2→*(已打款再变)一律拒绝。 --- 幂等核心:uk_biz_no(biz_no) 防重复提现申请——同一 biz_no 重复提交返回已有申请,不重复冻结余额。 --- 资金安全:申请时原子校验 amount ≥ Nacos trade.withdraw-min 且 ≤ balance,扣 balance 加 frozen;金额单位「分」。 --- ----------------------------------------------------------------------------- -CREATE TABLE `game_trade_withdraw` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '提现申请 ID', - `user_id` BIGINT NOT NULL COMMENT '创作者用户 ID(DataPermission:只见自己提现)', - `amount` BIGINT NOT NULL COMMENT '提现金额(单位:分,BIGINT 禁浮点;申请时校验 ≥Nacos trade.withdraw-min 且 ≤可提现余额)', - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态机:0待审核 1审核通过打款中 2已打款 3驳回 4打款失败(合法流转见表头注释,非法流转 Service 拒绝)', - `biz_no` VARCHAR(64) NOT NULL COMMENT '提现业务单号 = 幂等键(前端生成唯一;重复提交同一 biz_no 不重复冻结)', - `channel` VARCHAR(16) NOT NULL DEFAULT 'mock' COMMENT '打款渠道:MVP=mock 桩;真实 later 接 pay 渠道异步回调', - `apply_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', - `audit_time` DATETIME NULL COMMENT '审核时间(审核后写入)', - `audit_user_id` BIGINT NULL COMMENT '审核人用户 ID(管理端 RBAC)', - `pay_time` DATETIME NULL COMMENT '打款时间(打款成功 status=2 时写入)', - `reject_reason` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '驳回理由(status=3 时必填,回填创作者)', - `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注(申请/审核备注)', - `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列;申请人)', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删', - `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(Yudao 多租户兼容,MVP 单租户=0)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_biz_no` (`biz_no`, `tenant_id`) COMMENT '提现幂等键:同一 biz_no 只受理一次(防重复提现/重复冻结)', - KEY `idx_user_status` (`user_id`, `status`) COMMENT '我的提现记录分页(创作者+状态)', - KEY `idx_status_apply` (`status`, `apply_time`) COMMENT '管理端提现审核队列(按状态+申请时间)' -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '提现申请表(提现门槛/冻结/审核/打款,属 trade)';