diff --git a/api/app/lib/models/work_order_notice.js b/api/app/lib/models/work_order_notice.js new file mode 100644 index 0000000..5855edd --- /dev/null +++ b/api/app/lib/models/work_order_notice.js @@ -0,0 +1,75 @@ +'use strict' + +module.exports = dc => { + const DataTypes = dc.ORM + + const WorkOrderNotice = dc.orm.define( + 'workOrderNotice', + { + id: { + field: 'id', + type: dc.ORM.INTEGER, + primaryKey: true, + unique: true, + allowNull: false, + autoIncrement: true, + unique: 'work_order_notice_pk', + }, + planTime: { + field: 'plan_time', + type: DataTypes.DATE, + allowNull: true, + comment: '', + primaryKey: false, + autoIncrement: false, + }, + processingPersonnelId: { + field: 'processing_personnel_id', + type: DataTypes.INTEGER, + allowNull: true, + comment: '', + primaryKey: false, + autoIncrement: false, + }, + processingPersonnelPhone: { + field: 'processing_personnel_phone', + type: DataTypes.STRING, + allowNull: true, + comment: '', + primaryKey: false, + autoIncrement: false, + }, + isSend: { + field: 'is_send', + type: DataTypes.BOOLEAN, + allowNull: true, + comment: '', + primaryKey: false, + autoIncrement: false, + }, + procinstId: { + field: 'procinst_id', + type: DataTypes.STRING, + allowNull: true, + comment: '', + primaryKey: false, + autoIncrement: false, + }, + haveDone: { + field: 'have_done', + type: DataTypes.BOOLEAN, + allowNull: true, + comment: '', + primaryKey: false, + autoIncrement: false, + }, + }, + { + tableName: 'work_order_notice', + } + ) + + dc.models.WorkOrderNotice = WorkOrderNotice + + return WorkOrderNotice +} diff --git a/api/app/lib/schedule/work_notice.js b/api/app/lib/schedule/work_notice.js new file mode 100644 index 0000000..74b0999 --- /dev/null +++ b/api/app/lib/schedule/work_notice.js @@ -0,0 +1,189 @@ +/** + * 运维中台售后问题处理工单,根据计划修复时间(pa3aGiey3N),发送企业微信通知,因为不是必填项,没有填写的不推送!!!! + *1.已完成的工单 + * 根据表单数据中的是否已完成字段(Solution_Status)判断,该单子是否推送通知, + * 已完成的不推送,未完成的推送通知 + * 推送给处理人(Processing_Personnel) + * 2.未完成的工单 + * + */ + +const schedule = require('node-schedule') +const moment = require('moment') +const request = require('superagent') +let isDev = false +isDev = true +const sendType = { + 'mini': 'miniprogram_notice', +} + +module.exports = function (app, opts) { + const workOrderNotice = app.fs.scheduleInit( + { + interval: '0 * * * *',//一小时执行一次 + immediate: isDev, + proRun: !isDev, + }, + async () => { + try { + //前一次执行时间 + console.log('运维中台售后问题处理工单数据抽取开始', moment().format('YYYY-MM-DD HH:mm:ss')) + const { parseProcessData,getUserId,sendWechat } = app.fs.utils + const startTime = moment().format('YYYY-MM-DD HH:mm:ss') + const { models } = app.fs.dc + const { clickHouse } = app.fs + const { database: camWorkflow } = clickHouse.camWorkflow.opts.config + const formRes = await clickHouse.pepEmis.query( + `SELECT + story.id AS historyId, + story.procinst_id as procinstId, + story.apply_user AS pepUserId, + story.form_data AS formData, + story.submit_form_data AS submitFormData, + story.create_at as createTime, + fform.form_schema AS formSchema, + fprocess.name AS processName, + procin.state_ AS state, + procin.end_time_ as endTime, + fform.id AS formId, + fversion.id AS versionId, + fgroup.name AS groupName + FROM + workflow_process_history AS story + INNER JOIN workflow_process_version AS fversion + ON fversion.id = story.version_id + INNER JOIN workflow_process_form AS fform + ON fform.id = fversion.form_id + AND fform.form_schema IS NOT NULL + INNER JOIN workflow_process AS fprocess + ON fprocess.id = fform.process_id + AND fprocess.name = '运维中台售后问题处理工单' + INNER JOIN workflow_group AS fgroup + ON fgroup.id = fprocess.group_id + INNER JOIN ${camWorkflow}.act_hi_procinst AS procin + ON procin.id_ = story.procinst_id + AND procin.state_ in ('COMPLETED','ACTIVE')` + ).toPromise() + if (formRes && formRes.length > 0) { + for (let f of formRes) { + let user = {} + const parseData = f.formSchema && parseProcessData({ + formSchema: JSON.parse(f.formSchema), + formData: JSON.parse(f.formData) + }) + const rlst = await models.WorkOrderNotice.findOne({ + where: { + procinstId: f.procinstId, + } + }) + //没有计划完成时间,没有处理人,没有是否解决,都不推送通知 + if (parseData.processing_personnel.value && parseData.planTime.value && parseData.solution_status.value) { + user = await clickHouse.pepEmis.query(`select * from user where id=${Number(parseData.processing_personnel.value)}`).toPromise() + if (!rlst) { + await models.WorkOrderNotice.create({ + procinstId: f.procinstId, + planTime: parseData.planTime.value, + processingPersonnelId: parseData.processing_personnel.value, + processingPersonnelPhone: user && user.length && user[0].phone, + isSend: false, + haveDone: parseData.solution_status.value === '否' ? false : true, + }) + } + } else { + console.log('没有对应的处理人', f.procinstId) + } + + } + + + } else { + console.log('未查询到数据') + } + //数据全部插入完成之后(筛选数据进行发送企业微信通知) + // 获取当前时间 + // 计算当前时间之后一天的日期 + const lastDay = moment().subtract(1, 'days') + //需要发送提醒的数据 + const sendRslt=await models.WorkOrderNotice.findAll({ + where: { + isSend: false,//未发送 + haveDone: false,//未完成 + + planTime:{ + $gte: lastDay + } + } + }) + //发送企业微信通知 + if(sendRslt&&sendRslt.length){ + for (let { dataValues: c } of sendRslt) { + //根据电话号码查询企业微信对应的userId + //15083558812,兰邦夫的电话号码 + if(c.processingPersonnelPhone==='15083558812'){ + const userId=await getUserId(opts,c.processingPersonnelPhone) + if(userId){ + const res= await sendWechat({ + opts, msgtype: sendType["mini"], + data: { userId:userId, touser: [userId] } + }) + if(res.body.errmsg==='ok'){ + //将已发送的记录的isSend修改为true(已发送) + await models.WorkOrderNotice.update({ isSend: true }, { where: { id: c.id } }) + }else{ + console.log('发送消息失败',res.body.errmsg) + } + }else{ + console.log('查询不到对应的userId',c.processingPersonnelPhone) + } + } + const userId=await getUserId(opts,c.processingPersonnelPhone) + const userLan=await getUserId(opts,'15083558812') + //根据查询的userId发送通知 + if(userId){ + const res= await sendWechat({ + opts, msgtype: sendType["mini"], + data: { userId:userId, touser: [userId] } + }) + if(res.body.errmsg==='ok'){ + //将已发送的记录的isSend修改为true(已发送) + await models.WorkOrderNotice.update({ isSend: true }, { where: { id: c.id } }) + }else{ + console.log('发送消息失败',res.body.errmsg) + } + }else{ + console.log('查询不到对应的userId',c.processingPersonnelPhone) + } + if(userLan){ + const res= await sendWechat({ + opts, msgtype: sendType["mini"], + data: { userId:userLan, touser: [userLan] } + }) + if(res.body.errmsg==='ok'){ + //将已发送的记录的isSend修改为true(已发送) + console.log('已发送给兰邦夫') + // await models.WorkOrderNotice.update({ isSend: true }, { where: { id: c.id } }) + }else{ + console.log('发送消息失败',res.body.errmsg) + } + }else{ + console.log('查询不到对应的userId','15083558812') + } + + + } + + } + + + + + + } catch (error) { + console.error('失败原因', error) + } + } + ); + return { + workOrderNotice, + } +} \ No newline at end of file diff --git a/api/app/lib/utils/parseProcessData.js b/api/app/lib/utils/parseProcessData.js index f4a958b..639da9e 100644 --- a/api/app/lib/utils/parseProcessData.js +++ b/api/app/lib/utils/parseProcessData.js @@ -99,7 +99,18 @@ module.exports = function (app, opts) { }, solution: { keyWord: '解决方案' + }, + processing_personnel:{ + keyWord: '处理人员', + fromDataSource: true + }, + planTime:{ + keyWord: '计划修复时间' + }, + solution_status:{ + keyWord:'是否解决' } + }) => { let needData = JSON.parse(JSON.stringify(pomsNeedData)) getData(applyDetail, needData) diff --git a/api/app/lib/utils/qy.js b/api/app/lib/utils/qy.js new file mode 100644 index 0000000..8edca0b --- /dev/null +++ b/api/app/lib/utils/qy.js @@ -0,0 +1,113 @@ +'use strict'; +const request = require('superagent'); +const moment = require('moment') +let wechatExpires = {//企业微信access_token过期 + accessToken: null, + expire: null +} +//相应的 +const WechatApi = { + getAccessToken: '/gettoken',//获取企业微信token相当于鉴权 + getUserId: '/user/getuserid',//根据电话号码查询企业用户id + postSendMesg: '/message/send',//发送信息 + +} +const sendType = { + 'mini': 'miniprogram_notice', +} + +module.exports = function (app, opts) { + const getGetToken = async (opts) => { + console.log(`wechatInfo , accessToken:${wechatExpires && wechatExpires.accessToken}, expire: ${wechatExpires && wechatExpires.expire}`) + /**if access_token超时,重新请求 */ + if (wechatExpires && wechatExpires.accessToken && wechatExpires.expire && moment(wechatExpires.expire).isSameOrAfter(moment())) { + return true; + } else { + const { reqUrl, corpid, corpsecret } = opts.weChat || {}; + console.log('Wechat getAccessToken:', moment()); + const accessToken = await request.get(`${reqUrl}${WechatApi.getAccessToken}?corpid=${corpid}&corpsecret=${corpsecret}`); + // const accessToken = await ctx.app.business.request.get(`${reqUrl}${WechatApi.getAccessToken}?corpid=${corpid}&corpsecret=${corpsecret}`); + const { errmsg, access_token, expires_in } = accessToken && accessToken.body; + if ('ok' != errmsg) { + console.log('请求企业微信鉴权失败') + } else { + wechatExpires = { + accessToken: access_token, + expire: moment().add(expires_in - 600, 's').format('YYYY-MM-DD HH:mm:ss') + } + return true; + } + } + } + + //获取用户id + const getUserId = async (opts, phoneNum) => { + console.log(`info: getGetToken--start-- `) + await getGetToken(opts) + console.log(`info: getGetToken--end-- `) + const res = await request.post(`${opts.weChat.reqUrl}${WechatApi.getUserId}?access_token=${wechatExpires.accessToken}`, { mobile: phoneNum }) + if(res.body.errmsg==='ok'){ + return res.body.userid + + }else{ + return null + } + + } + //发送超期提醒消息 + async function sendWechat({ opts, data, msgtype = sendType["mini"], exDep = '' }) { + let rslt={} + console.log(`info: sendMesg--enter-- `); + try { + const { touser } = data; + if (touser && touser.length) { + let sendData = {}; + if (sendType["mini"] == msgtype) {//小程序通知 + const { appid } = opts.weChat || {}; + const { userId, content, url, title, description, noDescription = false } = data; + sendData = { + "touser": touser.join("|"), + "msgtype": "miniprogram_notice", + "miniprogram_notice": { + "appid": appid, + "page": `${url ? url : 'package/approvalCenter/approvalCenter'}?userId=${userId}&msgRet=${true}`, + "title": title || "超期提醒", + "description": description || "详情如下,请您尽快处理", + "content_item": content + } + } + if (noDescription) { + delete sendData.miniprogram_notice.description; + } + } + console.log(`info: getGetToken--start-- `) + await getGetToken(opts) + console.log(`info: getGetToken--end-- `) + /** + * 返回示例 + * {"errcode" : 0, + "errmsg" : "ok", + "invaliduser" : ["userid1","userid2","CorpId1/userid1","CorpId2/userid2"], // 不区分大小写,返回的列表都统一转为小写 + "invalidparty" : ["partyid1","partyid2","LinkedId1/partyid1","LinkedId2/partyid2"], + "invalidtag":["tagid1","tagid2"] + } + * + */ + rslt = await request.post(`${opts.weChat.reqUrl}${WechatApi.postSendMesg}?access_token=${wechatExpires.accessToken}`, sendData) + console.log(`info: WechatApi.postSendMesg----end-- `) + return rslt + } else { + console.log('缺少参数,请检查') + return rslt + } + } catch (err) { + console.log('发送消息失败', err && err.message) + } + } + + return { + getGetToken, + getUserId, + sendWechat + } +} \ No newline at end of file diff --git a/api/config.js b/api/config.js index c8d5ff0..243f330 100644 --- a/api/config.js +++ b/api/config.js @@ -78,6 +78,14 @@ const ANXINCLOUD_KAFKA_BROKERS = process.env.ANXINCLOUD_KAFKA_BROKERS || flags.k // 以太代理 const IOT_PROXY = process.env.IOT_PROXY || flags.iotaProxy; +//企业微信相关配置 +const WXCHAT_REQURL = process.env.WXCHAT_REQURL || 'https://qyapi.weixin.qq.com/cgi-bin'; +const WXCHAT_AGENTID = process.env.WXCHAT_AGENTID || 1000012; +const WXCHAT_APPID = process.env.WXCHAT_APPID || 'wxcba57321bee95eef';//绑定的小程序id(只能绑定商用) +const WXCHAT_CORPID = process.env.WXCHAT_CORPID || 'wwb729be4a7f781f75'; +const WXCHAT_CORPSECRET = process.env.WXCHAT_CORPSECRET || "JkzFbnl9fX0EvwSmV8inWir5vjV_m7lKyrjKwWft1gs"; +const WXCHAT_ADDRESS_CORPSECRET = process.env.WXCHAT_ADDRESS_CORPSECRET || "GmgiT4Fjjm4SD_2tI8zH1ucpuDUqM7Eh00LkPxCs9ZE";//企业微信通讯录同步secret +const WXCHAT_REDIRECT_URL = process.env.WXCHAT_REDIRECT_URL || `https://open.weixin.qq.com/connect/oauth2/authorize?appid={corpid}&redirect_uri={redirectUrl}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect`; // Redis 参数 const IOTA_REDIS_SERVER_HOST = process.env.IOTA_REDIS_SERVER_HOST || flags.redisHost || "localhost";//redis IP @@ -112,6 +120,8 @@ const QINIU_DOMAIN_QNDMN_RESOURCE = process.env.ANXINCLOUD_QINIU_DOMAIN_QNDMN_RE const QINIU_BUCKET_RESOURCE = process.env.ANXINCLOUD_QINIU_BUCKET_RESOURCE || flags.qnbkt; const QINIU_AK = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak; const QINIU_SK = process.env.ANXINCLOUD_QINIU_SECRETKEY || flags.qnsk; +//项鳍web +const FS_EMIS_WEB = process.env.FS_EMIS_WEB || flags.emisWeb; // clickHouse const CLICKHOUST_URL = process.env.CLICKHOUST_URL || flags.clickHouseUrl @@ -219,6 +229,7 @@ const product = { host: FS_CAMUNDA_HOST, root: FS_CAMUNDA_ROOT }, + emisWeb: FS_EMIS_WEB, exclude: [ // "*", @@ -271,6 +282,15 @@ const product = { rootURL: ANXINCLOUD_KAFKA_BROKERS, topicPrefix: PLATFORM_NAME }, + weChat: {//企业微信配置 + reqUrl: WXCHAT_REQURL, + agentid: WXCHAT_AGENTID, + appid: WXCHAT_APPID,//绑定的小程序id + corpid: WXCHAT_CORPID, + corpsecret: WXCHAT_CORPSECRET, + redirect_url: WXCHAT_REDIRECT_URL, + address_corpsecret: WXCHAT_ADDRESS_CORPSECRET //企业微信通讯录同步secret + }, redis: { host: IOTA_REDIS_SERVER_HOST, port: IOTA_REDIS_SERVER_PORT,