一、uni-app 移动端打包上架
1.1 Android 打包签名
生成签名文件
# 使用 keytool 生成签名文件
keytool -genkey -v -keystore myapp.keystore -alias myapp -keyalg RSA -keysize 2048 -validity 10000
# 参数说明:
# -keystore: 签名文件名称
# -alias: 别名
# -keyalg: 加密算法
# -keysize: 密钥长度
# -validity: 有效期(天)
# 输入信息:
密钥库口令: your_password
再次输入: your_password
姓名: Zhang San
组织单位: Dev Team
组织: MyCompany
城市: Beijing
省份: Beijing
国家代码: CN
HBuilderX 配置签名
1. 打开项目 manifest.json
2. 点击"App 模块配置"
3. 选择"Android 设置"
4. 填写签名信息:
- 证书别名: myapp
- 证书私钥密码: your_password
- 证书文件路径: /path/to/myapp.keystore
云打包
1. HBuilderX 菜单: 发行 -> 原生App-云打包
2. 选择平台: Android
3. 选择打包类型:
- 正式包: 用于发布
- 测试包: 用于内部测试
4. 填写应用信息:
- 应用名称
- 应用版本号
- 应用版本名称
5. 使用自有证书
6. 开始打包
7. 等待打包完成(约 5-10 分钟)
8. 下载 apk 文件
本地打包(离线打包)
# 1. 下载离线 SDK
# https://nativesupport.dcloud.net.cn/AppDocs/download/android
# 2. 导入 Android Studio
File -> Open -> 选择 SDK 目录
# 3. 配置签名
在 app/build.gradle 中添加:
android {
signingConfigs {
release {
storeFile file("../myapp.keystore")
storePassword "your_password"
keyAlias "myapp"
keyPassword "your_password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
# 4. 打包
./gradlew assembleRelease
# 5. 输出位置
app/build/outputs/apk/release/app-release.apk
1.2 iOS 打包签名
申请开发者账号
1. 访问 https://developer.apple.com
2. 注册账号(个人 $99/年,企业 $299/年)
3. 完成实名认证
4. 等待审核通过(1-2 天)
创建 App ID
1. 登录 Apple Developer
2. Certificates, Identifiers & Profiles
3. Identifiers -> App IDs
4. 点击 + 创建新 App ID
5. 填写信息:
- Description: 应用描述
- Bundle ID: com.company.appname(必须唯一)
- Capabilities: 选择需要的能力(推送、支付等)
6. 保存
创建证书
1. 证书类型:
- Development: 开发证书(调试用)
- Distribution: 发布证书(上架用)
2. 创建 CSR 文件(在 Mac 上):
- 打开"钥匙串访问"
- 证书助理 -> 从证书颁发机构请求证书
- 邮箱: your@email.com
- 常用名称: Your Name
- 选择"存储到磁盘"
- 保存为 CertificateSigningRequest.certSigningRequest
3. 上传 CSR:
- Certificates -> + -> iOS Distribution
- 上传 CSR 文件
- 下载证书(.cer 文件)
- 双击安装到钥匙串
4. 导出 p12:
- 钥匙串中找到证书
- 右键 -> 导出
- 保存为 .p12 文件
- 设置密码
创建描述文件
1. Profiles -> +
2. 选择类型:
- Development: 开发
- Ad Hoc: 内测
- App Store: 上架
3. 选择 App ID
4. 选择证书
5. 选择设备(Development 和 Ad Hoc 需要)
6. 输入名称
7. 下载 .mobileprovision 文件
HBuilderX 云打包 iOS
1. 发行 -> 原生App-云打包
2. 选择 iOS
3. 选择证书类型:
- AppStore: 正式发布
- AdHoc: 企业内测
- development: 开发测试
4. 上传证书:
- 私钥证书: .p12 文件
- 私钥密码: p12 密码
- 证书 profile: .mobileprovision 文件
5. 开始打包
6. 下载 .ipa 文件
本地打包(离线打包)
# 1. 下载离线 SDK
# https://nativesupport.dcloud.net.cn/AppDocs/download/ios
# 2. 用 Xcode 打开项目
open HBuilder-uniPlugin.xcodeproj
# 3. 配置签名
- General -> Signing
- Team: 选择开发者账号
- Bundle Identifier: 填写 App ID
# 4. 配置证书
- Build Settings -> Code Signing
- Code Signing Identity: iOS Distribution
- Provisioning Profile: 选择下载的描述文件
# 5. 选择设备
Generic iOS Device
# 6. 打包
Product -> Archive
# 7. 导出 ipa
- 选择 Archive
- Distribute App
- App Store Connect / Ad Hoc / Development
- 选择证书
- Export
1.3 应用商店上架
Android 应用市场上架
应用宝(腾讯)
1. 注册开发者账号
https://open.qq.com
2. 创建应用
- 应用名称
- 应用包名
- 应用分类
3. 上传 APK
- APK 文件
- 应用图标(512x512)
- 应用截图(至少 3 张)
- 应用简介
- 更新说明
4. 填写资质
- 软著证书
- ICP 备案
- 隐私政策
- 用户协议
5. 提交审核
- 初审:1-2 天
- 终审:3-5 天
6. 审核通过后自动上架
华为应用市场
1. 注册开发者账号
https://developer.huawei.com/consumer/cn/
2. 实名认证
- 个人: 身份证
- 企业: 营业执照
3. 创建应用
- 应用信息
- 分类标签
4. 上传安装包
- APK 文件
- 应用图标
- 应用截图(3-5 张)
- 宣传视频(可选)
5. 版本信息
- 版本号
- 更新说明
- 测试账号
6. 内容分级
- 年龄分级
- 内容标签
7. 提交审核
- 审核时间: 1-3 个工作日
小米应用商店
类似流程,需要额外注意:
- APK 需要签名校验
- 应用图标尺寸: 512x512
- 需要提供测试账号
- 审核周期: 2-3 天
iOS App Store 上架
准备工作
1. 注册 App Store Connect 账号
https://appstoreconnect.apple.com
2. 签订协议
- Agreements, Tax, and Banking
- 签署 Paid Applications 协议
- 填写税务信息
- 填写银行账户信息(收款用)
创建应用
1. App Store Connect -> 我的 App -> +
2. 选择平台: iOS
3. 填写信息:
- 名称: 应用名称(30 字符以内)
- 主要语言: 简体中文
- Bundle ID: 选择之前创建的 App ID
- SKU: 唯一标识符
- 用户访问权限: 完全访问
配置应用信息
1. 版本信息:
- 应用名称
- 副标题(可选)
- 隐私政策 URL
- 类别(主要/次要)
- 内容版权
- 年龄分级
2. 准备提交:
- 屏幕截图(必需):
* 6.5 英寸: 1242x2688 或 1284x2778
* 5.5 英寸: 1242x2208
* 至少 3 张,最多 10 张
- 应用预览视频(可选):
* 格式: .mov/.mp4/.m4v
* 时长: 15-30 秒
- 推广文本(可选): 170 字符
- 描述: 4000 字符以内
- 关键词: 100 字符,逗号分隔
- 技术支持 URL
- 营销 URL(可选)
3. 构建版本:
- 使用 Xcode 上传构建版本
- 或使用 Transporter 工具上传 ipa
4. App 审核信息:
- 联系信息
- 演示账号(如需登录)
- 备注信息
上传构建版本
方式一:Xcode 上传
1. Archive 后选择 Distribute App
2. App Store Connect
3. Upload
4. 等待处理(10-30 分钟)
5. 在 App Store Connect 中选择构建版本
方式二:Transporter 工具
1. App Store 下载 Transporter
2. 登录 Apple ID
3. 拖入 ipa 文件
4. 交付
5. 等待处理
提交审核
1. 确认所有信息填写完整
2. 添加审核备注(如有特殊说明)
3. 点击"提交审核"
4. 审核状态:
- 等待审核: 排队中
- 正在审核: 1-2 天
- 被拒绝: 修改后重新提交
- 准备销售: 审核通过
5. 审核通过后:
- 自动发布(默认)
- 手动发布(需设置)
常见拒审原因
1. 崩溃或 bug
2. 功能不完整
3. 元数据不符(图标、截图、描述不一致)
4. 隐私政策缺失
5. 使用第三方登录但未提供其他登录方式
6. 应用内购买配置错误
7. 界面设计不符合 Apple 规范
1.4 版本更新策略
版本号规范
// manifest.json
{
"versionName": "1.2.3", // 版本名称,用户可见
"versionCode": 123 // 版本号,递增数字
}
// 版本号规则:主版本号.次版本号.修订号
// 1.2.3 表示:
// 1: 主版本(重大功能更新)
// 2: 次版本(新功能添加)
// 3: 修订号(bug 修复)
强制更新
// utils/update-checker.js
class UpdateChecker {
async checkUpdate() {
try {
const res = await uni.request({
url: 'https://api.xxx.com/app/version',
data: {
platform: uni.getSystemInfoSync().platform,
versionCode: this.getCurrentVersionCode()
}
})
const { hasUpdate, versionName, versionCode, downloadUrl, forceUpdate, updateInfo } = res.data
if (hasUpdate) {
if (forceUpdate) {
// 强制更新
this.showForceUpdateDialog(versionName, downloadUrl, updateInfo)
} else {
// 可选更新
this.showOptionalUpdateDialog(versionName, downloadUrl, updateInfo)
}
}
} catch (error) {
console.error('检查更新失败:', error)
}
}
showForceUpdateDialog(versionName, downloadUrl, updateInfo) {
uni.showModal({
title: '发现新版本',
content: `版本 ${versionName}\n\n${updateInfo}\n\n必须更新才能继续使用`,
showCancel: false,
confirmText: '立即更新',
success: () => {
this.downloadAndInstall(downloadUrl)
}
})
}
showOptionalUpdateDialog(versionName, downloadUrl, updateInfo) {
uni.showModal({
title: '发现新版本',
content: `版本 ${versionName}\n\n${updateInfo}`,
confirmText: '立即更新',
cancelText: '稍后提醒',
success: (res) => {
if (res.confirm) {
this.downloadAndInstall(downloadUrl)
} else {
// 记录用户选择,下次启动再提醒
this.saveUpdateReminder()
}
}
})
}
downloadAndInstall(url) {
// #ifdef APP-PLUS
uni.showLoading({ title: '下载中...' })
const downloadTask = uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200) {
uni.hideLoading()
// Android 直接安装
plus.runtime.install(res.tempFilePath, {
force: false
}, () => {
console.log('安装成功')
}, (error) => {
uni.showToast({ title: '安装失败', icon: 'none' })
})
}
},
fail: () => {
uni.hideLoading()
uni.showToast({ title: '下载失败', icon: 'none' })
}
})
downloadTask.onProgressUpdate((res) => {
uni.showLoading({
title: `下载中 ${res.progress}%`,
mask: true
})
})
// #endif
// #ifdef H5
window.location.href = url
// #endif
}
getCurrentVersionCode() {
// #ifdef APP-PLUS
return plus.runtime.versionCode
// #endif
return 100
}
saveUpdateReminder() {
const remindTime = Date.now() + 24 * 60 * 60 * 1000 // 24 小时后提醒
uni.setStorageSync('update_remind_time', remindTime)
}
}
export default new UpdateChecker()
灰度发布
// server/version-api.js
const express = require('express')
const router = express.Router()
router.get('/version', (req, res) => {
const { platform, versionCode, userId } = req.query
// 当前最新版本
const latestVersion = {
versionName: '2.0.0',
versionCode: 200,
downloadUrl: 'https://cdn.xxx.com/app-2.0.0.apk',
updateInfo: '1. 新增功能\n2. 修复bug',
forceUpdate: false
}
// 灰度配置
const grayConfig = {
enabled: true,
percentage: 20, // 20% 用户
whitelist: ['user_001', 'user_002'], // 白名单
blacklist: ['user_999'] // 黑名单
}
// 判断是否需要更新
if (versionCode >= latestVersion.versionCode) {
return res.json({ hasUpdate: false })
}
// 黑名单直接不更新
if (grayConfig.blacklist.includes(userId)) {
return res.json({ hasUpdate: false })
}
// 白名单直接更新
if (grayConfig.whitelist.includes(userId)) {
return res.json({
hasUpdate: true,
...latestVersion
})
}
// 灰度发布判断
if (grayConfig.enabled) {
// 使用 userId 的 hash 值决定是否在灰度范围内
const hash = hashCode(userId)
const isInGray = (hash % 100) < grayConfig.percentage
if (!isInGray) {
return res.json({ hasUpdate: false })
}
}
res.json({
hasUpdate: true,
...latestVersion
})
})
function hashCode(str) {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i)
hash = hash & hash
}
return Math.abs(hash)
}
module.exports = router
热更新(wgt 包)
// utils/hot-update.js
class HotUpdate {
async checkWgtUpdate() {
// #ifdef APP-PLUS
try {
const res = await uni.request({
url: 'https://api.xxx.com/wgt/version',
data: {
platform: plus.os.name,
version: plus.runtime.version
}
})
const { hasUpdate, wgtUrl, version } = res.data
if (hasUpdate) {
uni.showModal({
title: '发现更新',
content: `版本 ${version}`,
success: (res) => {
if (res.confirm) {
this.downloadWgt(wgtUrl)
}
}
})
}
} catch (error) {
console.error('检查 wgt 更新失败:', error)
}
// #endif
}
downloadWgt(url) {
// #ifdef APP-PLUS
uni.showLoading({ title: '下载中...' })
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200) {
// 安装 wgt 包
plus.runtime.install(res.tempFilePath, {
force: false
}, () => {
uni.hideLoading()
uni.showModal({
title: '安装成功',
content: '应用将重启以完成更新',
showCancel: false,
success: () => {
plus.runtime.restart()
}
})
}, (error) => {
uni.hideLoading()
uni.showToast({ title: '安装失败', icon: 'none' })
})
}
}
})
// #endif
}
}
export default new HotUpdate()
二、Flutter 移动端打包上架
2.1 Android 打包
生成签名
# 进入 android/app 目录
cd android/app
# 生成签名文件
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
配置签名
# android/key.properties
storePassword=your_store_password
keyPassword=your_key_password
keyAlias=key
storeFile=/path/to/key.jks
// android/app/build.gradle
// 加载签名配置
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
打包
# 打包 APK
flutter build apk --release
# 打包 App Bundle(推荐,Google Play 要求)
flutter build appbundle --release
# 指定架构
flutter build apk --release --split-per-abi
# 输出位置
build/app/outputs/flutter-apk/app-release.apk
build/app/outputs/bundle/release/app-release.aab
2.2 iOS 打包
配置签名
1. 打开 Xcode
open ios/Runner.xcworkspace
2. 选择 Runner target
3. Signing & Capabilities
4. Team: 选择开发者账号
5. Bundle Identifier: 填写 App ID
6. 勾选 "Automatically manage signing"
打包
# 构建 iOS
flutter build ios --release
# 使用 Xcode Archive
1. Xcode 选择 Generic iOS Device
2. Product -> Archive
3. 等待构建完成
4. Window -> Organizer
5. 选择 Archive -> Distribute App
6. App Store Connect
7. Upload
2.3 版本更新
应用内更新检查
// lib/utils/update_checker.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
class UpdateChecker {
Future<void> checkUpdate(BuildContext context) async {
try {
final response = await http.get(
Uri.parse('https://api.xxx.com/flutter/version'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['hasUpdate']) {
_showUpdateDialog(
context,
data['versionName'],
data['downloadUrl'],
data['updateInfo'],
data['forceUpdate'],
);
}
}
} catch (e) {
print('检查更新失败: $e');
}
}
void _showUpdateDialog(
BuildContext context,
String versionName,
String downloadUrl,
String updateInfo,
bool forceUpdate,
) {
showDialog(
context: context,
barrierDismissible: !forceUpdate,
builder: (context) => AlertDialog(
title: Text('发现新版本'),
content: Text('版本 $versionName\n\n$updateInfo'),
actions: [
if (!forceUpdate)
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('稍后'),
),
TextButton(
onPressed: () async {
final uri = Uri.parse(downloadUrl);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
},
child: Text('更新'),
),
],
),
);
}
}
Android 下载安装
// pubspec.yaml
dependencies:
install_plugin: ^2.1.0
// 下载并安装
import 'package:install_plugin/install_plugin.dart';
Future<void> downloadAndInstall(String url) async {
final directory = await getApplicationDocumentsDirectory();
final filePath = '${directory.path}/app.apk';
// 下载
await Dio().download(url, filePath,
onReceiveProgress: (received, total) {
print('下载进度: ${(received / total * 100).toStringAsFixed(0)}%');
}
);
// 安装
await InstallPlugin.installApk(filePath, 'com.example.app');
}
三、React Native 移动端打包上架
3.1 Android 打包
生成签名
cd android/app
keytool -genkeypair -v -storetype PKCS12 -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
配置签名
# android/gradle.properties
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=your_password
MYAPP_RELEASE_KEY_PASSWORD=your_password
// android/app/build.gradle
android {
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
打包
cd android
./gradlew assembleRelease
# 输出位置
android/app/build/outputs/apk/release/app-release.apk
3.2 iOS 打包
# 安装依赖
cd ios
pod install
# 使用 Xcode 打包
open ios/YourApp.xcworkspace
# 或命令行
npx react-native run-ios --configuration Release
3.3 热更新(CodePush)
安装 CodePush
npm install --save react-native-code-push
npx react-native link react-native-code-push
# 或使用 appcenter CLI
npm install -g appcenter-cli
appcenter login
appcenter apps create -d MyApp-iOS -o iOS -p React-Native
appcenter apps create -d MyApp-Android -o Android -p React-Native
配置 CodePush
// App.js
import codePush from 'react-native-code-push';
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.IMMEDIATE,
};
class App extends Component {
componentDidMount() {
codePush.sync({
updateDialog: {
title: '发现新版本',
optionalUpdateMessage: '是否立即更新?',
optionalInstallButtonLabel: '更新',
optionalIgnoreButtonLabel: '稍后',
},
installMode: codePush.InstallMode.IMMEDIATE,
});
}
render() {
return <Root />;
}
}
export default codePush(codePushOptions)(App);
发布更新
# 发布到生产环境
appcenter codepush release-react -a username/MyApp-iOS -d Production
# 发布到测试环境
appcenter codepush release-react -a username/MyApp-iOS -d Staging
# 灰度发布(20%)
appcenter codepush release-react -a username/MyApp-iOS -d Production --rollout 20
# 查看发布历史
appcenter codepush deployment history Production -a username/MyApp-iOS
# 回滚
appcenter codepush rollback Production -a username/MyApp-iOS
四、Electron 桌面端打包上架
4.1 Windows 打包签名
申请代码签名证书
1. 购买证书
- Digicert
- Sectigo
- 价格: $100-$500/年
2. 验证身份
- 公司: 营业执照、法人身份证
- 个人: 身份证、地址证明
3. 下载证书
- .pfx 或 .p12 文件
配置签名
// package.json
{
"build": {
"win": {
"target": "nsis",
"certificateFile": "./cert.pfx",
"certificatePassword": "your_password",
"signingHashAlgorithms": ["sha256"],
"rfc3161TimeStampServer": "http://timestamp.digicert.com"
}
}
}
打包
# 安装 electron-builder
npm install electron-builder --save-dev
# 打包 Windows
npm run build:win
# 或
electron-builder --win --x64
# 输出位置
dist/MyApp Setup 1.0.0.exe
4.2 macOS 打包签名
申请开发者证书
1. 加入 Apple Developer Program ($99/年)
2. 创建证书
- Developer ID Application(分发用)
- Developer ID Installer(安装包用)
3. 下载证书并安装到钥匙串
配置签名
// package.json
{
"build": {
"mac": {
"category": "public.app-category.productivity",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"identity": "Developer ID Application: Your Name (XXXXXXXXXX)"
}
}
}
<!-- build/entitlements.mac.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
公证(Notarization)
# 打包
electron-builder --mac
# 公证
xcrun altool --notarize-app \
--primary-bundle-id "com.company.app" \
--username "your@email.com" \
--password "@keychain:AC_PASSWORD" \
--file dist/MyApp-1.0.0.dmg
# 查询公证状态
xcrun altool --notarization-info <RequestUUID> \
--username "your@email.com" \
--password "@keychain:AC_PASSWORD"
# 公证完成后,装订公证信息
xcrun stapler staple dist/MyApp-1.0.0.dmg
4.3 Linux 打包
# AppImage
electron-builder --linux AppImage
# deb
electron-builder --linux deb
# rpm
electron-builder --linux rpm
# 输出位置
dist/MyApp-1.0.0.AppImage
dist/myapp_1.0.0_amd64.deb
dist/myapp-1.0.0.x86_64.rpm
4.4 自动更新
配置 electron-updater
// main.js
const { app, autoUpdater } = require('electron')
const { autoUpdater } = require('electron-updater')
const log = require('electron-log')
// 配置日志
autoUpdater.logger = log
autoUpdater.logger.transports.file.level = 'info'
// 配置更新服务器
autoUpdater.setFeedURL({
provider: 'generic',
url: 'https://updates.example.com/releases'
})
// 检查更新
function checkForUpdates() {
autoUpdater.checkForUpdates()
}
// 监听事件
autoUpdater.on('checking-for-update', () => {
log.info('检查更新...')
})
autoUpdater.on('update-available', (info) => {
log.info('发现新版本:', info.version)
// 通知用户
mainWindow.webContents.send('update-available', info)
})
autoUpdater.on('update-not-available', () => {
log.info('当前已是最新版本')
})
autoUpdater.on('download-progress', (progress) => {
log.info(`下载进度: ${progress.percent}%`)
// 通知渲染进程
mainWindow.webContents.send('download-progress', progress)
})
autoUpdater.on('update-downloaded', (info) => {
log.info('下载完成')
// 提示用户重启
dialog.showMessageBox({
type: 'info',
title: '安装更新',
message: '更新已下载完成,是否立即重启安装?',
buttons: ['立即重启', '稍后']
}).then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
autoUpdater.on('error', (error) => {
log.error('更新错误:', error)
})
// App 启动时检查
app.on('ready', () => {
// 3 秒后检查更新
setTimeout(checkForUpdates, 3000)
})
渲染进程处理
// renderer.js
const { ipcRenderer } = require('electron')
// 监听更新通知
ipcRenderer.on('update-available', (event, info) => {
showUpdateNotification(`发现新版本 ${info.version}`)
})
ipcRenderer.on('download-progress', (event, progress) => {
updateProgressBar(progress.percent)
})
function showUpdateNotification(message) {
const notification = document.createElement('div')
notification.className = 'update-notification'
notification.textContent = message
document.body.appendChild(notification)
}
function updateProgressBar(percent) {
const progressBar = document.getElementById('progress-bar')
progressBar.style.width = `${percent}%`
}
发布更新
# 打包并发布
npm run build
# 上传到更新服务器
# 目录结构:
releases/
├── latest.yml # 版本信息(Windows)
├── latest-mac.yml # 版本信息(macOS)
├── latest-linux.yml # 版本信息(Linux)
├── MyApp-1.0.0.exe # Windows 安装包
├── MyApp-1.0.0.dmg # macOS 安装包
└── MyApp-1.0.0.AppImage # Linux 安装包
# latest.yml 示例
version: 1.0.1
files:
- url: MyApp-Setup-1.0.1.exe
sha512: abcd1234...
size: 50000000
path: MyApp-Setup-1.0.1.exe
sha512: abcd1234...
releaseDate: '2024-01-01T00:00:00.000Z'
4.5 应用商店上架
Microsoft Store(Windows)
1. 注册开发者账号
https://partner.microsoft.com/dashboard
费用: $19(个人),$99(企业)
2. 创建应用
- 应用名称
- 类别
- 定价
3. 准备提交包
npm install -g electron-windows-store
electron-windows-store \
--input-directory dist/win-unpacked \
--output-directory dist/appx \
--package-name "MyApp" \
--package-display-name "My App" \
--publisher-display-name "Your Company" \
--identity-name "YourCompany.MyApp"
4. 上传 appx 包
- 应用包
- 应用截图(至少 1 张)
- 应用描述
- 隐私政策
5. 提交审核
- 审核时间: 1-3 天
Mac App Store
1. 准备工作
- Apple Developer 账号
- Mac App Store 证书
- 沙盒配置
2. 配置沙盒
// build/entitlements.mas.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
3. 打包
electron-builder --mac mas
4. 上传 App Store Connect
- Transporter 工具
- 填写应用信息
- 提交审核
5. 审核周期
- 1-2 周
五、版本管理最佳实践
5.1 版本号规范
语义化版本:主版本号.次版本号.修订号
1.0.0 -> 1.0.1 修复 bug
1.0.1 -> 1.1.0 新增功能(向下兼容)
1.1.0 -> 2.0.0 重大更新(可能不兼容)
示例:
1.0.0 首次发布
1.0.1 修复登录 bug
1.1.0 新增分享功能
1.2.0 新增支付功能
2.0.0 重构架构,UI 改版
5.2 灰度发布策略
// 灰度配置示例
const grayReleaseConfig = {
// 是否启用灰度
enabled: true,
// 灰度比例(百分比)
percentage: 10,
// 灰度用户白名单(测试用户)
whitelist: [
'user_test_001',
'user_test_002'
],
// 灰度用户黑名单(问题用户)
blacklist: [
'user_problem_001'
],
// 灰度地区
regions: ['beijing', 'shanghai'],
// 灰度设备
devices: {
android: {
minVersion: '8.0',
brands: ['xiaomi', 'huawei']
},
ios: {
minVersion: '13.0'
}
},
// 灰度时间
schedule: {
start: '2024-01-01 00:00:00',
end: '2024-01-03 23:59:59'
}
}
5.3 更新策略建议
1. 强制更新(Force Update)
场景:
- 严重安全漏洞
- 接口不兼容
- 重大 bug 修复
策略:
- 启动时检查
- 无法跳过
- 立即下载安装
2. 推荐更新(Recommended Update)
场景:
- 新功能上线
- 性能优化
- 一般 bug 修复
策略:
- 启动时提示
- 可以跳过
- 下次启动再提醒
3. 静默更新(Silent Update)
场景:
- 小版本更新
- 资源文件更新
- 配置更新
策略:
- 后台下载
- 下次启动生效
- 不打扰用户
4. 热更新(Hot Update)
场景:
- 紧急 bug 修复
- 小功能调整
- H5 页面更新
策略:
- 启动时检查
- 后台下载
- 立即生效或重启生效
5.4 回滚方案
// 服务端支持版本回滚
router.post('/version/rollback', (req, res) => {
const { version } = req.body
// 回滚到指定版本
VersionConfig.update({
currentVersion: version,
rollbackTime: Date.now()
})
res.json({ success: true })
})
// 客户端检查到回滚
if (serverVersion < currentVersion) {
// 提示用户
showDialog('检测到版本回滚,建议重新安装应用')
// 或自动下载旧版本
downloadAndInstall(oldVersionUrl)
}
六、常见问题
Q1: iOS 审核被拒怎么办?
常见原因和解决方案
- 崩溃问题
- 彻底测试所有功能
- 使用 TestFlight 内测
- 修复所有崩溃后重新提交
- 隐私政策
- 元数据不一致
- 使用私有 API
- 检查第三方库
- 移除私有 API 调用
- 使用官方 API 替代
Q2: Android 渠道包怎么管理?
// android/app/build.gradle
android {
flavorDimensions "channel"
productFlavors {
huawei {
dimension "channel"
manifestPlaceholders = [CHANNEL: "huawei"]
}
xiaomi {
dimension "channel"
manifestPlaceholders = [CHANNEL: "xiaomi"]
}
oppo {
dimension "channel"
manifestPlaceholders = [CHANNEL: "oppo"]
}
}
}
// 打包不同渠道
./gradlew assembleHuaweiRelease
./gradlew assembleXiaomiRelease
./gradlew assembleOppoRelease
Q3: 热更新有什么限制?
uni-app wgt 包限制
- 只能更新前端资源(HTML/CSS/JS)
- 不能更新原生代码
- iOS 审核风险(不能改变核心功能)
React Native CodePush 限制
- 只能更新 JS bundle 和资源
- 不能更新原生模块
- 文件大小建议 < 10MB
Flutter 热更新
- 官方不支持热更新
- 需要第三方方案(如代码推送)
- 风险较高
Q4: 不同环境怎么配置?
// config.js
const ENV = {
dev: {
apiUrl: 'https://dev.api.com',
appKey: 'dev_key'
},
test: {
apiUrl: 'https://test.api.com',
appKey: 'test_key'
},
prod: {
apiUrl: 'https://api.com',
appKey: 'prod_key'
}
}
const currentEnv = process.env.NODE_ENV || 'prod'
export default ENV[currentEnv]
七、总结
移动端上架流程
1. 开发完成
2. 生成签名文件
3. 配置签名
4. 打包应用
5. 测试验证
6. 准备资料(截图、描述、隐私政策)
7. 提交应用商店
8. 等待审核
9. 审核通过,上架
10. 持续迭代更新
桌面端发布流程
1. 开发完成
2. 申请代码签名证书
3. 配置签名
4. 打包应用
5. 公证(macOS)
6. 配置自动更新
7. 上传更新服务器
8. 发布下载链接
9. 或提交应用商店
10. 用户自动更新
版本更新流程
1. 开发新版本
2. 内部测试
3. 灰度发布(10% -> 50% -> 100%)
4. 监控数据
5. 发现问题立即回滚
6. 修复后重新发布
7. 全量发布
8. 强制更新旧版本