Files
nxxmdata/backend/controllers/smartCollarAlertController.js

689 lines
21 KiB
JavaScript
Raw Normal View History

/**
* 智能项圈预警控制器
* @file smartCollarAlertController.js
* @description 处理智能项圈预警相关的请求
*/
const { IotXqClient } = require('../models');
const { Op } = require('sequelize');
/**
* 获取智能项圈预警统计
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
exports.getCollarAlertStats = async (req, res) => {
try {
console.log('=== 获取智能项圈预警统计 ===');
// 获取项圈设备总数
const totalDevices = await IotXqClient.count();
console.log('项圈设备总数:', totalDevices);
// 获取所有设备数据用于生成预警统计
const allDevices = await IotXqClient.findAll({
order: [['uptime', 'DESC'], ['id', 'DESC']]
});
// 统计各类预警数量
let stats = {
totalDevices: totalDevices,
lowBattery: 0,
offline: 0,
highTemperature: 0,
lowTemperature: 0,
abnormalMovement: 0,
wearOff: 0,
totalAlerts: 0
};
allDevices.forEach(device => {
const actualBattery = parseInt(device.battery) || 0;
const actualTemperature = parseFloat(device.temperature) || 0;
const totalSteps = parseInt(device.steps) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps;
// 离线预警
if (device.state === 0) {
stats.offline++;
stats.totalAlerts++;
}
// 低电量预警
if (actualBattery > 0 && actualBattery < 20) {
stats.lowBattery++;
stats.totalAlerts++;
}
// 温度预警
if (actualTemperature > 0) {
if (actualTemperature < 30) {
stats.lowTemperature++;
stats.totalAlerts++;
} else if (actualTemperature > 40) {
stats.highTemperature++;
stats.totalAlerts++;
}
}
// 异常运动预警
if (dailySteps === 0 && totalSteps > 0) {
stats.abnormalMovement++;
stats.totalAlerts++;
}
// 项圈脱落预警
if (device.bandge_status === 0) {
stats.wearOff++;
stats.totalAlerts++;
}
});
console.log('预警统计结果:', stats);
res.json({
success: true,
data: stats,
message: '获取智能项圈预警统计成功'
});
} catch (error) {
console.error('获取智能项圈预警统计失败:', error);
res.status(500).json({
success: false,
message: '获取智能项圈预警统计失败',
error: error.message
});
}
};
/**
* 获取智能项圈预警列表
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
exports.getCollarAlerts = async (req, res) => {
try {
const {
page = 1,
limit = 10,
status,
search,
alertType,
alertLevel,
startDate,
endDate
} = req.query;
const offset = (page - 1) * limit;
console.log('智能项圈预警API请求参数:', {
page, limit, status, search, alertType, alertLevel, startDate, endDate
});
// 构建查询条件
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;
}
}
// 搜索条件
if (search) {
whereConditions[Op.or] = [
{ sn: { [Op.like]: `%${search}%` } },
{ deviceId: { [Op.like]: `%${search}%` } }
];
}
// 时间范围筛选
if (startDate || endDate) {
whereConditions.uptime = {};
if (startDate) {
whereConditions.uptime[Op.gte] = new Date(startDate);
}
if (endDate) {
whereConditions.uptime[Op.lte] = new Date(endDate);
}
}
// 查询所有符合条件的设备数据
const allDevices = await IotXqClient.findAll({
where: whereConditions,
order: [['uptime', 'DESC'], ['id', 'DESC']]
});
// 生成预警数据
const allAlerts = [];
allDevices.forEach(device => {
const deviceId = device.sn || `COLLAR${device.id}`;
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
const actualBattery = parseInt(device.battery) || 0;
const actualTemperature = parseFloat(device.temperature) || 0;
const totalSteps = parseInt(device.steps) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps;
// 添加基础信息到预警的函数
const addBaseInfoToAlert = (alert) => {
alert.deviceId = device.id;
alert.deviceName = deviceId;
alert.collarNumber = deviceId;
alert.dailySteps = dailySteps;
alert.totalSteps = totalSteps;
alert.yesterdaySteps = yesterdaySteps;
alert.battery = actualBattery;
alert.temperature = actualTemperature;
alert.alertTime = alertTime;
alert.deviceStatus = device.state === 1 ? '在线' : '离线';
alert.gpsSignal = device.state === 1 ? '强' : '无';
alert.wearStatus = device.bandge_status === 1 ? '已佩戴' : '未佩戴';
alert.longitude = 116.3974 + (device.id % 100) * 0.0001;
alert.latitude = 39.9093 + (device.id % 100) * 0.0001;
return alert;
};
// 离线预警
if (device.state === 0) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_offline`,
alertType: 'offline',
alertLevel: 'high',
description: '设备已离线超过30分钟',
movementStatus: '静止'
}));
}
// 低电量预警
if (actualBattery > 0 && actualBattery < 20) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_battery`,
alertType: 'battery',
alertLevel: actualBattery < 10 ? 'high' : 'medium',
description: `设备电量低于20%,当前电量${actualBattery}%`,
movementStatus: '正常'
}));
}
// 温度预警
if (actualTemperature > 0) {
if (actualTemperature < 30) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_temperature_low`,
alertType: 'temperature',
alertLevel: actualTemperature < 20 ? 'high' : 'medium',
description: `设备温度过低,当前温度${actualTemperature}°C`,
movementStatus: '正常'
}));
} else if (actualTemperature > 40) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_temperature_high`,
alertType: 'temperature',
alertLevel: actualTemperature > 45 ? 'high' : 'medium',
description: `设备温度过高,当前温度${actualTemperature}°C`,
movementStatus: '正常'
}));
}
}
// 异常运动预警
if (dailySteps === 0 && totalSteps > 0) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_movement_zero`,
alertType: 'movement',
alertLevel: 'high',
description: '检测到步数异常当日运动量为0步可能为设备故障或动物异常',
movementStatus: '异常'
}));
}
// 项圈脱落预警
if (device.bandge_status === 0) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_wear`,
alertType: 'wear',
alertLevel: 'high',
description: '设备佩戴状态异常,可能已脱落',
movementStatus: '正常'
}));
}
});
// 预警类型筛选
let filteredAlerts = allAlerts;
if (alertType && alertType.trim() !== '') {
filteredAlerts = allAlerts.filter(alert => alert.alertType === alertType);
}
// 预警级别筛选
if (alertLevel && alertLevel.trim() !== '') {
filteredAlerts = filteredAlerts.filter(alert => alert.alertLevel === alertLevel);
}
// 计算统计数据
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
};
// 分页处理
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
});
}
};
/**
* 获取单个智能项圈预警详情
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
exports.getCollarAlertById = async (req, res) => {
try {
const { id } = req.params;
// 解析预警ID格式为 deviceId_alertType
const [deviceId, alertType] = id.split('_');
if (!deviceId || !alertType) {
return res.status(400).json({
success: false,
message: '无效的预警ID格式'
});
}
// 查找设备
const device = await IotXqClient.findByPk(deviceId);
if (!device) {
return res.status(404).json({
success: false,
message: '设备不存在'
});
}
const deviceNumber = device.sn || `COLLAR${device.id}`;
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
const actualBattery = parseInt(device.battery) || 0;
const actualTemperature = parseFloat(device.temperature) || 0;
const totalSteps = parseInt(device.steps) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps;
// 根据预警类型生成详情
let alertDetail = {
id: id,
deviceId: device.id,
deviceName: deviceNumber,
collarNumber: deviceNumber,
alertType: alertType,
alertTime: alertTime,
battery: actualBattery,
temperature: actualTemperature,
dailySteps: dailySteps,
totalSteps: totalSteps,
yesterdaySteps: yesterdaySteps,
deviceStatus: device.state === 1 ? '在线' : '离线',
gpsSignal: device.state === 1 ? '强' : '无',
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
longitude: 116.3974 + (device.id % 100) * 0.0001,
latitude: 39.9093 + (device.id % 100) * 0.0001,
movementStatus: '正常'
};
// 根据预警类型设置具体信息
switch (alertType) {
case 'offline':
alertDetail.alertLevel = 'high';
alertDetail.description = '设备已离线超过30分钟';
alertDetail.movementStatus = '静止';
break;
case 'battery':
alertDetail.alertLevel = actualBattery < 10 ? 'high' : 'medium';
alertDetail.description = `设备电量低于20%,当前电量${actualBattery}%`;
break;
case 'temperature':
if (actualTemperature < 30) {
alertDetail.alertLevel = actualTemperature < 20 ? 'high' : 'medium';
alertDetail.description = `设备温度过低,当前温度${actualTemperature}°C`;
} else if (actualTemperature > 40) {
alertDetail.alertLevel = actualTemperature > 45 ? 'high' : 'medium';
alertDetail.description = `设备温度过高,当前温度${actualTemperature}°C`;
}
break;
case 'movement':
alertDetail.alertLevel = 'high';
alertDetail.description = '检测到步数异常当日运动量为0步可能为设备故障或动物异常';
alertDetail.movementStatus = '异常';
break;
case 'wear':
alertDetail.alertLevel = 'high';
alertDetail.description = '设备佩戴状态异常,可能已脱落';
break;
}
res.json({
success: true,
data: alertDetail,
message: '获取智能项圈预警详情成功'
});
} catch (error) {
console.error('获取智能项圈预警详情失败:', error);
res.status(500).json({
success: false,
message: '获取智能项圈预警详情失败',
error: error.message
});
}
};
/**
* 处理智能项圈预警
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
exports.handleCollarAlert = async (req, res) => {
try {
const { id } = req.params;
const { action, notes, handler } = req.body;
// 解析预警ID
const [deviceId, alertType] = id.split('_');
if (!deviceId || !alertType) {
return res.status(400).json({
success: false,
message: '无效的预警ID格式'
});
}
// 这里可以实现预警处理逻辑,比如记录处理历史、发送通知等
// 目前只是返回成功响应
const result = {
alertId: id,
action: action || 'acknowledged',
notes: notes || '',
handler: handler || 'system',
processedAt: new Date().toISOString(),
status: 'processed'
};
console.log('处理智能项圈预警:', result);
res.json({
success: true,
data: result,
message: '预警处理成功'
});
} catch (error) {
console.error('处理智能项圈预警失败:', error);
res.status(500).json({
success: false,
message: '处理智能项圈预警失败',
error: error.message
});
}
};
/**
* 批量处理智能项圈预警
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
exports.batchHandleCollarAlerts = async (req, res) => {
try {
const { alertIds, action, notes, handler } = req.body;
if (!alertIds || !Array.isArray(alertIds) || alertIds.length === 0) {
return res.status(400).json({
success: false,
message: '请提供有效的预警ID列表'
});
}
const results = [];
for (const alertId of alertIds) {
const [deviceId, alertType] = alertId.split('_');
if (deviceId && alertType) {
results.push({
alertId: alertId,
action: action || 'acknowledged',
notes: notes || '',
handler: handler || 'system',
processedAt: new Date().toISOString(),
status: 'processed'
});
}
}
console.log('批量处理智能项圈预警:', results);
res.json({
success: true,
data: {
processedCount: results.length,
results: results
},
message: `成功处理 ${results.length} 个预警`
});
} catch (error) {
console.error('批量处理智能项圈预警失败:', error);
res.status(500).json({
success: false,
message: '批量处理智能项圈预警失败',
error: error.message
});
}
};
/**
* 导出智能项圈预警数据
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
exports.exportCollarAlerts = async (req, res) => {
try {
const {
search,
alertType,
alertLevel,
startDate,
endDate,
format = 'json'
} = req.query;
// 构建查询条件(与获取列表相同的逻辑)
const whereConditions = {};
if (search) {
whereConditions[Op.or] = [
{ sn: { [Op.like]: `%${search}%` } },
{ deviceId: { [Op.like]: `%${search}%` } }
];
}
if (startDate || endDate) {
whereConditions.uptime = {};
if (startDate) {
whereConditions.uptime[Op.gte] = new Date(startDate);
}
if (endDate) {
whereConditions.uptime[Op.lte] = new Date(endDate);
}
}
// 获取所有设备数据
const allDevices = await IotXqClient.findAll({
where: whereConditions,
order: [['uptime', 'DESC'], ['id', 'DESC']]
});
// 生成预警数据(与获取列表相同的逻辑)
const allAlerts = [];
allDevices.forEach(device => {
const deviceId = device.sn || `COLLAR${device.id}`;
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
const actualBattery = parseInt(device.battery) || 0;
const actualTemperature = parseFloat(device.temperature) || 0;
const totalSteps = parseInt(device.steps) || 0;
const yesterdaySteps = parseInt(device.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps;
// 添加基础信息到预警的函数
const addBaseInfoToAlert = (alert) => {
alert.deviceId = device.id;
alert.deviceName = deviceId;
alert.collarNumber = deviceId;
alert.dailySteps = dailySteps;
alert.totalSteps = totalSteps;
alert.yesterdaySteps = yesterdaySteps;
alert.battery = actualBattery;
alert.temperature = actualTemperature;
alert.alertTime = alertTime;
alert.deviceStatus = device.state === 1 ? '在线' : '离线';
alert.gpsSignal = device.state === 1 ? '强' : '无';
alert.wearStatus = device.bandge_status === 1 ? '已佩戴' : '未佩戴';
alert.longitude = 116.3974 + (device.id % 100) * 0.0001;
alert.latitude = 39.9093 + (device.id % 100) * 0.0001;
return alert;
};
// 生成各类预警(与获取列表相同的逻辑)
if (device.state === 0) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_offline`,
alertType: 'offline',
alertLevel: 'high',
description: '设备已离线超过30分钟',
movementStatus: '静止'
}));
}
if (actualBattery > 0 && actualBattery < 20) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_battery`,
alertType: 'battery',
alertLevel: actualBattery < 10 ? 'high' : 'medium',
description: `设备电量低于20%,当前电量${actualBattery}%`,
movementStatus: '正常'
}));
}
if (actualTemperature > 0) {
if (actualTemperature < 30) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_temperature_low`,
alertType: 'temperature',
alertLevel: actualTemperature < 20 ? 'high' : 'medium',
description: `设备温度过低,当前温度${actualTemperature}°C`,
movementStatus: '正常'
}));
} else if (actualTemperature > 40) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_temperature_high`,
alertType: 'temperature',
alertLevel: actualTemperature > 45 ? 'high' : 'medium',
description: `设备温度过高,当前温度${actualTemperature}°C`,
movementStatus: '正常'
}));
}
}
if (dailySteps === 0 && totalSteps > 0) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_movement_zero`,
alertType: 'movement',
alertLevel: 'high',
description: '检测到步数异常当日运动量为0步可能为设备故障或动物异常',
movementStatus: '异常'
}));
}
if (device.bandge_status === 0) {
allAlerts.push(addBaseInfoToAlert({
id: `${device.id}_wear`,
alertType: 'wear',
alertLevel: 'high',
description: '设备佩戴状态异常,可能已脱落',
movementStatus: '正常'
}));
}
});
// 应用筛选条件
let filteredAlerts = allAlerts;
if (alertType && alertType.trim() !== '') {
filteredAlerts = allAlerts.filter(alert => alert.alertType === alertType);
}
if (alertLevel && alertLevel.trim() !== '') {
filteredAlerts = filteredAlerts.filter(alert => alert.alertLevel === alertLevel);
}
// 根据格式返回数据
if (format === 'csv') {
// 这里可以实现CSV格式导出
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="collar_alerts.csv"');
res.send('CSV格式导出功能待实现');
} else {
res.json({
success: true,
data: filteredAlerts,
total: filteredAlerts.length,
message: '导出智能项圈预警数据成功'
});
}
} catch (error) {
console.error('导出智能项圈预警数据失败:', error);
res.status(500).json({
success: false,
message: '导出智能项圈预警数据失败',
error: error.message
});
}
};