/** * 智能设备路由 * @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] = [ { sn: { [Op.like]: `%${search}%` } }, { deviceId: { [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({ 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] = [ { sn: { [Op.like]: `%${search}%` } }, { deviceId: { [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({ 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;