let OSS = require('ali-oss'); let fs = require('fs'); let path = require('path'); var archiver = require('archiver'); let request = require("request"); let http = require('http'); let compressPercent = 0; let taskCodeQueue = []; let taskDownloaderQueue = {}; let pathConfig = { ossPrefix: 'http://4dkankan.oss-cn-shenzhen.aliyuncs.com/', // oss前缀 // serverPrefix: 'http://192.168.0.61:8888/', // 服务器前缀 serverPrefix: 'https://test.4dkankan.com/back/down/server/controller/', // 服务器前缀 localDataName: 'localData', layer: './', // 层级 rootFold: 'tmpData', // 根目录文件夹 staticPath: [ // 静态数据路径 'static' ], paths: [ // images/data路径 'images/images', 'data/data' ], filters: ['tiles'], dynamicPath: [ // 一些使用api加载的数据, 提前写入json中 'http://pro.4dkankan.com/api/scene/getInfo' ], } function downloader() { this.objArr = []; this.completeFolds = 0; // 记录已经递归完成的目录, completeFolds == foldNum, 表示所有目录递归完成 this.fileNum = 0; // 需要下载的文件总数 this.completeFileNum = 0; // 已经下载完成的文件总数 this.threadNum = 10; // 同时并发的线程总数 this.curActiveThread = 0; this.sta = 0; this.downloadProcess = 0; // 下载进度 this.zipProcess = 0; // 压缩进度 this.sceneCode = ''; // 场景码 this.sceneInfo = ''; // 场景的其他json数据 this.downloadResponse = null; this.timer = null; // 定时器 this.dirPath = path.join(__dirname, pathConfig.rootFold); this.foldNum = pathConfig.paths.length + pathConfig.staticPath.length; // 记录目录的总数, 用于判断是否目录是否递归完成 this.client = new OSS({ // 配置阿里云oss region: 'oss-cn-shenzhen', accessKeyId: 'LTAIUrvuHqj8pvry', accessKeySecret: 'JLOVl0k8Ke0aaM8nLMMiUAZ3EiiqI4', bucket: '4dkankan', }) } /** * 创建文件夹 * @param data 文件路径 */ downloader.prototype.createRootFold = function (data) { if (!fs.existsSync(this.dirPath)) { fs.mkdirSync(this.dirPath); } else { console.log('文件夹已存在'); } this.dirPath = path.join(this.dirPath, '/' + data); // 创建文件夹目录 if (!fs.existsSync(this.dirPath)) { fs.mkdirSync(this.dirPath); } else { console.log('文件夹已存在'); } } /** * 递归列举所有需要下载的文件, 记录文件夹以及所有文件的总数 */ downloader.prototype.listDir = async function (dir, prefixDir) { let that = this; for (let i = 0; i < pathConfig.filters.length; i++) { // 有些文件夹不需要下载, 则进行过滤 let filter = pathConfig.filters[i]; if (dir.indexOf(filter) > 0) { console.log('过滤的路径' + dir); that.completeFolds++; if (that.completeFolds === that.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数 that.fileNum = that.objArr.length; that.timer = setInterval(that.download, 16); // 开始下载 } return; } } // 列举当前文件夹中的所有文件, 最多1000个 let result = await this.client.list({ prefix: dir, delimiter: '/', 'max-keys': 1000 // 最大限制1000 }); dirArr = `${prefixDir}${dir}`.split('/'); // 分割其目录层级 let tmpDir = ''; let childDirPath; dirArr.forEach((item) => { // 对于路径中的每一个层级, 都创建对应的文件夹 tmpDir += '/' + item; childDirPath = path.join(this.dirPath, '/' + tmpDir); // 子路径 if (!fs.existsSync(childDirPath)) { fs.mkdirSync(childDirPath); console.log('创建文件夹成功: ' + childDirPath); } else { console.log('文件夹已存在'); } }); // result.objects就是当前文件夹下的所有文件 result.objects && result.objects.forEach(async function (obj) { let downloadURL = pathConfig.ossPrefix + obj.name; // 记录每个文件需要下载的url let arr = obj.name.split('/'); let objName = arr[arr.length - 1]; // 获取文件名, 上面的obj.name除了文件名, 还包含了文件所属的路径 if (objName === '') { // 过滤非法文件, 因为阿里云有时候会把文件夹当作文件返回, return; } let writeURL = path.join(childDirPath, objName); // 下载完成后, 应当写入的路径 let tack = { // 创建下载任务 downloadURL: downloadURL, writeURL: writeURL } that.objArr.push(tack); // 下载任务入列 }); if (result.prefixes) { // 如果当前目录还有子目录存在, 则继续递归 this.foldNum--; // 移除当前目录, 因为当前目录还不是最深层级 let that = this; result.prefixes.forEach(function (subDir) { that.foldNum++; // 记录当前目录的子目录 that.listDir(subDir, prefixDir); }); } else { // 当前目录已经递归完成 this.completeFolds++; console.log(this.completeFolds + ' ' + this.foldNum) if (this.completeFolds === this.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数 // console.log(this.objArr) this.fileNum = this.objArr.length; // 记录需要下载的文件总数 this.timer = setInterval(this.download.bind(this), 16); // 开始下载 } } } downloader.prototype.download = function () { if (this.objArr.length === 0) { // 所有文件已经下载完成, 清楚定时器 clearInterval(this.timer); this.timer = null; return; } if (this.curActiveThread <= this.threadNum) { // 当前活跃的线程数<总线程数, 则继续创建创建下载队列, 控制并发 let task = this.objArr.shift(); // 取队列中的第一个任务 let stream = fs.createWriteStream(task.writeURL); // 根据任务中的写入路径创建流 let readStream = request(task.downloadURL).on('error', function () { // 通过下载路径去请求文件 console.log("文件[" + task.downloadURL + "]下载出错 "); }).pipe(stream); let that = this; readStream.on('finish', function () { // 当前流下载完成, 释放线程 that.curActiveThread--; }) stream.on('finish', function () { // 文件写入完毕 that.completeFileNum++; // 已经下载完成的文件数+1 that.downloadProcess = that.completeFileNum / that.fileNum * 100; // 计算下载进度 if (that.completeFileNum === that.fileNum) { // 所有的oss文件已下载完 let sceneCode = that.sceneCode; // 记录场景码 // sceneData.json的写入路径, sceneData.json记录的是场景中的其他信息, 如场景描述、场景密码等可配置信息 let sceneJsonPath = path.join(__dirname, `tmpData/${sceneCode}/static/images/images${sceneCode}/sceneData.json`); // code.txt的写入路径, 其记录的是场景码, 本地浏览器需要读取该文件 let sceneCodePath = path.join(__dirname, `tmpData/${sceneCode}/code.txt`); fs.writeFile(sceneJsonPath, that.sceneInfo, function (err) { // 写入sceneInfo.json if (err) { return console.log(`写入文件出错: ${err}`); } fs.writeFile(sceneCodePath , sceneCode , function (err) { // 将场景码写入 console.log('开始压缩' + that.sceneCode) // let zipStream = fs.createWriteStream(`${__dirname}/tmpData/${that.sceneCode}/` + `/localData.zip`); let zipStream = fs.createWriteStream(`${__dirname}/tmpData/zip/` + `/${that.sceneCode}.zip`); // 创建压缩包 pathConfig.localDataName = that.sceneCode; let archive = archiver('zip', { zlib: { level: 9 // 压缩等级 } }); zipStream.on('close', function () { let respData = 'archiver has been finalized and the output file descriptor has closed.'; console.log(archive.pointer() + ' total bytes'); console.log('文件压缩已写入完成'); // 重置两个进度 downloadPercent = 0; compressPercent = 0; }); // 计算压缩进度 archive.on('progress', function (process) { compressPercent = that.zipProcess = process.fs.processedBytes / process.fs.totalBytes * 100; }) archive.on('error', function (err) { console.log(err); }) archive.pipe(zipStream); archive.directory(`${__dirname}/tmpData/${that.sceneCode}/`, false); // 将已经下载完成的全部内容放入至压缩流中 archive.directory(`${__dirname}/static/page`, false); // 追加html文件 archive.directory(`${__dirname}/browser/`, false); // 将浏览器放入压缩流中 archive.finalize(); }) }) } }) this.curActiveThread++; // 激活的线程数+1 } } downloader.prototype.execute = function (data) { console.log(data); this.createRootFold(data); pathConfig.paths.forEach(path => { this.listDir(path + data, `static/`); }) pathConfig.staticPath.forEach(path => { this.listDir(path, ''); }) } /** * 起始函数 * @param {*} response 响应 * @param {*} data 前端传过来的数据 */ function start(response, data) { let sceneCode = data.sceneCode; let respData; // 如果已经存在压缩包, 则直接返回压缩包的路径给浏览器进行下载 if (fs.existsSync(`${__dirname}/${pathConfig.rootFold}/${sceneCode}/${pathConfig.localDataName}.zip`)) { respData = { sta: 1003, // 1003 文件已存在 data: { percent: 100, url: `${pathConfig.serverPrefix}${pathConfig.rootFold}/${sceneCode}/${pathConfig.localDataName}.zip` }, msg: '文件已存在, 直接返回url' }; console.log('文件已存在, 直接返回url'); } else { respData = { sta: 1002, // 1002 data: { percent: 0 }, msg: '已加入任务队列' }; taskCodeQueue.push(sceneCode); taskDownloaderQueue[sceneCode] = new downloader(); // 对于每个前端发来的下载请求, 创建一个downloader实例 taskDownloaderQueue[sceneCode].sceneCode = data.sceneCode; taskDownloaderQueue[sceneCode].sceneInfo = data.sceneInfo; // sceneInfo就是场景中其他的可配置信息, 用于写入sceneData.json中 } let json = JSON.stringify(respData) response.send(json); } /** * 查询下载进度 * @param {*} response 响应 * @param {*} data 前端传过来的数据 */ function downloadProcess(response, data) { let sceneCode = data.sceneCode; let downloader = taskDownloaderQueue[sceneCode]; if (!downloader) return; if (downloader.downloadProcess < 100) { // 文件正在下载中 let respData = { sta: 1000, // 状态码 1000-文件正在下载 1001-文件正在压缩 data: { percent: downloader.downloadProcess }, msg: '文件下载中' }; let json = JSON.stringify(respData) response.send(json); } else { // 文件正在压缩中 let respData = { sta: 1001, // 状态码 1000-文件正在下载 1001-文件正在压缩 data: { percent: downloader.zipProcess }, msg: '文件压缩中' }; if (downloader.zipProcess === 100) { respData.data.url = `${pathConfig.serverPrefix}${pathConfig.rootFold}/zip/${pathConfig.localDataName}.zip` } let json = JSON.stringify(respData) response.send(json); } } (function () { // 服务器的downloader队列, 每隔1s处理一个前端的下载请求, 避免服务器压力过大 setInterval(function () { let taskCode = taskCodeQueue.shift(); if (taskCode) { taskDownloaderQueue[taskCode].execute(taskCode); } }, 1000) })() exports.start = start; exports.downloadProcess = downloadProcess;