/** * 统计控制器 * @file statsController.js * @description 处理数据统计相关的请求 */ const { Farm, Animal, Device, Alert, SensorData } = require('../models'); const { sequelize } = require('../config/database-simple'); const { Op } = require('sequelize'); /** * 获取仪表盘统计数据 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getDashboardStats = async (req, res) => { try { // 检查是否需要模拟500错误 if (req.query.testError === '500') { throw new Error('模拟服务器错误'); } // 检查是否需要模拟401错误 if (req.query.testError === '401') { return res.status(401).json({ success: false, message: '未授权' }); } // 从数据库获取真实统计数据 const [farmCount, animalCount, deviceCount, alertCount, onlineDeviceCount, alertsByLevel] = await Promise.all([ Farm.count(), Animal.sum('count') || 0, Device.count(), Alert.count(), Device.count({ where: { status: 'online' } }), Alert.findAll({ attributes: [ 'level', [sequelize.fn('COUNT', sequelize.col('id')), 'count'] ], group: ['level'], raw: true }) ]); // 计算设备在线率 const deviceOnlineRate = deviceCount > 0 ? (onlineDeviceCount / deviceCount) : 0; // 格式化预警级别统计 const alertLevels = { low: 0, medium: 0, high: 0, critical: 0 }; alertsByLevel.forEach(item => { alertLevels[item.level] = parseInt(item.count); }); const stats = { farmCount: farmCount || 0, animalCount: animalCount || 0, deviceCount: deviceCount || 0, alertCount: alertCount || 0, deviceOnlineRate: Math.round(deviceOnlineRate * 100) / 100, alertsByLevel: alertLevels }; res.status(200).json({ success: true, data: stats }); } catch (error) { console.error('获取仪表盘统计数据失败:', error); res.status(500).json({ success: false, message: '获取统计数据失败', error: error.message }); } }; /** * 获取养殖场统计数据 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getFarmStats = async (req, res) => { try { // 检查是否需要模拟500错误 if (req.query.testError === '500') { throw new Error('模拟服务器错误'); } // 检查是否需要模拟401错误 if (req.query.testError === '401') { return res.status(401).json({ success: false, message: '未授权' }); } // 从数据库获取真实养殖场统计数据 const [totalFarms, farmsByType, farmsByStatus] = await Promise.all([ Farm.count(), Farm.findAll({ attributes: [ 'type', [sequelize.fn('COUNT', sequelize.col('id')), 'count'] ], group: ['type'], raw: true }), Farm.findAll({ attributes: [ 'status', [sequelize.fn('COUNT', sequelize.col('id')), 'count'] ], group: ['status'], raw: true }) ]); // 格式化数据 const formattedFarmsByType = farmsByType.map(item => ({ type: item.type, count: parseInt(item.count) })); const formattedFarmsByStatus = farmsByStatus.map(item => ({ status: item.status, count: parseInt(item.count) })); const stats = { totalFarms: totalFarms || 0, farmsByType: formattedFarmsByType, farmsByStatus: formattedFarmsByStatus }; res.status(200).json({ success: true, data: stats }); } catch (error) { console.error('获取养殖场统计数据失败:', error); res.status(500).json({ success: false, message: '获取养殖场统计数据失败', error: error.message }); } }; /** * 获取动物统计数据 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getAnimalStats = async (req, res) => { try { // 检查是否需要模拟500错误 if (req.query.testError === '500') { throw new Error('模拟服务器错误'); } // 检查是否需要模拟401错误 if (req.query.testError === '401') { return res.status(401).json({ success: false, message: '未授权' }); } // 从数据库获取真实动物统计数据 const [totalAnimals, animalsByType, animalsByHealth] = await Promise.all([ Animal.sum('count') || 0, Animal.findAll({ attributes: [ 'type', [sequelize.fn('SUM', sequelize.col('count')), 'total_count'] ], group: ['type'], raw: true }), Animal.findAll({ attributes: [ 'health_status', [sequelize.fn('SUM', sequelize.col('count')), 'total_count'] ], group: ['health_status'], raw: true }) ]); // 格式化数据 const formattedAnimalsByType = animalsByType.map(item => ({ type: item.type, count: parseInt(item.total_count) || 0 })); const formattedAnimalsByHealth = animalsByHealth.map(item => ({ health_status: item.health_status, count: parseInt(item.total_count) || 0 })); const stats = { totalAnimals: totalAnimals || 0, animalsByType: formattedAnimalsByType, animalsByHealth: formattedAnimalsByHealth }; res.status(200).json({ success: true, data: stats }); } catch (error) { console.error('获取动物统计数据失败:', error); res.status(500).json({ success: false, message: '获取动物统计数据失败', error: error.message }); } }; /** * 获取设备统计数据 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getDeviceStats = async (req, res) => { try { // 检查是否需要模拟500错误 if (req.query.testError === '500') { throw new Error('模拟服务器错误'); } // 检查是否需要模拟401错误 if (req.query.testError === '401') { return res.status(401).json({ success: false, message: '未授权' }); } // 从数据库获取真实设备统计数据 const [totalDevices, devicesByType, devicesByStatus] = await Promise.all([ Device.count(), Device.findAll({ attributes: [ 'type', [sequelize.fn('COUNT', sequelize.col('id')), 'device_count'] ], group: ['type'], raw: true }), Device.findAll({ attributes: [ 'status', [sequelize.fn('COUNT', sequelize.col('id')), 'device_count'] ], group: ['status'], raw: true }) ]); // 格式化数据 const formattedDevicesByType = devicesByType.map(item => ({ type: item.type, count: parseInt(item.device_count) || 0 })); const formattedDevicesByStatus = devicesByStatus.map(item => ({ status: item.status, count: parseInt(item.device_count) || 0 })); // 计算在线率 const onlineDevices = formattedDevicesByStatus.find(item => item.status === 'online')?.count || 0; const onlineRate = totalDevices > 0 ? (onlineDevices / totalDevices) : 0; const stats = { totalDevices: totalDevices || 0, devicesByType: formattedDevicesByType, devicesByStatus: formattedDevicesByStatus, onlineRate: parseFloat(onlineRate.toFixed(2)) }; res.status(200).json({ success: true, data: stats }); } catch (error) { console.error('获取设备统计数据失败:', error); res.status(500).json({ success: false, message: '获取设备统计数据失败', error: error.message }); } }; /** * 获取预警统计数据 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getAlertStats = async (req, res) => { try { const { testError } = req.query; // 模拟401错误 if (testError === '401') { return res.status(401).json({ success: false, message: '未授权' }); } // 模拟500错误 if (testError === '500') { return res.status(500).json({ success: false, message: '服务器错误' }); } // 获取预警总数 const totalAlerts = await Alert.count(); // 按类型统计预警 const alertsByType = await Alert.findAll({ attributes: [ 'type', [sequelize.fn('COUNT', sequelize.col('id')), 'count'] ], group: ['type'] }); // 按级别统计预警 const alertsByLevel = await Alert.findAll({ attributes: [ 'level', [sequelize.fn('COUNT', sequelize.col('id')), 'count'] ], group: ['level'] }); // 按状态统计预警 const alertsByStatus = await Alert.findAll({ attributes: [ 'status', [sequelize.fn('COUNT', sequelize.col('id')), 'count'] ], group: ['status'] }); // 获取最近的预警 const recentAlerts = await Alert.findAll({ limit: 10, order: [['created_at', 'DESC']], attributes: ['id', 'type', 'level', 'message', 'created_at'] }); // 格式化数据 const formattedAlertsByType = alertsByType.map(item => ({ type: item.type, count: parseInt(item.dataValues.count) || 0 })); const formattedAlertsByLevel = alertsByLevel.map(item => ({ level: item.level, count: parseInt(item.dataValues.count) || 0 })); const formattedAlertsByStatus = alertsByStatus.map(item => ({ status: item.status, count: parseInt(item.dataValues.count) || 0 })); const stats = { totalAlerts: totalAlerts || 0, alertsByType: formattedAlertsByType, alertsByLevel: formattedAlertsByLevel, alertsByStatus: formattedAlertsByStatus, recentAlerts: recentAlerts }; res.status(200).json({ success: true, data: stats }); } catch (error) { console.error('获取预警统计数据失败:', error); res.status(500).json({ success: false, message: '获取预警统计数据失败', error: error.message }); } }; /** * 获取实时监控数据 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getMonitorData = async (req, res) => { try { const { testError } = req.query; // 模拟401错误 if (testError === '401') { return res.status(401).json({ success: false, message: '未授权' }); } // 模拟500错误 if (testError === '500') { return res.status(500).json({ success: false, message: '服务器错误' }); } // 获取设备状态统计 const devicesByStatus = await Device.findAll({ attributes: [ 'status', [sequelize.fn('COUNT', sequelize.col('id')), 'device_count'] ], group: ['status'] }); // 格式化设备状态数据 const deviceStatus = {}; devicesByStatus.forEach(item => { deviceStatus[item.status] = parseInt(item.dataValues.device_count) || 0; }); // 获取最近的预警 const recentAlerts = await Alert.findAll({ limit: 5, order: [['created_at', 'DESC']], attributes: ['id', 'type', 'level', 'message', 'created_at'] }); // 从传感器数据表获取真实环境数据 const [temperatureData, humidityData] = await Promise.all([ SensorData.findAll({ where: { sensor_type: 'temperature' }, order: [['recorded_at', 'DESC']], limit: 24, // 最近24小时数据 attributes: ['value', 'recorded_at', 'unit'], include: [{ model: Device, as: 'device', attributes: ['name'], include: [{ model: Farm, as: 'farm', attributes: ['location'] }] }] }), SensorData.findAll({ where: { sensor_type: 'humidity' }, order: [['recorded_at', 'DESC']], limit: 24, // 最近24小时数据 attributes: ['value', 'recorded_at', 'unit'], include: [{ model: Device, as: 'device', attributes: ['name'], include: [{ model: Farm, as: 'farm', attributes: ['location'] }] }] }) ]); // 格式化环境数据为前端期望的结构 const temperatureHistory = temperatureData.map(item => ({ time: item.recorded_at, value: parseFloat(item.value) })); const humidityHistory = humidityData.map(item => ({ time: item.recorded_at, value: parseFloat(item.value) })); const environmentalData = { temperature: { current: temperatureHistory.length > 0 ? temperatureHistory[0].value : 25.0, unit: '°C', history: temperatureHistory }, humidity: { current: humidityHistory.length > 0 ? humidityHistory[0].value : 65.0, unit: '%', history: humidityHistory } }; // 如果没有传感器数据,提供默认值 if (temperatureHistory.length === 0) { const now = new Date(); environmentalData.temperature.history = [{ time: now.toISOString(), value: 25.0 }]; environmentalData.temperature.current = 25.0; } if (humidityHistory.length === 0) { const now = new Date(); environmentalData.humidity.history = [{ time: now.toISOString(), value: 65.0 }]; environmentalData.humidity.current = 65.0; } const monitorData = { deviceStatus, recentAlerts, environmentData: environmentalData }; res.status(200).json({ success: true, data: monitorData }); } catch (error) { console.error('获取实时监控数据失败:', error); res.status(500).json({ success: false, message: '获取实时监控数据失败', error: error.message }); } }; /** * 获取月度数据趋势 * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getMonthlyTrends = async (req, res) => { try { // 获取最近12个月的数据 const months = []; const now = new Date(); for (let i = 11; i >= 0; i--) { const date = new Date(now.getFullYear(), now.getMonth() - i, 1); months.push({ year: date.getFullYear(), month: date.getMonth() + 1, label: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}` }); } // 获取每月的统计数据 const monthlyData = await Promise.all(months.map(async (monthInfo) => { const startDate = new Date(monthInfo.year, monthInfo.month - 1, 1); const endDate = new Date(monthInfo.year, monthInfo.month, 0, 23, 59, 59); const [farmCount, animalCount, deviceCount, alertCount] = await Promise.all([ Farm.count({ where: { created_at: { [Op.lte]: endDate } } }), Animal.sum('count', { where: { created_at: { [Op.lte]: endDate } } }) || 0, Device.count({ where: { created_at: { [Op.lte]: endDate } } }), Alert.count({ where: { created_at: { [Op.between]: [startDate, endDate] } } }) ]); return { month: monthInfo.label, farmCount: farmCount || 0, animalCount: animalCount || 0, deviceCount: deviceCount || 0, alertCount: alertCount || 0 }; })); // 格式化为图表数据 const trendData = { xAxis: monthlyData.map(item => item.month), series: [ { name: '养殖场数量', type: 'line', data: monthlyData.map(item => item.farmCount), itemStyle: { color: '#1890ff' }, areaStyle: { opacity: 0.3 } }, { name: '动物数量', type: 'line', data: monthlyData.map(item => item.animalCount), itemStyle: { color: '#52c41a' }, areaStyle: { opacity: 0.3 } }, { name: '设备数量', type: 'line', data: monthlyData.map(item => item.deviceCount), itemStyle: { color: '#faad14' }, areaStyle: { opacity: 0.3 } }, { name: '预警数量', type: 'line', data: monthlyData.map(item => item.alertCount), itemStyle: { color: '#ff4d4f' }, areaStyle: { opacity: 0.3 } } ] }; res.status(200).json({ success: true, data: trendData }); } catch (error) { console.error('获取月度数据趋势失败:', error); res.status(500).json({ success: false, message: '获取月度数据趋势失败', error: error.message }); } };