增加 启动后延迟更新开关.

使用此开关后,游戏包将在启动进入游戏后才请求更新信息, 直到重启后再打开游戏才应用更新.

优化热更流程,可以在构建完成后,再领奖注入热更,上传更新.
This commit is contained in:
andrewlu 2021-02-04 16:12:32 +08:00
parent ef915a121e
commit b81d004641
8 changed files with 141 additions and 204 deletions

View File

@ -112,6 +112,9 @@ class FileUploader {
this.queue.push({src: i, dst: (remoteStr + i.substring(startIndex)).replace(/\\/g, '/')});
}
}
logger.info('待上传文件数量:', this.queue.length);
this.checkUpload();
}

View File

@ -34,21 +34,22 @@ class FileUtil {
* @param src
* @param dst
*/
async copy(src, dst) {
copy(src, dst) {
const st = fs.statSync(src);
if (st.isFile()) {
const readable = fs.createReadStream(src);//创建读取流
const writable = fs.createWriteStream(dst);//创建写入流
readable.pipe(writable);
const data = fs.readFileSync(src);
fs.writeFileSync(dst, data);
return;
}
if (!fs.existsSync(dst)) {
fs.mkdirSync(dst, {recursive: true});
}
//读取目录
const paths = fs.readdirSync(src);
for (let path of paths) {
this.copy(src + '/' + path, dst + '/' + path);
const files = fs.readdirSync(src);
for (let p of files) {
this.copy(src + path.sep + p, dst + path.sep + p);
}
};

View File

@ -1,129 +0,0 @@
/**
* 方便直接获取当前版本号等信息.
* 使用方式:
* // @ts-ignore
* import UpdateManager = require( "UpdateManager");
* 然后在代码中直接使用即可.
*/
class UpdateManager {
cur_ver_code = 0;
cur_ver_name = "1.0.1";
cur_ver_desc = "";
ver_type = "dev";
new_ver_code = 0;
new_ver_name = "1.0.1";
new_ver_desc = "";
new_ver_info = {};
// 记录当前版本的bundles. 如果有新版本, 则自动下载新bundles.
cur_bundles = {};
updateSuccess = "0";
constructor() {
const curVer = window.localStorage.getItem("cur_ver_info");
if (curVer) {
const verInfo = JSON.parse(curVer);
this.new_ver_code = this.cur_ver_code = verInfo.versionCode;
this.new_ver_name = this.cur_ver_name = verInfo.versionName;
this.ver_type = verInfo.versionType;
this.new_ver_desc = this.cur_ver_desc = verInfo.versionLog;
this.new_ver_info = verInfo;
this.cur_bundles = verInfo.bundleVers;
}
this.updateSuccess = window.localStorage.getItem("ver_updated") || 0;
this.fetchNewVerInfo();
}
// 此方法适用于非强制更新包. 需要设计信息界面,显示版本信息.
// 手动调用进行更新下载, 下载完成返回true, 此时需要重启游戏才能应用更新. 需要界面提示用户.
doUpdate() {
if (this.hasNewVersion()) {
window.localStorage.setItem('cur_ver_info', JSON.stringify(this.new_ver_info));
return true;
}
return false;
}
resetUpdateFlag() {
window.localStorage.setItem('ver_updated', '0');
}
getUpdateFlag() {
return this.updateSuccess == 1;
}
// 检测是否有新版本.
hasNewVersion() {
return this.new_ver_info && this.new_ver_info.versionCode && this.new_ver_info.versionCode != this.cur_ver_code;
}
fetchNewVerInfo() {
let url = window.updateUrl || false;
if (!url) {
return;
}
// 游戏启动后再请求更新,避免影响启动速度.
url += `?_t=${Math.random()}`;
cc.log("请求更新地址:", url);
this._get(url).then(version => {
if (!version) {
console.log("未检测到更新版本内容");
return;
}
if (version) {
cc.log('当前版本号:', this.cur_ver_code, this.cur_ver_name);
cc.log('请求更新内容:', version);
let verInfo = version;
if (typeof version === 'string') {
verInfo = JSON.parse(version);
}
this.new_ver_code = verInfo.versionCode;
this.new_ver_name = verInfo.versionName;
this.new_ver_desc = verInfo.versionLog;
this.new_ver_info = verInfo;
// 如果首次运行,本地未缓存版本信息,则进行缓存.
if (!this.cur_ver_code) {
cc.log("当前首次运行,记录版本信息");
window.localStorage.setItem('cur_ver_info', JSON.stringify(this.new_ver_info));
this.new_ver_code = this.cur_ver_code = verInfo.versionCode;
this.new_ver_name = this.cur_ver_name = verInfo.versionName;
this.ver_type = verInfo.versionType;
this.new_ver_desc = this.cur_ver_desc = verInfo.versionLog;
} else if (this.hasNewVersion() && verInfo.forceUpdate) {
window.localStorage.setItem('cur_ver_info', JSON.stringify(this.new_ver_info));
window.localStorage.setItem('ver_updated', '1');
cc.log("有新版本,且默认强制更新");
}
}
});
}
// ajax 请求.
_get(url) {
return new Promise(resolve => {
const ajax = new XMLHttpRequest();
ajax.open("get", url, true);
ajax.setRequestHeader("Content-Type", "application/json;charset=utf-8");
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
if (ajax.status == 200) {
resolve(ajax.responseText);
} else {
resolve(null);
}
}
}
ajax.send(null);
});
}
}
module.exports = new UpdateManager();

View File

@ -1,9 +0,0 @@
{
"ver": "1.0.8",
"uuid": "aa7483f4-ac0c-4809-9c5e-4e5734a26db6",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -1,35 +1,20 @@
'use strict';
const fs = require('fs');
const {join} = require('path');
const path = require('path');
const FileUtil = require('./js/FileUtils');
const FileUpload = require('./js/FileUploader');
const logger = require('./js/logger');
// 生成更新文件.
// 编译完成仅保存每次生成的bundleVers. 之后注入热更阶段再合并.
const doMakeUpdatePackage = function (opt, cb) {
if (opt.platform == 'android' || opt.platform == 'ios') {
try {
const configStr = fs.readFileSync(
Editor.url('packages://update-manager/.settings.conf'), 'utf-8');
const configJson = JSON.parse(configStr || "{}");
if (typeof configJson.versionCfg.versionCode === 'undefined') {
logger.warn("未配置更新版本,不生成热更Patch");
cb && cb();
return;
}
if (!configJson.versionCfg.versionType || configJson.versionCfg.versionType.length <= 0) {
configJson.versionCfg.versionType = 'dev';
}
const bundleVers = {};
for (let b of opt.bundles) {
bundleVers[b.name] = b.version;
}
saveNewVersionInfo(opt, bundleVers, configJson);
const remoteDir = join(opt.dest, 'remote');
const assetDir = join(opt.dest, 'assets');
FileUtil.copy(assetDir, remoteDir);
let targetUpdatePath = path.join(opt.dest, `remote/bundleVers.json`);
FileUtil.write(targetUpdatePath, JSON.stringify(bundleVers));
cb && cb();
} catch (e) {
@ -41,20 +26,44 @@ const doMakeUpdatePackage = function (opt, cb) {
}
};
const saveNewVersionInfo = function (opt, bundleVers, configJson) {
const getConfigJson = function () {
const configStr = fs.readFileSync(
Editor.url('packages://update-manager/.settings.conf'), 'utf-8');
const configJson = JSON.parse(configStr || "{}");
if (typeof configJson.versionCfg.versionCode === 'undefined') {
logger.warn("未配置更新版本,不生成热更Patch");
return null;
}
if (!configJson.versionCfg.versionType || configJson.versionCfg.versionType.length <= 0) {
configJson.versionCfg.versionType = 'dev';
}
return configJson;
};
// 注入热更信息.
const updateHotFixInfo = function () {
const configJson = getConfigJson();
if (!configJson) return;
const bundleVers = path.join(Editor.Project.path, `build/jsb-link/remote/bundleVers.json`);
if (!fs.existsSync(bundleVers)) {
logger.warn('未找到编译信息,请先构建项目.');
return;
}
const bundles = JSON.parse(fs.readFileSync(bundleVers, 'utf-8'));
const data = {
versionCode: configJson.versionCfg.versionCode,
versionName: configJson.versionCfg.versionName,
versionType: configJson.versionCfg.versionType,
versionLog: configJson.versionCfg.versionLog,
forceUpdate: configJson.versionCfg.forceUpdate,
server: `${configJson.versionCfg.baseUrl}${configJson.ftpCfg.rootPath}${configJson.versionCfg.versionType}/${configJson.versionCfg.versionCode}`
server: `${configJson.versionCfg.baseUrl}${configJson.ftpCfg.rootPath}${configJson.versionCfg.versionType}/${configJson.versionCfg.versionCode}`,
bundles: bundles
};
const updateInfo = Object.assign(data, {bundles: bundleVers});
// 将版本更新信息写入文件
let targetUpdatePath = join(opt.dest, `remote/update-${configJson.versionCfg.versionType}.json`)
FileUtil.write(targetUpdatePath, JSON.stringify(updateInfo, null, 2));
logger.log("更新信息存储位置:", targetUpdatePath);
let targetUpdatePath = path.join(Editor.Project.path, `build/jsb-link/remote/update-${configJson.versionCfg.versionType}.json`);
FileUtil.write(targetUpdatePath, JSON.stringify(data, null, 2));
};
module.exports = {
@ -94,8 +103,17 @@ module.exports = {
'open'() {
Editor.Panel.open('update-manager');
},
'saveConfig'() {
// 上传更新包前,更新一次热更描述文件.
logger.info('生成热更文件...');
updateHotFixInfo();
logger.info('准备remote 资源...');
const remoteDir = path.join(Editor.Project.path, 'build/jsb-link/remote'); // remote 目录.
const assetDir = path.join(Editor.Project.path, 'build/jsb-link/assets'); // assets目录.
FileUtil.copy(assetDir, remoteDir);
logger.info('remote 资源准备完毕,可以开始上传...');
},
'upload'(event, dir, dst, options) {
logger.info('upload....');
this.ftp.setOption(options, options.maxThread);
this.ftp.upload(dir, dst);
},

View File

@ -9,15 +9,6 @@
"message": "update-manager:open"
}
},
"runtime-resource": {
"path": "./lib",
"name": "lib"
},
"reload": {
"ignore": [
"lib/**/*"
]
},
"panel": {
"main": "panel/index.html",
"type": "simple",

View File

@ -35,6 +35,13 @@
active-color="#13ce66" inactive-color="#ff4949"
style="margin-right: 20px;"></el-switch>
</el-tooltip>
<el-tooltip placement="top">
<div slot="content">开启时后续更新包将在游戏启动后静默请求更新<br/>否则将在启动时立即请求更新,然后才进入游戏.</div>
<el-switch v-model="versionCfg.updateBefore"
:active-text="versionCfg.updateBefore?'更新完进游戏':'进游戏后请求更新'"
active-color="#13ce66" inactive-color="#ff4949"
style="margin-right: 20px;"></el-switch>
</el-tooltip>
</el-form-item>
<el-tooltip effect="dark" :content="tip.baseUrl" open-delay=500 placement="top">
<el-form-item label="热更地址:">
@ -168,7 +175,7 @@
<el-row style="margin-top: 20px;">
<el-col :span="8">
<el-button type="success" @click="onSave">保存配置信息</el-button>
<el-button type="success" @click="onSave">注入热更配置</el-button>
</el-col>
<el-col :span="8">
<el-button type="danger" @click="onUpload">上传更新包</el-button>
@ -201,7 +208,7 @@
versionLog: "",
baseUrl: "",
forceUpdate: true, // 强制静默更新.
// updateMoment: true // 启动时直接请求更新.
updateBefore: true // 启动时直接请求更新.
},
ftpCfg: {
host: "127.0.0.1",
@ -268,9 +275,21 @@
this.ftpCfg.rootPath += "/";
}
const data = Object.assign({}, {versionCfg: this.versionCfg, ftpCfg: this.ftpCfg});
fs && fs.writeFileSync && fs.writeFileSync(
fs.writeFileSync(
Editor.url('packages://update-manager/.settings.conf'), JSON.stringify(data, null, 2), 'utf-8');
this.$message({message: "配置保存成功!", type: "success"});
Editor.Ipc.sendToMain('update-manager:saveConfig');
let filePath = `update-${this.versionCfg.versionType}.json`;
const uploadDstPath = `${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${filePath}`;
let updateUrl = `${this.versionCfg.baseUrl}${uploadDstPath}`;
const serverUrl = `${this.versionCfg.baseUrl}${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${this.versionCfg.versionCode}`;
if (this.notifyMainJs(updateUrl, serverUrl, this.versionCfg.updateBefore)) {
this.$message.info('热更注入成功');
} else {
this.$message.error('热更注入失败,请检查构建目录.');
}
},
onUpload() {
if (this.uploadState) {
@ -280,28 +299,17 @@
}
this.uploadState = true;
logger.info("准备上传更新包");
this.$message("准备上传更新包");
if (!this.versionCfg.baseUrl.endsWith('/') && this.versionCfg.baseUrl.length > 0) {
this.versionCfg.baseUrl += "/";
}
if (!this.ftpCfg.rootPath.endsWith('/') && this.ftpCfg.rootPath.length > 0) {
this.ftpCfg.rootPath += "/";
}
let filePath = `update-${this.versionCfg.versionType}.json`;
const uploadDstPath = `${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${filePath}`;
let updateUrl = `${this.versionCfg.baseUrl}${uploadDstPath}`;
const serverUrl = `${this.versionCfg.baseUrl}${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${this.versionCfg.versionCode}`;
if (!this.notifyMainJs(updateUrl, serverUrl)) {
return;
}
if (this.ftpCfg.onlyJson) {
logger.warn('当前仅上传JSON文件,请手动上传remote目录');
return;
}
const filePath = `update-${this.versionCfg.versionType}.json`;
const uploadDstPath = `${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${filePath}`;
logger.info('上传文件:', uploadDstPath);
// 上传更新描述文件.
const updateJsonPath = path.join(this.projectPath, `build/jsb-link/remote/${filePath}`);
Editor.Ipc.sendToMain('update-manager:upload', updateJsonPath, uploadDstPath, this.ftpCfg);
@ -324,7 +332,7 @@
self.uploadState = !args;
});
},
notifyMainJs(updateUrl, serverUrl) {
notifyMainJs(updateUrl, serverUrl, updateBefore) {
this.$message.info("notifyMainJs");
const filePath = path.join(this.projectPath, 'build/jsb-link/main.js');
const exists = fs.existsSync(filePath);
@ -335,12 +343,13 @@
let mainjs = fs.readFileSync(filePath, 'utf-8');
let varJs = `window.updateUrl="${updateUrl}";\r\n`;
let remoteJs = `window.remoteUrl="${serverUrl}";\r\n`;
let updateBeforeFlag = `window.updateBefore=${updateBefore};\r\n`;
// preMainJS read.
const preMain = fs && fs.readFileSync && fs.readFileSync(
Editor.url('packages://update-manager/templates/pre_main.js'), 'utf-8');
if (!mainjs.startsWith(varJs)) {
mainjs = mainjs.replace('window.boot();', 'window.beforeBoot();');
mainjs = varJs + remoteJs + preMain + mainjs;
mainjs = varJs + remoteJs + updateBeforeFlag + preMain + mainjs;
fs && fs.writeFileSync && fs.writeFileSync(filePath, mainjs, 'utf-8');
}
this.$message.success("Main.js 热更代码注入成功");

View File

@ -1,4 +1,5 @@
window.beforeBoot = function () {
// console.log("游戏正在启动中.")
if (window.remoteUrl) {
const settings = window._CCSettings;
@ -11,16 +12,51 @@ window.beforeBoot = function () {
window.boot();
return;
}
// 游戏启动后再请求更新,避免影响启动速度.
url += `?_t=${Math.random()}`;
cc.log("请求更新地址:", url);
if (window.updateBefore) {
_get(url).then(resp => {
if (!resp) {
window.boot();
return;
}
window.localStorage.setItem('cur_ver_info', resp);
window.onBooting();
});
} else {
window.onBooting();
_get(url).then(resp => {
if (!resp) {
return;
}
window.localStorage.setItem('cur_ver_info', resp);
});
}
};
window.onBooting = function () {
// 请求缓存信息,判断是否需要更新.
let assetStr = window.localStorage.getItem('cur_ver_info');
if (!assetStr) {
window.boot();
} else {
// console.log("当前版本信息:", assetStr);
console.log("当前版本信息:", assetStr);
let asset = JSON.parse(assetStr);
window.mergeVersion(asset);
window.boot();
// 判断当前是否有版本更新.
const lastVer = window.localStorage.getItem('last_ver_code') || asset.versionCode;
const curVer = asset.versionCode || 0;
if (lastVer != curVer) {
window.localStorage.setItem('new_ver_flag', "1");
} else {
window.localStorage.setItem('new_ver_flag', "0");
}
window.localStorage.setItem('last_ver_code', curVer);
// 当前版本名称.
window.localStorage.setItem('cur_ver_name', asset.versionName);
}
};
window.mergeVersion = function (updateInfo) {
@ -41,3 +77,20 @@ window.mergeVersion = function (updateInfo) {
}
};
function _get(url) {
return new Promise(resolve => {
const ajax = new XMLHttpRequest();
ajax.open("get", url, true);
ajax.setRequestHeader("Content-Type", "application/json;charset=utf-8");
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
if (ajax.status == 200) {
resolve(ajax.responseText);
} else {
resolve(null);
}
}
}
ajax.send(null);
});
};