zs 1 年之前
父節點
當前提交
942ba84eb9

+ 4 - 0
package.json

@@ -17,6 +17,9 @@
     "@midwayjs/typegoose": "^3.11.3",
     "@midwayjs/validate": "^3.0.0",
     "@typegoose/typegoose": "^11.0.1",
+    "amqp-connection-manager": "^4.1.13",
+    "amqplib": "^0.10.3",
+    "compressing": "^1.9.0",
     "exceljs": "^4.3.0",
     "free-midway-component": "^1.0.35",
     "moment": "^2.29.4",
@@ -26,6 +29,7 @@
   "devDependencies": {
     "@midwayjs/cli": "^2.0.0",
     "@midwayjs/mock": "^3.0.0",
+    "@types/amqplib": "^0.10.1",
     "@types/jest": "^29.2.0",
     "@types/jsonwebtoken": "^9.0.1",
     "@types/koa": "^2.13.4",

+ 18 - 0
src/config/config.default.ts

@@ -18,6 +18,24 @@ export default {
       zkzx_admin: {
         baseURL: 'http://127.0.0.1:12001/zkzx/v2/api',
       },
+      Axios: {
+        baseURL: 'http://broadcast.waityou24.cn',
+        //表明返回服务器返回的数据类型
+        responseType: 'arraybuffer',
+        headers: {
+          'Content-Type': 'application/json; application/octet-stream',
+        },
+      },
     },
   },
+  export: {
+    root_path: 'D:\\temp',
+    export_path: 'D:\\temp\\export',
+    export_dir: 'export',
+    patentInfo_dir: 'patentInfo',
+    domain: 'http://broadcast.waityou24.cn',
+  },
+  rabbitmq: {
+    url: 'amqp://localhost',
+  },
 } as MidwayConfig;

+ 18 - 0
src/config/config.local.ts

@@ -38,6 +38,24 @@ export default {
       zkzx_admin: {
         baseURL: 'http://127.0.0.1:12001/zkzx/v2/api',
       },
+      Axios: {
+        baseURL: 'http://broadcast.waityou24.cn',
+        //表明返回服务器返回的数据类型
+        responseType: 'arraybuffer',
+        headers: {
+          'Content-Type': 'application/json; application/octet-stream',
+        },
+      },
     },
   },
+  export: {
+    root_path: 'D:\\temp',
+    export_path: 'D:\\temp\\export',
+    export_dir: 'export',
+    patentInfo_dir: 'patentInfo',
+    domain: 'http://broadcast.waityou24.cn',
+  },
+  rabbitmq: {
+    url: 'amqp://localhost',
+  },
 } as MidwayConfig;

+ 18 - 0
src/config/config.prod.ts

@@ -38,6 +38,24 @@ export default {
       zkzx_admin: {
         baseURL: 'http://127.0.0.1:12001/zkzx/v2/api',
       },
+      Axios: {
+        baseURL: 'http://broadcast.waityou24.cn',
+        //表明返回服务器返回的数据类型
+        responseType: 'arraybuffer',
+        headers: {
+          'Content-Type': 'application/json; application/octet-stream',
+        },
+      },
     },
   },
+  export: {
+    root_path: 'D:\\temp',
+    export_path: 'D:\\temp\\export',
+    export_dir: 'export',
+    patentInfo_dir: 'patentInfo',
+    domain: 'http://broadcast.waityou24.cn',
+  },
+  rabbitmq: {
+    url: 'amqp://localhost',
+  },
 } as MidwayConfig;

+ 16 - 0
src/controller/patent.controller.ts

@@ -18,6 +18,7 @@ import {
   QVO_patent,
   UDTO_patent,
   UVAO_patent,
+  ImportDTO,
 } from '../interface/patent.interface';
 import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
 import { Validate } from '@midwayjs/validate';
@@ -75,6 +76,21 @@ export class PatentController 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;
+  }
+
+  @Post('/export')
+  @Validate()
+  async export(@Body() body: any) {
+    const result = await this.service.export(body);
+    return result;
+  }
+
   async createMany(...args: any[]) {
     throw new Error('Method not implemented.');
   }

+ 11 - 1
src/entity/patent.entity.ts

