跳到主要内容

APK 推送历史与检索索引

apk_push_history 在 MySQL 保存独立于任务的设备应用历史;同名 ES 索引用于列表、分页、组合条件和聚合检索。任务不存在不等于历史应删除,MySQL 与 ES 也不是同一个物理对象。

源码实际支持同设备同应用的多条历史,以版本号和推送时间区分;实体头部“只保留最新版本”的旧注释已与当前实现不一致。本页采用 Service 与 Mapper 的实际判断规则。

业务流程与关系​

Mermaid Diagram Code:

flowchart LR
    P[APK 推送回调] --> S[历史服务]
    U[卸载更新事件] --> S
    S --> M[MySQL apk_push_history]
    S --> E[ES apk_push_history]
    M --> J[按 ID 区间同步任务]
    J --> E
    Q[列表与分页查询] --> E
    E -.异常降级.-> M
  • 推送消息先按 (mac,cpuid,packageName) 找占位或完全匹配记录。占位记录的 version_code、push_task_time 均为 NULL,随后补齐;相同版本和时间的完全匹配记录跳过,否则插入新历史。
  • 卸载消息没有卸载时间时跳过;有历史时选 push_task_time DESC, id DESC 最新一条更新 uninstall_time;无历史时先插入占位,允许卸载消息先到。当前 blacklist 链路将消费消息时的服务端时间放入事件,因此这里保存的不是设备反馈 device_time。证据:blacklist 的 BlackCallbackConsumer.java:57–64 使用 LocalDateTime.now() 构造事件,report 的 ApkUninstallUpdateConsumer.java:78 使用事件时间。
  • cpuid=null 在服务写入前归一化为空字符串。当前匹配 Mapper 又将 blank 值按空串查询,不把老注释中的“允许 NULL”当作当前物理约束。
  • 内部匹配优先 ES,ES 未找到或异常时再向 MySQL 确认;列表 ES 计数超过 10000 时使用 MySQL,分页异常时降级 MySQL。
  • MySQL 插入/更新后尝试同步 ES,ES 失败被捕获,不能宣称两个存储在一个原子事务内提交。删除服务当前只调用 MySQL Mapper,没有同路径 ES 删除,因此不能把查询索引当作绝对同步的事实源。
  • Kafka 消费者和按 ID 区间的 apkPushHistoryMysqlToEs 任务是数据流入口/补同步入口,本轮只读分析未执行消费或同步。

证据:ik_project/yudao-cloud/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/service/apkpushhistory/ApkPushHistoryServiceImpl.java:40、:74、:110、:125、:158、:179、:188;同模块 dal/mysql/apkpushhistory/ApkPushHistoryMapper.java:101、:129,job/ApkPushHistoryEsJob.java:53,kafka/consumer/ApkPushHistoryConsumer.java:38、kafka/consumer/ApkUninstallUpdateConsumer.java:46。

MySQL 完整字段与物理约束​

来源:ik_project/yudao-cloud/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/dal/dataobject/apkpushhistory/ApkPushHistoryDO.java:26。本 DO 只实现 Serializable,没有租户、逻辑删除、审计继承字段。

实测:2026-09-18 20:51,MySQL 8.0.35,ik_yudao_report,InnoDB,utf8mb4_general_ci。

列Java 类型 / 声明行实际 SQL 类型可空 / 默认业务意义
idLong / 43bigint否 / 无默认,auto_incrementORM 标识,也用于 ES 文档 ID
macString / 48varchar(20)否 / 无默认设备 MAC
cpuidString / 58varchar(32)否 / 空字符串设备 CPU 序列,空串表示未提供
package_nameString / 63varchar(255)否 / 无默认应用包名
version_codeLong / 70bigint是 / NULL版本号;占位时为空
push_task_timeLocalDateTime / 77datetime是 / NULL推送事实时间;参与幂等识别
uninstall_timeLocalDateTime / 86datetime是 / NULL卸载事件时间;当前 blacklist 链路为服务端消费时间
version_code_key无实体字段bigint是 / 虚拟生成IFNULL(version_code,0),空版本参与唯一约束
push_task_time_key无实体字段datetime是 / 虚拟生成IFNULL(push_task_time,'1970-01-01 00:00:01'),空时间参与唯一约束

实测只有两个唯一索引:

索引名有序字段解释
idid,mac,cpuidUNIQUE;不是 PRIMARY,也不是 id 单列唯一约束
uk_mac_cpuid_package_versionmac,cpuid,package_name,version_code_key,push_task_time_keyUNIQUE;名称未写出时间,但实际包含生成时间列

元数据未返回 PRIMARY 或外键。@TableId(type=AUTO) 是 ORM 声明,不能覆盖上述数据库事实。唯一键以 0 和特定时间替代 NULL,因此真实值恰好等于这两个占位值时会共用唯一键值;本轮未读取业务数据评估是否发生碰撞。补采确认该表为 KEY(mac,cpuid)、16 个分区。设备维度追查应同时保留 MAC 与 CPUID,组合唯一索引还涉及包名和生成列;是否裁剪分区及选用索引需要执行计划,本轮未执行。

mac、cpuid 是跨业务标识,package_name 是应用标识;本表没有 task_id 或实体声明的外键。数据库中额外 apk_push_history11111、apk_push_history_new 也已纳入 完整物理字典,不能按名字认定为正在使用的历史源。

ES 完整映射​

实体:ik_project/yudao-cloud/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/dal/es/apkpushhistory/ApkPushHistoryEsEntity.java:31。实测 ES 7.17.6 的 7 个字段完整匹配实体;列名为下划线形式。全局命名配置证据:doc/数据库设计管理/nacos/application-common.yaml:337。

ES 字段Java 类型 / 声明行实测 mapping说明
idLong / 42long自定义文档 ID 来源为 MySQL id;字段本身也存 long
macString / 48text,analyzer=mac_analyzer子字段 keyword 为 keyword,ignore_above=255
cpuidString / 54keywordCPU 序列精确值
package_nameString / 62text,analyzer=standard子字段 keyword 为 keyword,ignore_above=255
version_codeLong / 68long版本号
push_task_timeLocalDateTime / 74date,format=yyyy-MM-dd HH:mm:ss推送时间
uninstall_timeLocalDateTime / 80date,format=yyyy-MM-dd HH:mm:ss同 MySQL 卸载事件时间口径

ES 不含 MySQL 的两个虚拟生成列。采集 mapping 没有显式 search_analyzer;源码声明查询分析器与索引分析器相同,应区分“注解意图”和“mapping 显式返回项”。ES mapping 不表达 MySQL 的 UNIQUE/NOT NULL,不能把关系库约束照搬到索引。

更多映射见 ES apk_push_history 字典。

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