07 - 遥测 (Telemetry) 与移动端版本管理开发指南
参考项目: rwadurian/backend/services/presence-service + admin-service
目标: 为 Genex 券金融平台增加 用户遥测分析 和 移动端 OTA 版本管理 能力
一、功能概览
1.1 遥测系统 (Telemetry)
| 能力 |
说明 |
| 心跳检测 |
客户端定时上报心跳,服务端通过 Redis Sorted Set 实时计算在线用户数 |
| 事件采集 |
批量上报客户端事件(会话开始、页面浏览、操作行为等),写入 PostgreSQL |
| DAU 统计 |
基于 app_session_start 事件按 userId/installId 去重,支持省市维度 |
| 在线快照 |
每分钟记录在线用户数快照,支持 1m/5m/1h 区间聚合查询 |
| Prometheus |
暴露 /metrics 端点供 Grafana 抓取(心跳数、事件数、在线人数、DAU) |
1.2 版本管理 (App Version / OTA Update)
| 能力 |
说明 |
| 版本 CRUD |
管理员创建/编辑/删除/启禁用版本记录 |
| APK/IPA 上传 |
上传安装包,自动解析 versionCode/versionName/minSdkVersion |
| 强制更新 |
isForceUpdate 标志,客户端据此决定是否阻断使用 |
| 检查更新 API |
移动端调用,返回是否有更新、下载地址、SHA256 校验 |
| 断点续传下载 |
HTTP Range 206 支持,大文件友好 |
| 文件完整性 |
SHA256 哈希校验,防篡改 |
二、Genex 项目适配方案
2.1 架构决策
参考 rwadurian 项目的独立服务架构,Genex 同样采用独立微服务方案:
| 功能 |
归属服务 |
端口 |
理由 |
| 遥测 (Telemetry) |
telemetry-service |
:3011 |
独立微服务,专注心跳检测、事件采集、DAU 统计、Prometheus 指标 |
| 版本管理 (App Version) |
admin-service |
:3012 |
独立微服务,专注 APK/IPA 管理、OTA 更新、MinIO 文件存储 |
2.2 与参考项目的差异
| 维度 |
rwadurian |
Genex |
| ORM |
Prisma |
TypeORM (与现有一致) |
| 架构 |
独立服务 + CQRS |
独立服务 + DDD 四层架构 |
| 文件存储 |
本地 ./uploads |
MinIO (已有基础设施) |
| 事件总线 |
Kafka |
Kafka (KRaft 模式) |
| 缓存 |
Redis |
Redis (已有) |
| APK 解析 |
adbkit-apkreader |
同方案 |
| IPA 解析 |
unzipper + bplist-parser |
同方案 |
| Prometheus |
prom-client |
同方案 |
| 在线检测窗口 |
180s (3 min) |
180s |
| DAU 计算 |
每天凌晨 1:00 + 每小时滚动 |
同方案 |
| 下载端点 |
Express 流式 |
NestJS StreamableFile + Range |
2.3 数据库表 (新增 4 张)
telemetry_events — 事件日志 (append-only)
CREATE TABLE IF NOT EXISTS telemetry_events (
id BIGSERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id),
install_id VARCHAR(128) NOT NULL,
event_name VARCHAR(64) NOT NULL,
event_time TIMESTAMPTZ NOT NULL,
properties JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_telemetry_events_time ON telemetry_events(event_time);
CREATE INDEX idx_telemetry_events_name_time ON telemetry_events(event_name, event_time);
CREATE INDEX idx_telemetry_events_user ON telemetry_events(user_id);
daily_active_stats — DAU 日统计
CREATE TABLE IF NOT EXISTS daily_active_stats (
day DATE PRIMARY KEY,
dau_count INT NOT NULL DEFAULT 0,
dau_by_platform JSONB DEFAULT '{}',
dau_by_region JSONB DEFAULT '{}',
calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
version INT NOT NULL DEFAULT 1
);
online_snapshots — 在线快照
CREATE TABLE IF NOT EXISTS online_snapshots (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ NOT NULL UNIQUE,
online_count INT NOT NULL DEFAULT 0,
window_seconds INT NOT NULL DEFAULT 180
);
CREATE INDEX idx_online_snapshots_ts ON online_snapshots(ts DESC);
app_versions — 应用版本
CREATE TABLE IF NOT EXISTS app_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
platform VARCHAR(10) NOT NULL CHECK (platform IN ('ANDROID', 'IOS')),
version_code INT NOT NULL,
version_name VARCHAR(32) NOT NULL,
build_number VARCHAR(64) NOT NULL,
download_url TEXT NOT NULL,
file_size BIGINT NOT NULL DEFAULT 0,
file_sha256 VARCHAR(64) NOT NULL,
min_os_version VARCHAR(16),
changelog TEXT NOT NULL DEFAULT '',
is_force_update BOOLEAN NOT NULL DEFAULT FALSE,
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
release_date TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID,
updated_by UUID
);
CREATE INDEX idx_app_versions_platform ON app_versions(platform, is_enabled);
CREATE INDEX idx_app_versions_code ON app_versions(platform, version_code DESC);
2.4 Redis 数据结构
| Key |
类型 |
用途 |
genex:presence:online |
Sorted Set |
member=userId, score=heartbeat Unix时间戳 |
genex:dau:{YYYY-MM-DD} |
HyperLogLog |
实时近似 DAU(userId/installId) |
genex:dau:{YYYY-MM-DD}:ttl |
- |
自动过期 48h |
2.5 Kafka Topics (新增)
| Topic |
生产者 |
消费者 |
用途 |
telemetry.session.started |
user-service |
notification-service |
会话开始事件 |
telemetry.heartbeat |
user-service |
- |
心跳事件(可选) |
2.6 MinIO Bucket
| Bucket |
用途 |
app-releases |
存放 APK/IPA 安装包 |
三、API 设计
3.1 遥测 API
POST /api/v1/telemetry/events — 批量上报事件 (无需认证,支持匿名)
POST /api/v1/telemetry/heartbeat — 心跳上报 (需认证)
GET /api/v1/telemetry/online-count — 当前在线人数 (需认证)
GET /api/v1/telemetry/online-history — 在线历史趋势 (需认证)
# Admin (遥测分析 — 独立路径避免与 issuer 的 analytics 冲突)
GET /api/v1/admin/telemetry/dau — DAU 统计查询
GET /api/v1/admin/telemetry/events — 事件列表查询
GET /api/v1/admin/telemetry/realtime — 实时数据面板
3.2 版本管理 API
# 移动端 (无需/轻认证)
GET /api/v1/app/version/check — 检查更新
GET /api/v1/app/version/download/:id — 下载安装包 (支持断点续传)
# Admin
GET /api/v1/admin/versions — 版本列表
GET /api/v1/admin/versions/:id — 版本详情
POST /api/v1/admin/versions — 创建版本 (手动填写)
POST /api/v1/admin/versions/upload — 上传 APK/IPA 自动创建
POST /api/v1/admin/versions/parse — 解析安装包 (不保存)
PUT /api/v1/admin/versions/:id — 更新版本信息
PATCH /api/v1/admin/versions/:id/toggle — 启用/禁用
DELETE /api/v1/admin/versions/:id — 删除版本
3.3 检查更新响应示例
{
"code": 0,
"data": {
"needUpdate": true,
"forceUpdate": true,
"version": "2.1.0",
"versionCode": 210,
"downloadUrl": "https://minio.gogenex.com/app-releases/ANDROID-2.1.0.apk",
"fileSize": 52428800,
"fileSizeFriendly": "50.0 MB",
"sha256": "a1b2c3d4e5f6...",
"updateLog": "1. 新增券转让功能\n2. 修复钱包余额显示\n3. 性能优化",
"releaseDate": "2026-02-12T10:00:00Z"
}
}
3.4 强制更新决策流程
客户端 → GET /app/version/check?platform=android¤t_version_code=200
↓
服务端: 查找该平台最新 enabled 版本
↓
┌── 无更新版本 → { needUpdate: false }
│
└── 有更新版本 (latestCode > currentCode)
↓
isForceUpdate && isEnabled?
├── YES → { needUpdate: true, forceUpdate: true, ... }
└── NO → { needUpdate: true, forceUpdate: false, ... }
↓
客户端:
forceUpdate=true → 弹窗阻断,只能更新
forceUpdate=false → 提示可选更新
四、实现文件清单
4.1 遥测服务 (telemetry-service :3011)
services/telemetry-service/src/
├── domain/
│ ├── entities/
│ │ ├── telemetry-event.entity.ts # 事件日志实体
│ │ ├── online-snapshot.entity.ts # 在线快照实体
│ │ └── daily-active-stats.entity.ts # DAU 统计实体
│ ├── value-objects/
│ │ ├── event-name.vo.ts # 事件名值对象
│ │ ├── install-id.vo.ts # 安装ID值对象
│ │ └── time-window.vo.ts # 时间窗口值对象
│ └── events/
│ ├── session-started.event.ts # 会话开始领域事件
│ └── heartbeat-received.event.ts # 心跳接收领域事件
├── application/services/
│ ├── telemetry.service.ts # 事件采集 + 心跳 + DAU
│ └── telemetry-scheduler.service.ts # 定时任务 (快照/DAU/清理)
├── infrastructure/
│ ├── redis/
│ │ └── presence-redis.service.ts # Redis 在线检测操作
│ ├── kafka/
│ │ └── telemetry-producer.service.ts # Kafka 事件发布
│ └── metrics/
│ └── telemetry-metrics.service.ts # Prometheus 指标
└── interface/http/
├── controllers/
│ ├── telemetry.controller.ts # 遥测 API (心跳/事件/在线)
│ ├── admin-telemetry.controller.ts # Admin 遥测分析 API
│ ├── metrics.controller.ts # GET /metrics (Prometheus)
│ └── health.controller.ts # 健康检查
└── dto/
├── batch-events.dto.ts
├── heartbeat.dto.ts
└── query-dau.dto.ts
4.2 版本管理服务 (admin-service :3012)
services/admin-service/src/
├── domain/
│ ├── entities/
│ │ └── app-version.entity.ts # 版本实体 + Platform 枚举
│ └── value-objects/
│ ├── version-code.vo.ts # 版本号值对象
│ ├── version-name.vo.ts # 版本名值对象
│ ├── file-sha256.vo.ts # 文件哈希值对象
│ └── download-url.vo.ts # 下载链接值对象
├── application/services/
│ ├── app-version.service.ts # 版本 CRUD + 检查更新
│ └── file-storage.service.ts # MinIO 文件上传/下载
├── infrastructure/
│ └── parsers/
│ └── package-parser.service.ts # APK/IPA 解析
└── interface/http/
├── controllers/
│ ├── app-version.controller.ts # 移动端检查更新 + 下载 (无认证)
│ ├── admin-version.controller.ts # Admin 版本 CRUD (JWT+ADMIN)
│ └── health.controller.ts # 健康检查
└── dto/
├── check-update.dto.ts
├── create-version.dto.ts
└── upload-version.dto.ts
4.3 数据库迁移
migrations/
├── 032_create_telemetry_events.sql
├── 033_create_daily_active_stats.sql
├── 034_create_online_snapshots.sql
└── 035_create_app_versions.sql
4.4 Kong 路由
# telemetry-service (:3011)
- name: telemetry-service
url: http://telemetry-service:3011
routes:
- name: telemetry-routes
paths: [/api/v1/telemetry]
- name: admin-telemetry-routes
paths: [/api/v1/admin/telemetry]
# admin-service (:3012)
- name: admin-service
url: http://admin-service:3012
routes:
- name: app-version-routes
paths: [/api/v1/app/version]
- name: admin-version-routes
paths: [/api/v1/admin/versions]
4.5 Docker Compose 服务
telemetry-service:
build: ./services/telemetry-service
ports: ["3011:3011"]
depends_on: [postgres, redis, kafka]
admin-service:
build: ./services/admin-service
ports: ["3012:3012"]
depends_on: [postgres, minio]
五、关键实现要点
5.1 心跳在线检测
- 客户端每 60s 发送心跳
- 服务端用 Redis
ZADD genex:presence:online userId timestamp
- 在线判定窗口: 180s (3 分钟内有心跳即在线)
- 在线人数:
ZCOUNT genex:presence:online (now-180) +inf
- 每小时清理 24 小时前的过期数据:
ZREMRANGEBYSCORE
5.2 DAU 计算策略
- 实时近似: Redis HyperLogLog
PFADD genex:dau:2026-02-12 userId
- 精确计算: 每天凌晨 1:00 从
telemetry_events 查询 app_session_start
- 去重优先级: userId > installId
- 地理维度: 从 event properties 中提取 province/city
5.3 文件存储 (MinIO)
- Bucket:
app-releases
- 对象命名:
{platform}/{versionName}/{timestamp}-{random}.{ext}
- 预签名 URL: 下载时生成 24h 有效的签名链接
- SHA256: 上传时计算,存入
app_versions.file_sha256
5.4 APK/IPA 解析
- Android:
adbkit-apkreader 读取 AndroidManifest.xml
- 提取: packageName, versionCode, versionName, minSdkVersion
- iOS:
unzipper 解压 → bplist-parser 读取 Info.plist
- 提取: CFBundleIdentifier, CFBundleVersion, CFBundleShortVersionString, MinimumOSVersion
5.5 断点续传
- 响应头:
Accept-Ranges: bytes
- 请求头:
Range: bytes=1048576-
- 状态码: 206 Partial Content
- 流式读取:
fs.createReadStream(path, { start, end })
六、与现有系统集成
6.1 Admin-web 管理面板
admin-web 已有 Dashboard 页面,新增:
- 遥测面板: DAU 趋势图、在线用户数、事件分析
- 版本管理页: 版本列表、上传、强制更新开关
6.2 genex-mobile / miniapp
Flutter/Taro 客户端需要:
- 启动时发送
app_session_start 事件
- 定时心跳 (60s)
- 启动时调用检查更新 API
- 根据
forceUpdate 决定是否阻断
6.3 notification-service
消费 telemetry.session.started 事件:
- 新用户首次登录 → 发送欢迎通知
- 用户回归 (>7 天未登录) → 发送召回通知
七、Prometheus 指标
| 指标名 |
类型 |
标签 |
说明 |
genex_online_users |
Gauge |
- |
当前在线用户数 |
genex_dau |
Gauge |
date |
每日活跃用户数 |
genex_heartbeat_total |
Counter |
app_version |
心跳总数 |
genex_events_total |
Counter |
event_name |
事件总数 |
genex_event_batch_duration |
Histogram |
- |
批量事件处理耗时 |