diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index f9f2031..f87c4f7 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -19,7 +19,7 @@ // "-g postgres://postgres:123@10.8.30.32:5432/orational_service", // 测试 "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", - "-k node35:6667,node36:6667,node37:6667", + "-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", @@ -61,6 +61,7 @@ "--clickHouseProjectManage peppm8", "--clickHouseVcmp video_access_dev", "--clickHouseDataAlarm default", + "--clickHouseIot iot", "--confirmAlarmAnxinUserId 1", "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", diff --git a/api/app/lib/controllers/alarm/alarmConfirmLog.js b/api/app/lib/controllers/alarm/alarmConfirmLog.js index 378c584..1e67018 100644 --- a/api/app/lib/controllers/alarm/alarmConfirmLog.js +++ b/api/app/lib/controllers/alarm/alarmConfirmLog.js @@ -5,6 +5,7 @@ const moment = require('moment') async function alarmConfirmLog(ctx, confirmPost, content) { try { const { models } = ctx.fs.dc; + const { utils: { sendConfirmToWeb } } = ctx.app.fs //存日志 let logDatas = []; confirmPost.map(cp => { @@ -33,9 +34,9 @@ async function alarmConfirmLog(ctx, confirmPost, content) { await models.LatestDynamicList.bulkCreate(dynamics); //消息推送到前端 - //ctx.app.socket.emit('TEST', { someProperty: '【广播】呼叫青铜时代号!!!', }) - - + if (logDatas.length) { + await sendConfirmToWeb(logDatas, false); + } } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; diff --git a/api/app/lib/controllers/alarm/app.js b/api/app/lib/controllers/alarm/app.js index 4472c04..54b9b9e 100644 --- a/api/app/lib/controllers/alarm/app.js +++ b/api/app/lib/controllers/alarm/app.js @@ -143,6 +143,7 @@ async function notedInspection(ctx) { async function apiError(ctx) { try { + const { utils: { sendAppearToWeb } } = ctx.app.fs const models = ctx.fs.dc.models; const { projectAppId, alarmContent, router, statusCode, screenshot = '', type } = ctx.request.body const now = moment().format() @@ -198,6 +199,46 @@ async function apiError(ctx) { storageData.createTime = now storageData.screenshot = screenshot await models.AppAlarm.create(storageData) + + //存告警记录 + let constTypes = { 'element': "元素异常", 'apiError': "接口报错 ", 'timeout': "加载超时" } + let belongsTo = await models.ProjectApp.findOne({ + where: { + id: projectAppId + }, + include: [{ + model: models.ProjectCorrelation, + attributes: ['id'], + where: { del: false } + }] + }) + if (belongsTo) { + let appName = await models.App.findOne({ + where: { + id: belongsTo.appId + }, + attributes: ['name'], + }) + let pId = belongsTo.projectCorrelation.dataValues.id;//归属项目 + let data = { + projectCorrelationId: pId, + alarmInfo: { messageMode: 'AlarmGeneration', sourceName: appName.name, content: alarmContent, type },//AlarmGeneration代表告警首次产生 + time: now, + type: constTypes[type] + } + let r = await models.AlarmAppearRecord.create(data, { returning: true }); + let dynamic = { + time: r.dataValues.time, + alarmAppearId: r.dataValues.id, + projectCorrelationId: r.dataValues.projectCorrelationId, + type: 1//发现 + } + await models.LatestDynamicList.create(dynamic); + + + //消息推送到前端 + await sendAppearToWeb([data], 'app'); + } } } diff --git a/api/app/lib/controllers/alarm/video.js b/api/app/lib/controllers/alarm/video.js index 46fa882..a7084a1 100644 --- a/api/app/lib/controllers/alarm/video.js +++ b/api/app/lib/controllers/alarm/video.js @@ -1,7 +1,7 @@ 'use strict'; const moment = require('moment') const { alarmConfirmLog } = require('./alarmConfirmLog'); -async function deviceType (ctx) { +async function deviceType(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -21,7 +21,7 @@ async function deviceType (ctx) { } } -async function alarmList (ctx) { +async function alarmList(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -262,7 +262,7 @@ async function alarmList (ctx) { } } -async function confirm (ctx) { +async function confirm(ctx) { try { const { alarmId, content, confirmPost } = ctx.request.body; // TODO: 以视频·应用的秘钥进行鉴权 @@ -284,7 +284,57 @@ async function confirm (ctx) { } } -async function vcmpAppAuthToken (ctx) { +async function alarmAdded(ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { utils: { anxinStrucIdRange, sendAppearToWeb } } = ctx.app.fs + let anxinStruc = await anxinStrucIdRange({ ctx }) + + const { serial_no, channel_no, create_time, description, status_id } = ctx.request.body; + + let belongToStruct = await clickHouse.anxinyun.query( + `SELECT name, structure FROM t_video_ipc WHERE serial_no='${serial_no}' and channel_no='${channel_no}'`).toPromise() + let structId = belongToStruct.length ? belongToStruct[0].structure : null + + if (structId) { + let exist = anxinStruc.find(s => s.strucId == structId); + let projects = exist.pomsProject.filter(d => !d.del).map(p => p.id); + let datas = projects.map(d => {//需要 项目,告警源,异常类型,时间 + return { + projectCorrelationId: d, + alarmInfo: { messageMode: 'AlarmGeneration', sourceName: belongToStruct[0].name, status_id, content: description },//AlarmGeneration代表告警首次产生 + time: create_time, + type: description + } + }) + let rslt = await models.AlarmAppearRecord.bulkCreate(datas, { returning: true }); + let dynamics = rslt.map(r => { + return { + time: r.time, + alarmAppearId: r.id, + projectCorrelationId: r.projectCorrelationId, + type: 1//发现 + } + }) + await models.LatestDynamicList.bulkCreate(dynamics); + + //消息推送到前端 + if (datas.length) { + await sendAppearToWeb(datas, 'video'); + } + } + ctx.status = 200; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function vcmpAppAuthToken(ctx) { try { const { models } = ctx.fs.dc; const { utils: { vcmpAuth } } = ctx.app.fs @@ -306,5 +356,6 @@ module.exports = { deviceType, alarmList, confirm, + alarmAdded, vcmpAppAuthToken }; \ No newline at end of file diff --git a/api/app/lib/routes/alarm/index.js b/api/app/lib/routes/alarm/index.js index 9cfcbdd..cfba9d0 100644 --- a/api/app/lib/routes/alarm/index.js +++ b/api/app/lib/routes/alarm/index.js @@ -55,6 +55,9 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['PUT/alarm/video/confirm'] = { content: '确认视频告警信息', visible: true }; router.put('/alarm/video/confirm', videoAlarm.confirm); + app.fs.api.logAttr['POST/alarm/video/added_log'] = { content: '新增视频告警记录', visible: true }; + router.post('/alarm/video/added_log', videoAlarm.alarmAdded); + app.fs.api.logAttr['GET/vcmp/auth'] = { content: '获取视频平台应用鉴权token', visible: true }; router.get('/vcmp/auth', videoAlarm.vcmpAppAuthToken); }; diff --git a/api/app/lib/schedule/alarms_push.js b/api/app/lib/schedule/alarms_push.js index eec2f63..9b2c556 100644 --- a/api/app/lib/schedule/alarms_push.js +++ b/api/app/lib/schedule/alarms_push.js @@ -4,7 +4,7 @@ module.exports = function (app, opts) { const alarmsPush = app.fs.scheduleInit( { interval: '12 */1 * * * *', - immediate: true, // dev + // immediate: true, // dev proRun: true, }, async () => { @@ -12,19 +12,20 @@ module.exports = function (app, opts) { const { models, ORM: sequelize } = app.fs.dc const { clickHouse } = app.fs const { database: anxinyun } = clickHouse.anxinyun.opts.config - const { pushBySms, pushByEmail } = app.fs.utils + const { pushBySms, pushByEmail, sendNoticeToWeb } = app.fs.utils const curMinOfYear = moment().diff(moment().startOf('year'), 'minutes') const configListRes = await models.AlarmPushConfig.findAll({ where: { del: false }, + order: ['id'], include: [{ model: models.ProjectCorrelation, where: { del: false }, required: true - }] + }], }) let pepProjectIds = new Set() for (let { dataValues: c } of configListRes) { @@ -111,7 +112,17 @@ module.exports = function (app, opts) { ` ).toPromise() : [] - let searchStrucIds = strucListRes.map(s => s.id) + let strucThingId = [] + let searchStrucIds = strucListRes.map(s => { + if (s.iotaThingId) { + strucThingId.push(s.iotaThingId) + } + return s.id + }) + + // 开发测试用的数据 + // searchStrucIds = searchStrucIds.concat([991, 1052, 700]) + if (searchStrucIds.length) { searchStrucIds.unshift(-1) } @@ -139,42 +150,60 @@ module.exports = function (app, opts) { let alarmCameraCount = 0 // 判断推送策略 let nowTime = moment().startOf('minute') - let pointTime = moment().subtract(parseInt(interval), 'minute').startOf('minute').format('YYYY-MM-DD HH:mm:ss') + 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') { - // dataAlarmOption.push(`StartTime <= '${pointTime}'`); - // appAlarmWhereOption.createTime = { $lte: pointTime } - // videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) - emailTitle += `持续时长推送服务` - emailSubTitle += `告警持续时长超${interval}分钟的告警源,详情如下:` - } else if (c.tactics == 'abnormal_rate') { - // dataAlarmOption.push(`StartTime <= '${pointTime}'`); - if (c.alarmType.includes('data_outages') || c.alarmType.includes('data_exception')) { - // 查了设备异常率 去安心云查当前项目下的设备数量 - // TODO 等同步以太数据再查 - deviceCount = 9999 - // await clickHouse.anxinyun.query(` - // SELECT count(*) FROM - // `).toPromise() - } - 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 + } 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)}个的 --%,详情如下` } - // appAlarmWhereOption.createTime = { $lte: pointTime } - // videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) - emailTitle += `异常率推送服务` - emailSubTitle += `持续产生时间超过${interval}分钟的异常设备数量${interval}个,异常率达到项目或结构物内设备总数量${deviceCount + cameraCount}个的${interval}%,详情如下` } emailTitle += '——POMS飞尚运维中台' @@ -202,6 +231,7 @@ module.exports = function (app, opts) { 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, @@ -213,6 +243,7 @@ module.exports = function (app, opts) { 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, @@ -240,7 +271,8 @@ module.exports = function (app, opts) { 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 @@ -251,6 +283,7 @@ module.exports = function (app, opts) { ON anxinIpcStation.ipc = anxinIpc.id LEFT JOIN ${anxinyun}.t_sensor AS anxinStation ON anxinStation.id = anxinIpcStation.station + ORDER BY cameraAlarm.createTime DESC ` ).toPromise() : [] @@ -290,6 +323,7 @@ module.exports = function (app, opts) { alarmId: a.alarmId, confirmContent: a.confirmContent, confirmTime: a.confirmTime, + cameraKind: a.cameraKind, struc: [], station: [] } @@ -315,10 +349,12 @@ module.exports = function (app, opts) { } } let p = 1 + videoAlarms = returnD } if (c.alarmType.includes('app_exception')) { appAlarms = await models.AppAlarm.findAll({ where: appAlarmWhereOption, + order: [['createTime', 'DESC']], include: [{ model: models.App, include: [{ @@ -329,7 +365,6 @@ module.exports = function (app, opts) { }] }] }) - let a = 2 } if (c.alarmType.includes('device_exception')) { dataAlarmGroupOption.push(4) @@ -343,9 +378,10 @@ module.exports = function (app, opts) { dataAlarms = await clickHouse.dataAlarm.query(` SELECT * FROM alarms WHERE - State NOT IN (3, 4) - AND StructureId IN (${searchStrucIds.join(',')}) + ${`State NOT IN (3, 4) AND `} + StructureId IN (${searchStrucIds.join(',')}) ${dataAlarmOption.length ? ' AND ' + dataAlarmOption.join(' AND ') : ''} + ORDER BY StartTime DESC `).toPromise() console.log(dataAlarms); } @@ -358,7 +394,7 @@ module.exports = function (app, opts) { n: '结构物', k: '', f: (d) => { - return (strucListRes.find(s => s.id == d.StructureId) || {}).name + return (strucListRes.find(s => s.id == d.StructureId) || { name: '' }).name } }, { n: '告警源名称', @@ -405,49 +441,289 @@ module.exports = function (app, opts) { } },] - let html = '' + 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 { + 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 = ',详情如下:' - function packageTableTitle (tArr) { + function packageTableTitle (titleArr) { + let tableTitle = '' + for (let t of titleArr) { + tableTitle += `${t.n}` + } + tableTitle += '' + return tableTitle } - if (c.alarmType.includes('data_outages')) { - let tableTitlePrefix = '数据中断告警源' + 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 + } + + function packageAlarmData2Table (titlePrefix, alarmData, alarmTitleArr, keyOfStartTime = 'StartTime') { + if (!alarmData.length) { + return '' + } + ifEmailSend = true + let tableTitlePrefix = titlePrefix + '告警源' let newAddCount = 0 - let alarmHtml = '' + let alarmHtml = '' + let alarmContent = '' + let alarmHtmlTitle = packageTableTitle(alarmTitleArr) - if (c.tactics == 'immediately') { - for (let a of dataAlarms.filter(d => d.AlarmGroup == 1)) { - alarmHtml += 1 + for (let a of alarmData) { + alarmContent += packageTableData(a, alarmTitleArr) + if (a[keyOfStartTime] && moment(a[keyOfStartTime]).isBetween(newAddStartTime, newAddEndTime)) { + newAddCount++ } - tableTitlePrefix += 1 - } else if (c.tactics == 'continue') { + } + tableTitlePrefix += + c.tactics == 'abnormal_rate' ? + `${alarmData.length}个` : + `新增${newAddCount}个,未解决累计${alarmData.length}个` + tableTitlePostfix + alarmHtml += `' + alarmHtml += alarmHtmlTitle + alarmHtml += alarmContent + alarmHtml += '
` + tableTitlePrefix + '

' - } else if (c.tactics == 'abnormal_rate') { + 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) } + deviceStatistic.add(d.SourceId) } - if (c.alarmType.includes('data_exception')) { + 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) + '%') + } + let html = ` + + + + + + +
${emailSubTitle}
+ ` + if (c.alarmType.includes('data_outages')) { + html += packageAlarmData2Table( + '数据中断', + dataAlarmG1, + dataAlarmTitle, + ) + } + if (c.alarmType.includes('data_exception')) { + html += packageAlarmData2Table( + '数据异常', + dataAlarmG2, + dataAlarmTitle, + ) } if (c.alarmType.includes('strategy_hit')) { - + html += packageAlarmData2Table( + '策略命中', + dataAlarmG3, + dataAlarmTitle, + ) } if (c.alarmType.includes('video_exception')) { - + html += packageAlarmData2Table( + '视频异常', + videoAlarms, + videoAlarmTitle, + 'createTime', + ) } if (c.alarmType.includes('app_exception')) { - + html += packageAlarmData2Table( + '应用异常', + appAlarms, + appAlarmTitle, + 'createTime', + ) } if (c.alarmType.includes('device_exception')) { - + html += packageAlarmData2Table( + '设备异常', + dataAlarmG45, + dataAlarmTitle, + ) } - await pushByEmail({ - email: ['1650192445@qq.com'], - title: emailTitle, - text: '', - html: '' - }) + 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 + }) + + //存日志 存动态 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); + } + } } } } diff --git a/api/app/lib/service/kafka.js b/api/app/lib/service/kafka.js index f164eee..5c4cb9a 100644 --- a/api/app/lib/service/kafka.js +++ b/api/app/lib/service/kafka.js @@ -1,20 +1,171 @@ 'use strict'; - +const moment = require('moment'); const Kafka = require('kafka-node'); +module.exports = async function factory(app, opts) { + try { + const client = new Kafka.KafkaClient({ kafkaHost: opts.kafka.rootURL, fromOffset: true }); + const producer = new Kafka.HighLevelProducer(client); + producer.on('error', function (err) { + app.fs.logger.log('error', "[FS-KAFKA]", err); + }); + producer.on("ready", function () { + console.log('111111 ready 666666666666') + }) + + // const kafka = { + // producer: producer, + // configUpdateMessage: opts.configUpdateMessage || {} + // }; + + // app.fs.kafka = kafka; + app.fs.logger.log('debug', "[FS-KAFKA]", "Init.Success"); + + + // const topics = [{ topic: 'anxinyun_alarm', partition: 0 }] + // const options = { + // // groupId: 'topic-test-one', + // autoCommit: false, + // //fromOffset: true, + // fromOffset: 'latest' + // } + // const consumer = new Kafka.Consumer(client, topics, options) + // consumer.on("ready", function () { + // console.log('consumer ready 666666666666') + // }) + // // consumer.on("message", function (w) { + // // console.log('consumer ready 666666666666') + // // }) + // consumer.on('message', function (message) { + // const decodedMessage = JSON.parse(message.value) + // console.log('decodedMessage: ', decodedMessage) + // }) + // consumer.on('error', function (err) { + // console.log('consumer err:') + // console.log(err); + // }); + + + let consumer = new Kafka.ConsumerGroup(Object.assign({}, { groupId: 'yunwei-platform-api', fromOffset: 'latest' }, { kafkaHost: opts.kafka.rootURL }), ['anxinyun_alarm']) + consumer.on('message', async function (message) { + let msg = JSON.parse(message.value) + console.log('kafka consumer----------接收到消息'); + await savePullAlarm(msg); + }) + // let offset = new Kafka.Offset(client); + // consumer.on('offsetOutOfRange', function (topic) { + // console.log('offsetOutOfRange') + // // consumer.setOffset('topic', 0, 0); + // // topic.maxNum = 1; + // offset.fetch([topic], function (err, offsets) { + // if (err) { + // return console.error(err); + // } + // try { + // const max = Math.max.apply(null, offsets[topic.topic][topic.partition]); + // consumer.setOffset(topic.topic, topic.partition, max); + // } catch (error) { + // console.log(error); + // } + + // }); + // }); + + let structsAche = { + dataList: [], + expireTime: null//10分钟更新一次结构物列表 + } + async function getStructsAche() { + const { utils: { getAxyStructs } } = app.fs + try { + if (!structsAche.dataList.length || moment() > moment(structsAche.expireTime)) { + let structList = await getAxyStructs(); + structsAche.dataList = structList; + structsAche.expireTime = moment().add(10, 'minute').format('YYYY-MM-DD HH:mm:ss'); + } + return structsAche; + } catch (err) { + console.log(`获取结构物列表失败, error: ${err}`); + } + } + + //保存告警[发现] + async function savePullAlarm(msg) { + const { clickHouse, utils: { sendAppearToWeb, sendConfirmToWeb } } = app.fs + try { + let { messageMode, structureId, sourceName, alarmTypeCode, alarmCode, content, time } = msg; + let structsAche = await getStructsAche(); + if (structsAche) { + let structs = structsAche.dataList;//结构物列表 + const { models } = app.fs.dc + let exist = structs.find(s => s.strucId == structureId); + if (exist) { + let alarmType = await clickHouse.anxinyun.query( + `SELECT name FROM t_alarm_type WHERE code='${alarmTypeCode}'`).toPromise() + let type = alarmType.length ? alarmType[0].name : '' + + let projects = exist.pomsProject.filter(d => !d.del).map(p => p.id); + if (messageMode == 'AlarmGeneration') {//告警产生--------------------------------------------------1 + let datas = projects.map(d => {//需要 项目,告警源,异常类型,时间 + return { + projectCorrelationId: d, + alarmInfo: { messageMode, sourceName, alarmTypeCode, content }, + time: time, + type//异常类型 + } + }) + let rslt = await models.AlarmAppearRecord.bulkCreate(datas, { returning: true }); + let dynamics = rslt.map(r => { + return { + time: r.time, + alarmAppearId: r.id, + projectCorrelationId: r.projectCorrelationId, + type: 1//发现 + } + }) + await models.LatestDynamicList.bulkCreate(dynamics); + + //消息推送到前端 + if (datas.length) { + await sendAppearToWeb(datas, 'data'); + } -module.exports = async function factory (app, opts) { - const client = new Kafka.KafkaClient({ kafkaHost: opts.kafka.rootURL }); - const producer = new Kafka.HighLevelProducer(client); - producer.on('error', function (err) { - app.fs.logger.log('error', "[FS-KAFKA]", err); - }); + } else if (messageMode == 'AlarmAutoElimination') {//告警自动恢复------------------------------------2 + let datas = projects.map(d => { + return { + pepUserId: null, + projectCorrelationId: d, + alarmInfo: { source: sourceName, type },//包含告警id,type,source + confirmTime: time, + confirmContent: '自动恢复' + } + }) + let rslt = await models.AlarmConfirmLog.bulkCreate(datas, { returning: true }); + let dynamics = rslt.map(r => { + return { + time: r.confirmTime, + alarmConfirmId: r.id, + projectCorrelationId: r.projectCorrelationId, + type: 4//告警确认 + } + }) + await models.LatestDynamicList.bulkCreate(dynamics); - const kafka = { - producer: producer, - configUpdateMessage: opts.configUpdateMessage || {} - }; - app.fs.kafka = kafka; - app.fs.logger.log('debug', "[FS-KAFKA]", "Init.Success"); + //消息推送到前端 + if (datas.length) { + await sendConfirmToWeb(datas, true); + } + } + } + } else { + console.log(`获取结构物列表失败, error: ${err}`); + } + } catch (error) { + console.error(error); + } + } + } catch (error) { + console.log(error); + } } \ No newline at end of file diff --git a/api/app/lib/utils/alarmHandle.js b/api/app/lib/utils/alarmHandle.js new file mode 100644 index 0000000..c3b8a67 --- /dev/null +++ b/api/app/lib/utils/alarmHandle.js @@ -0,0 +1,132 @@ +'use strict'; + +module.exports = function (app, opts) { + const { models } = app.fs.dc + const { clickHouse } = app.fs + + let constAlarmGroups = { + 1: '数据中断', + 2: '数据异常', + 3: '策略命中', + 4: '设备异常', + 5: '设备异常', + 'video': '视频异常', + 'app': '应用异常' + } + async function sendAppearToWeb(datas, ttype) { + try { + //告警类型 + let alarmGroup = null + + //项目信息 + let { projects, pepProjects } = await getProjectsInfo(datas); + + //数据类区分alarmGroup + if (ttype == 'data') { + let alarm_group = await clickHouse.anxinyun.query( + `SELECT alarm_group FROM t_alarm_code WHERE code='${datas[0].alarmInfo.alarmTypeCode}'`).toPromise(); + + alarmGroup = alarm_group.length ? constAlarmGroups[alarm_group[0].alarm_group] : null + } else { + alarmGroup = constAlarmGroups[ttype] + } + + let sendData = [] + datas.map(ld => { + let pepPId = projects.find(p => p.id == ld.projectCorrelationId).pepProjectId; + sendData.push({ + projectCorrelationId: ld.projectCorrelationId, + project: projects.find(p => p.id == ld.projectCorrelationId).name || pepProjects.find(pp => pp.id == pepPId).project_name,//前者为自定义项目名称 + source: ld.alarmInfo.sourceName, + type: ld.type, + time: ld.time, + alarmGroup//告警类型 + }) + }) + app.socket.emit('alarmSendSocket', { type: 'alarmAppear', sendData }) + } catch (err) { + console.log(`告警(发现)推送失败, error: ${err}`); + } + } + + async function sendConfirmToWeb(logDatas, isAuto) { + try { + //用户信息 + let userName = null + if (!isAuto) { + let userPepRes = await clickHouse.pepEmis.query( + `SELECT DISTINCT user.id AS id, "user"."name" AS name FROM user WHERE user.id=${logDatas[0].pepUserId}`).toPromise(); + userName = userPepRes.length ? userPepRes[0].name : null + } + + //项目信息 + let { projects, pepProjects } = await getProjectsInfo(logDatas); + let sendData = [] + logDatas.map(ld => { + let pepPId = projects.find(p => p.id == ld.projectCorrelationId).pepProjectId; + sendData.push({ + user: userName, + projectCorrelationId: ld.projectCorrelationId, + project: projects.find(p => p.id == ld.projectCorrelationId).name || pepProjects.find(pp => pp.id == pepPId).project_name,//前者为自定义项目名称 + source: ld.alarmInfo.source, + type: ld.alarmInfo.type, + time: ld.confirmTime, + isAuto//是否为自动恢复,自动恢复时user为null + }) + }) + app.socket.emit('alarmSendSocket', { type: 'alarmConfirm', sendData })//小飞(处理人) 确认并关闭了A项目(项目) DTU设备(告警源) 状态异常(异常类型)的问题 + } catch (err) { + console.log(`告警(确认)推送失败, error: ${err}`); + } + } + + async function getProjectsInfo(datas) { + try { + let pIds = datas.map(l => l.projectCorrelationId);//所有的项目的id + let projects = await models.ProjectCorrelation.findAll({ + where: { id: { $in: pIds } }, + attributes: ['id', 'name', 'pepProjectId'] + }); + + let pepPojectIds = new Set(); + for (let p of projects) { + pepPojectIds.add(p.pepProjectId); + } + + let pepProjects = pepPojectIds.size ? await clickHouse.projectManage.query(` + SELECT id, project_name FROM t_pim_project WHERE id IN (${[...pepPojectIds]})`).toPromise() : []; + + return { projects, pepProjects }; + } catch (err) { + console.log(`获取项目信息失败, error: ${err}`); + } + } + + async function sendNoticeToWeb(pepUsers, data) { + try { + let { cfgName, tactics, tacticsParams, projectCorrelationId, time } = data; + //项目信息 + let { projects, pepProjects } = await getProjectsInfo([data]); + let pepPId = projects.find(p => p.id == projectCorrelationId).pepProjectId; + + //需要 策略名称 处理人 项目 策略和参数 时间 + let sendData = { + pushConfig: { cfgName, tactics, tacticsParams },//策略信息 + pepUsers, + projectCorrelationId: projectCorrelationId, + project: projects.find(p => p.id == projectCorrelationId).name || pepProjects.find(pp => pp.id == pepPId).project_name,//前者为自定义项目名称 + time + } + + app.socket.emit('alarmSendSocket', { type: 'alarmNotice', sendData }) + } catch (err) { + console.log(`推送通知失败, error: ${err}`); + } + } + + return { + sendAppearToWeb,//推送告警发现 + sendConfirmToWeb,//推送告警确认 + sendNoticeToWeb//推送通知 + } +} \ No newline at end of file diff --git a/api/app/lib/utils/helper.js b/api/app/lib/utils/helper.js new file mode 100644 index 0000000..f2f56d5 --- /dev/null +++ b/api/app/lib/utils/helper.js @@ -0,0 +1,159 @@ +'use strict'; + +const moment = require('moment') + +module.exports = function (app, opts) { + const { models } = app.fs.dc + const { clickHouse } = app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + async function getAxyStructs(pepProjectId) { + try { + try { + const { pepProjectRes, bindRes } = await pomsWithPepRangeParams(pepProjectId) + // 获取不重复的 安心云项目 id + const anxinProjectIds = [ + ...(bindRes).reduce( + (arr, b) => { + for (let sid of b.anxinProjectId) { + arr.add(sid); + } + return arr; + }, + new Set() + ) + ] + // 查询安心云项目及结构物信息 + const undelStrucRes = anxinProjectIds.length ? + await clickHouse.anxinyun.query( + `SELECT + t_project.id AS projectId, + t_structure.id AS strucId, + t_structure.name AS strucName, + project_state + 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 (${anxinProjectIds.join(',')})`).toPromise() : [] + + // 构建安心云结构物和项企项目的关系 + // 并保存信息至数据 + let undelStruc = [] + for (let s of undelStrucRes) { + let corStruc = undelStruc.find(us => us.strucId == s.strucId) + if (corStruc) { + if (!corStruc.project.some(cp => cp.id == s.projectId)) { + corStruc.project.push({ + id: s.projectId + }) + } + } else { + corStruc = { + strucId: s.strucId, + strucName: s.strucName, + projectId: s.projectId, + project: [{ + id: s.projectId, + }], + pomsProject: [] + } + undelStruc.push(corStruc) + } + for (let { dataValues: br } of bindRes) { + if (br.anxinProjectId.some(braId => braId == s.projectId)) { + let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) + let corPomsProject = corStruc.pomsProject.find(cp => cp.id == br.id) + + if (corPomsProject) { + // poms 的 project 和 pep 的 project 是一对一的关系 所以这个情况不用处理 + } else { + corStruc.pomsProject.push({ + ...br, + pepProject: corPepProject + }) + } + + } + } + } + return undelStruc + } catch (error) { + console.error(error); + } + } catch (error) { + console.log(error) + } + } + + async function pomsWithPepRangeParams(pepProjectId) { + try { + let findOption = { + where: { + del: false + } + } + if (pepProjectId) { + // 有 特定的项目id 就按此查询 + findOption.where.id = pepProjectId + } + const bindRes = await models.ProjectCorrelation.findAll(findOption); + // 获取不重复的 项企项目id + let pepProjectIds = [] + for (let b of bindRes) { + if (b.pepProjectId) { + pepProjectIds.push(b.pepProjectId) + } + } + // 查询项企项目的信息 + const pepProjectRes = pepProjectIds.length ? + await clickHouse.projectManage.query( + `SELECT + t_pim_project.id AS id, + t_pim_project.project_name AS projectName, + t_pim_project.isdelete AS isdelete, + t_pim_project_construction.construction_status_id AS constructionStatusId, + t_pim_project_state.construction_status AS constructionStatus + 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(',')})` + ).toPromise() : []; + + return { + pepProjectRes, bindRes + } + + } catch (error) { + console.error(error); + } + } + + return { + getAxyStructs + } +} \ No newline at end of file diff --git a/api/config.js b/api/config.js index a7badf7..507837c 100644 --- a/api/config.js +++ b/api/config.js @@ -43,6 +43,7 @@ args.option('clickHousePepEmis', 'clickHouse 项企数据库名称'); args.option('clickHouseProjectManage', 'clickHouse 项目管理数据库名称'); args.option('clickHouseVcmp', 'clickHouse 视频平台数据库名称'); args.option('clickHouseDataAlarm', 'clickHouse 视频平台数据告警库名称'); +args.option('clickHouseIot', 'clickHouse IOT平台设备信息库名称'); args.option('confirmAlarmAnxinUserId', '确认告警时保存到 ES 的安心云的用户的 id'); @@ -98,6 +99,7 @@ const CLICKHOUST_PEP_EMIS = process.env.CLICKHOUST_PEP_EMIS || flags.clickHouseP const CLICKHOUST_PROJECT_MANAGE = process.env.CLICKHOUST_PROJECT_MANAGE || flags.clickHouseProjectManage const CLICKHOUST_VCMP = process.env.CLICKHOUST_VCMP || flags.clickHouseVcmp const CLICKHOUST_DATA_ALARM = process.env.CLICKHOUST_DATA_ALARM || flags.clickHouseDataAlarm +const CLICKHOUST_IOT = process.env.CLICKHOUST_IOT || flags.clickHouseIot const CONFIRM_ALARM_ANXIN_USER_ID = process.env.CONFIRM_ALARM_ANXIN_USER_ID || flags.confirmAlarmAnxinUserId @@ -118,7 +120,7 @@ if ( || !API_IOT_AUTH || !QINIU_DOMAIN_QNDMN_RESOURCE || !QINIU_BUCKET_RESOURCE || !QINIU_AK || !QINIU_SK || !CLICKHOUST_URL || !CLICKHOUST_PORT - || !CLICKHOUST_ANXINCLOUD || !CLICKHOUST_PEP_EMIS || !CLICKHOUST_PROJECT_MANAGE || !CLICKHOUST_VCMP || !CLICKHOUST_DATA_ALARM + || !CLICKHOUST_ANXINCLOUD || !CLICKHOUST_PEP_EMIS || !CLICKHOUST_PROJECT_MANAGE || !CLICKHOUST_VCMP || !CLICKHOUST_DATA_ALARM || !CLICKHOUST_IOT || !CONFIRM_ALARM_ANXIN_USER_ID || !VCMP_APP_ID || !VCMP_APP_SECRET ) { @@ -150,7 +152,8 @@ const product = { { p: '/attachments/:p', o: 'POST' }, { p: '/alarm/application/inspection', o: 'POST' }, { p: '/project/app_list', o: 'GET' }, - { p: '/alarm/application/api', o: 'POST' } + { p: '/alarm/application/api', o: 'POST' }, + { p: '/alarm/video/added_log', o: 'POST' } ], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由 anxinCloud: { confirmAlarmAnxinUserId: CONFIRM_ALARM_ANXIN_USER_ID @@ -233,6 +236,9 @@ const product = { }, { name: 'dataAlarm', db: CLICKHOUST_DATA_ALARM + }, { + name: 'iot', + db: CLICKHOUST_IOT } ] } diff --git a/api/package.json b/api/package.json index 4d3e893..f630fc4 100644 --- a/api/package.json +++ b/api/package.json @@ -22,7 +22,7 @@ "file-saver": "^2.0.2", "fs-web-server-scaffold": "^2.0.2", "ioredis": "^5.0.4", - "kafka-node": "^2.2.3", + "kafka-node": "^5.0.0", "koa-convert": "^1.2.0", "koa-proxy": "^0.9.0", "moment": "^2.24.0", diff --git a/web/client/src/sections/control/containers/control.jsx b/web/client/src/sections/control/containers/control.jsx index 950ae75..fcff3c2 100644 --- a/web/client/src/sections/control/containers/control.jsx +++ b/web/client/src/sections/control/containers/control.jsx @@ -45,6 +45,25 @@ const Control = (props) => { const exhibition = useRef({ workbench: [], statistical: [] }) //页面结构 const FormApi = useRef() + // websocket 使用测试 + useEffect(() => { + if (socket) { + socket.on('alarmSendSocket', function (msg) { + //console.info(msg); + if (msg.type == "alarmAppear") {//告警出现 + + } else if (msg.type == "alarmConfirm") {//告警确认 + + } else if (msg.type == "alarmNotice") {//通知 + + } + }); + return () => { + socket.off("alarmSendSocket"); + } + } + }, [socket]) + useEffect(() => { consoleToollink() @@ -928,7 +947,7 @@ function mapStateToProps (state) { actions: global.actions, pepProjectId: global.pepProjectId, // members: members.data, - // socket: webSocket.socket + socket: webSocket.socket }; } diff --git a/web/client/src/sections/service/actions/emPush.js b/web/client/src/sections/service/actions/emPush.js index cadbb00..b560310 100644 --- a/web/client/src/sections/service/actions/emPush.js +++ b/web/client/src/sections/service/actions/emPush.js @@ -10,7 +10,7 @@ export function getPush (query) { //获取推送配置列表 actionType: 'GET_PUSH', url: `${ApiTable.getPush}`, msg: { error: '获取推送配置列表失败' }, - reducer: { name: '' } + reducer: { name: "getPush", params: { noClear: true } }, }); } diff --git a/web/client/src/sections/service/components/pushModal.jsx b/web/client/src/sections/service/components/pushModal.jsx index 3c089c9..d271001 100644 --- a/web/client/src/sections/service/components/pushModal.jsx +++ b/web/client/src/sections/service/components/pushModal.jsx @@ -378,7 +378,6 @@ function pushModal (props) { labelAlign="right" labelWidth="120px" onValueChange={(values, field) => { - console.log('values', values); for (var key in field) { if (key == 'tactics') { if (values.tactics == 'abnormal_rate') { diff --git a/web/client/src/sections/service/containers/emPush.jsx b/web/client/src/sections/service/containers/emPush.jsx index 1e1354a..8a4795d 100644 --- a/web/client/src/sections/service/containers/emPush.jsx +++ b/web/client/src/sections/service/containers/emPush.jsx @@ -7,7 +7,6 @@ import moment from "moment"; import PushModal from '../components/pushModal' import '../style.less' import { Setup } from "$components"; -// import { set } from 'nprogress'; const EmPush = (props) => { const form = useRef();//表单 @@ -15,22 +14,13 @@ const EmPush = (props) => { const { service } = actions; const [setup, setSetup] = useState(false); //表格设置是否显现 const [setupp, setSetupp] = useState([]);//实际显示的表格列表 - const [tableSetup, setTableSetup] = useState([]); //单一表格设置信息 const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 const [limits, setLimits] = useState()//每页实际条数 const mylimits = useRef(); //每页实际条数 const [pushModal, setPushModal] = useState(false) //信鸽弹框 const [pushEdit, setPushEdit] = useState(false) //是否是修改 - const [anxincloudList, setAnxincloudList] = useState([]) //安心云列表 - const [peplist, setPeplist] = useState([]) //PEP项目管理项目列表 - const [appList, setAppList] = useState([]) //应用列表 - const [pepProjectId, setPepProjectId] = useState() //修改时项企id - const [anxincloudArr, setAnxincloudArr] = useState([]) //修改时已经选择的安心云列表 - const [pepname, setPepname] = useState() //修改时自定义项目名称 - const [anxinDelete, setAnxinDelete] = useState([]) //修改时安心云项目有删除,显示提示信息 - const [appArr, setAppArr] = useState([]) //修改时添加应用 - const [bindId, setBindId] = useState() //修改时绑定的id - const [tableKey, setTableKey] = useState([]) //修改时绑定的id + const [change, setChange] = useState(false) //是否改变 + const [allTableData, setAllTableData] = useState([]) //获取到的所有表格信息 const [editObj, setEditObj] = useState({});//管理员弹框修改内容 const [projectStatus, setProjectStatus] = useState([]); //获取项目状态列表 const page = useRef(query.page);//哪一页 @@ -82,16 +72,6 @@ const EmPush = (props) => { const [tableData, setTableData] = useState([]) //表格数据 useEffect(() => { - // dispatch(service.getPush(query)).then((res) => {//获取推送配置列表 - // console.log('res.payload.datares.payload.data',res.payload.data); - // setAnxincloudList(res.payload.data) - // }) - // dispatch(install.getProjectPmanage(query)).then((res) => {//获取PEP项目管理项目 - // setPeplist(res.payload.data) - // }) - // dispatch(install.getProjectAppList(query)).then((res) => {//获取应用列表 - // setAppList(res.payload.data) - // }) localStorage.getItem(EMPUSH) == null ? localStorage.setItem( EMPUSH, @@ -99,23 +79,28 @@ const EmPush = (props) => { ) : ""; getProjectStatusList() + getPushList(query); }, []) useEffect(() => { - getPushList(); - }, [query]); + let showTableData = JSON.parse(JSON.stringify(allTableData)).slice(query.page * query.limit, (query.page + 1) * query.limit) + setTableData(showTableData) + mylimits.current = showTableData.length + }, [change]); - function getPushList () { + function getPushList (query) { let val = form.current.getValues() - // , ...query dispatch(service.getPush({ ...val })).then((res) => {//获取已绑定项目 if (res.success) { let mytableData = JSON.parse(JSON.stringify(res.payload.data)); for (let index = 0; index < mytableData.length; index++) { mytableData[index].key = String(mytableData[index].id) } - setTableData(mytableData) + setAllTableData(mytableData) + let showTableData = mytableData.slice(query.page * query.limit, (query.page + 1) * query.limit) + setTableData(showTableData) + setQuery(query) setLimits(res.payload.data.length) - mylimits.current = res.payload.data.length + mylimits.current = showTableData.length } }) } @@ -138,37 +123,9 @@ const EmPush = (props) => {