Files
nxxmdata/backend/routes/smart-alerts.js

590 lines
20 KiB
JavaScript
Raw Normal View History

2025-09-12 20:08:42 +08:00
/**
* 智能预警路由
* @file smart-alerts.js
* @description 定义智能预警相关的API路由
*/
const express = require('express');
const router = express.Router();
const { IotJbqClient, IotXqClient } = require('../models');
const { Op } = require('sequelize');
// 公开API路由不需要验证token
const publicRoutes = express.Router();
router.use('/public', publicRoutes);
/**
* 获取智能预警统计
*/
publicRoutes.get('/stats', async (req, res) => {
try {
// 获取耳标设备数量
const eartagCount = await IotJbqClient.count();
// 获取项圈设备数量
const collarCount = await IotXqClient.count();
// 生成耳标预警数据与预警列表API使用相同逻辑
const eartagDevices = await IotJbqClient.findAll({
order: [['uptime', 'DESC'], ['id', 'DESC']]
});
const eartagAlerts = [];
eartagDevices.forEach(device => {
const actualBattery = parseInt(device.voltage) || 0;
const actualTemperature = parseFloat(device.temperature) || 0;
const totalSteps = parseInt(device.walk) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps; // 当日步数 = 总步数 - 昨日步数
// 离线预警
if (device.state === 0) {
eartagAlerts.push({ type: 'offline' });
}
// 低电量预警
if (actualBattery > 0 && actualBattery < 20) {
eartagAlerts.push({ type: 'battery' });
}
// 温度预警
if (actualTemperature > 0) {
// 低温预警
if (actualTemperature < 30) {
eartagAlerts.push({ type: 'temperature' });
}
// 高温预警
else if (actualTemperature > 40) {
eartagAlerts.push({ type: 'temperature' });
}
}
// 异常运动预警
// 步数异常预警当日步数为0
if (dailySteps === 0 && totalSteps > 0) {
eartagAlerts.push({ type: 'movement' });
}
});
// 生成项圈预警数据(简化版本)
const collarAlerts = await IotXqClient.count({
where: {
state: 2 // 预警状态
}
});
res.json({
success: true,
data: {
totalAlerts: eartagAlerts.length + collarAlerts,
eartagAlerts: eartagAlerts.length,
collarAlerts: collarAlerts,
eartagDevices: eartagCount,
collarDevices: collarCount
},
message: '获取智能预警统计成功'
});
} catch (error) {
console.error('获取智能预警统计失败:', error);
res.status(500).json({
success: false,
message: '获取智能预警统计失败',
error: error.message
});
}
});
/**
* 获取智能耳标预警列表
*/
publicRoutes.get('/eartag', async (req, res) => {
try {
const { page = 1, limit = 10, status, search, alertType } = req.query;
const offset = (page - 1) * limit;
console.log('智能耳标预警API请求参数:', { page, limit, status, search, alertType });
console.log('alertType 详细信息:', {
value: alertType,
type: typeof alertType,
isString: typeof alertType === 'string',
isEmpty: alertType === '',
isUndefined: alertType === undefined,
isNull: alertType === null,
length: alertType ? alertType.length : 'N/A'
});
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
switch (status) {
case 'online':
whereConditions.state = 1;
break;
case 'offline':
whereConditions.state = 0;
break;
case 'alarm':
whereConditions.state = 2;
break;
case 'maintenance':
whereConditions.state = 3;
break;
}
}
// 搜索条件 - 耳标设备使用aaid字段搜索
if (search) {
whereConditions[Op.or] = [
{ aaid: { [Op.like]: `%${search}%` } },
{ cid: { [Op.like]: `%${search}%` } }
];
console.log('耳标搜索条件:', whereConditions[Op.or]);
}
// 查询所有符合条件的设备数据(用于生成预警)
const allDevices = await IotJbqClient.findAll({
where: whereConditions,
order: [
['uptime', 'DESC'],
['id', 'DESC']
]
});
// 生成预警数据 - 为每个设备生成对应的预警记录
const allAlerts = [];
allDevices.forEach(device => {
// 耳标设备编号使用aaid字段
const deviceId = device.aaid || `EARTAG${device.id}`;
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
// 获取实际设备数据
const actualBattery = parseInt(device.voltage) || 0;
const actualTemperature = parseFloat(device.temperature) || 0;
// 获取步数数据
const totalSteps = parseInt(device.walk) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps; // 当日步数 = 总步数 - 昨日步数
// 为所有预警添加当日步数字段的函数
const addDailyStepsToAlert = (alert) => {
alert.dailySteps = dailySteps;
alert.totalSteps = totalSteps;
alert.yesterdaySteps = yesterdaySteps;
return alert;
};
// 离线预警 - 基于实际设备状态生成预警
if (device.state === 0) {
allAlerts.push(addDailyStepsToAlert({
id: `${device.id}_offline`,
eartagNumber: deviceId,
alertType: 'offline',
alertLevel: 'high',
alertTime: alertTime,
battery: actualBattery,
temperature: actualTemperature,
gpsSignal: '无',
movementStatus: '静止',
description: '设备已离线超过30分钟',
longitude: 0,
latitude: 0
}));
}
// 低电量预警 - 基于实际电量数据生成预警
if (actualBattery > 0 && actualBattery < 20) {
allAlerts.push(addDailyStepsToAlert({
id: `${device.id}_battery`,
eartagNumber: deviceId,
alertType: 'battery',
alertLevel: actualBattery < 10 ? 'high' : 'medium',
alertTime: alertTime,
battery: actualBattery,
temperature: device.temperature || 0,
gpsSignal: '强',
movementStatus: '正常',
description: `设备电量低于20%,当前电量${actualBattery}%`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
// 温度预警 - 基于实际温度数据生成预警
if (actualTemperature > 0) {
// 低温预警
if (actualTemperature < 30) {
allAlerts.push(addDailyStepsToAlert({
id: `${device.id}_temperature_low`,
eartagNumber: deviceId,
alertType: 'temperature',
alertLevel: actualTemperature < 20 ? 'high' : 'medium',
alertTime: alertTime,
battery: actualBattery,
temperature: actualTemperature,
gpsSignal: '强',
movementStatus: '正常',
description: `设备温度过低,当前温度${actualTemperature}°C`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
// 高温预警
else if (actualTemperature > 40) {
allAlerts.push(addDailyStepsToAlert({
id: `${device.id}_temperature_high`,
eartagNumber: deviceId,
alertType: 'temperature',
alertLevel: actualTemperature > 45 ? 'high' : 'medium',
alertTime: alertTime,
battery: actualBattery,
temperature: actualTemperature,
gpsSignal: '强',
movementStatus: '正常',
description: `设备温度过高,当前温度${actualTemperature}°C`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
}
// 异常运动预警 - 基于实际运动数据生成预警
// 步数异常预警当日步数为0
if (dailySteps === 0 && totalSteps > 0) {
allAlerts.push(addDailyStepsToAlert({
id: `${device.id}_movement_zero`,
eartagNumber: deviceId,
alertType: 'movement',
alertLevel: 'high',
alertTime: alertTime,
battery: actualBattery,
temperature: actualTemperature,
gpsSignal: '强',
movementStatus: '异常',
description: `检测到步数异常当日运动量为0步可能为设备故障或动物异常`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
});
// 预警类型筛选
let filteredAlerts = allAlerts;
console.log('=== 开始预警类型筛选 ===');
console.log('原始预警数量:', allAlerts.length);
console.log('alertType 值:', alertType);
console.log('alertType 条件检查:', {
'alertType 存在': !!alertType,
'alertType.trim() !== ""': alertType && alertType.trim() !== '',
'alertType 类型': typeof alertType,
'alertType 长度': alertType ? alertType.length : 'N/A'
});
if (alertType && alertType.trim() !== '') {
console.log(`执行筛选,筛选类型: ${alertType}`);
filteredAlerts = allAlerts.filter(alert => alert.alertType === alertType);
console.log(`筛选结果: ${allAlerts.length} -> ${filteredAlerts.length}`);
console.log('筛选后的预警类型分布:',
filteredAlerts.reduce((acc, alert) => {
acc[alert.alertType] = (acc[alert.alertType] || 0) + 1;
return acc;
}, {})
);
} else {
console.log('跳过筛选,显示所有预警');
}
// 计算统计数据
const stats = {
lowBattery: filteredAlerts.filter(alert => alert.alertType === 'battery').length,
offline: filteredAlerts.filter(alert => alert.alertType === 'offline').length,
highTemperature: filteredAlerts.filter(alert => alert.alertType === 'temperature').length,
abnormalMovement: filteredAlerts.filter(alert => alert.alertType === 'movement').length
};
// 对筛选后的预警数据进行分页
const startIndex = parseInt(offset);
const endIndex = startIndex + parseInt(limit);
const paginatedAlerts = filteredAlerts.slice(startIndex, endIndex);
res.json({
success: true,
data: paginatedAlerts,
total: filteredAlerts.length,
stats: stats,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: filteredAlerts.length,
pages: Math.ceil(filteredAlerts.length / limit)
},
message: '获取智能耳标预警列表成功'
});
} catch (error) {
console.error('获取智能耳标预警列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能耳标预警列表失败',
error: error.message
});
}
});
/**
* 获取智能项圈预警列表
*/
publicRoutes.get('/collar', async (req, res) => {
try {
const { page = 1, limit = 10, status, search, alertType } = req.query;
const offset = (page - 1) * limit;
console.log('智能项圈预警API请求参数:', { page, limit, status, search, alertType });
console.log('alertType 详细信息:', {
value: alertType,
type: typeof alertType,
isString: typeof alertType === 'string',
isEmpty: alertType === '',
isUndefined: alertType === undefined,
isNull: alertType === null,
length: alertType ? alertType.length : 'N/A'
});
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
switch (status) {
case 'online':
whereConditions.state = 1;
break;
case 'offline':
whereConditions.state = 0;
break;
case 'alarm':
whereConditions.state = 2;
break;
case 'maintenance':
whereConditions.state = 3;
break;
}
}
// 搜索条件 - 项圈设备使用sn字段搜索
if (search) {
whereConditions[Op.or] = [
{ sn: { [Op.like]: `%${search}%` } },
{ deviceId: { [Op.like]: `%${search}%` } }
];
console.log('项圈搜索条件:', whereConditions[Op.or]);
}
// 查询数据库
const { count, rows } = await IotXqClient.findAndCountAll({
where: whereConditions,
limit: parseInt(limit),
offset: parseInt(offset),
order: [
['uptime', 'DESC'],
['id', 'DESC']
]
});
// 生成预警数据 - 为每个设备生成对应的预警记录
const alerts = [];
rows.forEach(device => {
// 项圈设备编号使用sn字段
const deviceId = device.sn || `COLLAR${device.id}`;
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
// 获取步数数据
const totalSteps = parseInt(device.steps) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps; // 当日步数 = 总步数 - 昨日步数
// 为所有预警添加当日步数字段的函数
const addDailyStepsToAlert = (alert) => {
alert.dailySteps = dailySteps;
alert.totalSteps = totalSteps;
alert.yesterdaySteps = yesterdaySteps;
return alert;
};
// 离线预警
if (device.state === 0) {
alerts.push(addDailyStepsToAlert({
id: `${device.id}_offline`,
collarNumber: deviceId,
alertType: 'offline',
alertLevel: 'high',
alertTime: alertTime,
battery: device.battery || 0,
temperature: device.temperature || 0,
gpsSignal: '无',
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
description: '设备已离线超过30分钟',
longitude: 0,
latitude: 0
}));
}
// 低电量预警
if (device.battery < 20) {
alerts.push(addDailyStepsToAlert({
id: `${device.id}_battery`,
collarNumber: deviceId,
alertType: 'battery',
alertLevel: device.battery < 10 ? 'high' : 'medium',
alertTime: alertTime,
battery: device.battery || 0,
temperature: device.temperature || 0,
gpsSignal: '强',
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
description: `设备电量低于20%,当前电量${device.battery}%`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
// 温度预警
const actualTemperature = parseFloat(device.temperature) || 0;
if (actualTemperature > 0) {
// 低温预警
if (actualTemperature < 30) {
alerts.push(addDailyStepsToAlert({
id: `${device.id}_temperature_low`,
collarNumber: deviceId,
alertType: 'temperature',
alertLevel: actualTemperature < 20 ? 'high' : 'medium',
alertTime: alertTime,
battery: device.battery || 0,
temperature: actualTemperature,
gpsSignal: '强',
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
description: `设备温度过低,当前温度${actualTemperature}°C`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
// 高温预警
else if (actualTemperature > 40) {
alerts.push(addDailyStepsToAlert({
id: `${device.id}_temperature_high`,
collarNumber: deviceId,
alertType: 'temperature',
alertLevel: actualTemperature > 45 ? 'high' : 'medium',
alertTime: alertTime,
battery: device.battery || 0,
temperature: actualTemperature,
gpsSignal: '强',
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
description: `设备温度过高,当前温度${actualTemperature}°C`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
}
// 异常运动预警 - 基于实际运动数据生成预警
// 步数异常预警当日步数为0
if (dailySteps === 0 && totalSteps > 0) {
alerts.push(addDailyStepsToAlert({
id: `${device.id}_movement_zero`,
collarNumber: deviceId,
alertType: 'movement',
alertLevel: 'high',
alertTime: alertTime,
battery: device.battery || 0,
temperature: actualTemperature,
gpsSignal: '强',
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
description: `检测到步数异常当日运动量为0步可能为设备故障或动物异常`,
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
// 项圈脱落预警
if (device.bandge_status === 0) {
alerts.push(addDailyStepsToAlert({
id: `${device.id}_wear`,
collarNumber: deviceId,
alertType: 'wear',
alertLevel: 'high',
alertTime: alertTime,
battery: device.battery || 0,
temperature: actualTemperature,
gpsSignal: '中',
wearStatus: '未佩戴',
description: '设备佩戴状态异常,可能已脱落',
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001
}));
}
});
// 预警类型筛选
let filteredAlerts = alerts;
console.log('=== 开始预警类型筛选 ===');
console.log('原始预警数量:', alerts.length);
console.log('alertType 值:', alertType);
console.log('alertType 条件检查:', {
'alertType 存在': !!alertType,
'alertType.trim() !== ""': alertType && alertType.trim() !== '',
'alertType 类型': typeof alertType,
'alertType 长度': alertType ? alertType.length : 'N/A'
});
if (alertType && alertType.trim() !== '') {
console.log(`执行筛选,筛选类型: ${alertType}`);
filteredAlerts = alerts.filter(alert => alert.alertType === alertType);
console.log(`筛选结果: ${alerts.length} -> ${filteredAlerts.length}`);
console.log('筛选后的预警类型分布:',
filteredAlerts.reduce((acc, alert) => {
acc[alert.alertType] = (acc[alert.alertType] || 0) + 1;
return acc;
}, {})
);
} else {
console.log('跳过筛选,显示所有预警');
}
// 计算统计数据
const stats = {
lowBattery: filteredAlerts.filter(alert => alert.alertType === 'battery').length,
offline: filteredAlerts.filter(alert => alert.alertType === 'offline').length,
highTemperature: filteredAlerts.filter(alert => alert.alertType === 'temperature').length,
abnormalMovement: filteredAlerts.filter(alert => alert.alertType === 'movement').length,
wearOff: filteredAlerts.filter(alert => alert.alertType === 'wear').length
};
res.json({
success: true,
data: filteredAlerts,
total: filteredAlerts.length,
stats: stats,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: filteredAlerts.length,
pages: Math.ceil(filteredAlerts.length / limit)
},
message: '获取智能项圈预警列表成功'
});
} catch (error) {
console.error('获取智能项圈预警列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能项圈预警列表失败',
error: error.message
});
}
});
module.exports = router;