Files
nxxmdata/backend/controllers/statsController.js
2025-08-25 15:00:46 +08:00

670 lines
17 KiB
JavaScript

/**
* 统计控制器
* @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
});
}
};