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