'use strict'; const _ = require('lodash'); const fs = require('fs'); const Excel = require('exceljs'); const { CrudService } = require('naf-framework-mongoose/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; const moment = require('moment'); const nodemailer = require('nodemailer'); const docx = require('docx'); const archiver = require('archiver'); const { ObjectId } = require('mongoose').Types; class UtilService extends CrudService { constructor(ctx) { super(ctx); this.mq = this.ctx.mq; } async updatedate() { let date = new Date(); date = moment(date).format('YYYY-MM-DD HH:mm:ss'); return date; } async sendMail(email, subject, text, html) { const setting = await this.ctx.model.Setting.findOne(); let user_email = this.ctx.app.config.user_email; let auth_code = this.ctx.app.config.auth_code; if (setting) { user_email = setting.user_email; auth_code = setting.auth_code; } const transporter = nodemailer.createTransport({ host: 'smtp.exmail.qq.com', secureConnection: true, port: 465, auth: { user: user_email, // 账号 pass: auth_code, // 授权码 }, }); if (process.env.NODE_ENV === 'development') email = '402788946@qq.com'; const mailOptions = { from: user_email, // 发送者,与上面的user一致 to: email, // 接收者,可以同时发送多个,以逗号隔开 subject, // 标题 text, // 文本 html, }; try { await transporter.sendMail(mailOptions); return true; } catch (err) { return false; } } async findone({ modelname }, data) { // 查询单条 const _model = _.capitalize(modelname); const res = await this.ctx.model[_model].findOne({ ...data }); return res; } async findbyids({ modelname }, { data }) { // 共通批量查询方法 const _model = _.capitalize(modelname); const res = []; for (const elm of data) { const result = await this.ctx.model[_model].findById(elm); res.push(result); } return res; } async findmodel({ modelname }) { const _model = _.capitalize(modelname); const data = this.ctx.model[_model].prototype.schema.obj; const keys = Object.keys(data); const res = {}; for (const k of keys) { const obj = data[k]; if (_.get(obj, 'zh')) res[k] = obj; } return res; } async utilMethod(query, body) { const crypto = require('crypto'); const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem', }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'sks_secret', }, }); console.log(publicKey); console.log('-------------------'); console.log(privateKey); // const privateKey = `-----BEGIN ENCRYPTED PRIVATE KEY----- // MIIJrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI+SbyJ6RzQY8CAggA // MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBACELvWKbd/L0zagGXmrwrKBIIJ // UBn774mFtXJgWi9NVh+GO5CpcqDxU2JjP+lOILG0k9EIJiwVBH++LHtJX4S9Y7MP // qtJQZBLSdyjh7mzMige/xTSEkF2wGactP1QE2imfcT52NoHGQVyeFBfa+ZEZixPU // h/6uqS8TjABU561I3A/89vHYLrZ9va1T8AZTp4NbyUaD5zYCPUG+mn9Ln9PO56Bk // HEUXfZa2qGU819CxC2Ju4T/KMLDXHKVbbCiDnB1vLxuO4vSd8719nwR9czfTkGKv // MJtk7wbFUrstmEVFBM931D6wNT/BHtYGCf2xw10CwtSjLKbB/iINqH5CUYMjA6H8 // 4ts7nBHkmkuRa5bGZSHeCR25fLE6i/WU0mAB4gnaU3nSmInA2JF5QFKV/2+RsZBN // DCAU8TwjnsY4dsqwC0CjUPY3EguiuXAVZ3eQO43ERIVuK6flfY1vaK1186KAfLf9 // 9dYMcoag8ohs22etBiFVQZMy8YTC7knndyMyP+c/ohgENzUc1lPVHj1n0kGW2Mgu // Gc4mLXTmifr3SM/Z5cUI97i/OZ1aYm01G2UiR2ZLLdLSwuBoqs2J1r5Xk97BqaPm // f93PgENfafd7Sd+RH1f635FMKM61NbZBzNMDJmKW1B4RgezLgxanJy5AqJNGfiuS // 9m0daXKPEhAhh+Hx5LEYXxuSVgaSAJtvOZze5yHA/kHEy5+DZ5Gv31brEVHmAphA // EFSdtDbf44hqGMk2LAeiahkqc+MXz+RVFAvuh24qR8NeulIszu5ExHM13ZVL1GIP // IqnplKnX/Xjt8j1CJOxgiHJvqcX3LAg0NDh0CbmpSJVjjL/yqxwYMBSiLgS8ViT+ // AaGqLvuT95Oyq7IiMDngymTMT4ilWjeY+ovFWC3JxwCL8kzFVFBL680JhOlvBVft // e9+JbJeV3xAq482FolBRXla6Eqe1eeFVjmY8+n/UqepT8CLk7hJxzN0VCVJsx7VT // wlBjRobcIdaLtwJllu7Ebpc2MwLVgPJvvNHigLJVNW5SyHhleT2pJZDROgm+0itc // 39L8MN5Jo/q/wqO1KO+Fx17xNiMhQr7rs+9zcy8a1/D8v0zmNbpayWx8z0V6TyYJ // 2QhQAPuO7mnxsbzZPEnj93+2ifoojU3YOgvxQCUR4LNbreScSotJmX8ADi9rpmgp // VxSUSpZ9smzX2kVMPQPWCOuNzuCJIGPmwI1+2wSGgYkPW1hjVReQTevrIt6YMKxh // 7NuiJ9Hzi96uGAlcfPPp85wR2NUGJ+++lu6874AHa4nfwrxBqps/e/IFgbxfOXoo // ZS1thHfViSlPeV6+gv2OdBb6mZbMlH2UWjKuXmvYPSKBy/PA3Kl9gyYbOnpBb7Cz // Bt0jpx0oJtWmndzXpRRDOj/ehppSe1tbhe3D1Eg1EOn3C0IS284dQ2JzMATbdxV3 // okrKYcIxuYvCVNjgAo+GdfC+V3A4c15HlPjYsgy8hLIZGWbfFvNIFPNw7LmxAG6w // UD+EAFKtijRV/vKjMraiVfaB8LQ+QLgBkgE+8JFRkFUmwzCWB2KoewHPUbk+7/a1 // 7PQx0+sk+ghIb6lkC0EOcAFjfDiQYJttZ/ax3txR76hHNHOcMKeFp5DgwBzlvllR // BHKRDHflyr6CGf9gTMw83xwHp9HCpz/BleBLvoMd23FZ/BXkHlBWUusfLlUj+iNp // uQuM/xXNZ9JTixibCcQzC5KFJsOcnGWkoID/nT22iR4G2nqivHcbq3GM/s+Ty9zQ // q7Pot2cCkPOSuacyuZxrBsWmSGVibMnYEDcWs4N3Vsgw+hrk886VLDNEt7bDwy83 // KQflDKNSwEeav6NceWywmMWP6lQQoCPQtuUxdBpJ/vanrEOU8Tg5/PNiqnqtqtN2 // Z6g3ZNyGFaYXkzxizznrIaH81HUmC5wVHmozzH7FiLqIt2yj+IbOBD9T4KiarnQX // zsgR+eYbHUO7wzOvlMj0O3n1jMHK01gBe5IlGcPW/n4/mUp8qACgVipKhO45NxL8 // u9+BcPoBNCFD23sCnjUHChGHEhfNyQutjXyFUzmIkni4M5idudgri7KDHAdt/Sid // 6X2/R+b/p5zK9DqqtaEeRyQutzBJsCHaup9w46ySuen+NdfbCzITvaDpOpQJua9x // 4R+YEE6h31tlp13whR3wKNs7nrpS9oRQo3tXKIzDSXYZM81DuSR1fJ+6H9yAfkg5 // 4ScerEFSU+nvwzx5Gpj8AfDQHKHOAb6uGjGMp2bDhIWqqYJZS5JkcqF3CNr7C6EE // A8iGy/QisX2cxquT2uKjjoIVSVVQKjrV4LI3f13bHEJ5I6RBrSWNic7WAKBkjxTH // OPtuz2cgL5KAs/hvhMqVaEmeGvC9mW0jy3S0rFFMZapb8OFfFM2fEVNNzMvVy2Gd // RAZMPY4hzyqHzlLDUOF8fGa+Tly6cG0Rjvt+EsGy7w3yhR4FEUD71T2Hdh4Qlj+f // J1TDw6uEHFoYZQB9mRM+b7zDZX6Q7f4Yqk8Zp4bmlvdvoPtFS8ai8qosHDKZfuJe // 5yRQhx7GUL6se2kzcb12ZvHWdq4i9DRZ9mamcr4lnqWc1lWF/yPxow42fcGEYiSn // fg458CL16dV2JMrlNI67KX/oxKQbtI8Qqow9GA0xHT9APeRHDwQH9hhPE+I9nzjk // vXiKSk1CyQcMFJv63Jelq9v9SlM4OhbLM6BnBMCc5UqZ5y7plN+j+1y07+TVB+qE // 1b8WXHKkWNm1PyUkX57amLjHzvk/z7yIAWQV35ZuZe0Hq65QfNuG8T6aMTNEUEVj // ciuYB1KM5XhtTJvn/EyDktpCMvPF5X++uT6Pq2dbHUSVng/JHWm8qOStBnzosvHR // Ebmw4uSELfqrIONBid3rUNJ0HuLhm9IcSspj+kmAkumOjX8tQ/GER/3DKs7phjir // sCAQBf9BxnJUICP8jbr+OYXfJs6FsPNrtEZOg+78BdFQpWGgLptUWRTjHUPR3bb2 // J2i7ADAgW2t6eh+bhJ5JyDQ0xqGRVx57mHOvAlKkaa/GNMVexTqEH4eybilsQCpY // jEtwj9SJMe5jD3J4hzAQUaeOfyruuZ2R2nC1C/070TRL6qXf6DNnWeXiYIOuYW36 // ere7RirrD3vwbgu1jzsixYgGSRXLYis42brchhzw7+9Ut3vvQCMc03aO3fe+6Aji // WZo3nW1v3vEf3fS4fFtQF7VP1a2A0Leawv7ZxXUS2A8S // -----END ENCRYPTED PRIVATE KEY-----`; // const publicKey = `-----BEGIN PUBLIC KEY----- // MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzGSOl6+oYHZyHRzJYtC3 // wlqczFs2QKHLi6bnQj1Sb6J4Kx0UkJqb2yJpyFenBTjW+W4rMM7bt9RzM4pHqjWI // ubTDFL8aU8R1LxOI/9HqpC9CTyZpmGjrCZoJz07sMz/RNo6e8nwpAdEN3Z8sSaGD // Wm+Jv8uk8NtXDksyXa9b1aAylxMCIu+yg1V+QvAqfxFW2HHQodjE1/8/QNMqPr90 // DoCS35atn5vl6XQyHY40jk5r6it/zr7IBkuZnPJHSL3Mcvf2lZXYSg4Q2TQ9q4G5 // zx4UsYK3oPI/bP4+KK2o+nmdHjP+YlWpgIjMOZ7bYBu90APHUaRskRAwbruBdN0v // UQuUvrZ+ePptXBwvqDycCwF0VVYSUKWC0rCkk+zGNiramsILRGEbkHZ0OCIW6vs1 // aijKiUbDLVFXRjzwKFlrczt0DszEZDvBbVU6aaia1fvHoMTa9Qh48IL7czncgwc+ // 7n8IxyiE69PAB/Y54AoNbANsAjbHJKL5POE3g4BfJfvzGStTGHT08tdMwfyWlKa4 // V1f9/W1PiPvr018oTyEpjYuitA1AsslJWce2PysVQc5w4EVJHMSz9feqNYv8eFSD // rEXSHLXwtK0TK8asdX13zi2klGe6zpkejAuLVq+PUbMV6RzTLue5qlOp/pwGuVFM // Z9vYX8dtRkmfeDitSjRTpEkCAwEAAQ== // -----END PUBLIC KEY-----`; const data = { code: '1', org: '2', type: '中心管理员' }; const enc = crypto.publicEncrypt(publicKey, Buffer.from(JSON.stringify(data))); const str = enc.toString('base64'); console.log(str); const sb = Buffer.from(str, 'base64'); const decpub = crypto.privateDecrypt( { key: privateKey, passphrase: 'sks_secret', }, sb ); console.log(`decpub:${decpub.toString()}`); // for (const trainPlan of allTranPlan) { // const { termnum = [] } = trainPlan; // const termids = termnum.map(f => ObjectId(f._id).toString()); // // 查出该期所有的申请 // const termApplys = await applyModel.find({ termid: termids }).lean(); // const needDeletes = termApplys.filter(f => !(allSubject.find(s => s._id === f.subid) && allTeacher.find(t => t._id === f.teacherid))); // const deleteIds = needDeletes.map(i => i._id); // console.log(deleteIds.length); // // await applyModel.deleteMany({ _id: deleteIds }); // } // // // 重置班级 // // 找到要删除的学生 // const res = await this.ctx.model.Student.find({ school_name: '吉林职业技术学院', planid: '60769b703cead37068645fb2', termid: { $ne: '60779c0b2ec7ac704ce301ab' } }); // // const test = res.map(f => { // // return { name: f.name, openid: f.openid, id: f._id }; // // }); // const ids = res.map(i => i._id); // await this.ctx.model.User.deleteMany({ uid: ids }); // await this.ctx.model.Student.deleteMany({ school_name: '吉林职业技术学院', planid: '60769b703cead37068645fb2', termid: { $ne: '60779c0b2ec7ac704ce301ab' } }); // const ids = res.map(i => i.openid); // // console.log(ids); // // console.log(userRes.length); // const { planid, termid, batchid, classid } = query; // const filters = {}; // if (classid) filters.classid = classid; // else if (batchid)filters.batchid = batchid; // else if (termid)filters.termid = termid; // else if (planid)filters.planid = planid; // else throw new BusinessError(ErrorCode.BADPARAM, '需要重置范围'); // await this.ctx.model.Student.updateMany(filters, { classid: undefined }); // // 重置班级结束 } async getQueryOptions({ skip, limit, sort, desc } = {}) { if (sort && _.isString(sort)) { sort = { [sort]: desc ? -1 : 1 }; } else if (sort && _.isArray(sort)) { sort = sort.map(f => ({ [f]: desc ? -1 : 1 })).reduce((p, c) => ({ ...p, ...c }), {}); } return { skip, limit, sort }; } /** * 更新进度,状态 不存在/完成以外(不是2的值)的数值, 不添加参数,只更新进度 * @param {String} missionid 任务id * @param {String} progress 进度(人为划分) * @param {String} status 状态 * @param {Object} params 参数 */ async updateProcess(missionid, progress, status, params) { try { if (!missionid) return; const { baseUrl } = _.get(this.ctx.app.config, 'mission'); let url = `${baseUrl}`; let data; if (!baseUrl) return; if (!status || status !== '2') { url = `${url}/api/mission/progress`; data = { id: missionid, progress }; } else if (status === '3') { url = `${url}/api/mission/update/${missionid}`; data = { status }; } else { url = `${url}/api/mission/update/${missionid}`; data = { progress: '100', params, status }; } await this.ctx.curl(url, { method: 'post', headers: { 'content-type': 'application/json', }, data, dataType: 'json', }); } catch (error) { console.error(`任务更新进度报错,missionid:${missionid}\n`); } } async teacherImport() { // const filepath = './teacherlist.xlsx'; // const workbook = new Excel.Workbook(); // await workbook.xlsx.readFile(filepath); // const worksheet = workbook.getWorksheet(1); // if (!worksheet) return; // let arr = []; // worksheet.eachRow((row, ri) => { // if (ri !== 1) { // const obj = {}; // obj.name = row.getCell(3).value || undefined; // obj.department = row.getCell(4).value || undefined; // if (row.getCell(5).value) obj.job = row.getCell(5).value; // obj.phone = row.getCell(6).value || undefined; // obj.status = '4'; // arr.push(obj); // } // }); // // 检查谁生成过了, user表和teacher表 // let ur = await this.ctx.model.User.find({ mobile: { $in: arr.map(i => i.phone) }, type: '3' }); // let tr = await this.ctx.model.Teacher.find({ phone: { $in: arr.map(i => i.phone) } }); // // 将有的老师过滤出去 // if (ur) { // ur = JSON.parse(JSON.stringify(ur)); // arr = arr.filter(f => !ur.find(uf => `${uf.mobile}` === `${f.phone}`)); // } // if (tr) { // tr = JSON.parse(JSON.stringify(tr)); // arr = arr.filter(f => !(tr.find(tf => `${tf.phone}` === `${f.phone}`))); // } // for (const tea of arr) { // const ctr = await this.ctx.model.Teacher.create(tea); // if (ctr) { // const obj = { name: tea.name, mobile: tea.phone, type: '3', uid: ctr._id }; // const cur = await this.ctx.model.User.create(obj); // } // } // const user = await this.ctx.model.User.find({ passwd: { $exists: false } }); // for (const u of user) { // u.passwd = { secret: '12345678' }; // u.save(); // } } /** * 导出excel * @param {Array} dataList 数据集合 * @param {Array} meta 表头(可以没有) * @param {String} fn 文件名 * @param {Array} opera 单元格操作 */ async toExcel(dataList, meta, fn = '导出结果', opera) { // 导出excel const { app } = this; const nowDate = new Date().getTime(); const filename = `${fn}-${nowDate}.xlsx`; // 取出预设存储地址 const rootPath = `${app.config.cdn.repos_root_path}`; const rooturl = `${app.config.cdn.repos_root_url_excel}`; const path = `${rootPath}${rooturl}`; if (!path) { throw new BusinessError(ErrorCode.BUSINESS, '服务端没有设置存储路径'); } // if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\'; if (!fs.existsSync(path)) { // 如果不存在文件夹,就创建 fs.mkdirSync(path); } // 生成文件 const filepath = `${path}${filename}`; fs.createWriteStream(filepath); const workbook = new Excel.Workbook(); const sheet = workbook.addWorksheet('sheet'); if (meta) sheet.columns = meta; sheet.addRows(dataList); if (_.isArray(opera)) { for (const o of opera) { const { startRow, startCol, endRow, endCol } = o; sheet.mergeCells(startRow, startCol, endRow, endCol); } } // 垂直居中 const length = dataList.length; const alignment = { vertical: 'middle', horizontal: 'center' }; for (let i = 1; i <= length; i++) { sheet.getRow(i).alignment = alignment; } await workbook.xlsx.writeFile(filepath); return `/files/excel/${filename}`; } /** * 创建/重复写入excel * @param {Array} dataList 数据 * @param {String} fn 文件名, 第一次进入时,只是单纯的名,第二次开始,有后缀与时间戳 * @param {String} downloadPath 下载文件路径 * @param {String} type 方向,没有默认横向 */ async toAsyncExcel(dataList, fn = '导出结果', downloadPath, type) { const { app } = this; const workbook = new Excel.Workbook(); let sheet; // 取出预设存储地址 const rootPath = `${app.config.cdn.repos_root_path}`; const rooturl = `${app.config.cdn.repos_root_url_excel}`; let path = `${rootPath}${rooturl}`; let filepath = ''; if (!path) { throw new BusinessError(ErrorCode.BUSINESS, '服务端没有设置存储路径'); } if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\excel\\'; if (!fs.existsSync(path)) { // 如果不存在文件夹,就创建 fs.mkdirSync(path); } if (!downloadPath) { // 第一次进入,文件还未生成 const nowDate = new Date().getTime(); fn = `${fn}-${nowDate}.xlsx`; sheet = workbook.addWorksheet('sheet'); } else { let domain = 'http://127.0.0.1'; if (process.env.NODE_ENV === 'development') domain = `${domain}:8000`; const file = await this.ctx.curl(`${domain}${downloadPath}`); if (!(file && file.data)) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找导出的excel'); } // 读取文件 await workbook.xlsx.load(file.data); sheet = workbook.getWorksheet('sheet'); } if (!type || type === 'horizontal') sheet.addRows(dataList); else if (type === 'vertical') { for (let i = 1; i <= dataList.length; i++) { const element = dataList[i - 1]; const rows = sheet.getRow(i); if (rows.values.length <= 0) rows.values = element; else rows.values = rows.values.concat(element); rows.commit(); } } filepath = `${path}${fn}`; await workbook.xlsx.writeFile(filepath); return { downloadPath: `/files/excel/${fn}`, fn }; } /** * 导出docx * @param {Array} data 数据[{title,content([]),author}] * @param {String} fn 文件名 */ async toDocx(data, fn = '培训心得') { const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } = docx; const doc = new Document(); const children = []; // 整理数据 for (let i = 0; i < data.length; i++) { const obj = data[i]; const { title, content, author } = obj; const c = []; if (title) { const tit = new Paragraph({ children: [ new TextRun({ text: title, bold: true }) ], heading: HeadingLevel.TITLE, alignment: AlignmentType.CENTER, }); c.push(tit); } if (author) { const auth = new Paragraph({ children: [ new TextRun({ color: '#000000', text: author }) ], heading: HeadingLevel.HEADING_2, alignment: AlignmentType.RIGHT, }); c.push(auth); } if (content && _.isArray(content) && content.length > 0) { for (const cont of content) { const p = new Paragraph({ children: [ new TextRun({ text: cont, bold: true }) ], }); c.push(p); } } if (i !== data.length - 1) { // 换页 const last = new Paragraph({ pageBreakBefore: true, }); c.push(last); } if (c.length > 0) children.push(...c); } doc.addSection({ properties: {}, children, }); const { app } = this; const rootPath = `${app.config.cdn.repos_root_path}`; const rooturl = `${app.config.cdn.repos_root_url_experience}`; const path = `${rootPath}${rooturl}`; // 如果不存在文件夹,就创建 // if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\'; if (!fs.existsSync(path)) { fs.mkdirSync(path); } const num = new Date().getTime(); const buffer = await Packer.toBuffer(doc); fs.writeFileSync(`${path}${fn}-${num}.docx`, buffer); // Packer.toBuffer(doc).then(buffer => { // fs.writeFileSync(`${path}${fn}-${num}.docx`, buffer); // }); return `/files${rooturl}${fn}-${num}.docx`; } /** * 将选择的文件导出到zip压缩包中,提供下载 * @param {*} fileList 需要导入到zip中的列表,格式有2中: [{url:""}]或[String] * @param {*} fn 文件名,默认为 "导出结果" */ async toZip(fileList, fn = '导出结果') { if (!_.isArray(fileList)) { throw new BusinessError(ErrorCode.DATA_INVALID, '需要压缩的文件数据格式错误'); } fn = `${fn}.zip`; // zip文件夹创建 const { app } = this; const rootPath = `${app.config.cdn.repos_root_path}`; const zipPath = `${app.config.cdn.repos_root_url_zip}`; let path = `${rootPath}${zipPath}`; if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\'; if (!fs.existsSync(path)) { fs.mkdirSync(path); } // 文件请求后将数据整理到这里 const resetFileList = []; for (const file of fileList) { let uri = ''; let filename = ''; let prefixs; if (_.isString(file)) { uri = file; const arr = file.split('/'); const last = _.last(arr); if (last) filename = last; } else if (_.isObject(file)) { const { uri: furi, url: furl, name, prefix } = file; if (furi) uri = furi; else if (furl) uri = furl; if (name) filename = name; else { const arr = uri.split('/'); const last = _.last(arr); if (last) filename = last; } if (prefix) prefixs = prefix; } const obj = {}; if (uri) obj.uri = uri; if (filename) obj.filename = filename; if (prefixs) obj.prefix = prefixs; resetFileList.push(obj); } // 导出 const output = fs.createWriteStream(`${path}${fn}`); const archive = archiver('zip', { zlib: { level: 9 }, }); archive.pipe(output); // 请求文件,追加进压缩包 for (const file of resetFileList) { const { uri, filename, prefix } = file; const res = await this.ctx.curl(`http://127.0.0.1${uri}`); if (res && res.data) { const options = {}; if (filename) options.name = filename; if (prefix) options.prefix = prefix; if (filename) { archive.append(res.data, options); } } } await archive.finalize(); return `/files${zipPath}${fn}`; } } module.exports = UtilService;