基于uni-app和Node.js實(shí)現(xiàn)app更新功能
業(yè)務(wù)背景
uniapp 打包 ios,android 之后,有時(shí)候緊急修復(fù)或修改 ui,還需要走應(yīng)用市場審核,往往審核時(shí)間就需要幾天,如果是有bug需要升級(jí)就會(huì)很著急,有熱更之后,可以避免應(yīng)用市場長時(shí)間審核,用戶很快就能收到更新。
整體思路:
要在uni-app中實(shí)現(xiàn)app更新功能,并使用Node.js作為后端服務(wù),可以按照以下思路和步驟進(jìn)行:
1、后端服務(wù)
- 使用Express創(chuàng)建一個(gè)簡單的Web服務(wù)器。
- 提供兩個(gè)API接口:
/checkForUpdate/:version用于檢查是否有新版本。/downloadApp/:version用于下載app。
2、uni-app前端
- 在頁面加載時(shí)調(diào)用checkForUpdate方法檢查是否有新版本。
- 如果有新版本,彈出提示框詢問用戶是否要更新。
- 如果用戶選擇更新,則下載新版本文件并下載安裝過程。
步驟一 創(chuàng)建Node.js后端服務(wù)
1、安裝必要依賴:
- 安裝
express或其他 Node.js web 框架來做后端服務(wù)。 - 安裝
cors用于處理跨域請(qǐng)求。
npm install express cors
2、創(chuàng)建一個(gè)簡單的后端服務(wù):
- 在項(xiàng)目根目錄下創(chuàng)建一個(gè)名為
public的文件夾,并在其中創(chuàng)建一個(gè)名為apps的文件夾用于存放要更新的App。 - 將app打包好的app命名為:
appx.x.x.wgtapp更新文件放到apps文件夾中。 - 在項(xiàng)目根目錄下創(chuàng)建一個(gè)名為
server.js的文件,并寫入以下代碼:
// app更新
const express = require('express'); // 導(dǎo)入 Express 模塊
const cors = require('cors'); // 導(dǎo)入 CORS 模塊,用于處理跨域請(qǐng)求
const fs = require('node:fs'); // node內(nèi)置模塊,用于文件系統(tǒng)操作。
const path = require('node:path');//node內(nèi)置模塊,用于處理文件路徑。
const app = express(); // 創(chuàng)建 Express 應(yīng)用實(shí)例。
app.use(cors()); // 使用 CORS 中間件解決跨越請(qǐng)求。
// 配置靜態(tài)文件服務(wù),使得/public路徑下的文件可以直接訪問,如果沒有請(qǐng)手動(dòng)創(chuàng)建。
app.use('/public', express.static(path.join(__dirname, 'public')));
// 存放app版本的文件夾,如果沒有請(qǐng)手動(dòng)創(chuàng)建。
const appDir = path.join(__dirname, 'public/apps');
// 服務(wù)器的地址 類似于:http://localhost:3000
let serverAddress = ''
/**
* 根據(jù)客戶端提供的版本號(hào)檢查是否有新版本。
*/
app.get('/checkForUpdate/:version', async (req, res) => {
// uniapp當(dāng)前版本號(hào)
const appCurrentVersion = req.params.version
// uniapp最新版本號(hào)
let appLatestVersion = ''
try {
// 讀取存放app目錄下的所有文件
const files = fs.readdirSync(appDir);
// 過濾出以app開頭的文件
const appFiles = files.filter(file => path.basename(file).startsWith('app'));
// 對(duì)文件列表進(jìn)行排序,按照版本號(hào)從小到大排序
const sortedFiles = appFiles.sort((a, b) => {
const aParts = a.split('.').map(Number);
const bParts = b.split('.').map(Number);
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
if (aParts[i] > bParts[i]) return 1;
if (aParts[i] < bParts[i]) return -1;
}
return 0;
});
// 數(shù)組中最后一項(xiàng)版本就是最大最新的版本
appLatestVersion = sortedFiles.pop()
// 再對(duì)當(dāng)前文件進(jìn)行處理 ,將 app1.0.3.wgt => '1.0.3'
appLatestVersion = appLatestVersion.replace(/^app/, '').replace(/\.wgt$/, '')
} catch (error) {
throw new Error('Error reading public directory:' + error)
}
// 如果請(qǐng)求的版本小于最新版本,則提供下載鏈接
if (appLatestVersion > appCurrentVersion) {
res.send({
version: appLatestVersion, // 當(dāng)前最新版本
url: `${serverAddress}/downloadApp/${appLatestVersion}`, // 更新下載地址
update: true, // 是否更新
mandatoryUpdate:true // 強(qiáng)制更新
})
} else {
res.send({
version: '',
url: '',
update: false,
mandatoryUpdate:false
})
}
})
/**
* 提供文件下載
*/
app.get('/downloadApp/:version', async (req, res) => {
// 要下載的 app 版本號(hào)
const version = req.params.version
const appName = `app${version}.wgt`
// app 存放路徑
const appFilePath = `${appDir}/${appName}`
// 檢查文件是否存在
fs.stat(appFilePath, (err, stats) => {
if (err) {
throw new Error(`未找到 app${version}版本下載地址`)
}
// 設(shè)置響應(yīng)頭
// 指示瀏覽器以下載的方式處理文件,并設(shè)置文件名。
res.setHeader('Content-Disposition', `attachment; filename=${appName}`);
// 表示文件類型未知或二進(jìn)制文件。
res.setHeader('Content-Type', 'application/octet-stream');
// 創(chuàng)建文件流
const fileStream = fs.createReadStream(appFilePath);
// 當(dāng)文件流結(jié)束時(shí),關(guān)閉響應(yīng)
fileStream.on('end', () => {
console.log('File download completed.');
});
// 如果發(fā)生錯(cuò)誤,處理錯(cuò)誤
fileStream.on('error', (error) => {
throw new Error('Error downloading the file.:' + error)
});
// 將文件流管道發(fā)送到客戶端
fileStream.pipe(res);
});
})
const port = 3000; // 設(shè)置應(yīng)用監(jiān)聽的端口號(hào)
// 啟動(dòng)服務(wù)器并監(jiān)聽端口
const server = app.listen(port, () => {
// 獲取服務(wù)器綁定的地址信息
const addressInfo = server.address();
const host = addressInfo.address === '::' ? 'localhost' : addressInfo.address;
const port = addressInfo.port;
serverAddress = `http://${host}:${port}`
console.log(`Server is running at http://${host}:${port}`);
});
3. 啟動(dòng)后端服務(wù)
打開終端,進(jìn)入到項(xiàng)目根目錄,執(zhí)行以下命令:
node server.js
步驟二 創(chuàng)建uni-app前端應(yīng)用
1、創(chuàng)建uni-app項(xiàng)目
打開HBuilderX 選擇菜單欄上的 [文件] -> [新建] -> [項(xiàng)目] 創(chuàng)建一個(gè)新的uni-app項(xiàng)目。

