zs 2 yıl önce
ebeveyn
işleme
5546e9863b

+ 2 - 0
package.json

@@ -24,7 +24,9 @@
     "amqp-connection-manager": "^4.1.13",
     "amqplib": "^0.10.3",
     "compressing": "^1.9.0",
+    "encoding": "^0.1.13",
     "exceljs": "^4.3.0",
+    "fast-xml-parser": "^4.2.2",
     "free-midway-component": "^1.0.35",
     "midway-schedule": "^2.15.0",
     "moment": "^2.29.4",

+ 8 - 0
src/controller/cpcMessage.controller.ts

@@ -18,6 +18,7 @@ import {
   QVO_cpcMessage,
   UDTO_cpcMessage,
   UVAO_cpcMessage,
+  ImportDTO,
 } from '../interface/cpcMessage.interface';
 import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
 import { Validate } from '@midwayjs/validate';
@@ -75,6 +76,13 @@ export class CpcMessageController extends BaseController {
     await this.service.delete(id);
     return 'ok';
   }
+
+  @Post('/import')
+  @Validate()
+  async import(@Body() body: ImportDTO) {
+    const result = await this.service.import(body);
+    return result;
+  }
   async createMany(...args: any[]) {
     throw new Error('Method not implemented.');
   }

+ 6 - 0
src/interface/cpcMessage.interface.ts

@@ -102,6 +102,12 @@ export class CDTO_cpcMessage {
   'is_read': string = undefined;
 }
 
