Browse Source

信鸽推送 80%

release_1.1.2
巴林闲侠 3 years ago
parent
commit
50b5cdaeb1
  1. 63
      code/VideoAccess-VCMP/api/app/lib/controllers/status/push.js
  2. 17
      code/VideoAccess-VCMP/api/app/lib/index.js
  3. 52
      code/VideoAccess-VCMP/api/app/lib/models/camera_status_offline_log.js
  4. 170
      code/VideoAccess-VCMP/api/app/lib/schedule/cameraStatePush.js
  5. 29
      code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js
  6. 4
      code/VideoAccess-VCMP/api/app/lib/schedule/index.js
  7. 6
      code/VideoAccess-VCMP/api/app/lib/utils/push.js
  8. 2
      code/VideoAccess-VCMP/api/sequelize-automate.config.js

63
code/VideoAccess-VCMP/api/app/lib/controllers/status/push.js

@ -11,20 +11,55 @@ async function edit (ctx) {
receiver, cameraId
} = ctx.request.body;
let configId_ = configId
let receiverSorted = receiver.sort()
let cameraIdSorted = cameraId.sort()
let noticeWaySorted = noticeWay.sort()
let editDataExist = false
// 判重
const configAllRes = await models.CameraStatusPushConfig.findAll({
include: [{
model: models.CameraStatusPushMonitor,
attributes: ['cameraId'],
required: false,
duplicating: true,
}, {
model: models.CameraStatusPushReceiver,
attributes: ['receiver'],
duplicating: false,
required: false,
},]
})
for (let c of configAllRes) {
if (configId_ && c.id == configId_) {
editDataExist = true
continue
} else if (c.name == name) {
throw '已有同名称配置信息'
}
const cReceiver = c.cameraStatusPushReceivers.map(r => r.receiver)
const cCameraId = c.cameraStatusPushMonitors.map(m => m.cameraId)
const cReceiverSorted = cReceiver.sort()
const cCameraIdSorted = cCameraId.sort()
if (
receiverSorted.join(',') == cReceiverSorted.join(',') && cameraIdSorted.join(',') == cCameraIdSorted.join(',') &&
c.pushWay == pushWay &&
c.noticeWay.sort().join(',') == noticeWaySorted.join(',') &&
(
(!c.timing && !timing) ||
c.timing == timing
)
) {
throw '已有相同配置信息'
}
}
let config = {
name, pushWay, noticeWay, timing
}
if (configId) {
const configRes = await models.CameraStatusPushConfig.findOne({
where: {
id: configId
}
})
if (!configRes) {
if (!editDataExist) {
throw '参数错误'
} else {
// DO UPDATE
@ -89,7 +124,7 @@ async function get (ctx) {
try {
const models = ctx.fs.dc.models;
const { userId, token } = ctx.fs.api
const { limit, page, orderBy, orderDirection, keyword, pushWay } = ctx.query
const { limit, page, orderBy, orderDirection, name, pushWay } = ctx.query
const sequelize = ctx.fs.dc.ORM;
let findOption = {
@ -104,13 +139,16 @@ async function get (ctx) {
if (page && limit) {
findOption.offset = page * limit
}
if (keyword) {
if (name) {
findOption.where['$or'] = {
name: {
$like: `%${keyword}%`
$like: `%${name}%`
},
}
}
if (pushWay) {
findOption.where.pushWay = pushWay
}
const configLimitRes = await models.CameraStatusPushConfig.findAll(findOption)
@ -131,6 +169,7 @@ async function get (ctx) {
'cameraStatusPushMonitors.id',
'cameraStatusPushLogs.id',
'cameraStatusPushReceivers.id',
'cameraStatusPushMonitors->camera.id',
]
findOption.order = [
[orderBy || 'id', orderDirection || 'DESC']
@ -141,6 +180,10 @@ async function get (ctx) {
attributes: ['cameraId'],
required: false,
duplicating: true,
include: [{
model: models.Camera,
attributes: ['name'],
}]
},
{
model: models.CameraStatusPushLog,

17
code/VideoAccess-VCMP/api/app/lib/index.js

@ -46,15 +46,6 @@ module.exports.entry = function (app, router, opts) {
module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Sequelize, models: {} }
// 加载定义模型 历史写法
// require('./models/nvr')(dc);
// require('./models/camera_ability')(dc);
// require('./models/camera_kind')(dc);
// require('./models/camera')(dc);
// require('./models/camera_ability_bind')(dc);
// require('./models/vender')(dc);
// require('./models/secret_yingshi')(dc);
// require('./models/gb_camera')(dc);
// require('./models/ax_project')(dc);
// require('./models/camera_remark')(dc);
// 模型关系摘出来 初始化之后再定义关系才行
fs.readdirSync(path.join(__dirname, '/models')).forEach((filename) => {
@ -64,7 +55,7 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
const {
Nvr, Camera, CameraAbility, CameraAbilityBind, CameraKind, CameraRemark,
GbCamera, SecretYingshi, Vender, CameraStatus, CameraStatusResolve, CameraStatusLog,
CameraStatusPushConfig, CameraStatusPushMonitor, CameraStatusPushLog, CameraStatusPushReceiver,
CameraStatusPushConfig, CameraStatusPushMonitor, CameraStatusPushLog, CameraStatusPushReceiver, CameraStatusOfflineLog
} = dc.models;
// Nvr.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' });
@ -105,9 +96,15 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
CameraStatusPushMonitor.belongsTo(CameraStatusPushConfig, { foreignKey: 'configId', targetKey: 'id' });
CameraStatusPushConfig.hasMany(CameraStatusPushMonitor, { foreignKey: 'configId', sourceKey: 'id' });
CameraStatusPushMonitor.belongsTo(Camera, { foreignKey: 'cameraId', targetKey: 'id' });
Camera.hasMany(CameraStatusPushMonitor, { foreignKey: 'cameraId', sourceKey: 'id' });
CameraStatusPushLog.belongsTo(CameraStatusPushConfig, { foreignKey: 'pushConfigId', targetKey: 'id' });
CameraStatusPushConfig.hasMany(CameraStatusPushLog, { foreignKey: 'pushConfigId', sourceKey: 'id' });
CameraStatusPushReceiver.belongsTo(CameraStatusPushConfig, { foreignKey: 'configId', targetKey: 'id' });
CameraStatusPushConfig.hasMany(CameraStatusPushReceiver, { foreignKey: 'configId', sourceKey: 'id' });
CameraStatusOfflineLog.belongsTo(Camera, { foreignKey: 'cameraId', targetKey: 'id' });
Camera.hasMany(CameraStatusOfflineLog, { foreignKey: 'cameraId', sourceKey: 'id' });
};

52
code/VideoAccess-VCMP/api/app/lib/models/camera_status_offline_log.js

@ -0,0 +1,52 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const CameraStatusOfflineLog = sequelize.define("cameraStatusOfflineLog", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "camera_status_offline_log_id_uindex"
},
cameraId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "camera_id",
autoIncrement: false
},
status: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "status",
autoIncrement: false
},
time: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "time",
autoIncrement: false
}
}, {
tableName: "camera_status_offline_log",
comment: "",
indexes: []
});
dc.models.CameraStatusOfflineLog = CameraStatusOfflineLog;
return CameraStatusOfflineLog;
};

170
code/VideoAccess-VCMP/api/app/lib/schedule/cameraStatePush.js

@ -3,16 +3,178 @@ const moment = require('moment')
module.exports = function (app, opts) {
const cameraOnlinePush = app.fs.scheduleInit(
{
// interval: '* * 4 * * *',
interval: '*/15 * * * *',
interval: '* */15 * * * *',
// interval: '*/15 * * * * *',
immediate: false,
proRun: false,
},
async () => {
try {
const { models } = app.fs.dc
const { pushBySms, pushByEmail } = app.fs.utils
const configRes = await models.CameraStatusPushConfig.findAll({
where: {
forbidden: false
},
include: [
{
model: models.CameraStatusPushMonitor,
attributes: ['cameraId'],
required: false,
duplicating: true
},
{
model: models.CameraStatusPushReceiver,
attributes: ['receiver'],
duplicating: false,
required: false,
},
],
})
const timeNow = moment().format()
for (let c of configRes) {
// 查配置信息所对应的摄像头15min内的在离线状态
const cameraIds = c.cameraStatusPushMonitors.map(m => m.cameraId)
const offlineStatusRes = await models.CameraStatusOfflineLog.findAll({
where: {
cameraId: { $in: cameraIds },
time: {
$between: [moment(timeNow).subtract(15, 'minutes').format(), timeNow]
}
},
include: [{
model: models.Camera,
attributes: ['name']
}],
order: [['time', 'ASC']],
})
if (offlineStatusRes.length) {
const cameraStatusMap = {}
// 当前逻辑
// 只要最后状态是离线 就做离线推送
// 只要出现一次上线 就做上线推送
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,
}],
name: s.camera.name,
}
}
}
let offArr = []
let onArr = []
for (let k in Object.keys(cameraStatusMap).sort()) {
const data = cameraStatusMap[k]
if (data.status[0].status == 'OFF') {
offArr.push({
name: data.name,
time: data.status[0].time,
})
}
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,
time: data.status[onLineIndex].time,
}
if (onLineLastOffIndex >= 0) {
onlineData.offTime = data.status[onLineLastOffIndex].time
}
onArr.push(onlineData)
}
}
// 查当前配置由谁配置
const corUser = await app.fs.authRequest.get(`user/${c.createUser}/message`, {
query: {
// TODO 这里拿不到 token 去鉴权 怎么能系统间鉴权呢
// 暂时取消了鉴权系统对这里的判断
token: ''
}
})
const receiver = c.cameraStatusPushReceivers.map(r => r.receiver)
// 离线推送
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`
text += offArr.map(o => `${o.name}】于【${o.time}】掉线,`).join('\n')
text += `\n请及时处理!`
await pushByEmail({
email: receiver,
title: '',
text,
})
} else if (c.pushWay == 'phone') {
// 短信
let text = `${corUser[0].namePresent}】账号下的`
if (offArr.length == 1) {
text += `${offArr[0].name}】离线,请及时处理!【${moment(offArr[0].time).format('YYYY年HH时mm分')}`
} else {
text += offArr.map(o => `${o.name}`).join('')
if (text.length > 35) {
text = text.substring(0, 35) + '...'
text += `${offArr.length}个摄像头离线`
}
text += `,请及时处理!【${moment().format('MM月DD日HH时mm分')}-${moment(timeNow).subtract(15, 'minutes').format('MM月DD日HH时mm分')}`
}
await pushBySms({
phone: receiver,
templateCode: '',
templateParam: '',
text
})
}
}
// 上线推送
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']],
// })
// }
} else if (c.pushWay == 'phone') {
// 短信
}
}
}
}
console.log('cameraOnlinePush')
} catch (error) {
}
}
)

29
code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js

@ -4,9 +4,10 @@ const moment = require('moment')
module.exports = function (app, opts) {
const freshYingshiState = app.fs.scheduleInit(
{
// interval: '* * 4 * * *',
interval: '*/10 * * * *',
interval: '* */5 * * * *',
// interval: '*/10 * * * *',
immediate: false,
proRun: true,
},
async () => {
@ -49,22 +50,39 @@ module.exports = function (app, opts) {
if (existD.online != storageD.online) {
// 状态更新
if (storageD.online == 'ON' && !existD.playUrl) {
// 播放地址更新
const playUrlRes = await getYingshiPlayUrl({ deviceSerial: d.deviceSerial, token: tokenYingshi })
storageD.playUrl = playUrlRes
}
// 在离线状态更新
await models.GbCamera.update(storageD, {
where: {
id: existD.id
}
})
// 状态更新 END
// 状态推送
// 状态向前端页面推送
cameraStatePush({
gbId: existD.id,
online: storageD.online,
ipctype: storageD.ipctype,
})
// 记录日志
const cameraRes = await models.Camera.findAll({
where: {
gbId: existD.id
}
})
const nowTime = moment().format()
let bulkCreateData = cameraRes.map(c => {
return {
cameraId: c.id,
status: storageD.online,
time: nowTime
}
})
if (bulkCreateData.length) {
await models.CameraStatusOfflineLog.bulkCreate(bulkCreateData)
}
}
} else {
const yingshiRes = await models.GbCamera.create(storageD)
@ -99,6 +117,7 @@ module.exports = function (app, opts) {
// interval: '*/30 * * * *',
interval: '0 34 5 1 * *',
immediate: true,
proRun: true,
},
async () => {
try {

4
code/VideoAccess-VCMP/api/app/lib/schedule/index.js

@ -7,10 +7,10 @@ const nodeSchedule = require('node-schedule');
module.exports = async function (app, opts) {
const scheduleInit = ({
interval, immediate
interval, immediate, proRun,
}, callback) => {
const j = nodeSchedule.scheduleJob(interval, callback);
if (immediate && !opts.dev) {
if (immediate && (!proRun || (proRun && !opts.dev))) {
setTimeout(callback, 0)
}
return j;

6
code/VideoAccess-VCMP/api/app/lib/utils/push.js

@ -5,7 +5,7 @@ const Core = require('@alicloud/pop-core');
const nodemailer = require('nodemailer')
module.exports = function (app, opts) {
const pushBySms = async ({ phone = [] } = {}) => {
const pushBySms = async ({ phone = [], templateCode, templateParam } = {}) => {
try {
if (phone.length) {
const client = new Core({
@ -29,7 +29,7 @@ module.exports = function (app, opts) {
}
}
const pushByEmail = async () => {
const pushByEmail = async ({ email = [], title, text = '', html = '', attachments = undefined } = {}) => {
try {
let transporter = nodemailer.createTransport({
host: opts.email.host,
@ -44,7 +44,7 @@ module.exports = function (app, opts) {
// send mail with defined transport object
await transporter.sendMail({
from: 'grazing-stars@qq.com', // sender address
to: email, // list of receivers 逗号分隔字符串
to: email.join(','), // list of receivers 逗号分隔字符串
subject: title, // Subject line
text: text, // plain text body
html: html, // html body

2
code/VideoAccess-VCMP/api/sequelize-automate.config.js

@ -26,7 +26,7 @@ module.exports = {
dir: './app/lib/models', // 指定输出 models 文件的目录
typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义
emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir`
tables: ['camera_status_push_log'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
tables: ['camera_status_offline_log'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面

Loading…
Cancel
Save