/** * 统计控制器 * @file statsController.js * @description 处理数据统计相关的请求 */ const { Farm, Animal, Device, Alert, SensorData, IotCattle } = require('../models'); const { sequelize } = require('../config/database-simple'); const { Op } = require('sequelize'); /** * 获取动物总数统计(实时数据库查询) * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getAnimalCount = async (req, res) => { try { // 测试数据库连接状态 await sequelize.authenticate(); // 执行精确的SQL查询统计动物总数 const animalCountResult = await sequelize.query( 'SELECT COUNT(*) as total_animals FROM iot_cattle', { type: sequelize.QueryTypes.SELECT, raw: true } ); // 获取按类型分组的动物数量 const animalsByTypeResult = await sequelize.query( 'SELECT cate as animal_type, COUNT(*) as total_count FROM iot_cattle GROUP BY cate', { type: sequelize.QueryTypes.SELECT, raw: true } ); // 获取按健康状态分组的动物数量 const animalsByHealthResult = await sequelize.query( 'SELECT level as health_status, COUNT(*) as total_count FROM iot_cattle GROUP BY level', { type: sequelize.QueryTypes.SELECT, raw: true } ); // 获取按农场分组的动物数量 const animalsByFarmResult = await sequelize.query( 'SELECT org_id as farm_id, COUNT(*) as total_count FROM iot_cattle GROUP BY org_id', { type: sequelize.QueryTypes.SELECT, raw: true } ); const totalAnimals = animalCountResult[0]?.total_animals || 0; // 格式化类型统计数据 const typeStats = {}; animalsByTypeResult.forEach(item => { typeStats[item.type] = parseInt(item.total_count); }); // 格式化健康状态统计数据 const healthStats = { healthy: 0, sick: 0, quarantine: 0, treatment: 0 }; animalsByHealthResult.forEach(item => { healthStats[item.health_status] = parseInt(item.total_count); }); // 格式化农场统计数据 const farmStats = {}; animalsByFarmResult.forEach(item => { farmStats[`farm_${item.farm_id}`] = parseInt(item.total_count); }); const response = { totalAnimals: parseInt(totalAnimals), animalsByType: typeStats, animalsByHealth: healthStats, animalsByFarm: farmStats, lastUpdated: new Date().toISOString(), dataSource: 'mysql_realtime' }; res.status(200).json({ success: true, data: response }); } catch (error) { console.error('获取动物总数统计失败:', error); // 数据库连接错误处理 if (error.name === 'SequelizeConnectionError' || error.name === 'SequelizeConnectionRefusedError' || error.name === 'SequelizeHostNotFoundError') { return res.status(503).json({ success: false, message: '数据库连接失败,无法获取实时数据', error: 'DATABASE_CONNECTION_ERROR', details: error.message }); } // 其他数据库错误 if (error.name && error.name.startsWith('Sequelize')) { return res.status(500).json({ success: false, message: '数据库查询错误', error: 'DATABASE_QUERY_ERROR', details: error.message }); } // 通用错误处理 res.status(500).json({ success: false, message: '获取动物总数统计失败', error: 'INTERNAL_SERVER_ERROR', details: error.message }); } }; /** * 获取农场总数统计(实时数据库查询) * @param {Object} req - 请求对象 * @param {Object} res - 响应对象 */ exports.getFarmCount = async (req, res) => { try { // 测试数据库连接状态 await sequelize.authenticate(); // 执行精确的SQL查询统计农场总数 const farmCountResult = await sequelize.query( 'SELECT COUNT(*) as total_farms FROM farms', { type: sequelize.QueryTypes.SELECT, raw: true } ); // 获取按状态分组的农场数量 const farmsByStatusResult = await sequelize.query( 'SELECT status, COUNT(*) as count FROM farms GROUP BY status', { type: sequelize.QueryTypes.SELECT, raw: true } ); // 获取按类型分组的农场数量 const farmsByTypeResult = await sequelize.query( 'SELECT type, COUNT(*) as count FROM farms GROUP BY type', { type: sequelize.QueryTypes.SELECT, raw: true } ); const totalFarms = farmCountResult[0]?.total_farms || 0; // 格式化状态统计数据 const statusStats = { active: 0, inactive: 0, maintenance: 0 }; farmsByStatusResult.forEach(item => { statusStats[item.status] = parseInt(item.count); }); // 格式化类型统计数据 const typeStats = {}; farmsByTypeResult.forEach(item => { typeStats[item.type] = parseInt(item.count); }); const response = { totalFarms: parseInt(totalFarms), farmsByStatus: statusStats, farmsByType: typeStats, lastUpdated: new Date().toISOString(), dataSource: 'mysql_realtime' }; res.status(200).json({ success: true, data: response }); } catch (error) { console.error('获取农场总数统计失败:', error); // 数据库连接错误处理 if (error.name === 'SequelizeConnectionError' || error.name === 'SequelizeConnectionRefusedError' || error.name === 'SequelizeHostNotFoundError') { return res.status(503).json({ success: false, message: '数据库连接失败,无法获取实时数据', error: 'DATABASE_CONNECTION_ERROR', details: error.message }); } // 其他数据库错误 if (error.name && error.name.startsWith('Sequelize')) { return res.status(500).json({ success: false, message: '数据库查询错误', error: 'DATABASE_QUERY_ERROR', details: error.message }); } // 通用错误处理 res.status(500).json({ success: false, message: '获取农场总数统计失败', error: 'INTERNAL_SERVER_ERROR', details: error.message }); } }; /** * 获取仪表盘统计数据 * @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(), IotCattle.count(), // 修改:使用IotCattle表而不是Animal表 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([ IotCattle.count(), IotCattle.findAll({ attributes: [ 'cate', [sequelize.fn('COUNT', sequelize.col('id')), 'total_count'] ], group: ['cate'], raw: true }), IotCattle.findAll({ attributes: [ 'level', [sequelize.fn('COUNT', sequelize.col('id')), 'total_count'] ], group: ['level'], raw: true }) ]); // 格式化数据 const formattedAnimalsByType = animalsByType.map(item => ({ type: item.cate, count: parseInt(item.total_count) || 0 })); const formattedAnimalsByHealth = animalsByHealth.map(item => ({ health_status: item.level, 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 } } }), IotCattle.count({ where: { created_at: { [Op.lte]: endDate } } }), 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 }); } };