+export class ImportDTO {
+  @ApiProperty({ description: 'url地址', example: '' })
+  @Rule(RuleType['string']().empty(''))
+  'url': string = undefined;
+}
+
 export class CVO_cpcMessage extends FVO_cpcMessage {
   constructor(data: object) {
     super(data);

+ 352 - 1
src/service/cpcMessage.service.ts

@@ -1,11 +1,362 @@
 import { Provide } from '@midwayjs/decorator';
 import { InjectEntityModel } from '@midwayjs/typegoose';
 import { ReturnModelType } from '@typegoose/typegoose';
-import { BaseService } from 'free-midway-component';
+import {
+  BaseService,
+  FrameworkErrorEnum,
+  ServiceError,
+} from 'free-midway-component';
 import { CpcMessage } from '../entity/cpcMessage.entity';
+import { Config } from '@midwayjs/core';
+import _ = require('lodash');
+const compressing = require('compressing');
+const encoding = require('encoding');
+const fxp = require('fast-xml-parser');
+import * as fs from 'node:fs';
+import * as Path from 'node:path';
+import { Apply } from '../entity/apply.entity';
+import { Patent } from '../entity/patent.entity';
+const sep = Path.sep;
+import { Types } from 'mongoose';
+const ObjectId = Types.ObjectId;
 type modelType = ReturnModelType<typeof CpcMessage>;
 @Provide()
 export class CpcMessageService extends BaseService<modelType> {
   @InjectEntityModel(CpcMessage)
   model: modelType;
+
+  @InjectEntityModel(Apply)
+  ApplyModel: ReturnModelType<typeof Apply>;
+
+  @InjectEntityModel(Patent)
+  PatentModel: ReturnModelType<typeof Patent>;
+
+  @Config('export.root_path')
+  import_root_path;
+
+  async import(body) {
+    let uri = body.url;
+    uri = uri.replace('/files', this.import_root_path);
+    const fileFullName = Path.basename(uri);
+    const arr = fileFullName.split('.');
+    const ext = _.last(arr);
+    const uncompressFilePath = `${this.import_root_path}${sep}temp`;
+    if (ext !== 'zip') {
+      throw new ServiceError(
+        '只接收压缩格式为zip的压缩包',
+        FrameworkErrorEnum.BAD_QUERY
+      );
+    }
+    // 解压
+    try {
+      await compressing.zip.uncompress(uri, `${uncompressFilePath}`);
+    } catch (error) {
+      throw new ServiceError('解压失败', FrameworkErrorEnum.REQUEST_FAULT);
+    }
+    // 修改,多个专利:需要扫描下解压后的文件夹,然后循环处理下面所有的内容
+    const tempDirRootPath = `${uncompressFilePath}${sep}`;
+    const tempDir = await fs.readdirSync(tempDirRootPath);
+    const errorList = [];
+    for (const tempDirPath of tempDir) {
+      const thisDirPath = `${tempDirRootPath}${tempDirPath}`;
+      const patentNumber = tempDirPath;
+      try {
+        // 找到
+        let filePaths = [];
+        try {
+          const arr = patentNumber.split('.');
+          const fileNametwo = _.head(arr);
+          const exttwo = _.last(arr);
+          const tempDirtwo = `${tempDirRootPath}${fileNametwo}`;
+          if (exttwo === 'zip') {
+            await compressing.zip.uncompress(thisDirPath, tempDirtwo);
+            filePaths = await this.findFileNameLoop(`${tempDirtwo}${sep}`);
+          }
+        } catch (error) {
+          errorList.push({ key: patentNumber, word: '解析文件具体位置失败' });
+          continue;
+        }
+        if (filePaths.length <= 0) {
+          errorList.push({ key: patentNumber, word: '未找到文件' });
+          continue;
+        }
+        // 2021-10-16 修改: 取出第一层的xml文件,即:路径最短的;除文件名外,字符串长度最小的
+        let xmlPaths = filePaths.filter(f => f.includes('.xml'));
+        if (xmlPaths.length > 0) {
+          // 去掉文件名
+          xmlPaths = xmlPaths.map(i => {
+            const arr = i.split(sep);
+            arr.pop();
+            const obj = { dir: arr.join(sep), fullPath: i };
+            return obj;
+          });
+          // 按路径字符串长度排序
+          xmlPaths = xmlPaths.sort((a, b) => {
+            const anum = a.dir.length;
+            const bnum = b.dir.length;
+            return anum - bnum;
+          });
+        }
+        const xmlPath = _.get(_.head(xmlPaths), 'fullPath');
+        if (!xmlPath) {
+          errorList.push({ key: patentNumber, word: '未找到xml文件' });
+          continue;
+        }
+        let xmlData;
+        try {
+          xmlData = await this.dealXml(xmlPath);
+        } catch (error) {
+          errorList.push({
+            key: patentNumber,
+            word: `解析xml数据中的 申请号 和 专利名称 失败 于:${xmlPath}`,
+          });
+          continue;
+        }
+        if (!xmlData) {
+          errorList.push({ key: patentNumber, word: '未找到xml的数据' });
+          continue;
+        }
+        let result;
+        try {
+          result = await this.getPatent(xmlData);
+        } catch (error) {
+          errorList.push({ key: patentNumber, word: '查询专利信息失败' });
+          continue;
+        }
+        // 找不到就算了,弹回去了
+        if (!result || !_.get(result, 'patent')) {
+          errorList.push({
+            key: patentNumber,
+            word: '未通过 申请号 或 专利名称 找到指定专利',
+          });
+          continue;
+        }
+        const patent = _.get(result, 'patent');
+        let warningData;
+        try {
+          warningData = await this.getWarningData(patent);
+        } catch (error) {
+          errorList.push({
+            key: patentNumber,
+            word: '组织专利信息警告数据失败',
+          });
+          continue;
+        }
+        const tifPaths = filePaths.filter(f => f.includes('.tif'));
+        if (tifPaths && tifPaths.length > 0) {
+          try {
+            await this.toUploadTif(warningData, tifPaths);
+          } catch (error) {
+            errorList.push({ key: patentNumber, word: '存储数据失败' });
+            continue;
+          }
+        }
+        // 查看是否需要更改申请号
+        const needChange = _.get(result, 'needChange');
+        if (needChange) {
+          try {
+            await this.ApplyModel.updateOne({ _id: patent._id }, patent);
+          } catch (error) {
+            continue;
+          }
+        }
+      } finally {
+        try {
+          await this.dirDelete(`${thisDirPath}`);
+        } catch (error) {
+          this.ctx.logger.error('清除缓存失败');
+        }
+      }
+    }
+    if (errorList.length > 0) return errorList;
+    return 'ok';
+  }
+
+  /**
+   * 上传图片,保存
+   * @param {Array} data 警告数据,未保存
+   * @param {Array} files tif图片文件路径数组
+   */
+  async toUploadTif(data, files) {
+    const filePaths = [];
+    const savePath = new ObjectId();
+    for (const file of files) {
+      let arr = file.split(sep);
+      const fullFileName: any = _.last(arr);
+      arr = fullFileName.split('.');
+      // 文件后缀
+      const ext = _.last(arr);
+      // 图片文件
+      const buffer = await fs.readFileSync(file);
+      // 新文件名
+      const randomStr = new ObjectId();
+      // 新文件名带后缀
+      const newFileName = `${randomStr}.${ext}`;
+      // 上传后部分路径
+      const shortPath = `platform${sep}patent${sep}${savePath}${sep}`;
+      // 上传全路径
+      const path = `${this.import_root_path}${sep}${shortPath}`;
+      // 检查上传全路径是否存在,不存在就创建文件夹
+      await this.checkPathAndCreate(path);
+      // 另存文件
+      await fs.writeFileSync(`${path}${newFileName}`, buffer);
+      // 替换为files开头
+      let dataPath = path.replace(this.import_root_path, '/files');
+      while (dataPath.indexOf('\\') >= 0) {
+        dataPath = dataPath.replace('\\', '/');
+      }
+      // 补全请求用路径的所有路径
+      dataPath += newFileName;
+      filePaths.push(dataPath);
+    }
+    for (const i of data) {
+      i.file_url = filePaths;
+    }
+    const res = await this.model.insertMany(data);
+    return res;
+  }
+
+  /**
+   * 检查路径,没有创建
+   * @param {Array} filePath 图片路径数组
+   */
+  async checkPathAndCreate(filePath) {
+    const arr = filePath.split(sep);
+    // w:第一个是盘符,直接和第二个合并
+    // l:前几个应该都是存在的.也可以合并
+    let head = _.head(arr);
+    if (arr[1]) head = `${head}${sep}${arr[1]}`;
+    arr.shift();
+    arr[0] = head;
+    let curPath;
+    for (const p of arr) {
+      curPath = `${curPath ? curPath + sep : ''}${p}`;
+      if (!fs.existsSync(curPath)) {
+        await fs.mkdirSync(curPath);
+      }
+    }
+  }
+
+  /**
+   * 组织专利预警数据
+   * @param {Object} patent 专利
+   */
+  async getWarningData(patent) {
+    const object: any = {};
+    if (_.get(patent, 'create_number'))
+      object.create_number = _.get(patent, 'create_number');
+    if (_.get(patent, 'id')) object.patent_id = _.get(patent, 'id');
+    if (_.get(patent, 'name')) object.patent_name = _.get(patent, 'name');
+    // 找人的信息
+    const users = _.get(patent, 'inventor');
+    const arr = [];
+    for (const user of users) {
+      if (!_.get(user, 'user_id') || !_.get(user, 'name')) continue;
+      const { user_id, name } = user;
+      const obj = _.cloneDeep(object);
+      obj.to_id = user_id;
+      obj.to_name = name;
+      arr.push(obj);
+    }
+    return arr;
+  }
+
+  /**
+   * 在patentApply/patentInfo表中找指定的专利
+   * @param {Object} object xml的数据
+   */
+  async getPatent(object) {
+    const { name, create_number } = object;
+    let res;
+    let needChange = false;
+    res = await this.ApplyModel.findOne({ create_number });
+    // 如果在申请中,使用名称找到,则之后需要将该数据的专利号改为xml中的专利号
+    if (!res) {
+      res = await this.ApplyModel.findOne({ name });
+      needChange = true;
+    }
+    if (!res) {
+      res = await this.PatentModel.findOne({ create_number });
+    }
+    if (!res) {
+      res = await this.PatentModel.findOne({ name });
+    }
+    const returnObject: any = {};
+    if (res) {
+      if (needChange) {
+        res.create_number = create_number;
+        returnObject.needChange = needChange;
+      }
+      returnObject.patent = res;
+      return returnObject;
+    }
+    return null;
+  }
+
+  /**
+   * 根据路径找到xml,再找到该xml的申请号和专利名称
+   * @param {String} filePath xml的文件路径
+   */
+  async dealXml(filePath) {
+    const buffer = await fs.readFileSync(filePath);
+    let str = encoding.convert(buffer, 'UTF-8', 'GBK');
+    if (!str)
+      throw new ServiceError(
+        '读取xml文件失败',
+        FrameworkErrorEnum.REQUEST_FAULT
+      );
+    str = str.toString();
+    const json = fxp.parse(str);
+    const create_number = _.get(
+      json,
+      'data-bus.TONGZHISXJ.SHUXINGXX.SHENQINGH'
+    );
+    const name = _.get(json, 'data-bus.TONGZHISXJ.SHUXINGXX.FAMINGMC');
+    if (!name) return null;
+    return { name, create_number };
+  }
+
+  /**
+   * 找到深层的tif和xml
+   * 因为命名方式都是以文件名,之后tif的文件夹也在深层fileName.套娃且文件名都是fileName
+   * @param {String} uri temp路径
+   */
+  async findFileNameLoop(uri) {
+    const filePath = `${_.trimEnd(uri, sep)}${sep}`;
+    if (!fs.existsSync(filePath)) {
+      return [];
+    }
+    const files = fs.readdirSync(filePath);
+    let result = [];
+    for (const file of files) {
+      const curPath = `${filePath}${file}`;
+      if (fs.statSync(curPath).isDirectory()) {
+        const arr = await this.findFileNameLoop(curPath);
+        result.push(...arr);
+      } else {
+        result.push(curPath);
+      }
+    }
+    result = result.filter(f => f.includes('.tif') || f.includes('.xml'));
+    return result;
+  }
+
+  /**
+   * 删除路径下的所有内容
+   * @param {String} filePath 路径
+   */
+  async dirDelete(filePath) {
+    if (fs.existsSync(filePath)) {
+      const files = fs.readdirSync(filePath);
+      for (const file of files) {
+        const curPath = `${filePath}${sep}${file}`;
+        if (fs.statSync(curPath).isDirectory()) {
+          this.dirDelete(curPath);
+        } else {
+          fs.unlinkSync(curPath);
+        }
+      }
+      fs.rmdirSync(filePath);
+    }
+  }
 }