18 KiB
18 KiB
移动应用升级服务架构文档
1. 概述
本文档描述了 RWA Durian 移动应用升级服务的完整架构,包括后端 Admin Service 和前端 Flutter Mobile App 的协作方式。
1.1 系统架构图
┌─────────────────────────────────────────────────────────────────────┐
│ 管理员操作流程 │
│ 1. 构建新版APK → 2. 计算SHA256 → 3. 上传至文件服务器 → 4. 调用API创建版本 │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Admin Service (NestJS 后端) │
│ • 存储版本元数据 (PostgreSQL) │
│ • 提供版本检查API (公开) │
│ • 管理员版本管理API (需认证) │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Mobile App (Flutter 前端) │
│ • 启动时检查更新 │
│ • 下载APK + SHA256校验 │
│ • 触发系统安装 │
└─────────────────────────────────────────────────────────────────────┘
1.2 双渠道更新策略
| 渠道 | 机制 | 使用场景 | 流程 |
|---|---|---|---|
| Google Play | Google Play Core Library | 正式发布、应用商店 | UpdateService → GooglePlayUpdater → InAppUpdate API |
| Self-Hosted | 自定义APK下载 | 侧载安装、企业分发、中国市场 | UpdateService → SelfHostedUpdater → VersionChecker → DownloadManager |
应用通过 AppMarketDetector.isFromAppMarket() 检测安装来源,应用市场安装的版本会跳转到 Play Store,侧载版本使用自托管下载。
2. 后端架构 (Admin Service)
2.1 API 端点
公开端点 (无需认证)
| 端点 | 方法 | 用途 |
|---|---|---|
/api/app/version/check |
GET | 移动端检查更新 (推荐) |
/api/v1/versions/check-update |
GET | 检查更新 (兼容端点) |
/api/v1/health |
GET | 健康检查 |
/uploads/:filename |
GET | 下载APK文件 |
管理员端点 (需要认证)
| 端点 | 方法 | 用途 |
|---|---|---|
/api/v1/versions |
GET | 获取版本列表 |
/api/v1/versions |
POST | 创建新版本 (URL方式) |
/api/v1/versions/upload |
POST | 上传APK并创建版本 |
/api/v1/versions/:id |
GET | 获取单个版本详情 |
/api/v1/versions/:id |
PUT | 更新版本信息 |
/api/v1/versions/:id |
DELETE | 删除版本 |
/api/v1/versions/:id/toggle |
PATCH | 启用/禁用版本 |
2.2 移动端检查更新 API (推荐)
此端点专为移动端设计,返回格式与 Flutter 应用的 VersionInfo 模型完全兼容。
请求:
GET /api/app/version/check?platform=android¤t_version=1.0.0¤t_version_code=100
响应 (有更新):
{
"needUpdate": true,
"version": "1.0.1",
"versionCode": 101,
"downloadUrl": "https://api.example.com/uploads/android-1.0.1-xxx.apk",
"fileSize": 52428800,
"fileSizeFriendly": "50.0 MB",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"forceUpdate": false,
"updateLog": "1. 修复登录问题\n2. 性能优化",
"releaseDate": "2025-12-02T10:00:00.000Z"
}
响应 (无更新):
{
"needUpdate": false
}
2.3 管理员检查更新 API (兼容)
请求:
GET /api/v1/versions/check-update?platform=android¤tVersionCode=100
响应:
{
"hasUpdate": true,
"isForceUpdate": false,
"latestVersion": {
"versionCode": 101,
"versionName": "1.0.1",
"downloadUrl": "https://cdn.example.com/app-v1.0.1.apk",
"fileSize": "52428800",
"fileSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"changelog": "1. 修复登录问题\n2. 性能优化",
"minOsVersion": "5.0",
"releaseDate": "2025-12-02T10:00:00Z"
}
}
2.3 创建版本 API
请求:
POST /api/v1/versions
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"platform": "android",
"versionCode": 101,
"versionName": "1.0.1",
"buildNumber": "202512021200",
"downloadUrl": "https://cdn.example.com/app-v1.0.1.apk",
"fileSize": "52428800",
"fileSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"changelog": "1. 修复登录问题\n2. 性能优化",
"isForceUpdate": false,
"minOsVersion": "5.0",
"releaseDate": "2025-12-02T10:00:00Z"
}
响应 (201 Created):
{
"id": "uuid",
"platform": "android",
"versionCode": 101,
"versionName": "1.0.1",
"buildNumber": "202512021200",
"downloadUrl": "https://cdn.example.com/app-v1.0.1.apk",
"fileSize": "52428800",
"fileSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"changelog": "1. 修复登录问题\n2. 性能优化",
"isForceUpdate": false,
"isEnabled": true,
"minOsVersion": "5.0",
"releaseDate": "2025-12-02T10:00:00Z",
"createdAt": "2025-12-02T10:00:00Z",
"updatedAt": "2025-12-02T10:00:00Z"
}
2.4 数据验证规则
| 字段 | 类型 | 验证规则 |
|---|---|---|
platform |
enum | android | ios |
versionCode |
number | 1 ~ 2147483647 |
versionName |
string | 语义化版本格式: X.Y.Z |
buildNumber |
string | 非空字符串 |
downloadUrl |
string | 有效的 HTTP/HTTPS URL |
fileSize |
string | 0 ~ 2GB (以字节为单位) |
fileSha256 |
string | 64位十六进制字符 |
changelog |
string | 非空字符串 |
minOsVersion |
string | 格式: X.Y 或 X.Y.Z |
2.5 数据库表结构
CREATE TYPE "Platform" AS ENUM ('ANDROID', 'IOS');
CREATE TABLE "app_versions" (
id UUID PRIMARY KEY,
platform Platform NOT NULL,
versionCode INTEGER NOT NULL,
versionName TEXT NOT NULL,
buildNumber TEXT NOT NULL,
downloadUrl TEXT NOT NULL,
fileSize BIGINT NOT NULL,
fileSha256 TEXT NOT NULL,
changelog TEXT NOT NULL,
minOsVersion TEXT,
isForceUpdate BOOLEAN DEFAULT false,
isEnabled BOOLEAN DEFAULT true,
releaseDate TIMESTAMP,
createdAt TIMESTAMP DEFAULT NOW(),
updatedAt TIMESTAMP,
createdBy TEXT NOT NULL,
updatedBy TEXT
);
CREATE INDEX idx_app_versions_platform_enabled ON "app_versions"(platform, isEnabled);
CREATE INDEX idx_app_versions_platform_code ON "app_versions"(platform, versionCode);
3. 移动端架构 (Flutter)
3.1 初始化配置
// lib/bootstrap.dart
const String _apiBaseUrl = 'https://api.rwadurian.com';
UpdateService().initialize(
UpdateConfig.selfHosted(
apiBaseUrl: _apiBaseUrl,
enabled: true,
checkIntervalSeconds: 86400, // 24小时
),
);
3.2 核心组件
| 组件 | 文件路径 | 职责 |
|---|---|---|
UpdateService |
lib/core/updater/update_service.dart |
统一入口,管理更新流程 |
VersionChecker |
lib/core/updater/version_checker.dart |
与后端API通信,获取最新版本 |
DownloadManager |
lib/core/updater/download_manager.dart |
下载APK,验证SHA256 |
ApkInstaller |
lib/core/updater/apk_installer.dart |
调用系统安装APK |
SelfHostedUpdater |
lib/core/updater/channels/self_hosted_updater.dart |
自托管更新完整流程 |
3.3 版本信息模型
class VersionInfo extends Equatable {
final String version; // "1.0.1"
final int versionCode; // 101
final String downloadUrl; // APK下载URL
final int fileSize; // 字节数
final String fileSizeFriendly; // "50.0 MB"
final String sha256; // SHA-256校验和
final bool forceUpdate; // 强制更新标志
final String? updateLog; // 更新日志
final DateTime releaseDate; // 发布日期
}
3.4 更新检查流程
App启动 (SplashPage)
↓
UpdateService.checkForUpdate(context)
↓
VersionChecker.fetchLatestVersion()
├─ 获取当前版本: PackageInfo.fromPlatform()
└─ 请求后端: GET /api/v1/versions/check-update
↓
比较 latestVersionCode > currentVersionCode ?
↓
显示更新对话框
├─ 普通更新: 显示 [稍后] [立即更新]
└─ 强制更新: 只显示 [立即更新], 禁止关闭对话框
3.5 下载与安装流程
用户点击"立即更新"
↓
显示下载进度对话框
↓
DownloadManager.downloadApk()
├─ 验证HTTPS URL
├─ 下载到应用私有目录
├─ 显示下载进度
└─ 验证SHA256校验和
↓
ApkInstaller.installApk(apkFile)
├─ 请求安装权限 (Android 8.0+)
└─ 调用系统安装界面
↓
用户完成安装,应用重启
4. 安全机制
| 安全措施 | 实现位置 | 说明 |
|---|---|---|
| HTTPS强制 | DownloadManager | 只接受 https:// 开头的URL |
| SHA256校验 | DownloadManager | 下载后验证文件完整性 |
| 文件大小限制 | FileSize值对象 | 最大2GB |
| 版本号验证 | VersionCode值对象 | 1 ~ 2147483647 |
| 无外部存储权限 | 应用私有目录 | 使用 getApplicationDocumentsDirectory() |
| 安装权限请求 | ApkInstaller | Android 8.0+ 需要 INSTALL_UNKNOWN_APPS 权限 |
| 管理员认证 | Bearer Token | 创建/修改版本需要认证 |
5. 管理员操作指南
5.1 发布新版本流程
# 1. 构建Release APK
cd android
./gradlew assembleRelease
# 2. 计算SHA256校验和
sha256sum app/build/outputs/apk/release/app-release.apk
# 输出: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
# 3. 上传APK到文件服务器 (选择以下任一方式)
# 阿里云OSS
aliyun oss cp app-release.apk oss://bucket/app-v1.0.1.apk
# AWS S3
aws s3 cp app-release.apk s3://bucket/app-v1.0.1.apk
# 4. 调用API创建版本
curl -X POST https://api.rwadurian.com/api/v1/versions \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"platform": "android",
"versionCode": 101,
"versionName": "1.0.1",
"buildNumber": "202512021200",
"downloadUrl": "https://cdn.example.com/app-v1.0.1.apk",
"fileSize": "52428800",
"fileSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"changelog": "1. 修复登录问题\n2. 性能优化",
"isForceUpdate": false,
"minOsVersion": "5.0"
}'
5.2 启用/禁用版本
# 禁用版本
curl -X PATCH https://api.rwadurian.com/api/v1/versions/{id}/toggle \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"isEnabled": false}'
# 启用版本
curl -X PATCH https://api.rwadurian.com/api/v1/versions/{id}/toggle \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"isEnabled": true}'
5.3 设置强制更新
curl -X PUT https://api.rwadurian.com/api/v1/versions/{id} \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"isForceUpdate": true}'
6. 版本号约定
6.1 versionCode vs versionName
| 属性 | 类型 | 用途 | 示例 |
|---|---|---|---|
versionCode |
整数 | 用于比较新旧版本,必须递增 | 101, 102, 200 |
versionName |
字符串 | 用户可见的版本号 | "1.0.1", "2.0.0" |
6.2 版本号递增规则
versionCode 计算公式建议:
major * 10000 + minor * 100 + patch
示例:
1.0.0 → 10000
1.0.1 → 10001
1.1.0 → 10100
2.0.0 → 20000
6.3 Android 配置
// android/app/build.gradle
android {
defaultConfig {
versionCode 10001 // 用于更新比较
versionName "1.0.1" // 用户可见
}
}
6.4 Flutter 配置
# pubspec.yaml
version: 1.0.1+10001
# 格式: versionName+versionCode
7. 错误处理
7.1 后端错误码
| HTTP状态码 | 错误类型 | 说明 |
|---|---|---|
| 400 | Bad Request | 请求参数验证失败 |
| 401 | Unauthorized | 未提供或无效的认证令牌 |
| 404 | Not Found | 版本不存在 |
| 409 | Conflict | 版本号已存在 |
| 500 | Internal Server Error | 服务器内部错误 |
7.2 移动端错误处理
try {
final versionInfo = await versionChecker.checkForUpdate();
if (versionInfo != null) {
await showUpdateDialog(context, versionInfo);
}
} on DioException catch (e) {
// 网络错误,静默失败,不影响用户使用
debugPrint('Update check failed: ${e.message}');
} catch (e) {
debugPrint('Unexpected error: $e');
}
8. 文件结构
8.1 后端 (Admin Service)
src/
├── api/
│ ├── controllers/
│ │ └── version.controller.ts
│ └── dto/
│ ├── request/
│ │ ├── check-update.dto.ts
│ │ ├── create-version.dto.ts
│ │ └── update-version.dto.ts
│ └── response/
│ └── version.dto.ts
├── application/
│ ├── commands/
│ │ ├── create-version/
│ │ ├── update-version/
│ │ └── delete-version/
│ └── queries/
│ ├── check-update/
│ ├── get-version/
│ └── list-versions/
├── domain/
│ ├── entities/
│ │ └── app-version.entity.ts
│ ├── enums/
│ │ └── platform.enum.ts
│ ├── repositories/
│ │ └── app-version.repository.ts
│ └── value-objects/
│ ├── version-code.vo.ts
│ ├── version-name.vo.ts
│ └── ... (其他值对象)
└── infrastructure/
└── persistence/
├── mappers/
│ └── app-version.mapper.ts
└── repositories/
└── app-version.repository.impl.ts
8.2 移动端 (Flutter)
lib/
├── core/
│ └── updater/
│ ├── models/
│ │ ├── version_info.dart
│ │ └── update_config.dart
│ ├── channels/
│ │ ├── google_play_updater.dart
│ │ └── self_hosted_updater.dart
│ ├── update_service.dart
│ ├── version_checker.dart
│ ├── download_manager.dart
│ ├── apk_installer.dart
│ └── app_market_detector.dart
└── features/
└── auth/
└── presentation/
└── pages/
└── splash_page.dart
9. 测试
9.1 后端测试
# 单元测试
npm run test:unit
# 集成测试
npm run test:integration
# E2E测试
npm run test:e2e
9.2 移动端测试
# 运行所有测试
flutter test
# 测试更新模块
flutter test test/core/updater/
9.3 手动测试流程
-
创建测试版本
curl -X POST http://localhost:3010/api/v1/versions ... -
验证检查更新
curl "http://localhost:3010/api/v1/versions/check-update?platform=android¤tVersionCode=100" -
在模拟器中测试
- 安装旧版本APK
- 启动应用,观察更新对话框
- 测试下载和安装流程
10. 常见问题
Q1: 如何实现灰度发布?
目前系统不支持灰度发布。可通过以下方式扩展:
- 添加
rolloutPercentage字段 - 基于设备ID哈希值判断是否推送更新
Q2: 如何支持多语言更新日志?
可将 changelog 字段改为 JSON 格式:
{
"zh": "1. 修复问题\n2. 性能优化",
"en": "1. Bug fixes\n2. Performance improvements"
}
Q3: 下载失败怎么办?
移动端实现了以下容错机制:
- 自动重试 (最多3次)
- 断点续传支持
- 失败时提示用户手动下载
Q4: 如何处理大文件下载?
- 使用CDN加速
- 支持分片下载
- 后台下载 + 通知栏进度