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

1680 lines
47 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

/**
* 智能设备路由
* @file 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');
const { createSuccessResponse, createErrorResponse, createPaginatedResponse, SUCCESS_MESSAGES, ERROR_CODES } = require('../utils/apiResponse');
// 公开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] = [
{ aaid: { [Op.like]: `%${search}%` } },
{ cid: { [Op.like]: `%${search}%` } },
{ sid: { [Op.like]: `%${search}%` } }
];
}
// 查询所有数据,不分页
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(createSuccessResponse(formattedData, SUCCESS_MESSAGES.DATA_RETRIEVED, {
total: formattedData.length
}));
} catch (error) {
console.error('导出智能耳标数据失败:', error);
res.status(500).json(createErrorResponse(
'导出数据获取失败: ' + error.message,
ERROR_CODES.DATABASE_ERROR
));
}
});
// 公开获取智能耳标列表
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] = [
{ aaid: { [Op.like]: `%${search}%` } },
{ cid: { [Op.like]: `%${search}%` } },
{ sid: { [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,
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(createPaginatedResponse(
{
list: formattedData,
stats: stats
},
count,
parseInt(page),
parseInt(limit),
SUCCESS_MESSAGES.DATA_RETRIEVED
));
} catch (error) {
console.error('获取智能耳标列表失败:', error);
res.status(500).json(createErrorResponse(
'获取智能耳标列表失败: ' + error.message,
ERROR_CODES.DATABASE_ERROR
));
}
});
// 公开获取智能耳标详情
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: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* description: 每页数量
* - in: query
* name: status
* schema:
* type: string
* enum: [online, offline, alarm, maintenance]
* description: 设备状态筛选
* - in: query
* name: search
* schema:
* type: string
* description: 搜索关键词设备ID或序列号
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* sn:
* type: string
* battery:
* type: number
* rsrp:
* type: number
* bandge_status:
* type: integer
* deviceInfo:
* type: string
* temperature:
* type: number
* status:
* type: string
* steps:
* type: integer
* location:
* type: string
* updateInterval:
* type: integer
* lastUpdate:
* type: string
* total:
* type: integer
* stats:
* type: object
* pagination:
* type: object
*/
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;