|
@@ -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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|