拓冰建站拓冰建站
首页 / 资讯中心 / 正文

uniapp 不用发版也可以做到版本更新

h5打开以查看主要是针对前端发版真的很麻烦改了一个 UI 样式或修复了一个小 Bug重新打包、提审、等审核安卓还好iOS 动辄等几天用户还不一定马上更新如果是一个小 bug 却影响了大的功能改了几行前端代码还能回退版本重新发版却要完整走一遍应用商店发版流程你自己想想...以下只适合于 uniapp 的开发直接使用复制整体的架构思想是结合现有的 uniapp 能力实现如果这方向有兴趣大家可以以后台可配置并开关控制前端显隐、兼容这种思维架构放在平时开发中减少发版。其实这个能力应该是很久之前就有了不过利用起来使用的还是相对较少的一部分是现在用于开发小程序的比较多小程序得审核所以用不到另一部分觉得发都发了直接发整版比较稳妥可能发版也不是很经常。很多人都不喜欢 uniapp觉得这不行那不行但是国内很多都在用。一、WGT 热更新是什么WGTWidget Package是 uni-app 的App 资源升级包本质上打包的是manifest.json、页面、JS、CSS、图片等前端资源不包含原生引擎和原生插件的变更。对比项WGT 热更新整包更新APK/IPA更新内容前端页面、样式、逻辑原生引擎、SDK、插件是否需要上架否是包体大小通常几 MB几十 MB 起用户感知下载后重启即可需重新安装适用场景Bug 修复、UI 调整、业务逻辑变更新增原生模块、升级 SDK二、整体架构后台存数据冷启动做判断核心思路非常简单┌─────────────┐ 冷启动请求 ┌──────────────┐ │ App 客户端 │ ────────────────▶ │ 版本检查接口 │ │ (uni-app) │ ◀──────────────── │ (后端 API) │ └─────────────┘ 返回版本信息 └──────┬───────┘ │ │ │ 有新版本 │ 读取 ▼ ▼ 下载 .wgt 包 ┌──────────────┐ │ │ 数据库 / 配置 │ ▼ │ version │ plus.runtime.install │ wgtUrl │ │ │ pkgUrl │ ▼ │ forceUpdate │ plus.runtime.restart └──────────────┘流程说明App冷启动App.vue的onLaunch时读取当前版本号调用后端接口传入当前版本后端比对数据库中的最新版本返回是否需要更新、WGT 下载地址等客户端下载 WGT → 安装 → 重启新版本生效这套方案不依赖特定后台框架数据库、Redis、JSON 配置文件、CMS 后台都可以只要接口能返回约定字段即可。三、后台设计数据库里放什么3.1 推荐的数据表结构以 MySQL 为例一张app_version表就够起步CREATE TABLE app_version ( id INT PRIMARY KEY AUTO_INCREMENT, app_id VARCHAR(64) NOT NULL COMMENT 应用标识如 __UNI__XXXXXX, platform VARCHAR(16) NOT NULL DEFAULT all COMMENT android / ios / all, version_name VARCHAR(32) NOT NULL COMMENT 版本名如 1.0.1, version_code INT NOT NULL COMMENT 版本号递增整数, wgt_url VARCHAR(512) DEFAULT NULL COMMENT WGT 包下载地址, pkg_url VARCHAR(512) DEFAULT NULL COMMENT 整包下载地址可选, update_type TINYINT NOT NULL DEFAULT 1 COMMENT 1热更新 2整包更新 3强制整包, force_update TINYINT NOT NULL DEFAULT 0 COMMENT 是否强制更新, update_log TEXT COMMENT 更新说明, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );也可以不用数据库直接在后台管理系统里维护一个 JSON 配置原理一样{ version: 1.0.2, versionCode: 102, update: true, wgtUrl: https://cdn.example.com/app/__UNI__XXXXXX.wgt, pkgUrl: , forceUpdate: false, description: 修复若干已知问题优化首页加载速度 }3.2 版本检查接口约定请求参数参数名类型说明nameString应用名称versionString客户端当前版本号platformString可选android / ios响应参数参数名类型说明updateBoolean是否有更新wgtUrlStringWGT 包下载地址pkgUrlString整包下载地址大版本升级时使用forceUpdateBoolean是否强制更新descriptionString更新说明Node.js 示例Expressrouter.get(/api/app/check-update, async (req, res) { const { name, version, platform android } req.query; // 从数据库查询最新启用版本 const latest await db.query( SELECT * FROM app_version WHERE app_id ? AND status 1 ORDER BY version_code DESC LIMIT 1, [name] ); if (!latest) { return res.json({ update: false }); } const hasUpdate compareVersion(version, latest.version_name) 0; res.json({ update: hasUpdate, wgtUrl: hasUpdate ? latest.wgt_url : , pkgUrl: latest.pkg_url || , forceUpdate: !!latest.force_update, description: latest.update_log || }); }); // 简单版本号比较1.0.1 vs 1.0.2 function compareVersion(v1, v2) { const a v1.split(.).map(Number); const b v2.split(.).map(Number); for (let i 0; i Math.max(a.length, b.length); i) { const diff (a[i] || 0) - (b[i] || 0); if (diff ! 0) return diff; } return 0; }提示版本比对逻辑可按业务自定义有的团队用versionCode整数比较更稳妥。四、制作 WGT 包HBuilderX 两步搞定4.1 修改版本号打开manifest.json递增版本信息应用版本名称versionName如1.0.1→1.0.2应用版本号versionCode如101→102⚠️关键新 WGT 包的版本号必须严格大于当前 App 已安装的版本否则安装会报错WGT安装包中manifest.json文件的version版本不匹配4.2 发行 WGT 包HBuilderX 菜单发行 → App-制作应用wgt包生成完成后控制台会输出.wgt文件路径文件名通常为{appid}.wgt例如__UNI__832D722.wgt。4.3 上传到 CDN / 服务器将 WGT 文件上传到可公网访问的 HTTPS 地址例如https://cdn.example.com/app/__UNI__832D722.wgt然后把该 URL 写入数据库或后台配置供接口返回。五、客户端实现冷启动检查 下载安装5.1 为什么必须用plus.runtime.getProperty这是最容易踩的坑之一API读取来源WGT 更新后是否变化plus.runtime.version原生 APK/IPA 版本❌ 不变plus.runtime.getProperty()manifest.json 资源版本✅ 会变结论版本检测必须用plus.runtime.getProperty不能用plus.runtime.version。否则 WGT 更新成功后客户端读到的仍是原生包版本会无限提示更新。5.2 示例这里没有放灰度、兜底回退等代码逻辑需要的可自行修改添加建议封装为独立模块在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 } };5.3 核心 API 说明官方文档plus.runtime.installplus.runtime.install(filePath, options, successCallback, errorCallback)filePathWGT 本地路径需先用uni.downloadFile下载到本地options.force是否强制安装版本不匹配时强制覆盖安装成功后必须调用plus.runtime.restart()新资源才会生效六、哪些情况不能只用 WGT以下场景必须走整包更新原生 SDK 变更如新增 Maps 模块、升级推送 SDK原生插件增改新增或修改 uni 原生插件App 原生引擎升级某些平台特殊限制如从非 nvue 工程新增 nvue 且使用非自定义组件编译模式遇到这些情况接口应返回pkgUrl引导用户下载整包而不是wgtUrl。七、注意事项7.1 开发调试真机运行期间读到的 appid、版本号是 HBuilder 基座信息必须打自定义基座或正式包才能正确测试热更新使用#ifdef APP-PLUS条件编译避免在 H5 / 小程序环境调用plusAPI7.2 版本管理WGT 包的manifest.json版本必须大于客户端当前版本版本检测用plus.runtime.getProperty不要用plus.runtime.version建议在后台保留历史版本记录方便回滚7.3 安全与合规WGT 下载地址强烈建议使用 HTTPS防止中间人篡改iOS 上架审核期间不要弹出热更新提示热更新内容需符合应用商店政策不要通过热更新绕过虚拟支付等规则参考说明热更新是否影响应用上架7.4 兼容性WGT 资源包与原生基座存在兼容关系大跨度升级建议在 manifest 中配置忽略不兼容提示或先充分测试详见wgt 与原生基座兼容性说明7.5 用户体验非强制更新建议弹窗让用户选择不要每次冷启动都静默强制更新下载过程展示进度避免用户以为 App 卡死安装成功后先uni.hideLoading()再plus.runtime.restart()避免 loading 残留八、官方升级中心更省心的选择如果使用 uniCloud可以直接接入 DCloud 官方的uni-upgrade-center开箱支持WGT 热更新 整包更新后台可视化管理版本多应用、多平台统一管理官方文档uni-upgrade-center对于已有自建后端的团队本文的「数据库 接口 客户端」方案更灵活对于 uniCloud 项目官方方案能省不少轮子。九、总结步骤操作1后台 / 数据库维护版本号、WGT 下载地址2提供版本检查接口客户端冷启动时调用3HBuilderX 修改版本号 → 制作 WGT 包 → 上传 CDN4客户端getProperty读版本 → 下载 →install→restart原来不用发版也可以做到版本更新——这句话成立的前提是改的是前端资源不是原生能力。在这个边界内WGT 热更新能显著缩短从开发到用户手中的路径特别适合高频迭代的业务型 App。h5打开以查看
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门