hot-update/packages/update-manager/panel/index.html
andrewlu b81d004641 增加 启动后延迟更新开关.
使用此开关后,游戏包将在启动进入游戏后才请求更新信息, 直到重启后再打开游戏才应用更新.

优化热更流程,可以在构建完成后,再领奖注入热更,上传更新.
2021-02-04 16:12:32 +08:00

361 lines
19 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- import CSS -->
<link rel="stylesheet" href="./element-ui/index.css">
<link rel="stylesheet" href="./main.css">
<title>热更新配置</title>
</head>
<body style="height: 1000px;">
<div id="app">
<el-tabs v-model="tabName">
<el-tab-pane label="版本配置" name="versionConfig" class="tab-content">
<el-form ref="version" :model="versionCfg" label-width="80px">
<el-tooltip effect="dark" :content="tip.versionCode" open-delay=500 placement="top">
<el-form-item label="新版本号:">
<el-input type="number" v-model="versionCfg.versionCode" placeholder="1"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.versionName" open-delay=500 placement="top">
<el-form-item label="版本名称:">
<el-input v-model="versionCfg.versionName" placeholder="1.0.0"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.versionType" open-delay=500 placement="top">
<el-form-item label="版本类型:">
<el-input v-model="versionCfg.versionType" placeholder="dev"></el-input>
</el-form-item>
</el-tooltip>
<el-form-item label="强更开关:">
<el-tooltip content="开启时将强制更新用户端版本" placement="top">
<el-switch v-model="versionCfg.forceUpdate"
:active-text="versionCfg.forceUpdate?'强制静默更新':'手动检测更新'"
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="热更地址:">
<el-input v-model="versionCfg.baseUrl"
placeholder="https://cdn-xxx.com"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.versionLog" open-delay=500 placement="top">
<el-form-item label="版本描述:">
<el-input type="textarea" v-model="versionCfg.versionLog"
placeholder="版本更新内容..."></el-input>
</el-form-item>
</el-tooltip>
</el-form>
</el-tab-pane>
<el-tab-pane label="上传配置" name="ftpConfig" class="tab-content">
<el-form ref="ftpCfg" :model="ftpCfg" label-width="80px">
<el-tooltip effect="dark" :content="tip.ip" placement="top" open-delay=500>
<el-form-item label="IP:">
<el-col :span="15">
<el-input type="text" v-model="ftpCfg.host" placeholder="127.0.0.1"></el-input>
</el-col>
<el-col class="line" :span="1">-</el-col>
<el-col :span="6">
<el-input type="number" v-model="ftpCfg.port" placeholder="端口号"></el-input>
</el-col>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.user" open-delay=500 placement="top">
<el-form-item label="登录帐号:">
<el-col :span="12">
<el-input type="text" v-model="ftpCfg.user" placeholder="登录帐号"></el-input>
</el-col>
<el-col class="line" :span="1">-</el-col>
<el-col :span="11">
<el-input type="password" v-model="ftpCfg.password" placeholder="密码"></el-input>
</el-col>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" content="FTP长连接持续心跳时间,一般情况下忽略此选项" open-delay=500 placement="top">
<el-form-item label="KeepAlive:">
<el-input type="number" v-model="ftpCfg.keepalive" placeholder="10000"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" content="FTP同时上传文件数,数量越大,上传效率越高<br/>受限于带宽性能,并非越大越好,推荐5~10" open-delay=500
placement="top">
<el-form-item label="并发数量:">
<el-input type="number" v-model="ftpCfg.maxThread" placeholder="同时上传文件数"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" :content="tip.rootPath" open-delay=500 placement="top">
<el-form-item label="FTP目录:">
<el-input v-model="ftpCfg.rootPath"
placeholder="TempDir/"></el-input>
</el-form-item>
</el-tooltip>
<el-tooltip effect="dark" content="勾选后仅会上传热更描述文件,<br/>需要手动上传remote目录" open-delay=500 placement="top">
<el-form-item label="手动上传:">
<el-switch v-model="ftpCfg.onlyJson"></el-switch>
</el-form-item>
</el-tooltip>
</el-form>
</el-tab-pane>
<el-tab-pane label="使用帮助" name="help" class="tab-content">
<el-alert :closable="false"
title="如不需要发布新版本,可无需做任何操作,以正常步骤打包发布即可"
type="info">
</el-alert>
<el-divider content-position="left"></el-divider>
<el-steps direction="vertical" :active="1" space="100px">
<el-step title="版本配置"
description="使用前需要先配置版本信息,并点击保存配置."></el-step>
<el-step title="构建版本"
description="开始构建版本,构建完成后会在构建目录中生成remote文件夹以及update-dev.json更新描述文件
注:不需要设置Bundle为远程包,插件将自动调整远程包设置"></el-step>
<el-step title="上传更新包"
description="构建完成时,如需发布新版本,则需要点击[上传更新包],更新包会向游戏代码中注入热更逻辑."></el-step>
</el-steps>
</el-tab-pane>
<el-tab-pane label="任务状态" name="states" class="tab-content">
<template v-if="states.length>0">
<el-row :gutter="10">
<el-col v-for="(value,index) in states" :key="index" :span="24" style="margin-bottom:10px">
<el-card :body-style="{ padding: '0px' }">
<div slot="header" class="clearfix">
<el-col :span="16">
<h3 style="line-height: 5px;">{{index+1}}-线程:{{value.name}}</h3>
</el-col>
<el-col :span="8">
<el-button :loading="!value.idle" size="small"
:type="value.idle?'success':'danger'">
{{value.idle?"空闲中":"正忙"}}
</el-button>
<el-tooltip effect="dark" content="如果任务长时间卡住,点击重试即可" placement="top" open-delay=500>
<el-button icon="el-icon-refresh-right" size="small" circle type="warning"
@click="onRestart(value.name)"></el-button>
</el-tooltip>
</el-col>
</div>
<el-form label-position="right" label-width="80px">
<el-form-item label="剩余任务:" style="margin-bottom: 5px;">
<span>{{value.totalTasks-value.remain}}/{{value.totalTasks}}</span>
</el-form-item>
<el-form-item label="是否异常:" style="margin-bottom: 5px;">
<span>{{value.hasError?"正在重试中...":"线程正常"}}</span>
</el-form-item>
<el-form-item label="当前上传:" style="margin-bottom: 5px;">
<span>{{value.curTaskPath?value.curTaskPath.dst.substring(value.curTaskPath.dst.length>>1):"无目标"}}</span>
</el-form-item>
<el-form-item label="上传用时:" style="margin-bottom: 5px;">
<span>{{(!value.idle && value.taskStartTime)? Math.floor(new Date().getTime()-value.taskStartTime):0}} 毫秒</span>
</el-form-item>
<el-form-item label="任务进度:" style="margin-bottom: 5px;">
<el-progress :text-inside="true" :stroke-width="22"
style="width: 90%; line-height: unset;"
:percentage="value.progress" :color="customColors"></el-progress>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</template>
<template v-else>
<h1 style="text-align: center; vertical-align: center;line-height: 15;">无任务</h1>
</template>
</el-tab-pane>
</el-tabs>
<el-row style="margin-top: 20px;">
<el-col :span="8">
<el-button type="success" @click="onSave">注入热更配置</el-button>
</el-col>
<el-col :span="8">
<el-button type="danger" @click="onUpload">上传更新包</el-button>
</el-col>
<el-col :span="8">
<el-button type="warning" @click="onStopUpload">终止上传</el-button>
</el-col>
</el-row>
</div>
</body>
<!-- import Vue before Element -->
<script src="../js/vue.2.5.16.js"></script>
<script src="../js/logger.js"></script>
<!-- import JavaScript -->
<script src="./element-ui/index.js"></script>
<script>
const fs = require('fs');
const path = require('path');
new Vue({
el: '#app',
data() {
return {
tabName: "versionConfig",
projectPath: "",
versionCfg: {
versionCode: 1,
versionName: "1.0.0",
versionType: "dev",
versionLog: "",
baseUrl: "",
forceUpdate: true, // 强制静默更新.
updateBefore: true // 启动时直接请求更新.
},
ftpCfg: {
host: "127.0.0.1",
port: 21,
user: "",
password: "",
rootPath: "",
keepalive: 100000,
onlyJson: false,
maxThread: 1
},
tip: {
versionCode: "递增版本号,如果与旧版本号相同,则会替换线上版本包.",
versionName: "版本号名称,示例:1.0.0, 用于游戏内显示",
versionType: "版本类型,用于标记当前版本所处的线上环境类型,<br/>如灰度包,内测包,发布包等,不同类型的版本物理隔离,不会相互影响",
versionLog: "版本发布日志,用于游戏内显示更新内容.",
baseUrl: "FTP上传服务器对应的CDN访问URL,仅需要填写根域名即可.",
ip: "FTP上传IP地址,示例:127.0.0.1",
port: "FTP端口号,默认21",
user: "FTP登录验证帐号及密码",
rootPath: "FTP文件上传根目录"
},
states: [],
refreshTimer: 0,
uploadState: false
};
},
created: function () {
let data = fs.readFileSync(
Editor.url('packages://update-manager/.settings.conf'), 'utf-8');
if (data) {
Object.assign(this, JSON.parse(data));
}
//Bugfix: simple类型的面板无法在渲染进程中拿到项目路径.
const self = this;
Editor.Ipc.sendToMain('update-manager:getProjectPath', function (arg) {
self.projectPath = arg;
});
},
mounted() {
this.refreshTimer = setInterval(this.checkThreads.bind(this), 200);
},
destroyed() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
},
methods: {
customColors(p) {
return `#${this.getR(0x90, 0x67, p)}${this.getR(0x93, 0xc2, p)}${this.getR(0x99, 0x3a, p)}`;
},
getR(from, to, percent) {
return Math.floor(from + (to - from) * percent / 100).toString(16)
},
onRestart(id) {
Editor.Ipc.sendToMain('update-manager:restartThread', id);
},
onSave() {
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 += "/";
}
const data = Object.assign({}, {versionCfg: this.versionCfg, ftpCfg: this.ftpCfg});
fs.writeFileSync(
Editor.url('packages://update-manager/.settings.conf'), JSON.stringify(data, null, 2), 'utf-8');
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) {
this.$message('上传正在进行中,请勿重复点击哦~');
logger.warn('上传正在进行中,请勿重复点击哦~');
return;
}
this.uploadState = true;
this.$message("准备上传更新包");
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);
// 上传远程更新包.
let uploadZipPath = path.join(this.projectPath, `build/jsb-link/remote`);
let uploadZipDstPath = `${this.ftpCfg.rootPath}${this.versionCfg.versionType}/${this.versionCfg.versionCode}/remote`;
Editor.Ipc.sendToMain('update-manager:upload', uploadZipPath, uploadZipDstPath, this.ftpCfg);
},
onStopUpload() {
Editor.Ipc.sendToMain('update-manager:uploadStop');
},
checkThreads() {
const self = this;
Editor.Ipc.sendToMain('update-manager:checkThreads', function (args) {
self.states = args;
});
// 检测是否空闲中.
Editor.Ipc.sendToMain('update-manager:queryThreadStates', function (args) {
self.uploadState = !args;
});
},
notifyMainJs(updateUrl, serverUrl, updateBefore) {
this.$message.info("notifyMainJs");
const filePath = path.join(this.projectPath, 'build/jsb-link/main.js');
const exists = fs.existsSync(filePath);
if (!exists) {
this.$message.error('main.js 不存在,请重新构建');
return false;
}
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 + updateBeforeFlag + preMain + mainjs;
fs && fs.writeFileSync && fs.writeFileSync(filePath, mainjs, 'utf-8');
}
this.$message.success("Main.js 热更代码注入成功");
return true;
}
}
});
</script>
</html>