- 范围:全仓除已完成的 game-cloud/game-admin(及排除区);91 文件 - cn.iocoder.yudao→com.wanxiang.huijing / cn.wanxiang.game→com.wanxiang.huijing.game / cn.iocoder.cloud→com.wanxiang / Yudao*→Huijing* / bare yudao→huijing - 含 contracts/api-schemas(apiInterface FQCN)、docs/architecture+agent-specs(含中文名,git ls-files -z 修复)、AGENTS.md/CLAUDE.md 等根文档 - 保留不动:上游归属 URL(gitee/github yudaocode·YunaiV,改则断链+违反署名)、game-cloud Flyway 迁移注释(保 checksum)、lockfile(装包重生) - 纯文档/字符串,零构建影响(game-cloud/game-admin 字节码未动) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
9.8 KiB
SQL
78 lines
9.8 KiB
SQL
-- =============================================================================
|
||
-- 契约 #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;显式列;含 Huijing 审计列 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 '创建者(Huijing 审计列)',
|
||
`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(Huijing 多租户兼容,MVP 单租户=0)',
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_slot` (`slot_id`, `deleted`, `tenant_id`) COMMENT '广告位逻辑 ID 业务唯一键(含 deleted/tenant_id 适配 Huijing 逻辑删除+多租户,避免软删后无法复用 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 '创建者(Huijing 审计列;计费上报为 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(Huijing 多租户兼容,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 分账)';
|