123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- 'use strict';
- const assert = require('assert');
- const moment = require('moment');
- const Excel = require('exceljs');
- const Path = require('path');
- const _ = require('lodash');
- const { sep } = require('path');
- const fs = require('fs');
- const { CrudService } = require('naf-framework-mongoose-free/lib/service');
- const { BusinessError, ErrorCode } = require('naf-core').Error;
- const { trimData } = require('naf-core').Util;
- const { ObjectId } = require('mongoose').Types;
- const compressing = require('compressing');
- // 专利信息
- class PatentinfoService extends CrudService {
- constructor(ctx) {
- super(ctx, 'patentinfo');
- this.model = this.ctx.model.Patent.Patentinfo;
- this.personalModel = this.ctx.model.User.Personal;
- this.adminModel = this.ctx.model.User.Admin;
- this.organizationModel = this.ctx.model.User.Organization;
- this.root_path = _.get(this.ctx.app.config.export, 'root_path');
- this.patentInfo_dir = _.get(this.ctx.app.config.export, 'patentInfo_dir');
- this.export_dir = _.get(this.ctx.app.config.export, 'export_dir');
- // if (process.env.NODE_ENV === 'development') {
- // this.root_path = 'S:\\workspace\\exportFile\\';
- // }
- this.file_type = '';
- if (!fs.existsSync(`${this.root_path}${this.file_type}`)) {
- // 如果不存在文件夹,就创建
- fs.mkdirSync(`${this.root_path}${this.file_type}`);
- }
- this.excel_path = `${sep}excel${sep}`;
- this.domain = 'http://127.0.0.1';
- this.export_limit = 500;
- }
- async query(query, { skip = 0, limit = 0, projection }) {
- const newquery = await this.resetCode(query);
- const res = await this.model.find(newquery, projection).skip(parseInt(skip)).limit(parseInt(limit))
- .sort({ create_date: -1 });
- return res;
- }
- async count(query) {
- const newquery = await this.resetCode(query);
- const res = await this.model.countDocuments(trimData(newquery)).exec();
- return res;
- }
- async resetCode(query) {
- query = this.dealKey(query);
- let newquery = _.cloneDeep(query);
- newquery = this.ctx.service.util.util.dealQuery(newquery);
- if (Object.keys(newquery).length <= 0) return newquery;
- const { pid, user_id, key_word, single_inventor } = newquery;
- let arr = [];
- if (pid) {
- const plist = await this.adminModel.find({ pid, role: '2' }, { _id: 1, deptname: 1 });
- arr = plist.map(i => i.deptname);
- if (arr.length > 0) {
- newquery.first_apply = arr;
- delete newquery.pid;
- }
- }
- if (user_id) {
- newquery['user_id.user_id'] = user_id;
- delete newquery.user_id;
- }
- if (key_word) {
- newquery.$or = [{ name: new RegExp(key_word) }, { abstract: new RegExp(key_word) }, { first_ask: new RegExp(key_word) }];
- delete newquery.key_word;
- }
- if (single_inventor) {
- newquery['inventor.name'] = { $in: [ single_inventor ] };
- delete newquery.single_inventor;
- }
- return newquery;
- }
- /**
- * 处理模糊查询
- * @param {Object} query 查询条件
- */
- dealKey(query) {
- const vagueList = [
- 'inventor',
- 'agent',
- 'agent_personal',
- 'abstract',
- 'address',
- 'name',
- 'apply_personal',
- 'nationality',
- 'ipc_type',
- 'onlegal_status',
- 'legal_status',
- 'on_obligee',
- 'apply_address',
- 'apply_other',
- 'law_num',
- 'invention_design',
- 'incopat_link',
- 'first_ask',
- 'first_apply',
- 'apply_city',
- 'business_code',
- 'business_address',
- 'first_inventor',
- 'shared_value',
- 'techol_stable',
- 'techol_advanced',
- 'pct_apply',
- 'pct_publish',
- ];
- const keys = Object.keys(query);
- for (const key of keys) {
- if (vagueList.includes(key)) {
- query[`%${key}%`] = query[key];
- delete query[key];
- }
- }
- return query;
- }
- async toImport({ uri, code }) {
- assert(uri, '未获取到文件地址');
- const file = await this.ctx.curl(`${this.domain}${uri}`);
- if (!(file && file.data)) {
- throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定文件');
- }
- const workbook = new Excel.Workbook();
- await workbook.xlsx.load(file.data);
- const sheet = workbook.getWorksheet(1);
- const arr = [];
- const allNotice = [];
- const sheetImageInfo = sheet.getImages();
- const imgids = _.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 = _.get(this.model, 'schema.obj');
- const meta = Object.keys(models).map((i, index) => ({
- key: i,
- index: index + 2,
- }));
- sheet.eachRow((row, rindex) => {
- if (rindex !== 1) {
- // 组织数据,图片的索引和行索引不一致,准确的说是:图片索引比行索引少1
- // 原因:图片在工作簿中获取,按照1,2,3...顺序排序,但是行的第一行是表头(当前文件),所以当前行数需要减掉表头那一行
- const imgid = imgids.find(f => f.row === rindex - 1);
- const img_url = [];
- if (imgid) {
- img_url.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 = { img_url };
- 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 === 'img_url') {
- // 上面默认处理了
- continue;
- } else if (key === 'incopat_link') {
- obj[key] = this.getUrl(_.get(row.getCell(index), '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 = await this.personalModel.findOne({ code, phone: first_inventor });
- const user_id = {};
- // 有用户,直接取id,没有用户;创建用户,取id;
- if (person) {
- user_id.user_id = person._id || person.id;
- user_id.name = person.name;
- } else {
- const personObject = {
- name: first_inventor,
- phone: first_inventor,
- status: '1',
- code,
- };
- try {
- person = await this.ctx.service.users.personal.create(personObject);
- } catch (error) {
- allNotice.push(`${create_number} 的 第一发明人用户,已有机构所属的个人用户使用该手机号!`);
- continue;
- }
- if (person) {
- user_id.user_id = person._id || person.id;
- user_id.name = person.name;
- }
- }
- obj.user_id = [ JSON.parse(JSON.stringify(user_id)) ];
- } catch (error) {
- allNotice.push(`${create_number} 的 第一发明人用户 创建失败!`);
- continue;
- }
- }
- }
- if (allNotice.length > 0) return allNotice;
- // 根据申请号做添加/修改
- for (const obj of arr) {
- try {
- const has_data = await this.model.count({
- create_number: obj.create_number,
- });
- let res;
- if (!has_data) {
- res = await this.model.create(obj);
- } else {
- await this.model.updateOne({ create_number: obj.create_number }, obj);
- res = await this.model.find({ create_number: obj.create_number });
- }
- // 处理警告
- if (res.trem === '有效') this.ctx.service.patent.patentearly.dealData([ res ]);
- } catch (error) {
- allNotice.push(`申请号 为${create_number} 的 专利信息 创建失败!`);
- continue;
- }
- }
- }
- async toExport({ user, query }) {
- const data = {
- title: '专利导出',
- params: {
- project: 'market',
- service: 'patent.patentinfo',
- method: 'export',
- query,
- },
- user,
- tenant: 'live',
- };
- try {
- await this.ctx.service.util.httpUtil.cpost('/api/mission', 'mission', data);
- } catch (error) {
- console.log(error);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '任务创建失败');
- }
- }
- async export({ missionid, query = {} } = {}) {
- const path = `${this.root_path}${sep}${this.file_type}${sep}${this.patentInfo_dir}${new Date().getTime()}`;
- if (!path) {
- throw new BusinessError(ErrorCode.BUSINESS, '服务端没有设置存储路径');
- }
- if (!fs.existsSync(path)) {
- // 如果不存在文件夹,就创建
- fs.mkdirSync(path);
- }
- const total = await this.count(_.cloneDeep(query));
- let skip = 0;
- const getHeader = require('../../public/patent/infoExport');
- const meta = getHeader();
- const projection = {};
- for (const m of meta) {
- const { key } = m;
- projection[key] = 1;
- }
- // 将数据分割,否则容易直接把js堆栈干满了,服务就炸了
- const head = meta.map(i => i.header);
- for (let i = 0; i < total; i = i + this.export_limit) {
- try {
- const workbook = new Excel.Workbook();
- const sheet = workbook.addWorksheet('sheet');
- sheet.addRow(head);
- const list = await this.query(query, { skip, limit: this.export_limit }, projection);
- 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 + this.export_limit;
- // 生成excel
- const filename = `专利信息导出结果(${i + 1}-${i + this.export_limit}).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);
- }
- /**
- * 删除路径下的所有内容
- * @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);
- }
- }
- 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') || '失败'} %`);
- const ch = await this.ctx.mq.conn.createChannel();
- const queue = 'mission/return';
- await ch.assertQueue(queue, { durable: false });
- await ch.sendToQueue(queue, Buffer.from(JSON.stringify(data)));
- await ch.close();
- }
- /**
- * 检查数据是否没填 必填项
- * @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: 'success_number', zh: '公开(公告)号' },
- // { column: 'success_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 };
- }
- /**
- * 转换图片为base64
- * @param {Object} object excel获取的图片object
- * @property extension 后缀,文件类型
- * @property buffer 图片内容,不含头部信息的,
- */
- turnImageToBase64(object = {}) {
- 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;
- }
- async getByCreateNumber({ create_number }) {
- const data = await this.model.findOne({ create_number });
- return data;
- }
- async mechQuery({ skip = 0, limit = 0, ...query }) {
- const code = _.get(query, 'code');
- assert(code, '缺少机构信息!');
- const { empower_sort, ...others } = query;
- const newQuery = await this.toSetQuery(others);
- let sort = 'desc';
- if (empower_sort === '0') {
- sort = 'asc';
- }
- const data = await this.model.find(newQuery).sort({ empower_date: sort }).skip(parseInt(skip))
- .limit(parseInt(limit));
- const total = await this.model.count(newQuery);
- return { data, total };
- }
- async toSetQuery(query) {
- let queryObject = {};
- for (const key in query) {
- if (key === 'code') {
- const cquery = await this.dealCode(query[key]);
- queryObject = { ...queryObject, ...cquery };
- } else if (key === 'empower_year') {
- queryObject.empower_date = this.dealYearRange(query[key]);
- } else if (key === 'apply_year') {
- queryObject.create_date = this.dealYearRange(query[key]);
- } else if (key === 'ipc_type') {
- queryObject.ipc_type = new RegExp(`^${query[key]}`, 'i');
- } else if (key === 'field') {
- queryObject.abstract = new RegExp(query[key]);
- } else if (key === 'create_number') {
- queryObject.create_number = new RegExp(query[key]);
- } else if (key === 'first_inventor') {
- queryObject.inventor = new RegExp(`^${query[key]}`, 'i');
- } else if (key === 'on_obligee') {
- queryObject.on_obligee = new RegExp(`^${query[key]}`, 'i');
- } else if (key === 'type') {
- queryObject.type = new RegExp(`^${query[key]}`, 'i');
- } else if (key === 'is_empower') {
- let r = '';
- if (query[key] === '0') r = true;
- else r = false;
- queryObject.empower_date = { $exists: r };
- } else if (key === 'term') {
- queryObject.term = new RegExp(query[key]);
- } else if (key === 'is_lose') {
- const toDay = moment().format('YYYY-MM-DD');
- let r = '';
- if (query[key] === '0') r = { $gt: toDay };
- else r = { $lte: toDay };
- queryObject.lose_date = r;
- }
- }
- return queryObject;
- }
- async dealCode(code) {
- let pids = await this.personalModel.find({ code }, { _id: 1 });
- if (pids.length <= 0) return { data: [], total: 0 };
- pids = pids.map(i => i._id);
- const query = { 'inventor.user_id': { $in: pids } };
- return query;
- }
- async dealYearRange(year) {
- const start = `${year}-01-01`;
- const end = `${year}-12-31`;
- const obj = { $gte: start, $lte: end };
- return obj;
- }
- /**
- * 根据人名数组查询每个人的数据.组织数据返回
- * @param {Array[String]} nameList 名单
- */
- async toGetUser(nameList) {
- const res = await this.personalModel.find({ name: { $in: nameList } });
- const result = [];
- if (res && res.length > 0) {
- for (const i of res) {
- const { _id: id, name } = i;
- result.push({ id, name });
- }
- }
- return result;
- }
- async updateUser({ patentData, user_id }) {
- if (!patentData || !_.isArray(patentData)) {
- throw new BusinessError(ErrorCode.DATA_INVALID, '专利信息参数错误');
- }
- if (!user_id || !_.isArray(user_id)) {
- throw new BusinessError(ErrorCode.DATA_INVALID, '用户信息参数错误');
- }
- const data = await this.model.find({ _id: patentData.map(i => ObjectId(i)) });
- for (const i of data) {
- i.user_id = _.uniqBy([ ...i.user_id, ...user_id ], 'user_id');
- await i.save();
- }
- }
- /**
- * 批量删除
- * @param {Object} query 参数,查询范围
- */
- async deleteMany(query) {
- const newquery = await this.resetCode(query);
- const res = await this.model.deleteMany(newquery);
- return res;
- }
- }
- module.exports = PatentinfoService;
|