跳到主要内容

Launcher 执行反馈与播放统计

拿到广告配置只说明设备获得了下发内容,并不等于完成下载、安装或真实播放。因此 Launcher 将“执行结果”和“播放效果”分开保存:执行记录回答素材或 APK 是否处理完成,播放统计回答某个包名、版本、广告位置在一定时间内播放了多少次、多久。

执行反馈:从素材回到推送任务​

设备上报执行完成事件,接口补充或选择时间后发 Kafka,LauncherExecRecordConsumer 将每条记录写到 tdLauncherExec。记录类型为 0 图片下载完成、1 视频下载完成、2 APP 安装完成、3 APP 已存在。ts 是执行时间,d_time 是调用接口时的设备时间,create_time 是服务接收保存时间;三者服务于不同的排查问题。

launcher_exec_record.ad_resource_id 指向 MySQL launcher_index_advert.id。需要按推送任务查看安装结果时,先从 launcher_advert_push_task.push_task_id 得到素材 ID,再查 TDengine 的 type=2 反馈;当前分页与计数 SQL 按 MAC 去重,不能把结果说明为 MAC+CPU 去重的设备数。底层时序记录仍保存 mac/cpu/data_key。

类型 2 的反馈还从素材配置补充包名、版本,发送 ApkPushCallbackEvent.TOPIC_FROM_LAUNCHER,交给跨服务安装回执链处理。这是消息关联,不是对 task/device 数据库的跨库外键或同一事务写入。

普通字段TAG特殊字段及子表规则
ts, data_key, mac, cpu, d_time, create_timead_resource_id, typetbname 是 TDengine 特殊表名字段;源码生成 ler_{adResourceId}_{type},不是按 MAC/CPU 建表

实测 data_key 标为 COMPOSITE KEY,与首列时间戳配合识别记录;Java getter 用 MAC 与 CPU 拼接形成值。STABLE_NAME="ler" 在该 DO 中实际用于子表前缀,实际超级表名称来自 @TableName("launcher_exec_record")。

证据:controller/app/ForeignLauncherController.java 的 addLauncherExecRecord;kafka/consumer/LauncherExecRecordConsumer.java:35;dal/mysql/execrecord/LauncherExecRecordMapper.java:21;XML execrecord/LauncherExecRecordMapper.xml:6。Mapper 虽放在 dal/mysql/,实际有 @DS(tdLauncherExec),不得因此归入 MySQL。

物理字典:执行记录。

播放数据:两种入口与三级粒度​

不是所有设备都上传每次播放明细。代码同时接收包含起止时间的明细上报和包含播放次数/时长的汇总上报,因此仅用明细行数不能解释所有日统计。

  • 明细入口按包名、版本、位置、日期聚合本批设备日数据;是否同时写单次明细受 launcher.save.ad.palay.log 控制,配置键保留历史拼写 palay。
  • Kafka 消费者对特定播放分类或配置的实时包名走明细保存,其余走设备日统计。实时分支会按北京与德国时差调整时间,并设置第三方播放分类 1;这条分支不应被概括成所有上报均按统一时区归日。
  • 作业可从明细重算设备日统计,再生成设备月、年统计。另一个作业将设备统计聚合为 MySQL 概览,并在 MySQL 内完成日到月、月到年的汇总。

Mermaid Diagram Code:

flowchart LR
  A[设备明细上报] --> B[TD 播放明细:可配置保存]
  A --> C[TD 设备日统计]
  D[设备汇总或 Kafka 上报] --> C
  D -->|实时分类分支| B
  B -->|重算作业| C
  C -->|日转月| E[TD 设备月统计]
  E -->|月转年| F[TD 设备年统计]
  C -->|统计作业| G[MySQL 播放概览]
  E -->|按类型选择统计源| G
  F -->|按类型选择统计源| G

