diff --git a/code/VideoAccess-VCMP/api/app/lib/models/camera_status_push_log.js b/code/VideoAccess-VCMP/api/app/lib/models/camera_status_push_log.js index 1a8d9c7..c95ebda 100644 --- a/code/VideoAccess-VCMP/api/app/lib/models/camera_status_push_log.js +++ b/code/VideoAccess-VCMP/api/app/lib/models/camera_status_push_log.js @@ -68,7 +68,16 @@ module.exports = dc => { primaryKey: false, field: "timing", autoIncrement: false - } + }, + noticeWay: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: false, + defaultValue: null, + comment: "通知方式 offline / online / timing", + primaryKey: false, + field: "notice_way", + autoIncrement: false + }, }, { tableName: "camera_status_push_log", comment: "", diff --git a/code/VideoAccess-VCMP/api/app/lib/schedule/cameraStatePush.js b/code/VideoAccess-VCMP/api/app/lib/schedule/cameraStatePush.js index 252b305..fe4e7b2 100644 --- a/code/VideoAccess-VCMP/api/app/lib/schedule/cameraStatePush.js +++ b/code/VideoAccess-VCMP/api/app/lib/schedule/cameraStatePush.js @@ -6,11 +6,11 @@ module.exports = function (app, opts) { interval: '* */15 * * * *', // interval: '*/15 * * * * *', immediate: false, - proRun: false, + proRun: true, }, async () => { try { - const { models } = app.fs.dc + const { models, ORM: sequelize } = app.fs.dc const { pushBySms, pushByEmail } = app.fs.utils const configRes = await models.CameraStatusPushConfig.findAll({ where: { @@ -50,8 +50,6 @@ module.exports = function (app, opts) { order: [['time', 'ASC']], }) - - if (offlineStatusRes.length) { const cameraStatusMap = {} // 当前逻辑 @@ -60,27 +58,27 @@ module.exports = function (app, opts) { for (let s of offlineStatusRes) { if (cameraStatusMap[s.cameraId]) { cameraStatusMap.status.push({ - cameraId: s.cameraId, status: s.status, time: s.time, }) } else { cameraStatusMap[s.cameraId] = { status: [{ - cameraId: s.cameraId, status: s.status, time: s.time, }], + cameraId: s.cameraId, name: s.camera.name, } } } let offArr = [] let onArr = [] - for (let k in Object.keys(cameraStatusMap).sort()) { + for (let k of Object.keys(cameraStatusMap).sort()) { const data = cameraStatusMap[k] if (data.status[0].status == 'OFF') { offArr.push({ + cameraId: data.cameraId, name: data.name, time: data.status[0].time, }) @@ -88,9 +86,10 @@ module.exports = function (app, opts) { const onLineIndex = data.status.findIndex(s => s.status == 'ON') if (onLineIndex >= 0) { const onLineLastOffIndex = data.status.findIndex((s, i) => s.status == 'OFF' && i > onLineIndex) + // 当前在线记录的上一次离线记录 let onlineData = { - cameraId: data.status[onLineIndex].cameraId, - name: data.status[onLineIndex].name, + cameraId: data.cameraId, + name: data.name, time: data.status[onLineIndex].time, } if (onLineLastOffIndex >= 0) { @@ -110,37 +109,57 @@ module.exports = function (app, opts) { }) const receiver = c.cameraStatusPushReceivers.map(r => r.receiver) + const logData = { + pushConfigId: c.id, + receiver: receiver, + timing: c.timing, + time: moment(timeNow).format(), + pushWay: c.pushWay, + // camera: cameraIds, + } // 离线推送 if (offArr.length && c.noticeWay && c.noticeWay.includes('offline')) { if (c.pushWay == 'email') { // 邮件 - let text = `【${corUser[0].namePresent}】账号下的设备,截止【${moment(timeNow).format('YYYY年HH时mm分')}】,有${offArr.length}个设备掉线:\n` + let text = `【${corUser[0].namePresent}】账号下的设备,截止【${moment(timeNow).format('MM月DD日 HH时mm分')}】,有${offArr.length}个设备掉线:\n` text += offArr.map(o => `【${o.name}】于【${o.time}】掉线,`).join('\n') text += `\n请及时处理!` await pushByEmail({ email: receiver, - title: '', + title: '尚视设备离线通知', text, }) + await models.CameraStatusPushLog.create({ ...logData, pushWay: 'email', noticeWay: ['offline'], camera: offArr.map(o => o.cameraId) }) } else if (c.pushWay == 'phone') { // 短信 - let text = `【${corUser[0].namePresent}】账号下的` + let templateParam = { + name: corUser[0].namePresent, + } + let useTempCode = 'SMS_247900122' + // let text = `【${corUser[0].namePresent}】账号下的` if (offArr.length == 1) { - text += `【${offArr[0].name}】离线,请及时处理!【${moment(offArr[0].time).format('YYYY年HH时mm分')}】` + // text += `【${offArr[0].name}】离线,请及时处理!【${moment(offArr[0].time).format('MM月DD日 HH时mm分')}】` + templateParam.deviceName = `【${offArr[0].name}】` + templateParam.time = moment(offArr[0].time).format('MM月DD日 HH时mm分') } else { + let text = '' text += offArr.map(o => `【${o.name}】`).join('') if (text.length > 35) { text = text.substring(0, 35) + '...' - text += `等${offArr.length}个摄像头离线` + // text += `等${offArr.length}个摄像头离线` + templateParam.deviceCount = offArr.length + useTempCode = 'SMS_247985123' } - text += `,请及时处理!【${moment().format('MM月DD日HH时mm分')}-${moment(timeNow).subtract(15, 'minutes').format('MM月DD日HH时mm分')}】` + templateParam.deviceName = text + // text += `,请及时处理!【${moment().format('MM月DD日HH时mm分')}-${moment(timeNow).subtract(15, 'minutes').format('MM月DD日HH时mm分')}】` + templateParam.time = `【${moment().format('MM月DD日HH时mm分')} -${moment(timeNow).subtract(15, 'minutes').format('MM月DD日HH时mm分')}】` } await pushBySms({ phone: receiver, - templateCode: '', - templateParam: '', - text + templateCode: useTempCode, + templateParam: templateParam, }) + await models.CameraStatusPushLog.create({ ...logData, pushWay: 'phone', noticeWay: ['offline'], camera: offArr.map(o => o.cameraId) }) } } @@ -148,38 +167,202 @@ module.exports = function (app, opts) { if (onArr.length && c.noticeWay && c.noticeWay.includes('online')) { if (c.pushWay == 'email') { // 邮件 - // const outTimeCameraOff = onArr.filter(a => !a.offTime) - // if (outTimeCameraOff.length) { - // let cameraIds = outTimeCameraOff.map(a => a.cameraId) - // const lastOfflineRes = await models.CameraStatus.findAll({ - // where: { - // cameraId: { $in: cameraIds }, - // status: 'OFF', - // time: { - // $lt: moment(timeNow).subtract(15, 'minutes').format() - // } - // }, - // group: ['cameraId'], - // attributes: ['cameraId', [sequelize.fn('max', sequelize.col('time')), 'time']], - // }) + const outTimeCameraOff = onArr.filter(a => !a.offTime) + if (outTimeCameraOff.length) { + let cameraIds = outTimeCameraOff.map(a => a.cameraId) + const lastOfflineRes = await models.CameraStatusOfflineLog.findAll({ + where: { + cameraId: { $in: cameraIds }, + status: 'OFF', + time: { + $lt: moment(timeNow).subtract(15, 'minutes').format() + } + }, + group: ['cameraId'], + attributes: ['cameraId', [sequelize.fn('max', sequelize.col('time')), 'time']], + }) + for (let a of onArr) { + if (!a.offTime) { + const lastOffline = lastOfflineRes.find(l => l.cameraId == a.cameraId) + if (lastOffline) { + a.offTime = lastOffline.time + } + } + } + } + + let text = `【${corUser[0].namePresent}】账号下的设备:\n` + text += onArr.map(o => `【${o.name}】于【${moment(o.offTime).format('MM月DD日HH时mm分')}】掉线,【${moment(o.time).format('MM月DD日HH时mm分')}】已恢复`).join('\n') + await pushByEmail({ + email: receiver, + title: '尚视设备上线通知', + text, + }) + await models.CameraStatusPushLog.create({ ...logData, pushWay: 'email', noticeWay: ['online'], camera: onArr.map(o => o.cameraId) }) + } else if (c.pushWay == 'phone') { + // 短信 + // let text = `【${corUser[0].namePresent}】账号下的` + let templateParam = { + name: corUser[0].namePresent, + } + let useTempCode = 'SMS_247820204' + if (onArr.length == 1) { + // text += `【${onArr[0].name}】已恢复上线,请及时处理!【${moment(onArr[0].time).format('MM月DD日 HH时mm分')}】` + templateParam.deviceName = `【${onArr[0].name}】` + templateParam.time = moment(onArr[0].time).format('MM月DD日 HH时mm分') + } else { + let text = '' + text += onArr.map(o => `【${o.name}】`).join('') + if (text.length > 35) { + text = text.substring(0, 35) + '...' + // text += `等${onArr.length}个摄像头已恢复上线!` + templateParam.deviceCount = onArr.length + useTempCode = 'SMS_247825189' + } + templateParam.deviceName = text + // text += `【${moment().format('MM月DD日HH时mm分')}-${moment(timeNow).subtract(15, 'minutes').format('MM月DD日HH时mm分')}】!` + templateParam.time = `【${moment().format('MM月DD日HH时mm分')}-${moment(timeNow).subtract(15, 'minutes').format('MM月DD日HH时mm分')}】!` + } + await pushBySms({ + phone: receiver, + templateCode: useTempCode, + templateParam: templateParam, + }) + await models.CameraStatusPushLog.create({ ...logData, pushWay: 'phone', noticeWay: ['online'], camera: onArr.map(o => o.cameraId) }) + } + } + } + } + } catch (error) { + console.error(error); + } + } + ) - // } + // 摄像头离线定时统计推送 + const cameraOnlineTimingPush = app.fs.scheduleInit( + { + interval: '* */5 * * * *', + // interval: '*/10 * * * * *', // dev + immediate: false, + proRun: true, + }, + async () => { + try { + const { models, ORM: sequelize } = app.fs.dc + const { pushBySms, pushByEmail } = app.fs.utils + const timeNow = moment().format() + const configRes = await models.CameraStatusPushConfig.findAll({ + where: { + forbidden: false, + noticeWay: { + $contains: ['timing'] + }, + timing: moment(timeNow).format('HH:mm') + }, + include: [ + { + model: models.CameraStatusPushMonitor, + attributes: ['cameraId'], + required: false, + duplicating: true + }, + { + model: models.CameraStatusPushReceiver, + attributes: ['receiver'], + duplicating: false, + required: false, + }, + ], + }) + for (let c of configRes) { + // 查配置信息所对应的摄像头15min内的在离线状态 + const cameraIds = c.cameraStatusPushMonitors.map(m => m.cameraId) + const offlineRes = await models.Camera.findAll({ + where: { + id: { $in: cameraIds }, + }, + attributes: ['id', 'name'], + include: [{ + model: models.GbCamera, + attributes: ['id', 'online'], + required: true, + where: { + online: 'OFF' + } + }], + }) + if (offlineRes.length) { + // 查当前配置由谁配置 + const corUser = await app.fs.authRequest.get(`user/${c.createUser}/message`, { + query: { + token: '' + } + }) + const receiver = c.cameraStatusPushReceivers.map(r => r.receiver) + if (receiver.length) { + const logData = { + pushConfigId: c.id, + receiver: receiver, + timing: c.timing, + time: moment(timeNow).format(), + pushWay: c.pushWay, + camera: offlineRes.map(o => o.id), + } + if (c.pushWay == 'email') { + // 邮件 + const offlineTimeRes = await models.CameraStatusOfflineLog.findAll({ + attributes: ['cameraId', [sequelize.fn('max', sequelize.col('time')), 'time']], + where: { + cameraId: { $in: offlineRes.map(oc => oc.id) }, + status: 'OFF', + }, + group: ['cameraId'], + }) + if (offlineTimeRes.length) { + let text = `【${corUser[0].namePresent}】账号下的设备,截止${moment(timeNow).format('MM月DD日 HH时mm分')},有${offlineRes.length}个设备掉线:\n` + text += offlineTimeRes.map(o => { + let corCamera = offlineRes.find(c => c.id == o.cameraId) + if (corCamera) { + return `【${(corCamera || {}).name}】于【${moment(o.time).format('MM月DD日 HH时mm分')}】掉线,` + } + return '' + }).join('\n') + text += `\n请及时处理!` + await pushByEmail({ + email: receiver, + title: '尚视设备离线定时统计通知', + text, + }) + await models.CameraStatusPushLog.create({ ...logData, pushWay: 'email', noticeWay: ['timing'] }) + } } else if (c.pushWay == 'phone') { // 短信 + // let text = `【${corUser[0].namePresent}】账号下截止${moment(timeNow).format('YYYY年MM月DD日 HH时')},有${offlineRes.length}个设备掉线,请及时处理!` + await pushBySms({ + phone: receiver, + templateCode: 'SMS_247785195', + templateParam: JSON.stringify({ + name: corUser[0].namePresent, + time: moment(timeNow).format('YYYY年MM月DD日 HH时'), + deviceCount: offlineRes.length, + }), + }) + await models.CameraStatusPushLog.create({ ...logData, pushWay: 'phone', noticeWay: ['timing'] }) } } } } - console.log('cameraOnlinePush') } catch (error) { - + console.error(error); } } ) return { cameraOnlinePush, + cameraOnlineTimingPush, } } \ No newline at end of file diff --git a/code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js b/code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js index 941015e..f41e1d5 100644 --- a/code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js +++ b/code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js @@ -4,8 +4,8 @@ const moment = require('moment') module.exports = function (app, opts) { const freshYingshiState = app.fs.scheduleInit( { - interval: '* */5 * * * *', - // interval: '*/10 * * * *', + // interval: '* */5 * * * *', + interval: '*/10 * * * *', immediate: false, proRun: true, }, diff --git a/code/VideoAccess-VCMP/api/app/lib/schedule/index.js b/code/VideoAccess-VCMP/api/app/lib/schedule/index.js index e8f4137..121b8a9 100644 --- a/code/VideoAccess-VCMP/api/app/lib/schedule/index.js +++ b/code/VideoAccess-VCMP/api/app/lib/schedule/index.js @@ -9,6 +9,9 @@ module.exports = async function (app, opts) { const scheduleInit = ({ interval, immediate, proRun, }, callback) => { + if (proRun && opts.dev) { + return; + } const j = nodeSchedule.scheduleJob(interval, callback); if (immediate && (!proRun || (proRun && !opts.dev))) { setTimeout(callback, 0) diff --git a/code/VideoAccess-VCMP/api/app/lib/utils/push.js b/code/VideoAccess-VCMP/api/app/lib/utils/push.js index cb2e9b5..2eeb275 100644 --- a/code/VideoAccess-VCMP/api/app/lib/utils/push.js +++ b/code/VideoAccess-VCMP/api/app/lib/utils/push.js @@ -16,9 +16,9 @@ module.exports = function (app, opts) { }); const SendSmsRes = await client.request('SendSms', { "PhoneNumbers": `${phone.join(',')}`,//接收短信的手机号码。 - "SignName": "安心云",//短信签名名称。必须是已添加、并通过审核的短信签名。 - "TemplateCode": "SMS_216403225",//短信模板ID。必须是已添加、并通过审核的短信签名;且发送国际/港澳台消息时,请使用国际/港澳台短信模版。 - "TemplateParam": `{"code":${code}}`//短信模板变量对应的实际值,JSON格式。 + "SignName": "尚视",//短信签名名称。必须是已添加、并通过审核的短信签名。 + "TemplateCode": templateCode,//短信模板ID。必须是已添加、并通过审核的短信签名;且发送国际/港澳台消息时,请使用国际/港澳台短信模版。 + "TemplateParam": JSON.stringify(templateParam)//短信模板变量对应的实际值,JSON格式。 }, { method: 'POST' }); @@ -43,7 +43,7 @@ module.exports = function (app, opts) { // send mail with defined transport object await transporter.sendMail({ - from: 'grazing-stars@qq.com', // sender address + from: `${opts.email.sender.name}<${opts.email.sender.address}>`, // sender address to: email.join(','), // list of receivers 逗号分隔字符串 subject: title, // Subject line text: text, // plain text body diff --git a/code/VideoAccess-VCMP/api/config.js b/code/VideoAccess-VCMP/api/config.js index 013b890..00e5f2e 100644 --- a/code/VideoAccess-VCMP/api/config.js +++ b/code/VideoAccess-VCMP/api/config.js @@ -115,23 +115,14 @@ const product = { accessSecret: '1trYkmiqfBtvZL6BxkNH2uQcQQPs0S' }, email: { - // enabled: true, - // host: 'smtp.exmail.qq.com', - // port: 465, - // sender: { - // name: '知物云', - // address: 'no-reply@zhiwucloud.com', - // password: 'Zwy123456_' - // }, - - // enabled: true, - // host: 'smtp.exmail.qq.com', - // port: 465, - // sender: { - // name: '知物云', - // address: 'no-reply@zhiwucloud.com', - // password: 'Zwy123456_' - // } + enabled: true, + host: 'smtp.exmail.qq.com', + port: 465, + sender: { + name: '尚视', + address: 'no-reply@zhiwucloud.com', + password: 'Zwy123456_' + } }, pssaRequest: [ {// name 会作为一个 request 出现在 ctx.app.fs diff --git a/code/VideoAccess-VCMP/script/1.1.2/schema/2.update_push_log.sql b/code/VideoAccess-VCMP/script/1.1.2/schema/2.update_push_log.sql new file mode 100644 index 0000000..dbb0afd --- /dev/null +++ b/code/VideoAccess-VCMP/script/1.1.2/schema/2.update_push_log.sql @@ -0,0 +1,3 @@ +alter table camera_status_push_log + add notice_way varchar[]; +