# 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) ```sql 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 日统计 ```sql 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` — 在线快照 ```sql 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` — 应用版本 ```sql 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 检查更新响应示例 ```json { "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 路由 ```yaml # 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 服务 ```yaml 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 | - | 批量事件处理耗时 |