const moment = require('moment') let isDev = false // isDev = true let proDebug = false proDebug = true module.exports = function (app, opts) { const alarmsPush = app.fs.scheduleInit( { interval: '12 */1 * * * *', immediate: isDev, proRun: !isDev, }, async () => { try { const { models, ORM: sequelize } = app.fs.dc const { apMergeDeVeAnxinProjectId = '' } = opts const { clickHouse } = app.fs const { database: anxinyun } = clickHouse.anxinyun.opts.config const { pushBySms, pushByEmail, sendNoticeToWeb } = app.fs.utils const curMinOfYear = moment().diff(moment().startOf('year'), 'minutes') const configListRes = await models.AlarmPushConfig.findAll({ where: { del: false, disable: false }, order: ['id'], }) let pomsProjectId = new Set() let pepProjectIds = new Set() for (let { dataValues: c } of configListRes) { if (c.pomsProjectId) { c.pomsProjectId.forEach(pid => pomsProjectId.add(pid)) } } const pomsProjectRes = pomsProjectId.size ? await models.ProjectCorrelation.findAll({ where: { id: { $in: [...pomsProjectId] } } }) : [] for (let { dataValues: c } of pomsProjectRes) { if (c.pepProjectId) { pepProjectIds.add(c.pepProjectId) } } const pepProjectRes = pepProjectIds.size ? await clickHouse.projectManage.query( ` SELECT t_pim_project.id AS id, t_pim_project.project_name AS project_name, t_pim_project.isdelete AS isdelete, t_pim_project_construction.construction_status_id AS construction_status_id, t_pim_project_state.construction_status AS construction_status FROM t_pim_project LEFT JOIN t_pim_project_construction ON t_pim_project.id = t_pim_project_construction.project_id LEFT JOIN t_pim_project_state ON t_pim_project_construction.construction_status_id = t_pim_project_state.id WHERE id IN (${[...pepProjectIds].join(',')}, -1) ` ).toPromise() : [] const calcMinute2DHM = (minute) => { if (!minute && minute != 0) { return minute } let dm = 24 * 60 let d = Math.floor(minute / dm) let h = Math.floor(minute % dm / 60) let m = (minute % dm) % 60 return `${d}天${h}时${m}分` } for (let { dataValues: c } of configListRes) { if (c.tacticsParams && c.tactics) { if (proDebug) { console.log(`当前运行EM配置:id=${c.id} name=${c.name}`); } // pomsProjectId 是个数组 [] const { strucId, pomsProjectId, pomsStrucFactorId } = c const { interval, deviceProportion } = c.tacticsParams if ( curMinOfYear % parseInt(interval) == 0 || isDev ) { const corPomsProject = pomsProjectRes.filter(poms => pomsProjectId.includes(poms.id)) let curAnxinProjectId = new Set() let pepProjectName_ = [] for (let { dataValues: poms } of corPomsProject) { if (poms.pepProjectId) { // 找对应的项企项目 const corPepProject = pepProjectRes.find(p => p.id == poms.pepProjectId) if (corPepProject && c.timeType.some(ct => ct == corPepProject.construction_status_id)) { pepProjectName_.push(corPepProject.project_name) } else { // 不符合当前项目的时间节点 continue } } else { // 是自定义项目 poms.name ? pepProjectName_.push(poms.name) : null } // 筛选全部的 anxinProjectId pepProjectId for (let axId of poms.anxinProjectId) { curAnxinProjectId.add(axId) } } const anxinProjectId = [...curAnxinProjectId] // 查当前 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, t_project.id AS projectId 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 strucThingId = [] let strucMap = {} let searchStrucIds = strucListRes.map(s => { if (s.iotaThingId) { strucThingId.push(s.iotaThingId) } strucMap[s.id] = { ...s } return s.id }) // !开发测试用的数据 if (isDev) { searchStrucIds = searchStrucIds.concat([991, 1052, 700]) } if (searchStrucIds.length) { searchStrucIds.unshift(-1) } else { // 没有结构物可查 continue } let pepProjectName = pepProjectName_.length ? pepProjectName_.join('
') : '' let emailTitle = `${pepProjectName_.length ? pepProjectName_.join('、') : ''}-${c.name}-` let emailSubTitle = '' let dataAlarmOption = [] let dataAlarmGroupOption = [] let dataAlarmSubType = [] let dataAlarms = [] let videoAlarmWhereOption = [] let videoAlarms = [] let appAlarmWhereOption = { confirmTime: null, } let appAlarms = [] let deviceCount = 0 let alarmDeviceCount = 0 let cameraCount = 0 let alarmCameraCount = 0 // 判断推送策略 let nowTime = moment().startOf('minute') let pointTime = moment() .subtract( parseInt(interval), // + 1440 * 365, 'minute' ) .startOf('minute') .format('YYYY-MM-DD HH:mm:ss') let newAddStartTime = pointTime let newAddEndTime = nowTime.clone() if (c.tactics == 'immediately') { // !查所有未解决告警 所以时间范围大可不必 // 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' || c.tactics == 'abnormal_rate') { // 新增的应该是上一个时间节点到上上个节点之间 dataAlarmOption.push(`StartTime <= '${pointTime}'`); appAlarmWhereOption.createTime = { $lte: pointTime } videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) // 新增的应该是上一个时间节点到上上个节点之间 newAddStartTime = moment(pointTime).subtract(parseInt(interval)).format('YYYY-MM-DD HH:mm:ss') newAddEndTime = pointTime if (c.tactics == 'continue') { emailTitle += `持续时长推送服务` emailSubTitle += `告警持续时长超${interval}分钟的告警源,详情如下:` } else { if (c.alarmType.includes('data_outages') || c.alarmType.includes('data_exception')) { // 查了设备异常率 去安心云查当前项目下的设备数量 let deviceCountRes = strucThingId.length ? await clickHouse.iot.query(` SELECT count(DeviceId) AS count FROM device WHERE ThingId IN (${strucThingId.map(t => `'${t}'`).join(',')}, '-1') `).toPromise() : [] deviceCount = deviceCountRes.length ? deviceCountRes[0].count : 0 } if (c.alarmType.includes('video_exception')) { // 查了视频异常 去安心云查 接入的 萤石 设备数量 cameraCount = searchStrucIds.length ? (await clickHouse.anxinyun.query(` SELECT count(*) AS count FROM t_video_ipc WHERE structure IN (${searchStrucIds.join(',')}) `).toPromise())[0].count : 0 } emailTitle += `异常率推送服务` emailSubTitle += `持续产生时间超过${interval}分钟的异常设备数量${interval}个,异常率达到项目或结构物内设备总数量${parseInt(deviceCount) + parseInt(cameraCount)}个的 --%,详情如下` } } emailTitle += '——POMS飞尚运维中台' // 判断告警数据范围 if (c.alarmType.includes('data_outages')) { dataAlarmGroupOption.push(1) if (c.alarmSubType) dataAlarmSubType = dataAlarmSubType.concat(c.alarmSubType['data_outages']) } if (c.alarmType.includes('data_exception')) { dataAlarmGroupOption.push(2) if (c.alarmSubType) dataAlarmSubType = dataAlarmSubType.concat(c.alarmSubType['data_exception']) } if (c.alarmType.includes('strategy_hit')) { dataAlarmGroupOption.push(3) if (c.alarmSubType) dataAlarmSubType = dataAlarmSubType.concat(c.alarmSubType['strategy_hit']) } if (c.alarmType.includes('video_exception')) { let videoAlarmSubType = c.alarmSubType ? c.alarmSubType['video_exception'] : [] if (videoAlarmSubType.length == 1) { videoAlarmSubType.push(-1) } videoAlarms = searchStrucIds.length && ( !c.alarmSubType || videoAlarmSubType.length > 0 ) ? await clickHouse.vcmp.query( ` SELECT cameraAlarm.cameraId AS cameraId, cameraAlarm.cameraName AS cameraName, cameraAlarm.cameraSerialNo AS cameraSerialNo, cameraAlarm.cameraChannelNo AS cameraChannelNo, cameraAlarm.alarmId AS alarmId, cameraAlarm.createTime AS createTime, cameraAlarm.confirmContent AS confirmContent, cameraAlarm.confirmTime AS confirmTime, cameraAlarm.autoRestore AS autoRestore, camera_status.describe AS statusDescribe, camera_kind.kind AS cameraKind, "gbCamera".online AS cameraOnline, anxinIpc.t_video_ipc.name AS anxinIpcPosition, anxinStation.id AS anxinStationId, anxinStation.name AS anxinStationName, anxinStation.factor AS anxinStationFactorId, anxinStruc.name AS strucName, anxinStruc.id AS strucId FROM ( SELECT camera.id AS cameraId, camera.gb_id AS gbId, camera.name AS cameraName, camera.kind_id AS cameraKindId, camera_status_alarm.id AS alarmId, camera_status_alarm.create_time AS createTime, camera_status_alarm.status_id AS statusId, camera_status_alarm.serial_no AS cameraSerialNo, camera_status_alarm.channel_no AS cameraChannelNo, camera_status_alarm.confirm AS confirmContent, camera_status_alarm.auto_restore AS autoRestore, camera_status_alarm.confirm_time AS confirmTime FROM camera_status_alarm 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 (${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 ${c.alarmSubType ? `AND camera.kind_id in (${videoAlarmSubType.join(',')})` : ""} 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 AND cameraAlarm.statusId = camera_status.id LEFT JOIN "gbCamera" ON "gbCamera".id = cameraAlarm.gbId LEFT JOIN camera_kind ON camera_kind.id = cameraAlarm.cameraKindId LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo LEFT JOIN ${anxinyun}.t_structure AS anxinStruc ON anxinStruc.id = anxinIpc.structure 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 ON anxinStation.id = anxinIpcStation.station ORDER BY cameraAlarm.createTime DESC ` ).toPromise() : [] let returnD = [] let positionD = {} // 每个设备一个告警 for (let a of videoAlarms) { if (positionD[a.cameraId]) { let curD = returnD[positionD[a.cameraId].positionReturnD] if (a.strucId && !curD.struc.some(s => s.id == a.strucId)) { curD.struc.push({ id: a.strucId, projectId: a.projectId, name: a.strucName }) } if (a.anxinStationId && !curD.station.some(s => s.id == a.anxinStationId)) { curD.station.push({ id: a.anxinStationId, name: a.anxinStationName, position: a.anxinIpcPosition }) } } else { /**按监测因素 factor 筛选告警*/ // if (pomsStrucFactorId) { // if (!a.strucId || !a.anxinStationFactorId) { // // 当前告警没有绑定结构物或者摄像头没有绑定测点 // continue // } else if (!pomsStrucFactorId[a.strucId]) { // // 推送配置没配置这个结构物 // continue // } else if (!pomsStrucFactorId[a.strucId].includes(a.anxinStationFactorId)) { // // 不包含这个监测因素 // continue // } // } let d = { cameraId: a.cameraId, cameraName: a.cameraName, camerOnline: a.cameraOnline, cameraSerialNo: a.cameraSerialNo, cameraChannelNo: a.cameraChannelNo, autoRestore: a.autoRestore, createTime: a.createTime, updateTime: a.updateTime, platform: a.platform, statusDescribe: a.statusDescribe, alarmId: a.alarmId, confirmContent: a.confirmContent, confirmTime: a.confirmTime, cameraKind: a.cameraKind, factorId: a.anxinStationFactorId, struc: [], station: [] } if (a.strucId) { d.struc.push({ id: a.strucId, projectId: a.projectId, name: a.strucName }) } if (a.anxinStationId) { d.station.push({ id: a.anxinStationId, name: a.anxinStationName, position: a.anxinIpcPosition }) } returnD.push(d) positionD[a.cameraId] = { positionReturnD: returnD.length - 1 } } } videoAlarms = returnD } if (c.alarmType.includes('app_exception')) { if (c.alarmSubType) { appAlarmWhereOption.type = { $in: c.alarmSubType['app_exception'] || [] } } appAlarms = c.alarmSubType && c.alarmSubType['app_exception'].length ? await models.AppAlarm.findAll({ where: appAlarmWhereOption, order: [['createTime', 'DESC']], include: [{ model: models.App, required: true, include: [{ model: models.ProjectApp, where: { projectId: { $in: pomsProjectId } }, required: true, }] }] }) : [] } if (c.alarmType.includes('device_exception')) { dataAlarmGroupOption.push(4) dataAlarmGroupOption.push(5) if (c.alarmSubType) dataAlarmSubType = dataAlarmSubType.concat(c.alarmSubType['device_exception']) } // 查数据告警 三警合一 if (dataAlarmGroupOption.length && searchStrucIds.length) { dataAlarmGroupOption.push(-1) dataAlarmOption.push(`AlarmGroup IN (${dataAlarmGroupOption.join(',')})`) if (c.alarmSubType && dataAlarmSubType.length) { dataAlarmSubType.push(-1) dataAlarmOption.push(`AlarmGroupUnit IN (${dataAlarmSubType.join(',')})`) } dataAlarms = !c.alarmSubType || dataAlarmSubType.length ? await clickHouse.dataAlarm.query(` SELECT * FROM alarms LEFT JOIN ${anxinyun}.t_sensor AS anxinStation ON toString(anxinStation.id) = alarms.SourceId AND alarms.SourceTypeId = 2 WHERE ${`State NOT IN (3, 4) AND `} StructureId IN (${searchStrucIds.join(',')}) ${dataAlarmOption.length ? ' AND ' + dataAlarmOption.join(' AND ') : ''} ORDER BY StartTime DESC `).toPromise() : [] } let dataAlarmTitle = [{ n: '项目', k: '', v: pepProjectName }, { n: '结构物', k: '', f: (d) => { return (strucMap[d.StructureId] || { name: '' }).name // return (strucListRes.find(s => s.id == d.StructureId) || { name: '' }).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 ? '超过' + calcMinute2DHM(moment().diff(moment(d.StartTime), 'minutes')) : '' } },] let videoAlarmTitle = [{ n: '项目', k: '', v: pepProjectName }, { n: '结构物', k: '', f: (d) => { return d.struc.map(ds => ds.name).join('、') } }, { n: '告警源名称', k: 'cameraName' }, { n: '告警源类型', k: 'cameraKind' }, { n: '序列号', k: 'cameraSerialNo' }, { n: '通道号', k: 'cameraChannelNo' }, { n: '测点', k: '', f: (d) => { return d.station ? d.station.map(ds => ds.name).join('、') : '' } }, { n: '位置', k: '', f: (d) => { return d.station ? d.station.map(ds => ds.position).join('、') : '' } }, { n: '告警信息', k: 'statusDescribe' }, { n: '持续时间', k: '', f: (d) => { return d.createTime ? '超过' + calcMinute2DHM(moment().diff(moment(d.createTime), 'minutes')) : '' } },] let appAlarmTitle = [{ n: '项目', k: '', v: pepProjectName }, { n: '应用名称', k: '', f: (d) => { return d.app ? d.app.name : '' } }, { n: '异常类型', k: '', f: (d) => { if (d.type == 'element') { return '元素异常' } else if (d.type == 'apiError') { return '接口报错' } else { return '' } } }, { n: '异常信息', k: 'alarmContent' }, { n: 'URL地址', k: 'cameraName', f: (d) => { return d.app && d.app.url ? `${d.app.url}` : '' } }, { n: '持续时间', k: '', f: (d) => { return d.createTime ? '超过' + calcMinute2DHM(moment().diff(moment(d.createTime), 'minutes')) : '' } },] let ifEmailSend = false let tableTitlePostfix = ',详情如下:' function packageTableTitle (titleArr) { let tableTitle = '' for (let t of titleArr) { tableTitle += `${t.n}` } tableTitle += '' return tableTitle } function packageTableData ({ data, titleArr }) { let tableData = '' for (let t of titleArr) { if (t.v) { tableData += `${t.v || ''}` } else if (t.f) { tableData += `${t.f(data) || ''}` } else if (t.k) { tableData += `${data[t.k] || ''}` } else { tableData += `` } } tableData += '' return tableData } let apMergeDeVeAnxinProjectId_ = apMergeDeVeAnxinProjectId ? apMergeDeVeAnxinProjectId.split(',') : []; let apMergeDeVeAlarms = { // 结构物id :{ // data_exception 数据异常告警:[], // video_exception 视频异常告警:[] // } } let dataAlarmG1 = []; let dataAlarmG2 = []; let dataAlarmG3 = []; let dataAlarmG45 = []; let deviceStatistic = new Set() let dataAlarmDetails = [] if (proDebug) { console.log(`查得数据告警 ${dataAlarms.length} 条`); console.log(dataAlarms); } if (dataAlarms.length) { const alarmIds = dataAlarms .map(ar => "'" + ar.AlarmId + "'") dataAlarmDetails = await clickHouse.dataAlarm.query(` SELECT * FROM alarm_details WHERE AlarmId IN (${alarmIds.join(',')}, '-1') AND AlarmState = 0 `).toPromise() } if (proDebug) { console.log(`查得数据告警详情数据 ${dataAlarmDetails.length} 条`); } if (proDebug) { console.log(`pomsStrucFactorId:`, pomsStrucFactorId); } let deviceIds = new Set() for (let d of dataAlarms) { d = { ...d, stationId: d.id } /** 按监测因素筛选 且为测点告警 */ // if (pomsStrucFactorId && d.SourceTypeId == 2) { // // 做了监测因素筛选 且当前告警有监测因素 // if (!d.factor || d.factor == 0) { // // 监测因素不对劲 // continue // } else if (!d.StructureId) { // // 当前告警没有绑定结构物 // continue // } else if (!pomsStrucFactorId[d.StructureId]) { // // 推送配置没配置这个结构物 // continue // } else if (!pomsStrucFactorId[d.StructureId].includes(d.factor)) { // // 不包含这个监测因素 // continue // } // } if (d.AlarmGroup == 1) { dataAlarmG1.push(d) } else if (d.AlarmGroup == 2) { dataAlarmG2.push(d) /** 注1 * 根据指定的安心云结构物把数据异常和视频告警合并在一起的代码 * 还要指定是扬尘设备 * 以测点关联 */ if ( isDev || ( apMergeDeVeAnxinProjectId_.length && d.SourceName && d.SourceName.includes('扬尘') && apMergeDeVeAnxinProjectId_.some(pid => pid == (strucMap[d.StructureId] || {}).projectId) ) ) { // SourceTypeId 0: 'DTU' / 1: '传感器' / 2: '测点' if (d.SourceTypeId != 2) { deviceIds.add(d.SourceId) } if (apMergeDeVeAlarms[d.StructureId]) { apMergeDeVeAlarms[d.StructureId].data_exception.push(d) } else { apMergeDeVeAlarms[d.StructureId] = { data_exception: [d], video_exception: [] } } } // 注1 END } else if (d.AlarmGroup == 3) { /** 按监测因项 factor-item 的名称筛选*/ if (pomsStrucFactorId) { // 兼容之前的设置 有这个信息才进行判断 if (!d.StructureId) { // 当前告警没有绑定结构物 continue } else if (!pomsStrucFactorId[d.StructureId]) { // 推送配置没配置这个结构物 continue } else { let corDetail = dataAlarmDetails.find(da => da.AlarmId == d.AlarmId) if (proDebug) { console.log(`相应告警详情(策略命中):`, corDetail); } if (corDetail) { // 判断告警详情信息里有没有监测项关键字 if (!pomsStrucFactorId[d.StructureId].some(factorItemName => { return corDetail.Content.includes(factorItemName) })) { continue } } else { continue } } } if (proDebug) { console.log(`符合条件的策略命中 + 1`, d); } dataAlarmG3.push(d) } else if (d.AlarmGroup == 4 || d.AlarmGroup == 5) { dataAlarmG45.push(d) } deviceStatistic.add(d.SourceId) } if (c.tactics == 'abnormal_rate') { let rate = ((deviceStatistic.size + videoAlarms.length) / (parseInt(deviceCount) + parseInt(cameraCount))); if (rate < parseFloat(deviceProportion)) { // 设备异常率低于设定值 continue } emailSubTitle = emailSubTitle.replace('--%', rate.toFixed(1) + '%') } // 注1 if (apMergeDeVeAnxinProjectId_.length || isDev) { for (let a of videoAlarms) { let existStruc = a.struc.find(asc => apMergeDeVeAlarms[asc.id] || isDev) if (existStruc) { apMergeDeVeAlarms[existStruc.id].video_exception.push(a) } } } // 注1 END let html = `
${emailSubTitle}
` function packageAlarmData2Table ({ titlePrefix, alarmData, alarmTitleArr, keyOfStartTime = 'StartTime' }) { if (!alarmData.length) { return '' } ifEmailSend = true let tableTitlePrefix = titlePrefix + '告警源' let newAddCount = 0 let alarmHtml = '' let alarmContent = '' let alarmHtmlTitle = packageTableTitle(alarmTitleArr) for (let [index, a] of alarmData.entries()) { alarmContent += packageTableData({ data: a, titleArr: alarmTitleArr }) if (a[keyOfStartTime] && moment(a[keyOfStartTime]).isBetween(newAddStartTime, newAddEndTime)) { newAddCount++ } } tableTitlePrefix += titlePrefix != '数据异常&视频异常' ? c.tactics == 'abnormal_rate' ? `${alarmData.length}个` : `新增${newAddCount}个,未解决累计${alarmData.length}个` + tableTitlePostfix : '' alarmHtml += `' alarmHtml += alarmHtmlTitle alarmHtml += alarmContent alarmHtml += '
` + tableTitlePrefix + '

' return alarmHtml } if (c.alarmType.includes('data_outages')) { html += packageAlarmData2Table({ titlePrefix: '数据中断', alarmData: dataAlarmG1, alarmTitleArr: dataAlarmTitle, }) } if (c.alarmType.includes('data_exception')) { html += packageAlarmData2Table({ titlePrefix: '数据异常', alarmData: dataAlarmG2, alarmTitleArr: dataAlarmTitle, }) } if (c.alarmType.includes('strategy_hit')) { html += packageAlarmData2Table({ titlePrefix: '策略命中', alarmData: dataAlarmG3, alarmTitleArr: dataAlarmTitle, }) } if (c.alarmType.includes('video_exception')) { html += packageAlarmData2Table({ titlePrefix: '视频异常', alarmData: videoAlarms, alarmTitleArr: videoAlarmTitle, keyOfStartTime: 'createTime', }) } if (c.alarmType.includes('app_exception')) { html += packageAlarmData2Table({ titlePrefix: '应用异常', alarmData: appAlarms, alarmTitleArr: appAlarmTitle, keyOfStartTime: 'createTime', }) } if (c.alarmType.includes('device_exception')) { html += packageAlarmData2Table({ titlePrefix: '设备异常', alarmData: dataAlarmG45, alarmTitleArr: dataAlarmTitle, }) } if (Object.keys(apMergeDeVeAlarms).length) { if (proDebug) { console.log(`查得数据异常、视频异常合并の告警:`); console.log(apMergeDeVeAlarms); } let deviceSensorRes = [] if (deviceIds.size) { const device4Search = [...deviceIds] .map(id => "'" + id + "'") deviceSensorRes = await clickHouse.anxinyun.query(` SELECT iota_device_id, sensor FROM t_device_sensor WHERE iota_device_id ${device4Search.length > 1 ? `IN (${device4Search.join(',')})` : `= ${device4Search[0]}`} `).toPromise() } if (proDebug) { console.log(`相关设备及测点信息:`); console.log(deviceSensorRes); } let alarmTitle = dataAlarmTitle.concat( videoAlarmTitle.slice(2).map(v => { return { ...v, n: v.n == '持续时间' ? '摄像头告警' + v.n : '摄像头' + v.n } }) ) let alarmData = [] for (let aKey in apMergeDeVeAlarms) { let curStrucAlarm = apMergeDeVeAlarms[aKey] for (let de of curStrucAlarm.data_exception) { if (!de.id) { let corSensor = deviceSensorRes.find(ds => ds.iota_device_id == de.SourceId) if (corSensor) { de.id = corSensor.sensor } } let corVideoException = curStrucAlarm.video_exception.filter(v => { // ! de.id 是告警信息关联查出来的测点的id return v.station.some(vs => vs.id == de.id) }) if (!corVideoException.length) { // 构造一个长度 以便在for循环里把 data_exception 放进去 corVideoException.push({}) } for (let ve of corVideoException) { alarmData.push({ ...de, ...ve }) } } } html += packageAlarmData2Table({ titlePrefix: '数据异常&视频异常', alarmData: alarmData, alarmTitleArr: alarmTitle, }) } if (ifEmailSend) { // 查接收人的信息 const receiverRes = c.receiverPepUserId.length ? await clickHouse.pepEmis.query(` SELECT id, name, email FROM user WHERE id IN (${c.receiverPepUserId.join(',')},-1) `).toPromise() : [] let receiverId = [] let emails = receiverRes.reduce((arr, r) => { if (r.email) { arr.push(r.email) receiverId.push({ id: r.id, name: r.name }) } return arr }, []) if (isDev) { // !开发测试用的数据 emails = ['1650192445@qq.com'] } if (emails.length) { await pushByEmail({ email: emails, title: emailTitle, text: '', html: html }) //存日志 存动态 socket到前端 let dataToSave = { time: moment().format(), pushConfigId: c.id, cfgName: c.name,//策略名称 tactics: c.tactics, tacticsParams: c.tacticsParams, projectCorrelationId: pomsProjectId, toPepUserIds: receiverId.map(r => r.id) } let r = await models.EmailSendLog.create(dataToSave, { returning: true }) let dynamic = { time: r.dataValues.time, emailSendId: r.dataValues.id, // projectCorrelationId: r.dataValues.projectCorrelationId, type: 2//通知 } await models.LatestDynamicList.create(dynamic); //消息推送到前端 await sendNoticeToWeb(receiverId, dataToSave); } } } } } } catch (error) { console.error(error); } } ) return { alarmsPush, } }