开发完成只是半程,把 uni-app 项目发布到微信、H5、App 三个渠道,每条路都有独立的流程和坑:证书管理、各平台审核规则、App 热更新。本文讲透发布链路的完整实操。
一、发布全景
| 渠道 | 产物 | 发布方式 | 更新机制 |
|---|---|---|---|
| 微信小程序 | wxapkg | 微信后台上传 + 提审 | 冷启动异步更新 |
| H5 | 静态文件 | 部署到服务器/CDN | 刷新即最新 |
| App(Android) | apk / aab | 应用市场/官网分发 | 整包更新 + wgt 热更新 |
| App(iOS) | ipa | App Store + TestFlight | 整包更新 + wgt(受限) |
二、微信小程序发布
发布流程
# 1. HBuilderX 发行 → 小程序-微信,生成编译产物
# 产物在 unpackage/dist/build/mp-weixin
# 2. 用微信开发者工具打开该目录
# 3. 工具栏「上传」→ 填版本号和备注 → 上传为开发版本
# 4. 微信公众平台 → 版本管理 → 提交审核 → 审核通过 → 全量发布也可以用 miniprogram-ci 做命令行自动化上传:
// scripts/upload-mp.js
const ci = require('miniprogram-ci')
async function upload() {
const project = new ci.Project({
appid: 'wx_xxxxxxxx',
type: 'miniProgram',
projectPath: './unpackage/dist/build/mp-weixin',
privateKeyPath: './private.key' // 微信后台下载的上传密钥
})
await ci.upload({
project,
version: require('../package.json').version,
desc: 'release: ' + new Date().toLocaleString(),
setting: { es6: true, minify: true }
})
}配到 package.json scripts 里,CI/CD 流水线一键发布。
审核避坑清单
微信审核被拒的高频原因,发布前自查:
- 类目与功能不符:有交易功能但类目没选电商;有社区内容但没选社交类目
- 诱导分享:分享得奖励、强制分享解锁功能的文案全部要清掉
- 测试账号:提审备注里必须给一个可登录的测试账号(有登录功能时)
- 隐私接口声明:用了 getLocation、chooseAddress、相机等隐私接口,必须在
app.json的requiredPrivateInfos声明并申请权限 - 虚拟支付:iOS 端虚拟商品(会员、课程)不能直接用微信支付,要走 IAP 或隐藏入口
- 完整可体验:每个 tab 每个入口都要能点,"敬请期待"的空页面是高频被拒项
小程序的静默更新机制
小程序新版本发布后,已打开的用户用的仍是旧版,下次冷启动时检查更新。强制立即更新的方案:
// App.vue
const updateManager = uni.getUpdateManager()
updateManager.onCheckForUpdate((res) => {
console.log('是否有新版本:', res.hasUpdate)
})
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已准备好,是否重启应用?',
success(res) {
if (res.confirm) {
updateManager.applyUpdate() // 立即应用新版本并重启
}
}
})
})
updateManager.onUpdateFailed(() => {
uni.showToast({ title: '更新失败,请删除小程序重新打开', icon: 'none' })
})三、H5 发布
构建与部署
# HBuilderX 发行 → 网站-H5,或 CLI
npx uni build
# 产物 unpackage/dist/build/h5部署要点:
# Nginx 配置:history 路由需要 fallback
server {
listen 443 ssl;
server_name h5.example.com;
root /var/www/h5;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源长缓存(文件名带 hash)
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}跨域处理:H5 调后端接口要么后端开 CORS,要么 Nginx 加反向代理把 /api 转发到服务端,生产环境强烈推荐代理方案,前端请求同源路径,省掉 CORS 一切麻烦。
微信 H5 特殊处理
在微信内打开的 H5 要做微信 JS-SDK 授权(自定义分享卡片、微信支付):
- 公众号后台配 JS 接口安全域名
- 后端实现签名接口(access_token → jsapi_ticket → signature)
- 前端 wx.config 注入配置后才能调分享等 API
四、App 打包:云打包与证书
证书体系认知
Android:
- 正式签名证书(keystore):自己用 keytool 生成,务必自己保管,丢了就无法给老用户覆盖升级
- 云打包时可以用 DCloud 公共测试证书(仅测试),正式包必须用自己的证书
# 生成 Android 证书
keytool -genkey -alias myapp -keyalg RSA -keysize 2048 -validity 36500 \
-keystore myapp.keystoreiOS:
- 需要 Apple 开发者账号(个人版 $99/年)
- 证书(p12)+ 描述文件(mobileprovision)+ Bundle ID 三件套
- 发布 App Store 需要 distribution 证书;真机调试需要 development 证书
云打包实操
HBuilderX → 发行 → 原生App-云打包
→ 选择平台(Android/iOS)
→ 填证书信息(Android: keystore + 密码 + 别名)
→ 勾选原生插件(推送、地图、支付等模块)
→ 打包云打包排队可能要几分钟到半小时。打完的产物:
- Android:apk 直接可装;上架 Google Play 需要转 aab
- iOS:ipa 需要 TestFlight 或 XCode 上传(Windows 用户的经典痛点,需要 Mac 或云打包的"越狱包/测试包"过渡)
Android 各应用市场分发
国内市场(华为、小米、OPPO、vivo、应用宝)每个都要单独注册开发者、上传软著或承诺函、单独提审。实际操作建议:
- 官网直接放 apk 下载(最快触达存量用户)
- 主流市场覆盖上架(获取新用户的主要渠道)
- 各市场对隐私政策、权限用途说明的审查口径不同,逐个过
隐私合规(上架必过项)
国内 App 上架的硬性要求:
- 首次启动弹《隐私政策》,用户同意前不得初始化任何采集 SDK(uni-app 里注意 App.vue onLaunch 的时机,把需要合规前置的 SDK init 放到同意回调之后)
- 申请权限(定位、相册、相机)必须先弹用途说明
- 在应用市场后台填写隐私采集清单
五、wgt 热更新:不发整包的升级
App 发版要走市场审核,周期长。纯前端代码(页面、JS、样式)的修改可以打 wgt 包热更新,只有原生层变更(新增原生插件、修改 SDK 配置)才必须整包升级。
服务端版本接口
// GET /app/version?platform=android&version=1.2.0
{
"code": 0,
"data": {
"forceUpdate": false,
"wgtUrl": "https://cdn.example.com/app/1.2.1.wgt",
"apkUrl": "https://cdn.example.com/app/app-1.3.0.apk",
"updateLog": "1. 修复已知问题\n2. 优化性能"
}
}wgtUrl 有值走热更,只有 apkUrl 走整包(判断逻辑:版本号第一位变化 → 整包,否则 wgt)。
客户端热更新实现
// App.vue
export default {
onLaunch() {
this.checkUpdate()
},
methods: {
async checkUpdate() {
// #ifdef APP-PLUS
const { version, platform } = await this.getAppInfo()
try {
const res = await request.get('/app/version', {
platform: platform === 'android' ? 'android' : 'ios',
version
})
if (this.compareVersion(res.wgtVersion, version) > 0) {
this.doWgtUpdate(res)
} else if (this.compareVersion(res.version, version) > 0) {
this.doApkUpdate(res) // 大版本整包
}
} catch (e) {
// 静默失败,不影响启动
}
// #endif
},
getAppInfo() {
return new Promise((resolve) => {
plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) => {
resolve({
version: widgetInfo.version, // wgt 版本
platform: uni.getSystemInfoSync().platform
})
})
})
},
compareVersion(v1, v2) {
const a = v1.split('.').map(Number)
const b = v2.split('.').map(Number)
for (let i = 0; i < 3; i++) {
if (a[i] > b[i]) return 1
if (a[i] < b[i]) return -1
}
return 0
},
doWgtUpdate({ wgtUrl, updateLog, forceUpdate }) {
uni.showModal({
title: `发现新版本`,
content: updateLog,
showCancel: !forceUpdate,
success: (res) => {
if (!res.confirm) return
uni.showLoading({ title: '更新中...' })
uni.downloadFile({
url: wgtUrl,
success: (downloadRes) => {
if (downloadRes.statusCode !== 200) return
plus.runtime.install(
downloadRes.tempFilePath,
{ force: false },
() => {
uni.hideLoading()
uni.showModal({
title: '更新完成',
content: '重启应用后生效',
showCancel: false,
success: () => plus.runtime.restart()
})
},
(e) => {
uni.hideLoading()
uni.showToast({ title: '安装失败', icon: 'none' })
}
)
},
fail: () => {
uni.hideLoading()
uni.showToast({ title: '下载失败', icon: 'none' })
}
})
}
})
}
}
}wgt 包的生成与发布
HBuilderX → 发行 → 原生App-制作应用wgt包生成 .wgt 文件上传到 CDN,更新服务端版本接口的 wgtVersion 字段即可。
wgt 热更的纪律:
- 原生层改动绝不发 wgt:新增了原生插件、改了 manifest 的 SDK 配置,wgt 升级后必崩
- wgt 版本号独立递增:和应用整包版本分开管理,
plus.runtime.getProperty拿到的是 wgt 版本 - iOS 慎用:Apple 对热更新口径收紧,wgt 仅限 JS 资源修复,且不保证过审安全;能走 App Store 就走 App Store
六、版本管理建议
package.json的 version 作为唯一真实源,小程序、App 版本号统一从这读取- 云打包产物按
app-android-v1.2.1.apk命名归档到对象存储,出问题可回滚 - 服务端保留接口灰度能力:
wgtUrl按用户 ID 取模灰度放量,热更出问题影响面可控 - 小程序保留"版本回退"操作权限:微信后台可在 24 小时内回退到上一版
总结
- 小程序发布链路:上传 → 提审 → 全量,miniprogram-ci 可自动化;静默更新用 UpdateManager 主动提示
- Android 证书自己生成自己保管,丢了等于丢掉全部老用户;iOS 三件套缺一不可
- 合规是上架硬门槛:隐私弹窗前不初始化任何采集 SDK
- wgt 热更新只适用于前端层变更,原生变更必须整包
- 版本号统一管理 + CDN 归档 + 灰度放量,是热更安全的最后防线
发布链路琐碎且各平台规则持续变动,本文流程可作为 checklist,每次发版对照走一遍。
评论 (0)