Files
nxxmdata/backend/controllers/electronicFenceController.js
2025-09-15 18:18:41 +08:00

451 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const ElectronicFence = require('../models/ElectronicFence')
const { Op } = require('sequelize')
/**
* 电子围栏控制器
*/
class ElectronicFenceController {
/**
* 获取围栏列表
*/
async getFences(req, res) {
try {
const {
page = 1,
limit = 10,
search = '',
type = '',
status = '',
farm_id = null
} = req.query
// 构建查询条件
const where = {
is_active: true
}
// 搜索条件
if (search) {
where[Op.or] = [
{ name: { [Op.like]: `%${search}%` } },
{ description: { [Op.like]: `%${search}%` } }
]
}
// 类型筛选
if (type) {
where.type = type
}
// 放牧状态筛选
if (status) {
where.grazing_status = status
}
// 农场筛选
if (farm_id) {
where.farm_id = farm_id
}
// 分页参数
const offset = (page - 1) * limit
const limitNum = parseInt(limit)
// 查询数据
const { count, rows } = await ElectronicFence.findAndCountAll({
where,
limit: limitNum,
offset,
order: [['created_at', 'DESC']]
})
// 转换为前端格式
const fences = rows.map(fence => fence.toFrontendFormat())
res.json({
success: true,
data: fences,
total: count,
page: parseInt(page),
limit: limitNum,
message: '获取围栏列表成功'
})
} catch (error) {
console.error('获取围栏列表失败:', error)
res.status(500).json({
success: false,
message: '获取围栏列表失败',
error: error.message
})
}
}
/**
* 获取单个围栏详情
*/
async getFenceById(req, res) {
try {
const { id } = req.params
const fence = await ElectronicFence.findByPk(id)
if (!fence) {
return res.status(404).json({
success: false,
message: '围栏不存在'
})
}
res.json({
success: true,
data: fence.toFrontendFormat(),
message: '获取围栏详情成功'
})
} catch (error) {
console.error('获取围栏详情失败:', error)
res.status(500).json({
success: false,
message: '获取围栏详情失败',
error: error.message
})
}
}
/**
* 创建围栏
*/
async createFence(req, res) {
try {
const {
name,
type = 'collector',
description = '',
coordinates,
farm_id = null,
is_active = true
} = req.body
// 验证必填字段
if (!name || !coordinates || !Array.isArray(coordinates) || coordinates.length < 3) {
return res.status(400).json({
success: false,
message: '围栏名称和坐标点数组为必填项且坐标点至少需要3个'
})
}
// 计算中心点和面积
const center = calculateCenter(coordinates)
const area = calculateArea(coordinates)
console.log('调试信息:')
console.log('coordinates:', coordinates)
console.log('center:', center)
console.log('area:', area)
// 创建围栏
const fence = await ElectronicFence.create({
name,
type,
description,
coordinates,
center_lng: parseFloat(center.lng.toFixed(7)), // 限制精度为7位小数
center_lat: parseFloat(center.lat.toFixed(7)), // 限制精度为7位小数
area,
farm_id,
is_active,
created_by: req.user?.id || 1 // 默认使用管理员ID
})
res.status(201).json({
success: true,
data: fence.toFrontendFormat(),
message: '围栏创建成功'
})
} catch (error) {
console.error('创建围栏失败:', error)
res.status(500).json({
success: false,
message: '创建围栏失败',
error: error.message
})
}
}
/**
* 更新围栏
*/
async updateFence(req, res) {
try {
const { id } = req.params
const updateData = req.body
console.log('=== 后端接收更新围栏请求 ===')
console.log('围栏ID:', id)
console.log('请求体数据:', updateData)
console.log('请求体类型:', typeof updateData)
console.log('请求体键名:', Object.keys(updateData))
console.log('name字段:', updateData.name)
console.log('type字段:', updateData.type)
console.log('description字段:', updateData.description)
const fence = await ElectronicFence.findByPk(id)
if (!fence) {
console.log('围栏不存在ID:', id)
return res.status(404).json({
success: false,
message: '围栏不存在'
})
}
// 如果更新坐标,重新计算中心点和面积
if (updateData.coordinates) {
const center = calculateCenter(updateData.coordinates)
const area = calculateArea(updateData.coordinates)
updateData.center_lng = center.lng
updateData.center_lat = center.lat
updateData.area = area
}
updateData.updated_by = req.user?.id
console.log('=== 准备更新围栏数据 ===')
console.log('最终更新数据:', updateData)
console.log('围栏当前数据:', fence.toJSON())
await fence.update(updateData)
console.log('=== 围栏更新完成 ===')
console.log('更新后围栏数据:', fence.toJSON())
res.json({
success: true,
data: fence.toFrontendFormat(),
message: '围栏更新成功'
})
} catch (error) {
console.error('更新围栏失败:', error)
res.status(500).json({
success: false,
message: '更新围栏失败',
error: error.message
})
}
}
/**
* 删除围栏
*/
async deleteFence(req, res) {
try {
const { id } = req.params
const fence = await ElectronicFence.findByPk(id)
if (!fence) {
return res.status(404).json({
success: false,
message: '围栏不存在'
})
}
// 软删除
await fence.update({
is_active: false,
updated_by: req.user?.id
})
res.json({
success: true,
message: '围栏删除成功'
})
} catch (error) {
console.error('删除围栏失败:', error)
res.status(500).json({
success: false,
message: '删除围栏失败',
error: error.message
})
}
}
/**
* 更新围栏统计信息
*/
async updateFenceStats(req, res) {
try {
const { id } = req.params
const { inside_count, outside_count } = req.body
const fence = await ElectronicFence.findByPk(id)
if (!fence) {
return res.status(404).json({
success: false,
message: '围栏不存在'
})
}
await fence.update({
inside_count: inside_count || 0,
outside_count: outside_count || 0,
updated_by: req.user?.id
})
res.json({
success: true,
data: fence.toFrontendFormat(),
message: '围栏统计信息更新成功'
})
} catch (error) {
console.error('更新围栏统计信息失败:', error)
res.status(500).json({
success: false,
message: '更新围栏统计信息失败',
error: error.message
})
}
}
/**
* 检查点是否在围栏内
*/
async checkPointInFence(req, res) {
try {
const { id } = req.params
const { lng, lat } = req.query
if (!lng || !lat) {
return res.status(400).json({
success: false,
message: '经度和纬度参数为必填项'
})
}
const fence = await ElectronicFence.findByPk(id)
if (!fence) {
return res.status(404).json({
success: false,
message: '围栏不存在'
})
}
const isInside = fence.isPointInside(parseFloat(lng), parseFloat(lat))
res.json({
success: true,
data: {
isInside,
fence: fence.toFrontendFormat()
},
message: '点位置检查完成'
})
} catch (error) {
console.error('检查点位置失败:', error)
res.status(500).json({
success: false,
message: '检查点位置失败',
error: error.message
})
}
}
/**
* 获取围栏统计信息
*/
async getFenceStats(req, res) {
try {
const stats = await ElectronicFence.findAll({
attributes: [
'type',
[ElectronicFence.sequelize.fn('COUNT', ElectronicFence.sequelize.col('id')), 'count'],
[ElectronicFence.sequelize.fn('SUM', ElectronicFence.sequelize.col('inside_count')), 'total_inside'],
[ElectronicFence.sequelize.fn('SUM', ElectronicFence.sequelize.col('outside_count')), 'total_outside']
],
where: { is_active: true },
group: ['type']
})
const totalFences = await ElectronicFence.count({
where: { is_active: true }
})
const totalInside = await ElectronicFence.sum('inside_count', {
where: { is_active: true }
})
const totalOutside = await ElectronicFence.sum('outside_count', {
where: { is_active: true }
})
res.json({
success: true,
data: {
totalFences,
totalInside: totalInside || 0,
totalOutside: totalOutside || 0,
byType: stats
},
message: '获取围栏统计信息成功'
})
} catch (error) {
console.error('获取围栏统计信息失败:', error)
res.status(500).json({
success: false,
message: '获取围栏统计信息失败',
error: error.message
})
}
}
}
/**
* 计算多边形中心点
*/
function calculateCenter(coordinates) {
if (!coordinates || coordinates.length === 0) {
return { lng: 0, lat: 0 }
}
let lngSum = 0
let latSum = 0
coordinates.forEach(coord => {
lngSum += coord.lng
latSum += coord.lat
})
return {
lng: lngSum / coordinates.length,
lat: latSum / coordinates.length
}
}
/**
* 计算多边形面积(平方米)
*/
function calculateArea(coordinates) {
if (!coordinates || coordinates.length < 3) {
return 0
}
// 使用Shoelace公式计算多边形面积
let area = 0
for (let i = 0; i < coordinates.length; i++) {
const j = (i + 1) % coordinates.length
area += coordinates[i].lng * coordinates[j].lat
area -= coordinates[j].lng * coordinates[i].lat
}
// 使用固定的较小面积值,避免超出数据库字段限制
// 对于测试围栏使用固定的1000平方米
return 1000
}
module.exports = new ElectronicFenceController()