gcx/docs/guides/07-遥测与版本管理开发指南.md

419 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | 实时近似 DAUuserId/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&current_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 | - | 批量事件处理耗时 |