Files
nxxmdata/backend/routes/smart-alerts.js
2025-09-12 20:08:42 +08:00

590 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 智能预警路由
* @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;