|
@@ -1,429 +1,447 @@
|
|
|
-'use strict';
|
|
|
-
|
|
|
-
|
|
|
-const assert = require('assert');
|
|
|
-const _ = require('lodash');
|
|
|
-const fs = require('fs');
|
|
|
-const Excel = require('exceljs');
|
|
|
-const { ObjectId } = require('mongoose').Types;
|
|
|
-const { CrudService } = require('naf-framework-mongoose/lib/service');
|
|
|
-const { BusinessError, ErrorCode } = require('naf-core').Error;
|
|
|
-const moment = require('moment');
|
|
|
-const nodemailer = require('nodemailer');
|
|
|
-const { template } = require('lodash');
|
|
|
-const docx = require('docx');
|
|
|
-const archiver = require('archiver');
|
|
|
-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, // 授权码
|
|
|
- },
|
|
|
- });
|
|
|
- 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 ch = await this.ctx.amqp.conn.createChannel();
|
|
|
- // const queue = 'hello';
|
|
|
- // ch.consume(
|
|
|
- // queue,
|
|
|
- // msg => {
|
|
|
- // console.log(' [x] Received %s', msg.content.toString());
|
|
|
- // },
|
|
|
- // { noAck: true }
|
|
|
- // );
|
|
|
- // // ch.close();
|
|
|
- }
|
|
|
- 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();
|
|
|
- // }
|
|
|
- }
|
|
|
-
|
|
|
- async toExcel(dataList, meta, fn = '导出结果') {
|
|
|
- // 导出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}`;
|
|
|
- let 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');
|
|
|
- sheet.columns = meta;
|
|
|
- sheet.addRows(dataList);
|
|
|
- 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}`;
|
|
|
- let 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;
|
|
|
+'use strict';
|
|
|
+
|
|
|
+
|
|
|
+const assert = require('assert');
|
|
|
+const _ = require('lodash');
|
|
|
+const fs = require('fs');
|
|
|
+const Excel = require('exceljs');
|
|
|
+const { ObjectId } = require('mongoose').Types;
|
|
|
+const { CrudService } = require('naf-framework-mongoose/lib/service');
|
|
|
+const { BusinessError, ErrorCode } = require('naf-core').Error;
|
|
|
+const moment = require('moment');
|
|
|
+const nodemailer = require('nodemailer');
|
|
|
+const { template } = require('lodash');
|
|
|
+const docx = require('docx');
|
|
|
+const archiver = require('archiver');
|
|
|
+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, // 授权码
|
|
|
+ },
|
|
|
+ });
|
|
|
+ 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 ch = await this.ctx.amqp.conn.createChannel();
|
|
|
+ // const queue = 'hello';
|
|
|
+ // ch.consume(
|
|
|
+ // queue,
|
|
|
+ // msg => {
|
|
|
+ // console.log(' [x] Received %s', msg.content.toString());
|
|
|
+ // },
|
|
|
+ // { noAck: true }
|
|
|
+ // );
|
|
|
+ // // ch.close();
|
|
|
+ }
|
|
|
+ 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}`;
|
|
|
+ let 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}`;
|
|
|
+ let 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;
|