util.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. 'use strict';
  2. const assert = require('assert');
  3. const _ = require('lodash');
  4. const fs = require('fs');
  5. const Excel = require('exceljs');
  6. const { ObjectId } = require('mongoose').Types;
  7. const { CrudService } = require('naf-framework-mongoose/lib/service');
  8. const { BusinessError, ErrorCode } = require('naf-core').Error;
  9. const moment = require('moment');
  10. const nodemailer = require('nodemailer');
  11. const { template } = require('lodash');
  12. const docx = require('docx');
  13. const archiver = require('archiver');
  14. class UtilService extends CrudService {
  15. constructor(ctx) {
  16. super(ctx);
  17. this.mq = this.ctx.mq;
  18. }
  19. async updatedate() {
  20. let date = new Date();
  21. date = moment(date).format('YYYY-MM-DD HH:mm:ss');
  22. return date;
  23. }
  24. async sendMail(email, subject, text, html) {
  25. const setting = await this.ctx.model.Setting.findOne();
  26. let user_email = this.ctx.app.config.user_email;
  27. let auth_code = this.ctx.app.config.auth_code;
  28. if (setting) {
  29. user_email = setting.user_email;
  30. auth_code = setting.auth_code;
  31. }
  32. const transporter = nodemailer.createTransport({
  33. host: 'smtp.exmail.qq.com',
  34. secureConnection: true,
  35. port: 465,
  36. auth: {
  37. user: user_email, // 账号
  38. pass: auth_code, // 授权码
  39. },
  40. });
  41. const mailOptions = {
  42. from: user_email, // 发送者,与上面的user一致
  43. to: email, // 接收者,可以同时发送多个,以逗号隔开
  44. subject, // 标题
  45. text, // 文本
  46. html,
  47. };
  48. try {
  49. await transporter.sendMail(mailOptions);
  50. return true;
  51. } catch (err) {
  52. return false;
  53. }
  54. }
  55. async findone({ modelname }, data) {
  56. // 查询单条
  57. const _model = _.capitalize(modelname);
  58. const res = await this.ctx.model[_model].findOne({ ...data });
  59. return res;
  60. }
  61. async findbyids({ modelname }, { data }) {
  62. // 共通批量查询方法
  63. const _model = _.capitalize(modelname);
  64. const res = [];
  65. for (const elm of data) {
  66. const result = await this.ctx.model[_model].findById(elm);
  67. res.push(result);
  68. }
  69. return res;
  70. }
  71. async findmodel({ modelname }) {
  72. const _model = _.capitalize(modelname);
  73. const data = this.ctx.model[_model].prototype.schema.obj;
  74. const keys = Object.keys(data);
  75. const res = {};
  76. for (const k of keys) {
  77. const obj = data[k];
  78. if (_.get(obj, 'zh')) res[k] = obj;
  79. }
  80. return res;
  81. }
  82. async utilMethod(query, body) {
  83. // const ch = await this.ctx.amqp.conn.createChannel();
  84. // const queue = 'hello';
  85. // ch.consume(
  86. // queue,
  87. // msg => {
  88. // console.log(' [x] Received %s', msg.content.toString());
  89. // },
  90. // { noAck: true }
  91. // );
  92. // // ch.close();
  93. }
  94. async getQueryOptions({ skip, limit, sort, desc } = {}) {
  95. if (sort && _.isString(sort)) {
  96. sort = { [sort]: desc ? -1 : 1 };
  97. } else if (sort && _.isArray(sort)) {
  98. sort = sort
  99. .map(f => ({ [f]: desc ? -1 : 1 }))
  100. .reduce((p, c) => ({ ...p, ...c }), {});
  101. }
  102. return { skip, limit, sort };
  103. }
  104. /**
  105. * 更新进度,状态 不存在/完成以外(不是2的值)的数值, 不添加参数,只更新进度
  106. * @param {String} missionid 任务id
  107. * @param {String} progress 进度(人为划分)
  108. * @param {String} status 状态
  109. * @param {Object} params 参数
  110. */
  111. async updateProcess(missionid, progress, status, params) {
  112. try {
  113. if (!missionid) return;
  114. const { baseUrl } = _.get(this.ctx.app.config, 'mission');
  115. let url = `${baseUrl}`;
  116. let data;
  117. if (!baseUrl) return;
  118. if (!status || status !== '2') {
  119. url = `${url}/api/mission/progress`;
  120. data = { id: missionid, progress };
  121. } else if (status === '3') {
  122. url = `${url}/api/mission/update/${missionid}`;
  123. data = { status };
  124. } else {
  125. url = `${url}/api/mission/update/${missionid}`;
  126. data = { progress: '100', params, status };
  127. }
  128. await this.ctx.curl(url, {
  129. method: 'post',
  130. headers: {
  131. 'content-type': 'application/json',
  132. },
  133. data,
  134. dataType: 'json',
  135. });
  136. } catch (error) {
  137. console.error(`任务更新进度报错,missionid:${missionid}\n`);
  138. }
  139. }
  140. async teacherImport() {
  141. // const filepath = './teacherlist.xlsx';
  142. // const workbook = new Excel.Workbook();
  143. // await workbook.xlsx.readFile(filepath);
  144. // const worksheet = workbook.getWorksheet(1);
  145. // if (!worksheet) return;
  146. // let arr = [];
  147. // worksheet.eachRow((row, ri) => {
  148. // if (ri !== 1) {
  149. // const obj = {};
  150. // obj.name = row.getCell(3).value || undefined;
  151. // obj.department = row.getCell(4).value || undefined;
  152. // if (row.getCell(5).value) obj.job = row.getCell(5).value;
  153. // obj.phone = row.getCell(6).value || undefined;
  154. // obj.status = '4';
  155. // arr.push(obj);
  156. // }
  157. // });
  158. // // 检查谁生成过了, user表和teacher表
  159. // let ur = await this.ctx.model.User.find({ mobile: { $in: arr.map(i => i.phone) }, type: '3' });
  160. // let tr = await this.ctx.model.Teacher.find({ phone: { $in: arr.map(i => i.phone) } });
  161. // // 将有的老师过滤出去
  162. // if (ur) {
  163. // ur = JSON.parse(JSON.stringify(ur));
  164. // arr = arr.filter(f => !ur.find(uf => `${uf.mobile}` === `${f.phone}`));
  165. // }
  166. // if (tr) {
  167. // tr = JSON.parse(JSON.stringify(tr));
  168. // arr = arr.filter(f => !(tr.find(tf => `${tf.phone}` === `${f.phone}`)));
  169. // }
  170. // for (const tea of arr) {
  171. // const ctr = await this.ctx.model.Teacher.create(tea);
  172. // if (ctr) {
  173. // const obj = { name: tea.name, mobile: tea.phone, type: '3', uid: ctr._id };
  174. // const cur = await this.ctx.model.User.create(obj);
  175. // }
  176. // }
  177. // const user = await this.ctx.model.User.find({ passwd: { $exists: false } });
  178. // for (const u of user) {
  179. // u.passwd = { secret: '12345678' };
  180. // u.save();
  181. // }
  182. }
  183. async toExcel(dataList, meta, fn = '导出结果') {
  184. // 导出excel
  185. const { app } = this;
  186. const nowDate = new Date().getTime();
  187. const filename = `${fn}-${nowDate}.xlsx`;
  188. // 取出预设存储地址
  189. const rootPath = `${app.config.cdn.repos_root_path}`;
  190. const rooturl = `${app.config.cdn.repos_root_url_excel}`;
  191. let path = `${rootPath}${rooturl}`;
  192. if (!path) {
  193. throw new BusinessError(ErrorCode.BUSINESS, '服务端没有设置存储路径');
  194. }
  195. if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\';
  196. if (!fs.existsSync(path)) {
  197. // 如果不存在文件夹,就创建
  198. fs.mkdirSync(path);
  199. }
  200. // 生成文件
  201. const filepath = `${path}${filename}`;
  202. fs.createWriteStream(filepath);
  203. const workbook = new Excel.Workbook();
  204. const sheet = workbook.addWorksheet('sheet');
  205. sheet.columns = meta;
  206. sheet.addRows(dataList);
  207. await workbook.xlsx.writeFile(filepath);
  208. return `/files/excel/${filename}`;
  209. }
  210. /**
  211. * 创建/重复写入excel
  212. * @param {Array} dataList 数据
  213. * @param {String} fn 文件名, 第一次进入时,只是单纯的名,第二次开始,有后缀与时间戳
  214. * @param {String} downloadPath 下载文件路径
  215. * @param {String} type 方向,没有默认横向
  216. */
  217. async toAsyncExcel(dataList, fn = '导出结果', downloadPath, type) {
  218. const { app } = this;
  219. const workbook = new Excel.Workbook();
  220. let sheet;
  221. // 取出预设存储地址
  222. const rootPath = `${app.config.cdn.repos_root_path}`;
  223. const rooturl = `${app.config.cdn.repos_root_url_excel}`;
  224. let path = `${rootPath}${rooturl}`;
  225. let filepath = '';
  226. if (!path) {
  227. throw new BusinessError(ErrorCode.BUSINESS, '服务端没有设置存储路径');
  228. }
  229. if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\excel\\';
  230. if (!fs.existsSync(path)) {
  231. // 如果不存在文件夹,就创建
  232. fs.mkdirSync(path);
  233. }
  234. if (!downloadPath) {
  235. // 第一次进入,文件还未生成
  236. const nowDate = new Date().getTime();
  237. fn = `${fn}-${nowDate}.xlsx`;
  238. sheet = workbook.addWorksheet('sheet');
  239. } else {
  240. let domain = 'http://127.0.0.1';
  241. if (process.env.NODE_ENV === 'development')domain = `${domain}:8000`;
  242. const file = await this.ctx.curl(`${domain}${downloadPath}`);
  243. if (!(file && file.data)) {
  244. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找导出的excel');
  245. }
  246. // 读取文件
  247. await workbook.xlsx.load(file.data);
  248. sheet = workbook.getWorksheet('sheet');
  249. }
  250. if (!type) sheet.addRows(dataList);
  251. else {
  252. for (let i = 1; i <= dataList.length; i++) {
  253. const element = dataList[i - 1];
  254. const rows = sheet.getRow(i);
  255. if (rows.values.length <= 0) rows.values = element;
  256. else rows.values = rows.values.concat(element);
  257. rows.commit();
  258. }
  259. }
  260. filepath = `${path}${fn}`;
  261. await workbook.xlsx.writeFile(filepath);
  262. return { downloadPath: `/files/excel/${fn}`, fn };
  263. }
  264. /**
  265. * 导出docx
  266. * @param {Array} data 数据[{title,content([]),author}]
  267. * @param {String} fn 文件名
  268. */
  269. async toDocx(data, fn = '培训心得') {
  270. const {
  271. Document,
  272. Packer,
  273. Paragraph,
  274. TextRun,
  275. HeadingLevel,
  276. AlignmentType,
  277. } = docx;
  278. const doc = new Document();
  279. const children = [];
  280. // 整理数据
  281. for (let i = 0; i < data.length; i++) {
  282. const obj = data[i];
  283. const { title, content, author } = obj;
  284. const c = [];
  285. if (title) {
  286. const tit = new Paragraph({
  287. children: [ new TextRun({ text: title, bold: true }) ],
  288. heading: HeadingLevel.TITLE,
  289. alignment: AlignmentType.CENTER,
  290. });
  291. c.push(tit);
  292. }
  293. if (author) {
  294. const auth = new Paragraph({
  295. children: [ new TextRun({ color: '#000000', text: author }) ],
  296. heading: HeadingLevel.HEADING_2,
  297. alignment: AlignmentType.RIGHT,
  298. });
  299. c.push(auth);
  300. }
  301. if (content && _.isArray(content) && content.length > 0) {
  302. for (const cont of content) {
  303. const p = new Paragraph({
  304. children: [ new TextRun({ text: cont, bold: true }) ],
  305. });
  306. c.push(p);
  307. }
  308. }
  309. if (i !== data.length - 1) {
  310. // 换页
  311. const last = new Paragraph({
  312. pageBreakBefore: true,
  313. });
  314. c.push(last);
  315. }
  316. if (c.length > 0) children.push(...c);
  317. }
  318. doc.addSection({
  319. properties: {},
  320. children,
  321. });
  322. const { app } = this;
  323. const rootPath = `${app.config.cdn.repos_root_path}`;
  324. const rooturl = `${app.config.cdn.repos_root_url_experience}`;
  325. let path = `${rootPath}${rooturl}`;
  326. // 如果不存在文件夹,就创建
  327. if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\';
  328. if (!fs.existsSync(path)) {
  329. fs.mkdirSync(path);
  330. }
  331. const num = new Date().getTime();
  332. const buffer = await Packer.toBuffer(doc);
  333. fs.writeFileSync(`${path}${fn}-${num}.docx`, buffer);
  334. // Packer.toBuffer(doc).then(buffer => {
  335. // fs.writeFileSync(`${path}${fn}-${num}.docx`, buffer);
  336. // });
  337. return `/files${rooturl}${fn}-${num}.docx`;
  338. }
  339. /**
  340. * 将选择的文件导出到zip压缩包中,提供下载
  341. * @param {*} fileList 需要导入到zip中的列表,格式有2中: [{url:""}]或[String]
  342. * @param {*} fn 文件名,默认为 "导出结果"
  343. */
  344. async toZip(fileList, fn = '导出结果') {
  345. if (!_.isArray(fileList)) {
  346. throw new BusinessError(
  347. ErrorCode.DATA_INVALID,
  348. '需要压缩的文件数据格式错误'
  349. );
  350. }
  351. fn = `${fn}.zip`;
  352. // zip文件夹创建
  353. const { app } = this;
  354. const rootPath = `${app.config.cdn.repos_root_path}`;
  355. const zipPath = `${app.config.cdn.repos_root_url_zip}`;
  356. let path = `${rootPath}${zipPath}`;
  357. if (process.env.NODE_ENV === 'development') path = 'E:\\exportFile\\';
  358. if (!fs.existsSync(path)) {
  359. fs.mkdirSync(path);
  360. }
  361. // 文件请求后将数据整理到这里
  362. const resetFileList = [];
  363. for (const file of fileList) {
  364. let uri = '';
  365. let filename = '';
  366. let prefixs;
  367. if (_.isString(file)) {
  368. uri = file;
  369. const arr = file.split('/');
  370. const last = _.last(arr);
  371. if (last) filename = last;
  372. } else if (_.isObject(file)) {
  373. const { uri: furi, url: furl, name, prefix } = file;
  374. if (furi) uri = furi;
  375. else if (furl) uri = furl;
  376. if (name) filename = name;
  377. else {
  378. const arr = uri.split('/');
  379. const last = _.last(arr);
  380. if (last) filename = last;
  381. }
  382. if (prefix) prefixs = prefix;
  383. }
  384. const obj = {};
  385. if (uri) obj.uri = uri;
  386. if (filename) obj.filename = filename;
  387. if (prefixs) obj.prefix = prefixs;
  388. resetFileList.push(obj);
  389. }
  390. // 导出
  391. const output = fs.createWriteStream(`${path}${fn}`);
  392. const archive = archiver('zip', {
  393. zlib: { level: 9 },
  394. });
  395. archive.pipe(output);
  396. // 请求文件,追加进压缩包
  397. for (const file of resetFileList) {
  398. const { uri, filename, prefix } = file;
  399. const res = await this.ctx.curl(`http://127.0.0.1${uri}`);
  400. if (res && res.data) {
  401. const options = {};
  402. if (filename) options.name = filename;
  403. if (prefix) options.prefix = prefix;
  404. if (filename) {
  405. archive.append(res.data, options);
  406. }
  407. }
  408. }
  409. await archive.finalize();
  410. return `/files${zipPath}${fn}`;
  411. }
  412. }
  413. module.exports = UtilService;