diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 8125a3e..b93dda7 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", + "--redisHost localhost", "--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,7 +54,6 @@ // "--clickHouseVcmp video_accrss1", // "--clickHouseDataAlarm default", // "--clickHouseIot iot", - // 测试 "--clickHouseAnxincloud anxinyun88", "--clickHousePepEmis pepca8", @@ -63,7 +61,6 @@ "--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/Dockerfile b/api/Dockerfile index 74ac3c3..10fc786 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,36 +1,36 @@ -# FROM repository.anxinyun.cn/devops/node:12-dev as builder +FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12-dev as builder -# COPY . /var/app +COPY . /var/app -# WORKDIR /var/app +WORKDIR /var/app -# EXPOSE 8080 +EXPOSE 8080 -# RUN npm config set registry=http://10.8.30.22:7000 -# RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json -# RUN npm cache clean -f -# RUN rm -rf package-lock.json -# RUN npm install --registry http://10.8.30.22:7000 +RUN npm config set registry=http://10.8.30.22:7000 +RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json +RUN npm cache clean -f +RUN rm -rf package-lock.json +RUN npm install --registry http://10.8.30.22:7000 -# FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12 +FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12 -# COPY --from=builder --chown=node /var/app /home/node/app +COPY --from=builder --chown=node /var/app /home/node/app -# WORKDIR /home/node/app +WORKDIR /home/node/app -# CMD ["node", "server.js"] +CMD ["node", "server.js"] # 旧版本构建方式 -FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2 +# FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2 -COPY . /var/app +# COPY . /var/app -WORKDIR /var/app +# WORKDIR /var/app -EXPOSE 8080 +# EXPOSE 8080 -CMD ["-u", "http://localhost:8088"] +# CMD ["-u", "http://localhost:8088"] -ENTRYPOINT [ "node", "server.js" ] \ No newline at end of file +# ENTRYPOINT [ "node", "server.js" ] \ No newline at end of file diff --git a/api/app/lib/controllers/alarm/data.js b/api/app/lib/controllers/alarm/data.js index ca1204a..87ac48f 100644 --- a/api/app/lib/controllers/alarm/data.js +++ b/api/app/lib/controllers/alarm/data.js @@ -8,17 +8,23 @@ async function groupList (ctx) { const { clickHouse } = ctx.app.fs const { database: dataAlarm } = clickHouse.dataAlarm.opts.config + const { showAll } = ctx.query const groupRes = await clickHouse.anxinyun.query(` SELECT * FROM t_alarm_group `).toPromise(); + let whereOption = [] + if (!showAll) { + whereOption.push(` INNER JOIN ${dataAlarm}.alarms + ON t_alarm_group_unit.id = ${dataAlarm}.alarms.AlarmGroupUnit`) + } + for (let g of groupRes) { g.unit = await await clickHouse.anxinyun.query(` SELECT DISTINCT t_alarm_group_unit.id AS id,t_alarm_group_unit.name AS name,t_alarm_group_unit.group_id AS groupId FROM t_alarm_group_unit - INNER JOIN ${dataAlarm}.alarms - ON t_alarm_group_unit.id = ${dataAlarm}.alarms.AlarmGroupUnit + ${whereOption} WHERE group_id = ${g.id} `).toPromise(); } @@ -90,7 +96,7 @@ async function list (ctx) { whereOption.push(`DeviceStatus.Status = ${0}`) } } - + let alarmQueryOptionStr = ` FROM alarms @@ -143,7 +149,6 @@ async function list (ctx) { `).toPromise(); const confirmedAlarm = alarmRes - // TODO: 开发临时注释 .filter(ar => ar.State && ar.State > 2) .map(ar => "'" + ar.AlarmId + "'") const confirmedAlarmDetailMax = confirmedAlarm.length ? @@ -218,7 +223,7 @@ async function list (ctx) { } } -async function getAlarmGroups(ctx) { +async function getAlarmGroups (ctx) { try { const { clickHouse } = ctx.app.fs const groupRes = await clickHouse.anxinyun.query(` @@ -239,7 +244,7 @@ async function getAlarmGroups(ctx) { } } } -async function exportDataAlarms(ctx, alarmList, groupId) { +async function exportDataAlarms (ctx, alarmList, groupId) { try { const { utils: { simpleExcelDown, getExportAlarmHeader } } = ctx.app.fs; let header = await getExportAlarmHeader(groupId); @@ -303,7 +308,7 @@ async function exportDataAlarms(ctx, alarmList, groupId) { } } } -async function detail(ctx) { +async function detail (ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs diff --git a/api/app/lib/controllers/alarm/video.js b/api/app/lib/controllers/alarm/video.js index ab2cff1..b9a0dbf 100644 --- a/api/app/lib/controllers/alarm/video.js +++ b/api/app/lib/controllers/alarm/video.js @@ -8,11 +8,18 @@ async function deviceType (ctx) { const { clickHouse } = ctx.app.fs const { database: anxinyun } = clickHouse.anxinyun.opts.config const { utils: { judgeSuper, anxinStrucIdRange } } = ctx.app.fs + const { showAll } = ctx.query let anxinStruc = await anxinStrucIdRange({ ctx, }) const anxinStrucIds = anxinStruc.map(a => a.strucId) - + let whereOption = [] + if (!showAll) { + whereOption.push(`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 + ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`}`) + } const kindRes = await clickHouse.vcmp.query(` SELECT DISTINCT camera_kind.id AS id,camera_kind.kind AS kind @@ -22,10 +29,7 @@ async function deviceType (ctx) { INNER JOIN camera_status_alarm ON camera.channel_no = camera_status_alarm.channel_no AND camera.serial_no = camera_status_alarm.serial_no - 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 - ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} + ${whereOption} `).toPromise() ctx.status = 200; ctx.body = kindRes @@ -38,7 +42,7 @@ async function deviceType (ctx) { } } -async function exceptionType(ctx) { +async function exceptionType (ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs diff --git a/api/app/lib/controllers/auth/index.js b/api/app/lib/controllers/auth/index.js index 555caf8..3ce02bb 100644 --- a/api/app/lib/controllers/auth/index.js +++ b/api/app/lib/controllers/auth/index.js @@ -10,9 +10,18 @@ async function login (ctx, next) { const models = ctx.fs.dc.models; const params = ctx.request.body; - const emisLoginRes = await ctx.app.fs.emisRequest.post('login', { - data: params - }) + let emisLoginRes = null + if (params.username && params.password) { + emisLoginRes = await ctx.app.fs.emisRequest.post('login', { + data: { ...params, code: 'POMS' } + }) + } else if (params.token) { + emisLoginRes = await ctx.app.fs.emisRequest.get('user-info', { + query: { + token: params.token, code: 'POMS' + } + }) + } if (!emisLoginRes) { throw "无此用户,请使用正确的登录信息" @@ -70,7 +79,7 @@ async function login (ctx, next) { await ctx.redis.hmset(emisLoginRes.token, { expired: moment().add(1, 'day'), - userInfo:JSON.stringify(emisLoginRes) + userInfo: JSON.stringify(emisLoginRes) }); ctx.status = 200; diff --git a/api/app/lib/controllers/control/data.js b/api/app/lib/controllers/control/data.js index 4b09bcd..5cdd5ad 100644 --- a/api/app/lib/controllers/control/data.js +++ b/api/app/lib/controllers/control/data.js @@ -2,7 +2,7 @@ const moment = require('moment'); //BI分析-数据 -async function getDataAlarmsAggDay(ctx) { +async function getDataAlarmsAggDay (ctx) { try { const { utils: { anxinStrucIdRange } } = ctx.app.fs const { pepProjectId } = ctx.query @@ -12,10 +12,8 @@ async function getDataAlarmsAggDay(ctx) { ctx, pepProjectId }) let whereOption = [] - // ! 1 开发临时增加 if (anxinStruc.length) { const anxinStrucIds = anxinStruc.map(a => a.strucId) - // ! 开发临时注释 whereOption.push(`alarms.StructureId IN (${anxinStrucIds.join(",")})`) let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年 @@ -41,7 +39,7 @@ async function getDataAlarmsAggDay(ctx) { } } -async function queryAlarm(ctx, alarmQueryOptionStr, type) { +async function queryAlarm (ctx, alarmQueryOptionStr, type) { const { clickHouse } = ctx.app.fs try { const alarmRes = await clickHouse.dataAlarm.query(` @@ -76,7 +74,7 @@ async function queryAlarm(ctx, alarmQueryOptionStr, type) { } //BI分析-应用异常 -async function getAppAlarmsAggDay(ctx) { +async function getAppAlarmsAggDay (ctx) { try { const models = ctx.fs.dc.models; const { utils: { pomsProjectRange } } = ctx.app.fs @@ -129,7 +127,7 @@ async function getAppAlarmsAggDay(ctx) { } //BI分析-视频异常 -async function getVideoAlarmsAggDay(ctx) { +async function getVideoAlarmsAggDay (ctx) { try { const { clickHouse, utils: { anxinStrucIdRange } } = ctx.app.fs const { database: anxinyun } = clickHouse.anxinyun.opts.config @@ -246,7 +244,7 @@ async function getVideoAlarmsAggDay(ctx) { } //BI分析-问题处置效率分析 -async function getAlarmsHandleStatistics(ctx) { +async function getAlarmsHandleStatistics (ctx) { try { const { projectCorrelationId } = ctx.query const models = ctx.fs.dc.models; @@ -266,7 +264,7 @@ async function getAlarmsHandleStatistics(ctx) { } } //最新动态 -async function getLatestDynamic(ctx) { +async function getLatestDynamic (ctx) { try { const { models } = ctx.fs.dc; const { limit, page, projectCorrelationId, types } = ctx.query; @@ -303,9 +301,9 @@ async function getLatestDynamic(ctx) { }); //查项目名称 查用户名 - let pepPojectIds = new Set(), notedUserIds = new Set(); + let pepPojectIds = new Set(), notedUserIds = new Set(), emailSendPomsProjectIds = new Set(); for (let p of news) { - if(p.projectCorrelation && p.projectCorrelation.pepProjectId){ + if (p.projectCorrelation && p.projectCorrelation.pepProjectId) { pepPojectIds.add(p.projectCorrelation.pepProjectId); } @@ -313,11 +311,28 @@ async function getLatestDynamic(ctx) { p.emailSendLog.toPepUserIds.map(u => { notedUserIds.add(u);//通知 接收人 }) + p.emailSendLog.projectCorrelationId.forEach(pid => emailSendPomsProjectIds.add(pid)) } if (p.alarmConfirmLog && p.alarmConfirmLog.pepUserId) { notedUserIds.add(p.alarmConfirmLog.pepUserId);//确认 操作者 } } + + // EM 推送的特殊处理 + // 查找对应的 projectCorrelation + const emailLogProjectCorrelationRes = + emailSendPomsProjectIds.size ? + await models.ProjectCorrelation.findAll({ + where: { + id: { $in: [...emailSendPomsProjectIds] } + } + }) : [] + + for (let { dataValues: p } of emailLogProjectCorrelationRes) { + if (p.pepProjectId) { + pepPojectIds.add(p.pepProjectId) + } + } let pepProjects = pepPojectIds.size ? await clickHouse.projectManage.query(` SELECT id, project_name FROM t_pim_project WHERE id IN (${[...pepPojectIds].join(',')},-1) `).toPromise() : []; @@ -339,7 +354,15 @@ async function getLatestDynamic(ctx) { if (d.emailSendId) { notice.push({ userName: userPepRes.filter(u => d.emailSendLog.toPepUserIds.indexOf(u.id) != -1), - projectName, + projectName: d.emailSendLog.projectCorrelationId.map(p => { + let projectName = '' + if (p.pepProjectId) { + projectName = pepProjects.find(pp => pp.id == p.pepProjectId).project_name + } else { + projectName = p.name + } + return projectName + }).join('、'), ...d.emailSendLog.dataValues }); } diff --git a/api/app/lib/controllers/project/bind.js b/api/app/lib/controllers/project/bind.js index 06d25c2..3cfa989 100644 --- a/api/app/lib/controllers/project/bind.js +++ b/api/app/lib/controllers/project/bind.js @@ -105,6 +105,9 @@ async function bindAnxin2pep (ctx) { // 但是有之前的数据 if (existRes.del) { // 不过之前的删除了 + storageData.createUser = userId + storageData.updateTime = now.format() + storageData.createTime = now.format() await models.ProjectCorrelation.update(storageData, { where: { pepProjectId: pepProjectId diff --git a/api/app/lib/controllers/project/index.js b/api/app/lib/controllers/project/index.js index 2659964..45d0f77 100644 --- a/api/app/lib/controllers/project/index.js +++ b/api/app/lib/controllers/project/index.js @@ -57,7 +57,7 @@ async function pomsProject (ctx) { if (p.pepProjectId) { pepProjectIds.add(p.pepProjectId) } - if(p.createUser){ + if (p.createUser) { createUsers.add(p.createUser) } for (let ap of p.anxinProjectId) { @@ -66,18 +66,18 @@ async function pomsProject (ctx) { } } } - const pomsUser = await models.User.findAll({ - where: { - id: { $in: [...createUsers]} - } - }) - let pepUserIds = new Set() - for (let p of pomsUser) { - if (p.pepUserId) { + const pomsUser = await models.User.findAll({ + where: { + id: { $in: [...createUsers] } + } + }) + let pepUserIds = new Set() + for (let p of pomsUser) { + if (p.pepUserId) { pepUserIds.add(p.pepUserId) - } - } - const pepcaUser = pepUserIds.size ? + } + } + const pepcaUser = pepUserIds.size ? await clickHouse.pepEmis.query( ` SELECT * FROM user @@ -112,7 +112,7 @@ async function pomsProject (ctx) { for (let p of proRes.rows) { const corPro = pepProjectRes.find(pp => pp.id == p.pepProjectId) || {} - const pepUserName = (pepcaUser.find(qq => qq.id == (pomsUser.find(oo => oo.id == p.createUser)||{}).pepUserId)||{}).name ||'' + const pepUserName = (pepcaUser.find(qq => qq.id == (pomsUser.find(oo => oo.id == p.createUser) || {}).pepUserId) || {}).name || '' p.dataValues.pepProjectName = corPro.project_name p.dataValues.pepProjectIsDelete = corPro.isdelete p.dataValues.constructionStatusId = corPro.construction_status_id @@ -126,7 +126,7 @@ async function pomsProject (ctx) { ctx.status = 200; ctx.body = proRes } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`) ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -210,20 +210,33 @@ async function strucWithPomsProject (ctx) { const { clickHouse } = ctx.app.fs const { pomsProjectId } = ctx.query - const bindRes = await models.ProjectCorrelation.findOne({ + const bindRes = await models.ProjectCorrelation.findAll({ where: { - id: pomsProjectId + id: { $in: pomsProjectId.split(',') } } }) + let anxinProjectIds = new Set() + for (let b of bindRes) { + if (b.anxinProjectId.length) { + for (let aid of b.anxinProjectId) { + anxinProjectIds.add(aid) + } + } + } let undelStruc = [] if (bindRes) { - const undelStrucRes = bindRes.anxinProjectId.length ? + const undelStrucRes = anxinProjectIds.size ? await clickHouse.anxinyun.query( ` SELECT t_structure.id AS strucId, - t_structure.name AS strucName + t_structure.name AS strucName, + t_factor.id AS factorId, + t_factor.name AS factorName, + t_factor.proto AS factorProto, + t_factor_proto_item.name AS factorItemName, + t_factor_proto_item.id AS factorItemId FROM t_project LEFT JOIN @@ -246,20 +259,70 @@ async function strucWithPomsProject (ctx) { ON t_structure.id = t_project_structure.structure OR t_structure.id = t_structuregroup_structure.structure OR t_structure.id = t_structure_site.structid + LEFT JOIN t_structure_factor + ON t_structure_factor.structure = t_structure.id + LEFT JOIN t_factor + ON t_structure_factor.factor = t_factor.id + LEFT JOIN t_factor_proto_item + ON t_factor_proto_item.proto = t_factor.proto WHERE project_state != -1 AND - t_project.id IN (${bindRes.anxinProjectId.join(',')}) + t_project.id IN (${[...anxinProjectIds].join(',')}, -1) ORDER BY strucId ` ).toPromise() : [] for (let s of undelStrucRes) { - if (!undelStruc.some(us => us.id == s.strucId)) { + let corStrut = undelStruc.find(us => us.id == s.strucId) + if (!corStrut) { + let nextFacor = [] + if (s.factorId) { + let nextFactorItem = [] + if (s.factorItemId) { + nextFactorItem.push({ + id: s.factorItemId, + name: s.factorItemName, + }) + } + nextFacor.push({ + id: s.factorId, + name: s.factorName, + proto: s.factorProto, + item: nextFactorItem, + }) + } undelStruc.push({ id: s.strucId, name: s.strucName, + factor: nextFacor }) + } else { + if (s.factorId) { + let corFactor = corStrut.factor.find(v => v.id == s.factorId) + let nextFactorItem = null + if (s.factorItemId) { + nextFactorItem = { + id: s.factorItemId, + name: s.factorItemName, + } + } + if (corFactor) { + if (!corFactor.item.some(fi => fi.id == s.factorItemId) && nextFactorItem) { + corFactor.item.push(nextFactorItem) + } + } else { + corStrut.factor.push({ + id: s.factorId, + name: s.factorName, + proto: s.factorProto, + item: nextFactorItem ? [{ + id: s.factorItemId, + name: s.factorItemName, + }] : [], + }) + } + } } } } diff --git a/api/app/lib/controllers/push/config.js b/api/app/lib/controllers/push/config.js index 0566837..43feb26 100644 --- a/api/app/lib/controllers/push/config.js +++ b/api/app/lib/controllers/push/config.js @@ -8,23 +8,11 @@ async function list (ctx) { const { clickHouse } = ctx.app.fs const { utils: { anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs const { keyword, keywordTarget, alarmType, state, tactics, pomsProjectId } = ctx.query - - let projectCorrelationWhere = { - del: false, - } - if (state == 'notYet') { - projectCorrelationWhere.pepProjectId = { $ne: null } - } let findOption = { where: { del: false }, order: [['id', 'desc']], - include: [{ - model: models.ProjectCorrelation, - where: projectCorrelationWhere, - required: true - }] } let anxinStrucsRange = await anxinStrucIdRange({ @@ -48,7 +36,7 @@ async function list (ctx) { ctx, pepProjectId: pomsProjectId, keywordTarget, keyword }) let pomsProjectIds = pomsProjectRes.map(p => p.id) - findOption.where.pomsProjectId = { $in: pomsProjectIds } + findOption.where.pomsProjectId = { $overlap: pomsProjectIds } if (alarmType) { findOption.where.alarmType = { $contains: [alarmType] } @@ -65,6 +53,7 @@ async function list (ctx) { } const listRes = await models.AlarmPushConfig.findAll(findOption) + // const listRes = await models.AlarmPushConfig.findAll({}) let allStrucIds = new Set() let allConfigId = [] let allReceiverIds = new Set() @@ -104,17 +93,28 @@ async function list (ctx) { let returnD = [] for (let { dataValues: p } of listRes) { // 查对应的 poms 绑定的结构物绑定关系 - const corBind = pomsProjectRes.find(ppj => ppj.id == p.pomsProjectId) - if (corBind.pepProjectId) { - if (state == 'notYet') { - if (corBind.pepProject && p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) { - continue - } - } else if (state == 'takeEffect') { - if (!corBind.pepProject || !p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) { - continue + const corBinds = pomsProjectRes.filter(ppj => p.pomsProjectId.includes(ppj.id)) + + + let filterBinds = [] + for (let corBind of corBinds) { + if (corBind.pepProjectId) { + if (state == 'notYet') { + if (corBind.pepProject && p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) { + continue + } + } else if (state == 'takeEffect') { + if (!corBind.pepProject || !p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) { + continue + } } + } else if (state == 'notYet') { + continue } + filterBinds.push(corBind) + } + if (!filterBinds.length) { + continue } // 结构物信息 @@ -129,7 +129,7 @@ async function list (ctx) { returnStruc.push({ id: sid, name: structure.name, - unbind: !anxinStrucSeen || !corBind.anxinProjectId.includes(anxinStrucSeen.projectId) + unbind: !anxinStrucSeen || corBinds.every(corBinds => !corBinds.anxinProjectId.includes(anxinStrucSeen.projectId)) }) } else { // 这个结构物已删 @@ -142,7 +142,7 @@ async function list (ctx) { const corReceiver = userRes.filter(u => p.receiverPepUserId.some(prId => u.id == prId)) returnD.push({ ...p, - pomsProject: corBind, + pomsProject: corBinds, structure: returnStruc, pushCount: corLogCount ? corLogCount.dataValues.count : 0, receiverPepUser: corReceiver @@ -165,11 +165,11 @@ async function edit (ctx) { const models = ctx.fs.dc.models; const { userId, pepUserId } = ctx.fs.api const { pushId, name, pomsProjectId, alarmType = [], receiverPepUserId = [], timeType = [], disable, - strucId = [], tactics, tacticsParams } = ctx.request.body + strucId = [], tactics, tacticsParams, alarmSubType = {}, pomsStrucFactorId = {} } = ctx.request.body let storageData = { name, pomsProjectId, alarmType, receiverPepUserId, timeType, disable, - strucId, tactics, tacticsParams + strucId, tactics, tacticsParams, alarmSubType, pomsStrucFactorId } let repeatOption = { diff --git a/api/app/lib/index.js b/api/app/lib/index.js index dcc655c..84559e9 100644 --- a/api/app/lib/index.js +++ b/api/app/lib/index.js @@ -77,8 +77,8 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq AppAlarm.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); App.hasMany(AppAlarm, { foreignKey: 'projectAppId', sourceKey: 'id' }); - AlarmPushConfig.belongsTo(ProjectCorrelation, { foreignKey: 'pomsProjectId', targetKey: 'id' }); - ProjectCorrelation.hasMany(AlarmPushConfig, { foreignKey: 'pomsProjectId', sourceKey: 'id' }); + // AlarmPushConfig.belongsTo(ProjectCorrelation, { foreignKey: 'pomsProjectId', targetKey: 'id' }); + // ProjectCorrelation.hasMany(AlarmPushConfig, { foreignKey: 'pomsProjectId', sourceKey: 'id' }); AlarmAppearRecord.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); ProjectCorrelation.hasMany(AlarmAppearRecord, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); diff --git a/api/app/lib/models/alarm_push_config.js b/api/app/lib/models/alarm_push_config.js index 7ea7a19..515e635 100644 --- a/api/app/lib/models/alarm_push_config.js +++ b/api/app/lib/models/alarm_push_config.js @@ -25,7 +25,7 @@ module.exports = dc => { autoIncrement: false }, pomsProjectId: { - type: DataTypes.INTEGER, + type: DataTypes.ARRAY(DataTypes.INTEGER), allowNull: false, defaultValue: null, comment: null, @@ -42,6 +42,24 @@ module.exports = dc => { field: "alarm_type", autoIncrement: false }, + alarmSubType: { + type: DataTypes.JSONB, + allowNull: true, + defaultValue: null, + comment: "监听的告警类型的子类id的关联", + primaryKey: false, + field: "alarm_sub_type", + autoIncrement: false + }, + pomsStrucFactorId: { + type: DataTypes.JSONB, + allowNull: true, + defaultValue: null, + comment: "结构物与监测项id的关联", + primaryKey: false, + field: "poms_struc_factor_id", + autoIncrement: false + }, receiverPepUserId: { type: DataTypes.ARRAY(DataTypes.INTEGER), allowNull: true, diff --git a/api/app/lib/models/email_send_log.js b/api/app/lib/models/email_send_log.js index e1d95b6..da0a97f 100644 --- a/api/app/lib/models/email_send_log.js +++ b/api/app/lib/models/email_send_log.js @@ -53,7 +53,7 @@ module.exports = dc => { autoIncrement: false }, projectCorrelationId: { - type: DataTypes.INTEGER, + type: DataTypes.ARRAY(DataTypes.INTEGER), allowNull: false, defaultValue: null, comment: null, diff --git a/api/app/lib/models/latest_dynamic_list.js b/api/app/lib/models/latest_dynamic_list.js index 5b53051..320191a 100644 --- a/api/app/lib/models/latest_dynamic_list.js +++ b/api/app/lib/models/latest_dynamic_list.js @@ -54,7 +54,7 @@ module.exports = dc => { }, projectCorrelationId: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, defaultValue: null, comment: null, primaryKey: false, diff --git a/api/app/lib/schedule/alarms_push.js b/api/app/lib/schedule/alarms_push.js index 57f8736..6c4d851 100644 --- a/api/app/lib/schedule/alarms_push.js +++ b/api/app/lib/schedule/alarms_push.js @@ -1,15 +1,22 @@ 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: true, // dev - proRun: true, + 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 @@ -20,18 +27,22 @@ module.exports = function (app, opts) { disable: false }, order: ['id'], - include: [{ - model: models.ProjectCorrelation, - where: { - del: false, - }, - required: true - }], }) + let pomsProjectId = new Set() let pepProjectIds = new Set() for (let { dataValues: c } of configListRes) { - if (c.projectCorrelation.pepProjectId) { - pepProjectIds.add(c.projectCorrelation.pepProjectId) + 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 ? @@ -55,34 +66,53 @@ module.exports = function (app, opts) { for (let { dataValues: c } of configListRes) { if (c.tacticsParams && c.tactics) { - const { projectCorrelation, strucId, pomsProjectId, } = c + 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 + curMinOfYear % parseInt(interval) == 0 || isDev ) { - const corPepProject = projectCorrelation.pepProjectId ? - pepProjectRes.find(p => p.id == projectCorrelation.pepProjectId) - : null - if ( - !projectCorrelation.pepProjectId - || ( - corPepProject - && c.timeType.some(ct => ct == corPepProject.construction_status_id) - ) - ) { - const { anxinProjectId, pepProjectId } = projectCorrelation - // 查当前 poms 下的结构物 并把不包含的去掉 - // 可能有结构物已解绑 - const strucListRes = strucId.length && anxinProjectId.length ? - await clickHouse.anxinyun.query( - ` + 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_structure.iota_thing_id AS iotaThingId, + t_project.id AS projectId FROM t_project LEFT JOIN @@ -109,118 +139,142 @@ module.exports = function (app, opts) { project_state != -1 AND t_project.id IN (${anxinProjectId.join(',')}) AND t_structure.id IN (${strucId.join(',')}) - ` - ).toPromise() : - [] - let strucThingId = [] - let searchStrucIds = strucListRes.map(s => { - if (s.iotaThingId) { - strucThingId.push(s.iotaThingId) - } - return s.id - }) + ).toPromise() : + [] + let strucThingId = [] + let strucMap = {} + let searchStrucIds = strucListRes.map(s => { + if (s.iotaThingId) { + strucThingId.push(s.iotaThingId) + } + strucMap[s.id] = { + ...s + } + return s.id + }) - // 开发测试用的数据 - // searchStrucIds = searchStrucIds.concat([991, 1052, 700]) - - 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 = { - 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(` + // !开发测试用的数据 + 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(` + : [] + 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)}个的 --%,详情如下` + : 0 } + emailTitle += `异常率推送服务` + emailSubTitle += `持续产生时间超过${interval}分钟的异常设备数量${interval}个,异常率达到项目或结构物内设备总数量${parseInt(deviceCount) + parseInt(cameraCount)}个的 --%,详情如下` } - emailTitle += '——POMS飞尚运维中台' + } + emailTitle += '——POMS飞尚运维中台' - // 判断告警数据范围 - if (c.alarmType.includes('data_outages')) { - dataAlarmGroupOption.push(1) - } - if (c.alarmType.includes('data_exception')) { - dataAlarmGroupOption.push(2) - } - if (c.alarmType.includes('strategy_hit')) { - dataAlarmGroupOption.push(3) + // 判断告警数据范围 + 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) } - if (c.alarmType.includes('video_exception')) { - videoAlarms = searchStrucIds.length ? await clickHouse.vcmp.query( - ` + videoAlarms = + searchStrucIds.length && ( + !c.alarmSubType || videoAlarmSubType.length > 0 + ) ? + await clickHouse.vcmp.query( + ` SELECT cameraAlarm.cameraId AS cameraId, cameraAlarm.cameraName AS cameraName, @@ -237,6 +291,7 @@ module.exports = function (app, opts) { 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 ( @@ -263,6 +318,7 @@ module.exports = function (app, opts) { 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 ')}` : ''} @@ -286,74 +342,93 @@ module.exports = function (app, opts) { 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 { - 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, - struc: [], - station: [] - } + ).toPromise() : [] - 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 - } + 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 } } - let p = 1 - videoAlarms = returnD } - if (c.alarmType.includes('app_exception')) { - appAlarms = await models.AppAlarm.findAll({ + 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: [{ @@ -362,259 +437,364 @@ module.exports = function (app, opts) { include: [{ model: models.ProjectApp, where: { - projectId: pomsProjectId + projectId: { $in: pomsProjectId } }, required: true, }] }] - }) - } - if (c.alarmType.includes('device_exception')) { - dataAlarmGroupOption.push(4) - dataAlarmGroupOption.push(5) - } + }) : [] + } + 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(',')})`) - dataAlarms = await clickHouse.dataAlarm.query(` + // 查数据告警 三警合一 + 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() - console.log(dataAlarms); - } - - let dataAlarmTitle = [{ - n: '项目', - k: '', - v: pepProjectName - }, { - n: '结构物', - k: '', - f: (d) => { - 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 ? - '超过' + 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.map(ds => ds.name).join('、') - } - }, { - n: '位置', - k: '', - f: (d) => { - return d.station.map(ds => ds.position).join('、') - } - }, { - n: '告警信息', - k: 'statusDescribe' - }, { - n: '持续时间', - k: '', - f: (d) => { - return d.createTime ? - '超过' + 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 { + `).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: 'URL地址', - k: 'cameraName', - f: (d) => { - return d.app && d.app.url ? `${d.app.url}` : '' + } + }, { + 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.createTime ? - '超过' + moment().diff(moment(d.createTime), 'minutes') + '分钟' : '' + } + }, { + n: '持续时间', + k: '', + f: (d) => { + return d.StartTime ? + '超过' + 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 ? + '超过' + 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 ? + '超过' + moment().diff(moment(d.createTime), 'minutes') + '分钟' : '' + } + },] - let ifEmailSend = false - let tableTitlePostfix = ',详情如下:' + let ifEmailSend = false + let tableTitlePostfix = ',详情如下:' - function packageTableTitle (titleArr) { - let tableTitle = '' - for (let t of titleArr) { - tableTitle += `${t.n}` + 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 += `` } - 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]}` + } + 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 { - tableData += `` + apMergeDeVeAlarms[d.StructureId] = { + data_exception: [d], + video_exception: [] + } } } - tableData += '' - return tableData - } + // 注1 END - 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++ + } 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 + } } } - tableTitlePrefix += - c.tactics == 'abnormal_rate' ? - `${alarmData.length}个` : - `新增${newAddCount}个,未解决累计${alarmData.length}个` + tableTitlePostfix - alarmHtml += `' - alarmHtml += alarmHtmlTitle - alarmHtml += alarmContent - alarmHtml += '
` + tableTitlePrefix + '

' - - return alarmHtml - } - - let dataAlarmG1 = []; - let dataAlarmG2 = []; - let dataAlarmG3 = []; - let dataAlarmG45 = []; - let deviceStatistic = new Set() - for (let d of dataAlarms) { - if (d.AlarmGroup == 1) { - dataAlarmG1.push(d) - } else if (d.AlarmGroup == 2) { - dataAlarmG2.push(d) - } else if (d.AlarmGroup == 3) { - dataAlarmG3.push(d) - } else if (d.AlarmGroup == 4 || d.AlarmGroup == 5) { - dataAlarmG45.push(d) + if (proDebug) { + console.log(`符合条件的策略命中 + 1`, d); } - deviceStatistic.add(d.SourceId) + dataAlarmG3.push(d) + } else if (d.AlarmGroup == 4 || d.AlarmGroup == 5) { + dataAlarmG45.push(d) } - if (c.tactics == 'abnormal_rate') { - let rate = ((deviceStatistic.size + videoAlarms.length) / (parseInt(deviceCount) + parseInt(cameraCount))); + deviceStatistic.add(d.SourceId) + } + if (c.tactics == 'abnormal_rate') { + let rate = ((deviceStatistic.size + videoAlarms.length) / (parseInt(deviceCount) + parseInt(cameraCount))); - if (rate < parseFloat(deviceProportion)) { - continue - } + if (rate < parseFloat(deviceProportion)) { + // 设备异常率低于设定值 + continue + } - emailSubTitle = emailSubTitle.replace('--%', rate.toFixed(1) + '%') + 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) + } } - let html = ` + } + + // 注1 END + let html = ` @@ -633,99 +813,205 @@ module.exports = function (app, opts) {
${emailSubTitle}
` - if (c.alarmType.includes('data_outages')) { - html += packageAlarmData2Table( - '数据中断', - dataAlarmG1, - dataAlarmTitle, - ) + + function packageAlarmData2Table ({ + titlePrefix, alarmData, alarmTitleArr, keyOfStartTime = 'StartTime' + }) { + if (!alarmData.length) { + return '' } - if (c.alarmType.includes('data_exception')) { - html += packageAlarmData2Table( - '数据异常', - dataAlarmG2, - dataAlarmTitle, - ) + 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++ + } } - if (c.alarmType.includes('strategy_hit')) { - html += packageAlarmData2Table( - '策略命中', - dataAlarmG3, - dataAlarmTitle, - ) + 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); } - if (c.alarmType.includes('video_exception')) { - html += packageAlarmData2Table( - '视频异常', - videoAlarms, - videoAlarmTitle, - 'createTime', - ) + 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 (c.alarmType.includes('app_exception')) { - html += packageAlarmData2Table( - '应用异常', - appAlarms, - appAlarmTitle, - 'createTime', - ) + if (proDebug) { + console.log(`相关设备及测点信息:`); + console.log(deviceSensorRes); } - if (c.alarmType.includes('device_exception')) { - html += packageAlarmData2Table( - '设备异常', - dataAlarmG45, - dataAlarmTitle, - ) + 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 + }) + } + } } - if (ifEmailSend) { - // 查接收人的信息 - const receiverRes = - c.receiverPepUserId.length ? - await clickHouse.pepEmis.query(` + 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 = [] - const emails = receiverRes.reduce((arr, r) => { - if (r.email) { - arr.push(r.email) - receiverId.push({ id: r.id, name: r.name }) - } - return arr - }, []) - if (emails.length) { - await pushByEmail({ - email: emails, - title: emailTitle, - text: '', - html: html - }) + : [] + 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 + }, []) - //存日志 存动态 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 }) + if (isDev) { + // !开发测试用的数据 + emails = ['1650192445@qq.com'] + } - let dynamic = { - time: r.dataValues.time, - emailSendId: r.dataValues.id, - projectCorrelationId: r.dataValues.projectCorrelationId, - type: 2//通知 - } - await models.LatestDynamicList.create(dynamic); + 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 }) - //消息推送到前端 - await sendNoticeToWeb(receiverId, dataToSave); + 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); } } } diff --git a/api/app/lib/utils/alarmHandle.js b/api/app/lib/utils/alarmHandle.js index cdc0807..a9fdf5a 100644 --- a/api/app/lib/utils/alarmHandle.js +++ b/api/app/lib/utils/alarmHandle.js @@ -110,7 +110,7 @@ module.exports = function (app, opts) { } let pepProjects = pepPojectIds.size ? await clickHouse.projectManage.query(` - SELECT id, project_name FROM t_pim_project WHERE id IN (${[...pepPojectIds]}, -1)` + SELECT id, project_name FROM t_pim_project WHERE id IN (${[...pepPojectIds].join(',')}, -1)` ).toPromise() : []; return { projects, pepProjects }; 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 }, diff --git a/jenkinsfile_poms_api b/jenkinsfile_poms_api index c9291bd..f6e2ce8 100644 --- a/jenkinsfile_poms_api +++ b/jenkinsfile_poms_api @@ -1,19 +1,19 @@ pipeline { agent { - node{ + node{ label 'jnlp-slave' - } + } } stages { - stage('Testing poms ......') { + stage('Testing site......') { steps { - sh 'switch-auth.sh anxinyun' - buildName "#${BUILD_NUMBER} ~/fs-cloud/${JOB_NAME}:${IMAGE_VERSION}" - buildDescription "registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}" - sh 'docker build -t registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION} ./api' - sh 'docker push registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}' + buildName "#${BUILD_NUMBER} ~/iot/${JOB_NAME}:${IMAGE_VERSION}" + buildDescription "harbor.anxinyun.cn/iot/${JOB_NAME}:${IMAGE_VERSION}" + sh 'nerdctl build -t harbor.anxinyun.cn/iot/${JOB_NAME}:${IMAGE_VERSION} ./api' + sh 'nerdctl push harbor.anxinyun.cn/iot/${JOB_NAME}:${IMAGE_VERSION}' + } } - } + } } \ No newline at end of file diff --git a/jenkinsfile_poms_web b/jenkinsfile_poms_web index 9d43c6e..a30d41e 100644 --- a/jenkinsfile_poms_web +++ b/jenkinsfile_poms_web @@ -2,18 +2,18 @@ pipeline { agent { node{ label 'jnlp-slave' - } + } } stages { - stage('Testing poms ......') { + stage('Testing site......') { steps { - sh 'switch-auth.sh anxinyun' - buildName "#${BUILD_NUMBER} ~/fs-cloud/${JOB_NAME}:${IMAGE_VERSION}" - buildDescription "registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}" - sh 'docker build -t registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION} ./web' - sh 'docker push registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}' + buildName "#${BUILD_NUMBER} ~/iot/${JOB_NAME}:${IMAGE_VERSION}" + buildDescription "harbor.anxinyun.cn/iot/${JOB_NAME}:${IMAGE_VERSION}" + sh 'nerdctl build -t harbor.anxinyun.cn/iot/${JOB_NAME}:${IMAGE_VERSION} ./web' + sh 'nerdctl push harbor.anxinyun.cn/iot/${JOB_NAME}:${IMAGE_VERSION}' + } } - } + } } \ No newline at end of file diff --git a/script/0.10/schema/1.alter_alarm_push_config.sql b/script/0.10/schema/1.alter_alarm_push_config.sql new file mode 100644 index 0000000..7a59716 --- /dev/null +++ b/script/0.10/schema/1.alter_alarm_push_config.sql @@ -0,0 +1,16 @@ +alter table alarm_push_config + add alarm_sub_type jsonb; + +comment on column alarm_push_config.alarm_sub_type is '存对应监听模块(alarm_type)的子类( + 数据告警:alarms.AlarmGroupUnit, + 视频异常:camera.kind_id, + 应用异常:app_alarm.errType +)'; + + +ALTER TABLE alarm_push_config ALTER COLUMN poms_project_id TYPE INTEGER [] + USING CASE + WHEN poms_project_id is NULL + then NULL + ELSE array [poms_project_id] + END ::INTEGER []; \ No newline at end of file diff --git a/script/0.11/schema/1.alter_alarm_push_config.sql b/script/0.11/schema/1.alter_alarm_push_config.sql new file mode 100644 index 0000000..b48458e --- /dev/null +++ b/script/0.11/schema/1.alter_alarm_push_config.sql @@ -0,0 +1,4 @@ +alter table alarm_push_config + add poms_struc_factor_id jsonb; + +comment on column alarm_push_config.poms_struc_factor_id is '结构物对应监测项 id'; \ No newline at end of file diff --git a/script/0.11/schema/2.alter_email_send_log.sql b/script/0.11/schema/2.alter_email_send_log.sql new file mode 100644 index 0000000..b6ea2b4 --- /dev/null +++ b/script/0.11/schema/2.alter_email_send_log.sql @@ -0,0 +1,6 @@ +ALTER TABLE email_send_log ALTER COLUMN project_correlation_id TYPE INTEGER [] + USING CASE + WHEN project_correlation_id is NULL + then NULL + ELSE array [project_correlation_id] + END ::INTEGER []; \ No newline at end of file diff --git a/script/0.11/schema/3.alert_latest_dynamic_list.sql b/script/0.11/schema/3.alert_latest_dynamic_list.sql new file mode 100644 index 0000000..13bb8f4 --- /dev/null +++ b/script/0.11/schema/3.alert_latest_dynamic_list.sql @@ -0,0 +1 @@ +alter table latest_dynamic_list alter column project_correlation_id drop not null; \ No newline at end of file diff --git a/web/Dockerfile b/web/Dockerfile index 3991885..0ef1cde 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,40 +1,40 @@ -# FROM repository.anxinyun.cn/devops/node:12-dev as builder +FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12-dev as builder -# COPY . /var/app +COPY . /var/app -# WORKDIR /var/app +WORKDIR /var/app -# EXPOSE 8080 +EXPOSE 8080 -# RUN npm config set registry=http://10.8.30.22:7000 -# RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json -# RUN npm cache clean -f -# RUN npm install --registry http://10.8.30.22:7000 -# RUN npm run build -# RUN rm -rf client/src -# RUN rm -rf node_modules -# RUN npm install --production --registry http://10.8.30.22:7000 +RUN npm config set registry=http://10.8.30.22:7000 +RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json +RUN npm cache clean -f +RUN npm install --registry http://10.8.30.22:7000 +RUN npm run build +RUN rm -rf client/src +RUN rm -rf node_modules +RUN npm install --production --registry http://10.8.30.22:7000 -# FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node-16:7.22-06-20 +FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node-16:7.22-06-20 -# COPY --from=builder --chown=node /var/app /home/node/app +COPY --from=builder --chown=node /var/app /home/node/app -# WORKDIR /home/node/app +WORKDIR /home/node/app -# CMD ["node", "server.js"] +CMD ["node", "server.js"] # 旧版本构建方式 -FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2 +# FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2 -COPY . /var/app +# COPY . /var/app -WORKDIR /var/app +# WORKDIR /var/app -EXPOSE 8080 +# EXPOSE 8080 -CMD ["-u", "http://localhost:8088"] +# CMD ["-u", "http://localhost:8088"] -ENTRYPOINT [ "node", "server.js" ] \ No newline at end of file +# ENTRYPOINT [ "node", "server.js" ] \ No newline at end of file diff --git a/web/client/assets/images/background/B.png b/web/client/assets/images/background/B.png new file mode 100644 index 0000000..2f4b365 Binary files /dev/null and b/web/client/assets/images/background/B.png differ diff --git a/web/client/assets/images/background/General.png b/web/client/assets/images/background/General.png new file mode 100644 index 0000000..b81a580 Binary files /dev/null and b/web/client/assets/images/background/General.png differ diff --git a/web/client/index.html b/web/client/index.html index 302a7e4..3ee9392 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -2,26 +2,26 @@ - - + + - + - - + + - - + + - -
+ +
- - + + - - + - + - + \ No newline at end of file diff --git a/web/client/src/layout/actions/global.js b/web/client/src/layout/actions/global.js index 015d0c5..cf8082d 100644 --- a/web/client/src/layout/actions/global.js +++ b/web/client/src/layout/actions/global.js @@ -39,6 +39,8 @@ export function initApiRoot () { payload: { apiRoot: res.root, iotVcmpWeb:res.iotVcmpWeb, + pomsMonitor:res.pomsMonitor, + dcWeb:res.dcWeb, } }) }); diff --git a/web/client/src/layout/reducers/global.js b/web/client/src/layout/reducers/global.js index 2590521..2b16eef 100644 --- a/web/client/src/layout/reducers/global.js +++ b/web/client/src/layout/reducers/global.js @@ -12,6 +12,8 @@ function global (state = { clientWidth: 1024, apiRoot: '', iotVcmpWeb: '', + pomsMonitor:'', + dcWeb:'', }, action) { const payload = action.payload; switch (action.type) { @@ -31,7 +33,9 @@ function global (state = { case INIT_API_ROOT: return Immutable.fromJS(state).merge({ apiRoot: payload.apiRoot, - iotVcmpWeb: payload.iotVcmpWeb + iotVcmpWeb: payload.iotVcmpWeb, + pomsMonitor:payload.pomsMonitor, + dcWeb:payload.dcWeb, }).toJS(); case PEPPROJECTID: return Immutable.fromJS(state).merge({ diff --git a/web/client/src/sections/control/containers/control.jsx b/web/client/src/sections/control/containers/control.jsx index c4857d2..acba920 100644 --- a/web/client/src/sections/control/containers/control.jsx +++ b/web/client/src/sections/control/containers/control.jsx @@ -8,7 +8,6 @@ import repairFQA from '../../means/containers/repairFQA'; import { Setup, OutHidden } from "$components"; import ReactECharts from 'echarts-for-react'; import moment from "moment"; -import { log } from 'ezuikit-js'; let newScrollbar; let overviewScrollbar; @@ -43,7 +42,7 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const [videoBI, setVideoBI] = useState([]); //查询BI分析数据-视频 const [appBI, setAppBI] = useState([]); //查询BI分析数据-应用 const [efficiencyBI, setEfficiencyBI] = useState({}); //查询BI分析数据-问题处置 - const [query, setQuery] = useState({ limit: 10, page: 0, projectCorrelationId: '', types: '1' }); //最新动态 + const [query, setQuery] = useState({ limit: 23, page: 0, projectCorrelationId: '', types: '1' }); //最新动态 const [querydata1, setQueryData1] = useState([]); //最新动态数据 const [long, setLong] = useState(''); //最新动态设置 const [pomsList, setPomsList] = useState([]); //项目 @@ -184,7 +183,7 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject newest.sort((a, b) => (moment(a.time).isBefore(b.time) ? 1 : -1)) querydata.current = [...newest, ...querydata.current] setQueryData1([...querydata.current]) - + } }, [socketData]) useEffect(() => { @@ -398,6 +397,7 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject })) } if (exhibition?.current?.dynamic?.find(v => v.key == 'notice')) { + // EM 推送 res.payload.data?.notice?.map(v => data.push({ seed: 'notice', time: v.time, @@ -444,14 +444,26 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const line = document.getElementById("line") const news = document.getElementById("news") if (line && news) { - news.onscroll = () => { + news.onscroll = (e) => { + e.stopPropagation(); if ((line.clientHeight - 578) < news.scrollTop + 10) { setQuery({ ...query, page: query.page + 1 }) if (exhibition?.current?.dynamic?.length > 0) { dispatch(control.getLatestDynamic({ ...query, projectCorrelationId: pepProjectId, page: query.page + 1 })).then(res => { - news.scrollTop = news.scrollTop - 640 + // news.scrollTop = news.scrollTop - 640 let data = querydata.current if (res.success) { + let returnJudge = true + for (let k in res.payload.data) { + if (res.payload.data[k].length) { + returnJudge = false + break + } + } + if (returnJudge) { + return + } + if (exhibition?.current?.dynamic?.find(v => v.key == 'discovery')) { res.payload.data?.appear?.map(v => data.push({ seed: 'discovery', @@ -503,9 +515,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject = document.getElementById("news"); if (domProject) { - // newScrollbar = new PerfectScrollbar("#news", { - // suppressScrollX: true, - // }); if (domProject && newScrollbar) { newScrollbar.update(); } @@ -513,9 +522,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const pomsList = document.getElementById("pomsList"); if (pomsList) { - // pomsListScrollbar = new PerfectScrollbar("#pomsList", { - // suppressScrollX: true, - // }); if (pomsList && pomsListScrollbar) { pomsListScrollbar.update(); } @@ -523,9 +529,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject1 = document.getElementById("overviewCalc"); if (domProject1) { - // overviewScrollbar = new PerfectScrollbar("#overviewCalc", { - // suppressScrollY: true, - // }); if (domProject1 && overviewScrollbar) { overviewScrollbar.update(); } @@ -533,9 +536,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject2 = document.getElementById("member"); if (domProject2) { - // memberScrollbar = new PerfectScrollbar("#member", { - // suppressScrollX: true, - // }); if (domProject2 && memberScrollbar) { memberScrollbar.update(); } @@ -543,9 +543,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject3 = document.getElementById("equipment"); if (domProject3) { - // equipmentScrollbar = new PerfectScrollbar("#equipment", { - // suppressScrollX: true, - // }); if (domProject3 && equipmentScrollbar) { equipmentScrollbar.update(); } @@ -553,9 +550,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject4 = document.getElementById("web"); if (domProject4) { - // webScrollbar = new PerfectScrollbar("#web", { - // suppressScrollX: true, - // }) if (domProject4 && webScrollbar) { webScrollbar.update(); } @@ -563,9 +557,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject5 = document.getElementById("problems"); if (domProject5) { - // problemsScrollbar = new PerfectScrollbar("#problems", { - // suppressScrollX: true, - // }); if (domProject5 && problemsScrollbar) { problemsScrollbar.update(); } @@ -573,17 +564,11 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject const domProject6 = document.getElementById("alarm"); if (domProject6) { - // alarmScrollbar = new PerfectScrollbar("#alarm", { - // suppressScrollY: true, - // }); if (domProject6 && alarmScrollbar) { alarmScrollbar.update(); } } - - // ACTION 示例 - // dispatch(actions.example.getMembers(user.orgId)) }) const consoleToollink = () => { @@ -625,8 +610,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject - - let Select = { overall: ['workbench', 'statistical', 'analyse', 'dynamic', 'tool'], workbench: ['project', 'data', 'app', 'device'], @@ -672,8 +655,6 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject { name: 'BI分析模块', sort: 3, key: 'analyse', }, { name: '最新动态', sort: 4, key: 'dynamic', }, { name: '我常用的工具', sort: 5, key: 'tool', }, - - ] useEffect(() => { @@ -1245,8 +1226,8 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject }} /> -
-
+
+
{querydata.current?.map((v, index) => { let title = '' @@ -1256,8 +1237,13 @@ const Control = ({ dispatch, actions, user, history, loading, socket, pepProject title = v.userName ? (v.userName + '确认并关闭' + v.project + '【' + v.sources + '】' + v.type + '的问题') : v.project + '【' + v.sources + '】' + v.type + '已恢复' } else { - title = '【信鸽-' + v.alarmPushConfig + '】已邮件通知' + - v.userName?.map((u, i) => (i > 0 ? ',' + u : u)) + '【' + v.project + '】【' + + title = + '【信鸽-' + v.alarmPushConfig + '】已邮件通知' + + + v.userName?.map((u, i) => (i > 0 ? ',' + u : u)) + + '【' + v.project + + '】【' + + (v.tactics == 'immediately' ? '发现在' + v.interval + '分钟内,有告警源新增' : (v.tactics == 'continue' ? '告警源持续产生时间超过' + v.interval + '分钟' : '异常设备数量达到项目或结构物内设备总数量的' + diff --git a/web/client/src/sections/control/containers/userCenter.jsx b/web/client/src/sections/control/containers/userCenter.jsx index 11cc71e..e9e0958 100644 --- a/web/client/src/sections/control/containers/userCenter.jsx +++ b/web/client/src/sections/control/containers/userCenter.jsx @@ -16,10 +16,10 @@ const UserCenter = (props) => {
- HI,欢迎回来,行业服务部 + HI,欢迎回来,{user?.department[0]?.name}
- 刘昊然! + {user?.name}!
diff --git a/web/client/src/sections/facility/containers/monitor.jsx b/web/client/src/sections/facility/containers/monitor.jsx index 8abf95c..73628d6 100644 --- a/web/client/src/sections/facility/containers/monitor.jsx +++ b/web/client/src/sections/facility/containers/monitor.jsx @@ -4,7 +4,7 @@ import { connect } from 'react-redux'; const Server = (props) => { - const { dispatch, actions, user, loading, socket } = props + const { dispatch, actions, user, loading, socket, pomsMonitor } = props useEffect(() => { @@ -13,13 +13,32 @@ const Server = (props) => { return ( <> -
- +
+
+ +
+ 点击可跳转服务器监控网站Grafana
+
+ {/*