@@ -13,7 +13,7 @@ export class Patent extends BaseModel {
   @prop({ required: false, index: true, zh: '公开(公告)日' })
   success_date: string;
   @prop({ required: false, index: true, zh: '发明人' })
-  inventor: Array<any>;
+  inventor: string;
   @prop({ required: false, index: false, zh: '代理机构' })
   agent: string;
   @prop({ required: false, index: false, zh: '代理人' })
@@ -102,4 +102,14 @@ export class Patent extends BaseModel {
     default: '0',
   })
   status: string;
+  @prop({
+    required: false,
+    index: true,
+    zh: '交易状态',
+    remark: '字典:common_status',
+    default: '0',
+  })
+  trans_status: string;
+  @prop({ required: false, index: false, zh: '专利关联人' })
+  users: Array<any>;
 }

+ 26 - 4
src/interface/patent.interface.ts

@@ -23,7 +23,7 @@ export class FVO_patent {
   @ApiProperty({ description: '公开(公告)日' })
   'success_date': string = undefined;
   @ApiProperty({ description: '发明人' })
-  'inventor': Array<any> = undefined;
+  'inventor': string = undefined;
   @ApiProperty({ description: '代理机构' })
   'agent': string = undefined;
   @ApiProperty({ description: '代理人' })
@@ -96,6 +96,10 @@ export class FVO_patent {
   'pct_publish': string = undefined;
   @ApiProperty({ description: '状态' })
   'status': string = undefined;
+  @ApiProperty({ description: '交易状态' })
+  'trans_status': string = undefined;
+  @ApiProperty({ description: '专利关联人' })
+  'users': Array<any> = undefined;
 }
 
 export class QDTO_patent extends SearchBase {
@@ -115,6 +119,7 @@ export class QDTO_patent extends SearchBase {
       'first_apply',
       'first_inventor',
       'status',
+      'trans_status',
     ];
     const mapping = [];
     super({ like_prop, props, mapping });
@@ -128,7 +133,7 @@ export class QDTO_patent extends SearchBase {
   @ApiProperty({ description: '公开(公告)日' })
   'success_date': string = undefined;
   @ApiProperty({ description: '发明人' })
-  'inventor': Array<any> = undefined;
+  'inventor': string = undefined;
   @ApiProperty({ description: '发明人地址' })
   'address': string = undefined;
   @ApiProperty({ description: '标题(中文)' })
@@ -145,6 +150,8 @@ export class QDTO_patent extends SearchBase {
   'first_inventor': string = undefined;
   @ApiProperty({ description: '状态' })
   'status': string = undefined;
+  @ApiProperty({ description: '交易状态' })
+  'trans_status': string = undefined;
 }
 
 export class QVO_patent extends FVO_patent {
@@ -168,8 +175,8 @@ export class CDTO_patent {
   @Rule(RuleType['string']().empty(''))
   'success_date': string = undefined;
   @ApiProperty({ description: '发明人' })
-  @Rule(RuleType['array']().empty(''))
-  'inventor': Array<any> = undefined;
+  @Rule(RuleType['string']().empty(''))
+  'inventor': string = undefined;
   @ApiProperty({ description: '代理机构' })
   @Rule(RuleType['string']().empty(''))
   'agent': string = undefined;
@@ -278,6 +285,12 @@ export class CDTO_patent {
   @ApiProperty({ description: '状态' })
   @Rule(RuleType['string']().empty(''))
   'status': string = undefined;
+  @ApiProperty({ description: '交易状态' })
+  @Rule(RuleType['string']().empty(''))
+  'trans_status': string = undefined;
+  @ApiProperty({ description: '专利关联人' })
+  @Rule(RuleType['array']().empty(''))
+  'users': Array<any> = undefined;
 }
 
 export class CVO_patent extends FVO_patent {
@@ -299,3 +312,12 @@ export class UVAO_patent extends FVO_patent {
     dealVO(this, data);
   }
 }
+
+export class ImportDTO {
+  @ApiProperty({ description: 'url地址', example: '' })
+  @Rule(RuleType['string']().empty(''))
+  'url': string = undefined;
+  @ApiProperty({ description: '验证码', example: '' })
+  @Rule(RuleType['string']().empty(''))
+  'code': string = undefined;
+}

+ 6 - 2
src/service/analysis.service.ts

@@ -68,7 +68,9 @@ export class AnalysisService extends BaseService<modelType> {
         create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
         save_id: admin_id,
         save_name: admin_name,
-        content: `${user_name}提交了${name}专利的查新检索交底单已经审核完成,审核意见:${desc},请及时查看审核通知消息`,
+        content: `${user_name}提交了${name}专利的查新检索交底单已经审核完成,审核意见:${
+          desc || '无'
+        },请及时查看审核通知消息`,
       };
       if (body.record) body.record.push(info);
       else body.record = [info];
@@ -104,7 +106,9 @@ export class AnalysisService extends BaseService<modelType> {
         create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
         save_id: user_id,
         save_name: user_name,
-        content: `${user_name}提交了${name}专利的查新检索交底单,审核意见:${desc},报告已接收`,
+        content: `${user_name}提交了${name}专利的查新检索交底单,审核意见:${
+          desc || '无'
+        },报告已接收`,
       };
       if (body.record) body.record.push(info);
       else body.record = [info];

+ 6 - 2
src/service/apply.service.ts

@@ -77,7 +77,9 @@ export class ApplyService extends BaseService<modelType> {
         create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
         save_id: mech_id,
         save_name: mech_name,
-        content: `${user_name}提交了${name}专利审批单已经审核完成,审核意见:${desc},请及时查看并处理`,
+        content: `${user_name}提交了${name}专利审批单已经审核完成,审核意见:${
+          desc || '无'
+        },请及时查看并处理`,
       };
       if (body.record) body.record.push(info);
       else body.record = [info];
@@ -117,7 +119,9 @@ export class ApplyService extends BaseService<modelType> {
       if (status === '6') {
         info.content = `${user_name}提交了${name}专利审批单已经审核完成,并已上传到国知局,请时刻关注cpc消息`;
       } else {
-        info.content = `${user_name}提交了${name}专利审批单已经审核完成,审核意见:${desc},请及时查看并处理`;
+        info.content = `${user_name}提交了${name}专利审批单已经审核完成,审核意见:${
+          desc || '无'
+        },请及时查看并处理`;
       }
       if (body.record) body.record.push(info);
       else body.record = [info];

+ 6 - 2
src/service/assessment.service.ts

@@ -68,7 +68,9 @@ export class AssessmentService extends BaseService<modelType> {
         create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
         save_id: admin_id,
         save_name: admin_name,
-        content: `${user_name}提交了${name}专利的价值评估单已经审核完成,审核意见:${desc},请及时查看审核通知消息`,
+        content: `${user_name}提交了${name}专利的价值评估单已经审核完成,审核意见:${
+          desc || '无'
+        },请及时查看审核通知消息`,
       };
       if (body.record) body.record.push(info);
       else body.record = [info];
@@ -104,7 +106,9 @@ export class AssessmentService extends BaseService<modelType> {
         create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
         save_id: user_id,
         save_name: user_name,
-        content: `${user_name}提交了${name}专利的价值评估单,审核意见:${desc},报告已接收`,
+        content: `${user_name}提交了${name}专利的价值评估单,审核意见:${
+          desc || '无'
+        },报告已接收`,
       };
       if (body.record) body.record.push(info);
       else body.record = [info];

+ 526 - 2
src/service/patent.service.ts

@@ -1,11 +1,535 @@
-import { Provide } from '@midwayjs/decorator';
+import { Provide, Inject } 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 { Patent } from '../entity/patent.entity';
+import { PatentWarning } from '../entity/patentWarning.entity';
+import { HttpServiceFactory, HttpService } from '@midwayjs/axios';
+import { Config, InjectClient } from '@midwayjs/core';
+import { RabbitmqService } from './util/rabbitmq';
+const assert = require('assert');
+const Excel = require('exceljs');
+import _ = require('lodash');
+const moment = require('moment');
+const compressing = require('compressing');
+import * as fs from 'node:fs';
+import * as Path from 'node:path';
+const sep = Path.sep;
 type modelType = ReturnModelType<typeof Patent>;
 @Provide()
 export class PatentService extends BaseService<modelType> {
   @InjectEntityModel(Patent)
   model: modelType;
+
+  @InjectEntityModel(PatentWarning)
+  PatentModel: ReturnModelType<typeof PatentWarning>;
+
+  @InjectClient(HttpServiceFactory, 'Axios')
+  Axios: HttpService;
+
+  @InjectClient(HttpServiceFactory, 'zkzx_admin')
+  adminAxios: HttpService;
+
+  @Config('export.root_path')
+  root_path;
+  @Config('export.patentInfo_dir')
+  patentInfo_dir;
+  @Config('export.file_type')
+  file_type;
+  @Config('export.export_dir')
+  export_dir;
+
+  @Inject()
+  rabbitmqService: RabbitmqService;
+  // 导入
+  async import({ url, code }) {
+    assert(url, '未获取到文件地址');
+    const file = await this.Axios.get(url);
+    if (!file) {
+      throw new ServiceError(
+        '未找到指定文件',
+        FrameworkErrorEnum.NOT_FOUND_DATA
+      );
+    }
+    const workbook = new Excel.Workbook();
+    await workbook.xlsx.load(file);
+    const sheet = workbook.getWorksheet(1);
+    const arr = [];
+    const allNotice = [];
+    const sheetImageInfo = sheet.getImages();
+    const imgids: any = _.compact(
+      sheetImageInfo.map(i => {
+        const { imageId, range } = i;
+        const row = _.get(range, 'tl.nativeRow');
+        if (row) return { row, imageId };
+      })
+    );
+    // 2021-11-18 修改导入,根据model文件(和excel顺序一致)的顺序进行取字段
+    const models: any = _.get(this.model.schema, 'tree');
+    delete models._id;
+    const meta = Object.keys(models).map((i, index) => ({
+      key: i,
+      index: index + 2,
+    }));
+    const termList: any = await this.adminAxios.get(
+      'dictData?type=patent_term'
+    );
+    const typeList: any = await this.adminAxios.get(
+      'dictData?type=patent_type'
+    );
+    sheet.eachRow((row, rindex) => {
+      if (rindex !== 1) {
+        // 组织数据,图片的索引和行索引不一致,准确的说是:图片索引比行索引少1
+        // 原因:图片在工作簿中获取,按照1,2,3...顺序排序,但是行的第一行是表头(当前文件),所以当前行数需要减掉表头那一行
+        const imgid: any = imgids.find(f => f.row === rindex - 1);
+        const file = [];
+        if (imgid) {
+          file.push({
+            url: this.turnImageToBase64(workbook.getImage(imgid.imageId)),
+          });
+        }
+        // 根据meta整理数据
+        // 需要日期转换的字段
+        const dateColumnArray = [
+          'create_date',
+          'success_date',
+          'law_date',
+          'first_opendate',
+          'empower_date',
+          'lose_date',
+          'examine_date',
+        ];
+        let obj: any = { file };
+        for (const m of meta) {
+          const { key, index } = m;
+          if (dateColumnArray.includes(key)) {
+            obj[key] = _.get(row.getCell(index), 'value')
+              ? moment(_.get(row.getCell(index), 'value')).format('YYYY-MM-DD')
+              : '';
+          } else if (key === 'file') {
+            // 上面默认处理了
+            continue;
+          } else if (key === 'incopat_link') {
+            obj[key] = this.getUrl(_.get(row.getCell(index), 'value'));
+          } else if (key === 'term') {
+            const data = termList.data.find(
+              i => i.label === _.get(row.getCell(index), 'value')
+            );
+            obj[key] = _.get(data, 'value');
+          } else if (key === 'type') {
+            const data = typeList.data.find(
+              i => i.label === _.get(row.getCell(index), 'value')
+            );
+            obj[key] = _.get(data, 'value');
+          } else obj[key] = _.get(row.getCell(index), 'value');
+        }
+        obj = _.pickBy(obj, _.identity);
+        arr.push(obj);
+      }
+    });
+    for (const obj of arr) {
+      const { result, notice } = this.tocheckData(obj);
+      if (!result) {
+        allNotice.push(notice);
+      }
+    }
+    if (allNotice.length > 0) return allNotice;
+    // 有选择机构,创建用户
+    if (code) {
+      for (const obj of arr) {
+        const { first_inventor, create_number } = obj;
+        try {
+          let person: any = await this.adminAxios.get(
+            `/personal?code=${code}&account=${first_inventor}`
+          );
+          const user_id: any = {};
+          // 有用户,直接取id,没有用户;创建用户,取id;
+          if (person && person.total > 0) {
+            user_id.user_id = person.data[0]._id || person.data[0].id;
+            user_id.name = person.data[0].name;
+          } else {
+            const personObject = {
+              code,
+              account: first_inventor,
+              password: '123456',
+              name: first_inventor,
+              status: '1',
+            };
+            try {
+              person = await this.adminAxios.post('/personal', personObject);
+            } catch (error) {
+              allNotice.push(
+                `${create_number} 的 第一发明人用户,已有机构所属的个人用户使用该手机号!`
+              );
+              continue;
+            }
+            if (person.data) {
+              user_id.user_id = person.data._id || person.data.id;
+              user_id.name = person.data.name;
+            }
+          }
+          obj.users = [JSON.parse(JSON.stringify(user_id))];
+        } catch (error) {
+          allNotice.push(`${create_number} 的 第一发明人用户 创建失败!`);
+          continue;
+        }
+      }
+    }
+    if (allNotice.length > 0) return allNotice;
+    // 根据申请号做添加/修改
+    for (const obj of arr) {
+      const { create_number } = obj;
+      try {
+        const has_data = await this.model.count({
+          create_number: obj.create_number,
+        });
+        let res;
+        if (has_data === 0) {
+          res = await this.model.create(obj);
+        } else {
+          await this.model.updateOne({ create_number: obj.create_number }, obj);
+          res = await this.model.findOne({ create_number: obj.create_number });
+        }
+        // 处理警告
+        if (res.term === '1') this.dealData([res]);
+      } catch (error) {
+        allNotice.push(`申请号 为${create_number} 的 专利信息 创建失败!`);
+        continue;
+      }
+    }
+  }
+  // 导出
+  async export({ missionid, query }) {
+    const path = `${this.root_path}${sep}${this.file_type}${sep}${
+      this.patentInfo_dir
+    }${new Date().getTime()}`;
+    if (!path) {
+      throw new ServiceError(
+        '服务端没有设置存储路径',
+        FrameworkErrorEnum.SERVICE_FAULT
+      );
+    }
+    if (!fs.existsSync(path)) {
+      // 如果不存在文件夹,就创建
+      this.mkdir(path);
+    }
+    const total = await this.model.count(query.filter);
+    let skip = 0;
+    const meta = query.file;
+    const projection = {};
+    for (const m of meta) {
+      const { model } = m;
+      projection[model] = 1;
+    }
+    // 将数据分割,否则容易直接把js堆栈干满了,服务就炸了
+    const head = meta.map(i => i.label);
+    for (let i = 0; i < total; i = i + query.end_num) {
+      try {
+        const workbook = new Excel.Workbook();
+        const sheet = workbook.addWorksheet('sheet');
+        sheet.addRow(head);
+        const list = await this.model
+          .find(query.filter)
+          .skip(query.start_num)
+          .limit(query.end_num);
+        for (let k = 0; k < list.length; k++) {
+          const d = list[k];
+          const arr = [];
+          for (const m of meta) {
+            const { key } = m;
+            if (key === 'img_url') {
+              const value = d[key];
+              // 需要图片占列,否则会窜位置
+              arr.push('');
+              if (_.isArray(value)) {
+                const img = _.get(_.head(value), 'url');
+                if (img && img.includes('image/png')) {
+                  const imgId = workbook.addImage({
+                    base64: img,
+                    extension: 'png',
+                  });
+                  const sheetRange = {
+                    tl: { col: 13, row: k + 1 },
+                    ext: { width: 60, height: 60 },
+                    editAs: 'oneCell',
+                  };
+                  if (_.isFunction(sheet.addImage))
+                    sheet.addImage(imgId, sheetRange);
+                }
+              }
+            } else arr.push(d[key]);
+          }
+          sheet.addRow(arr);
+        }
+        skip = skip + query.end_num;
+        // 生成excel
+        const filename = `专利信息导出结果(${i + 1}-${i + query.end_num}).xlsx`;
+        const filePath = `${path}${sep}${filename}`;
+        console.log(filePath);
+        await workbook.xlsx.writeFile(filePath);
+
+        try {
+          let progress = _.ceil((skip / total) * 100);
+          if (progress > 95) progress = 95;
+          const data = {
+            progress,
+            status: '1',
+            id: missionid,
+            remark: '组织数据生成excel中...',
+          };
+          await this.sendToMQ(data);
+        } catch (error) {
+          this.ctx.logger.error('更新进度失败');
+        }
+      } catch (error) {
+        const data = {
+          progress: 0,
+          status: '-1',
+          id: missionid,
+          remark: '组织数据进入excel失败',
+        };
+        this.sendToMQ(data);
+      }
+    }
+
+    try {
+      const data = {
+        progress: 98,
+        status: '1',
+        id: missionid,
+        remark: '正在打包',
+      };
+      this.sendToMQ(data);
+    } catch (error) {
+      this.ctx.logger.error('正在打包');
+    }
+
+    try {
+      const nowDate = new Date().getTime();
+      const zipName = `导出专利信息-${nowDate}.zip`;
+      const exportPath = `${this.root_path}${sep}${this.export_dir}`;
+      if (!fs.existsSync(exportPath)) {
+        // 如果不存在文件夹,就创建
+        fs.mkdirSync(exportPath);
+      }
+      await compressing.zip.compressDir(path, `${exportPath}${sep}${zipName}`);
+      const downloadPath = `/files/${this.export_dir}/${zipName}`;
+      const data = {
+        progress: 100,
+        status: '2',
+        uri: downloadPath,
+        id: missionid,
+        remark: '打包成功',
+      };
+      this.sendToMQ(data);
+    } catch (error) {
+      const data = {
+        progress: 0,
+        status: '-2',
+        id: missionid,
+        remark: '打包失败',
+      };
+      this.sendToMQ(data);
+      this.ctx.logger.error('打包失败');
+      this.ctx.logger.error(error);
+    }
+    this.dirDelete(path);
+  }
+
+  async sendToMQ(data) {
+    if (
+      _.get(data, 'progress') &&
+      _.isNumber(parseFloat(_.get(data, 'progress'))) &&
+      parseFloat(_.get(data, 'progress')) > 100
+    ) {
+      data.progress = 100;
+    }
+    console.log(`${_.get(data, 'progress') || '失败'} %`);
+    await this.rabbitmqService.connect();
+    await this.rabbitmqService.sendToQueue('tasks', data);
+    await this.rabbitmqService.close();
+  }
+
+  // 创建文件夹
+  mkdir(dirname) {
+    if (fs.existsSync(dirname)) {
+      return true;
+    }
+    if (this.mkdir(Path.dirname(dirname))) {
+      fs.mkdirSync(dirname);
+      return true;
+    }
+  }
+  /**
+   * 删除路径下的所有内容
+   * @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()) {
+          // recurse
+          this.dirDelete(curPath);
+        } else {
+          // delete file
+          fs.unlinkSync(curPath);
+        }
+      }
+      fs.rmdirSync(filePath);
+    }
+  }
+  /**
+   * 转换图片为base64
+   * @param {Object} object excel获取的图片object
+   * @property extension  后缀,文件类型
+   * @property buffer 图片内容,不含头部信息的,
+   */
+  turnImageToBase64(object: any = {}) {
+    const { extension, buffer } = object;
+    if (extension && buffer) {
+      const suffix = object.extension;
+      const ib = object.buffer.toString('base64');
+      const base64 = `data:image/${suffix};base64,${ib}`;
+      return base64;
+    }
+  }
+
+  /**
+   * 将excel的超链接对象中的url
+   * @param {Object} object excel中的超链接对象
+   */
+  getUrl(object) {
+    let urlStr = _.get(object, 'formula');
+    let url = '';
+    if (urlStr) {
+      const s = urlStr.indexOf('(');
+      urlStr = urlStr.substring(s);
+      urlStr = _.trim(_.trim(urlStr, '('), ')');
+      const midArr = urlStr.split(',');
+      url = _.trim(_.head(midArr), '"');
+    }
+    return url;
+  }
+
+  /**
+   * 检查数据是否没填 必填项
+   * @param {Object} object 每行数据,已转换成model的字段名
+   */
+  tocheckData(object) {
+    let result = true;
+    const { number } = object;
+    let notice;
+    const arr = [
+      { column: 'create_number', zh: '申请号' },
+      { column: 'create_date', zh: '申请日' },
+      { column: 'name', zh: '标题' },
+    ];
+    const word = [];
+    for (const o of arr) {
+      const { column, zh } = o;
+      if (!_.get(object, column)) {
+        result = false;
+        word.push(`${zh}`);
+      }
+    }
+    if (!result) {
+      notice = `序号${number}缺少:${word.join(';')}`;
+    }
+    return { result, notice };
+  }
+
+  /**
+   *
+   * @param {Array} data 处理数据
+   */
+  async dealData(data) {
+    // 取出今天是不是在失效时间的前${limitMonth}个月范围内
+    const limitMonth = 3;
+    for (const i of data) {
+      try {
+        const { create_date, create_number, name } = i;
+        // 专利到期时间
+        const expire_date =
+          moment(new Date()).format('YYYY') +
+          '-' +
+          moment(create_date).format('MM-DD');
+        // 专利到期时间延后一个月
+        const end = moment(expire_date).add(1, 'months').format('YYYY-MM-DD');
+        // 专利到期前三个月
+        const start = moment(end)
+          .subtract(limitMonth, 'months')
+          .format('YYYY-MM-DD');
+        // 专利到期前两个月
+        const start_two = moment(end)
+          .subtract(2, 'months')
+          .format('YYYY-MM-DD');
+        // 专利到期前一个月
+        const start_thr = moment(end)
+          .subtract(1, 'months')
+          .format('YYYY-MM-DD');
+        // 判断是否是三个月的区间
+        const r = moment().isBetween(start, end, null, '[]');
+        // 判断是否在一个月的区间
+        if (r) {
+          // 三个月内的第一天||两个月内第一天||一个月内的每天 发送消息
+          // 是否发送的变量
+          let dr = false;
+          const toDay = moment().format('YYYY-MM-DD');
+          if (toDay === start || toDay === start_two || toDay === start_thr) {
+            dr = true;
+          }
+          // 不发就继续
+          if (!dr) continue;
+          const { users } = i;
+          const user_id = users.map(i => i.user_id);
+          // 判断预警次数
+          const early_num =
+            toDay === start
+              ? 1
+              : toDay === start_two
+              ? 2
+              : toDay === start_thr
+              ? 3
+              : '';
+          // 截止日期
+          const lose_date = end;
+          const content = '您可能需缴年费了,具体以缴费通知书为准 ';
+          const nobj = {
+            ..._.omit(i, ['_id', 'id', 'users']),
+            content,
+            parent_id: i._id,
+            users: user_id,
+            early_num,
+            lose_date,
+          };
+          this.model.create(nobj);
+          // 2021-11-04添加 向 patentexamine 表中添加数据
+          const patentexamineArray = [];
+          for (const i of users) {
+            const { user_id } = i;
+            // const peContent = `【${name}】 专利即将过期,请用户及时到相应功能模块中处理专利信息!`;
+            const peContent = '您可能需缴年费了,具体以缴费通知书为准';
+            const patentexamineObject = {
+              create_number,
+              patnet_name: name,
+              send_date: moment().format('YYYY-MM-DD HH:mm:ss'),
+              lose_date,
+              receive: user_id,
+              content: peContent,
+            };
+            patentexamineArray.push(patentexamineObject);
+          }
+          this.PatentModel.insertMany(patentexamineArray);
+        }
+      } catch (error) {
+        continue;
+      }
+    }
+  }
 }

+ 46 - 0
src/service/util/rabbitmq.ts

@@ -0,0 +1,46 @@
+import {
+  Provide,
+  Scope,
+  ScopeEnum,
+  Init,
+  Autoload,
+  Destroy,
+} from '@midwayjs/core';
+import * as amqp from 'amqp-connection-manager';
+
+@Autoload()
+@Provide()
+@Scope(ScopeEnum.Singleton) // Singleton 单例,全局唯一(进程级别)
+export class RabbitmqService {
+  private connection: amqp.AmqpConnectionManager;
+
+  private channelWrapper;
+
+  @Init()
+  async connect() {
+    // 创建连接,你可以把配置放在 Config 中,然后注入进来
+    this.connection = await amqp.connect('amqp://localhost');
+
+    // 创建 channel
+    this.channelWrapper = this.connection.createChannel({
+      json: true,
+      setup: function (channel) {
+        return Promise.all([
+          // 绑定队列
+          channel.assertQueue('tasks', { durable: true }),
+        ]);
+      },
+    });
+  }
+
+  // 发送消息
+  public async sendToQueue(queueName: string, data: any) {
+    return this.channelWrapper.sendToQueue(queueName, data);
+  }
+
+  @Destroy()
+  async close() {
+    await this.channelWrapper.close();
+    await this.connection.close();
+  }
+}