590 lines
20 KiB
JavaScript
590 lines
20 KiB
JavaScript
|
|
/**
|
|||
|
|
* 智能预警路由
|
|||
|
|
* @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;
|