rwadurian/frontend/mobile-app/docs/backend_api_guide.md

18 KiB
Raw Blame History

后端 API 开发指南

本文档为 Flutter 前端已实现功能提供后端 API 接口规范,供后端开发参考。


目录

  1. APK 在线升级 API
  2. 遥测系统 API
  3. 数据库设计
  4. Redis 数据结构
  5. 定时任务

1. APK 在线升级 API

1.1 版本检测接口

请求

GET /api/app/version/check

Query 参数

参数 类型 必填 说明
platform string 平台类型: android / ios
currentVersion string 当前版本号,如 1.0.0
currentVersionCode int 当前版本代码,如 1

响应示例 - 有新版本

{
  "code": 0,
  "message": "success",
  "data": {
    "hasUpdate": true,
    "version": "1.1.0",
    "versionCode": 2,
    "downloadUrl": "https://cdn.rwadurian.com/releases/app-v1.1.0.apk",
    "fileSize": 52428800,
    "fileSizeFriendly": "50.0 MB",
    "sha256": "a1b2c3d4e5f6...完整64字符哈希",
    "forceUpdate": false,
    "updateLog": "1. 新增挖矿动画效果\n2. 修复已知BUG\n3. 性能优化",
    "releaseDate": "2024-01-15T10:00:00Z"
  }
}

响应示例 - 已是最新版本

{
  "code": 0,
  "message": "success",
  "data": {
    "hasUpdate": false
  }
}

前端对应代码

// lib/core/updater/version_checker.dart
Future<VersionInfo?> checkForUpdate() async {
  final response = await _dio.get(
    '/api/app/version/check',
    queryParameters: {
      'platform': 'android',
      'currentVersion': packageInfo.version,
      'currentVersionCode': int.parse(packageInfo.buildNumber),
    },
  );
  // ...
}

1.2 APK 下载

要求

  • 下载链接必须使用 HTTPS
  • 支持断点续传 (Range 请求头)
  • 提供正确的 Content-Length 响应头
  • 提供 SHA-256 校验值供前端验证文件完整性

前端下载流程

1. 请求版本检测接口获取下载信息
2. 使用 Dio 下载 APK 到应用私有目录
3. 计算下载文件 SHA-256 与服务器返回值比对
4. 校验通过后调用系统安装器安装

1.3 版本管理后台 (建议)

建议实现后台管理界面支持:

  • 上传新版本 APK
  • 自动计算文件大小和 SHA-256
  • 设置强制更新标志
  • 填写更新日志
  • 查看各版本下载统计

2. 遥测系统 API

2.1 事件上报接口

请求

POST /api/v1/analytics/events

请求头

Content-Type: application/json
Authorization: Bearer <token>  // 可选,用户已登录时携带

请求体

{
  "events": [
    {
      "eventId": "550e8400-e29b-41d4-a716-446655440000",
      "name": "page_view",
      "type": "page_view",
      "level": "info",
      "installId": "device-unique-install-id",
      "deviceContextId": "ctx-abc123",
      "sessionId": "session-xyz789",
      "userId": "user-123",
      "timestamp": "2024-01-15T10:30:00.000Z",
      "properties": {
        "page": "/ranking",
        "referrer": "/splash"
      }
    },
    {
      "eventId": "550e8400-e29b-41d4-a716-446655440001",
      "name": "button_click",
      "type": "user_action",
      "level": "info",
      "installId": "device-unique-install-id",
      "deviceContextId": "ctx-abc123",
      "sessionId": "session-xyz789",
      "userId": "user-123",
      "timestamp": "2024-01-15T10:30:05.000Z",
      "properties": {
        "button": "start_mining",
        "page": "/mining"
      }
    }
  ]
}

事件类型枚举 (EventType)

说明
page_view 页面浏览
user_action 用户操作
api_call API 调用
performance 性能指标
error 错误
crash 崩溃
session 会话事件
presence 在线状态

事件级别枚举 (EventLevel)

说明
debug 调试
info 信息
warning 警告
error 错误

响应

{
  "code": 0,
  "message": "success"
}

前端对应代码

// lib/core/telemetry/uploader/telemetry_uploader.dart
Future<bool> uploadBatch({int batchSize = 20}) async {
  final events = storage.dequeueEvents(batchSize);
  final response = await _dio.post(
    '/api/v1/analytics/events',
    data: {
      'events': events.map((e) => e.toJson()).toList(),
    },
  );
  // ...
}

2.2 心跳接口 (在线状态)

请求

POST /api/v1/presence/heartbeat

请求头

Content-Type: application/json
Authorization: Bearer <token>  // 可选

请求体

