# 后端 API 开发指南 > 本文档为 Flutter 前端已实现功能提供后端 API 接口规范,供后端开发参考。 --- ## 目录 1. [APK 在线升级 API](#1-apk-在线升级-api) 2. [遥测系统 API](#2-遥测系统-api) 3. [数据库设计](#3-数据库设计) 4. [Redis 数据结构](#4-redis-数据结构) 5. [定时任务](#5-定时任务) --- ## 1. APK 在线升级 API ### 1.1 版本检测接口 **请求** ``` GET /api/app/version/check ``` **Query 参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | platform | string | 是 | 平台类型: `android` / `ios` | | currentVersion | string | 是 | 当前版本号,如 `1.0.0` | | currentVersionCode | int | 是 | 当前版本代码,如 `1` | **响应示例 - 有新版本** ```json { "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" } } ``` **响应示例 - 已是最新版本** ```json { "code": 0, "message": "success", "data": { "hasUpdate": false } } ``` **前端对应代码** ```dart // lib/core/updater/version_checker.dart Future 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 // 可选,用户已登录时携带 ``` **请求体** ```json { "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` | 错误 | **响应** ```json { "code": 0, "message": "success" } ``` **前端对应代码** ```dart // lib/core/telemetry/uploader/telemetry_uploader.dart Future 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 // 可选 ``` **请求体** ```json { "installId": "device-unique-install-id", "sessionId": "session-xyz789", "userId": "user-123", "timestamp": "2024-01-15T10:30:00.000Z" } ``` **响应** ```json { "code": 0, "message": "success" } ``` **前端行为** - 应用在前台时,每 **60 秒** 发送一次心跳 - 应用进入后台时暂停心跳 - 应用恢复前台时立即发送心跳并恢复定时器 **前端对应代码** ```dart // 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 ``` **响应** ```json { "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 | 会话超时分钟数 | **前端对应代码** ```dart // lib/core/telemetry/models/telemetry_config.dart class TelemetryConfig { final bool enabled; final double samplingRate; final List enabledEventTypes; final int maxQueueSize; final int uploadBatchSize; final int uploadIntervalSeconds; final int heartbeatIntervalSeconds; final int sessionTimeoutMinutes; } ``` --- ## 3. 数据库设计 ### 3.1 应用版本表 (app_versions) ```sql 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) ```sql 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) ```sql 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) ```sql 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) ```sql 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**: `installId` 或 `userId` (取决于是否登录) ```redis # 用户发送心跳时 ZADD presence:online_users # 获取在线人数 (2分钟内有心跳的用户) ZCOUNT presence:online_users # 清理过期用户 (可选,定时任务执行) ZREMRANGEBYSCORE presence:online_users -inf ``` **在线判定规则** 用户最后心跳时间在 **2 分钟** 内视为在线(心跳间隔 60 秒,允许 1 次丢失) ### 4.2 日活去重集合 **Key**: `analytics:dau:` (如 `analytics:dau:2024-01-15`) **Type**: Set **Member**: `installId` **TTL**: 48 小时 ```redis # 记录日活 SADD analytics:dau:2024-01-15 EXPIRE analytics:dau:2024-01-15 172800 # 获取当日DAU SCARD analytics:dau:2024-01-15 ``` ### 4.3 会话缓存 **Key**: `session:` **Type**: Hash **TTL**: 30 分钟 (随活动更新) ```redis 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 ```python # 伪代码 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 分钟 ```python # 伪代码 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 ```python # 伪代码 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 分钟 **会话结束条件**: - 应用进入后台 **前端发送事件**: ```dart // 会话开始 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 # 遥测服务入口 ```