contract-first 基座(B1 step①,编译验证待 mini-desktop,与 step② 合并后编译): - feed-api: FeedApi.upsertRank + FeedRankUpsertReqDTO(UpsertMode 分流) + ApiConstants - runtime-api: RuntimePackageApi.publish + ApiConstants - project-api: ProjectVersionApi.createForPackage + DTO + 错误码 PROJECT_PUBLISH_PACKAGE_NOT_READY - V10: game_runtime_package.package_json + game_telemetry_event.event_id/uk_event_id(替换 uk_dedup);仅 contracts 源 + yudao-server 单一执行副本(守门②) - 契约 yaml: events/telemetry 信封加 eventId + runtime /manifest 端点(原样 JSON 不包 CommonResult) - pom: project-server→feed/runtime-api、telemetry-server→feed-api(守门④无环已核) 主 agent 独立核验: V10 双副本一致 / uk_dedup 索引名 / 错误码无碰撞 / 守门①②④ / telemetry x-mq 幂等口径同步改 eventId 避免契约自相矛盾 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
224 lines
13 KiB
YAML
224 lines
13 KiB
YAML
openapi: 3.0.3
|
||
# 契约 #1 API | 模块:telemetry(照抄黄金模板 project 风格)| owner:WS5 主笔,全员 review
|
||
# 端:/app-api(产品端 game-studio,用户/匿名 Token 上报) /admin-api(管理端 game-admin,RBAC 查看板数据源);前缀由 yudao 框架按 controller.app/admin 包名自动添加
|
||
# 错误码段:telemetry = 1-104-***-***
|
||
# 响应统一 Yudao CommonResult 信封:{ code, data, msg };code=0 成功
|
||
# 脊柱:批量事件(#5 envelope) → /app-api/telemetry/events/batch → MQ 异步消费(幂等) → 原始事件表 game_telemetry_event + 聚合 game_telemetry_game_stat(play_count/like_count/quality_score) → 回灌 feed 与 project
|
||
# 量纲约定:telemetry 侧 quality_score/qualityScore = 运营质量分(0-100),≠ aigc 生成质量分(0-1),二者量纲不同、不可互相回灌
|
||
# play_count 权威源:= 本模块 game_play_start 事件聚合(runtime session 仅观测、不计数);二者勿混为计数源
|
||
# ad/income 范围:ad_impression/ad_reward/income_settled 事件原始表照常入库,但聚合 = Wave2 变现范围外,later 补列(非遗漏)
|
||
# 外部依赖:MQ(RocketMQ) 只定义"对接点契约"(见 components.x-mq-contracts),本契约不实现、不 mock
|
||
info:
|
||
title: 绘境AI telemetry 模块 API
|
||
version: 1.0.0
|
||
description: >-
|
||
遥测/事件批量入库 → MQ 异步 → 增量聚合 → quality_score → 回灌 feed/project 的数据回路。
|
||
上报通道接收 #5 events.schema.json 的事件信封(envelope);不渲染看板/建议/导出 UI(供数据给产品域)。
|
||
前端据此 vite-plugin-mock 自动生成 mock。
|
||
|
||
servers:
|
||
- url: http://localhost:48080
|
||
description: 本地(Swagger/Knife4j http://localhost:48080/doc.html)
|
||
|
||
paths:
|
||
# ===========================================================================
|
||
# 上报通道(产品端,用户 Token 或匿名 anon Token;高频写入)
|
||
# ===========================================================================
|
||
/app-api/telemetry/events/batch:
|
||
post:
|
||
tags: [app-telemetry]
|
||
summary: 事件批量摄取(T-TEL-01;快速 ACK 后投递 MQ 异步入库,不同步聚合)
|
||
description: >-
|
||
接收 #5 envelope 批量事件 { batch: Envelope[] }。服务端只做轻量校验(事件名在注册表、信封必填字段、批量上限)后
|
||
投递 RocketMQ(topic=telemetry-event,见 x-mq-contracts),立即返回受理结果;落原始表与聚合在消费侧异步完成(幂等)。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/EventBatchReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 受理结果(accepted/rejected 计数;非整批失败语义)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultEventBatchResult' }
|
||
|
||
/app-api/telemetry/perf/beacon:
|
||
post:
|
||
tags: [app-telemetry]
|
||
summary: 性能埋点兜底通道(T-TEL-03 sendBeacon / T-TEL-11 加载耗时;页面卸载场景)
|
||
description: >-
|
||
sendBeacon 在页面卸载/跳转时上报首屏与加载耗时(perf_first_screen / game_load_failed 等)。
|
||
与 /events/batch 同样投递 MQ;单条信封,body 体积小,永远返回受理成功(不阻塞前端卸载)。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/EnvelopeReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 受理成功(恒为 true)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
# ===========================================================================
|
||
# 看板数据源(管理端 RBAC;只读聚合,供产品域 UI 取数)
|
||
# ===========================================================================
|
||
/admin-api/telemetry/game-stat/page:
|
||
get:
|
||
tags: [admin-telemetry]
|
||
summary: 游戏维度聚合分页(T-TEL-05;创作者/运营看板数据源,本模块只供数据不渲染 UI)
|
||
parameters:
|
||
- { name: gameId, in: query, required: false, schema: { type: integer, format: int64 }, description: 指定游戏;空则全量分页 }
|
||
- { 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/CommonResultGameStatPage' }
|
||
|
||
/admin-api/telemetry/game-stat/{gameId}:
|
||
get:
|
||
tags: [admin-telemetry]
|
||
summary: 单游戏最新聚合快照(含 quality_score=运营质量分(0-100),≠ aigc 生成质量分(0-1);feed 排序信号与看板共用读口)
|
||
parameters:
|
||
- { name: gameId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultGameStat' }
|
||
|
||
components:
|
||
schemas:
|
||
# ---- Yudao CommonResult 信封 ----
|
||
CommonResultBoolean:
|
||
type: object
|
||
properties:
|
||
code: { type: integer, description: '0=成功,非0=错误码 1-104-***-***' }
|
||
data: { type: boolean }
|
||
msg: { type: string }
|
||
CommonResultEventBatchResult:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/EventBatchResultVO' }
|
||
msg: { type: string }
|
||
CommonResultGameStat:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/GameStatRespVO' }
|
||
msg: { type: string }
|
||
CommonResultGameStatPage:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data:
|
||
type: object
|
||
properties:
|
||
list: { type: array, items: { $ref: '#/components/schemas/GameStatRespVO' } }
|
||
total: { type: integer, format: int64 }
|
||
msg: { type: string }
|
||
|
||
# ---- 事件信封(对齐契约 #5 events.schema.json envelope;此处为上报入参镜像)----
|
||
EnvelopeReqVO:
|
||
type: object
|
||
description: 单条事件信封;字段语义与约束以 #5 events.schema.json 为单一事实源,此处仅做 OpenAPI 入参映射。
|
||
required: [event, schemaVersion, ts, traceId, eventId]
|
||
properties:
|
||
event: { type: string, description: '事件名(须在 #5 eventRegistry 中登记,否则消费侧丢弃并计 rejected)' }
|
||
schemaVersion: { type: string, pattern: '^v[0-9]+$', description: '事件 Schema 版本,如 v1/v2(对应 #5 schema_version)' }
|
||
ts: { type: integer, format: int64, description: '事件发生时间戳(毫秒)' }
|
||
traceId: { type: string, description: '全链路追踪 ID,贯穿生成→编译→加载→运行→上报(对应 #5 trace_id)' }
|
||
eventId: { type: string, description: '事件实例 UUID(必填,每事件实例唯一,幂等真身,对应 #5 eventId / game_telemetry_event.event_id 的 uk_event_id 唯一键)。前端 crypto.randomUUID() 生成;重放同 eventId 不重复计数' }
|
||
sessionId: { type: string, description: '会话 ID(一次游戏流消费/一次创作会话,对应 #5 session_id);来源=runtime sessionId(int64) 转 string,作 session 维度对账键' }
|
||
user:
|
||
type: object
|
||
description: 用户标识;匿名玩家用 anonId,登录后带 userId(对应 #5 user)
|
||
properties:
|
||
userId: { type: string }
|
||
anonId: { type: string, description: 匿名 token 派生的稳定匿名 ID }
|
||
context:
|
||
type: object
|
||
description: 上下文(对应 #5 context;game_id/version_id 是聚合落点关键)
|
||
properties:
|
||
gameId: { type: string }
|
||
versionId: { type: string }
|
||
channel: { type: string, description: 渠道归因(utm/channel 解析) }
|
||
deviceType: { type: string, enum: [mobile, desktop, miniapp] }
|
||
props:
|
||
type: object
|
||
description: 事件私有字段(结构见 #5 对应 eventRegistry 项,如 duration_ms/completed/slot_id/reason)
|
||
additionalProperties: true
|
||
EventBatchReqVO:
|
||
type: object
|
||
required: [batch]
|
||
properties:
|
||
batch:
|
||
type: array
|
||
description: 批量事件信封;单次上限 200 条(超限整体 rejected,前端分批重传)
|
||
minItems: 1
|
||
maxItems: 200
|
||
items: { $ref: '#/components/schemas/EnvelopeReqVO' }
|
||
|
||
# ---- 响应 VO ----
|
||
EventBatchResultVO:
|
||
type: object
|
||
description: 批量受理结果(部分成功语义:已受理投递 MQ 的条数与被拒条数)
|
||
properties:
|
||
accepted: { type: integer, description: 通过轻量校验并投递 MQ 的事件数 }
|
||
rejected: { type: integer, description: 校验失败被拒的事件数(事件名未登记/信封字段缺失等) }
|
||
traceId: { type: string, description: 本次批量受理回执 traceId(便于排障) }
|
||
GameStatRespVO:
|
||
type: object
|
||
description: >-
|
||
游戏维度聚合快照(消费侧增量聚合产出;play_count/like_count 回灌 project,quality_score 回灌 feed 排序)。
|
||
ad/income 范围说明:ad_impression/ad_reward/income_settled 事件原始表(game_telemetry_event)照常入库,
|
||
但聚合 = Wave2 变现范围外,later 补列(本 VO 暂不含广告/收益聚合字段,非遗漏)。
|
||
properties:
|
||
gameId: { type: integer, format: int64 }
|
||
statDate: { type: string, format: date, description: 统计日(yyyy-MM-dd;按日聚合粒度) }
|
||
playCount: { type: integer, format: int64, description: '当日累计试玩数;权威源=本模块 game_play_start 事件聚合(runtime session 仅观测、不计数)' }
|
||
playEndCount: { type: integer, format: int64, description: 当日试玩结束数(game_play_end) }
|
||
completedCount: { type: integer, format: int64, description: '当日完玩数(props.completed=true)' }
|
||
avgDurationMs: { type: integer, format: int64, description: 平均试玩时长(毫秒;quality_score 输入) }
|
||
likeCount: { type: integer, format: int64, description: 当日点赞数 }
|
||
favoriteCount: { type: integer, format: int64, description: 当日收藏数 }
|
||
shareCount: { type: integer, format: int64, description: 当日分享数 }
|
||
reportCount: { type: integer, format: int64, description: 当日举报数(举报阈值降权信号) }
|
||
loadFailCount: { type: integer, format: int64, description: 当日加载失败数(降权信号) }
|
||
qualityScore: { type: number, format: float, description: '运营质量分(0-100)(T-TEL-04;回灌 feed 排序);≠ aigc 生成质量分(0-1),量纲不同、不可互相回灌' }
|
||
updateTime: { type: string, format: date-time }
|
||
|
||
# ===========================================================================
|
||
# 外部依赖对接点契约(MQ 异步消费=脊柱对接点;只定义契约,不实现、不 mock)
|
||
# ===========================================================================
|
||
x-mq-contracts:
|
||
description: >-
|
||
telemetry 摄取与消费之间的 RocketMQ 对接点契约。生产者=上报通道(/events/batch、/perf/beacon),
|
||
消费者=本模块异步消费组(落 game_telemetry_event + 增量聚合 game_telemetry_game_stat + 回灌 feed/project)。
|
||
幂等键=信封 eventId(事件实例 UUID),对应原始表 uk_event_id 唯一约束,重复消息消费侧落库幂等(INSERT IGNORE 语义)。
|
||
(历史口径 (traceId,event,ts)/uk_dedup 已于 V10 被 uk_event_id 取代——同毫秒同名会误吞、重放会重计数,见 V10 迁移与 HJ-EXEC-006 §3.5 C5。)
|
||
producer:
|
||
topic: telemetry-event
|
||
tag: 'event | perf'
|
||
keys: traceId
|
||
messageBody: '{ envelope: EnvelopeReqVO } # 与上报入参同构,逐条投递'
|
||
orderly: false
|
||
note: 上报通道快速 ACK 后异步投递;投递失败本地落兜底表后定时补投(不丢事件)。
|
||
consumer:
|
||
group: telemetry-event-consumer
|
||
concurrency: 建议并发消费(无序);聚合写入按 gameId 行级原子累加避免覆盖
|
||
idempotency: 以 eventId(事件实例 UUID)为幂等键,命中 game_telemetry_event.uk_event_id 即跳过,保证至少一次投递下不重复聚合(V10 起;原 uk_dedup 已下线)
|
||
retry: 默认重试 ≤16 次仍失败转 DLQ(telemetry-event-dlq),由 T-TEL-19 数据质量监控告警
|
||
sideEffects:
|
||
- '落 game_telemetry_event(原始事件,幂等)'
|
||
- '增量聚合 game_telemetry_game_stat(play/like/quality 等按 gameId+statDate 累加)'
|
||
- '回灌:play_count/like_count → project(game_project.play_count/like_count);quality_score → feed 排序信号'
|