{
  "installId": "device-unique-install-id",
  "sessionId": "session-xyz789",
  "userId": "user-123",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

响应

{
  "code": 0,
  "message": "success"
}

前端行为

  • 应用在前台时,每 60 秒 发送一次心跳
  • 应用进入后台时暂停心跳
  • 应用恢复前台时立即发送心跳并恢复定时器

前端对应代码

// lib/core/telemetry/presence/heartbeat_service.dart
void _sendHeartbeat() {
  _dio.post(
    '/api/v1/presence/heartbeat',
    data: {
      'installId': _installId,
      'sessionId': _sessionId,
      'userId': _userId,
      'timestamp': DateTime.now().toUtc().toIso8601String(),
    },
  );
}

2.3 遥测配置接口

请求

GET /api/telemetry/config

响应

{
  "code": 0,
  "message": "success",
  "data": {
    "enabled": true,
    "samplingRate": 1.0,
    "enabledEventTypes": ["page_view", "user_action", "error", "crash", "session", "presence"],
    "maxQueueSize": 1000,
    "uploadBatchSize": 20,
    "uploadIntervalSeconds": 30,
    "heartbeatIntervalSeconds": 60,
    "sessionTimeoutMinutes": 30
  }
}

字段说明

字段 类型 说明
enabled bool 全局开关false 时前端停止所有遥测
samplingRate double 采样率 0.0~1.0,用于控制上报比例
enabledEventTypes string[] 启用的事件类型列表
maxQueueSize int 本地队列最大容量
uploadBatchSize int 每批上传事件数量
uploadIntervalSeconds int 上传间隔秒数
heartbeatIntervalSeconds int 心跳间隔秒数
sessionTimeoutMinutes int 会话超时分钟数

前端对应代码

// lib/core/telemetry/models/telemetry_config.dart
class TelemetryConfig {
  final bool enabled;
  final double samplingRate;
  final List<String> enabledEventTypes;
  final int maxQueueSize;
  final int uploadBatchSize;
  final int uploadIntervalSeconds;
  final int heartbeatIntervalSeconds;
  final int sessionTimeoutMinutes;
}

3. 数据库设计

3.1 应用版本表 (app_versions)

CREATE TABLE app_versions (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  platform ENUM('android', 'ios') NOT NULL,
  version VARCHAR(20) NOT NULL COMMENT '版本号 如 1.0.0',
  version_code INT NOT NULL COMMENT '版本代码 如 1',
  download_url VARCHAR(500) NOT NULL COMMENT 'APK/IPA 下载地址',
  file_size BIGINT NOT NULL COMMENT '文件大小(字节)',
  sha256 CHAR(64) NOT NULL COMMENT 'SHA-256 哈希值',
  force_update TINYINT(1) DEFAULT 0 COMMENT '是否强制更新',
  update_log TEXT COMMENT '更新日志',
  release_date DATETIME NOT NULL COMMENT '发布时间',
  is_active TINYINT(1) DEFAULT 1 COMMENT '是否激活',
  download_count INT DEFAULT 0 COMMENT '下载次数',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  UNIQUE KEY uk_platform_version (platform, version_code),
  INDEX idx_platform_active (platform, is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用版本管理';

3.2 遥测事件表 (analytics_events)

CREATE TABLE analytics_events (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  event_id VARCHAR(36) NOT NULL COMMENT '事件唯一ID (UUID)',
  name VARCHAR(100) NOT NULL COMMENT '事件名称',
  type ENUM('page_view', 'user_action', 'api_call', 'performance', 'error', 'crash', 'session', 'presence') NOT NULL,
  level ENUM('debug', 'info', 'warning', 'error') DEFAULT 'info',
  install_id VARCHAR(100) NOT NULL COMMENT '设备安装ID',
  device_context_id VARCHAR(100) COMMENT '设备上下文ID',
  session_id VARCHAR(100) COMMENT '会话ID',
  user_id VARCHAR(100) COMMENT '用户ID',
  properties JSON COMMENT '事件属性',
  event_time DATETIME(3) NOT NULL COMMENT '事件发生时间',
  received_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '服务器接收时间',

  UNIQUE KEY uk_event_id (event_id),
  INDEX idx_install_id (install_id),
  INDEX idx_user_id (user_id),
  INDEX idx_session_id (session_id),
  INDEX idx_type_time (type, event_time),
  INDEX idx_event_time (event_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='遥测事件记录'
PARTITION BY RANGE (TO_DAYS(event_time)) (
  PARTITION p_default VALUES LESS THAN MAXVALUE
);

建议按天分区,便于数据清理和查询优化

3.3 设备上下文表 (device_contexts)

CREATE TABLE device_contexts (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  context_id VARCHAR(100) NOT NULL COMMENT '上下文ID',
  install_id VARCHAR(100) NOT NULL COMMENT '设备安装ID',
  platform VARCHAR(20) NOT NULL COMMENT '平台 android/ios',
  brand VARCHAR(50) COMMENT '品牌',
  model VARCHAR(100) COMMENT '型号',
  manufacturer VARCHAR(100) COMMENT '制造商',
  is_physical_device TINYINT(1) COMMENT '是否真机',
  os_version VARCHAR(50) COMMENT '系统版本',
  sdk_int INT COMMENT 'SDK版本号',
  android_id VARCHAR(100) COMMENT 'Android ID',
  screen_width INT COMMENT '屏幕宽度',
  screen_height INT COMMENT '屏幕高度',
  screen_density DECIMAL(4,2) COMMENT '屏幕密度',
  app_name VARCHAR(100) COMMENT '应用名称',
  package_name VARCHAR(200) COMMENT '包名',
  app_version VARCHAR(20) COMMENT '应用版本',
  build_number VARCHAR(20) COMMENT '构建号',
  build_mode VARCHAR(20) COMMENT '构建模式 debug/release',
  locale VARCHAR(20) COMMENT '语言区域',
  timezone VARCHAR(50) COMMENT '时区',
  network_type VARCHAR(20) COMMENT '网络类型',
  is_dark_mode TINYINT(1) COMMENT '是否深色模式',
  collected_at DATETIME(3) NOT NULL COMMENT '采集时间',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

  UNIQUE KEY uk_context_id (context_id),
  INDEX idx_install_id (install_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备上下文信息';

3.4 日活统计表 (analytics_daily_active)

CREATE TABLE analytics_daily_active (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  stat_date DATE NOT NULL COMMENT '统计日期',
  total_dau INT DEFAULT 0 COMMENT '总DAU',
  new_users INT DEFAULT 0 COMMENT '新用户数',
  returning_users INT DEFAULT 0 COMMENT '回访用户数',
  total_sessions INT DEFAULT 0 COMMENT '总会话数',
  avg_session_duration INT DEFAULT 0 COMMENT '平均会话时长(秒)',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  UNIQUE KEY uk_stat_date (stat_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='日活统计汇总';

3.5 在线快照表 (analytics_online_snapshots)

CREATE TABLE analytics_online_snapshots (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  snapshot_time DATETIME NOT NULL COMMENT '快照时间',
  online_count INT NOT NULL COMMENT '在线人数',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_snapshot_time (snapshot_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='在线人数快照';

4. Redis 数据结构

4.1 在线用户集合 (实时在线统计)

Key: presence:online_users Type: Sorted Set (ZSET) Score: 最后心跳时间戳 (Unix timestamp) Member: installIduserId (取决于是否登录)

# 用户发送心跳时
ZADD presence:online_users <timestamp> <installId>

# 获取在线人数 (2分钟内有心跳的用户)
ZCOUNT presence:online_users <now - 120> <now>

# 清理过期用户 (可选,定时任务执行)
ZREMRANGEBYSCORE presence:online_users -inf <now - 120>

在线判定规则

用户最后心跳时间在 2 分钟 内视为在线(心跳间隔 60 秒,允许 1 次丢失)

4.2 日活去重集合

Key: analytics:dau:<date> (如 analytics:dau:2024-01-15) Type: Set Member: installId TTL: 48 小时

# 记录日活
SADD analytics:dau:2024-01-15 <installId>
EXPIRE analytics:dau:2024-01-15 172800

# 获取当日DAU
SCARD analytics:dau:2024-01-15

4.3 会话缓存

Key: session:<sessionId> Type: Hash TTL: 30 分钟 (随活动更新)

HSET session:xyz789
  installId "device-xxx"
  userId "user-123"
  startTime "2024-01-15T10:00:00Z"
  lastActiveTime "2024-01-15T10:30:00Z"

EXPIRE session:xyz789 1800

5. 定时任务

5.1 DAU 统计任务

执行频率: 每天凌晨 00:05

# 伪代码
def calculate_daily_dau():
    yesterday = date.today() - timedelta(days=1)
    date_str = yesterday.strftime('%Y-%m-%d')

    # 从 Redis 获取昨日 DAU
    dau_key = f'analytics:dau:{date_str}'
    total_dau = redis.scard(dau_key)

    # 或从数据库统计 (session_start 事件去重)
    total_dau = db.query("""
        SELECT COUNT(DISTINCT install_id)
        FROM analytics_events
        WHERE type = 'session'
          AND name = 'session_start'
          AND DATE(event_time) = %s
    """, [date_str])

    # 统计新用户 (首次出现的 install_id)
    new_users = db.query("""
        SELECT COUNT(*) FROM (
            SELECT install_id
            FROM analytics_events
            WHERE type = 'session' AND name = 'session_start'
            GROUP BY install_id
            HAVING MIN(DATE(event_time)) = %s
        ) t
    """, [date_str])

    # 写入统计表
    db.upsert('analytics_daily_active', {
        'stat_date': date_str,
        'total_dau': total_dau,
        'new_users': new_users,
        'returning_users': total_dau - new_users
    })

5.2 在线人数快照任务

执行频率: 每 5 分钟

# 伪代码
def snapshot_online_count():
    now = datetime.now()
    threshold = now - timedelta(minutes=2)

    # 从 Redis 获取在线人数
    online_count = redis.zcount(
        'presence:online_users',
        threshold.timestamp(),
        now.timestamp()
    )

    # 写入快照表
    db.insert('analytics_online_snapshots', {
        'snapshot_time': now,
        'online_count': online_count
    })

5.3 过期数据清理任务

执行频率: 每天凌晨 03:00

# 伪代码
def cleanup_expired_data():
    # 清理 30 天前的事件数据
    retention_days = 30
    cutoff_date = date.today() - timedelta(days=retention_days)

    db.execute("""
        DELETE FROM analytics_events
        WHERE event_time < %s
        LIMIT 100000
    """, [cutoff_date])

    # 清理 Redis 过期在线状态
    redis.zremrangebyscore(
        'presence:online_users',
        '-inf',
        (datetime.now() - timedelta(minutes=5)).timestamp()
    )

6. 前端关键行为说明

6.1 会话管理

会话开始条件:

  • 应用冷启动
  • 应用从后台恢复且距上次活动超过 30 分钟

会话结束条件:

  • 应用进入后台

前端发送事件:

// 会话开始
TelemetryService().trackEvent(
  name: SessionEvents.sessionStart,
  type: EventType.session,
  properties: {'trigger': 'app_launch'},
);

// 会话结束
TelemetryService().trackEvent(
  name: SessionEvents.sessionEnd,
  type: EventType.session,
  properties: {
    'duration_seconds': duration,
    'trigger': 'app_pause',
  },
);

6.2 事件上报策略

  • 队列阈值触发: 本地队列 ≥10 条时触发上传
  • 定时触发: 每 30 秒检查一次
  • 应用退出触发: 强制上传全部队列
  • 批量大小: 每批最多 20 条

6.3 设备上下文

前端采集设备信息后生成唯一 deviceContextId,同一设备配置不变时复用相同 ID。设备信息变化如系统升级时生成新 ID。


7. API 错误码规范

code 说明
0 成功
1001 参数错误
1002 认证失败
2001 服务器内部错误
2002 数据库错误
3001 版本不存在
3002 下载链接已过期

8. 安全建议

  1. APK 下载

    • 使用 HTTPS
    • 提供 SHA-256 校验
    • 下载链接可设置有效期
  2. 遥测数据

    • 不采集敏感个人信息 (如通讯录、短信)
    • installId 使用 UUID 生成,不关联设备硬件标识
    • 支持用户关闭遥测 (通过远程配置)
  3. 心跳接口

    • 防刷限流 (每个 installId 每分钟最多 2 次)
    • 异常流量监控

附录: 前端代码目录结构

lib/core/
├── updater/                        # APK升级模块
│   ├── models/
│   │   ├── version_info.dart       # 版本信息模型
│   │   └── update_config.dart      # 更新配置模型
│   ├── channels/
│   │   ├── google_play_updater.dart # Google Play更新器
│   │   └── self_hosted_updater.dart # 自建服务器更新器
│   ├── version_checker.dart        # 版本检测器
│   ├── download_manager.dart       # 下载管理器
│   ├── apk_installer.dart          # APK安装器
│   ├── app_market_detector.dart    # 应用市场检测
│   └── update_service.dart         # 统一更新服务
│
└── telemetry/                      # 遥测模块
    ├── models/
    │   ├── device_context.dart     # 设备上下文模型
    │   ├── telemetry_event.dart    # 遥测事件模型
    │   └── telemetry_config.dart   # 遥测配置模型
    ├── collectors/
    │   └── device_info_collector.dart # 设备信息采集器
    ├── storage/
    │   └── telemetry_storage.dart  # 本地事件存储
    ├── uploader/
    │   └── telemetry_uploader.dart # 事件上传器
    ├── session/
    │   ├── session_events.dart     # 会话事件常量
    │   └── session_manager.dart    # 会话管理器
    ├── presence/
    │   ├── presence_config.dart    # 心跳配置
    │   └── heartbeat_service.dart  # 心跳服务
    └── telemetry_service.dart      # 遥测服务入口