lili b31cc37a4f chore(brand): 造梦AI→绘境AI 全活层品牌不变量收口(441 文件)
把活层退役旧品牌「造梦AI」统一改为现行「绘境AI」,brand-invariant 门红线归零。构成:
~412 Java @author 署名 + game-studio/index.html 浏览器标题 + game-runtime/package.json 描述
+ game-admin/.env VITE_APP_TITLE 运营后台标题 + contracts API title/@Schema 用户协议示例/events 描述
+ docs-design mockup 品牌位 + 各模块注释。全是注释/显示/元数据,零逻辑改动。
zaomeng 拼音持久化标识符按 2026-06-24 评审 GR-05 不动(347 处保留)。
白名单(docs/ip 法律备案 / _archive / 带日期留痕)与 AGENTS.md 门定义自身不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:29:11 -07:00

470 lines
31 KiB
YAML
Raw Permalink 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.

openapi: 3.0.3
# 契约 #1 API | 模块adgame-module-adWave2 变现域「广告引擎」)| ownerWS5 主笔,全员 review
# 职责(架构 Doc B联盟接入 / 广告位 AI 植入 / 曝光计费 / eCPM / 归因。不做结算提现(归 trade
# MVP provider 默认 mock真实 csj 穿山甲 / gdt 优量汇 走审核闸门后注入,见契约#7 ad-slot 与执行 spec 风险#6
# Provider 用 SPI 思路AdProvider:mock/csj/gdt本契约层只暴露统一形态不绑死联盟provider!=mock 时由人提供 providerSlotId审核闸门后注入
# 端:/app-api产品端 game-studioSDK Plugin.Ad用户/匿名 Token /admin-api管理端 game-adminRBAC前缀由 huijing 框架按 controller.app/admin 包名自动添加
# 错误码段ad = 1-111-***-***本模块独占禁止与他模块重叠100 project/101 aigc/102 runtime/103 feed/104 telemetry/106 trade
# 响应统一 Huijing CommonResult 信封:{ code, data, msg }code=0 成功
# 脊柱钱财闭环上游SDK 拉广告位(#7) → 有效曝光/激励完成上报(计费级,幂等) → mock eCPM 计收入 → 落广告收入台账 game_ad_revenue(settle_status=0 未结算) → 经 x-feign-contracts 暴露给 trade 做 T+1 分账trade 入账后回标 settle_status=1
# 计费 vs 分析埋点:本模块 report/impression、report/reward = 计费级(落 game_ad_revenue决定收入区别于 telemetry(#5 ad_impression/ad_reward) 的分析埋点(落 game_telemetry_event仅统计二者并存、口径不同、不可互相替代
# 计费幂等键game_ad_revenue.uk_trace=(trace_id, event_type, tenant_id)。同 (traceId,event_type) 重复上报幂等去重;同一 traceId 的 impression 与 reward 各落一条、互不覆盖(防 reward 被 impression 静默吞掉)
# 收入口径避免同一次展示重复计收入type=rewarded 收入计在 rewardimpression 若上报 revenue_amount=0type=interstitial/banner 收入计在 impression。MVP SDK 仅 showRewarded一次广告交互一个 traceId故 rewarded 一次展示即一条 reward 计费
# 与契约#7 对齐:广告位 CRUD 字段严格对齐 ad-slot.schema.jsonslotId/type/provider/placement/providerSlotId/ecpmFloor/enabled/compliance不重造广告位结构
# 与契约#5 对齐:不重造 ad_impression/ad_reward/income_settled 事件(那是 telemetry 分析埋点);本模块只新增计费上报接口与收入台账
info:
title: 绘境AI ad 模块 API
version: 1.0.0
description: >-
广告引擎("赚得到"上游。SDK Plugin.Ad 拉取启用广告位(契约#7) → 有效曝光/激励视频完成计费上报 →
mock provider 算 eCPM 收入 → 落广告收入台账 game_ad_revenue → 经 Feign 暴露未结算收入给 trade 做 T+1 分账。
MVP 全 mock provider真实联盟走 SPI + 审核闸门后注入);前端据此 vite-plugin-mock 自动生成 mock。
servers:
- url: http://localhost:48080
description: 本地Swagger/Knife4j http://localhost:48080/doc.html
paths:
# ===========================================================================
# 产品端 /app-apiSDK Plugin.Ad用户 Token 或匿名 anon Token
# ===========================================================================
/app-api/ad/slot/list-enabled:
get:
tags: [app-ad]
summary: 拉取启用的广告位配置SDK Plugin.Ad 初始化用;返回 AdSlotConfig 列表,对齐契约#7
description: >-
SDK Plugin.Ad 据此渲染广告位。返回 enabled=true 的广告位配置,字段对齐契约#7 ad-slot.schema.json
slotId/type/provider/placement/ecpmFloor/compliance
安全provider!=mock 时 providerSlotId 为联盟侧真实广告位 ID非前端渲染必需则不下发明文默认不下发仅服务端调联盟时使用本接口 providerSlotId 恒为空串占位,真实联盟由服务端代理拉广告)。
可选按 placement 过滤feed 间隙/本局结束/暂停),便于 SDK 按场景取位。
parameters:
- { name: placement, in: query, required: false, schema: { type: string, enum: [game_end, pause, feed] }, description: '触发场景过滤(对齐#7 placement空则返回全部启用广告位' }
responses:
'200':
description: 成功(启用广告位配置列表)
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultAdSlotConfigList' }
/app-api/ad/report/impression:
post:
tags: [app-ad]
summary: 有效曝光上报计费级T-AD-01幂等键=traceIdmock 算 eCPM 收入落台账)
description: >-
有效曝光计费上报(区别于 telemetry 分析埋点 ad_impression。服务端按 slotId 找广告位配置,
校验合规compliance.blockMinor=未成年不计费、maxPerSession 单会话上限mock provider 用广告位 ecpmFloor 算单次曝光收入
mock 口径revenue = ecpmFloor / 1000单位分向下取整见 decisions落广告收入台账 game_ad_revenue(event_type=impression, settle_status=0 未结算)。
幂等:以 traceId 为幂等键(对应 game_ad_revenue.uk_trace重复上报同一 traceId 只计一次收入(命中唯一键即跳过,返回上次受理结果)。
收入归因creatorUserId 由 gameId 反查 project.game_project.creator_user_id收入归到游戏作者落台账 creator_user_id 供 trade 按创作者聚合分账。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AdImpressionReqVO' }
responses:
'200':
description: 受理结果(计费收入 + 是否首次计费)
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultAdReportResult' }
/app-api/ad/report/reward:
post:
tags: [app-ad]
summary: 激励视频完成校验T-AD-02幂等键=traceIdmock 直接校验通过并计激励收入)
description: >-
激励视频type=rewarded观看完成校验决定是否发奖rewarded=true 时前端发放游戏内奖励)。
mock provider 直接校验通过返回 rewarded=true真实 provider later 走服务端回调校验防作弊,见 decisions本接口为前端上报入口真实校验由服务端→联盟回调链路 /app-api/ad/reward/callback 替换/增强)。
【M4】provider=callback(sandbox 自回调桩)/csj/gdt 时发奖凭据从「前端自报」升级为「服务端回调验签」AdProvider.verifyReward 演进为携带回调上下文的验签形态mock 仍直通。
校验通过同时按 reward 计费落 game_ad_revenue(event_type=reward, settle_status=0),收入归因同 impression。
幂等:以 traceId 为幂等键uk_trace重复上报同一 traceId 只发奖一次、只计费一次。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AdRewardReqVO' }
responses:
'200':
description: 校验结果rewarded + 计费收入)
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultAdRewardResult' }
/app-api/ad/reward/callback:
post:
tags: [app-ad]
summary: 激励视频服务端回调校验T-AD-09联盟回调入站端点签名校验防作弊
description: >-
【M4 真实化】激励视频「服务端→联盟回调校验」入站端点(防作弊,替代「前端上报直通发奖」)。
真实联盟csj/gdt在用户看完激励视频后由联盟服务端回调本端点携带 transId联盟侧交易号+ sign联盟签名+ 业务参数;
服务端用联盟密钥校验 sign 通过后,方按 reward 计费落 game_ad_revenueevent_type=reward并标记该 traceId 可发奖。
provider=mock/callback(sandbox 自回调桩) 时签名校验走桩直通;真实 csj/gdt 由 SPI 实现注入真实验签(审核闸门后)。
幂等:以 traceIduk_trace去重重复回调只计费一次、只发奖一次。签名校验失败返回 1-111-002-004回调签名校验失败不计费不发奖。
本端点为服务端可信入站(联盟→平台),与 /app-api/ad/report/reward前端上报入口形态不同报奖凭据从「前端自报」升级为「联盟回调+验签」。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AdRewardCallbackReqVO' }
responses:
'200':
description: 回调受理结果(验签通过并计费=accepted:true
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultAdRewardCallbackResult' }
# ===========================================================================
# 管理端 /admin-api运营/管理员 RBAC广告位 CRUD + 广告收入台账对账
# ===========================================================================
/admin-api/ad/slot/create:
post:
tags: [admin-ad]
summary: 新建广告位T-AD-03落 game_ad_slot字段对齐契约#7
description: 新建广告位配置。slotId 业务唯一uk_slot重复报错1-111-001-001。provider!=mock 时 providerSlotId 必填(审核闸门后由人填写联盟真实广告位 ID
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AdSlotSaveReqVO' }
responses:
'200':
description: 返回新建广告位主键 ID
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultLong' }
/admin-api/ad/slot/update:
put:
tags: [admin-ad]
summary: 更新广告位T-AD-03按主键 id 更新slotId 不可改)
description: 更新广告位配置type/provider/placement/providerSlotId/ecpmFloor/enabled/compliance。slotId 为业务唯一键不可改(改则用新建)。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AdSlotSaveReqVO' }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
/admin-api/ad/slot/delete:
delete:
tags: [admin-ad]
summary: 删除广告位T-AD-03逻辑删除 deleted=1
parameters:
- { name: id, in: query, required: true, schema: { type: integer, format: int64 }, description: 广告位主键 ID }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
/admin-api/ad/slot/get:
get:
tags: [admin-ad]
summary: 广告位详情T-AD-03按主键 id
parameters:
- { name: id, in: query, required: true, schema: { type: integer, format: int64 }, description: 广告位主键 ID }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultAdSlot' }
/admin-api/ad/slot/page:
get:
tags: [admin-ad]
summary: 广告位分页T-AD-03按 type/provider/enabled 筛选)
parameters:
- { name: type, in: query, required: false, schema: { type: string, enum: [rewarded, interstitial, banner] }, description: 广告形式过滤 }
- { name: provider, in: query, required: false, schema: { type: string, enum: [csj, gdt, mock, callback] }, description: 联盟过滤 }
- { name: enabled, in: query, required: false, schema: { type: boolean }, 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/CommonResultAdSlotPage' }
/admin-api/ad/revenue/page:
get:
tags: [admin-ad]
summary: 广告收入台账分页T-AD-04归因/计费对账,只读)
description: >-
广告收入台账查询game_ad_revenue供运营对账按广告位/游戏/创作者/结算状态/归集日筛选)。
这是 ad↔trade seam 的人工对账视图;与 trade 侧 game_trade_income.source_ref 通过 game_ad_revenue.id + trace_id 对齐。
parameters:
- { name: slotId, in: query, required: false, schema: { type: string }, description: 广告位逻辑 ID 过滤 }
- { name: gameId, in: query, required: false, schema: { type: integer, format: int64 }, description: 游戏 ID 过滤 }
- { name: creatorUserId, in: query, required: false, schema: { type: integer, format: int64 }, description: 收入归因到的创作者 ID 过滤 }
- { name: eventType, in: query, required: false, schema: { type: string, enum: [impression, reward] }, description: 计费事件类型过滤 }
- { name: settleStatus, in: query, required: false, schema: { type: integer, enum: [0, 1] }, description: '结算状态过滤0未结算 1已结算' }
- { name: statDate, in: query, required: false, schema: { type: string, format: date }, description: 归集日yyyy-MM-dd过滤 }
- { 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/CommonResultAdRevenuePage' }
components:
schemas:
# ---- Huijing CommonResult 信封 ----
CommonResultBoolean:
type: object
properties:
code: { type: integer, description: '0=成功非0=错误码 1-111-***-***' }
data: { type: boolean }
msg: { type: string }
CommonResultLong:
type: object
properties:
code: { type: integer }
data: { type: integer, format: int64, description: 新建广告位主键 ID }
msg: { type: string }
CommonResultAdSlot:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/AdSlotRespVO' }
msg: { type: string }
CommonResultAdSlotPage:
type: object
properties:
code: { type: integer }
data:
type: object
properties:
list: { type: array, items: { $ref: '#/components/schemas/AdSlotRespVO' } }
total: { type: integer, format: int64 }
msg: { type: string }
CommonResultAdSlotConfigList:
type: object
properties:
code: { type: integer }
data: { type: array, items: { $ref: '#/components/schemas/AdSlotConfigVO' } }
msg: { type: string }
CommonResultAdReportResult:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/AdReportResultVO' }
msg: { type: string }
CommonResultAdRewardResult:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/AdRewardResultVO' }
msg: { type: string }
CommonResultAdRevenuePage:
type: object
properties:
code: { type: integer }
data:
type: object
properties:
list: { type: array, items: { $ref: '#/components/schemas/AdRevenueRespVO' } }
total: { type: integer, format: int64 }
msg: { type: string }
# ---- 计费上报入参app-api计费级幂等键=traceId----
AdImpressionReqVO:
type: object
description: 有效曝光计费上报入参(区别于 telemetry 分析埋点。traceId 为幂等键,重复上报只计一次收入。
required: [slotId, gameId, traceId]
properties:
slotId: { type: string, description: '广告位逻辑 ID对齐契约#7 slotId据此找配置算 eCPM' }
gameId: { type: integer, format: int64, description: '当前试玩游戏 IDproject.game_project.id服务端据此反查 creator_user_id 做收入归因' }
traceId: { type: string, minLength: 1, description: '全链路追踪 ID = 计费幂等键(对应 game_ad_revenue.uk_trace重复上报只计一次' }
sessionId: { type: string, description: '会话 ID统计 maxPerSession 单会话上限用;对齐 #5 session_id 口径)' }
AdRewardReqVO:
type: object
description: 激励视频完成校验入参。mock 直接通过traceId 为幂等键,重复上报只发奖+计费一次。
required: [slotId, gameId, traceId]
properties:
slotId: { type: string, description: '广告位逻辑 IDtype 应为 rewarded' }
gameId: { type: integer, format: int64, description: '当前试玩游戏 ID收入归因到该游戏创作者' }
traceId: { type: string, minLength: 1, description: '全链路追踪 ID = 发奖/计费幂等键(对应 game_ad_revenue.uk_trace' }
sessionId: { type: string, description: '会话 ID防作弊/会话上限用)' }
# ---- 计费上报出参 ----
AdReportResultVO:
type: object
description: 有效曝光计费受理结果
properties:
accepted: { type: boolean, description: '是否受理计费(合规拦截/会话超限时为 false不计费' }
firstBilling: { type: boolean, description: '是否首次计费(幂等:重复 traceId 为 false表示命中已存在台账、未重复计收入' }
revenueAmount: { type: integer, format: int64, description: '本次计入的广告收入单位mock=ecpmFloor/1000 向下取整);未受理时为 0' }
traceId: { type: string, description: '回执 traceId便于对账排障' }
AdRewardResultVO:
type: object
description: 激励视频完成校验结果
properties:
rewarded: { type: boolean, description: '是否发奖mock 恒 true真实 provider 由服务端回调校验决定)' }
firstBilling: { type: boolean, description: '是否首次计费(幂等:重复 traceId 为 false' }
revenueAmount: { type: integer, format: int64, description: '本次计入的广告收入(单位:分);未发奖时为 0' }
traceId: { type: string, description: '回执 traceId' }
# ---- M4 新增激励视频服务端回调校验T-AD-09 防作弊回调入站)----
AdRewardCallbackReqVO:
type: object
description: 激励视频服务端回调入参(联盟→平台;服务端验签后计费发奖)
required: [slotId, gameId, traceId, sign]
properties:
slotId: { type: string, minLength: 1, description: '广告位逻辑 IDtype 应为 rewarded据此取 provider 选验签实现)' }
gameId: { type: integer, format: int64, description: '游戏 ID收入归因到该游戏创作者' }
traceId: { type: string, minLength: 1, description: '全链路追踪 ID = 发奖/计费幂等键uk_trace' }
transId: { type: string, description: '联盟侧交易号真实联盟回调携带mock/callback 桩可空)' }
sign: { type: string, minLength: 1, description: '联盟回调签名服务端用联盟密钥校验mock/callback 桩走桩直通)' }
timestamp: { type: integer, format: int64, description: '回调时间戳(防重放,可选;真实联盟验签纳入)' }
extra: { type: string, description: '联盟透传业务参数JSON 串,可空)' }
AdRewardCallbackResultVO:
type: object
description: 激励视频回调受理结果
properties:
accepted: { type: boolean, description: '是否受理(验签通过并计费=true验签失败已由错误码 1-111-002-004 拦截,不达此)' }
rewarded: { type: boolean, description: '是否可发奖(验签通过即 true' }
firstBilling: { type: boolean, description: '是否首次计费(幂等:重复 traceId 为 false' }
revenueAmount: { type: integer, format: int64, description: '本次计入的广告收入(单位:分)' }
traceId: { type: string, description: '回执 traceId' }
CommonResultAdRewardCallbackResult:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/AdRewardCallbackResultVO' }
msg: { type: string }
# ---- 广告位配置(对齐契约#7 ad-slot.schema.json----
AdSlotConfigVO:
type: object
description: >-
SDK 下发的广告位配置(对齐契约#7 ad-slot.schema.json AdSlotConfig
安全providerSlotId 默认不下发明文(恒空串),真实联盟由服务端代理拉广告,前端不持有联盟广告位 ID。
properties:
slotId: { type: string, description: '广告位逻辑 ID平台内唯一埋点 ad_impression.slot_id 引用)' }
type: { type: string, enum: [rewarded, interstitial, banner], description: '广告形式(对齐#7 type' }
provider: { type: string, enum: [csj, gdt, mock, callback], description: '联盟csj 穿山甲/gdt 优量汇/mock 桩MVP 默认 mock' }
placement: { type: string, enum: [game_end, pause, feed], description: '触发场景game_end 本局结束/pause 暂停/feed 游戏流间隙' }
ecpmFloor: { type: number, minimum: 0, description: 'eCPM 底价(分),低于此不展示' }
compliance: { $ref: '#/components/schemas/AdComplianceVO' }
AdComplianceVO:
type: object
description: 合规植入规则(对齐契约#7 compliance未成年人保护
properties:
blockMinor: { type: boolean, default: true, description: '识别为未成年则不展示/不计费' }
maxPerSession: { type: integer, minimum: 0, description: '单会话最大展示次数(计费上报按此限流)' }
# ---- 广告位 admin CRUD VO对齐契约#7 全字段)----
AdSlotSaveReqVO:
type: object
description: 广告位新建/更新入参(字段严格对齐契约#7 ad-slot.schema.json。新建无 id更新带 idslotId 不可改。
required: [slotId, type, provider, enabled]
properties:
id: { type: integer, format: int64, description: '主键 ID更新必填、新建不传' }
slotId: { type: string, minLength: 1, description: '广告位逻辑 ID业务唯一键 uk_slot新建唯一、更新不可改' }
type: { type: string, enum: [rewarded, interstitial, banner], description: '广告形式(对齐#7' }
provider: { type: string, enum: [csj, gdt, mock, callback], description: '联盟MVP 默认 mockcsj/gdt 走审核闸门后注入)' }
placement: { type: string, enum: [game_end, pause, feed], description: '触发场景' }
providerSlotId: { type: string, description: '联盟侧真实广告位 IDprovider!=mock 时必填由人提供——审核闸门后注入mock 时留空)' }
ecpmFloor: { type: number, minimum: 0, description: 'eCPM 底价低于此不展示mock 计费亦用此算收入' }
enabled: { type: boolean, description: 是否启用 }
compliance: { $ref: '#/components/schemas/AdComplianceVO' }
AdSlotRespVO:
type: object
description: 广告位详情admin #7 全字段 + 主键 + 审计时间)
properties:
id: { type: integer, format: int64, description: 主键 ID }
slotId: { type: string, description: 广告位逻辑 ID业务唯一键 }
type: { type: string, enum: [rewarded, interstitial, banner] }
provider: { type: string, enum: [csj, gdt, mock, callback] }
placement: { type: string, enum: [game_end, pause, feed] }
providerSlotId: { type: string, description: '联盟侧真实广告位 IDadmin 可见明文,供运营核对)' }
ecpmFloor: { type: number, minimum: 0, description: 'eCPM 底价(分)' }
enabled: { type: boolean }
compliance: { $ref: '#/components/schemas/AdComplianceVO' }
createTime: { type: string, format: date-time }
updateTime: { type: string, format: date-time }
# ---- 广告收入台账 VOadmin 对账只读)----
AdRevenueRespVO:
type: object
description: >-
广告收入台账记录game_ad_revenue。这是 ad↔trade seam 的数据源:
settleStatus=0 的记录由 trade 经 AdRevenueApi.getUnsettledRevenue 拉取做 T+1 分账,分账后回标 settleStatus=1。
properties:
id: { type: integer, format: int64, description: '台账记录 ID= trade 侧 game_trade_income.source_ref 对账锚点)' }
slotId: { type: string, description: 广告位逻辑 ID }
gameId: { type: integer, format: int64, description: 产生收入的游戏 ID }
creatorUserId: { type: integer, format: int64, description: '收入归因到的创作者用户 IDtrade 按此聚合分账)' }
eventType: { type: string, enum: [impression, reward], description: '计费事件类型impression 有效曝光/reward 激励完成' }
provider: { type: string, enum: [csj, gdt, mock, callback], description: 产生收入的联盟 }
ecpm: { type: integer, format: int64, description: 'eCPMmock 取广告位 ecpmFloor' }
revenueAmount: { type: integer, format: int64, description: '本条广告收入单位BIGINT金额一律用分禁浮点' }
traceId: { type: string, description: '计费幂等键 / 对账 traceId' }
settleStatus: { type: integer, enum: [0, 1], description: '结算状态机0未结算 1已结算trade 分账成功后经 markSettled 回标 1' }
statDate: { type: string, format: date, description: '归集日yyyy-MM-dd按日归集供 trade 按日扫未结算)' }
createTime: { type: string, format: date-time }
# ===========================================================================
# ad↔trade seamPhase 0 锁死的跨模块 Feign 契约;两侧 agent 据此实现,不得各自发挥)
# 仿 telemetry.yaml x-mq-contracts 写法;跨模块只依赖 -api 走 Feign见 add-business-module 规范)
# ===========================================================================
x-feign-contracts:
description: >-
ad 模块通过 -api 包暴露给 trade 模块的 Feign 同步契约AdRevenueApi
用途trade 的 T+1 结算 job 据此拉取未结算广告收入做分账,并在入账成功后回标已结算。
跨模块只依赖 ad 的 -apicom.wanxiang.huijing.game.module.ad.api禁止 trade 依赖 ad 的 -server避免编译耦合。
全链路对账:广告计费产生的 game_ad_revenue.id + trace_id 透传到 trade 的 game_trade_income.source_ref + trace_id可端到端对账。
provider:
module: ad
apiInterface: 'com.wanxiang.huijing.game.module.ad.api.AdRevenueApi'
consumedBy: 'tradeT+1 结算 jobcom.wanxiang.huijing.game.module.trade.job.SettlementJob仅契约约定trade 侧实现)'
operations:
- name: getUnsettledRevenue
signature: 'List<AdRevenueRespDTO> getUnsettledRevenue(LocalDate statDate)'
semantics: >-
返回指定归集日 statDate 下 settle_status=0未结算的广告收入列表。
trade 据此按 creatorUserId 聚合后 ×creator_share 分账。
只读、幂等(重复调用返回当前仍未结算的集合);按 statDate 命中 game_ad_revenue.idx_settle_date 索引扫描,不裸 select*。
input:
statDate: 'LocalDate —— 归集日yyyy-MM-dd通常为 T+1 结算的 T 日'
output:
itemType: AdRevenueRespDTO
itemFields:
- 'id: Long —— 台账记录 IDtrade 侧作 source_ref 对账锚点uk(source, source_ref) 防重分账)'
- 'creatorUserId: Long —— 收入归因到的创作者用户 IDtrade 按此聚合)'
- 'revenueAmount: Long —— 本条广告收入(单位:分,原始 grosstrade ×share_rate 得 net'
- 'statDate: LocalDate —— 归集日透传trade 作 settle_date'
- 'traceId: String —— 全链路 traceId透传到 game_trade_income.trace_id 供对账)'
- name: markSettled
signature: 'boolean markSettled(List<Long> revenueIds)'
semantics: >-
trade 分账入账(写 game_trade_income + 累加 game_trade_account.balance成功后回调
把这些 game_ad_revenue 记录 settle_status 0→1已结算
幂等:已是 settle_status=1 的记录跳过(不报错、不重复处理),保证至少一次回调下不重复置位;
仅做状态位更新UPDATE ... WHERE id IN (...) AND settle_status=0不删数据、可追溯。
input:
revenueIds: 'List<Long> —— 已成功分账入账的 game_ad_revenue.id 列表(即 getUnsettledRevenue 返回项的 id'
output:
result: 'boolean —— 回标是否成功(部分已为 1 仍视为成功,幂等语义)'
settlementFlow:
description: >-
T+1 结算流trade 侧 SettlementJob 编排ad 仅提供数据与回标):
1) trade job 调 AdRevenueApi.getUnsettledRevenue(statDate=T 日) 拉未结算广告收入;
2) 按 creatorUserId 聚合 gross 总额;
3) ×creator_shareNacos 配置 trade.creator-shareMVP=0.80)算 net
4) 写 game_trade_incomeuk(source, source_ref) 防同一笔收入重复分账source=ad、source_ref=game_ad_revenue.id
+ 累加 game_trade_account.balance账户为流水物化汇总余额变更必有流水
5) 调 AdRevenueApi.markSettled(revenueIds) 把 ad 台账回标 settle_status=1。
全链路用 trace_idad.traceId → trade.trace_id+ source_refad.id → trade.source_ref可对账。
失败处理:步骤 4 成功但步骤 5 markSettled 失败 → 下轮 job 因 ad 仍为 settle_status=0 会重拉,但 trade 侧 uk(source, source_ref) 拦截重复入账(幂等),下轮重试 markSettled 即可补偿,不会重复发钱。