414 lines
16 KiB
TypeScript
414 lines
16 KiB
TypeScript
import { Controller, FileStream } from 'egg';
|
|
import { Stream } from 'stream';
|
|
import { STATUS } from '@consts';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const unzip = require("unzip-stream");
|
|
const temp = require('temp');
|
|
const compressing = require("compressing");
|
|
const moment = require("moment");
|
|
const pump = require('mz-modules/pump');
|
|
|
|
const folderName = 'hot_update_files';
|
|
const hotUpdateAddr = `/root/${folderName}`;
|
|
const publishPath = '/root/hot_update_backup';
|
|
import { exec } from 'child_process'
|
|
import { gameData, reloadResources } from '@pubUtils/data';
|
|
import { decodeArrayStr, genCode, parseGoodStr } from '@pubUtils/util';
|
|
import { getLocalImgUrl, getLocalQrCodeUrl, getRemoteImgUrl, getRemoteQrCodeUrl } from '@pubUtils/battleUtils';
|
|
import { RewardInter } from '@pubUtils/interface';
|
|
import { RoleModel } from '@db/Role';
|
|
import { isNumber, isString } from 'util';
|
|
const sendToWormhole = require('stream-wormhole');
|
|
const XLSX = require('xlsx');
|
|
|
|
export default class UploadController extends Controller {
|
|
|
|
private deleteFolder(path) {
|
|
let files = [];
|
|
if (fs.existsSync(path)) {
|
|
files = fs.readdirSync(path);
|
|
files.forEach((file) => {
|
|
let curPath = path + "/" + file;
|
|
if (fs.statSync(curPath).isDirectory()) {
|
|
this.deleteFolder(curPath);
|
|
} else {
|
|
fs.unlinkSync(curPath);
|
|
}
|
|
});
|
|
fs.rmdirSync(path);
|
|
}
|
|
}
|
|
|
|
|
|
private getFileList(p: string, resultArr: Array<{ path: string, name: string }>) {
|
|
console.log(p, fs.existsSync(p))
|
|
if (fs.existsSync(p)) {
|
|
let files = fs.readdirSync(p);
|
|
console.log(p, files, resultArr);
|
|
files.forEach((file) => {
|
|
let curPath = p + "/" + file;
|
|
// console.log(curPath, fs.statSync(curPath).isDirectory())
|
|
if (fs.statSync(curPath).isDirectory()) {
|
|
this.getFileList(curPath, resultArr);
|
|
} else {
|
|
resultArr.push({
|
|
path: curPath,
|
|
name: file
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public async upload() {
|
|
const { ctx } = this;
|
|
const stream = await ctx.getFileStream();
|
|
// const filename = stream.filename;
|
|
// const target = path.join(url, filename);
|
|
|
|
// const writeStream = fs.createWriteStream(target);
|
|
if (stream.mimeType == 'application/zip') {
|
|
try {
|
|
|
|
let dirPath = await temp.mkdir(folderName); // 临时文件夹
|
|
|
|
// 解压上传文件的stream
|
|
await this.doUnzipExtra(stream, dirPath);
|
|
|
|
console.log('读取文件');
|
|
let files: any = fs.readdirSync(dirPath);
|
|
let msg = '', result = '';
|
|
for (let file of files) {
|
|
let flag = false;
|
|
|
|
let arr = ['project.manifest', 'version.manifest', 'assets', 'src'];
|
|
for (let fileName of arr) {
|
|
console.log(`${dirPath}/${file}/${fileName}`);
|
|
let result = fs.existsSync(`${dirPath}/${file}/${fileName}`);
|
|
if (!result) {
|
|
msg = '缺少文件' + fileName;
|
|
flag = true;
|
|
break;
|
|
}
|
|
}
|
|
if (flag) {
|
|
continue;
|
|
} else {
|
|
result = file;
|
|
dirPath += `/${file}`;
|
|
};
|
|
}
|
|
if (!result) {
|
|
throw new Error(msg);
|
|
}
|
|
|
|
// 历史记录压缩移动
|
|
console.log('历史记录压缩移动');
|
|
let isEmpty = true;
|
|
try {
|
|
let oldFiles: any = fs.readdirSync(hotUpdateAddr);
|
|
console.log(oldFiles.length);
|
|
isEmpty = oldFiles.length <= 0;
|
|
} catch (e) {
|
|
isEmpty = true;
|
|
}
|
|
if (!isEmpty) {
|
|
await compressing.zip.compressDir(hotUpdateAddr, `${dirPath}/${folderName}.zip`);
|
|
if (!fs.existsSync(publishPath)) {
|
|
fs.mkdirSync(publishPath);
|
|
}
|
|
let versionManifest: string = '';
|
|
try {
|
|
let version = JSON.parse(fs.readFileSync(`${hotUpdateAddr}/version.manifest`));
|
|
versionManifest = version.version;
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
fs.renameSync(`${dirPath}/${folderName}.zip`, `${publishPath}/${folderName}_${versionManifest}_${moment().format('YYMMDDHHmmss')}.zip`);
|
|
|
|
// 删除原始文件
|
|
this.deleteFolder(hotUpdateAddr);
|
|
}
|
|
|
|
// 保存新文件
|
|
console.log('保存至热更新地址');
|
|
fs.renameSync(dirPath, hotUpdateAddr);
|
|
|
|
temp.cleanupSync();
|
|
return ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS);
|
|
|
|
} catch (err) {
|
|
console.log(err)
|
|
return ctx.body = ctx.service.utils.resResult(STATUS.GM_UPLOAD_FORMAT_ERR, null, err.message);
|
|
}
|
|
|
|
} else {
|
|
return ctx.body = ctx.service.utils.resResult(STATUS.GM_UPLOAD_FORMAT_ERR);
|
|
}
|
|
}
|
|
|
|
private async doUnzipExtra(stream: Stream, dirPath: string) {
|
|
return new Promise((resolve, reject) => {
|
|
// 解压上传文件的stream
|
|
var unzipExtractor = unzip.Extract({ path: dirPath });
|
|
unzipExtractor.on('close', function () {
|
|
resolve(null);
|
|
});
|
|
|
|
unzipExtractor.on('close', function (e) {
|
|
reject(e);
|
|
});
|
|
|
|
stream.pipe(unzipExtractor); // 异步写入文件
|
|
})
|
|
}
|
|
|
|
private jsonFolder = '/app/resource/jsons';
|
|
private warjsonFolder = '/app/resource/warJsons';
|
|
private tsFolder = '/app/pubUtils';
|
|
|
|
private distJsonFolder = `/../game-server/dist${this.jsonFolder}`;
|
|
private distWarjsonFolder = `/../game-server/dist${this.warjsonFolder}`;
|
|
private distTsFolder = `/../game-server/dist${this.tsFolder}`;
|
|
|
|
public async uploadJson() {
|
|
const { ctx } = this;
|
|
|
|
|
|
const parts = ctx.multipart({});
|
|
const files = [];
|
|
|
|
try {
|
|
let stream;
|
|
let writeStream;
|
|
|
|
while ((stream = await parts()) != null) {
|
|
console.log('******', stream);
|
|
const filename = stream.filename;
|
|
let filenameWithoutEx = filename ? filename.split('.')[0] : '';
|
|
if (stream.mimeType == 'application/json') {
|
|
if (filenameWithoutEx.match(/^\d{1,}$/)) {
|
|
let target1 = path.join(this.config.baseDir, this.warjsonFolder, filename);
|
|
let target2 = path.join(this.config.baseDir, this.distWarjsonFolder, filename);
|
|
writeStream = fs.createWriteStream(target1);
|
|
await pump(stream, writeStream);
|
|
fs.copyFileSync(target1, target2);
|
|
files.push(filename);
|
|
} else {
|
|
let target1 = path.join(this.config.baseDir, this.jsonFolder, filename);
|
|
let target2 = path.join(this.config.baseDir, this.distJsonFolder, filename);
|
|
writeStream = fs.createWriteStream(target1);
|
|
await pump(stream, writeStream);
|
|
fs.copyFileSync(target1, target2);
|
|
files.push(filename);
|
|
}
|
|
} else if (stream.mimeType == 'application/octet-stream') {
|
|
let target1 = path.join(this.config.baseDir, this.tsFolder, 'dicParam.js');
|
|
let target2 = path.join(this.config.baseDir, this.distTsFolder, filename);
|
|
writeStream = fs.createWriteStream(target1);
|
|
await pump(stream, writeStream);
|
|
fs.copyFileSync(target1, target2);
|
|
files.push(filename);
|
|
}
|
|
|
|
let envs = decodeArrayStr(ctx.request.headers.env || '', ',');
|
|
|
|
if (envs.length > 0) { // 转发
|
|
for (let env of envs) {
|
|
if (env == ctx.app.config.realEnv) continue;
|
|
await ctx.service.utils.transmit(env, ctx.request.url, writeStream);
|
|
}
|
|
}
|
|
sendToWormhole(stream);
|
|
}
|
|
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
|
|
return ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, {
|
|
"files": files
|
|
});
|
|
|
|
// console.log('****', stream);
|
|
// // const filename = stream.filename;
|
|
// // const target = path.join(url, filename);
|
|
|
|
// if(stream.mimeType == 'application/zip') {
|
|
// try {
|
|
// let dirPath = await temp.mkdir(this.jsonFolderName); // 临时文件夹
|
|
|
|
// console.log('读取文件');
|
|
// await this.doUnzipExtra(stream, dirPath);
|
|
// let files = new Array<{path:string,name:string}>();
|
|
// this.getFileList(dirPath, files);
|
|
// console.log(files);
|
|
|
|
// console.log('保存文件');
|
|
// if (!fs.existsSync(this.jsonFolderPath)) {
|
|
// fs.mkdirSync(this.jsonFolderPath);
|
|
// }
|
|
// for (let {path, name} of files) {
|
|
// console.log(`${this.jsonFolderPath}/${name}`);
|
|
// fs.renameSync(path, `${this.jsonFolderPath}/${name}`);
|
|
// }
|
|
|
|
// temp.cleanupSync();
|
|
// return ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, {
|
|
// "files": JSON.stringify(files)
|
|
// });
|
|
// } catch (err) {
|
|
// console.log(err)
|
|
// return ctx.body = ctx.service.utils.resResult(STATUS.INTERNAL_ERR);
|
|
// }
|
|
|
|
// } else {
|
|
// return ctx.body = ctx.service.utils.resResult(STATUS.GM_UPLOAD_FORMAT_ERR);
|
|
// }
|
|
}
|
|
|
|
public async reloadResource() {
|
|
const { ctx } = this;
|
|
|
|
try {
|
|
|
|
reloadResources();
|
|
ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, { isOK: true });
|
|
return;
|
|
} catch (e) {
|
|
ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, { isOK: false, err: e.stack });
|
|
return;
|
|
}
|
|
}
|
|
|
|
public async uploadQrCode() {
|
|
const { ctx } = this;
|
|
|
|
try {
|
|
const { ctx } = this;
|
|
const parts = ctx.multipart();
|
|
let part, env = ctx.request.headers.env, type = ctx.request.headers.type, fileName = '', writePath = '', ext = '', remoteUrl = '';
|
|
while ((part = await parts()) != null) {
|
|
if (part.length) {
|
|
console.log('kv: ', `${part[0]}: ${part[1]}`);
|
|
} else {
|
|
if (!part.filename) continue;
|
|
|
|
let filenames = part.filename.split('.');
|
|
ext = filenames[filenames.length - 1];
|
|
if (type == 'popNotice' || type == 'loginPage' || type == 'loadingPage' || type == 'icon') {
|
|
writePath = getLocalImgUrl(this.app.config.realEnv, type);
|
|
fileName = `${moment().valueOf()}.${ext}`;
|
|
remoteUrl = getRemoteImgUrl(this.app.config.realEnv, fileName, type);
|
|
} else {
|
|
writePath = getLocalQrCodeUrl(this.app.config.realEnv, env);
|
|
fileName = `QR${moment().valueOf()}.${ext}`;
|
|
remoteUrl = getRemoteQrCodeUrl(env, fileName);
|
|
}
|
|
|
|
let fullPath = `${writePath}/${fileName}`;
|
|
try {
|
|
fs.accessSync(writePath);
|
|
} catch (err) {
|
|
if (err) {
|
|
fs.mkdirSync(writePath, { recursive: true });
|
|
}
|
|
}
|
|
if (!fs.existsSync(fullPath)) {
|
|
fs.writeFileSync(fullPath, '');
|
|
}
|
|
const writeStream = fs.createWriteStream(fullPath);
|
|
await pump(part, writeStream);
|
|
|
|
// remoteUrl = `${getRemoteRplUrl(ctx.app.config.realEnv, roleId, warType, battleCode)}/${battleCode}.bin`;
|
|
}
|
|
await sendToWormhole(part);
|
|
}
|
|
ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, { isOK: true, url: remoteUrl, ext });
|
|
return;
|
|
} catch (e) {
|
|
ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, { isOK: false, err: e.stack });
|
|
return;
|
|
}
|
|
}
|
|
|
|
public async uploadMailExcel() {
|
|
const { ctx } = this;
|
|
|
|
try {
|
|
const { ctx } = this;
|
|
const stream = await ctx.getFileStream();
|
|
let exceldata = await this.streamToExcekData(stream);
|
|
|
|
let result: { code: string, env: string, serverId: number, roleId: string, roleName: string, rewards: string, isError: boolean, isNameError: boolean, isRewardError: boolean }[] = [];
|
|
for(let i = 1; i < exceldata.length; i++) {
|
|
let data = exceldata[i];
|
|
let newObj = { code: genCode(5), env: ctx.app.config.realEnv, serverId: data.serverId, roleId: data.roleId, roleName: '', rewards: '', isError: true, isNameError: true, isRewardError: true };
|
|
if(data.roleId && isString(data.roleId)) {
|
|
let role = await RoleModel.findByRoleId(data.roleId, 'roleName serverId');
|
|
if(role && role.serverId == data.serverId) {
|
|
newObj.roleName = role.roleName;
|
|
newObj.serverId = role.serverId;
|
|
newObj.isNameError = false;
|
|
}
|
|
}
|
|
if(data.rewards && isString(data.rewards)) {
|
|
try {
|
|
let rewards: RewardInter[] = parseGoodStr(data.rewards);
|
|
let isRewardError = false;
|
|
for(let { id, count } of rewards) {
|
|
let dicGoods = gameData.goods.get(id);
|
|
if(!dicGoods) { isRewardError = true; break; }
|
|
if(!isNumber(count) || count <= 0) { isRewardError = true; break; }
|
|
}
|
|
newObj.rewards = JSON.stringify(rewards);
|
|
newObj.isRewardError = isRewardError;
|
|
} catch(e) {
|
|
console.log('updateMailExcel', e)
|
|
}
|
|
}
|
|
newObj.isError = newObj.isNameError || newObj.isRewardError;
|
|
result.push(newObj);
|
|
}
|
|
|
|
ctx.body = ctx.service.utils.resResult(STATUS.SUCCESS, { result });
|
|
return;
|
|
} catch (e) {
|
|
console.error(e)
|
|
ctx.body = ctx.service.utils.resResult(STATUS.WRONG_PARMS, { result: [] });
|
|
return;
|
|
}
|
|
}
|
|
|
|
public async streamToExcekData(stream: FileStream): Promise<{ serverId: number, roleId: string, rewards: string }[]> {
|
|
return new Promise((resolve, reject) => {
|
|
let exceldata = [];
|
|
stream.on('error', reject);
|
|
stream.on('data', function(chunk) {
|
|
const workbook = XLSX.read(chunk);
|
|
for(let sheet in workbook.Sheets) {
|
|
if(workbook.Sheets.hasOwnProperty(sheet)) {
|
|
exceldata = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
stream.on('end', () => resolve(exceldata))
|
|
});
|
|
}
|
|
}
|
|
|
|
export function childExec(commond: string) {
|
|
return new Promise((resolve, reject) => {
|
|
exec(commond, function (error, stdout, stderr) {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
console.log('stdout: ' + stdout);
|
|
console.log('stderr: ' + typeof stderr);
|
|
resolve(stdout);
|
|
});
|
|
})
|
|
} |