games-development-ai/contracts/db-schemas/V6.0.0__create_game_ad.sql
zizi 43fce856d7 feat: Wave2 变现域 ad+trade 后端建成验证 + game-admin 运营台脊柱
后端(主 agent 独立复跑验证,非采信子 agent 自报):
- 新建 game-module-ad(广告引擎)+ game-module-trade(分账/结算/提现),克隆黄金模板 project
- 契约先行已锁:ad.yaml/trade.yaml + Flyway V6/V7;ad 计费 uk_trace 收紧为 (trace_id,event_type,tenant_id) 防 reward 被 impression 静默吞掉
- ad↔trade Feign seam(AdRevenueApi.getUnsettledRevenue/markSettled)+ project-api 归因(getCreatorUserId,game 模块首个跨模块 Feign,仿 yudao DictDataApi)
- AdProvider SPI(mock,留 csj/gdt 注入位);资金安全:金额用分/uk 幂等/状态机 CAS/防超扣/T+1 结算补偿
- pay 不建,复用 yudao-pay 后置(Doc B:收单归 pay、钱包提现归 trade;MVP 钱财闭环 ad→trade 不经收单)
- 验证:mvn -pl yudao-server -am compile 全量 BUILD SUCCESS;ad 15 + trade 24 = 39 单测绿;huijing 残留=0、裸 select*=0

前端 game-admin(运营后台):
- 克隆 yudao-ui-admin-vue3 裁剪(删 crm/erp/mall/mes/iot 等无关业务模块)+ 5 个 MVP 运营页接 admin-api(审核/精选/看板/广告位/提现审核)+ mock 兜底
- 构建验证移至 mini-desktop(本机 5.8G 内存 OOM,见执行 spec)

文档:HJ-PAR-002 review+execution 双 spec + Workflow 编排脚本

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:20:17 +00:00

78 lines
9.7 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================================================
-- 契约 #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 分账)';