2、實(shí)現(xiàn)檢查更新邏輯
打開項(xiàng)目根目錄下的pages/index/index.vue文件,新增checkForUpdate方法,并在onLoad生命周期中調(diào)用該方法。
<template>
<text class="title" style="text-align: center;">
當(dāng)前app資源版本為:{{appWgtVersion}}
</text>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const appWgtVersion = ref('')
// 在頁面加載時(shí)調(diào)用checkForUpdate方法檢查是否有新版本。
onLoad(() => {
checkForUpdate()
})
/**
* 檢查是否需要更新app
*/
function checkForUpdate() {
// 只在 app 中才會(huì)執(zhí)行以下代碼
// #ifdef APP-PLUS
// 獲取手機(jī)系統(tǒng)信息
const systemInfo = uni.getSystemInfoSync()
// 獲取到 app 資源包版本
appWgtVersion.value = systemInfo.appWgtVersion
// 向 Node.js 后端發(fā)送請(qǐng)求檢查是否需要更新
uni.request({
url: 'http://192.168.43.245:3000/checkForUpdate/' + appWgtVersion.value,
success: (res) => {
console.log('request-res', res);
if (res.data && res.data.update) {
uni.showModal({
title: '新版本發(fā)布',
content: '檢查到當(dāng)前有新版本,需要更新嗎?',
showCancel: true,
confirmText: '立即更新',
cancelText: '暫不更新',
// 接口調(diào)用成功
success: (modalRes) => {
if (modalRes.confirm) {
// 立即更新app操作
uni.showLoading({
title: '正在下載'
})
console.log('res.data.url',res.data.url);
// 開始下載任務(wù)
const downloadTask = uni.downloadFile({
url: res.data.url,
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
uni.showLoading({
title: '正在安裝更新...'
});
plus.runtime.install(downloadRes.tempFilePath, {
force: true
}, () => {
console.log('install success...');
uni.hideLoading()
plus.runtime.restart();
}, (e) => {
console.log('install fail...', e);
uni.hideLoading()
uni.showToast({
title: '安裝失敗:' + JSON.stringify(e),
icon: 'fail',
duration: 1500
});
});
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '安裝成功!',
icon: 'none'
});
}, 3000);
}
},
// 接口調(diào)用失敗
fail: (fail) => {
console.log('網(wǎng)絡(luò)錯(cuò)誤,下載失敗!', fail);
uni.hideLoading();
},
// 接口調(diào)用結(jié)束
complete: () => {
console.log('----------------Complete----------------:', downloadTask)
downloadTask.offProgressUpdate(); //取消監(jiān)聽加載進(jìn)度
}
});
//監(jiān)聽下載進(jìn)度
downloadTask.onProgressUpdate(res => {
// console.log('下載進(jìn)度百分比:' + res.progress); // 下載進(jìn)度百分比
// console.log('已經(jīng)下載的數(shù)據(jù)長度:' + res.totalBytesWritten); // 已經(jīng)下載的數(shù)據(jù)長度,單位 Bytes
// console.log('預(yù)期需要下載的數(shù)據(jù)總長度:' + res.totalBytesExpectedToWrite); // 預(yù)期需要下載的數(shù)據(jù)總長度,單位 Bytes
});
} else {
// 暫不更新app操作
// 如果是你的發(fā)布需要強(qiáng)制更新的話,不更新app可以直接退出 APP 不讓使用
if(res.data.mandatoryUpdate){
if (systemInfo.platform === 'android') {
// 安卓退出app
plus.runtime.quit();
} else {
// 判斷為ios的手機(jī),退出App
plus.ios.import("UIApplication").sharedApplication().performSelector("exit");
}
}
}
}
});
}
},
fail: (fail) => {
console.log('檢查更新請(qǐng)求失敗!', fail);
}
});
// #endif
}
</script>
3、制作應(yīng)用wgt包
1、打開項(xiàng)目根目錄下的manifest.json配置文件,在基礎(chǔ)設(shè)置中將應(yīng)用版本名稱設(shè)置為1.0.2。

