downloadController.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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 cutArr = [
  11. {
  12. name:'2k_face',
  13. size:'2048',
  14. arr:[
  15. '0','511','1023','1535'
  16. ]
  17. },
  18. {
  19. name:'1k_face',
  20. size:'1024',
  21. arr:[
  22. '0','511'
  23. ]
  24. },
  25. {
  26. name:'512_face',
  27. size:'512',
  28. arr:['0']
  29. }
  30. ]
  31. let pathConfig = {
  32. ossPrefix: 'https://hongwei-4dkankan.oss-cn-shenzhen.aliyuncs.com/', // oss前缀
  33. // serverPrefix: 'http://192.168.0.208:8887/', // 服务器前缀
  34. serverPrefix: 'https://www.ysdli.com/download_scene/controller/', // 服务器前缀
  35. localDataName: 'localData',
  36. layer: './', // 层级
  37. rootFold: 'tmpData', // 根目录文件夹
  38. staticPath: [ // 静态数据路径
  39. 'v3local'
  40. ],
  41. paths: [ // images/data路径
  42. 'images/images',
  43. 'data/data',
  44. 'voice/voice',
  45. 'video/video'
  46. ],
  47. transforms:[
  48. 'images/images',
  49. ],
  50. editPaths:[
  51. 'images/images',
  52. 'data/data',
  53. 'voice/voice'
  54. ],
  55. filters: [],
  56. downloadUrlServer:'https://www.ysdli.com/'
  57. }
  58. function downloader() {
  59. this.objArr = [];
  60. this.completeFolds = 0; // 记录已经递归完成的目录, completeFolds == foldNum, 表示所有目录递归完成
  61. this.fileNum = 0; // 需要下载的文件总数
  62. this.completeFileNum = 0; // 已经下载完成的文件总数
  63. this.threadNum = 10; // 同时并发的线程总数
  64. this.curActiveThread = 0;
  65. this.sta = 0;
  66. this.downloadProcess = 0; // 下载进度
  67. this.zipProcess = 0; // 压缩进度
  68. this.sceneCode = ''; // 场景码
  69. this.sceneCodeArr = [] // 场景码数据
  70. this.snCode = '' //sn码
  71. this.isTiles = true; // 是否下载tiles
  72. this.sceneInfo = ''; // 场景的其他json数据
  73. this.downloadResponse = null;
  74. this.timer = null; // 定时器
  75. this.dirPath = path.join(__dirname, pathConfig.rootFold);
  76. this.foldNum = pathConfig.paths.length + pathConfig.staticPath.length; // 记录目录的总数, 用于判断是否目录是否递归完成
  77. this.client = new OSS({ // 配置阿里云oss
  78. region: 'oss-cn-shenzhen',
  79. accessKeyId: 'LTAIUrvuHqj8pvry',
  80. accessKeySecret: 'JLOVl0k8Ke0aaM8nLMMiUAZ3EiiqI4',
  81. bucket: 'hongwei-4dkankan',
  82. })
  83. }
  84. /**
  85. * 创建文件夹
  86. * @param data 文件路径
  87. */
  88. downloader.prototype.createRootFold = function (data) {
  89. if (!fs.existsSync(this.dirPath)) {
  90. fs.mkdirSync(this.dirPath);
  91. } else {
  92. // console.log('文件夹已存在');
  93. }
  94. this.dirPath = path.join(this.dirPath, '/' + data);
  95. // 创建文件夹目录
  96. if (!fs.existsSync(this.dirPath)) {
  97. fs.mkdirSync(this.dirPath);
  98. } else {
  99. // console.log('文件夹已存在');
  100. }
  101. console.log('-------dirPath---------',this.dirPath)
  102. }
  103. /**
  104. * 递归列举所有瓦片图, 转化为本地可运行图片
  105. */
  106. downloader.prototype.transformImg = async function (dir,prefixDir) {
  107. let that = this;
  108. dirArr = `${prefixDir}${dir}`.split('/'); // 分割其目录层级
  109. let tmpDir = '';
  110. let childDirPath;
  111. console.log(dirArr);
  112. dirArr.forEach((item) => { // 对于路径中的每一个层级, 都创建对应的文件夹
  113. tmpDir += '/' + item;
  114. childDirPath = path.join(this.dirPath, '/' + tmpDir); // 子路径
  115. if (!fs.existsSync(childDirPath)) {
  116. fs.mkdirSync(childDirPath);
  117. // console.log('创建文件夹成功: ' + childDirPath);
  118. } else {
  119. // console.log('文件夹已存在');
  120. }
  121. });
  122. // 列举当前文件夹中的所有文件, 最多1000个
  123. let result = await this.client.list({
  124. prefix: dir,
  125. delimiter: '/',
  126. 'max-keys': 1000 // 最大限制1000
  127. });
  128. // console.log(result);
  129. // result.objects就是当前文件夹下的所有文件
  130. result.objects && result.objects.forEach(async function (obj) {
  131. // console.log(obj);
  132. cutArr.forEach(async function (sub) {
  133. // ?x-oss-process=image/resize,m_lfit,w_2048/crop,w_512,h_512,x_511,y_511&imagesVersion=0
  134. let imgArr = []
  135. let cutting = sub.arr
  136. for (let i = 0; i < cutting.length; i++) {
  137. let ei = cutting[i]
  138. for (let j = 0; j < cutting.length; j++) {
  139. let ej = cutting[j]
  140. imgArr.push({ei,ej,i,j})
  141. }
  142. }
  143. imgArr.forEach(async function (item) {
  144. let downloadURL = pathConfig.ossPrefix + obj.name+`?x-oss-process=image/resize,m_lfit,w_${sub.size}/crop,w_512,h_512,x_${item.ei},y_${item.ej}&imagesVersion=0`; // 记录每个文件需要下载的url
  145. let arr = obj.name.split('/');
  146. let objName = arr[arr.length - 1]; // 获取文件名, 上面的obj.name除了文件名, 还包含了文件所属的路径
  147. let i,h, tmp = objName.split('_skybox')
  148. i = tmp[0]
  149. h = tmp[1].split('.')[0]
  150. let writePath = childDirPath.split('2k')[0] + `${i}`
  151. if (!fs.existsSync(writePath)) {
  152. fs.mkdirSync(writePath);
  153. }
  154. if (objName === '') { // 过滤非法文件, 因为阿里云有时候会把文件夹当作文件返回,
  155. return;
  156. }
  157. let writeURL = path.join(writePath, `/${sub.name}${h}_${item.i}_${item.j}.${tmp[1].split('.')[1]}`); // 下载完成后, 应当写入的路径
  158. let tack = { // 创建下载任务
  159. downloadURL: encodeURI(downloadURL),
  160. writeURL: writeURL
  161. }
  162. // console.log(tack);
  163. that.objArr.push(tack); // 下载任务入列
  164. })
  165. })
  166. });
  167. if (result.prefixes) { // 如果当前目录还有子目录存在, 则继续递归
  168. this.foldNum--; // 移除当前目录, 因为当前目录还不是最深层级
  169. let that = this;
  170. result.prefixes.forEach(function (subDir) {
  171. that.foldNum++; // 记录当前目录的子目录
  172. that.transformImg(subDir, prefixDir);
  173. });
  174. } else { // 当前目录已经递归完成
  175. this.completeFolds++;
  176. // console.log(this.completeFolds + ' ' + this.foldNum)
  177. if (this.completeFolds === this.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数
  178. // console.log(this.objArr)
  179. this.fileNum = this.objArr.length; // 记录需要下载的文件总数
  180. this.timer = setInterval(this.download.bind(this), 16); // 开始下载
  181. }
  182. }
  183. }
  184. /**
  185. * 递归列举所有需要下载的文件, 记录文件夹以及所有文件的总数
  186. */
  187. downloader.prototype.listDir = async function (dir, prefixDir) {
  188. let that = this;
  189. let filters = pathConfig.filters
  190. if (!that.isTiles) {
  191. filters.push('tiles')
  192. }
  193. for (let i = 0; i < filters.length; i++) { // 有些文件夹不需要下载, 则进行过滤
  194. let filter = filters[i];
  195. if (dir.indexOf(filter) > 0) {
  196. // console.log('过滤的路径' + dir);
  197. that.completeFolds++;
  198. if (that.completeFolds === that.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数
  199. that.fileNum = that.objArr.length;
  200. that.timer = setInterval(that.download, 16); // 开始下载
  201. }
  202. return;
  203. }
  204. }
  205. dirArr = `${prefixDir}${dir}`.split('/'); // 分割其目录层级
  206. let tmpDir = '';
  207. let childDirPath;
  208. dirArr.forEach((item) => { // 对于路径中的每一个层级, 都创建对应的文件夹
  209. tmpDir += '/' + item;
  210. childDirPath = path.join(this.dirPath, '/' + tmpDir); // 子路径
  211. if (!fs.existsSync(childDirPath)) {
  212. fs.mkdirSync(childDirPath);
  213. // console.log('创建文件夹成功: ' + childDirPath);
  214. } else {
  215. // console.log('文件夹已存在');
  216. }
  217. });
  218. // 列举当前文件夹中的所有文件, 最多1000个
  219. let result = await this.client.list({
  220. prefix: dir,
  221. delimiter: '/',
  222. 'max-keys': 1000 // 最大限制1000
  223. });
  224. // console.log(result);
  225. // result.objects就是当前文件夹下的所有文件
  226. result.objects && result.objects.forEach(async function (obj) {
  227. let downloadURL = pathConfig.ossPrefix + obj.name; // 记录每个文件需要下载的url
  228. let arr = obj.name.split('/');
  229. let objName = arr[arr.length - 1]; // 获取文件名, 上面的obj.name除了文件名, 还包含了文件所属的路径
  230. if (objName === '') { // 过滤非法文件, 因为阿里云有时候会把文件夹当作文件返回,
  231. return;
  232. }
  233. let writeURL = path.join(childDirPath, objName); // 下载完成后, 应当写入的路径
  234. let tack = { // 创建下载任务
  235. downloadURL: encodeURI(downloadURL),
  236. writeURL: writeURL
  237. }
  238. that.objArr.push(tack); // 下载任务入列
  239. });
  240. if (result.prefixes) { // 如果当前目录还有子目录存在, 则继续递归
  241. this.foldNum--; // 移除当前目录, 因为当前目录还不是最深层级
  242. let that = this;
  243. result.prefixes.forEach(function (subDir) {
  244. that.foldNum++; // 记录当前目录的子目录
  245. that.listDir(subDir, prefixDir);
  246. });
  247. } else { // 当前目录已经递归完成
  248. this.completeFolds++;
  249. // console.log(this.completeFolds + ' ' + this.foldNum)
  250. if (this.completeFolds === this.foldNum) { // 已经列举完成的文件夹数目等于需要下载的文件夹总数
  251. // console.log(this.objArr)
  252. this.fileNum = this.objArr.length; // 记录需要下载的文件总数
  253. this.timer = setInterval(this.download.bind(this), 16); // 开始下载
  254. }
  255. }
  256. }
  257. downloader.prototype.download = function () {
  258. if (this.objArr.length === 0) { // 所有文件已经下载完成, 清楚定时器
  259. clearInterval(this.timer);
  260. this.timer = null;
  261. return;
  262. }
  263. if (this.curActiveThread <= this.threadNum) { // 当前活跃的线程数<总线程数, 则继续创建创建下载队列, 控制并发
  264. let task = this.objArr.shift(); // 取队列中的第一个任务
  265. let stream = fs.createWriteStream(task.writeURL); // 根据任务中的写入路径创建流
  266. let readStream = request(task.downloadURL).on('error', function (e) { // 通过下载路径去请求文件
  267. console.log(e);
  268. console.log("文件[" + task.downloadURL + "]下载出错 ");
  269. }).pipe(stream);
  270. let that = this;
  271. readStream.on('finish', function () { // 当前流下载完成, 释放线程
  272. that.curActiveThread--;
  273. })
  274. stream.on('finish', function () { // 文件写入完毕
  275. that.completeFileNum++; // 已经下载完成的文件数+1
  276. that.downloadProcess = that.completeFileNum / that.fileNum * 100; // 计算下载进度
  277. if (that.completeFileNum === that.fileNum) { // 所有的oss文件已下载完
  278. let sceneCode = that.sceneCode; // 记录场景码
  279. let sceneCodeArr = that.sceneCodeArr; // 记录场景码数组
  280. let snCode = that.snCode
  281. let sceneCodePath = path.join(__dirname, `tmpData/${snCode}/code.txt`);
  282. fs.writeFileSync(sceneCodePath , '')
  283. sceneCodeArr.forEach((item,idx)=>{
  284. // sceneData.json的写入路径, sceneData.json记录的是场景中的其他信息, 如场景描述、场景密码等可配置信息
  285. let sceneJsonPath = path.join(__dirname, `tmpData/${snCode}/v3local/data/data${item.sceneCode}/getInfo.json`);
  286. // code.txt的写入路径, 其记录的是场景码, 本地浏览器需要读取该文件
  287. fs.writeFileSync(sceneJsonPath, item.sceneInfo, function (err) { // 写入sceneInfo.json
  288. if (err) {
  289. return console.log(`写入文件出错: ${err}`);
  290. }
  291. })
  292. fs.writeFileSync(sceneCodePath , item.sceneCode+(idx===sceneCodeArr.length-1?'':','),{flag:'a'}, function (err,code) { // 将场景码写入
  293. // console.log('-----code-----',code)
  294. })
  295. })
  296. // console.log('开始压缩' + that.sceneCode)
  297. // let zipStream = fs.createWriteStream(`${__dirname}/tmpData/${that.sceneCode}/` + `/localData.zip`);
  298. let zipStream = fs.createWriteStream(`${__dirname}/tmpData/zip/` + `${snCode}.zip`); // 创建压缩包
  299. pathConfig.localDataName = snCode;
  300. let archive = archiver('zip', {
  301. zlib: {
  302. level: 9 // 压缩等级
  303. }
  304. });
  305. zipStream.on('close', function () {
  306. let respData = 'archiver has been finalized and the output file descriptor has closed.';
  307. // console.log(archive.pointer() + ' total bytes');
  308. // console.log('文件压缩已写入完成');
  309. // 重置两个进度
  310. downloadPercent = 0;
  311. compressPercent = 0;
  312. });
  313. // 计算压缩进度
  314. archive.on('progress', function (process) {
  315. compressPercent = that.zipProcess = process.fs.processedBytes / process.fs.totalBytes * 100;
  316. })
  317. archive.on('error', function (err) {
  318. // console.log(err);
  319. })
  320. archive.pipe(zipStream);
  321. archive.directory(`${__dirname}/tmpData/${snCode}/`, false); // 将已经下载完成的全部内容放入至压缩流中
  322. // archive.directory(`${__dirname}/static/page`, false); // 追加html文件
  323. archive.directory(`${__dirname}/browser/`, false); // 将浏览器放入压缩流中
  324. archive.finalize();
  325. }
  326. })
  327. this.curActiveThread++; // 激活的线程数+1
  328. }
  329. }
  330. downloader.prototype.execute = function (data) {
  331. // let arr = ['t-updScXn','t-J3VGU9J']
  332. this.createRootFold(this.snCode);
  333. this.sceneCodeArr.forEach(item => {
  334. pathConfig.paths.forEach(path => {
  335. this.listDir(path + item.sceneCode, `v3local/`);
  336. })
  337. pathConfig.staticPath.forEach(path => {
  338. this.listDir(path, '');
  339. })
  340. pathConfig.transforms.forEach(path => {
  341. this.transformImg(path + item.sceneCode + '/tiles/2k', `v3local/`);
  342. })
  343. })
  344. }
  345. /**
  346. * 起始函数
  347. * @param {*} response 响应
  348. * @param {*} data 前端传过来的数据
  349. */
  350. function start(response, data) {
  351. let sceneCode = data.sceneCode;
  352. let sceneCodeArr = data.sceneCodeArr
  353. let snCode = data.snCode
  354. let respData;
  355. // 如果已经存在压缩包, 则直接返回压缩包的路径给浏览器进行下载
  356. if (fs.existsSync(`${__dirname}/${pathConfig.rootFold}/${snCode}/${pathConfig.localDataName}.zip`)) {
  357. respData = {
  358. sta: 1003, // 1003 文件已存在
  359. data: {
  360. percent: 100,
  361. url: `${pathConfig.serverPrefix}${pathConfig.rootFold}/${snCode}/${pathConfig.localDataName}.zip`
  362. },
  363. msg: '文件已存在, 直接返回url'
  364. };
  365. // console.log('文件已存在, 直接返回url');
  366. } else {
  367. respData = {
  368. sta: 1002, // 1002
  369. data: {
  370. percent: 0
  371. },
  372. msg: '已加入任务队列'
  373. };
  374. taskCodeQueue.push(snCode);
  375. taskDownloaderQueue[snCode] = new downloader(); // 对于每个前端发来的下载请求, 创建一个downloader实例
  376. // taskDownloaderQueue[snCode].sceneCode = data.sceneCode;
  377. taskDownloaderQueue[snCode].sceneCodeArr = sceneCodeArr;
  378. taskDownloaderQueue[snCode].snCode = snCode;
  379. // taskDownloaderQueue[snCode].sceneCode = data.sceneCode;
  380. taskDownloaderQueue[snCode].isTiles = data.isTiles;
  381. // taskDownloaderQueue[snCode].sceneInfo = data.sceneInfo; // sceneInfo就是场景中其他的可配置信息, 用于写入sceneData.json中
  382. }
  383. let json = JSON.stringify(respData)
  384. response.send(json);
  385. }
  386. /**
  387. * 查询下载进度
  388. * @param {*} response 响应
  389. * @param {*} data 前端传过来的数据
  390. */
  391. function downloadProcess(response, data) {
  392. let snCode = data.snCode;
  393. let downloader = taskDownloaderQueue[snCode];
  394. if (!downloader) return;
  395. if (downloader.downloadProcess < 100) { // 文件正在下载中
  396. let respData = {
  397. sta: 1000, // 状态码 1000-文件正在下载 1001-文件正在压缩
  398. data: {
  399. percent: downloader.downloadProcess
  400. },
  401. msg: '文件下载中'
  402. };
  403. let json = JSON.stringify(respData)
  404. response.send(json);
  405. } else { // 文件正在压缩中
  406. let respData = {
  407. sta: 1001, // 状态码 1000-文件正在下载 1001-文件正在压缩
  408. data: {
  409. percent: downloader.zipProcess
  410. },
  411. msg: '文件压缩中'
  412. };
  413. if (downloader.zipProcess === 100) {
  414. let zip = `${pathConfig.serverPrefix}${pathConfig.rootFold}/zip/${pathConfig.localDataName}.zip`
  415. respData.data.url = zip
  416. setTimeout(() => {
  417. fs.unlink(`${__dirname}/tmpData/zip/` + `${pathConfig.localDataName}.zip`,(err)=>{
  418. if(err)
  419. return err;
  420. console.log('删除成功');
  421. })
  422. }, 1000 * 60 * 60 * 24);
  423. }
  424. let json = JSON.stringify(respData)
  425. response.send(json);
  426. }
  427. }
  428. (function () {
  429. // 服务器的downloader队列, 每隔1s处理一个前端的下载请求, 避免服务器压力过大
  430. setInterval(function () {
  431. let taskCode = taskCodeQueue.shift();
  432. if (taskCode) {
  433. taskDownloaderQueue[taskCode].execute(taskCode);
  434. }
  435. }, 1000)
  436. })()
  437. exports.start = start;
  438. exports.downloadProcess = downloadProcess;