From 8dc209efec2ff81073f4b238d33c5bc2051eb483 Mon Sep 17 00:00:00 2001 From: "gao.zhiyuan" Date: Fri, 28 Oct 2022 11:44:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81=E9=85=8D=E7=BD=AE=E6=90=9C?= =?UTF-8?q?=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/push/config.js | 2 +- api/app/lib/schedule/alarms_push.js | 207 ++++++++++++++++++++++--- api/app/lib/utils/push.js | 62 ++++++++ 3 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 api/app/lib/utils/push.js diff --git a/api/app/lib/controllers/push/config.js b/api/app/lib/controllers/push/config.js index 4564481..f915686 100644 --- a/api/app/lib/controllers/push/config.js +++ b/api/app/lib/controllers/push/config.js @@ -7,7 +7,7 @@ async function list (ctx) { const sequelize = ctx.fs.dc.ORM; const { clickHouse } = ctx.app.fs const { utils: { anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs - const { keyword, keywordTarget, alarmType, state, tactics, pomsProjectId } = ctx.request.body + const { keyword, keywordTarget, alarmType, state, tactics, pomsProjectId } = ctx.query let findOption = { where: { diff --git a/api/app/lib/schedule/alarms_push.js b/api/app/lib/schedule/alarms_push.js index a0d8918..eec2f63 100644 --- a/api/app/lib/schedule/alarms_push.js +++ b/api/app/lib/schedule/alarms_push.js @@ -53,11 +53,11 @@ module.exports = function (app, opts) { for (let { dataValues: c } of configListRes) { if (c.tacticsParams && c.tactics) { - const { projectCorrelation, strucId, pomsProjectId } = c + const { projectCorrelation, strucId, pomsProjectId, } = c const { interval, deviceProportion } = c.tacticsParams if ( - curMinOfYear % interval == 0 + curMinOfYear % parseInt(interval) == 0 ) { const corPepProject = projectCorrelation.pepProjectId ? pepProjectRes.find(p => p.id == projectCorrelation.pepProjectId) @@ -69,18 +69,63 @@ module.exports = function (app, opts) { && c.timeType.some(ct => ct == corPepProject.construction_status_id) ) ) { - // TODO 查当前 poms 下的结构物 并把不包含的去掉 + const { anxinProjectId, pepProjectId } = projectCorrelation - const strucListRes = strucId.length ? await clickHouse.anxinyun.query(` - SELECT id, iota_thing_id, name - FROM t_structure - WHERE id IN (${strucId.join(',')}) - `).toPromise() : [] + // 查当前 poms 下的结构物 并把不包含的去掉 + // 可能有结构物已解绑 + const strucListRes = strucId.length && anxinProjectId.length ? + await clickHouse.anxinyun.query( + ` + SELECT + DISTINCT id, + t_structure.id AS id, + t_structure.name AS name, + t_structure.iota_thing_id AS iotaThingId + FROM + t_project + LEFT JOIN + t_project_structure + ON t_project_structure.project = t_project.id + LEFT JOIN + t_project_structuregroup + ON t_project_structuregroup.project = t_project.id + LEFT JOIN + t_structuregroup_structure + ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup + LEFT JOIN + t_project_construction + ON t_project_construction.project = t_project.id + LEFT JOIN + t_structure_site + ON t_structure_site.siteid = t_project_construction.construction + RIGHT JOIN + t_structure + ON t_structure.id = t_project_structure.structure + OR t_structure.id = t_structuregroup_structure.structure + OR t_structure.id = t_structure_site.structid + WHERE + project_state != -1 + AND t_project.id IN (${anxinProjectId.join(',')}) + AND t_structure.id IN (${strucId.join(',')}) + + ` + ).toPromise() : + [] + let searchStrucIds = strucListRes.map(s => s.id) + if (searchStrucIds.length) { + searchStrucIds.unshift(-1) + } + let pepProjectName = pepProjectId ? + pepProjectRes.find(p => p.id == pepProjectId).project_name + : projectCorrelation.name + let emailTitle = `${pepProjectName}-${c.name}-` + let emailSubTitle = '' let dataAlarmOption = [] let dataAlarmGroupOption = [] let dataAlarms = [] + let videoAlarmWhereOption = [] let videoAlarms = [] let appAlarmWhereOption = { @@ -89,17 +134,26 @@ module.exports = function (app, opts) { let appAlarms = [] let deviceCount = 0 + let alarmDeviceCount = 0 let cameraCount = 0 + let alarmCameraCount = 0 // 判断推送策略 - let pointTime = moment().subtract(interval, 'minute').format('YYYY-MM-DD HH:mm:ss') + let nowTime = moment().startOf('minute') + let pointTime = moment().subtract(parseInt(interval), 'minute').startOf('minute').format('YYYY-MM-DD HH:mm:ss') if (c.tactics == 'immediately') { - dataAlarmOption.push(`StartTime >= '${pointTime}'`); - appAlarmWhereOption.createTime = { $gte: pointTime } + // dataAlarmOption.push(`StartTime >= '${pointTime}'`); + // appAlarmWhereOption.createTime = { $gte: pointTime } + // videoAlarmWhereOption.push(`camera_status_alarm.create_time >= '${pointTime}'`) + emailTitle += `即时推送服务` + emailSubTitle += `截止${moment(pointTime).format('YYYY年MM月DD日 HH时mm分')}-${moment(nowTime).format('HH时mm分')}:` } else if (c.tactics == 'continue') { - dataAlarmOption.push(`StartTime <= '${pointTime}'`); - appAlarmWhereOption.createTime = { $lte: pointTime } + // dataAlarmOption.push(`StartTime <= '${pointTime}'`); + // appAlarmWhereOption.createTime = { $lte: pointTime } + // videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) + emailTitle += `持续时长推送服务` + emailSubTitle += `告警持续时长超${interval}分钟的告警源,详情如下:` } else if (c.tactics == 'abnormal_rate') { - dataAlarmOption.push(`StartTime <= '${pointTime}'`); + // dataAlarmOption.push(`StartTime <= '${pointTime}'`); if (c.alarmType.includes('data_outages') || c.alarmType.includes('data_exception')) { // 查了设备异常率 去安心云查当前项目下的设备数量 // TODO 等同步以太数据再查 @@ -110,15 +164,19 @@ module.exports = function (app, opts) { } if (c.alarmType.includes('video_exception')) { // 查了视频异常 去安心云查 接入的 萤石 设备数量 - cameraCount = strucId.length ? + cameraCount = searchStrucIds.length ? (await clickHouse.anxinyun.query(` SELECT count(*) AS count FROM t_video_ipc - WHERE structure IN (${strucId.join(',')}) + WHERE structure IN (${searchStrucIds.join(',')}) `).toPromise())[0].count : 0 } - appAlarmWhereOption.createTime = { $lte: pointTime } + // appAlarmWhereOption.createTime = { $lte: pointTime } + // videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) + emailTitle += `异常率推送服务` + emailSubTitle += `持续产生时间超过${interval}分钟的异常设备数量${interval}个,异常率达到项目或结构物内设备总数量${deviceCount + cameraCount}个的${interval}%,详情如下` } + emailTitle += '——POMS飞尚运维中台' // 判断告警数据范围 if (c.alarmType.includes('data_outages')) { @@ -131,7 +189,7 @@ module.exports = function (app, opts) { dataAlarmGroupOption.push(3) } if (c.alarmType.includes('video_exception')) { - videoAlarms = strucId.length ? await clickHouse.vcmp.query( + videoAlarms = searchStrucIds.length ? await clickHouse.vcmp.query( ` SELECT cameraAlarm.cameraId AS cameraId, @@ -167,13 +225,15 @@ module.exports = function (app, opts) { INNER JOIN ${anxinyun}.t_video_ipc ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no - AND ${anxinyun}.t_video_ipc.structure IN (${strucId.join(',')}) + AND ${anxinyun}.t_video_ipc.structure IN (${searchStrucIds.join(',')}) INNER JOIN camera ON camera.serial_no = camera_status_alarm.serial_no AND camera.channel_no = camera_status_alarm.channel_no AND camera.delete = false AND camera.recycle_time is null - WHERE camera_status_alarm.confirm_time IS null + WHERE + camera_status_alarm.confirm_time IS null + ${videoAlarmWhereOption.length ? ` AND ${videoAlarmWhereOption.join(' AND ')}` : ''} ) AS cameraAlarm LEFT JOIN camera_status ON cameraAlarm.platform = camera_status.platform @@ -186,7 +246,7 @@ module.exports = function (app, opts) { AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo LEFT JOIN ${anxinyun}.t_structure AS anxinStruc ON anxinStruc.id = anxinIpc.structure - AND anxinStruc.id IN (${strucId.join(',')}) + AND anxinStruc.id IN (${searchStrucIds.join(',')}) LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation ON anxinIpcStation.ipc = anxinIpc.id LEFT JOIN ${anxinyun}.t_sensor AS anxinStation @@ -275,20 +335,119 @@ module.exports = function (app, opts) { dataAlarmGroupOption.push(4) dataAlarmGroupOption.push(5) } - // - if (dataAlarmGroupOption.length) { + + // 查数据告警 三警合一 + if (dataAlarmGroupOption.length && searchStrucIds.length) { dataAlarmGroupOption.push(-1) dataAlarmOption.push(`AlarmGroup IN (${dataAlarmGroupOption.join(',')})`) dataAlarms = await clickHouse.dataAlarm.query(` - SELECT * FROM alarms + SELECT * FROM alarms WHERE State NOT IN (3, 4) + AND StructureId IN (${searchStrucIds.join(',')}) ${dataAlarmOption.length ? ' AND ' + dataAlarmOption.join(' AND ') : ''} `).toPromise() console.log(dataAlarms); } + let dataAlarmTitle = [{ + n: '项目', + k: '', + v: pepProjectName + }, { + n: '结构物', + k: '', + f: (d) => { + return (strucListRes.find(s => s.id == d.StructureId) || {}).name + } + }, { + n: '告警源名称', + k: 'SourceName' + }, { + n: '告警源类型', + k: '', + f: (d) => { + switch (d.SourceTypeId) { + case 0: + return 'DTU' + case 1: + return '传感器' + case 2: + return '测点' + default: + return '' + } + } + }, { + n: '告警信息', + k: 'AlarmContent' + }, { + n: '告警等级(当前)', + k: '', + f: (d) => { + switch (d.CurrentLevel) { + case 1: + return '一级' + case 2: + return '二级' + case 3: + return '三级' + default: + return '' + } + } + }, { + n: '持续时间', + k: '', + f: (d) => { + return d.StartTime ? + '超过' + moment().diff(moment(d.StartTime), 'minutes') + '分钟' : '' + } + },] + + let html = '' + let tableTitlePostfix = ',详情如下:' + function packageTableTitle (tArr) { + + } + if (c.alarmType.includes('data_outages')) { + let tableTitlePrefix = '数据中断告警源' + let newAddCount = 0 + let alarmHtml = '' + + if (c.tactics == 'immediately') { + for (let a of dataAlarms.filter(d => d.AlarmGroup == 1)) { + alarmHtml += 1 + } + tableTitlePrefix += 1 + } else if (c.tactics == 'continue') { + + } else if (c.tactics == 'abnormal_rate') { + + } + } + if (c.alarmType.includes('data_exception')) { + + } + if (c.alarmType.includes('strategy_hit')) { + + } + if (c.alarmType.includes('video_exception')) { + + } + if (c.alarmType.includes('app_exception')) { + + } + if (c.alarmType.includes('device_exception')) { + + } + await pushByEmail({ + email: ['1650192445@qq.com'], + title: emailTitle, + text: '', + html: '' + }) } } } diff --git a/api/app/lib/utils/push.js b/api/app/lib/utils/push.js new file mode 100644 index 0000000..a0cf5ed --- /dev/null +++ b/api/app/lib/utils/push.js @@ -0,0 +1,62 @@ +'use strict'; + +const moment = require('moment') +const Core = require('@alicloud/pop-core'); +const nodemailer = require('nodemailer') + +module.exports = function (app, opts) { + const pushBySms = async ({ phone = [], templateCode, templateParam } = {}) => { + try { + if (phone.length) { + const client = new Core({ + accessKeyId: opts.sms.accessKey, + accessKeySecret: opts.sms.accessSecret, + endpoint: 'http://dysmsapi.aliyuncs.com',//固定 + apiVersion: '2017-05-25'//固定 + }); + const SendSmsRes = await client.request('SendSms', { + "PhoneNumbers": phone.join(','),//接收短信的手机号码。 + "SignName": "飞尚尚视",//短信签名名称。必须是已添加、并通过审核的短信签名。 + "TemplateCode": templateCode,//短信模板ID。必须是已添加、并通过审核的短信签名;且发送国际/港澳台消息时,请使用国际/港澳台短信模版。 + "TemplateParam": JSON.stringify(templateParam)//短信模板变量对应的实际值,JSON格式。 + }, { + method: 'POST' + }); + return SendSmsRes + } + } catch (error) { + throw error + } + } + + const pushByEmail = async ({ email = [], title, text = '', html = '', attachments = undefined, } = {}) => { + try { + let transporter = nodemailer.createTransport({ + host: opts.email.host, + port: opts.email.port, + secure: true, + auth: { + user: opts.email.sender.address, + pass: opts.email.sender.password, + } + }); + + // send mail with defined transport object + await transporter.sendMail({ + from: `${opts.email.sender.name}<${opts.email.sender.address}>`, // sender address + to: email.join(','), // list of receivers 逗号分隔字符串 + subject: title, // Subject line + text: text, // plain text body + html: html, // html body + attachments: attachments + }); + } catch (error) { + throw error + } + } + + return { + pushByEmail, + pushBySms, + } +} \ No newline at end of file