patentinfo.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. 'use strict';
  2. const assert = require('assert');
  3. const moment = require('moment');
  4. const Excel = require('exceljs');
  5. const Path = require('path');
  6. const _ = require('lodash');
  7. const { sep } = require('path');
  8. const fs = require('fs');
  9. const { CrudService } = require('naf-framework-mongoose-free/lib/service');
  10. const { BusinessError, ErrorCode } = require('naf-core').Error;
  11. const { trimData } = require('naf-core').Util;
  12. const { ObjectId } = require('mongoose').Types;
  13. const compressing = require('compressing');
  14. // 专利信息
  15. class PatentinfoService extends CrudService {
  16. constructor(ctx) {
  17. super(ctx, 'patentinfo');
  18. this.model = this.ctx.model.Patent.Patentinfo;
  19. this.personalModel = this.ctx.model.User.Personal;
  20. this.adminModel = this.ctx.model.User.Admin;
  21. this.organizationModel = this.ctx.model.User.Organization;
  22. this.root_path = _.get(this.ctx.app.config.export, 'root_path');
  23. this.patentInfo_dir = _.get(this.ctx.app.config.export, 'patentInfo_dir');
  24. this.export_dir = _.get(this.ctx.app.config.export, 'export_dir');
  25. // if (process.env.NODE_ENV === 'development') {
  26. // this.root_path = 'S:\\workspace\\exportFile\\';
  27. // }
  28. this.file_type = '';
  29. if (!fs.existsSync(`${this.root_path}${this.file_type}`)) {
  30. // 如果不存在文件夹,就创建
  31. fs.mkdirSync(`${this.root_path}${this.file_type}`);
  32. }
  33. this.excel_path = `${sep}excel${sep}`;
  34. this.domain = 'http://127.0.0.1';
  35. this.export_limit = 500;
  36. }
  37. async query(query, { skip = 0, limit = 0, projection }) {
  38. const newquery = await this.resetCode(query);
  39. const res = await this.model.find(newquery, projection).skip(parseInt(skip)).limit(parseInt(limit))
  40. .sort({ create_date: -1 });
  41. return res;
  42. }
  43. async count(query) {
  44. const newquery = await this.resetCode(query);
  45. const res = await this.model.countDocuments(trimData(newquery)).exec();
  46. return res;
  47. }
  48. async resetCode(query) {
  49. query = this.dealKey(query);
  50. let newquery = _.cloneDeep(query);
  51. newquery = this.ctx.service.util.util.dealQuery(newquery);
  52. if (Object.keys(newquery).length <= 0) return newquery;
  53. const { pid, user_id, key_word, single_inventor } = newquery;
  54. let arr = [];
  55. if (pid) {
  56. const plist = await this.adminModel.find({ pid, role: '2' }, { _id: 1, deptname: 1 });
  57. arr = plist.map(i => i.deptname);
  58. if (arr.length > 0) {
  59. newquery.first_apply = arr;
  60. delete newquery.pid;
  61. }
  62. }
  63. if (user_id) {
  64. newquery['user_id.user_id'] = user_id;
  65. delete newquery.user_id;
  66. }
  67. if (key_word) {
  68. newquery.$or = [{ name: new RegExp(key_word) }, { abstract: new RegExp(key_word) }, { first_ask: new RegExp(key_word) }];
  69. delete newquery.key_word;
  70. }
  71. if (single_inventor) {
  72. newquery['inventor.name'] = { $in: [ single_inventor ] };
  73. delete newquery.single_inventor;
  74. }
  75. return newquery;
  76. }
  77. /**
  78. * 处理模糊查询
  79. * @param {Object} query 查询条件
  80. */
  81. dealKey(query) {
  82. const vagueList = [
  83. 'inventor',
  84. 'agent',
  85. 'agent_personal',
  86. 'abstract',
  87. 'address',
  88. 'name',
  89. 'apply_personal',
  90. 'nationality',
  91. 'ipc_type',
  92. 'onlegal_status',
  93. 'legal_status',
  94. 'on_obligee',
  95. 'apply_address',
  96. 'apply_other',
  97. 'law_num',
  98. 'invention_design',
  99. 'incopat_link',
  100. 'first_ask',
  101. 'first_apply',
  102. 'apply_city',
  103. 'business_code',
  104. 'business_address',
  105. 'first_inventor',
  106. 'shared_value',
  107. 'techol_stable',
  108. 'techol_advanced',
  109. 'pct_apply',
  110. 'pct_publish',
  111. ];
  112. const keys = Object.keys(query);
  113. for (const key of keys) {
  114. if (vagueList.includes(key)) {
  115. query[`%${key}%`] = query[key];
  116. delete query[key];
  117. }
  118. }
  119. return query;
  120. }
  121. async toImport({ uri, code }) {
  122. assert(uri, '未获取到文件地址');
  123. const file = await this.ctx.curl(`${this.domain}${uri}`);
  124. if (!(file && file.data)) {
  125. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定文件');
  126. }
  127. const workbook = new Excel.Workbook();
  128. await workbook.xlsx.load(file.data);
  129. const sheet = workbook.getWorksheet(1);
  130. const arr = [];
  131. const allNotice = [];
  132. const sheetImageInfo = sheet.getImages();
  133. const imgids = _.compact(
  134. sheetImageInfo.map(i => {
  135. const { imageId, range } = i;
  136. const row = _.get(range, 'tl.nativeRow');
  137. if (row) return { row, imageId };
  138. })
  139. );
  140. // 2021-11-18 修改导入,根据model文件(和excel顺序一致)的顺序进行取字段
  141. const models = _.get(this.model, 'schema.obj');
  142. const meta = Object.keys(models).map((i, index) => ({
  143. key: i,
  144. index: index + 2,
  145. }));
  146. sheet.eachRow((row, rindex) => {
  147. if (rindex !== 1) {
  148. // 组织数据,图片的索引和行索引不一致,准确的说是:图片索引比行索引少1
  149. // 原因:图片在工作簿中获取,按照1,2,3...顺序排序,但是行的第一行是表头(当前文件),所以当前行数需要减掉表头那一行
  150. const imgid = imgids.find(f => f.row === rindex - 1);
  151. const img_url = [];
  152. if (imgid) {
  153. img_url.push({
  154. url: this.turnImageToBase64(workbook.getImage(imgid.imageId)),
  155. });
  156. }
  157. // 根据meta整理数据
  158. // 需要日期转换的字段
  159. const dateColumnArray = [ 'create_date', 'success_date', 'law_date', 'first_opendate', 'empower_date', 'lose_date', 'examine_date' ];
  160. let obj = { img_url };
  161. for (const m of meta) {
  162. const { key, index } = m;
  163. if (dateColumnArray.includes(key)) {
  164. obj[key] = _.get(row.getCell(index), 'value') ? moment(_.get(row.getCell(index), 'value')).format('YYYY-MM-DD') : '';
  165. } else if (key === 'img_url') {
  166. // 上面默认处理了
  167. continue;
  168. } else if (key === 'incopat_link') {
  169. obj[key] = this.getUrl(_.get(row.getCell(index), 'value'));
  170. } else obj[key] = _.get(row.getCell(index), 'value');
  171. }
  172. obj = _.pickBy(obj, _.identity);
  173. arr.push(obj);
  174. }
  175. });
  176. for (const obj of arr) {
  177. const { result, notice } = this.tocheckData(obj);
  178. if (!result) {
  179. allNotice.push(notice);
  180. }
  181. }
  182. if (allNotice.length > 0) return allNotice;
  183. // 有选择机构,创建用户
  184. if (code) {
  185. for (const obj of arr) {
  186. const { first_inventor, create_number } = obj;
  187. try {
  188. let person = await this.personalModel.findOne({ code, phone: first_inventor });
  189. const user_id = {};
  190. // 有用户,直接取id,没有用户;创建用户,取id;
  191. if (person) {
  192. user_id.user_id = person._id || person.id;
  193. user_id.name = person.name;
  194. } else {
  195. const personObject = {
  196. name: first_inventor,
  197. phone: first_inventor,
  198. status: '1',
  199. code,
  200. };
  201. try {
  202. person = await this.ctx.service.users.personal.create(personObject);
  203. } catch (error) {
  204. allNotice.push(`${create_number} 的 第一发明人用户,已有机构所属的个人用户使用该手机号!`);
  205. continue;
  206. }
  207. if (person) {
  208. user_id.user_id = person._id || person.id;
  209. user_id.name = person.name;
  210. }
  211. }
  212. obj.user_id = [ JSON.parse(JSON.stringify(user_id)) ];
  213. } catch (error) {
  214. allNotice.push(`${create_number} 的 第一发明人用户 创建失败!`);
  215. continue;
  216. }
  217. }
  218. }
  219. if (allNotice.length > 0) return allNotice;
  220. // 根据申请号做添加/修改
  221. for (const obj of arr) {
  222. try {
  223. const has_data = await this.model.count({
  224. create_number: obj.create_number,
  225. });
  226. let res;
  227. if (!has_data) {
  228. res = await this.model.create(obj);
  229. } else {
  230. await this.model.updateOne({ create_number: obj.create_number }, obj);
  231. res = await this.model.find({ create_number: obj.create_number });
  232. }
  233. // 处理警告
  234. if (res.trem === '有效') this.ctx.service.patent.patentearly.dealData([ res ]);
  235. } catch (error) {
  236. allNotice.push(`申请号 为${create_number} 的 专利信息 创建失败!`);
  237. continue;
  238. }
  239. }
  240. }
  241. async toExport({ user, query }) {
  242. const data = {
  243. title: '专利导出',
  244. params: {
  245. project: 'market',
  246. service: 'patent.patentinfo',
  247. method: 'export',
  248. query,
  249. },
  250. user,
  251. tenant: 'live',
  252. };
  253. try {
  254. await this.ctx.service.util.httpUtil.cpost('/api/mission', 'mission', data);
  255. } catch (error) {
  256. console.log(error);
  257. throw new BusinessError(ErrorCode.SERVICE_FAULT, '任务创建失败');
  258. }
  259. }
  260. async export({ missionid, query = {} } = {}) {
  261. const path = `${this.root_path}${sep}${this.file_type}${sep}${this.patentInfo_dir}${new Date().getTime()}`;
  262. if (!path) {
  263. throw new BusinessError(ErrorCode.BUSINESS, '服务端没有设置存储路径');
  264. }
  265. if (!fs.existsSync(path)) {
  266. // 如果不存在文件夹,就创建
  267. fs.mkdirSync(path);
  268. }
  269. const total = await this.count(_.cloneDeep(query));
  270. let skip = 0;
  271. const getHeader = require('../../public/patent/infoExport');
  272. const meta = getHeader();
  273. const projection = {};
  274. for (const m of meta) {
  275. const { key } = m;
  276. projection[key] = 1;
  277. }
  278. // 将数据分割,否则容易直接把js堆栈干满了,服务就炸了
  279. const head = meta.map(i => i.header);
  280. for (let i = 0; i < total; i = i + this.export_limit) {
  281. try {
  282. const workbook = new Excel.Workbook();
  283. const sheet = workbook.addWorksheet('sheet');
  284. sheet.addRow(head);
  285. const list = await this.query(query, { skip, limit: this.export_limit }, projection);
  286. for (let k = 0; k < list.length; k++) {
  287. const d = list[k];
  288. const arr = [];
  289. for (const m of meta) {
  290. const { key } = m;
  291. if (key === 'img_url') {
  292. const value = d[key];
  293. // 需要图片占列,否则会窜位置
  294. arr.push('');
  295. if (_.isArray(value)) {
  296. const img = _.get(_.head(value), 'url');
  297. if (img && img.includes('image/png')) {
  298. const imgId = workbook.addImage({
  299. base64: img,
  300. extension: 'png',
  301. });
  302. const sheetRange = {
  303. tl: { col: 13, row: k + 1 },
  304. ext: { width: 60, height: 60 },
  305. editAs: 'oneCell',
  306. };
  307. if (_.isFunction(sheet.addImage)) sheet.addImage(imgId, sheetRange);
  308. }
  309. }
  310. } else arr.push(d[key]);
  311. }
  312. sheet.addRow(arr);
  313. }
  314. skip = skip + this.export_limit;
  315. // 生成excel
  316. const filename = `专利信息导出结果(${i + 1}-${i + this.export_limit}).xlsx`;
  317. const filePath = `${path}${sep}${filename}`;
  318. console.log(filePath);
  319. await workbook.xlsx.writeFile(filePath);
  320. try {
  321. let progress = _.ceil((skip / total) * 100);
  322. if (progress > 95) progress = 95;
  323. const data = {
  324. progress,
  325. status: '1',
  326. id: missionid,
  327. remark: '组织数据生成excel中...',
  328. };
  329. await this.sendToMQ(data);
  330. } catch (error) {
  331. this.ctx.logger.error('更新进度失败');
  332. }
  333. } catch (error) {
  334. const data = {
  335. progress: 0,
  336. status: '-1',
  337. id: missionid,
  338. remark: '组织数据进入excel失败',
  339. };
  340. this.sendToMQ(data);
  341. }
  342. }
  343. try {
  344. const data = {
  345. progress: 98,
  346. status: '1',
  347. id: missionid,
  348. remark: '正在打包',
  349. };
  350. this.sendToMQ(data);
  351. } catch (error) {
  352. this.ctx.logger.error('正在打包');
  353. }
  354. try {
  355. const nowDate = new Date().getTime();
  356. const zipName = `导出专利信息-${nowDate}.zip`;
  357. const exportPath = `${this.root_path}${sep}${this.export_dir}`;
  358. if (!fs.existsSync(exportPath)) {
  359. // 如果不存在文件夹,就创建
  360. fs.mkdirSync(exportPath);
  361. }
  362. await compressing.zip.compressDir(path, `${exportPath}${sep}${zipName}`);
  363. const downloadPath = `/files/${this.export_dir}/${zipName}`;
  364. const data = {
  365. progress: 100,
  366. status: '2',
  367. uri: downloadPath,
  368. id: missionid,
  369. remark: '打包成功',
  370. };
  371. this.sendToMQ(data);
  372. } catch (error) {
  373. const data = {
  374. progress: 0,
  375. status: '-2',
  376. id: missionid,
  377. remark: '打包失败',
  378. };
  379. this.sendToMQ(data);
  380. this.ctx.logger.error('打包失败');
  381. this.ctx.logger.error(error);
  382. }
  383. this.dirDelete(path);
  384. }
  385. /**
  386. * 删除路径下的所有内容
  387. * @param {String} filePath 路径
  388. */
  389. async dirDelete(filePath) {
  390. if (fs.existsSync(filePath)) {
  391. const files = fs.readdirSync(filePath);
  392. for (const file of files) {
  393. const curPath = `${filePath}${sep}${file}`;
  394. if (fs.statSync(curPath).isDirectory()) {
  395. // recurse
  396. this.dirDelete(curPath);
  397. } else {
  398. // delete file
  399. fs.unlinkSync(curPath);
  400. }
  401. }
  402. fs.rmdirSync(filePath);
  403. }
  404. }
  405. async sendToMQ(data) {
  406. if (_.get(data, 'progress') && _.isNumber(parseFloat(_.get(data, 'progress'))) && parseFloat(_.get(data, 'progress')) > 100) {
  407. data.progress = 100;
  408. }
  409. console.log(`${_.get(data, 'progress') || '失败'} %`);
  410. const ch = await this.ctx.mq.conn.createChannel();
  411. const queue = 'mission/return';
  412. await ch.assertQueue(queue, { durable: false });
  413. await ch.sendToQueue(queue, Buffer.from(JSON.stringify(data)));
  414. await ch.close();
  415. }
  416. /**
  417. * 检查数据是否没填 必填项
  418. * @param {Object} object 每行数据,已转换成model的字段名
  419. */
  420. tocheckData(object) {
  421. let result = true;
  422. const { number } = object;
  423. let notice;
  424. const arr = [
  425. { column: 'create_number', zh: '申请号' },
  426. { column: 'create_date', zh: '申请日' },
  427. // { column: 'success_number', zh: '公开(公告)号' },
  428. // { column: 'success_date', zh: '公开(公告)日' },
  429. { column: 'name', zh: '标题' },
  430. ];
  431. const word = [];
  432. for (const o of arr) {
  433. const { column, zh } = o;
  434. if (!_.get(object, column)) {
  435. result = false;
  436. word.push(`${zh}`);
  437. }
  438. }
  439. if (!result) {
  440. notice = `序号${number}缺少:${word.join(';')}`;
  441. }
  442. return { result, notice };
  443. }
  444. /**
  445. * 转换图片为base64
  446. * @param {Object} object excel获取的图片object
  447. * @property extension 后缀,文件类型
  448. * @property buffer 图片内容,不含头部信息的,
  449. */
  450. turnImageToBase64(object = {}) {
  451. const { extension, buffer } = object;
  452. if (extension && buffer) {
  453. const suffix = object.extension;
  454. const ib = object.buffer.toString('base64');
  455. const base64 = `data:image/${suffix};base64,${ib}`;
  456. return base64;
  457. }
  458. }
  459. /**
  460. * 将excel的超链接对象中的url
  461. * @param {Object} object excel中的超链接对象
  462. */
  463. getUrl(object) {
  464. let urlStr = _.get(object, 'formula');
  465. let url = '';
  466. if (urlStr) {
  467. const s = urlStr.indexOf('(');
  468. urlStr = urlStr.substring(s);
  469. urlStr = _.trim(_.trim(urlStr, '('), ')');
  470. const midArr = urlStr.split(',');
  471. url = _.trim(_.head(midArr), '"');
  472. }
  473. return url;
  474. }
  475. async getByCreateNumber({ create_number }) {
  476. const data = await this.model.findOne({ create_number });
  477. return data;
  478. }
  479. async mechQuery({ skip = 0, limit = 0, ...query }) {
  480. const code = _.get(query, 'code');
  481. assert(code, '缺少机构信息!');
  482. const { empower_sort, ...others } = query;
  483. const newQuery = await this.toSetQuery(others);
  484. let sort = 'desc';
  485. if (empower_sort === '0') {
  486. sort = 'asc';
  487. }
  488. const data = await this.model.find(newQuery).sort({ empower_date: sort }).skip(parseInt(skip))
  489. .limit(parseInt(limit));
  490. const total = await this.model.count(newQuery);
  491. return { data, total };
  492. }
  493. async toSetQuery(query) {
  494. let queryObject = {};
  495. for (const key in query) {
  496. if (key === 'code') {
  497. const cquery = await this.dealCode(query[key]);
  498. queryObject = { ...queryObject, ...cquery };
  499. } else if (key === 'empower_year') {
  500. queryObject.empower_date = this.dealYearRange(query[key]);
  501. } else if (key === 'apply_year') {
  502. queryObject.create_date = this.dealYearRange(query[key]);
  503. } else if (key === 'ipc_type') {
  504. queryObject.ipc_type = new RegExp(`^${query[key]}`, 'i');
  505. } else if (key === 'field') {
  506. queryObject.abstract = new RegExp(query[key]);
  507. } else if (key === 'create_number') {
  508. queryObject.create_number = new RegExp(query[key]);
  509. } else if (key === 'first_inventor') {
  510. queryObject.inventor = new RegExp(`^${query[key]}`, 'i');
  511. } else if (key === 'on_obligee') {
  512. queryObject.on_obligee = new RegExp(`^${query[key]}`, 'i');
  513. } else if (key === 'type') {
  514. queryObject.type = new RegExp(`^${query[key]}`, 'i');
  515. } else if (key === 'is_empower') {
  516. let r = '';
  517. if (query[key] === '0') r = true;
  518. else r = false;
  519. queryObject.empower_date = { $exists: r };
  520. } else if (key === 'term') {
  521. queryObject.term = new RegExp(query[key]);
  522. } else if (key === 'is_lose') {
  523. const toDay = moment().format('YYYY-MM-DD');
  524. let r = '';
  525. if (query[key] === '0') r = { $gt: toDay };
  526. else r = { $lte: toDay };
  527. queryObject.lose_date = r;
  528. }
  529. }
  530. return queryObject;
  531. }
  532. async dealCode(code) {
  533. let pids = await this.personalModel.find({ code }, { _id: 1 });
  534. if (pids.length <= 0) return { data: [], total: 0 };
  535. pids = pids.map(i => i._id);
  536. const query = { 'inventor.user_id': { $in: pids } };
  537. return query;
  538. }
  539. async dealYearRange(year) {
  540. const start = `${year}-01-01`;
  541. const end = `${year}-12-31`;
  542. const obj = { $gte: start, $lte: end };
  543. return obj;
  544. }
  545. /**
  546. * 根据人名数组查询每个人的数据.组织数据返回
  547. * @param {Array[String]} nameList 名单
  548. */
  549. async toGetUser(nameList) {
  550. const res = await this.personalModel.find({ name: { $in: nameList } });
  551. const result = [];
  552. if (res && res.length > 0) {
  553. for (const i of res) {
  554. const { _id: id, name } = i;
  555. result.push({ id, name });
  556. }
  557. }
  558. return result;
  559. }
  560. async updateUser({ patentData, user_id }) {
  561. if (!patentData || !_.isArray(patentData)) {
  562. throw new BusinessError(ErrorCode.DATA_INVALID, '专利信息参数错误');
  563. }
  564. if (!user_id || !_.isArray(user_id)) {
  565. throw new BusinessError(ErrorCode.DATA_INVALID, '用户信息参数错误');
  566. }
  567. const data = await this.model.find({ _id: patentData.map(i => ObjectId(i)) });
  568. for (const i of data) {
  569. i.user_id = _.uniqBy([ ...i.user_id, ...user_id ], 'user_id');
  570. await i.save();
  571. }
  572. }
  573. /**
  574. * 批量删除
  575. * @param {Object} query 参数,查询范围
  576. */
  577. async deleteMany(query) {
  578. const newquery = await this.resetCode(query);
  579. const res = await this.model.deleteMany(newquery);
  580. return res;
  581. }
  582. }
  583. module.exports = PatentinfoService;