原来不用发版也可以做到版本更新 uniapp
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
前端发版真的有那么麻烦吗?真的很麻烦:
如果是一个小 bug 却影响了大的功能,改了几行前端代码还能回退版本重新发版,却要完整走一遍应用商店发版流程,你自己想想... 以下只适合于 uniapp 的开发直接使用复制,整体的架构思想是结合现有的 uniapp 能力实现,如果这方向有兴趣,大家可以以后台可配置并开关控制前端显隐、兼容这种思维架构放在平时开发中,减少发版。
一、WGT 热更新是什么?WGT(Widget Package)是 uni-app 的 App 资源升级包,本质上打包的是
二、整体架构:后台存数据,冷启动做判断核心思路非常简单:
流程说明:
这套方案不依赖特定后台框架,数据库、Redis、JSON 配置文件、CMS 后台 都可以,只要接口能返回约定字段即可。 三、后台设计:数据库里放什么?3.1 推荐的数据表结构以 MySQL 为例,一张
也可以不用数据库,直接在后台管理系统里维护一个 JSON 配置,原理一样:
3.2 版本检查接口约定请求参数:
响应参数:
Node.js 示例(Express):
四、制作 WGT 包:HBuilderX 两步搞定4.1 修改版本号打开
4.2 发行 WGT 包HBuilderX 菜单:
生成完成后,控制台会输出 4.3 上传到 CDN / 服务器将 WGT 文件上传到可公网访问的 HTTPS 地址,例如:
然后把该 URL 写入数据库或后台配置,供接口返回。 五、客户端实现:冷启动检查 + 下载安装5.1 为什么必须用
|
| API | 读取来源 | WGT 更新后是否变化 |
|---|---|---|
plus.runtime.version |
原生 APK/IPA 版本 | ❌ 不变 |
plus.runtime.getProperty() |
manifest.json 资源版本 | ✅ 会变 |
结论:版本检测必须用 plus.runtime.getProperty,不能用 plus.runtime.version。
否则 WGT 更新成功后,客户端读到的仍是原生包版本,会 无限提示更新。
建议封装为独立模块,在 App.vue 的 onLaunch 中调用:
// utils/appUpdate.js
const CHECK_UPDATE_URL = 'https://api.example.com/api/app/check-update';
/**
* 获取当前 App 资源版本信息
*/
export function getCurrentVersion() {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
plus.runtime.getProperty(plus.runtime.appid, (info) => {
resolve({
name: info.name,
version: info.version,
versionCode: info.versionCode
});
}, reject);
// #endif
// #ifndef APP-PLUS
reject(new Error('非 App 环境'));
// #endif
});
}
/**
* 检查并执行更新
*/
export async function checkAppUpdate(options = {}) {
const { silent = false } = options;
try {
const current = await getCurrentVersion();
const res = await uni.request({
url: CHECK_UPDATE_URL,
method: 'GET',
data: {
name: current.name,
version: current.version,
platform: uni.getSystemInfoSync().platform
}
});
const data = res[1]?.data || res.data;
if (!data?.update) return;
// 整包更新(大版本 / 原生变更)
if (data.pkgUrl && !data.wgtUrl) {
handlePkgUpdate(data);
return;
}
// WGT 热更新
if (data.wgtUrl) {
if (data.forceUpdate) {
await downloadAndInstallWgt(data.wgtUrl);
} else {
const confirmed = await showUpdateDialog(data.description);
if (confirmed) {
await downloadAndInstallWgt(data.wgtUrl);
}
}
}
} catch (err) {
if (!silent) {
console.error('[AppUpdate] 检查更新失败', err);
}
}
}
/** 弹出更新提示 */
function showUpdateDialog(description) {
return new Promise((resolve) => {
uni.showModal({
title: '发现新版本',
content: description || '是否立即更新?',
confirmText: '立即更新',
cancelText: '稍后再说',
success: (res) => resolve(res.confirm)
});
});
}
/** 下载并安装 WGT */
function downloadAndInstallWgt(wgtUrl) {
return new Promise((resolve, reject) => {
uni.showLoading({ title: '下载更新中...', mask: true });
const downloadTask = uni.downloadFile({
url: wgtUrl,
success: (downloadResult) => {
if (downloadResult.statusCode !== 200) {
uni.hideLoading();
uni.showToast({ title: '下载失败', icon: 'none' });
return reject(new Error('下载失败'));
}
uni.showLoading({ title: '安装中...', mask: true });
plus.runtime.install(
downloadResult.tempFilePath,
{ force: true },
() => {
uni.hideLoading();
uni.showModal({
title: '更新完成',
content: '应用将重启以生效',
showCancel: false,
success: () => {
plus.runtime.restart();
resolve();
}
});
},
(err) => {
uni.hideLoading();
uni.showToast({
title: '安装失败: ' + (err.message || '未知错误'),
icon: 'none'
});
reject(err);
}
);
},
fail: (err) => {
uni.hideLoading();
uni.showToast({ title: '下载失败', icon: 'none' });
reject(err);
}
});
// 监听下载进度(可选)
downloadTask.onProgressUpdate((res) => {
uni.showLoading({
title: `下载中 ${res.progress}%`,
mask: true
});
});
});
}
/** 整包更新:跳转浏览器或应用市场 */
function handlePkgUpdate(data) {
uni.showModal({
title: '发现新版本',
content: data.description || '请下载安装新版本',
showCancel: !data.forceUpdate,
confirmText: '去下载',
success: (res) => {
if (res.confirm && data.pkgUrl) {
plus.runtime.openURL(data.pkgUrl);
}
}
});
}
在 App.vue 中调用:
// App.vue
import { checkAppUpdate } from '@/utils/appUpdate.js';
export default {
onLaunch() {
// 冷启动时静默检查更新
// #ifdef APP-PLUS
checkAppUpdate({ silent: true });
// #endif
}
};
官方文档:plus.runtime.install
plus.runtime.install(filePath, options, successCallback, errorCallback)
uni.downloadFile 下载到本地)plus.runtime.restart(),新资源才会生效以下场景 必须走整包更新:
遇到这些情况,接口应返回 pkgUrl 引导用户下载整包,而不是 wgtUrl。
#ifdef APP-PLUS 条件编译,避免在 H5 / 小程序环境调用 plus APImanifest.json 版本必须大于客户端当前版本plus.runtime.getProperty,不要用 plus.runtime.versionuni.hideLoading() 再 plus.runtime.restart(),避免 loading 残留如果使用 uniCloud,可以直接接入 DCloud 官方的 uni-upgrade-center,开箱支持:
官方文档:uni-upgrade-center
对于已有自建后端的团队,本文的「数据库 + 接口 + 客户端」方案更灵活;对于 uniCloud 项目,官方方案能省不少轮子。
| 步骤 | 操作 |
|---|---|
| 1 | 后台 / 数据库维护版本号、WGT 下载地址 |
| 2 | 提供版本检查接口,客户端冷启动时调用 |
| 3 | HBuilderX 修改版本号 → 制作 WGT 包 → 上传 CDN |
| 4 | 客户端 getProperty 读版本 → 下载 → install → restart |
原来不用发版也可以做到版本更新——这句话成立的前提是:改的是前端资源,不是原生能力。在这个边界内,WGT 热更新能显著缩短从开发到用户手中的路径,特别适合高频迭代的业务型 App。
阅读原文:点击这里