图表示代码支持的数据路径,不代表所有作业当前都已配置调度或成功执行。本次没有启动作业,也没有查询业务记录验证汇总完整性。

维度、字段及子表​

对象普通字段TAG
launcher_ad_play_detailbegin_time, data_key, mac, cpu, end_time, play_duration, create_time, log_infopackage_name, version_code, ad_index_id, time_tag, ad_play_type, partition_index
日/月/年 launcher_ad_play_device_countrecord_time, data_key, mac, cpu, play_duration, play_count, create_timepackage_name, version_code, time_tag, ad_index_id, ad_play_type

ad_index_id 是设备上报的广告位置编号(实体注释为 0–13),不是 launcher_index.id;播放统计也没有素材 advert_id。所以统计天然按包名、版本、位置和分类汇总,不能直接宣称某个素材行的独立播放量。play_duration 单位为秒。日/月/年记录时间分别落在当天、月初、年初;作业设置的 time_tag 分别为 yyyyMMdd、yyyyMM、yyyy,不能只照搬 DO 中“均为 yyyyMMdd”的注释。

动态名称由 DO getter 生成:

  • 明细:{包名点号转下划线}_{versionCode}_{adIndexId}_{partitionIndex}[_{adPlayType}]_{timeTag}。分区为 abs((mac+cpu).hashCode() % 150)+1,范围 1–150。
  • 设备统计:launcher_ad_play_device_count_{包名点号转下划线}_{versionCode}_{adIndexId}[_{adPlayType}]_{timeTag},不含分区段。
  • 分类为空或 0 时省略分类段,保留旧子表命名兼容;非零分类才追加该段。不要批量补齐旧名称或把新旧名称形式自动判断为异常。

tbname 是 TDengine 伪列/路由字段,不是 DESCRIBE 缺失的业务列。三个统计库共享同一 DO;日库和月年库 TAG 顺序存在差别,完整字典各自保留实测顺序,不能从一个库复制顺序冒充全部一致。

物理字典:明细、日统计、月统计、年统计。

MySQL 概览的业务键​

launcher_ad_play_count 不存逐设备数据,而是按 type + package_name + ad_index_id + record_time + version_code + ad_play_type 汇总。type=0/1/2 区分日/月/年,实库 keys 唯一索引包含这六列,Mapper 通过 ON DUPLICATE KEY UPDATE 替换次数、时长并刷新时间。

无版本值在批量写入 XML 中用 -99999 表示,DO getter 又将这个值转回 null。这是已有兼容约定,不是应在整理文档时清理的“错误默认值”。分类列 ad_play_type 实库允许 NULL;唯一键包含可空列,不能把这个索引理解成任何输入条件下都绝对不会出现逻辑重复,具体历史数据本次未扫描。

证据:kafka/consumer/LauncherAdPlayConsumer.java:42,93;service/adplaydetail/LauncherAdPlayDetailServiceImpl.java:78;job/LauncherAdJob.java:52,108,172,235,271;dal/dataobject/adplaydetail/LauncherAdPlayDetailDO.java;dal/dataobject/adplaycount/LauncherAdPlayDeviceCountDO.java;XML adplaycount/LauncherAdPlayCountMapper.xml:11。

导出及临时存储​

异步配置/设备统计导出由 LauncherExportTaskEventListener 接收 task 的导出任务事件,交给 Launcher 业务服务执行,通过 task API 更新状态、infra 文件能力保存结果。任务生命周期归 task,文件元数据归 infra,不在 Launcher 另造一份任务表或文件表。

执行记录按任务导出还使用临时 Redis Set launcher:uniqueMac:{taskId}:{uuid} 做跨素材 MAC 去重,并生成临时 CSV/ZIP。此类临时对象与 MySQL 配置表、TDengine 业务记录分开理解。本次只核对其源码用途,未扫描缓存和导出文件。

用户文档
AI 助手
Agent 列表
请选择一个 Agent 开始对话
AI 问答