365 lines
10 KiB
JavaScript
365 lines
10 KiB
JavaScript
|
|
/**
|
|||
|
|
* 实时数据推送服务
|
|||
|
|
* @file realtimeService.js
|
|||
|
|
* @description 定期检查数据变化并通过WebSocket推送给客户端
|
|||
|
|
*/
|
|||
|
|
const cron = require('node-cron');
|
|||
|
|
const { Device, Alert, Animal, Farm } = require('../models');
|
|||
|
|
const { sequelize } = require('../config/database-simple');
|
|||
|
|
const webSocketManager = require('../utils/websocket');
|
|||
|
|
const notificationService = require('./notificationService');
|
|||
|
|
const logger = require('../utils/logger');
|
|||
|
|
const { Op } = require('sequelize');
|
|||
|
|
|
|||
|
|
class RealtimeService {
|
|||
|
|
constructor() {
|
|||
|
|
this.isRunning = false;
|
|||
|
|
this.lastUpdateTimes = {
|
|||
|
|
devices: null,
|
|||
|
|
alerts: null,
|
|||
|
|
animals: null,
|
|||
|
|
stats: null
|
|||
|
|
};
|
|||
|
|
this.updateInterval = 30; // 30秒更新间隔,符合文档要求
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 启动实时数据推送服务
|
|||
|
|
*/
|
|||
|
|
start() {
|
|||
|
|
if (this.isRunning) {
|
|||
|
|
logger.warn('实时数据推送服务已在运行中');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isRunning = true;
|
|||
|
|
|
|||
|
|
// 设备状态监控 - 每30秒检查一次
|
|||
|
|
cron.schedule(`*/${this.updateInterval} * * * * *`, () => {
|
|||
|
|
this.checkDeviceUpdates();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 预警监控 - 每10秒检查一次(预警更紧急)
|
|||
|
|
cron.schedule('*/10 * * * * *', () => {
|
|||
|
|
this.checkAlertUpdates();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 动物健康状态监控 - 每60秒检查一次
|
|||
|
|
cron.schedule('*/60 * * * * *', () => {
|
|||
|
|
this.checkAnimalUpdates();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 系统统计数据更新 - 每2分钟更新一次
|
|||
|
|
cron.schedule('*/120 * * * * *', () => {
|
|||
|
|
this.updateSystemStats();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info('实时数据推送服务已启动');
|
|||
|
|
console.log(`实时数据推送服务已启动,更新间隔: ${this.updateInterval}秒`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 停止实时数据推送服务
|
|||
|
|
*/
|
|||
|
|
stop() {
|
|||
|
|
this.isRunning = false;
|
|||
|
|
logger.info('实时数据推送服务已停止');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查设备状态更新
|
|||
|
|
*/
|
|||
|
|
async checkDeviceUpdates() {
|
|||
|
|
try {
|
|||
|
|
const lastCheck = this.lastUpdateTimes.devices || new Date(Date.now() - 60000); // 默认检查最近1分钟
|
|||
|
|
|
|||
|
|
const updatedDevices = await Device.findAll({
|
|||
|
|
where: {
|
|||
|
|
updated_at: {
|
|||
|
|
[Op.gt]: lastCheck
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
include: [{
|
|||
|
|
model: Farm,
|
|||
|
|
as: 'farm',
|
|||
|
|
attributes: ['id', 'name']
|
|||
|
|
}],
|
|||
|
|
order: [['updated_at', 'DESC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (updatedDevices.length > 0) {
|
|||
|
|
logger.info(`检测到 ${updatedDevices.length} 个设备状态更新`);
|
|||
|
|
|
|||
|
|
// 为每个更新的设备推送数据
|
|||
|
|
for (const device of updatedDevices) {
|
|||
|
|
webSocketManager.broadcastDeviceUpdate({
|
|||
|
|
id: device.id,
|
|||
|
|
name: device.name,
|
|||
|
|
type: device.type,
|
|||
|
|
status: device.status,
|
|||
|
|
farm_id: device.farm_id,
|
|||
|
|
farm_name: device.farm?.name,
|
|||
|
|
last_maintenance: device.last_maintenance,
|
|||
|
|
metrics: device.metrics,
|
|||
|
|
updated_at: device.updated_at
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.lastUpdateTimes.devices = new Date();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('检查设备更新失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查预警更新
|
|||
|
|
*/
|
|||
|
|
async checkAlertUpdates() {
|
|||
|
|
try {
|
|||
|
|
const lastCheck = this.lastUpdateTimes.alerts || new Date(Date.now() - 30000); // 默认检查最近30秒
|
|||
|
|
|
|||
|
|
const newAlerts = await Alert.findAll({
|
|||
|
|
where: {
|
|||
|
|
created_at: {
|
|||
|
|
[Op.gt]: lastCheck
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
include: [
|
|||
|
|
{
|
|||
|
|
model: Farm,
|
|||
|
|
as: 'farm',
|
|||
|
|
attributes: ['id', 'name', 'contact', 'phone']
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
model: Device,
|
|||
|
|
as: 'device',
|
|||
|
|
attributes: ['id', 'name', 'type']
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
order: [['created_at', 'DESC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (newAlerts.length > 0) {
|
|||
|
|
logger.info(`检测到 ${newAlerts.length} 个新预警`);
|
|||
|
|
|
|||
|
|
// 推送新预警
|
|||
|
|
for (const alert of newAlerts) {
|
|||
|
|
webSocketManager.broadcastAlert({
|
|||
|
|
id: alert.id,
|
|||
|
|
type: alert.type,
|
|||
|
|
level: alert.level,
|
|||
|
|
message: alert.message,
|
|||
|
|
status: alert.status,
|
|||
|
|
farm_id: alert.farm_id,
|
|||
|
|
farm_name: alert.farm?.name,
|
|||
|
|
device_id: alert.device_id,
|
|||
|
|
device_name: alert.device?.name,
|
|||
|
|
created_at: alert.created_at
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 如果是高级或紧急预警,立即发送通知
|
|||
|
|
if (alert.level === 'high' || alert.level === 'critical') {
|
|||
|
|
await this.sendUrgentNotification(alert);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.lastUpdateTimes.alerts = new Date();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('检查预警更新失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查动物健康状态更新
|
|||
|
|
*/
|
|||
|
|
async checkAnimalUpdates() {
|
|||
|
|
try {
|
|||
|
|
const lastCheck = this.lastUpdateTimes.animals || new Date(Date.now() - 120000); // 默认检查最近2分钟
|
|||
|
|
|
|||
|
|
const updatedAnimals = await Animal.findAll({
|
|||
|
|
where: {
|
|||
|
|
updated_at: {
|
|||
|
|
[Op.gt]: lastCheck
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
include: [{
|
|||
|
|
model: Farm,
|
|||
|
|
as: 'farm',
|
|||
|
|
attributes: ['id', 'name']
|
|||
|
|
}],
|
|||
|
|
order: [['updated_at', 'DESC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (updatedAnimals.length > 0) {
|
|||
|
|
logger.info(`检测到 ${updatedAnimals.length} 个动物健康状态更新`);
|
|||
|
|
|
|||
|
|
for (const animal of updatedAnimals) {
|
|||
|
|
webSocketManager.broadcastAnimalUpdate({
|
|||
|
|
id: animal.id,
|
|||
|
|
type: animal.type,
|
|||
|
|
count: animal.count,
|
|||
|
|
health_status: animal.health_status,
|
|||
|
|
farm_id: animal.farm_id,
|
|||
|
|
farm_name: animal.farm?.name,
|
|||
|
|
last_inspection: animal.last_inspection,
|
|||
|
|
updated_at: animal.updated_at
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.lastUpdateTimes.animals = new Date();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('检查动物更新失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新系统统计数据
|
|||
|
|
*/
|
|||
|
|
async updateSystemStats() {
|
|||
|
|
try {
|
|||
|
|
const stats = await this.getSystemStats();
|
|||
|
|
webSocketManager.broadcastStatsUpdate(stats);
|
|||
|
|
|
|||
|
|
this.lastUpdateTimes.stats = new Date();
|
|||
|
|
logger.info('系统统计数据已推送');
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('更新系统统计失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取系统统计数据
|
|||
|
|
* @returns {Promise<Object>} 统计数据
|
|||
|
|
*/
|
|||
|
|
async getSystemStats() {
|
|||
|
|
try {
|
|||
|
|
const [farmCount, deviceCount, animalCount, alertCount] = await Promise.all([
|
|||
|
|
Farm.count(),
|
|||
|
|
Device.count(),
|
|||
|
|
Animal.sum('count'),
|
|||
|
|
Alert.count({ where: { status: 'active' } })
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const deviceStatusStats = await Device.findAll({
|
|||
|
|
attributes: [
|
|||
|
|
'status',
|
|||
|
|
[sequelize.fn('COUNT', sequelize.col('status')), 'count']
|
|||
|
|
],
|
|||
|
|
group: ['status']
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const alertLevelStats = await Alert.findAll({
|
|||
|
|
where: { status: 'active' },
|
|||
|
|
attributes: [
|
|||
|
|
'level',
|
|||
|
|
[sequelize.fn('COUNT', sequelize.col('level')), 'count']
|
|||
|
|
],
|
|||
|
|
group: ['level']
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
farmCount: farmCount || 0,
|
|||
|
|
deviceCount: deviceCount || 0,
|
|||
|
|
animalCount: animalCount || 0,
|
|||
|
|
alertCount: alertCount || 0,
|
|||
|
|
deviceStatus: deviceStatusStats.reduce((acc, item) => {
|
|||
|
|
acc[item.status] = parseInt(item.dataValues.count);
|
|||
|
|
return acc;
|
|||
|
|
}, {}),
|
|||
|
|
alertLevels: alertLevelStats.reduce((acc, item) => {
|
|||
|
|
acc[item.level] = parseInt(item.dataValues.count);
|
|||
|
|
return acc;
|
|||
|
|
}, {}),
|
|||
|
|
timestamp: new Date()
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('获取系统统计数据失败:', error);
|
|||
|
|
return {
|
|||
|
|
error: '获取统计数据失败',
|
|||
|
|
timestamp: new Date()
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送紧急预警通知
|
|||
|
|
* @param {Object} alert 预警对象
|
|||
|
|
*/
|
|||
|
|
async sendUrgentNotification(alert) {
|
|||
|
|
try {
|
|||
|
|
logger.warn(`紧急预警: ${alert.message} (级别: ${alert.level})`);
|
|||
|
|
|
|||
|
|
// 发送实时WebSocket通知给管理员
|
|||
|
|
webSocketManager.broadcastAlert({
|
|||
|
|
id: alert.id,
|
|||
|
|
type: alert.type,
|
|||
|
|
level: alert.level,
|
|||
|
|
message: alert.message,
|
|||
|
|
farm_id: alert.farm_id,
|
|||
|
|
farm_name: alert.farm?.name,
|
|||
|
|
created_at: alert.created_at
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 发送邮件/短信通知
|
|||
|
|
const isUrgent = alert.level === 'critical' || alert.level === 'high';
|
|||
|
|
await notificationService.sendAlertNotification(alert, [], {
|
|||
|
|
urgent: isUrgent,
|
|||
|
|
includeSMS: alert.level === 'critical', // 仅紧急预警发送短信
|
|||
|
|
maxResponseTime: 300000 // 5分钟响应时间
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`预警通知已发送: 预警ID ${alert.id}, 紧急程度: ${isUrgent}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('发送紧急预警通知失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 模拟设备数据变化(用于演示)
|
|||
|
|
*/
|
|||
|
|
async simulateDeviceChange(deviceId) {
|
|||
|
|
try {
|
|||
|
|
const device = await Device.findByPk(deviceId);
|
|||
|
|
if (!device) return;
|
|||
|
|
|
|||
|
|
// 随机改变设备状态
|
|||
|
|
const statuses = ['online', 'offline', 'maintenance'];
|
|||
|
|
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
|
|||
|
|
|
|||
|
|
await device.update({
|
|||
|
|
status: randomStatus,
|
|||
|
|
metrics: {
|
|||
|
|
temperature: Math.random() * 10 + 20, // 20-30度
|
|||
|
|
humidity: Math.random() * 20 + 50, // 50-70%
|
|||
|
|
lastUpdate: new Date()
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`模拟设备 ${deviceId} 状态变化为: ${randomStatus}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error('模拟设备变化失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取服务状态
|
|||
|
|
* @returns {Object} 服务状态
|
|||
|
|
*/
|
|||
|
|
getStatus() {
|
|||
|
|
return {
|
|||
|
|
isRunning: this.isRunning,
|
|||
|
|
lastUpdateTimes: this.lastUpdateTimes,
|
|||
|
|
updateInterval: this.updateInterval,
|
|||
|
|
connectedClients: webSocketManager.getConnectionStats()
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建单例实例
|
|||
|
|
const realtimeService = new RealtimeService();
|
|||
|
|
|
|||
|
|
module.exports = realtimeService;
|