const axios = require('axios');
const fs = require('fs');
const path = require('path');
const logDirectory = path.join(__dirname, 'log');
const express = require('express');
const app = express();
const cors = require('cors');
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
const mqtt = require('mqtt');
const WebSocket = require('ws');
const ws = require('ws');
const schedule = require('node-schedule');
//测试服
const wsUrl = 'ws://192.168.1.252:8852/eam/websocket/lora';
// const wsUrl = 'ws://192.168.1.195:8081/eam/websocket/lora';
let wsClient = new ws(wsUrl);
if(!fs.existsSync(logDirectory)) {
fs.mkdirSync(logDirectory);
}
function getLogFileName(){
const date = new Date();
const year =date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return path.join(logDirectory, `${year}-${month}-${day}.txt`);
}
function cleanOldLogs(){
const files = fs.readdirSync(logDirectory);
const now = new Date();
files.forEach(file => {
const filePath = path.join(logDirectory, file);
const fileStats = fs.statSync(filePath);
const fileDate = new Date(fileStats.mtime);
const diffTime = Math.abs(now - fileDate);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays > 15){
fs.unlinkSync(filePath);
}
});
}
function logMessage(message) {
const logFileName = getLogFileName();
const logStream = fs.createWriteStream(logFileName, { flags: 'a' });
logStream.write(message);
logStream.end();
}
const originalLog = console.log;
const originalError = console.error;
console.log = (...args) => {
const message = `${new Date().toISOString()} - LOG:${args.join(' ')}\n`;
logMessage(message);
originalLog.apply(console, args);
};
console.error = (...args) => {
const message = `${new Date().toISOString()} - ERROR:${args.join(' ')}\n`;
logMessage(message);
originalError.apply(console, args);
};
//以上为日志相关以及依赖库引用
setInterval(cleanOldLogs, 24 * 60 * 60 * 1000);
//redis
const redis = require('redis');
const { status } = require('express/lib/response');
const { off } = require('process');
const client = redis.createClient({
// url: 'redis://192.168.1.252:6379'
url: 'redis://127.0.0.1:6379'
});
client.on('error', (err) => {
console.error('Redis Client Error', err);
});
client.connect().then(() => {
console.log('Connected to Redis');
client.select(0)
.then(() => {
console.log('已切换到数据库', 0);
})
.catch(error => {
console.error('数据库切换失败', error);
});
}).catch((err) => {
console.error('Failed to connect to Redis', err);
});
const Redis_KEY_PATTERNS = {
MODEL_DEVEUI: (custid, specid) => `LORA:MODEL:POWER:${custid}:${specid}`,
MODEL_VAL: (custid, specid) => `LORA:MODEL:POWER:${custid}:${specid}:VAL`
};
const modelDataStore = {
deviceMap: new Map(), //key: devEUI value: {custid,specid}
specDeviceMap: new Map(), //key: {custid}-{specid} value: Set<devEUI>
thresholdMap: new Map(), //key: {custid}-${specid} value:{work,await}
}
wsClient.on('open', () => {
console.log('WebSocket连接成功');
});
//监听消息事件,增加查询保存设备列表以及对应规格信号数据
wsClient.on('message', (message) => {
const msg = message.toString();
if (msg.startsWith('modelPower-') || msg.startsWith('modelDevices-')){
console.log('系统更新内容:',msg);
const [type, key] = msg.split('-');
const parts = key.split(':');
const custid = parts[3];
const specid = parts[4];
if (type === 'modelDevices'){
refreshModelDevices(custid, specid);
} else if (type === 'modelPower'){
refreshModelThresholds(custid, specid);
}
return;
} else if (msg !== 'lora_refresh' && msg !== '连接成功!'){
resetHours(message);
} else {
// console.log('收到消息:',msg);
}
});
// WebSocket 客户端连接断开后的回调
wsClient.on('close', () => {
console.error('WebSocket连接已关闭');
reconnect();
});
// WebSocket 客户端连接错误后的回调
wsClient.on('error', (error) => {
console.error('WebSocket 错误: ', error);
wsClient.close();
});
const messageQueue = [];
//Websocket 重连
function reconnect() {
console.log('正在重连……');
if(wsClient){
wsClient.close();
};
setTimeout(() => {
wsClient = new WebSocket(wsUrl);
wsClient.on('open', () => {
console.log('WebSocket连接成功');
while (messageQueue.length > 0) {
const message = messageQueue.shift();
wsClient.send(message);
}
});
wsClient.on('close', () => {
console.error('WebSocket连接已关闭');
reconnect();
});
wsClient.on('error', (error) => {
console.error('WebSocket 错误: ', error);
wsClient.close();
});
wsClient.on('message', (message) => {
const msg = message.toString();
if (msg.startsWith('modelPower-') || msg.startsWith('modelDevices-')){
const [type, key] = msg.split('-');
const parts = key.split(':');
const custid = parts[3];
const specid = parts[4];
if (type === 'modelDevices'){
refreshModelDevices(custid, specid);
} else if (type === 'modelPower'){
refreshModelThresholds(custid, specid);
}
return;
} else if (msg !== 'lora_refresh' && msg !== '连接成功!'){
resetHours(message);
} else {
console.log('收到消息:',msg);
}
});
}, 10000);
};
const epcCache = new Map();
function getCurrentTime() {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
const options = {
host: '123.207.3.132',
port: 1883,
username: 'admin',
password: 'admin',
};
const mqttClient = mqtt.connect(options);
mqttClient.on('connect', () => {
console.log('MQTT客户端已连接');
// 订阅主题
const topic = 'application/+/device/+/event/up';
mqttClient.subscribe(topic, { qos: 1 }, (err) => {
if (err) {
console.error('订阅失败:', err);
} else {
console.log(`已订阅主题: ${topic}`);
}
});
});
const knownDevEUIs = new Set();
const lastData = {};
let workHoursStorage = {};
let workCustid = {};
let workError = new Set();
let awaitHoursStorage = {};
let readDevices = [
{ devEUI: '0080e10101010123', applicationID: '1', custid: 'GZYS' }
];
// 当客户端接收到消息时
mqttClient.on('message',handleMQTTMessage);
async function handleMQTTMessage(topic, message) {
const mqttMessage = JSON.parse(message);
const mqttDataBase64 = mqttMessage.data;
const mqttDataBytes = Buffer.from(mqttDataBase64, 'base64');
// 提取所需的字段
const { deviceName, applicationID, applicationName, devEUI, fPort } = mqttMessage;
const gatewayName = mqttMessage.rxInfo[0].name;
const gatewayID = mqttMessage.rxInfo[0].gatewayID;
// 判断是否包含特定前缀
const containsPrefix = mqttDataBytes.toString('hex').startsWith('5a0001');
const rightData = mqttDataBytes.toString('hex').startsWith('53472000') && mqttDataBytes.toString('hex').length === 64;
if (containsPrefix) {
const mqttDataHex = mqttDataBytes.toString('hex');
// const epcStartIndex = 20;//从第二十位开始提取epc编号 ps:出现过一次epc编号并非在第二十位开始的情况,暂不清楚原因
const epcStartIndex = mqttDataHex.indexOf('e');//从第一个e开始提取epc编号
const epcLength = 24;
const epc = (mqttDataHex.substring(epcStartIndex, epcStartIndex + epcLength)).toUpperCase();//已将字母转为大写
const mqttDataUtf8 = mqttDataBytes.toString('utf-8');
// 提取 readerSerialNumber
const numberIndex = mqttDataUtf8.indexOf('N');
const numberLength = 20;
const readerSerialNumber = (mqttDataUtf8.substring(numberIndex, numberIndex + numberLength).toUpperCase()).toString('UTF-8');
const epcCacheEntry = epcCache.get(epc);
const currentTime = getCurrentTime();
if (epcCacheEntry && (new Date(currentTime) - new Date(epcCacheEntry.timestamp)) < 5000) {
return;
}
// 准备要发送的数据
const backendData = {
"cid": "lora",
"message": "lora_rfid_inout",
"custid": "GZYS",
"time": getCurrentTime(),
"devEUI": devEUI,
"deviceName": deviceName,
"readerSerialNumber": readerSerialNumber,
"epc": epc
};
// 将数据转换为 JSON 字符串
const mqttString = JSON.stringify(backendData);
// 更新缓存中的 epc 编号和时间戳
epcCache.set(epc, { timestamp: currentTime });
} else if(rightData) {
let mqttData = mqttDataBytes.toString('hex');
const openStart = 8;
const openEnd = 10;
const electricityStart = 12;
const electricityEnd = 20;
const powerStart = 20;
const powerEnd = 28;
const currentStart = 28;
const currentEnd = 32;
const voltageStart = 32;
const voltageEnd = 36;
const lightStart = 44;
const lightEnd = 48;
const USBStart = 48;
const USBEnd = 56;
const openHex = mqttData.substring(openStart, openEnd);
const electricityHex = mqttData.substring(electricityStart, electricityEnd);
const powerHex = mqttData.substring(powerStart, powerEnd);
const currentHex = mqttData.substring(currentStart, currentEnd);
const voltageHex = mqttData.substring(voltageStart, voltageEnd);
const lightHex = mqttData.substring(lightStart,lightEnd);
const USB = mqttData.substring(USBStart,USBEnd);
const electricity = parseInt(electricityHex, 16) * 0.001;
const power = parseInt(powerHex, 16) * 0.1;
const current = parseInt(currentHex, 16) * 0.001;
const voltage = parseInt(voltageHex, 16) * 0.01;
const light = (parseInt(lightHex.substring(1,2), 16)).toString(2).padStart(4, '0');
const isReadDevices = readDevices.find(device =>
device.devEUI === devEUI && device.applicationID === applicationID
)
if (isReadDevices){
const backendData = {
"cid": "lora",
"message": "lora_reader",
"time": getCurrentTime(),
"custid": isReadDevices.custid,
"devEUI": devEUI,
"USB ID": USB
}
const wsMessage = JSON.stringify(backendData);
if (wsClient.readyState === WebSocket.OPEN){
console.log('读卡器:',wsMessage);
} else {
console.error(`心跳连接断开,读写器${devEUI}消息传递失败`);
}
return;
} else {
const deviceInfo = modelDataStore.deviceMap.get(devEUI);
if (!deviceInfo){
console.error(`${devEUI}未配置规格型号`);
return;
}
const thresholds = modelDataStore.thresholdMap.get(`${deviceInfo.custid}-${deviceInfo.specid}`);
const backendData = {
"cid": "lora",
"message": "lora",
"custid": deviceInfo.custid,
"time": getCurrentTime(),
"applicationID": applicationID,
"applicationName": applicationName,
"gatewayName": gatewayName,
"gatewayID": gatewayID,
"devEUI": devEUI,
"deviceName": deviceName,
"fPort": fPort,
"data": mqttData
};
console.log(`消息来自application/${applicationID}/devEUI/${devEUI},内容为${mqttData}`);
// console.log(`电量: ${electricity} 度,获取数据为${electricityHex}`);
// console.log(`功率: ${power} W,获取数据为${powerHex}`);
// console.log(`电流: ${current} A,获取数据为${currentHex}`);
// console.log(`电压: ${voltage} V,获取数据为${voltageHex}`);
console.log(`警告灯: ${light},获取数据为${lightHex}`);
console.log(`位置ID为${USB}`);
console.log(`插座状态${openHex}`);
await updateGatewayStatus(gatewayID, gatewayName);
// 将数据转换为 JSON 字符串
let mqttString = JSON.stringify(backendData);
const value = power > thresholds.work ? '\"work\"' : power > thresholds.await ? '\"await\"' : '\"on\"';
deviceStatus(backendData.custid,backendData.devEUI,value);
if (USB !== '00000000' && USB != null) {
if (value === '\"work\"') {
const custid = backendData.custid;
const devEUI_USB = `${backendData.devEUI}_${USB}`;
if (!workHoursStorage[custid]){
workHoursStorage[custid] = {};
}
if (workHoursStorage[custid][devEUI_USB]){
workHoursStorage[custid][devEUI_USB] += 10;
} else {
workHoursStorage[custid][devEUI_USB] = 10;
}
}else if (value === '\"await\"') {
const custid = backendData.custid;
const devEUI_USB = `${backendData.devEUI}_${USB}`;
if (!awaitHoursStorage[custid]){
awaitHoursStorage[custid] = {};
}
if (awaitHoursStorage[custid][devEUI_USB]){
awaitHoursStorage[custid][devEUI_USB] += 10;
} else {
awaitHoursStorage[custid][devEUI_USB] = 10;
}
}
if (openHex === '00') {
let params = {
"deviceQueueItem": {
"confirmed": true,
"data": "U0cFAQE=",
"devEUI": devEUI,
"fPort": 2,
"jsonObject": ""
}
}
console.log(`${devEUI}加密狗已上线,允许插座通电`);
sendRequests(params);
}
} else if ((USB === '00000000' || USB == null) && openHex === '01'){
let params = {
"deviceQueueItem": {
"confirmed": true,
"data": "U0cFAQA=",
"devEUI": devEUI,
"fPort": 2,
"jsonObject": ""
}
}
console.log(`${devEUI}加密狗已离线,不允许插座通电`);
sendRequests(params);
}
//转义字符串
function escapeString(str) {
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"');
}
const dataString = "\"" + escapeString(mqttString) + "\"";
const lightData = "\"" + escapeString(light) + "\"";
if (!knownDevEUIs.has(`${USB}-${backendData.devEUI}`)) {
knownDevEUIs.add(`${USB}-${backendData.devEUI}`);
}
const last = lastData[devEUI] || {};
const dataChanged = !last || last.USB !== USB;
if(dataChanged || (last.light !== '0000' && light === '0000')) {
lastData[devEUI] = { light: light, USB: USB, time: getCurrentTime(), custid: backendData.custid };
if(wsClient.readyState === WebSocket.OPEN) {
if (!last.light || (last.light !== '0000' && light === '0000')) {
client.set(`EAM:LORA:BILL:LIGHT:TYPE:${backendData.custid}_${backendData.devEUI}`,lightData);
}
wsClient.send(mqttString);
console.log("位置改变",backendData.time);
}else{
messageQueue.push(mqttString);
}
}else{
lastData[devEUI] = { light: light, USB: USB, time: getCurrentTime(), custid: backendData.custid };
if(light !== '0000' && wsClient.readyState === WebSocket.OPEN) {
client.set(`EAM:LORA:BILL:LIGHT:TYPE:${backendData.custid}_${backendData.devEUI}`,lightData);
wsClient.send(mqttString);
console.log("灯光改变");
};
};
const expireTime = Date.now() + 3900000;
client.ZADD(`LORA:GATEWAY:${USB}:${backendData.devEUI}`, {"score":expireTime,"value": dataString})
.catch(error => {
console.error('error', error);
});
}
} else {
let mqttData = mqttDataBytes.toString('hex');
const backendData = {
'cid': 'lora',
'message': 'lora',
'custid': 'GZYS',
'time': getCurrentTime(),
'applicationID': applicationID,
'applicationName': applicationName,
'gatewayName': gatewayName,
'gatewayID': gatewayID,
'devEUI': devEUI,
'deviceName': deviceName,
'fPort': fPort,
'data': mqttData
};
console.error(`错误格式的数据来自application/${applicationID}/devEUI/${devEUI},内容为${mqttData}`);
let mqttString = JSON.stringify(backendData);
function escapeString(str) {
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"');
}
const dataString = "\"" + escapeString(mqttString) + "\"";
if (!workError.has(`${backendData.custid}-${backendData.devEUI}`)) {
workError.add(`${backendData.custid}-${backendData.devEUI}`);
wsClient.send();
}
// const expireTime = Date.now() + 1296000000; //半个月
const expireTime = Date.now() + 24 * 60 * 60 * 1000; //一天
client.ZADD(`LORA:GATEWAY:${backendData.custid}:ERROR:${backendData.devEUI}`,{"score":expireTime,'value': dataString})
.catch(err => {
console.error('保存错误日志时出现错误',err);
})
}
}
function escapeString(str) {
// return str.replace(/_/g, '\\_');
return str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/:/g, '\\:')
.replace(/;/g, '\\;');
}
async function sendRequests(params) {
for (let i = 0; i < 3; i++){
try {
if (params.deviceQueueItem.devEUI === '0080e10101010110') {
const res = await axios.post(
`http://123.207.3.132:8080/api/devices/${params.deviceQueueItem.devEUI}/queue`,
params,
{
headers: {
Authorization: `Bearer ${jwt}`,
}
}
);
console.log(`第${i+1}次触发成功`,res.status);
}
} catch (err){
console.error(`第${i+1}次触发失败`,err.response.data || err);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
setInterval(() => {
Object.keys(workHoursStorage).forEach(custid => {
const entries = workHoursStorage[custid];
const entriesArray = Object.keys(entries).map(devEUI_USB => {
return `${devEUI_USB}:${entries[devEUI_USB]}`;
})
const valueString = JSON.stringify(entriesArray.join(';'));
// const value = "\"" + escapeString(valueString) + "\"";
client.set(`LORA:GATEWAY:${custid}:WORKHOURS`, valueString)
.catch(err => {
console.error('工作时长存储错误',err);
});
});
Object.keys(awaitHoursStorage).forEach(custid => {
const entries = awaitHoursStorage[custid];
const entriesArray = Object.keys(entries).map(devEUI_USB => {
return `${devEUI_USB}:${entries[devEUI_USB]}`;
})
const valueString = JSON.stringify(entriesArray.join(';'));
client.set(`LORA:GATEWAY:${custid}:AWAITHOURS`, valueString)
.catch(err => {
console.error('待机时长存储错误',err);
})
})
}, 30000);
const get = {
'cid': 'lora',
'message': 'lora_position_get_custid'
};
const getString = JSON.stringify(get);
const rule = new schedule.RecurrenceRule();
rule.hour = 23;
rule.minute = 59;
const job = schedule.scheduleJob(rule, async () => {
try {
wsClient.send(getString);
await resetError();
await refreshAllThresholds();
} catch (err) {
console.error('定时任务失败:',err);
}
})
async function resetError() {
try {
console.log('开始清理错误数据');
const errorKeys = await client.KEYS('LORA:GATEWAY:*:ERROR:*');
await Promise.all(errorKeys.map(async (key) => {
try {
const members = await client.zRange(key, 0, -1);
const memberScores = await Promise.all(members.map(async (member) => ({
member,
score: await client.zScore(key, member)
})));
const toRemove = memberScores.filter(({ score }) => {
const isValid = !isNaN(score) && score <= Date.now();
return isValid;
}).map(({ member }) => member);
if (toRemove.length > 0){
await client.zRem(key, toRemove);
}else{
console.log(`${key}无过期数据`);
}
} catch (err) {
console.error(`处理${key}失败:`,err);
}
}))
} catch (err) {
console.error('错误处理失败:',err);
}
}
async function resetHours(message){
workHoursStorage = {};
awaitHoursStorage = {};
workCustid = message.toString().split(',').filter(element => element !== '');
console.log('已经调用resetHorus函数,获取到workCustid:',workCustid);
await Promise.all(
workCustid.map(custid => {
const key1 = `LORA:GATEWAY:${custid}:WORKHOURS`;
return client.get(key1)
.then(reply => {
return client.set(key1, ' ')
.then(reply => {
console.log('工作时长已重置',reply);
})
})
.catch(err => {
console.error('key1处理失败:', custid, err);
});
})
);
await Promise.all(workCustid.map(custid => {
const key2 = `LORA:GATEWAY:${custid}:AWAITHOURS`;
return client.get(key2)
.then(reply => {
return client.set(key2, ' ')
.then(reply => {
console.log('待机时长已重置',reply);
})
})
.catch(err => {
console.error('key2处理失败:', custid, err);
});
}))
};
function deviceStatus(custid,devEUI,value){
if(value === '\"off\"'){
client.del(`EAM:LORA:BILL:LIGHT:TYPE:${custid}_${devEUI}`);
client.setEx(`EAM:LORA:BILL:TYPE:${custid}_${devEUI}`,1800,value);
}else{
client.setEx(`EAM:LORA:BILL:TYPE:${custid}_${devEUI}`,60,value);
}
}
function checkAndClearOldData() {
const currentTime = Date.now();
for (const devEUI in lastData) {
if (currentTime - new Date(lastData[devEUI].time).getTime() > 60000) {
deviceStatus(lastData[devEUI].custid,devEUI,'\"off\"');
const lightData = "\"" + '0000' + "\"";
client.set(`EAM:LORA:BILL:LIGHT:TYPE:${lastData[devEUI].custid}_${devEUI}`, lightData);
delete lastData[devEUI];
console.log(`设备 ${devEUI} 的状态已被删除,因为超过一分钟没有接收到数据。`);
}
};
//定期清理epcCache中的内容
// epcCache.forEach((value, key) => {
// if (currentTime - value.timestamp > 5000) epcCache.delete(key);
// });
}
// 设置定时器,每分钟调用一次checkAndClearOldData函数
setInterval(checkAndClearOldData, 60000);
async function refreshModelDevices(custid, specid) {
try {
const devices = await client.get(Redis_KEY_PATTERNS.MODEL_DEVEUI(custid, specid));
const validDevices = (devices || '').split(',').map(d => d.replace(/["']/g, '').trim()).filter(d => d !== '');
updateModelDevices(custid, specid, validDevices);
console.log(`[设备更新] ${custid}-${specid} 完成,设备数量: ${validDevices.length}`);
} catch (err) {
console.error(`[设备更新] ${custid}-${specid} 失败:`, err.message);
}
}
async function refreshModelThresholds(custid, specid) {
try {
const thresholdsVal = await client.get(Redis_KEY_PATTERNS.MODEL_VAL(custid, specid));
if (thresholdsVal){
const cleanedVal = thresholdsVal.replace(/["']/g, '').trim();
const parts = cleanedVal.split(',').map(s => s.trim()).filter(s => s !== '');
if (parts.length !== 3){
throw new Error(`非法阈值格式:${thresholdsVal}`);
}
const parsed = parts.map((s, idx) => {
const num = parseFloat(s);
return num;
});
[, standby, work] = parsed;
}
updateModelThresholds(custid, specid, { standby, work });
console.log(`[阈值更新] ${custid}-${specid} 完成,新阈值: standby=${standby}, work=${work}`);
} catch (err) {
console.error(`[阈值更新] ${custid}-${specid} 失败:`, err.message);
}
}
function updateModelDevices(custid, specid, newDevices) {
const specKey = `${custid}-${specid}`;
const oldDevices = modelDataStore.specDeviceMap.get(specKey) || new Set();
oldDevices.forEach(devEUI => {
if (!newDevices.includes(devEUI)){
modelDataStore.deviceMap.delete(devEUI);
}
});
newDevices.forEach(devEUI => {
if(!modelDataStore.deviceMap.has(devEUI)){
modelDataStore.deviceMap.set(devEUI, { custid, specid });
}
});
modelDataStore.specDeviceMap.set(specKey, new Set(newDevices));
}
function updateModelThresholds(custid, specid, thresholds) {
const specKey = `${custid}-${specid}`;
modelDataStore.thresholdMap.set(specKey, {
await: thresholds.standby,
work: thresholds.work
});
}
async function refreshAllThresholds() {
try {
const deviceKeys = await client.KEYS(Redis_KEY_PATTERNS.MODEL_DEVEUI('*','*'));
const thresholdKyes = await client.KEYS(Redis_KEY_PATTERNS.MODEL_VAL('*','*'));
await Promise.all(deviceKeys.map(key => {
const [,,, custid, specid] = key.split(':');
return refreshModelDevices(custid, specid);
}));
await Promise.all(thresholdKyes.map(key => {
const [,,, custid, specid] = key.split(':');
return refreshModelThresholds(custid, specid);
}));
// console.log('全量:',modelDataStore);
} catch (err) {
console.error('全量阈值同步失败:',err);
}
}
refreshAllThresholds();
//设备上传数据设置到期删除
setInterval(() => {
const currentTime = Date.now();
knownDevEUIs.forEach((key) => {
const [USB, devEUI] = key.split('-');
const zSetName = `LORA:GATEWAY:${USB}:${devEUI}`;
client.zRange(zSetName, "0", "-1","WITHSCORES")
.then(members => {
Promise.all(members.map(member => client.zScore(zSetName, member)))
.then(scores => {
scores.forEach((score, index) => {
if (score <= currentTime) {
client.zRem(zSetName, members[index])
.catch(err => {
console.error('移除成员失败:', err);
});
}
});
})
.catch(err => {
console.error('获取分数失败:', err);
});
})
.catch(err => {
console.error('获取数据失败:', err);
});
});
}, 300000);
// 当客户端断开连接时
mqttClient.on('close', () => {
console.error('MQTT客户端已断开连接');
mqttReconnect();
});
function mqttReconnect() {
console.log('MQTT重连中');
if (mqttClient){
mqttClient.end(true);
};
setTimeout(() => {
mqttClient = mqtt.connect(options);
mqttClient.on('connect', () => {
const topic = 'application/+/device/+/event/up';
mqttClient.subscribe(topic, { qos: 1 }, (err) => {
if (err) {
console.error('订阅失败:',err);
} else {
console.log('已订阅主题:',topic);
}
})
});
mqttClient.on('message', handleMQTTMessage);
mqttClient.on('close', () => {
console.error('MQTT客户端已断开连接');
mqttReconnect();
});
mqttClient.on('error', (err) => {
console.error('MQTT客户端发生错误:', err);
mqttClient.end(true);
});
}, 5000);
}
// 当客户端发生错误时
mqttClient.on('error', (err) => {
console.error('MQTT客户端发生错误:', err);
mqttClient.end(true);
});
let jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5X2lkIjoiZjA5ZjViMWYtYTFjMy00NjZkLTkyNjItOGE1YTkzYTY1NTU5IiwiYXVkIjoiYXMiLCJpc3MiOiJhcyIsIm5iZiI6MTczMjU5MTM5Niwic3ViIjoiYXBpX2tleSJ9.32w2IsaBTTM8nZuKHxwwXJwT-mAeR2B5ubkDzMgU1XA';
let offGateways = [];
//从redis中获取离线网关的名单
function loadGateways(){
client.keys(`LORA:GATEWAYSTATUS:*:*`).then(reply => {
offGateways = [];
reply.forEach(key => {
const parts = key.split(':');
const organizationName = parts[2];
const gatewayID = parts[3];
offGateways.push({ organizationName, gatewayID });
});
}).catch(err => {
console.error("获取离线网关名单时出错",err);
});
}
//获取绑定组织名称和ID
function getOrgID(){
axios.get(`http://123.207.3.132:8080/api/organizations?limit=99999`, {
headers: {
Authorization: jwt,
}
}).then(response => {
const orgMap = new Map();
response.data.result.forEach(org => {
orgMap.set(org.id, org.name);
});
checkGatewayStatus([...orgMap.keys()], orgMap);
})
.catch(err => {
console.error("获取ID出现错误",err)
})
};
//网关工作情况
function checkGatewayStatus(organizationIDs,orgIdNameMap) {
const promises = organizationIDs.map(async orgID => {
try {
const response = await axios.get(`http://123.207.3.132:8080/api/gateways?limit=99999&offset=0&organizationID=${orgID}`, {
headers: {
Authorization: jwt,
}
});
const rawOrgName = orgIdNameMap.get(orgID);
const safeOrgName = rawOrgName.replace(/[^a-zA-Z0-9]/g, '_');
response.data.result.forEach(gateway => {
const gatewayID = gateway.id;
const redisKey = `LORA:GATEWAYSTATUS:${safeOrgName}:${gatewayID}`;
const lastSeenAt = gateway.lastSeenAt ? new Date(gateway.lastSeenAt).getTime() : null;
const isOffline = lastSeenAt && (Date.now() - lastSeenAt) > 32000;
if (isOffline){
const exists = offGateways.some(gw => gw.gatewayID === gatewayID);
if (!exists){
offGateways.push({
organizationName: rawOrgName,
gatewayID
});
}
client.set(redisKey, '\"off\"').then(() => console.log(`[${safeOrgName}] 网关${gatewayID} 离线状态已记录`))
.catch(err => console.error(`[${safeOrgName}] Redis写入失败`, err));
} else {
const index = offGateways.findIndex(gw => gw.gatewayID === gatewayID);
if (index !== -1){
offGateways.splice(index, 1);
}
client.del(redisKey).then(() => console.log(`[${safeOrgName}] 网关${gatewayID} 在线状态已更新`))
.catch(err => console.error(`[${safeOrgName}] Redis删除失败`, err));
}
});
} catch (error) {
console.error("checkDevicesStatus函数出错", error);
}
});
Promise.all(promises).then(() => {
console.log("离线网关列表",JSON.stringify(offGateways));
});
};
async function updateGatewayStatus(gatewayID, gatewayName) {
const index = offGateways.findIndex(gw => gw.gatewayID === gatewayID);
if (index !== -1){
const { organizationName } = offGateways[index];
offGateways.splice(index, 1);
const safeOrgName = organizationName.replace(/[^a-zA-Z0-9]/g, '-');
const redisKey = `LORA:GATEWAYSTATUS:${safeOrgName}:${gatewayID}`;
try {
await client.del(redisKey);
console.log(`[实时更新] 网关 ${gatewayName} (${gatewayID}) 重新上线`);
} catch (err) {
console.error(`[${organizationName}] Redis删除失败`, err);
}
}
}
loadGateways();
getOrgID();
setInterval(getOrgID, 15000);
app.get('/health', async (req, res) => {
const allClients = {
redis: {
connected: client.isReady,
name: 'Redis数据库'
},
mqtt: {
connected: mqttClient.connected,
name: 'MQTT消息队列'
},
websocket: {
connected: wsClient.readyState === WebSocket.OPEN,
name: 'WebSocket连接'
},
};
const statusReport = {
status: 'healthy',
allClients: {},
issues: [],
timestamp: Date.now()
};
let allHealthy = true;
Object.entries(allClients).forEach(([key, client]) => {
const isConnected = client.connected;
statusReport.allClients[key] = isConnected ? 'connected' : 'disconnected';
if (!isConnected) {
statusReport.issues.push({
clients: key,
name: client.name,
description: `${client.name}连接失败`
});
allHealthy = false;
}
});
statusReport.status = allHealthy ? 'healthy' : 'unhealthy';
res.status(allHealthy ? 200 : 503).json(statusReport);
});
app.listen(3000);
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的拒绝:', reason);
})
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
})
process.on('SIGINT', () => {
console.log('程序正在关闭...');
mqttClient.end();
client.quit();
wsClient.close();
process.exit();
});
遍历我的代码,查看哪里会发生修改const变量的情况,并且解释一下MQTT客户端发生错误: Error: read ECONNRESET,这是连接超时导致的错误吗
最新发布