Files
nxxmdata/backend/controllers/statsController.js
2025-09-12 20:08:42 +08:00

895 lines
23 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 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
});
}
};