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_time | ad_resource_id, type | tbname 是 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 内完成日到月、月到年的汇总。
图表示代码支持的数据路径,不代表所有作业当前都已配置调度或成功执行。本次没有启动作业,也没有查询业务记录验证汇总完整性。
维度、字段及子表
| 对象 | 普通字段 | TAG |
|---|---|---|
launcher_ad_play_detail | begin_time, data_key, mac, cpu, end_time, play_duration, create_time, log_info | package_name, version_code, ad_index_id, time_tag, ad_play_type, partition_index |
日/月/年 launcher_ad_play_device_count | record_time, data_key, mac, cpu, play_duration, play_count, create_time | package_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 业务记录分开理解。本次只核对其源码用途,未扫描缓存和导出文件。