592 lines
18 KiB
Markdown
592 lines
18 KiB
Markdown
# 移动应用升级服务架构文档
|
||
|
||
## 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
|
||
```
|
||
|
||
**响应 (有更新):**
|
||
```json
|
||
{
|
||
"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"
|
||
}
|
||
```
|
||
|
||
**响应 (无更新):**
|
||
```json
|
||
{
|
||
"needUpdate": false
|
||
}
|
||
```
|
||
|
||
### 2.3 管理员检查更新 API (兼容)
|
||
|
||
**请求:**
|
||
```
|
||
GET /api/v1/versions/check-update?platform=android¤tVersionCode=100
|
||
```
|
||
|
||
**响应:**
|
||
```json
|
||
{
|
||
"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):**
|
||
```json
|
||
{
|
||
"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 数据库表结构
|
||
|
||
```sql
|
||
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 初始化配置
|
||
|
||
```dart
|
||
// 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 版本信息模型
|
||
|
||
```dart
|
||
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 发布新版本流程
|
||
|
||
```bash
|
||
# 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 启用/禁用版本
|
||
|
||
```bash
|
||
# 禁用版本
|
||
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 设置强制更新
|
||
|
||
```bash
|
||
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 配置
|
||
|
||
```groovy
|
||
// android/app/build.gradle
|
||
android {
|
||
defaultConfig {
|
||
versionCode 10001 // 用于更新比较
|
||
versionName "1.0.1" // 用户可见
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.4 Flutter 配置
|
||
|
||
```yaml
|
||
# 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 移动端错误处理
|
||
|
||
```dart
|
||
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 后端测试
|
||
|
||
```bash
|
||
# 单元测试
|
||
npm run test:unit
|
||
|
||
# 集成测试
|
||
npm run test:integration
|
||
|
||
# E2E测试
|
||
npm run test:e2e
|
||
```
|
||
|
||
### 9.2 移动端测试
|
||
|
||
```bash
|
||
# 运行所有测试
|
||
flutter test
|
||
|
||
# 测试更新模块
|
||
flutter test test/core/updater/
|
||
```
|
||
|
||
### 9.3 手动测试流程
|
||
|
||
1. **创建测试版本**
|
||
```bash
|
||
curl -X POST http://localhost:3010/api/v1/versions ...
|
||
```
|
||
|
||
2. **验证检查更新**
|
||
```bash
|
||
curl "http://localhost:3010/api/v1/versions/check-update?platform=android¤tVersionCode=100"
|
||
```
|
||
|
||
3. **在模拟器中测试**
|
||
- 安装旧版本APK
|
||
- 启动应用,观察更新对话框
|
||
- 测试下载和安装流程
|
||
|
||
---
|
||
|
||
## 10. 常见问题
|
||
|
||
### Q1: 如何实现灰度发布?
|
||
|
||
目前系统不支持灰度发布。可通过以下方式扩展:
|
||
- 添加 `rolloutPercentage` 字段
|
||
- 基于设备ID哈希值判断是否推送更新
|
||
|
||
### Q2: 如何支持多语言更新日志?
|
||
|
||
可将 `changelog` 字段改为 JSON 格式:
|
||
```json
|
||
{
|
||
"zh": "1. 修复问题\n2. 性能优化",
|
||
"en": "1. Bug fixes\n2. Performance improvements"
|
||
}
|
||
```
|
||
|
||
### Q3: 下载失败怎么办?
|
||
|
||
移动端实现了以下容错机制:
|
||
- 自动重试 (最多3次)
|
||
- 断点续传支持
|
||
- 失败时提示用户手动下载
|
||
|
||
### Q4: 如何处理大文件下载?
|
||
|
||
- 使用CDN加速
|
||
- 支持分片下载
|
||
- 后台下载 + 通知栏进度
|