Files
nxxmdata/backend/routes/smart-devices.js

1618 lines
44 KiB
JavaScript
Raw Normal View History

2025-09-12 20:08:42 +08:00
/**
* 智能设备路由
* @file smart-devices.js
* @description 智能设备相关的API路由
*/
const express = require('express');
const router = express.Router();
const { verifyToken, checkRole } = require('../middleware/auth');
const { requirePermission } = require('../middleware/permission');
const { IotXqClient, IotJbqServer, IotJbqClient } = require('../models');
const { Op } = require('sequelize');
// 公开API路由不需要验证token
const publicRoutes = express.Router();
router.use('/public', publicRoutes);
// 公开导出智能耳标数据(无分页限制)
publicRoutes.get('/eartags/export', async (req, res) => {
try {
const { search } = req.query;
// 构建查询条件
const whereConditions = {};
// 搜索条件
if (search) {
whereConditions[Op.or] = [
2025-09-16 16:07:32 +08:00
{ aaid: { [Op.like]: `%${search}%` } },
{ cid: { [Op.like]: `%${search}%` } },
{ sid: { [Op.like]: `%${search}%` } }
2025-09-12 20:08:42 +08:00
];
}
// 查询所有数据,不分页
const rows = await IotJbqClient.findAll({
where: whereConditions,
order: [
['uptime', 'DESC'], // 按更新时间倒序
['id', 'DESC'] // 次要排序按ID倒序
]
});
// 格式化数据以匹配导出需求
const formattedData = rows.map(item => {
// 计算当日运动量
const totalSteps = parseInt(item.walk) || 0;
const yesterdaySteps = parseInt(item.y_steps) || 0;
const dailySteps = totalSteps - yesterdaySteps;
// 格式化时间
let lastUpdate = '';
if (item.uptime) {
const date = new Date(item.uptime * 1000);
lastUpdate = date.toLocaleString('zh-CN');
}
// 设备状态映射
const getDeviceStatus = (state) => {
switch (state) {
case 1: return '在线';
case 2: return '离线';
default: return '未知';
}
};
// 定位信息判断
const hasLocation = item.lat && item.lon && item.lat !== '0' && item.lon !== '0';
return {
id: item.id,
eartagNumber: item.aaid || `DEV${String(item.id).padStart(6, '0')}`,
deviceStatus: getDeviceStatus(item.state),
battery: item.voltage !== null && item.voltage !== undefined ? item.voltage : '0',
temperature: item.getTemperatureValue().toFixed(2),
collectedHost: item.sid || '-',
totalMovement: totalSteps,
dailyMovement: dailySteps,
location: hasLocation ? '有定位' : '无定位',
lastUpdate: lastUpdate,
bindingStatus: item.bandge_status === 1 ? '已绑定' : '未绑定',
gpsSignal: item.getGpsSignalLevel(),
longitude: item.lon || '',
latitude: item.lat || ''
};
});
res.json({
success: true,
data: formattedData,
total: formattedData.length,
message: '导出数据获取成功'
});
} catch (error) {
console.error('导出智能耳标数据失败:', error);
res.status(500).json({
success: false,
message: '导出数据获取失败: ' + error.message
});
}
});
// 公开获取智能耳标列表
publicRoutes.get('/eartags', async (req, res) => {
try {
const { page = 1, limit = 10, status, search } = req.query;
const offset = (page - 1) * limit;
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
switch (status) {
case 'online':
whereConditions.state = 1;
break;
case 'offline':
whereConditions.state = 0;
break;
case 'alarm':
whereConditions.state = 2;
break;
case 'maintenance':
whereConditions.state = 3;
break;
}
}
// 搜索条件
if (search) {
whereConditions[Op.or] = [
2025-09-16 16:07:32 +08:00
{ aaid: { [Op.like]: `%${search}%` } },
{ cid: { [Op.like]: `%${search}%` } },
{ sid: { [Op.like]: `%${search}%` } }
2025-09-12 20:08:42 +08:00
];
}
// 查询数据库
const { count, rows } = await IotJbqClient.findAndCountAll({
where: whereConditions,
limit: parseInt(limit),
offset: parseInt(offset),
order: [
['uptime', 'DESC'], // 按更新时间倒序
['id', 'DESC'] // 次要排序按ID倒序
]
});
// 格式化数据以匹配前端UI需求
const formattedData = rows.map(item => ({
id: item.id,
sn: item.sn || item.deviceId || `DEV${String(item.id).padStart(6, '0')}`,
battery: item.battery !== null ? item.battery : '0',
rsrp: item.rsrp || '-',
bandge_status: item.bandge_status || 0,
deviceInfo: item.ver || '未知',
temperature: item.getTemperatureValue().toFixed(2),
status: item.getStatusText(),
steps: item.steps || 0,
location: item.longitude && item.latitude ? '有定位' : '无定位',
updateInterval: Math.floor((Date.now() - (item.uptime * 1000)) / 60000) || 0,
lastUpdate: item.getLastUpdateTime(),
undefinedInfo: item.is_wear ? '已佩戴' : '未佩戴',
deviceStatus: item.is_connect ? '使用中' : '离线',
longitude: item.longitude,
latitude: item.latitude,
altitude: item.altitude,
is_wear: item.is_wear,
is_connect: item.is_connect,
fence_id: item.fence_id,
gpsSignal: item.getGpsSignalLevel(),
batteryPercent: item.getBatteryPercent()
}));
// 计算统计数据
const stats = {
total: count,
online: rows.filter(item => item.state === 1).length,
offline: rows.filter(item => item.state === 0).length,
alarm: rows.filter(item => item.state === 2).length,
maintenance: rows.filter(item => item.state === 3).length
};
res.json({
success: true,
data: {
list: formattedData,
pagination: {
current: parseInt(page),
pageSize: parseInt(limit),
total: count,
pages: Math.ceil(count / parseInt(limit))
},
stats
},
message: '获取智能耳标列表成功'
});
} catch (error) {
console.error('获取智能耳标列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能耳标列表失败',
error: error.message
});
}
});
// 公开获取智能耳标详情
publicRoutes.get('/eartags/search/:deviceNumber', async (req, res) => {
try {
const { deviceNumber } = req.params;
const device = await IotJbqClient.findOne({
where: {
[Op.or]: [
{ sn: deviceNumber },
{ deviceId: deviceNumber }
]
}
});
if (!device) {
return res.status(404).json({
success: false,
message: '未找到该设备'
});
}
// 格式化设备数据
const deviceData = {
id: device.id,
sn: device.sn || device.deviceId || `DEV${String(device.id).padStart(6, '0')}`,
battery: device.battery !== null ? device.battery : '0',
rsrp: device.rsrp || '-',
bandge_status: device.bandge_status || 0,
deviceInfo: device.ver || '未知',
temperature: device.getTemperatureValue().toFixed(2),
status: device.getStatusText(),
steps: device.steps || 0,
location: device.longitude && device.latitude ? '有定位' : '无定位',
updateInterval: Math.floor((Date.now() - (device.uptime * 1000)) / 60000) || 0,
lastUpdate: device.getLastUpdateTime(),
undefinedInfo: device.is_wear ? '已佩戴' : '未佩戴',
deviceStatus: device.is_connect ? '使用中' : '离线',
longitude: device.longitude,
latitude: device.latitude,
altitude: device.altitude,
is_wear: device.is_wear,
is_connect: device.is_connect,
fence_id: device.fence_id,
gpsSignal: device.getGpsSignalLevel(),
batteryPercent: device.getBatteryPercent()
};
res.json({
success: true,
data: deviceData,
message: '获取设备详情成功'
});
} catch (error) {
console.error('搜索设备失败:', error);
res.status(500).json({
success: false,
message: '搜索设备失败',
error: error.message
});
}
});
/**
* @swagger
* components:
* schemas:
* SmartDevice:
* type: object
* properties:
* id:
* type: integer
* deviceId:
* type: string
* type:
* type: string
* enum: [eartag, anklet, collar]
* animalId:
* type: integer
* model:
* type: string
* status:
* type: string
* battery:
* type: integer
* lastUpdate:
* type: string
* format: date-time
*/
// ==================== 智能耳标 API ====================
/**
* @swagger
* /api/smart-devices/eartags/search/{deviceNumber}:
* get:
* summary: 搜索智能耳标
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: deviceNumber
* required: true
* schema:
* type: string
* description: 耳标设备编号
* responses:
* 200:
* description: 搜索成功
* 404:
* description: 未找到耳标
*/
router.get('/eartags/search/:deviceNumber', verifyToken, requirePermission('smart_eartag:view'), async (req, res) => {
try {
const { deviceNumber } = req.params;
if (!deviceNumber) {
return res.status(400).json({
success: false,
message: '请输入耳标设备编号'
});
}
// 搜索耳标设备
const eartag = await IotJbqClient.findOne({
where: {
[Op.or]: [
{ sid: deviceNumber },
{ cid: deviceNumber }
]
}
});
if (!eartag) {
return res.status(404).json({
success: false,
message: '未找到指定的耳标设备',
data: null
});
}
// 检查绑定状态
let bindingInfo = null;
try {
const Animal = require('../models/Animal');
const animal = await Animal.findOne({
where: { collar_number: deviceNumber }
});
if (animal) {
bindingInfo = {
isBound: true,
animalId: animal.id,
earTag: animal.ear_tag,
animalType: animal.getAnimalTypeText(),
breed: animal.breed,
category: animal.getCategoryText(),
boundDate: animal.created_at
};
} else {
bindingInfo = {
isBound: false,
message: '该耳标未绑定动物'
};
}
} catch (bindingError) {
console.log('绑定检查失败:', bindingError.message);
bindingInfo = {
isBound: false,
message: '无法检查绑定状态'
};
}
// 格式化设备数据 - 严格按照图片中的字段映射
const formattedData = {
id: eartag.id,
eartagNumber: eartag.getEartagNumber(), // 耳标编号 (aaid)
battery: eartag.getBatteryPercent(), // 设备电量/%
temperature: eartag.getTemperatureValue().toFixed(2), // 设备温度/°C
collectedHost: eartag.getHostId(), // 被采集主机 (sid)
totalMovement: eartag.getTotalMovement(), // 总运动量 (walk)
dailyMovement: eartag.getDailyMovement(), // 当日运动量 (y_steps)
location: eartag.hasLocation() ? '有定位' : '无定位', // 定位信息
lastUpdate: eartag.getLastUpdateTime(), // 数据最后更新时间
bindingStatus: eartag.getBandgeStatusText(), // 绑定牲畜 (bandge_status)
deviceStatus: eartag.getStatusText(), // 设备状态 (state)
wearStatus: eartag.getWearStatusText(), // 佩戴状态 (is_wear)
bindingInfo: bindingInfo,
raw: {
state: eartag.state,
voltage: eartag.voltage,
temperature_raw: eartag.temperature,
lat: eartag.lat,
lon: eartag.lon,
gps_state: eartag.gps_state,
uptime: eartag.uptime,
time: eartag.time,
uid: eartag.uid,
sid: eartag.sid,
cid: eartag.cid,
bandge_status: eartag.bandge_status,
is_wear: eartag.is_wear
}
};
res.json({
success: true,
message: '搜索成功',
data: formattedData
});
} catch (error) {
console.error('搜索耳标失败:', error);
res.status(500).json({
success: false,
message: '搜索失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/eartags:
* get:
* summary: 获取智能耳标列表
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* description: 每页数量
* - in: query
* name: status
* schema:
* type: string
* description: 设备状态筛选
* - in: query
* name: search
* schema:
* type: string
* description: 搜索关键词
* responses:
* 200:
* description: 成功获取智能耳标列表
*/
router.get('/eartags', verifyToken, requirePermission('smart_eartag:view'), async (req, res) => {
try {
const { page = 1, limit = 10, status, search } = req.query;
const offset = (page - 1) * limit;
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
switch (status) {
case 'online':
whereConditions.state = 1;
break;
case 'offline':
whereConditions.state = 0;
break;
case 'alarm':
whereConditions.state = 2;
break;
case 'maintenance':
whereConditions.state = 3;
break;
}
}
// 搜索条件
if (search) {
whereConditions[Op.or] = [
{ sid: { [Op.like]: `%${search}%` } },
{ cid: { [Op.like]: `%${search}%` } }
];
}
// 查询数据库
const { count, rows } = await IotJbqClient.findAndCountAll({
where: whereConditions,
limit: parseInt(limit),
offset: parseInt(offset),
order: [
['uptime', 'DESC'], // 按更新时间倒序
['id', 'DESC'] // 次要排序按ID倒序
]
});
// 格式化数据以匹配前端UI需求 - 严格按照图片中的字段映射
const formattedData = rows.map(item => ({
id: item.id,
// 耳标编号字段 - 使用aaid字段
eartagNumber: item.getEartagNumber(),
// 设备电量%字段 - 使用电压计算电量百分比
battery: item.getBatteryPercent(),
// 设备温度字段 - 使用temperature字段
temperature: item.getTemperatureValue().toFixed(2),
// 被采集主机字段 - 使用sid字段
collectedHost: item.getHostId(),
// 总运动量字段 - 使用walk字段
totalMovement: item.getTotalMovement(),
// 当日运动量字段 - 使用y_steps字段
dailyMovement: item.getDailyMovement(),
// 定位信息 - 检查是否有经纬度
location: item.hasLocation() ? '有定位' : '无定位',
// 数据最后更新时间
lastUpdate: item.getLastUpdateTime(),
// 绑定牲畜状态 - 使用bandge_status字段
bindingStatus: item.getBandgeStatusText(),
// 设备状态
deviceStatus: item.getStatusText(),
// 佩戴状态
wearStatus: item.getWearStatusText(),
// 保留原始数据供其他功能使用
lat: item.lat,
lon: item.lon,
gps_state: item.gps_state,
voltage: item.voltage,
state: item.state,
bandge_status: item.bandge_status,
is_wear: item.is_wear,
uptime: item.uptime,
time: item.time,
uid: item.uid,
sid: item.sid,
cid: item.cid,
source_id: item.source_id,
raw: {
state: item.state,
voltage_raw: item.voltage,
temperature_raw: item.temperature,
lat: item.lat,
lon: item.lon,
gps_state: item.gps_state,
uptime: item.uptime,
time: item.time,
uid: item.uid,
sid: item.sid,
cid: item.cid,
bandge_status: item.bandge_status,
is_wear: item.is_wear,
walk: item.walk,
y_steps: item.y_steps
}
}));
// 计算统计数据
const stats = {
total: count,
online: formattedData.filter(item => item.deviceStatus === '在线').length,
offline: formattedData.filter(item => item.deviceStatus === '离线').length,
alarm: formattedData.filter(item => item.deviceStatus === '报警').length,
maintenance: formattedData.filter(item => item.deviceStatus === '维护').length,
bound: formattedData.filter(item => item.bindingStatus === '已绑定').length,
unbound: formattedData.filter(item => item.bindingStatus === '未绑定').length
};
res.json({
success: true,
data: formattedData,
total: count,
stats,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
});
} catch (error) {
console.error('获取智能耳标列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能耳标列表失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/eartags:
* post:
* summary: 创建智能耳标
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* deviceId:
* type: string
* animalId:
* type: integer
* model:
* type: string
* responses:
* 201:
* description: 智能耳标创建成功
*/
router.post('/eartags', verifyToken, requirePermission('smart_eartag:create'), async (req, res) => {
try {
const { deviceId, animalId, model, notes } = req.body;
// 这里应该调用数据库创建操作
const newEartag = {
id: Date.now(), // 模拟ID
deviceId,
animalId,
model,
status: 'online',
battery: 100,
notes,
created_at: new Date().toISOString()
};
res.status(201).json({
success: true,
data: newEartag,
message: '智能耳标创建成功'
});
} catch (error) {
console.error('创建智能耳标失败:', error);
res.status(500).json({
success: false,
message: '创建智能耳标失败'
});
}
});
// ==================== 智能脚环 API ====================
/**
* @swagger
* /api/smart-devices/anklets:
* get:
* summary: 获取智能脚环列表
* tags: [Smart Devices]
* security:
* - bearerAuth: []
*/
router.get('/anklets', verifyToken, requirePermission('smart_anklet:view'), async (req, res) => {
try {
// 模拟数据
const anklets = [
{
id: 1,
deviceId: 'AN001',
animalName: '牛001',
model: 'SmartAnklet-V1',
status: 'active',
stepCount: 2456,
heartRate: 75,
temperature: 38.5,
lastUpdate: '2025-01-18 10:30:00'
},
{
id: 2,
deviceId: 'AN002',
animalName: '牛002',
model: 'SmartAnklet-V1',
status: 'standby',
stepCount: 1823,
heartRate: 68,
temperature: 38.2,
lastUpdate: '2025-01-18 09:15:00'
},
{
id: 3,
deviceId: 'AN003',
animalName: '羊001',
model: 'SmartAnklet-V2',
status: 'active',
stepCount: 3124,
heartRate: 82,
temperature: 39.1,
lastUpdate: '2025-01-18 10:25:00'
}
];
res.json({
success: true,
data: anklets,
total: anklets.length
});
} catch (error) {
console.error('获取智能脚环列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能脚环列表失败'
});
}
});
/**
* @swagger
* /api/smart-devices/anklets:
* post:
* summary: 创建智能脚环
* tags: [Smart Devices]
* security:
* - bearerAuth: []
*/
router.post('/anklets', verifyToken, requirePermission('smart_anklet:create'), async (req, res) => {
try {
const { deviceId, animalId, model, frequency, notes } = req.body;
const newAnklet = {
id: Date.now(),
deviceId,
animalId,
model,
frequency,
status: 'active',
stepCount: 0,
heartRate: 0,
temperature: 0,
notes,
created_at: new Date().toISOString()
};
res.status(201).json({
success: true,
data: newAnklet,
message: '智能脚环创建成功'
});
} catch (error) {
console.error('创建智能脚环失败:', error);
res.status(500).json({
success: false,
message: '创建智能脚环失败'
});
}
});
// ==================== 智能项圈 API ====================
/**
* @swagger
* /api/smart-devices/collars/search/{collarNumber}:
* get:
* summary: 搜索智能项圈
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: collarNumber
* required: true
* schema:
* type: string
* description: 项圈编号
* responses:
* 200:
* description: 搜索成功
* 404:
* description: 未找到项圈
*/
router.get('/collars/search/:collarNumber', verifyToken, requirePermission('smart_collar:view'), async (req, res) => {
try {
const { collarNumber } = req.params;
if (!collarNumber) {
return res.status(400).json({
success: false,
message: '请输入项圈编号'
});
}
// 搜索项圈设备
const collar = await IotXqClient.findOne({
where: {
[Op.or]: [
{ sn: collarNumber },
{ deviceId: collarNumber }
]
}
});
if (!collar) {
return res.status(404).json({
success: false,
message: '未找到指定的项圈设备',
data: null
});
}
// 检查绑定状态
let bindingInfo = null;
try {
const Animal = require('../models/Animal');
const animal = await Animal.findOne({
where: { collar_number: collarNumber }
});
if (animal) {
bindingInfo = {
isBound: true,
animalId: animal.id,
earTag: animal.ear_tag,
animalType: animal.getAnimalTypeText(),
breed: animal.breed,
category: animal.getCategoryText(),
boundDate: animal.created_at
};
} else {
bindingInfo = {
isBound: false,
message: '该项圈未绑定动物'
};
}
} catch (bindingError) {
console.log('绑定检查失败:', bindingError.message);
bindingInfo = {
isBound: false,
message: '无法检查绑定状态'
};
}
// 格式化设备数据
const formattedData = {
id: collar.id,
sn: collar.sn || collar.deviceId || `DEV${String(collar.id).padStart(6, '0')}`,
battery: collar.battery !== null ? collar.battery : '0',
rsrp: collar.rsrp || '-',
bandge_status: collar.bandge_status || 0,
deviceInfo: collar.ver || '未知',
temperature: collar.getTemperatureValue().toFixed(2),
status: collar.getStatusText(),
steps: collar.steps || 0,
location: collar.longitude && collar.latitude ? '有定位' : '无定位',
updateInterval: Math.floor((Date.now() - (collar.uptime * 1000)) / 60000) || 0,
lastUpdate: collar.getLastUpdateTime(),
undefinedInfo: collar.is_wear ? '已佩戴' : '未佩戴',
deviceStatus: collar.is_connect ? '使用中' : '离线',
longitude: collar.longitude,
latitude: collar.latitude,
altitude: collar.altitude,
is_wear: collar.is_wear,
is_connect: collar.is_connect,
fence_id: collar.fence_id,
gpsSignal: collar.getGpsSignalLevel(),
batteryPercent: collar.getBatteryPercent(),
bindingInfo: bindingInfo,
raw: {
state: collar.state,
battery_raw: collar.battery,
temperature_raw: collar.temperature,
nsat: collar.nsat,
uptime: collar.uptime,
time: collar.time,
uid: collar.uid,
sn: collar.sn,
rsrp: collar.rsrp,
bandge_status: collar.bandge_status
}
};
res.json({
success: true,
message: '搜索成功',
data: formattedData
});
} catch (error) {
console.error('搜索项圈失败:', error);
res.status(500).json({
success: false,
message: '搜索失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/collars:
* get:
* summary: 获取智能项圈列表
* tags: [Smart Devices]
* security:
* - bearerAuth: []
*/
router.get('/collars', verifyToken, requirePermission('smart_collar:view'), async (req, res) => {
try {
const { page = 1, limit = 10, status, search } = req.query;
const offset = (page - 1) * limit;
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
switch (status) {
case 'online':
whereConditions.state = 1;
break;
case 'offline':
whereConditions.state = 0;
break;
case 'alarm':
whereConditions.state = 2;
break;
case 'maintenance':
whereConditions.state = 3;
break;
}
}
// 搜索条件
if (search) {
whereConditions[Op.or] = [
{ deviceId: { [Op.like]: `%${search}%` } },
{ sn: { [Op.like]: `%${search}%` } }
];
}
// 查询数据库
const { count, rows } = await IotXqClient.findAndCountAll({
where: whereConditions,
limit: parseInt(limit),
offset: parseInt(offset),
order: [
['uptime', 'DESC'], // 按更新时间倒序
['id', 'DESC'] // 次要排序按ID倒序
]
});
// 格式化数据以匹配前端UI需求 - 使用正确的字段映射
const formattedData = rows.map(item => ({
id: item.id,
// 项目编号字段 - 优先使用sn字段为空时使用deviceId最后使用id
sn: item.sn || item.deviceId || `DEV${String(item.id).padStart(6, '0')}`,
// 设备电量%字段 - 直接使用battery字段的真实值
battery: item.battery !== null ? item.battery : '0',
// 设备信号字段 - 使用rsrp字段
rsrp: item.rsrp || '-',
// 绑带状态字段 - 使用bandge_status字段
bandge_status: item.bandge_status || 0,
// 其他字段 - 全部使用数据库真实数据
deviceInfo: item.ver || '未知', // 使用固件版本作为设备信息
temperature: item.getTemperatureValue().toFixed(2),
status: item.getStatusText(),
steps: item.steps || 0,
location: item.longitude && item.latitude ? '有定位' : '无定位',
updateInterval: Math.floor((Date.now() - (item.uptime * 1000)) / 60000) || 0, // 计算实际更新间隔(分钟)
lastUpdate: item.getLastUpdateTime(),
undefinedInfo: item.is_wear ? '已佩戴' : '未佩戴', // 使用佩戴状态
deviceStatus: item.is_connect ? '使用中' : '离线',
// 保留原始数据供其他功能使用
longitude: item.longitude,
latitude: item.latitude,
altitude: item.altitude,
is_wear: item.is_wear,
is_connect: item.is_connect,
fence_id: item.fence_id,
gpsSignal: item.getGpsSignalLevel(),
batteryPercent: item.getBatteryPercent(), // 保留计算后的电量百分比
raw: {
state: item.state,
battery_raw: item.battery,
temperature_raw: item.temperature,
nsat: item.nsat,
uptime: item.uptime,
time: item.time,
uid: item.uid,
sn: item.sn,
rsrp: item.rsrp,
bandge_status: item.bandge_status
}
}));
// 计算统计数据
const stats = {
total: count,
normal: formattedData.filter(item => item.status === '在线').length,
lowBattery: formattedData.filter(item => item.battery < 20).length,
maintenance: formattedData.filter(item => item.status === '维护' || !item.raw.is_connect).length,
offline: formattedData.filter(item => item.status === '离线').length
};
res.json({
success: true,
data: formattedData,
total: count,
stats,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
});
} catch (error) {
console.error('获取智能项圈列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能项圈列表失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/collars:
* post:
* summary: 创建智能项圈
* tags: [Smart Devices]
* security:
* - bearerAuth: []
*/
router.post('/collars', verifyToken, requirePermission('smart_collar:create'), async (req, res) => {
try {
const { deviceId, animalId, model, features, uploadFreq, notes } = req.body;
const newCollar = {
id: Date.now(),
deviceId,
animalId,
model,
features,
uploadFreq,
status: 'normal',
battery: 100,
gpsSignal: 5,
temperature: 0,
notes,
created_at: new Date().toISOString()
};
res.status(201).json({
success: true,
data: newCollar,
message: '智能项圈创建成功'
});
} catch (error) {
console.error('创建智能项圈失败:', error);
res.status(500).json({
success: false,
message: '创建智能项圈失败'
});
}
});
// ==================== 通用设备操作 API ====================
/**
* @swagger
* /api/smart-devices/{type}/{id}:
* put:
* summary: 更新智能设备
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: type
* required: true
* schema:
* type: string
* enum: [eartags, anklets, collars]
* - in: path
* name: id
* required: true
* schema:
* type: integer
*/
router.put('/:type/:id', verifyToken, async (req, res) => {
try {
const { type, id } = req.params;
const updateData = req.body;
// 检查权限
const permissionMap = {
'eartags': 'smart_eartag:update',
'anklets': 'smart_anklet:update',
'collars': 'smart_collar:update'
};
const requiredPermission = permissionMap[type];
if (!requiredPermission || !req.user.permissions.includes(requiredPermission)) {
return res.status(403).json({
success: false,
message: '权限不足'
});
}
// 模拟更新操作
const updatedDevice = {
id: parseInt(id),
...updateData,
updated_at: new Date().toISOString()
};
res.json({
success: true,
data: updatedDevice,
message: '设备更新成功'
});
} catch (error) {
console.error('更新设备失败:', error);
res.status(500).json({
success: false,
message: '更新设备失败'
});
}
});
/**
* @swagger
* /api/smart-devices/{type}/{id}:
* delete:
* summary: 删除智能设备
* tags: [Smart Devices]
* security:
* - bearerAuth: []
*/
router.delete('/:type/:id', verifyToken, async (req, res) => {
try {
const { type, id } = req.params;
// 检查权限
const permissionMap = {
'eartags': 'smart_eartag:delete',
'anklets': 'smart_anklet:delete',
'collars': 'smart_collar:delete'
};
const requiredPermission = permissionMap[type];
if (!requiredPermission || !req.user.permissions.includes(requiredPermission)) {
return res.status(403).json({
success: false,
message: '权限不足'
});
}
// 模拟删除操作
res.json({
success: true,
message: '设备删除成功'
});
} catch (error) {
console.error('删除设备失败:', error);
res.status(500).json({
success: false,
message: '删除设备失败'
});
}
});
// ==================== 统计数据 API ====================
/**
* @swagger
* /api/smart-devices/stats:
* get:
* summary: 获取智能设备统计数据
* tags: [Smart Devices]
* security:
* - bearerAuth: []
*/
router.get('/stats', verifyToken, requirePermission('smart_device:view'), async (req, res) => {
try {
// 模拟统计数据
const stats = {
eartags: {
total: 15,
online: 12,
offline: 2,
error: 1
},
anklets: {
total: 18,
active: 14,
standby: 3,
fault: 1
},
collars: {
total: 22,
normal: 18,
lowBattery: 3,
maintenance: 1
}
};
res.json({
success: true,
data: stats
});
} catch (error) {
console.error('获取统计数据失败:', error);
res.status(500).json({
success: false,
message: '获取统计数据失败'
});
}
});
// ==================== 智能主机 API ====================
/**
* @swagger
* /api/smart-devices/hosts:
* get:
* summary: 获取智能主机列表
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* description: 每页数量
* - in: query
* name: status
* schema:
* type: string
* description: 设备状态筛选
* - in: query
* name: search
* schema:
* type: string
* description: 搜索关键词
* responses:
* 200:
* description: 成功获取智能主机列表
*/
router.get('/hosts', verifyToken, requirePermission('smart_host:view'), async (req, res) => {
try {
const { page = 1, limit = 10, status, search } = req.query;
const offset = (page - 1) * limit;
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
switch (status) {
case 'online':
whereConditions.state = 1;
break;
case 'offline':
whereConditions.state = 0;
break;
case 'alarm':
whereConditions.state = 2;
break;
case 'maintenance':
whereConditions.state = 3;
break;
}
}
// 搜索条件
if (search) {
whereConditions[Op.or] = [
{ sid: { [Op.like]: `%${search}%` } },
{ title: { [Op.like]: `%${search}%` } }
];
}
// 查询数据库
const { count, rows } = await IotJbqServer.findAndCountAll({
where: whereConditions,
limit: parseInt(limit),
offset: parseInt(offset),
order: [
['uptime', 'DESC'], // 按更新时间倒序
['id', 'DESC'] // 次要排序按ID倒序
]
});
// 格式化数据以匹配前端UI需求
const hosts = rows.map(host => ({
id: host.id,
deviceNumber: host.sid, // 设备编号
battery: host.getBatteryPercent(), // 设备电量%
signalValue: host.getSignalText(), // 设备信号值
temperature: host.getTemperatureValue(), // 设备温度/°C
updateTime: host.getLastUpdateTime(), // 更新时间
networkStatus: host.getNetworkStatusText(), // 联网状态基于simId
gpsStatus: host.getGpsStatusText(), // GPS状态
latitude: host.lat, // 纬度
longitude: host.lon, // 经度
voltage: host.voltage, // 电压
signal: host.signa, // 信号强度数值
state: host.state, // 设备状态
title: host.title, // 设备标题
org_id: host.org_id, // 组织ID
uid: host.uid, // 用户ID
fence_id: host.fence_id, // 围栏ID
source_id: host.source_id, // 数据源ID
simId: host.simId // SIM卡ID用于判断联网状态
}));
// 计算统计数据
const stats = {
total: count,
online: rows.filter(host => host.state === 1).length,
offline: rows.filter(host => host.state === 0).length,
alarm: rows.filter(host => host.state === 2).length,
maintenance: rows.filter(host => host.state === 3).length
};
res.json({
success: true,
data: hosts,
total: count,
stats: stats,
message: '获取智能主机列表成功'
});
} catch (error) {
console.error('获取智能主机列表失败:', error);
res.status(500).json({
success: false,
message: '获取智能主机列表失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/hosts/{id}:
* get:
* summary: 获取智能主机详情
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 主机ID
* responses:
* 200:
* description: 成功获取智能主机详情
*/
router.get('/hosts/:id', verifyToken, requirePermission('smart_host:view'), async (req, res) => {
try {
const { id } = req.params;
const host = await IotJbqServer.findByPk(id);
if (!host) {
return res.status(404).json({
success: false,
message: '智能主机不存在'
});
}
// 格式化数据
const hostData = {
id: host.id,
deviceNumber: host.sid,
battery: host.getBatteryPercent(),
signalValue: host.getSignalText(),
temperature: host.getTemperatureValue(),
updateTime: host.getLastUpdateTime(),
networkStatus: host.getNetworkStatusText(), // 联网状态基于simId
gpsStatus: host.getGpsStatusText(),
latitude: host.lat,
longitude: host.lon,
voltage: host.voltage,
signal: host.signa,
state: host.state,
title: host.title,
org_id: host.org_id,
uid: host.uid,
fence_id: host.fence_id,
source_id: host.source_id,
simId: host.simId, // SIM卡ID用于判断联网状态
gps_state: host.gps_state,
ver: host.ver,
macsid: host.macsid,
ctwing: host.ctwing,
bank_lanwei: host.bank_lanwei,
bank_house: host.bank_house,
bank_item_id: host.bank_item_id
};
res.json({
success: true,
data: hostData,
message: '获取智能主机详情成功'
});
} catch (error) {
console.error('获取智能主机详情失败:', error);
res.status(500).json({
success: false,
message: '获取智能主机详情失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/hosts:
* post:
* summary: 创建智能主机
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* sid:
* type: string
* title:
* type: string
* org_id:
* type: integer
* uid:
* type: integer
* responses:
* 201:
* description: 智能主机创建成功
*/
router.post('/hosts', verifyToken, requirePermission('smart_host:create'), async (req, res) => {
try {
const { sid, title, org_id, uid } = req.body;
const newHost = await IotJbqServer.create({
sid,
title: title || '',
org_id: org_id || 0,
uid: uid || 0,
time: Math.floor(Date.now() / 1000),
uptime: Math.floor(Date.now() / 1000),
state: 0,
gps_state: 'V',
lat: '90',
lon: '0',
signa: '0',
voltage: '100',
temperature: '25',
ver: '0',
fence_id: 0
});
res.status(201).json({
success: true,
data: newHost,
message: '智能主机创建成功'
});
} catch (error) {
console.error('创建智能主机失败:', error);
res.status(500).json({
success: false,
message: '创建智能主机失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/hosts/{id}:
* put:
* summary: 更新智能主机
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 主机ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* title:
* type: string
* state:
* type: integer
* responses:
* 200:
* description: 智能主机更新成功
*/
router.put('/hosts/:id', verifyToken, requirePermission('smart_host:update'), async (req, res) => {
try {
const { id } = req.params;
const { title, state } = req.body;
const host = await IotJbqServer.findByPk(id);
if (!host) {
return res.status(404).json({
success: false,
message: '智能主机不存在'
});
}
// 更新字段
if (title !== undefined) host.title = title;
if (state !== undefined) host.state = state;
// 更新上传时间
host.uptime = Math.floor(Date.now() / 1000);
await host.save();
res.json({
success: true,
data: host,
message: '智能主机更新成功'
});
} catch (error) {
console.error('更新智能主机失败:', error);
res.status(500).json({
success: false,
message: '更新智能主机失败',
error: error.message
});
}
});
/**
* @swagger
* /api/smart-devices/hosts/{id}:
* delete:
* summary: 删除智能主机
* tags: [Smart Devices]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 主机ID
* responses:
* 200:
* description: 智能主机删除成功
*/
router.delete('/hosts/:id', verifyToken, requirePermission('smart_host:delete'), async (req, res) => {
try {
const { id } = req.params;
const host = await IotJbqServer.findByPk(id);
if (!host) {
return res.status(404).json({
success: false,
message: '智能主机不存在'
});
}
await host.destroy();
res.json({
success: true,
message: '智能主机删除成功'
});
} catch (error) {
console.error('删除智能主机失败:', error);
res.status(500).json({
success: false,
message: '删除智能主机失败',
error: error.message
});
}
});
module.exports = router;