patentwarning.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const { ObjectId } = require('mongoose').Types;
  5. const moment = require('moment');
  6. const _ = require('lodash');
  7. const compressing = require('compressing');
  8. const { sep } = require('path');
  9. const path = require('path');
  10. const fs = require('fs');
  11. const encoding = require('encoding'); // 编码转换
  12. const fxp = require('fast-xml-parser'); // xml转换
  13. // 专利运营专利申请预警表
  14. class PatentwarningService extends CrudService {
  15. constructor(ctx) {
  16. super(ctx, 'patentwarning');
  17. this.model = this.ctx.model.Patent.Patentwarning;
  18. this.import_root_path = _.get(this.ctx.app.config.import, 'root_path');
  19. this.patentInfo = this.ctx.model.Patent.Patentinfo;
  20. this.patentApply = this.ctx.model.Patent.Patentapply;
  21. this.personalModel = this.ctx.model.Personal;
  22. this.cpcErrorMsg = this.ctx.model.Patent.Cpcimporterror;
  23. }
  24. async import({ uri }) {
  25. uri = uri.replace('/files', this.import_root_path);
  26. const fileFullName = path.basename(uri);
  27. const arr = fileFullName.split('.');
  28. const ext = _.last(arr);
  29. const uncompressFilePath = `${this.import_root_path}${sep}temp`;
  30. if (ext !== 'zip') {
  31. throw new BusinessError(ErrorCode.FILE_FAULT, '只接收压缩格式为zip的压缩包');
  32. }
  33. // 解压
  34. try {
  35. await compressing.zip.uncompress(uri, `${uncompressFilePath}`);
  36. } catch (error) {
  37. throw new BusinessError(ErrorCode.FILE_FAULT, '解压失败');
  38. }
  39. // 修改,多个专利:需要扫描下解压后的文件夹,然后循环处理下面所有的内容
  40. const tempDirRootPath = `${uncompressFilePath}${sep}`;
  41. let tempDir = fs.readdirSync(tempDirRootPath);
  42. let errorList = [];
  43. // 2023-02-06: zip格式修改: zip套dir => zip套zip, zip解压出dir;
  44. // 也就是需要将zip再次解压一遍,并将所有的zip删除即可
  45. const isAllZip = tempDir.some((f) => f.includes('zip'));
  46. if (isAllZip) {
  47. for (const p of tempDir) {
  48. if (!p.includes('.zip')) continue;
  49. const zipFullPath = `${uncompressFilePath}${sep}${p}`;
  50. const zipArr = p.split('.');
  51. const name = _.head(zipArr);
  52. try {
  53. await compressing.zip.uncompress(zipFullPath, `${uncompressFilePath}${sep}${name}`);
  54. } catch (error) {
  55. await this.dirDelete(uncompressFilePath);
  56. errorList.push({ key: p, word: '內部解压包,解压发生错误', error });
  57. }
  58. }
  59. // 删除zip
  60. await this.deleteZip(uncompressFilePath);
  61. tempDir = fs.readdirSync(tempDirRootPath);
  62. }
  63. for (const tempDirPath of tempDir) {
  64. if (tempDirPath.includes('.zip')) continue;
  65. // const thisDirPath = `${tempDirRootPath}${sep}${tempDirPath}`;
  66. const thisDirPath = path.resolve(tempDirRootPath, tempDirPath);
  67. const patentNumber = tempDirPath;
  68. // 找到
  69. let filePaths = [];
  70. try {
  71. filePaths = await this.findFileNameLoop(thisDirPath);
  72. } catch (error) {
  73. errorList.push({ key: patentNumber, word: '格式不对', error });
  74. continue;
  75. }
  76. if (filePaths.length <= 0) {
  77. errorList.push({ key: patentNumber, word: '未找到文件' });
  78. continue;
  79. }
  80. // 2021-10-16 修改: 取出第一层的xml文件,即:路径最短的;除文件名外,字符串长度最小的
  81. let xmlPaths = filePaths.filter((f) => f.includes('.xml'));
  82. if (xmlPaths.length > 0) {
  83. // 去掉文件名
  84. xmlPaths = xmlPaths.map((i) => {
  85. const arr = i.split(sep);
  86. arr.pop();
  87. const obj = { dir: arr.join(sep), fullPath: i };
  88. return obj;
  89. });
  90. // // 按路径字符串长度排序
  91. // xmlPaths = xmlPaths.sort((a, b) => {
  92. // const anum = a.fullPath.length;
  93. // const bnum = b.fullPath.length;
  94. // return anum - bnum;
  95. // });
  96. xmlPaths = xmlPaths.filter((f) => f.fullPath.includes('list.xml'));
  97. }
  98. const xmlPath = _.get(_.head(xmlPaths), 'fullPath');
  99. if (!xmlPath) {
  100. errorList.push({ key: patentNumber, word: '未找到xml文件' });
  101. continue;
  102. // throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到xml文件');
  103. }
  104. let xmlData;
  105. try {
  106. xmlData = await this.dealXml(xmlPath);
  107. } catch (error) {
  108. errorList.push({ key: patentNumber, word: `解析xml数据中的 申请号 和 专利名称 失败 于:${xmlPath}`, ...xmlData });
  109. continue;
  110. // throw new BusinessError(ErrorCode.SERVICE_FAULT, '解析申请号失败');
  111. }
  112. if (!xmlData) {
  113. errorList.push({ key: patentNumber, word: '未找到xml的数据' });
  114. continue;
  115. // throw new BusinessError(ErrorCode.FILE_FAULT, '未找到申请号');
  116. }
  117. /**
  118. * @property {Array} patent 专利信息
  119. * @property {Boolean || null} needChange 是否修改数据
  120. */
  121. let result;
  122. try {
  123. // 2022-09-02 修改为在数组中查询,所以下面所有关于result的处理都需要变成数组的处理
  124. result = await this.getPatent(xmlData);
  125. } catch (error) {
  126. errorList.push({ key: patentNumber, word: '查询专利信息失败', ...xmlData });
  127. continue;
  128. // throw new BusinessError(ErrorCode.SERVICE_FAULT, '查询专利信息失败');
  129. }
  130. // 2022-09-02 数组处理模式 ↑
  131. const patents = _.get(result, 'patent');
  132. if (!patents || patents.length <= 0) {
  133. errorList.push({ key: patentNumber, word: '未通过 申请号 或 专利名称 找到指定专利', ...xmlData });
  134. continue;
  135. }
  136. for (const patent of patents) {
  137. // 2022-09-02 原处理进入循环中处理
  138. let warningData;
  139. try {
  140. warningData = await this.getWarningData(patent);
  141. } catch (error) {
  142. errorList.push({ key: patentNumber, word: '组织专利信息警告数据失败', ...xmlData });
  143. continue;
  144. // throw new BusinessError(ErrorCode.SERVICE_FAULT, '组织专利信息警告数据失败');
  145. }
  146. // 2023-02-07 原来是.tif,现在是.pdf
  147. const uploadPaths = filePaths.filter((f) => f.includes('.pdf'));
  148. if (uploadPaths && uploadPaths.length > 0) {
  149. try {
  150. await this.toUploadFile(warningData, uploadPaths);
  151. } catch (error) {
  152. errorList.push({ key: patentNumber, word: '存储数据失败', ...xmlData });
  153. continue;
  154. }
  155. }
  156. }
  157. }
  158. try {
  159. // await this.dirDelete(`${thisDirPath}`);
  160. await this.dirDelete(uncompressFilePath);
  161. } catch (error) {
  162. this.ctx.logger.error('清除缓存失败');
  163. }
  164. // 2022-09-05 将错误信息添加到表中
  165. errorList = errorList.map((i) => ({ ...i, filepath: uri, time: moment().format('YYYY-MM-DD HH:mm:ss') }));
  166. await this.cpcErrorMsg.insertMany(errorList);
  167. if (errorList.length > 0) return errorList;
  168. return 'ok';
  169. }
  170. /**
  171. * 上传图片,保存
  172. * @param {Array} data 警告数据,未保存
  173. * @param {Array} files tif图片文件路径数组
  174. */
  175. async toUploadFile(data, files) {
  176. const filePaths = [];
  177. const savePath = ObjectId();
  178. for (const file of files) {
  179. let arr = file.split(sep);
  180. const fullFileName = _.last(arr);
  181. arr = fullFileName.split('.');
  182. // 文件后缀
  183. const ext = _.last(arr);
  184. // 图片文件
  185. const buffer = await fs.readFileSync(file);
  186. // 新文件名
  187. const randomStr = ObjectId();
  188. // 新文件名带后缀
  189. const newFileName = `${randomStr}.${ext}`;
  190. // 上传后部分路径
  191. const shortPath = `platform${sep}patent${sep}${savePath}${sep}`;
  192. // 上传全路径
  193. const path = `${this.import_root_path}${sep}${shortPath}`;
  194. // 检查上传全路径是否存在,不存在就创建文件夹
  195. await this.checkPathAndCreate(path);
  196. // 另存文件
  197. await fs.writeFileSync(`${path}${newFileName}`, buffer);
  198. // 替换为files开头
  199. let dataPath = path.replace(this.import_root_path, '/files');
  200. while (dataPath.indexOf('\\') >= 0) {
  201. dataPath = dataPath.replace('\\', '/');
  202. }
  203. // 补全请求用路径的所有路径
  204. dataPath += newFileName;
  205. filePaths.push(dataPath);
  206. }
  207. for (const i of data) {
  208. i.file_url = filePaths;
  209. }
  210. const res = await this.model.insertMany(data);
  211. return res;
  212. }
  213. /**
  214. * 检查路径,没有创建
  215. * @param {Array} filePath 图片路径数组
  216. */
  217. async checkPathAndCreate(filePath) {
  218. const arr = filePath.split(sep);
  219. // w:第一个是盘符,直接和第二个合并
  220. // l:前几个应该都是存在的.也可以合并
  221. let head = _.head(arr);
  222. if (arr[1]) head = `${head}${sep}${arr[1]}`;
  223. arr.shift();
  224. arr[0] = head;
  225. let curPath;
  226. for (const p of arr) {
  227. curPath = `${curPath ? curPath + sep : ''}${p}`;
  228. if (!fs.existsSync(curPath)) {
  229. await fs.mkdirSync(curPath);
  230. }
  231. }
  232. }
  233. /**
  234. * 组织专利预警数据
  235. * @param {Object} patent 专利
  236. */
  237. async getWarningData(patent) {
  238. const object = {};
  239. const arr = [];
  240. if (_.get(patent, 'create_number')) object.create_number = _.get(patent, 'create_number');
  241. if (_.get(patent, 'id')) object.patent_id = _.get(patent, 'id');
  242. if (_.get(patent, 'name')) object.patent_name = _.get(patent, 'name');
  243. // 找人的信息
  244. const users = _.get(patent, 'user_id');
  245. if (users && _.isArray(users)) {
  246. for (const user of users) {
  247. if (!_.get(user, 'user_id') || !_.get(user, 'name')) continue;
  248. const { user_id, name } = user;
  249. const obj = _.cloneDeep(object);
  250. obj.to_id = user_id;
  251. obj.to_name = name;
  252. arr.push(obj);
  253. }
  254. // 2021-11-18 添加,user,mech,admin,agentmech是否存在,如果存在,也添加预警信息
  255. const list = ['user', 'mech', 'admin', 'agentmech'];
  256. const suffix = ['id', 'name'];
  257. for (const prefix of list) {
  258. const marr = suffix.map((i) => `${prefix}_${i}`);
  259. const r = marr.every((i) => _.get(patent, i));
  260. if (r) {
  261. const obj = _.cloneDeep(object);
  262. for (const key of suffix) {
  263. obj[`to_${key}`] = _.get(patent, `${prefix}_${key}`);
  264. }
  265. arr.push(obj);
  266. }
  267. }
  268. } else if (users) {
  269. const obj = _.cloneDeep(object);
  270. const name = _.get(patent, 'user_name');
  271. obj.to_id = users;
  272. obj.to_name = name;
  273. arr.push(obj);
  274. }
  275. return arr;
  276. }
  277. /**
  278. * 在patentApply/patentInfo表中找指定的专利
  279. * @param {Object} object xml的数据
  280. */
  281. async getPatent(object) {
  282. let { name, create_number } = object;
  283. create_number = `CN${create_number}`;
  284. const arr = create_number.split('');
  285. arr.splice(arr.length - 1, 0, '.');
  286. create_number = arr.join('');
  287. let res;
  288. const needChange = false;
  289. // 2022-09-02 改为多条数据查询,不再针对一个数据
  290. // 1,名称相同,关联人不同:
  291. const listPA = await this.patentApply.find({ $or: [{ create_number }, { name }] });
  292. for (const i of listPA) {
  293. const { create_number: ocn, _id } = i;
  294. // create_number 不一致,就去更改
  295. if (ocn !== create_number) await this.patentApply.updateOne({ _id }, { create_number });
  296. }
  297. const listPI = await this.patentInfo.find({ $or: [{ create_number }, { name }] });
  298. res = [...listPA, ...listPI];
  299. const returnObject = {};
  300. if (res.length > 0) {
  301. returnObject.patent = JSON.parse(JSON.stringify(res));
  302. if (needChange) {
  303. returnObject.create_number = create_number;
  304. returnObject.needChange = needChange;
  305. }
  306. return returnObject;
  307. }
  308. // res = await this.patentApply.findOne({ create_number });
  309. // // 如果在申请中,使用名称找到,则之后需要将该数据的专利号改为xml中的专利号
  310. // if (!res) {
  311. // res = await this.patentApply.findOne({ name });
  312. // needChange = true;
  313. // }
  314. // if (!res) {
  315. // res = await this.patentInfo.findOne({ create_number });
  316. // }
  317. // if (!res) {
  318. // res = await this.patentInfo.findOne({ name });
  319. // // }
  320. // const returnObject = {};
  321. // if (res) {
  322. // if (needChange) {
  323. // res.create_number = create_number;
  324. // returnObject.needChange = needChange;
  325. // }
  326. // returnObject.patent = res;
  327. // return returnObject;
  328. // }
  329. return null;
  330. }
  331. /**
  332. * 根据路径找到xml,再找到该xml的申请号和专利名称
  333. * @param {String} filePath xml的文件路径
  334. */
  335. async dealXml(filePath) {
  336. const buffer = await fs.readFileSync(filePath);
  337. let str = encoding.convert(buffer, 'UTF8', 'UTF8');
  338. if (!str) throw new BusinessError(ErrorCode.FILE_FAULT, '读取xml文件失败');
  339. str = str.toString();
  340. const json = fxp.parse(str);
  341. const create_number = _.get(json, 'data-bus.TONGZHISXJ.SHUXINGXX.SHENQINGH');
  342. const name = _.get(json, 'data-bus.TONGZHISXJ.SHUXINGXX.FAMINGMC');
  343. if (!name) return null;
  344. return { name, create_number };
  345. }
  346. /**
  347. * 找到深层的tif和xml
  348. * 因为命名方式都是以文件名,之后tif的文件夹也在深层fileName.套娃且文件名都是fileName
  349. * @param {String} uri temp路径
  350. */
  351. async findFileNameLoop(uri) {
  352. const filePath = uri;
  353. if (!fs.existsSync(filePath)) {
  354. return [];
  355. }
  356. const files = fs.readdirSync(filePath);
  357. let result = [];
  358. for (const file of files) {
  359. const curPath = path.resolve(filePath, file);
  360. if (fs.statSync(curPath).isDirectory()) {
  361. const arr = await this.findFileNameLoop(curPath);
  362. result.push(...arr);
  363. } else {
  364. result.push(curPath);
  365. }
  366. }
  367. // 2023-02-07 原来是.tif,现在是.pdf
  368. result = result.filter((f) => f.includes('.pdf') || f.includes('.xml'));
  369. return result;
  370. }
  371. /**
  372. * 删除路径下的所有内容
  373. * @param {String} filePath 路径
  374. */
  375. async dirDelete(filePath) {
  376. if (fs.existsSync(filePath)) {
  377. const files = fs.readdirSync(filePath);
  378. for (const file of files) {
  379. const curPath = `${filePath}${sep}${file}`;
  380. if (fs.statSync(curPath).isDirectory()) {
  381. // recurse
  382. this.dirDelete(curPath);
  383. } else {
  384. // delete file
  385. fs.unlinkSync(curPath);
  386. }
  387. }
  388. fs.rmdirSync(filePath);
  389. }
  390. }
  391. /**
  392. * 删除路径下的所有zip
  393. * @param {String} filePath 路径
  394. */
  395. async deleteZip(filePath) {
  396. if (!fs.existsSync(filePath)) return;
  397. const files = fs.readdirSync(filePath);
  398. for (const file of files) {
  399. if (!file.includes('.zip')) continue;
  400. const curPath = `${filePath}${sep}${file}`;
  401. if (!fs.existsSync(curPath)) continue;
  402. fs.unlinkSync(curPath);
  403. }
  404. }
  405. }
  406. module.exports = PatentwarningService;