downloadController-注释.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. let OSS = require('ali-oss');
  2. let fs = require('fs');
  3. let path = require('path');
  4. var archiver = require('archiver');
  5. let request = require("request");
  6. let http = require('http');
  7. let compressPercent = 0;
  8. let taskCodeQueue = [];
  9. let taskDownloaderQueue = {};
  10. let pathConfig = {
  11. ossPrefix: 'http://4dkankan.oss-cn-shenzhen.aliyuncs.com/', // oss前缀
  12. // serverPrefix: 'http://192.168.0.61:8888/', // 服务器前缀
  13. serverPrefix: 'https://test.4dkankan.com/back/down/server/controller/', // 服务器前缀
  14. localDataName: 'localData',
  15. layer: './', // 层级
  16. rootFold: 'tmpData', // 根目录文件夹
  17. staticPath: [ // 静态数据路径
  18. 'static'
  19. ],
  20. paths: [ // images/data路径
  21. 'images/images',
  22. 'data/data'
  23. ],
  24. filters: ['tiles'],
  25. dynamicPath: [ // 一些使用api加载的数据, 提前写入json中
  26. 'http://pro.4dkankan.com/api/scene/getInfo'
  27. ],
  28. }
  29. function downloader() {
  30. this.objArr = [];
  31. this.completeFolds = 0; // 记录已经递归完成的目录, completeFolds == foldNum, 表示所有目录递归完成
  32. this.fileNum = 0; // 需要下载的文件总数
  33. this.completeFileNum = 0; // 已经下载完成的文件总数
  34. this.threadNum = 10; // 同时并发的线程总数
  35. this.curActiveThread = 0;
  36. this.sta = 0;
  37. this.downloadProcess = 0; // 下载进度
  38. this.zipProcess = 0; // 压缩进度
  39. this.sceneCode = ''; // 场景码
  40. this.sceneInfo = ''; // 场景的其他json数据
  41. this.downloadResponse = null;
  42. this.timer = null; // 定时器
  43. this.dirPath = path.join(__dirname, pathConfig.rootFold);
  44. this.foldNum = pathConfig.paths.length + pathConfig.staticPath.length; // 记录目录的总数, 用于判断是否目录是否递归完成
  45. this.client = new OSS({ // 配置阿里云oss
  46. region: 'oss-cn-shenzhen',
  47. accessKeyId: 'LTAIUrvuHqj8pvry',
  48. accessKeySecret: 'JLOVl0k8Ke0aaM8nLMMiUAZ3EiiqI4',
  49. bucket: '4dkankan',
  50. })
  51. }
  52. /**
  53. * 创建文件夹
  54. * @param data 文件路径
  55. */
  56. downloader.prototype.createRootFold = function (data) {
  57. if (!fs.existsSync(this.dirPath)) {
  58. fs.mkdirSync(this.dirPath);
  59. } else {
  60. console.log('文件夹已存在');
  61. }
  62. this.dirPath = path.join(this.dirPath, '/' + data);
  63. // 创建文件夹目录
  64. if (!fs.existsSync(this.dirPath)) {
  65. fs.mkdirSync(this.dirPath);
  66. } else {
  67. console.log('文件夹已存在');
  68. }
  69. }
  70. /**
  71. * 递归列举所有需要下载的文件, 记录文件夹以及所有文件的总数
  72. */
  73. downloader.prototype.listDir = async function (dir, prefixDir) {
  74. let that = this;
  75. for (let i = 0; i < pathConfig.filters.length; i++) { // 有些文件夹不需要下载, 则进行过滤
  76. let filter = pathConfig.filters[i];
  77. if (dir.indexOf(filter) > 0) {
  78. console.log('过滤的路径' + dir);
  79. that.completeFolds++;
  80. if (that.completeFolds === that.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数
  81. that.fileNum = that.objArr.length;
  82. that.timer = setInterval(that.download, 16); // 开始下载
  83. }
  84. return;
  85. }
  86. }
  87. // 列举当前文件夹中的所有文件, 最多1000个
  88. let result = await this.client.list({
  89. prefix: dir,
  90. delimiter: '/',
  91. 'max-keys': 1000 // 最大限制1000
  92. });
  93. dirArr = `${prefixDir}${dir}`.split('/'); // 分割其目录层级
  94. let tmpDir = '';
  95. let childDirPath;
  96. dirArr.forEach((item) => { // 对于路径中的每一个层级, 都创建对应的文件夹
  97. tmpDir += '/' + item;
  98. childDirPath = path.join(this.dirPath, '/' + tmpDir); // 子路径
  99. if (!fs.existsSync(childDirPath)) {
  100. fs.mkdirSync(childDirPath);
  101. console.log('创建文件夹成功: ' + childDirPath);
  102. } else {
  103. console.log('文件夹已存在');
  104. }
  105. });
  106. // result.objects就是当前文件夹下的所有文件
  107. result.objects && result.objects.forEach(async function (obj) {
  108. let downloadURL = pathConfig.ossPrefix + obj.name; // 记录每个文件需要下载的url
  109. let arr = obj.name.split('/');
  110. let objName = arr[arr.length - 1]; // 获取文件名, 上面的obj.name除了文件名, 还包含了文件所属的路径
  111. if (objName === '') { // 过滤非法文件, 因为阿里云有时候会把文件夹当作文件返回,
  112. return;
  113. }
  114. let writeURL = path.join(childDirPath, objName); // 下载完成后, 应当写入的路径
  115. let tack = { // 创建下载任务
  116. downloadURL: downloadURL,
  117. writeURL: writeURL
  118. }
  119. that.objArr.push(tack); // 下载任务入列
  120. });
  121. if (result.prefixes) { // 如果当前目录还有子目录存在, 则继续递归
  122. this.foldNum--; // 移除当前目录, 因为当前目录还不是最深层级
  123. let that = this;
  124. result.prefixes.forEach(function (subDir) {
  125. that.foldNum++; // 记录当前目录的子目录
  126. that.listDir(subDir, prefixDir);
  127. });
  128. } else { // 当前目录已经递归完成
  129. this.completeFolds++;
  130. console.log(this.completeFolds + ' ' + this.foldNum)
  131. if (this.completeFolds === this.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数
  132. // console.log(this.objArr)
  133. this.fileNum = this.objArr.length; // 记录需要下载的文件总数
  134. this.timer = setInterval(this.download.bind(this), 16); // 开始下载
  135. }
  136. }
  137. }
  138. downloader.prototype.download = function () {
  139. if (this.objArr.length === 0) { // 所有文件已经下载完成, 清楚定时器
  140. clearInterval(this.timer);
  141. this.timer = null;
  142. return;
  143. }
  144. if (this.curActiveThread <= this.threadNum) { // 当前活跃的线程数<总线程数, 则继续创建创建下载队列, 控制并发
  145. let task = this.objArr.shift(); // 取队列中的第一个任务
  146. let stream = fs.createWriteStream(task.writeURL); // 根据任务中的写入路径创建流
  147. let readStream = request(task.downloadURL).on('error', function () { // 通过下载路径去请求文件
  148. console.log("文件[" + task.downloadURL + "]下载出错 ");
  149. }).pipe(stream);
  150. let that = this;
  151. readStream.on('finish', function () { // 当前流下载完成, 释放线程
  152. that.curActiveThread--;
  153. })
  154. stream.on('finish', function () { // 文件写入完毕
  155. that.completeFileNum++; // 已经下载完成的文件数+1
  156. that.downloadProcess = that.completeFileNum / that.fileNum * 100; // 计算下载进度
  157. if (that.completeFileNum === that.fileNum) { // 所有的oss文件已下载完
  158. let sceneCode = that.sceneCode; // 记录场景码
  159. // sceneData.json的写入路径, sceneData.json记录的是场景中的其他信息, 如场景描述、场景密码等可配置信息
  160. let sceneJsonPath = path.join(__dirname, `tmpData/${sceneCode}/static/images/images${sceneCode}/sceneData.json`);
  161. // code.txt的写入路径, 其记录的是场景码, 本地浏览器需要读取该文件
  162. let sceneCodePath = path.join(__dirname, `tmpData/${sceneCode}/code.txt`);
  163. fs.writeFile(sceneJsonPath, that.sceneInfo, function (err) { // 写入sceneInfo.json
  164. if (err) {
  165. return console.log(`写入文件出错: ${err}`);
  166. }
  167. fs.writeFile(sceneCodePath , sceneCode , function (err) { // 将场景码写入
  168. console.log('开始压缩' + that.sceneCode)
  169. // let zipStream = fs.createWriteStream(`${__dirname}/tmpData/${that.sceneCode}/` + `/localData.zip`);
  170. let zipStream = fs.createWriteStream(`${__dirname}/tmpData/zip/` + `/${that.sceneCode}.zip`); // 创建压缩包
  171. pathConfig.localDataName = that.sceneCode;
  172. let archive = archiver('zip', {
  173. zlib: {
  174. level: 9 // 压缩等级
  175. }
  176. });
  177. zipStream.on('close', function () {
  178. let respData = 'archiver has been finalized and the output file descriptor has closed.';
  179. console.log(archive.pointer() + ' total bytes');
  180. console.log('文件压缩已写入完成');
  181. // 重置两个进度
  182. downloadPercent = 0;
  183. compressPercent = 0;
  184. });
  185. // 计算压缩进度
  186. archive.on('progress', function (process) {
  187. compressPercent = that.zipProcess = process.fs.processedBytes / process.fs.totalBytes * 100;
  188. })
  189. archive.on('error', function (err) {
  190. console.log(err);
  191. })
  192. archive.pipe(zipStream);
  193. archive.directory(`${__dirname}/tmpData/${that.sceneCode}/`, false); // 将已经下载完成的全部内容放入至压缩流中
  194. archive.directory(`${__dirname}/static/page`, false); // 追加html文件
  195. archive.directory(`${__dirname}/browser/`, false); // 将浏览器放入压缩流中
  196. archive.finalize();
  197. })
  198. })
  199. }
  200. })
  201. this.curActiveThread++; // 激活的线程数+1
  202. }
  203. }
  204. downloader.prototype.execute = function (data) {
  205. console.log(data);
  206. this.createRootFold(data);
  207. pathConfig.paths.forEach(path => {
  208. this.listDir(path + data, `static/`);
  209. })
  210. pathConfig.staticPath.forEach(path => {
  211. this.listDir(path, '');
  212. })
  213. }
  214. /**
  215. * 起始函数
  216. * @param {*} response 响应
  217. * @param {*} data 前端传过来的数据
  218. */
  219. function start(response, data) {
  220. let sceneCode = data.sceneCode;
  221. let respData;
  222. // 如果已经存在压缩包, 则直接返回压缩包的路径给浏览器进行下载
  223. if (fs.existsSync(`${__dirname}/${pathConfig.rootFold}/${sceneCode}/${pathConfig.localDataName}.zip`)) {
  224. respData = {
  225. sta: 1003, // 1003 文件已存在
  226. data: {
  227. percent: 100,
  228. url: `${pathConfig.serverPrefix}${pathConfig.rootFold}/${sceneCode}/${pathConfig.localDataName}.zip`
  229. },
  230. msg: '文件已存在, 直接返回url'
  231. };
  232. console.log('文件已存在, 直接返回url');
  233. } else {
  234. respData = {
  235. sta: 1002, // 1002
  236. data: {
  237. percent: 0
  238. },
  239. msg: '已加入任务队列'
  240. };
  241. taskCodeQueue.push(sceneCode);
  242. taskDownloaderQueue[sceneCode] = new downloader(); // 对于每个前端发来的下载请求, 创建一个downloader实例
  243. taskDownloaderQueue[sceneCode].sceneCode = data.sceneCode;
  244. taskDownloaderQueue[sceneCode].sceneInfo = data.sceneInfo; // sceneInfo就是场景中其他的可配置信息, 用于写入sceneData.json中
  245. }
  246. let json = JSON.stringify(respData)
  247. response.send(json);
  248. }
  249. /**
  250. * 查询下载进度
  251. * @param {*} response 响应
  252. * @param {*} data 前端传过来的数据
  253. */
  254. function downloadProcess(response, data) {
  255. let sceneCode = data.sceneCode;
  256. let downloader = taskDownloaderQueue[sceneCode];
  257. if (!downloader) return;
  258. if (downloader.downloadProcess < 100) { // 文件正在下载中
  259. let respData = {
  260. sta: 1000, // 状态码 1000-文件正在下载 1001-文件正在压缩
  261. data: {
  262. percent: downloader.downloadProcess
  263. },
  264. msg: '文件下载中'
  265. };
  266. let json = JSON.stringify(respData)
  267. response.send(json);
  268. } else { // 文件正在压缩中
  269. let respData = {
  270. sta: 1001, // 状态码 1000-文件正在下载 1001-文件正在压缩
  271. data: {
  272. percent: downloader.zipProcess
  273. },
  274. msg: '文件压缩中'
  275. };
  276. if (downloader.zipProcess === 100) {
  277. respData.data.url = `${pathConfig.serverPrefix}${pathConfig.rootFold}/zip/${pathConfig.localDataName}.zip`
  278. }
  279. let json = JSON.stringify(respData)
  280. response.send(json);
  281. }
  282. }
  283. (function () {
  284. // 服务器的downloader队列, 每隔1s处理一个前端的下载请求, 避免服务器压力过大
  285. setInterval(function () {
  286. let taskCode = taskCodeQueue.shift();
  287. if (taskCode) {
  288. taskDownloaderQueue[taskCode].execute(taskCode);
  289. }
  290. }, 1000)
  291. })()
  292. exports.start = start;
  293. exports.downloadProcess = downloadProcess;