2、選擇菜單欄上的 [發(fā)行] -> [原生App-制作應(yīng)用wgt包]

3、將打包好的wgt包更名為app1.0.2.wgt。
后端是按照這個(gè)命名規(guī)范來進(jìn)行升級(jí)的,所以我們按照這個(gè)規(guī)范來。

4、將打包好的app1.0.2.wgt包放在后端服務(wù)器的/public/apps文件夾中。
4、測試app更新功能
1、打開項(xiàng)目根目錄下的manifest.json配置文件,在基礎(chǔ)設(shè)置中將應(yīng)用版本名稱設(shè)置為1.0.0,只要低于服務(wù)器中的版本即可。
2、運(yùn)行app到手機(jī)
運(yùn)行到手機(jī)后,頁面會(huì)彈出更新提示框
點(diǎn)擊“立即更新”按鈕
app會(huì)自動(dòng)下載并安裝更新,安裝更新后的app后,會(huì)自動(dòng)啟動(dòng)并運(yùn)行。

點(diǎn)擊“稍后更新”按鈕
在App非強(qiáng)制更新的情況下則關(guān)閉更新提示框。

點(diǎn)擊“稍后更新”按鈕
在App強(qiáng)制更新的情況下則退出App。

注意事項(xiàng)
- 確保Node.js后端服務(wù)和uni-app前端應(yīng)用在同一網(wǎng)絡(luò)環(huán)境中運(yùn)行。
- 測試時(shí),請(qǐng)確保文件路徑和URL正確無誤。
以上步驟提供了一個(gè)基本的uni-app和Node.js實(shí)現(xiàn)app更新功能的示例。你可以根據(jù)具體需求進(jìn)行調(diào)整和擴(kuò)展。
以上就是基于uni-app和Node.js實(shí)現(xiàn)app更新功能的詳細(xì)內(nèi)容,更多關(guān)于uni-app和Node.js app更新的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
NodeJs實(shí)現(xiàn)跨域WebSocket即時(shí)通訊加強(qiáng)版
這篇文章主要為大家介紹了NodeJs實(shí)現(xiàn)跨域WebSocket即時(shí)通訊加強(qiáng)版示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
iPhone手機(jī)上搭建nodejs服務(wù)器步驟方法
這篇文章主要介紹了iPhone手機(jī)上搭建nodejs服務(wù)器步驟方法,本文給出了詳細(xì)的操作步驟以及操作命令,需要的朋友可以參考下2015-07-07
node.js 使用 net 模塊模擬 websocket 握手進(jìn)行數(shù)據(jù)傳遞操作示例
這篇文章主要介紹了node.js 使用 net 模塊模擬 websocket 握手進(jìn)行數(shù)據(jù)傳遞操作,結(jié)合實(shí)例形式分析了node.js基于net模塊模擬 websocket握手相關(guān)原理及進(jìn)行數(shù)據(jù)傳遞具體操作技巧,需要的朋友可以參考下2020-02-02
詳解在Node.js中發(fā)起HTTP請(qǐng)求的5種方法
這篇文章主要介紹了詳解在Node.js中發(fā)起HTTP請(qǐng)求的5種方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01

