Browse Source

feat:配置运维工单,按照计划修复时间发送超期提醒

dev
zhaobing 1 year ago
parent
commit
656240aa4c
  1. 75
      api/app/lib/models/work_order_notice.js
  2. 189
      api/app/lib/schedule/work_notice.js
  3. 11
      api/app/lib/utils/parseProcessData.js
  4. 113
      api/app/lib/utils/qy.js
  5. 20
      api/config.js

75
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
}

189
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,
}
}

11
api/app/lib/utils/parseProcessData.js

@ -99,7 +99,18 @@ module.exports = function (app, opts) {
}, },
solution: { solution: {
keyWord: '解决方案' keyWord: '解决方案'
},
processing_personnel:{
keyWord: '处理人员',
fromDataSource: true
},
planTime:{
keyWord: '计划修复时间'
},
solution_status:{
keyWord:'是否解决'
} }
}) => { }) => {
let needData = JSON.parse(JSON.stringify(pomsNeedData)) let needData = JSON.parse(JSON.stringify(pomsNeedData))
getData(applyDetail, needData) getData(applyDetail, needData)

113
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
}
}

20
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 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 参数 // Redis 参数
const IOTA_REDIS_SERVER_HOST = process.env.IOTA_REDIS_SERVER_HOST || flags.redisHost || "localhost";//redis IP 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_BUCKET_RESOURCE = process.env.ANXINCLOUD_QINIU_BUCKET_RESOURCE || flags.qnbkt;
const QINIU_AK = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak; const QINIU_AK = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak;
const QINIU_SK = process.env.ANXINCLOUD_QINIU_SECRETKEY || flags.qnsk; const QINIU_SK = process.env.ANXINCLOUD_QINIU_SECRETKEY || flags.qnsk;
//项鳍web
const FS_EMIS_WEB = process.env.FS_EMIS_WEB || flags.emisWeb;
// clickHouse // clickHouse
const CLICKHOUST_URL = process.env.CLICKHOUST_URL || flags.clickHouseUrl const CLICKHOUST_URL = process.env.CLICKHOUST_URL || flags.clickHouseUrl
@ -219,6 +229,7 @@ const product = {
host: FS_CAMUNDA_HOST, host: FS_CAMUNDA_HOST,
root: FS_CAMUNDA_ROOT root: FS_CAMUNDA_ROOT
}, },
emisWeb: FS_EMIS_WEB,
exclude: [ exclude: [
// "*", // "*",
@ -271,6 +282,15 @@ const product = {
rootURL: ANXINCLOUD_KAFKA_BROKERS, rootURL: ANXINCLOUD_KAFKA_BROKERS,
topicPrefix: PLATFORM_NAME 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: { redis: {
host: IOTA_REDIS_SERVER_HOST, host: IOTA_REDIS_SERVER_HOST,
port: IOTA_REDIS_SERVER_PORT, port: IOTA_REDIS_SERVER_PORT,

Loading…
Cancel
Save