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