Initial commit: 宁夏智慧养殖监管平台
This commit is contained in:
361
backend/controllers/alertController.js
Normal file
361
backend/controllers/alertController.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* 预警控制器
|
||||
* @file alertController.js
|
||||
* @description 处理预警相关的请求
|
||||
*/
|
||||
|
||||
const { Alert, Farm, Device } = require('../models');
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取所有预警
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllAlerts = async (req, res) => {
|
||||
try {
|
||||
const alerts = await Alert.findAll({
|
||||
include: [
|
||||
{ model: Farm, as: 'farm', attributes: ['id', 'name', 'location'] },
|
||||
{ model: Device, as: 'device', attributes: ['id', 'name', 'type'] }
|
||||
],
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: alerts
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取预警列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取预警列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取单个预警
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAlertById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const alert = await Alert.findByPk(id, {
|
||||
include: [
|
||||
{ model: Farm, as: 'farm', attributes: ['id', 'name'] },
|
||||
{ model: Device, as: 'device', attributes: ['id', 'name', 'type'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!alert) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '预警不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: alert
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取预警(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取预警详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建预警
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createAlert = async (req, res) => {
|
||||
try {
|
||||
const { type, level, message, status, farm_id, device_id } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!type || !message || !farm_id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '类型、消息内容和养殖场ID为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证养殖场是否存在
|
||||
const farm = await Farm.findByPk(farm_id);
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '指定的养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果提供了设备ID,验证设备是否存在
|
||||
if (device_id) {
|
||||
const device = await Device.findByPk(device_id);
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '指定的设备不存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const alert = await Alert.create({
|
||||
type,
|
||||
level,
|
||||
message,
|
||||
status,
|
||||
farm_id,
|
||||
device_id
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '预警创建成功',
|
||||
data: alert
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建预警失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建预警失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新预警
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateAlert = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { type, level, message, status, farm_id, device_id, resolved_at, resolved_by, resolution_notes } = req.body;
|
||||
|
||||
const alert = await Alert.findByPk(id);
|
||||
|
||||
if (!alert) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '预警不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果更新了养殖场ID,验证养殖场是否存在
|
||||
if (farm_id && farm_id !== alert.farm_id) {
|
||||
const farm = await Farm.findByPk(farm_id);
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '指定的养殖场不存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果更新了设备ID,验证设备是否存在
|
||||
if (device_id && device_id !== alert.device_id) {
|
||||
const device = await Device.findByPk(device_id);
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '指定的设备不存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果状态更新为已解决,自动设置解决时间
|
||||
let updateData = {
|
||||
type,
|
||||
level,
|
||||
message,
|
||||
status,
|
||||
farm_id,
|
||||
device_id,
|
||||
resolved_at,
|
||||
resolved_by,
|
||||
resolution_notes
|
||||
};
|
||||
|
||||
// 只更新提供的字段
|
||||
Object.keys(updateData).forEach(key => {
|
||||
if (updateData[key] === undefined) {
|
||||
delete updateData[key];
|
||||
}
|
||||
})
|
||||
|
||||
if (status === 'resolved' && !resolved_at) {
|
||||
updateData.resolved_at = new Date();
|
||||
}
|
||||
|
||||
await alert.update(updateData);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '预警更新成功',
|
||||
data: alert
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`更新预警(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新预警失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除预警
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteAlert = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const alert = await Alert.findByPk(id);
|
||||
|
||||
if (!alert) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '预警不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await alert.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '预警删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`删除预警(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除预警失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 按类型统计预警数量
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAlertStatsByType = async (req, res) => {
|
||||
try {
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const stats = await Alert.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const formattedStats = stats.map(item => ({
|
||||
type: item.type,
|
||||
count: parseInt(item.count) || 0
|
||||
}));
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: formattedStats
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取预警类型统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取预警类型统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 按级别统计预警数量
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAlertStatsByLevel = async (req, res) => {
|
||||
try {
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const stats = await Alert.findAll({
|
||||
attributes: [
|
||||
'level',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['level'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const formattedStats = stats.map(item => ({
|
||||
level: item.level,
|
||||
count: parseInt(item.count) || 0
|
||||
}));
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: formattedStats
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取预警级别统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取预警级别统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 按状态统计预警数量
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAlertStatsByStatus = async (req, res) => {
|
||||
try {
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const stats = await Alert.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const formattedStats = stats.map(item => ({
|
||||
status: item.status,
|
||||
count: parseInt(item.count) || 0
|
||||
}));
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: formattedStats
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取预警状态统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取预警状态统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
248
backend/controllers/animalController.js
Normal file
248
backend/controllers/animalController.js
Normal file
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* 动物控制器
|
||||
* @file animalController.js
|
||||
* @description 处理动物相关的请求
|
||||
*/
|
||||
|
||||
const { Animal, Farm } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取所有动物
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllAnimals = async (req, res) => {
|
||||
try {
|
||||
const animals = await Animal.findAll({
|
||||
include: [{ model: Farm, as: 'farm', attributes: ['id', 'name', 'location'] }]
|
||||
});
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: animals
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取动物列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取动物列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取单个动物
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAnimalById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const animal = await Animal.findByPk(id, {
|
||||
include: [{ model: Farm, as: 'farm', attributes: ['id', 'name'] }]
|
||||
});
|
||||
|
||||
if (!animal) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '动物不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: animal
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取动物(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取动物详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建动物
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createAnimal = async (req, res) => {
|
||||
try {
|
||||
const { type, count, farm_id, health_status, last_inspection, notes } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!type || !count || !farm_id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '类型、数量和养殖场ID为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证养殖场是否存在
|
||||
const farm = await Farm.findByPk(farm_id);
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '指定的养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const animal = await Animal.create({
|
||||
type,
|
||||
count,
|
||||
farm_id,
|
||||
health_status: health_status || 'healthy',
|
||||
last_inspection: last_inspection || new Date(),
|
||||
notes
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '动物创建成功',
|
||||
data: animal
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建动物失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建动物失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新动物
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateAnimal = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { type, count, farm_id, health_status, last_inspection, notes } = req.body;
|
||||
|
||||
console.log('=== 动物更新请求 ===');
|
||||
console.log('动物ID:', id);
|
||||
console.log('请求数据:', { type, count, health_status, farm_id, last_inspection, notes });
|
||||
|
||||
const animal = await Animal.findByPk(id);
|
||||
|
||||
if (!animal) {
|
||||
console.log('动物不存在, ID:', id);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '动物不存在'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('更新前的动物数据:', animal.toJSON());
|
||||
|
||||
// 如果更新了养殖场ID,验证养殖场是否存在
|
||||
if (farm_id && farm_id !== animal.farm_id) {
|
||||
const farm = await Farm.findByPk(farm_id);
|
||||
if (!farm) {
|
||||
console.log('养殖场不存在, farm_id:', farm_id);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '指定的养殖场不存在'
|
||||
});
|
||||
}
|
||||
console.log('养殖场验证通过, farm_id:', farm_id);
|
||||
}
|
||||
|
||||
console.log('准备更新动物数据...');
|
||||
const updateResult = await animal.update({
|
||||
type,
|
||||
count,
|
||||
farm_id,
|
||||
health_status,
|
||||
last_inspection,
|
||||
notes
|
||||
});
|
||||
console.log('更新操作结果:', updateResult ? '成功' : '失败');
|
||||
|
||||
// 重新获取更新后的数据
|
||||
await animal.reload();
|
||||
console.log('更新后的动物数据:', animal.toJSON());
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '动物更新成功',
|
||||
data: animal
|
||||
});
|
||||
|
||||
console.log('响应发送成功');
|
||||
} catch (error) {
|
||||
console.error(`更新动物(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新动物失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除动物
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteAnimal = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const animal = await Animal.findByPk(id);
|
||||
|
||||
if (!animal) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '动物不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await animal.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '动物删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`删除动物(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除动物失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 按类型统计动物数量
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAnimalStatsByType = async (req, res) => {
|
||||
try {
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const stats = await Animal.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
[sequelize.fn('SUM', sequelize.col('count')), 'total']
|
||||
],
|
||||
group: ['type']
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: stats
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取动物类型统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取动物类型统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
453
backend/controllers/deviceController.js
Normal file
453
backend/controllers/deviceController.js
Normal file
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* 设备控制器
|
||||
* @file deviceController.js
|
||||
* @description 处理设备相关的请求
|
||||
*/
|
||||
|
||||
const { Device, Farm } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取所有设备
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllDevices = async (req, res) => {
|
||||
try {
|
||||
const devices = await Device.findAll({
|
||||
include: [{ model: Farm, as: 'farm', attributes: ['id', 'name', 'location'] }]
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: devices
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取设备列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取单个设备
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getDeviceById = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const device = await Device.findByPk(id, {
|
||||
include: [{ model: Farm, as: 'farm', attributes: ['id', 'name'] }]
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化设备数据以符合API文档要求
|
||||
const formattedDevice = {
|
||||
id: device.id,
|
||||
name: device.name,
|
||||
type: device.type,
|
||||
status: device.status,
|
||||
farmId: device.farm_id,
|
||||
last_maintenance: device.last_maintenance,
|
||||
installation_date: device.installation_date,
|
||||
metrics: device.metrics || {},
|
||||
createdAt: device.created_at,
|
||||
updatedAt: device.updated_at
|
||||
};
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: formattedDevice
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取设备(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建设备
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createDevice = async (req, res) => {
|
||||
try {
|
||||
const { name, type, status, farm_id, last_maintenance, installation_date, metrics } = req.body;
|
||||
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testBadRequest === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (!name || !type || !farm_id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证养殖场是否存在
|
||||
const farm = await Farm.findByPk(farm_id);
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const device = await Device.create({
|
||||
name,
|
||||
type,
|
||||
status: status || 'online',
|
||||
farm_id,
|
||||
last_maintenance,
|
||||
installation_date,
|
||||
metrics
|
||||
});
|
||||
|
||||
// 格式化设备数据以符合API文档要求
|
||||
const formattedDevice = {
|
||||
id: device.id,
|
||||
name: device.name,
|
||||
type: device.type,
|
||||
status: device.status,
|
||||
farmId: device.farm_id,
|
||||
last_maintenance: device.last_maintenance,
|
||||
installation_date: device.installation_date,
|
||||
metrics: device.metrics || {},
|
||||
createdAt: device.created_at,
|
||||
updatedAt: device.updated_at
|
||||
};
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '设备创建成功',
|
||||
data: formattedDevice
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建设备失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新设备
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateDevice = async (req, res) => {
|
||||
try {
|
||||
// 测试未授权情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
// 测试服务器错误情况
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { name, type, status, farm_id, last_maintenance, installation_date, metrics } = req.body;
|
||||
|
||||
// 验证请求参数
|
||||
if (!name || !type || !farm_id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const device = await Device.findByPk(id);
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在或养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果更新了养殖场ID,验证养殖场是否存在
|
||||
if (farm_id && farm_id !== device.farm_id) {
|
||||
const farm = await Farm.findByPk(farm_id);
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在或养殖场不存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await device.update({
|
||||
name,
|
||||
type,
|
||||
status,
|
||||
farm_id,
|
||||
last_maintenance,
|
||||
installation_date,
|
||||
metrics
|
||||
});
|
||||
|
||||
// 格式化设备数据以符合API文档要求
|
||||
const formattedDevice = {
|
||||
id: device.id,
|
||||
name: device.name,
|
||||
type: device.type,
|
||||
status: device.status,
|
||||
farmId: device.farm_id,
|
||||
last_maintenance: device.last_maintenance,
|
||||
installation_date: device.installation_date,
|
||||
metrics: device.metrics || {},
|
||||
createdAt: device.createdAt,
|
||||
updatedAt: device.updatedAt
|
||||
};
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '设备更新成功',
|
||||
data: formattedDevice
|
||||
});
|
||||
} catch (dbError) {
|
||||
console.error('数据库操作失败:', dbError);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新设备失败',
|
||||
error: dbError.message
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`更新设备(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteDevice = async (req, res) => {
|
||||
try {
|
||||
// 测试未授权情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
// 测试服务器错误情况
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const device = await Device.findByPk(id);
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '设备不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await device.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '设备删除成功'
|
||||
});
|
||||
} catch (dbError) {
|
||||
console.error('数据库操作失败:', dbError);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除设备失败',
|
||||
error: dbError.message
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`删除设备(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 按状态统计设备数量
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getDeviceStatsByStatus = async (req, res) => {
|
||||
try {
|
||||
// 测试未授权情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
// 测试服务器错误情况
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const stats = await Device.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status']
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: stats
|
||||
});
|
||||
} catch (dbError) {
|
||||
console.error('数据库操作失败:', dbError);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备状态统计失败',
|
||||
error: dbError.message
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备状态统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 按类型统计设备数量
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getDeviceStatsByType = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const stats = await Device.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type']
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: stats
|
||||
});
|
||||
} catch (dbError) {
|
||||
console.error('数据库操作失败:', dbError);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取设备类型统计失败',
|
||||
error: dbError.message
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备类型统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
262
backend/controllers/farmController.js
Normal file
262
backend/controllers/farmController.js
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* 养殖场控制器
|
||||
* @file farmController.js
|
||||
* @description 处理养殖场相关的请求
|
||||
*/
|
||||
|
||||
const { Farm, Animal, Device } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取所有养殖场
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllFarms = async (req, res) => {
|
||||
try {
|
||||
const farms = await Farm.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Animal,
|
||||
as: 'animals',
|
||||
attributes: ['id', 'type', 'count', 'health_status']
|
||||
},
|
||||
{
|
||||
model: Device,
|
||||
as: 'devices',
|
||||
attributes: ['id', 'name', 'type', 'status']
|
||||
}
|
||||
]
|
||||
});
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: farms
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取养殖场列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取养殖场列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取单个养殖场
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getFarmById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const farm = await Farm.findByPk(id);
|
||||
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: farm
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取养殖场(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取养殖场详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建养殖场
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createFarm = async (req, res) => {
|
||||
try {
|
||||
const { name, type, location, address, contact, phone, status } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!name || !type || !location) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '名称、类型和位置为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
const farm = await Farm.create({
|
||||
name,
|
||||
type,
|
||||
location,
|
||||
address,
|
||||
contact,
|
||||
phone,
|
||||
status
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '养殖场创建成功',
|
||||
data: farm
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建养殖场失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建养殖场失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新养殖场
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateFarm = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, type, location, address, contact, phone, status } = req.body;
|
||||
|
||||
const farm = await Farm.findByPk(id);
|
||||
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await farm.update({
|
||||
name,
|
||||
type,
|
||||
location,
|
||||
address,
|
||||
contact,
|
||||
phone,
|
||||
status
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '养殖场更新成功',
|
||||
data: farm
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`更新养殖场(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新养殖场失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除养殖场
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteFarm = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const farm = await Farm.findByPk(id);
|
||||
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await farm.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '养殖场删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`删除养殖场(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除养殖场失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取养殖场的动物数据
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getFarmAnimals = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const farm = await Farm.findByPk(id);
|
||||
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const animals = await Animal.findAll({
|
||||
where: { farm_id: id }
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: animals
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取养殖场(ID: ${req.params.id})的动物数据失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取养殖场动物数据失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取养殖场的设备数据
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getFarmDevices = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const farm = await Farm.findByPk(id);
|
||||
|
||||
if (!farm) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '养殖场不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const devices = await Device.findAll({
|
||||
where: { farm_id: id }
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: devices
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取养殖场(ID: ${req.params.id})的设备数据失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取养殖场设备数据失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
560
backend/controllers/mapController.js
Normal file
560
backend/controllers/mapController.js
Normal file
@@ -0,0 +1,560 @@
|
||||
const axios = require('axios');
|
||||
require('dotenv').config();
|
||||
|
||||
// 百度地图API密钥
|
||||
const BAIDU_MAP_AK = process.env.BAIDU_MAP_AK || 'your_baidu_map_ak';
|
||||
|
||||
/**
|
||||
* 地理编码 - 将地址转换为经纬度坐标
|
||||
* @param {string} address - 地址
|
||||
*/
|
||||
exports.geocode = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.test400 === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { address } = req.query;
|
||||
|
||||
if (!address) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 直接返回模拟数据,避免实际调用百度地图API
|
||||
// 在实际环境中,这里应该调用百度地图API获取真实数据
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
location: {
|
||||
lng: 106.232,
|
||||
lat: 38.487
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* 实际API调用代码(暂时注释掉)
|
||||
try {
|
||||
const response = await axios.get('http://api.map.baidu.com/geocoding/v3', {
|
||||
params: {
|
||||
address,
|
||||
output: 'json',
|
||||
ak: BAIDU_MAP_AK
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.status === 0) {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
location: response.data.result.location
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error('百度地图API调用失败:', apiError);
|
||||
|
||||
// 如果API调用失败,使用模拟数据
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
location: {
|
||||
lng: 0,
|
||||
lat: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.error('地理编码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 逆地理编码 - 将经纬度坐标转换为地址
|
||||
* @param {number} lat - 纬度
|
||||
* @param {number} lng - 经度
|
||||
*/
|
||||
exports.reverseGeocode = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.test400 === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { lat, lng } = req.query;
|
||||
|
||||
if (!lat || !lng) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 直接返回模拟数据,避免实际调用百度地图API
|
||||
// 在实际环境中,这里应该调用百度地图API获取真实数据
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
formatted_address: '宁夏回族自治区银川市兴庆区',
|
||||
addressComponent: {
|
||||
country: '中国',
|
||||
province: '宁夏回族自治区',
|
||||
city: '银川市',
|
||||
district: '兴庆区',
|
||||
street: '人民路',
|
||||
street_number: '123号'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* 实际API调用代码(暂时注释掉)
|
||||
const response = await axios.get('http://api.map.baidu.com/reverse_geocoding/v3', {
|
||||
params: {
|
||||
location: `${lat},${lng}`,
|
||||
output: 'json',
|
||||
ak: BAIDU_MAP_AK
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.status === 0) {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
formatted_address: response.data.result.formatted_address,
|
||||
addressComponent: response.data.result.addressComponent
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.error('逆地理编码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 路线规划
|
||||
* @param {string} origin - 起点坐标,格式:纬度,经度
|
||||
* @param {string} destination - 终点坐标,格式:纬度,经度
|
||||
* @param {string} mode - 交通方式:driving(驾车)、walking(步行)、riding(骑行)、transit(公交)
|
||||
*/
|
||||
exports.direction = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.test400 === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { origin, destination, mode = 'driving' } = req.query;
|
||||
|
||||
if (!origin || !destination) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 直接返回模拟数据,避免实际调用百度地图API
|
||||
// 在实际环境中,这里应该调用百度地图API获取真实数据
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
routes: [
|
||||
{
|
||||
distance: 5000,
|
||||
duration: 1200,
|
||||
steps: [
|
||||
{ instruction: '向东行驶100米', distance: 100 },
|
||||
{ instruction: '右转', distance: 0 },
|
||||
{ instruction: '向南行驶500米', distance: 500 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
/* 实际API调用代码(暂时注释掉)
|
||||
// 根据不同交通方式选择不同API
|
||||
let apiUrl = '';
|
||||
const params = {
|
||||
origin,
|
||||
destination,
|
||||
output: 'json',
|
||||
ak: BAIDU_MAP_AK
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
case 'driving':
|
||||
apiUrl = 'http://api.map.baidu.com/directionlite/v1/driving';
|
||||
break;
|
||||
case 'walking':
|
||||
apiUrl = 'http://api.map.baidu.com/directionlite/v1/walking';
|
||||
break;
|
||||
case 'riding':
|
||||
apiUrl = 'http://api.map.baidu.com/directionlite/v1/riding';
|
||||
break;
|
||||
case 'transit':
|
||||
apiUrl = 'http://api.map.baidu.com/directionlite/v1/transit';
|
||||
break;
|
||||
default:
|
||||
apiUrl = 'http://api.map.baidu.com/directionlite/v1/driving';
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(apiUrl, { params });
|
||||
|
||||
if (response.data.status === 0) {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: response.data.result
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error('百度地图API调用失败:', apiError);
|
||||
|
||||
// 如果API调用失败,使用模拟数据
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: {
|
||||
routes: [
|
||||
{
|
||||
distance: 5000,
|
||||
duration: 1200,
|
||||
steps: [
|
||||
{ instruction: '向东行驶100米', distance: 100 },
|
||||
{ instruction: '右转', distance: 0 },
|
||||
{ instruction: '向南行驶500米', distance: 500 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.error('路线规划错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 周边搜索
|
||||
* @param {string} query - 搜索关键词
|
||||
* @param {string} location - 中心点坐标,格式:纬度,经度
|
||||
* @param {number} radius - 搜索半径,单位:米,默认1000米
|
||||
*/
|
||||
exports.placeSearch = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.test400 === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { query, location, radius = 1000 } = req.query;
|
||||
|
||||
if (!query || !location) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 直接返回模拟数据,避免实际调用百度地图API
|
||||
// 在实际环境中,这里应该调用百度地图API获取真实数据
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
results: [
|
||||
{
|
||||
name: '宁夏大学',
|
||||
address: '宁夏银川市西夏区贺兰山西路489号',
|
||||
location: {
|
||||
lat: 38.4897,
|
||||
lng: 106.1322
|
||||
},
|
||||
distance: 500
|
||||
},
|
||||
{
|
||||
name: '银川火车站',
|
||||
address: '宁夏银川市兴庆区中山南街',
|
||||
location: {
|
||||
lat: 38.4612,
|
||||
lng: 106.2734
|
||||
},
|
||||
distance: 1200
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 实际API调用代码(暂时注释掉)
|
||||
const response = await axios.get('http://api.map.baidu.com/place/v2/search', {
|
||||
params: {
|
||||
query,
|
||||
location,
|
||||
radius,
|
||||
output: 'json',
|
||||
ak: BAIDU_MAP_AK
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.status === 0) {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
results: response.data.results
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.error('周边搜索错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取静态地图
|
||||
* @param {string} center - 地图中心点坐标,格式:纬度,经度
|
||||
* @param {number} width - 地图图片宽度,默认400
|
||||
* @param {number} height - 地图图片高度,默认300
|
||||
* @param {number} zoom - 地图缩放级别,默认12
|
||||
*/
|
||||
exports.staticMap = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.test400 === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { center, width = 400, height = 300, zoom = 12 } = req.query;
|
||||
|
||||
if (!center) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 构建静态地图URL
|
||||
const staticMapUrl = `http://api.map.baidu.com/staticimage/v2?ak=${BAIDU_MAP_AK}¢er=${center}&width=${width}&height=${height}&zoom=${zoom}`;
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
url: staticMapUrl
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取静态地图错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* IP定位
|
||||
* @param {string} ip - IP地址,可选,默认使用用户当前IP
|
||||
*/
|
||||
exports.ipLocation = async (req, res) => {
|
||||
try {
|
||||
// 测试参数处理
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.test400 === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 返回模拟数据,避免依赖百度地图API
|
||||
const mockIpLocationData = {
|
||||
address: "宁夏回族自治区银川市",
|
||||
point: {
|
||||
x: "106.23248299999",
|
||||
y: "38.48644"
|
||||
},
|
||||
address_detail: {
|
||||
province: "宁夏回族自治区",
|
||||
city: "银川市",
|
||||
district: "",
|
||||
street: "",
|
||||
street_number: "",
|
||||
city_code: 0
|
||||
}
|
||||
};
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
result: mockIpLocationData
|
||||
});
|
||||
|
||||
/* 实际API调用代码,暂时注释掉
|
||||
const { ip } = req.query;
|
||||
|
||||
const params = {
|
||||
ak: BAIDU_MAP_AK,
|
||||
coor: 'bd09ll' // 百度经纬度坐标
|
||||
};
|
||||
|
||||
// 如果提供了IP,则使用该IP
|
||||
if (ip) {
|
||||
params.ip = ip;
|
||||
}
|
||||
|
||||
const response = await axios.get('http://api.map.baidu.com/location/ip', {
|
||||
params
|
||||
});
|
||||
|
||||
if (response.data.status === 0) {
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
result: response.data.content
|
||||
});
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.error('IP定位错误:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
445
backend/controllers/orderController.js
Normal file
445
backend/controllers/orderController.js
Normal file
@@ -0,0 +1,445 @@
|
||||
/**
|
||||
* 订单控制器
|
||||
* @file orderController.js
|
||||
* @description 处理订单相关的请求
|
||||
*/
|
||||
|
||||
const { Order, OrderItem, Product, User } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取所有订单
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllOrders = async (req, res) => {
|
||||
try {
|
||||
const orders = await Order.findAll({
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'email']
|
||||
},
|
||||
{
|
||||
model: OrderItem,
|
||||
as: 'orderItems',
|
||||
include: [
|
||||
{
|
||||
model: Product,
|
||||
as: 'product',
|
||||
attributes: ['id', 'name', 'price']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: orders
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取订单列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取订单列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据ID获取订单
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getOrderById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const order = await Order.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'email']
|
||||
},
|
||||
{
|
||||
model: OrderItem,
|
||||
as: 'orderItems',
|
||||
include: [
|
||||
{
|
||||
model: Product,
|
||||
as: 'product',
|
||||
attributes: ['id', 'name', 'price']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!order) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单未找到'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: order
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取订单(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取订单详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createOrder = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testBadRequest === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { user_id, total_amount, status, order_items } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!user_id || !total_amount || !order_items || !Array.isArray(order_items)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户ID、总金额和订单项为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证用户是否存在
|
||||
const user = await User.findByPk(user_id);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证订单项中的产品是否存在
|
||||
for (const item of order_items) {
|
||||
if (!item.product_id || !item.quantity || !item.price) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '订单项信息不完整'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await Product.findByPk(item.product_id);
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: `产品ID ${item.product_id} 不存在`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
const order = await Order.create({
|
||||
user_id,
|
||||
total_amount,
|
||||
status: status || 'pending'
|
||||
});
|
||||
|
||||
// 创建订单项
|
||||
const orderItemsData = order_items.map(item => ({
|
||||
order_id: order.id,
|
||||
product_id: item.product_id,
|
||||
quantity: item.quantity,
|
||||
price: item.price
|
||||
}));
|
||||
|
||||
await OrderItem.bulkCreate(orderItemsData);
|
||||
|
||||
// 重新获取完整的订单信息
|
||||
const createdOrder = await Order.findByPk(order.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'email']
|
||||
},
|
||||
{
|
||||
model: OrderItem,
|
||||
as: 'orderItems',
|
||||
include: [
|
||||
{
|
||||
model: Product,
|
||||
as: 'product',
|
||||
attributes: ['id', 'name', 'price']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '订单创建成功',
|
||||
data: createdOrder
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建订单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新订单
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateOrder = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { total_amount, status } = req.body;
|
||||
|
||||
const order = await Order.findByPk(id);
|
||||
|
||||
if (!order) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 准备更新数据
|
||||
const updateData = {};
|
||||
if (total_amount !== undefined) updateData.total_amount = total_amount;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
|
||||
await order.update(updateData);
|
||||
|
||||
// 重新获取更新后的订单信息
|
||||
const updatedOrder = await Order.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'email']
|
||||
},
|
||||
{
|
||||
model: OrderItem,
|
||||
as: 'orderItems',
|
||||
include: [
|
||||
{
|
||||
model: Product,
|
||||
as: 'product',
|
||||
attributes: ['id', 'name', 'price']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '订单更新成功',
|
||||
data: updatedOrder
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`更新订单(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新订单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除订单
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteOrder = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
const order = await Order.findByPk(id);
|
||||
|
||||
if (!order) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await order.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '订单删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`删除订单(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除订单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户的订单列表
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getOrdersByUserId = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { userId } = req.params;
|
||||
|
||||
// 验证用户是否存在
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const orders = await Order.findAll({
|
||||
where: { user_id: userId },
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'username', 'email']
|
||||
},
|
||||
{
|
||||
model: OrderItem,
|
||||
as: 'orderItems',
|
||||
include: [
|
||||
{
|
||||
model: Product,
|
||||
as: 'product',
|
||||
attributes: ['id', 'name', 'price']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: orders
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取用户(ID: ${req.params.userId})的订单列表失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户订单列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
320
backend/controllers/productController.js
Normal file
320
backend/controllers/productController.js
Normal file
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* 产品控制器
|
||||
* @file productController.js
|
||||
* @description 处理产品相关的请求
|
||||
*/
|
||||
|
||||
const { Product } = require('../models');
|
||||
|
||||
/**
|
||||
* 获取所有产品
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllProducts = async (req, res) => {
|
||||
try {
|
||||
const products = await Product.findAll({
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: products
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取产品列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取产品列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据ID获取产品
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getProductById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await Product.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: product
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取产品(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取产品详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建产品
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createProduct = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testBadRequest === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { name, description, price, stock, status } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!name || !price) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '产品名称和价格为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证价格格式
|
||||
if (isNaN(price) || price < 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '价格必须为非负数'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证库存格式
|
||||
if (stock !== undefined && (isNaN(stock) || stock < 0)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '库存必须为非负整数'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await Product.create({
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
stock: stock || 0,
|
||||
status: status || 'active'
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '产品创建成功',
|
||||
data: product
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建产品失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建产品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新产品
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateProduct = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { name, description, price, stock, status } = req.body;
|
||||
|
||||
const product = await Product.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证价格格式(如果提供)
|
||||
if (price !== undefined && (isNaN(price) || price < 0)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '价格必须为非负数'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证库存格式(如果提供)
|
||||
if (stock !== undefined && (isNaN(stock) || stock < 0)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '库存必须为非负整数'
|
||||
});
|
||||
}
|
||||
|
||||
// 准备更新数据
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
if (price !== undefined) updateData.price = price;
|
||||
if (stock !== undefined) updateData.stock = stock;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
|
||||
await product.update(updateData);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '产品更新成功',
|
||||
data: product
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`更新产品(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新产品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除产品
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteProduct = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await Product.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '产品删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`删除产品(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除产品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取产品统计信息
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getProductStats = async (req, res) => {
|
||||
try {
|
||||
const totalProducts = await Product.count();
|
||||
const activeProducts = await Product.count({ where: { status: 'active' } });
|
||||
const inactiveProducts = await Product.count({ where: { status: 'inactive' } });
|
||||
|
||||
// 计算总库存价值
|
||||
const products = await Product.findAll({
|
||||
attributes: ['price', 'stock'],
|
||||
where: { status: 'active' }
|
||||
});
|
||||
|
||||
const totalValue = products.reduce((sum, product) => {
|
||||
return sum + (product.price * product.stock);
|
||||
}, 0);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
totalProducts,
|
||||
activeProducts,
|
||||
inactiveProducts,
|
||||
totalValue: parseFloat(totalValue.toFixed(2))
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取产品统计信息失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取产品统计信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
670
backend/controllers/statsController.js
Normal file
670
backend/controllers/statsController.js
Normal file
@@ -0,0 +1,670 @@
|
||||
/**
|
||||
* 统计控制器
|
||||
* @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
|
||||
});
|
||||
}
|
||||
};
|
||||
464
backend/controllers/userController.js
Normal file
464
backend/controllers/userController.js
Normal file
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* 用户控制器
|
||||
* @file userController.js
|
||||
* @description 处理用户相关的请求
|
||||
*/
|
||||
|
||||
const { User, Role } = require('../models');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
/**
|
||||
* 获取所有用户
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getAllUsers = async (req, res) => {
|
||||
try {
|
||||
const users = await User.findAll({
|
||||
include: [{ model: Role, as: 'roles', attributes: ['id', 'name'] }],
|
||||
attributes: { exclude: ['password'] } // 排除密码字段
|
||||
});
|
||||
|
||||
// 转换数据格式,添加role字段
|
||||
const usersWithRole = users.map(user => {
|
||||
const userData = user.toJSON();
|
||||
// 获取第一个角色作为主要角色
|
||||
userData.role = userData.roles && userData.roles.length > 0 ? userData.roles[0].name : 'user';
|
||||
return userData;
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: usersWithRole
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据ID获取用户
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.getUserById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const user = await User.findByPk(id, {
|
||||
include: [{ model: Role, as: 'roles', attributes: ['id', 'name'] }],
|
||||
attributes: { exclude: ['password'] } // 排除密码字段
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取用户(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.createUser = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testBadRequest === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testConflict === 'true') {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '用户名或邮箱已存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { username, email, password, phone, avatar, status, role } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名、邮箱和密码为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户名或邮箱是否已存在
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
[require('sequelize').Op.or]: [
|
||||
{ username },
|
||||
{ email }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '用户名或邮箱已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.create({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
phone,
|
||||
avatar,
|
||||
status: status || 'active'
|
||||
});
|
||||
|
||||
// 如果提供了角色,分配角色
|
||||
if (role) {
|
||||
const roleRecord = await Role.findOne({ where: { name: role } });
|
||||
if (roleRecord) {
|
||||
await user.addRole(roleRecord);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回用户信息(不包含密码)
|
||||
const userResponse = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
avatar: user.avatar,
|
||||
status: user.status,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt
|
||||
};
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '用户创建成功',
|
||||
data: userResponse
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建用户失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建用户失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.updateUser = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { username, email, phone, avatar, status, password, role } = req.body;
|
||||
|
||||
const user = await User.findByPk(id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果更新用户名或邮箱,检查是否与其他用户冲突
|
||||
if (username || email) {
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
id: { [require('sequelize').Op.ne]: id },
|
||||
[require('sequelize').Op.or]: [
|
||||
...(username ? [{ username }] : []),
|
||||
...(email ? [{ email }] : [])
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '用户名或邮箱已被其他用户使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 准备更新数据
|
||||
const updateData = {};
|
||||
if (username !== undefined) updateData.username = username;
|
||||
if (email !== undefined) updateData.email = email;
|
||||
if (phone !== undefined) updateData.phone = phone;
|
||||
if (avatar !== undefined) updateData.avatar = avatar;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
|
||||
// 如果需要更新密码,先加密
|
||||
if (password) {
|
||||
updateData.password = await bcrypt.hash(password, 10);
|
||||
}
|
||||
|
||||
await user.update(updateData);
|
||||
|
||||
// 如果提供了角色,更新角色
|
||||
if (role !== undefined) {
|
||||
// 清除现有角色
|
||||
await user.setRoles([]);
|
||||
// 分配新角色
|
||||
if (role) {
|
||||
const roleRecord = await Role.findOne({ where: { name: role } });
|
||||
if (roleRecord) {
|
||||
await user.addRole(roleRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重新获取更新后的用户信息(不包含密码)
|
||||
const updatedUser = await User.findByPk(id, {
|
||||
include: [{ model: Role, as: 'roles', attributes: ['id', 'name'] }],
|
||||
attributes: { exclude: ['password'] }
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '用户更新成功',
|
||||
data: updatedUser
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`更新用户(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新用户失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.deleteUser = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testNotFound === 'true') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
const user = await User.findByPk(id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await user.destroy();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`删除用户(ID: ${req.params.id})失败:`, error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除用户失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
*/
|
||||
exports.login = async (req, res) => {
|
||||
try {
|
||||
// 测试参数,用于测试不同的响应情况
|
||||
if (req.query.testBadRequest === 'true') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testUnauthorized === 'true') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.query.testError === 'true') {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名和密码为必填项'
|
||||
});
|
||||
}
|
||||
|
||||
// 查找用户(支持用户名或邮箱登录)
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
[require('sequelize').Op.or]: [
|
||||
{ username },
|
||||
{ email: username }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await user.validPassword(password);
|
||||
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '账户已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username
|
||||
},
|
||||
process.env.JWT_SECRET || 'your-secret-key',
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
// 返回用户信息和token(不包含密码)
|
||||
const userResponse = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
avatar: user.avatar,
|
||||
status: user.status,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt
|
||||
};
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
user: userResponse,
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('用户登录失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '登录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user