diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index c428d9e..636e38a 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -16,13 +16,14 @@ "-p 4600", "-f http://localhost:4600", // 研发 - "-g postgres://postgres:123@10.8.30.32:5432/orational_service", + // "-g postgres://postgres:123@10.8.30.32:5432/orational_service", // 测试 - // "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", + "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", "-k 10.8.30.72:29092,10.8.30.73:29092,10.8.30.74:29092", "--iotaProxy http://10.8.30.157:17007", "--redisHost 10.8.30.112", "--redisPort 6379", + "--apMergeDeVeAnxinProjectId 1,2,3", "--axyApiUrl http://127.0.0.1:4100", // "--apiEmisUrl http://10.8.30.112:14000", // 测试 @@ -43,11 +44,9 @@ "--clickHouseUrl http://10.8.30.161", // "--clickHouseUrl https://clickhouse01.anxinyun.cn/play", "--clickHousePort 30123", - // 似乎不能传空 先注释 * 2 // "--clickHouseUser ", // "--clickHousePassword ", - // 研发 // "--clickHouseAnxincloud anxinyun", // "--clickHousePepEmis pepca", @@ -55,15 +54,13 @@ // "--clickHouseVcmp video_accrss1", // "--clickHouseDataAlarm default", // "--clickHouseIot iot", - // 测试 "--clickHouseAnxincloud anxinyun1", "--clickHousePepEmis pepca8", - "--clickHouseProjectManage peppm8", + "--clickHouseProjectManage peppm", "--clickHouseVcmp video_access_dev", "--clickHouseDataAlarm default", "--clickHouseIot iot", - "--confirmAlarmAnxinUserId 1", "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", diff --git a/api/app/lib/schedule/alarms_push.js b/api/app/lib/schedule/alarms_push.js index 3074b5a..f25e973 100644 --- a/api/app/lib/schedule/alarms_push.js +++ b/api/app/lib/schedule/alarms_push.js @@ -4,7 +4,7 @@ let isDev = false // isDev = true let proDebug = false -proDebug = true +// proDebug = true module.exports = function (app, opts) { const alarmsPush = app.fs.scheduleInit( @@ -16,6 +16,7 @@ module.exports = function (app, opts) { 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 @@ -73,7 +74,7 @@ module.exports = function (app, opts) { const { interval, deviceProportion } = c.tacticsParams if ( - curMinOfYear % parseInt(interval) == 0 + curMinOfYear % parseInt(interval) == 0 || isDev ) { const corPomsProject = pomsProjectRes.filter(poms => pomsProjectId.includes(poms.id)) @@ -110,7 +111,8 @@ module.exports = function (app, opts) { DISTINCT id, t_structure.id AS id, t_structure.name AS name, - t_structure.iota_thing_id AS iotaThingId + t_structure.iota_thing_id AS iotaThingId, + t_project.id AS projectId FROM t_project LEFT JOIN @@ -141,10 +143,14 @@ module.exports = function (app, opts) { ).toPromise() : [] let strucThingId = [] + let strucMap = {} let searchStrucIds = strucListRes.map(s => { if (s.iotaThingId) { strucThingId.push(s.iotaThingId) } + strucMap[s.id] = { + ...s + } return s.id }) @@ -476,7 +482,8 @@ module.exports = function (app, opts) { n: '结构物', k: '', f: (d) => { - return (strucListRes.find(s => s.id == d.StructureId) || { name: '' }).name + return (strucMap[d.StructureId] || { name: '' }).name + // return (strucListRes.find(s => s.id == d.StructureId) || { name: '' }).name } }, { n: '告警源名称', @@ -549,13 +556,13 @@ module.exports = function (app, opts) { n: '测点', k: '', f: (d) => { - return d.station.map(ds => ds.name).join('、') + return d.station ? d.station.map(ds => ds.name).join('、') : '' } }, { n: '位置', k: '', f: (d) => { - return d.station.map(ds => ds.position).join('、') + return d.station ? d.station.map(ds => ds.position).join('、') : '' } }, { n: '告警信息', @@ -621,15 +628,15 @@ module.exports = function (app, opts) { tableTitle += '' return tableTitle } - function packageTableData (data, titleArr) { + function packageTableData ({ data, titleArr }) { let tableData = '' for (let t of titleArr) { if (t.v) { - tableData += `${t.v}` + tableData += `${t.v || ''}` } else if (t.f) { - tableData += `${t.f(data)}` + tableData += `${t.f(data) || ''}` } else if (t.k) { - tableData += `${data[t.k]}` + tableData += `${data[t.k] || ''}` } else { tableData += `` } @@ -638,33 +645,13 @@ module.exports = function (app, opts) { return tableData } - 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 a of alarmData) { - alarmContent += packageTableData(a, alarmTitleArr) - if (a[keyOfStartTime] && moment(a[keyOfStartTime]).isBetween(newAddStartTime, newAddEndTime)) { - newAddCount++ - } - } - tableTitlePrefix += - c.tactics == 'abnormal_rate' ? - `${alarmData.length}个` : - `新增${newAddCount}个,未解决累计${alarmData.length}个` + tableTitlePostfix - alarmHtml += `' - alarmHtml += alarmHtmlTitle - alarmHtml += alarmContent - alarmHtml += '
` + tableTitlePrefix + '

' - - return alarmHtml + let apMergeDeVeAnxinProjectId_ = apMergeDeVeAnxinProjectId ? + apMergeDeVeAnxinProjectId.split(',') : []; + let apMergeDeVeAlarms = { + // 结构物id :{ + // data_exception 数据异常告警:[], + // video_exception 视频异常告警:[] + // } } let dataAlarmG1 = []; @@ -693,6 +680,7 @@ module.exports = function (app, opts) { if (proDebug) { console.log(`pomsStrucFactorId:`, pomsStrucFactorId); } + for (let d of dataAlarms) { /** 按监测因素筛选 且为测点告警 */ @@ -717,6 +705,30 @@ module.exports = function (app, opts) { 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) + ) + ) { + 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 的名称筛选*/ @@ -764,6 +776,17 @@ module.exports = function (app, opts) { 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 = ` @@ -783,49 +806,123 @@ module.exports = function (app, opts) {
${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( - '数据中断', - dataAlarmG1, - dataAlarmTitle, - ) + html += packageAlarmData2Table({ + titlePrefix: '数据中断', + alarmData: dataAlarmG1, + alarmTitleArr: dataAlarmTitle, + }) } if (c.alarmType.includes('data_exception')) { - html += packageAlarmData2Table( - '数据异常', - dataAlarmG2, - dataAlarmTitle, - ) + html += packageAlarmData2Table({ + titlePrefix: '数据异常', + alarmData: dataAlarmG2, + alarmTitleArr: dataAlarmTitle, + }) } if (c.alarmType.includes('strategy_hit')) { - html += packageAlarmData2Table( - '策略命中', - dataAlarmG3, - dataAlarmTitle, - ) + html += packageAlarmData2Table({ + titlePrefix: '策略命中', + alarmData: dataAlarmG3, + alarmTitleArr: dataAlarmTitle, + }) } if (c.alarmType.includes('video_exception')) { - html += packageAlarmData2Table( - '视频异常', - videoAlarms, - videoAlarmTitle, - 'createTime', - ) + html += packageAlarmData2Table({ + titlePrefix: '视频异常', + alarmData: videoAlarms, + alarmTitleArr: videoAlarmTitle, + keyOfStartTime: 'createTime', + }) } if (c.alarmType.includes('app_exception')) { - html += packageAlarmData2Table( - '应用异常', - appAlarms, - appAlarmTitle, - 'createTime', - ) + html += packageAlarmData2Table({ + titlePrefix: '应用异常', + alarmData: appAlarms, + alarmTitleArr: appAlarmTitle, + keyOfStartTime: 'createTime', + }) } if (c.alarmType.includes('device_exception')) { - html += packageAlarmData2Table( - '设备异常', - dataAlarmG45, - dataAlarmTitle, + html += packageAlarmData2Table({ + titlePrefix: '设备异常', + alarmData: dataAlarmG45, + alarmTitleArr: dataAlarmTitle, + }) + } + + if (Object.keys(apMergeDeVeAlarms).length) { + 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) { + 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) { diff --git a/api/config.js b/api/config.js index 507837c..9ee0158 100644 --- a/api/config.js +++ b/api/config.js @@ -19,6 +19,8 @@ args.option('redisHost', 'redisHost'); args.option('redisPort', 'redisPort'); args.option('redisPswd', 'redisPassword'); +args.option('apMergeDeVeAnxinProjectId', '告警推送自定义の合并数据异常(De)视频异常(Ve)的指定的结构物id'); + args.option('axyApiUrl', '安心云 api'); args.option('apiEmisUrl', '企业管理 api'); args.option('apiVcmpUrl', '视频平台 api'); @@ -67,6 +69,8 @@ const IOTA_REDIS_SERVER_HOST = process.env.IOTA_REDIS_SERVER_HOST || flags.redis const IOTA_REDIS_SERVER_PORT = process.env.IOTA_REDIS_SERVER_PORT || flags.redisPort || "6379";//redis 端口 const IOTA_REDIS_SERVER_PWD = process.env.IOTA_REDIS_SERVER_PWD || flags.redisPswd || "";//redis 密码 +const AP_MERGE_DEVE_ANXINPROJECT_ID = process.env.AP_MERGE_DEVE_ANXINPROJECT_ID || flags.apMergeDeVeAnxinProjectId || ""; + // 安心云api const API_ANXINYUN_URL = process.env.API_ANXINYUN_URL || flags.axyApiUrl; // 企业管理 api @@ -155,6 +159,7 @@ const product = { { p: '/alarm/application/api', o: 'POST' }, { p: '/alarm/video/added_log', o: 'POST' } ], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由 + apMergeDeVeAnxinProjectId: AP_MERGE_DEVE_ANXINPROJECT_ID, anxinCloud: { confirmAlarmAnxinUserId: CONFIRM_ALARM_ANXIN_USER_ID },