Files
nxxmdata/backend/controllers/smartCollarAlertController.js
2025-09-22 19:09:45 +08:00

689 lines
21 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 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
});
}
};