mqttBrokerService.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.MqttBrokerService = void 0;
  7. const aedes_1 = __importDefault(require("aedes"));
  8. const net_1 = require("net");
  9. const device_1 = require("../models/device");
  10. const mqttMessage_1 = require("../models/mqttMessage");
  11. const clientConnection_1 = require("../models/clientConnection");
  12. const clientAuth_1 = require("../models/clientAuth");
  13. const clientAcl_1 = require("../models/clientAcl");
  14. const authLog_1 = require("../models/authLog");
  15. const loggerService_1 = require("./loggerService");
  16. const websocketService_1 = require("./websocketService");
  17. const ota_1 = require("../models/ota");
  18. const firmware_1 = require("../models/firmware");
  19. class MqttBrokerService {
  20. constructor() {
  21. this.connectedClients = new Map();
  22. this.syncInterval = null;
  23. this.port = parseInt(process.env.MQTT_BROKER_PORT || '1883', 10);
  24. this.broker = new aedes_1.default({
  25. id: 'mqtt-vue-dashboard-broker',
  26. concurrency: 100,
  27. heartbeatInterval: 60000,
  28. connectTimeout: 30000,
  29. });
  30. this.server = (0, net_1.createServer)(this.broker.handle);
  31. this.setupBrokerEvents();
  32. }
  33. static getInstance() {
  34. if (!MqttBrokerService.instance) {
  35. MqttBrokerService.instance = new MqttBrokerService();
  36. }
  37. return MqttBrokerService.instance;
  38. }
  39. start() {
  40. return new Promise((resolve, reject) => {
  41. this.server.listen(this.port, () => {
  42. console.log(`MQTT Broker 已启动,监听端口: ${this.port}`);
  43. loggerService_1.LoggerService.info('MQTT Broker 已启动', {
  44. source: 'mqtt_broker',
  45. module: 'startup',
  46. details: JSON.stringify({ port: this.port })
  47. }).catch(() => { });
  48. this.startDeviceStatusSync();
  49. resolve();
  50. });
  51. this.server.on('error', (err) => {
  52. console.error('MQTT Broker 启动失败:', err.message);
  53. loggerService_1.LoggerService.error('MQTT Broker 启动失败', {
  54. source: 'mqtt_broker',
  55. module: 'startup',
  56. details: JSON.stringify({ error: err.message })
  57. }).catch(() => { });
  58. reject(err);
  59. });
  60. });
  61. }
  62. stop() {
  63. return new Promise((resolve) => {
  64. if (this.syncInterval) {
  65. clearInterval(this.syncInterval);
  66. this.syncInterval = null;
  67. }
  68. this.server.close(() => {
  69. this.broker.close(() => {
  70. console.log('MQTT Broker 已停止');
  71. resolve();
  72. });
  73. });
  74. });
  75. }
  76. setupBrokerEvents() {
  77. this.broker.authenticate = ((client, username, password, done) => {
  78. this.authenticateClient(client, username, password, done);
  79. });
  80. this.broker.authorizePublish = this.authorizePublish.bind(this);
  81. this.broker.authorizeSubscribe = this.authorizeSubscribe.bind(this);
  82. this.broker.on('client', (client) => {
  83. this.handleClientConnect(client);
  84. });
  85. this.broker.on('clientDisconnect', (client) => {
  86. this.handleClientDisconnect(client);
  87. });
  88. this.broker.on('publish', (packet, client) => {
  89. if (client) {
  90. this.handleMessagePublish(packet, client);
  91. }
  92. });
  93. this.broker.on('subscribe', (subscriptions, client) => {
  94. if (client) {
  95. this.handleClientSubscribe(subscriptions, client);
  96. }
  97. });
  98. this.broker.on('unsubscribe', (subscriptions, client) => {
  99. if (client) {
  100. this.handleClientUnsubscribe(subscriptions, client);
  101. }
  102. });
  103. this.broker.on('clientError', (client, err) => {
  104. console.error(`客户端错误 [${client.id}]:`, err.message);
  105. });
  106. }
  107. async authenticateClient(client, username, password, callback) {
  108. const clientId = client.id;
  109. const ip = this.getClientIp(client);
  110. if (!username || !password) {
  111. const allowAnonymous = process.env.MQTT_ALLOW_ANONYMOUS === 'true';
  112. if (allowAnonymous) {
  113. this.logAuth(clientId, username || '', ip, 'connect', 'success', '');
  114. callback(null, true);
  115. return;
  116. }
  117. this.logAuth(clientId, username || '', ip, 'connect', 'failure', '缺少用户名或密码');
  118. callback(null, false);
  119. return;
  120. }
  121. try {
  122. let authRecord = await clientAuth_1.ClientAuthModel.findByUsernameAndClientId(username, clientId);
  123. if (!authRecord) {
  124. authRecord = await clientAuth_1.ClientAuthModel.findByUsername(username);
  125. }
  126. if (!authRecord) {
  127. this.logAuth(clientId, username, ip, 'connect', 'failure', '认证记录不存在');
  128. callback(null, false);
  129. return;
  130. }
  131. if (authRecord.status === 'disabled') {
  132. this.logAuth(clientId, username, ip, 'connect', 'failure', '客户端已禁用');
  133. callback(null, false);
  134. return;
  135. }
  136. const passwordStr = password.toString();
  137. const isMatch = clientAuth_1.ClientAuthModel.verifyPassword(passwordStr, authRecord.salt || '', authRecord.password_hash, authRecord.use_salt);
  138. if (!isMatch) {
  139. this.logAuth(clientId, username, ip, 'connect', 'failure', '密码错误');
  140. callback(null, false);
  141. return;
  142. }
  143. await clientAuth_1.ClientAuthModel.updateLastLogin(username, clientId);
  144. this.logAuth(clientId, username, ip, 'connect', 'success', '');
  145. callback(null, true);
  146. }
  147. catch (error) {
  148. console.error('认证过程出错:', error);
  149. this.logAuth(clientId, username || '', ip, 'connect', 'failure', '认证服务错误');
  150. callback(null, false);
  151. }
  152. }
  153. async authorizePublish(client, packet, callback) {
  154. const username = client.username;
  155. const topic = packet.topic;
  156. try {
  157. if (topic && topic.startsWith('$SYS/')) {
  158. callback(null);
  159. return;
  160. }
  161. const isSuperuser = await this.isSuperuser(username);
  162. if (isSuperuser) {
  163. callback(null);
  164. return;
  165. }
  166. const hasPermission = await this.checkAclPermission(username, topic, 'publish');
  167. if (hasPermission) {
  168. callback(null);
  169. }
  170. else {
  171. this.logAuth(client.id, username || '', this.getClientIp(client), 'publish', 'failure', `无权发布到主题: ${topic}`);
  172. callback(new Error(`无权发布到主题: ${topic}`));
  173. }
  174. }
  175. catch (error) {
  176. console.error('发布授权检查出错:', error);
  177. callback(null);
  178. }
  179. }
  180. async authorizeSubscribe(client, subscription, callback) {
  181. const username = client.username;
  182. const topic = subscription.topic;
  183. try {
  184. const isSuperuser = await this.isSuperuser(username);
  185. if (isSuperuser) {
  186. callback(null, subscription);
  187. return;
  188. }
  189. if (!topic) {
  190. callback(null, subscription);
  191. return;
  192. }
  193. const hasPermission = await this.checkAclPermission(username, topic, 'subscribe');
  194. if (!hasPermission) {
  195. this.logAuth(client.id, username || '', this.getClientIp(client), 'subscribe', 'failure', `无权订阅主题: ${topic}`);
  196. callback(new Error(`无权订阅主题: ${topic}`));
  197. return;
  198. }
  199. callback(null, subscription);
  200. }
  201. catch (error) {
  202. console.error('订阅授权检查出错:', error);
  203. callback(null, subscription);
  204. }
  205. }
  206. async isSuperuser(username) {
  207. if (!username)
  208. return false;
  209. try {
  210. const authRecord = await clientAuth_1.ClientAuthModel.findByUsername(username);
  211. return authRecord ? (authRecord.is_superuser === true || authRecord.is_superuser === 1) : false;
  212. }
  213. catch {
  214. return false;
  215. }
  216. }
  217. async checkAclPermission(username, topic, action) {
  218. if (!username)
  219. return true;
  220. try {
  221. const aclRules = await clientAcl_1.ClientAclModel.getByUsername(username);
  222. if (!aclRules || aclRules.length === 0)
  223. return true;
  224. for (const rule of aclRules) {
  225. if (this.topicMatches(topic, rule.topic)) {
  226. if (rule.action === 'pubsub' || rule.action === action) {
  227. return rule.permission === 'allow';
  228. }
  229. }
  230. }
  231. return true;
  232. }
  233. catch {
  234. return true;
  235. }
  236. }
  237. topicMatches(topic, pattern) {
  238. if (pattern === '#')
  239. return true;
  240. if (pattern === topic)
  241. return true;
  242. const patternParts = pattern.split('/');
  243. const topicParts = topic.split('/');
  244. for (let i = 0; i < patternParts.length; i++) {
  245. if (patternParts[i] === '#')
  246. return true;
  247. if (i >= topicParts.length)
  248. return false;
  249. if (patternParts[i] !== '+' && patternParts[i] !== topicParts[i])
  250. return false;
  251. }
  252. return patternParts.length === topicParts.length;
  253. }
  254. getClientIp(client) {
  255. if (client.conn && client.conn.remoteAddress) {
  256. const remoteAddress = client.conn.remoteAddress;
  257. if (remoteAddress === '::1') {
  258. return '127.0.0.1';
  259. }
  260. if (remoteAddress.startsWith('::ffff:')) {
  261. return remoteAddress.substring(7);
  262. }
  263. return remoteAddress;
  264. }
  265. return 'unknown';
  266. }
  267. async handleClientConnect(client) {
  268. const clientId = client.id;
  269. const username = client.username || '';
  270. const ip = this.getClientIp(client);
  271. this.connectedClients.set(clientId, {
  272. id: clientId,
  273. username,
  274. ip,
  275. connectedAt: new Date()
  276. });
  277. console.log(`客户端已连接: ${clientId} (${username}) from ${ip}`);
  278. try {
  279. let device = await device_1.DeviceModel.getByClientId(clientId);
  280. if (device) {
  281. await device_1.DeviceModel.update(clientId, {
  282. status: 'online',
  283. last_event_time: new Date(),
  284. last_online_time: new Date(),
  285. device_ip_port: ip,
  286. last_ip_port: ip,
  287. connect_count: (device.connect_count || 0) + 1
  288. });
  289. }
  290. else {
  291. await device_1.DeviceModel.create({
  292. clientid: clientId,
  293. device_name: clientId,
  294. username,
  295. status: 'online',
  296. last_event_time: new Date(),
  297. last_online_time: new Date(),
  298. device_ip_port: ip,
  299. last_ip_port: ip,
  300. connect_count: 1
  301. });
  302. }
  303. await clientConnection_1.ClientConnectionModel.create({
  304. clientid: clientId,
  305. username,
  306. event: 'client.connected',
  307. timestamp: new Date(),
  308. connected_at: new Date(),
  309. node: 'mqtt-vue-dashboard',
  310. peername: ip,
  311. sockname: `0.0.0.0:${this.port}`,
  312. proto_name: 'MQTT',
  313. proto_ver: 4,
  314. keepalive: 60,
  315. clean_start: 1
  316. });
  317. this.broadcastToWebSocket('device_connected', {
  318. clientid: clientId,
  319. username,
  320. ip,
  321. timestamp: new Date().toISOString()
  322. });
  323. await this.executePendingOTATasks(clientId);
  324. }
  325. catch (error) {
  326. console.error('处理客户端连接事件出错:', error);
  327. }
  328. }
  329. async handleClientDisconnect(client) {
  330. const clientId = client.id;
  331. const clientInfo = this.connectedClients.get(clientId);
  332. const username = clientInfo?.username || '';
  333. const ip = clientInfo?.ip || 'unknown';
  334. const connectedAt = clientInfo?.connectedAt;
  335. this.connectedClients.delete(clientId);
  336. console.log(`客户端已断开: ${clientId}`);
  337. try {
  338. await device_1.DeviceModel.update(clientId, {
  339. status: 'offline',
  340. last_event_time: new Date(),
  341. last_offline_time: new Date()
  342. });
  343. let connectionDuration;
  344. if (connectedAt) {
  345. connectionDuration = Math.floor((Date.now() - connectedAt.getTime()) / 1000);
  346. await device_1.DeviceModel.updateOnlineDuration(clientId, connectionDuration);
  347. }
  348. await clientConnection_1.ClientConnectionModel.create({
  349. clientid: clientId,
  350. username,
  351. event: 'client.disconnected',
  352. timestamp: new Date(),
  353. connected_at: connectedAt || undefined,
  354. node: 'mqtt-vue-dashboard',
  355. peername: ip,
  356. sockname: `0.0.0.0:${this.port}`,
  357. proto_name: 'MQTT',
  358. proto_ver: 4,
  359. keepalive: 60,
  360. clean_start: 1,
  361. reason: 'normal',
  362. connection_duration: connectionDuration
  363. });
  364. this.broadcastToWebSocket('device_disconnected', {
  365. clientid: clientId,
  366. username,
  367. timestamp: new Date().toISOString()
  368. });
  369. }
  370. catch (error) {
  371. console.error('处理客户端断开事件出错:', error);
  372. }
  373. }
  374. async handleMessagePublish(packet, client) {
  375. const clientId = client.id;
  376. const username = client.username || '';
  377. const topic = packet.topic;
  378. const payload = packet.payload?.toString() || '';
  379. const qosValue = typeof packet.qos === 'number' ? Math.min(packet.qos, 2) : 0;
  380. try {
  381. if (topic.startsWith('$SYS/'))
  382. return;
  383. await mqttMessage_1.MqttMessageModel.create({
  384. clientid: clientId,
  385. topic,
  386. payload,
  387. qos: qosValue,
  388. retain: packet.retain ? 1 : 0,
  389. message_type: 'publish',
  390. timestamp: Date.now(),
  391. node: 'mqtt-vue-dashboard',
  392. username,
  393. proto_ver: 4,
  394. payload_format: this.detectPayloadFormat(payload),
  395. message_time: new Date()
  396. });
  397. this.broadcastToWebSocket('mqtt_message', {
  398. clientid: clientId,
  399. topic,
  400. payload,
  401. qos: qosValue,
  402. retain: packet.retain,
  403. timestamp: Date.now()
  404. });
  405. await this.handleDeviceMessage(clientId, topic, payload);
  406. }
  407. catch (error) {
  408. console.error('处理消息发布事件出错:', error);
  409. }
  410. }
  411. async handleClientSubscribe(subscriptions, client) {
  412. const clientId = client.id;
  413. const username = client.username || '';
  414. const subsList = Array.isArray(subscriptions) ? subscriptions : [subscriptions];
  415. for (const sub of subsList) {
  416. try {
  417. const qosValue = typeof sub.qos === 'number' ? Math.min(sub.qos, 2) : 0;
  418. await mqttMessage_1.MqttMessageModel.create({
  419. clientid: clientId,
  420. topic: sub.topic,
  421. payload: '',
  422. qos: qosValue,
  423. retain: 0,
  424. message_type: 'subscribe',
  425. timestamp: Date.now(),
  426. node: 'mqtt-vue-dashboard',
  427. username,
  428. proto_ver: 4,
  429. payload_format: 'text',
  430. message_time: new Date()
  431. });
  432. this.broadcastToWebSocket('mqtt_subscribe', {
  433. clientid: clientId,
  434. topic: sub.topic,
  435. qos: qosValue,
  436. timestamp: Date.now()
  437. });
  438. }
  439. catch (error) {
  440. console.error('处理订阅事件出错:', error);
  441. }
  442. }
  443. }
  444. async handleClientUnsubscribe(subscriptions, client) {
  445. const clientId = client.id;
  446. const username = client.username || '';
  447. const subsList = Array.isArray(subscriptions) ? subscriptions : [subscriptions];
  448. for (const topic of subsList) {
  449. try {
  450. await mqttMessage_1.MqttMessageModel.create({
  451. clientid: clientId,
  452. topic: typeof topic === 'string' ? topic : topic.topic,
  453. payload: '',
  454. qos: 0,
  455. retain: 0,
  456. message_type: 'unsubscribe',
  457. timestamp: Date.now(),
  458. node: 'mqtt-vue-dashboard',
  459. username,
  460. proto_ver: 4,
  461. payload_format: 'text',
  462. message_time: new Date()
  463. });
  464. }
  465. catch (error) {
  466. console.error('处理取消订阅事件出错:', error);
  467. }
  468. }
  469. }
  470. async handleDeviceMessage(clientId, topic, payload) {
  471. try {
  472. const topicParts = topic.split('/');
  473. if (topicParts.length < 2)
  474. return;
  475. const deviceId = topicParts[1];
  476. const messageType = topicParts.slice(2).join('/');
  477. if (messageType === 'ota/status' || messageType === 'ota/progress') {
  478. await this.handleOtaMessage(deviceId, payload, messageType);
  479. }
  480. else if (messageType === 'relay/state') {
  481. await this.handleRelayStateMessage(deviceId, payload);
  482. }
  483. else if (messageType === 'wifi/rssi') {
  484. await this.handleRssiMessage(deviceId, payload);
  485. }
  486. else if (messageType === 'wifi/info') {
  487. await this.handleWifiInfoMessage(deviceId, payload);
  488. }
  489. else if (messageType === 'wifi/status') {
  490. await this.handleWifiStatusMessage(deviceId, payload);
  491. }
  492. else if (messageType === 'sensor/data' || messageType === 'data') {
  493. await this.handleSensorData(deviceId, topic, payload);
  494. }
  495. }
  496. catch (error) {
  497. console.error('处理设备消息出错:', error);
  498. }
  499. }
  500. async handleOtaMessage(deviceId, payload, messageType) {
  501. try {
  502. const data = JSON.parse(payload);
  503. let taskId = data.task_id || data.tid;
  504. if (taskId === undefined || taskId === null)
  505. return;
  506. let task = await ota_1.OTATaskModel.getById(taskId);
  507. if (!task)
  508. return;
  509. if (messageType === 'ota/progress') {
  510. await ota_1.OTATaskModel.updateStatusAndProgress(taskId, task.status, data.progress || 0);
  511. }
  512. else if (messageType === 'ota/status') {
  513. const validStatuses = ['pending', 'downloading', 'installing', 'success', 'failed', 'ready'];
  514. const status = data.status;
  515. if (!validStatuses.includes(status))
  516. return;
  517. const progress = data.progress !== undefined ? data.progress : (status === 'success' || status === 'ready' ? 100 : task.progress);
  518. if (status === 'ready') {
  519. const firmware = await firmware_1.FirmwareFileModel.getById(task.firmware_id);
  520. if (firmware && data.firmware_version) {
  521. if (data.firmware_version === firmware.version) {
  522. await ota_1.OTATaskModel.updateStatusAndProgress(taskId, 'success', 100);
  523. }
  524. else {
  525. await device_1.DeviceModel.update(deviceId, { firmware_version: data.firmware_version });
  526. await this.publishOtaCommand(taskId);
  527. return;
  528. }
  529. }
  530. else {
  531. await ota_1.OTATaskModel.updateStatusAndProgress(taskId, 'success', 100);
  532. }
  533. }
  534. else {
  535. await ota_1.OTATaskModel.updateStatusAndProgress(taskId, status, progress);
  536. }
  537. if (status === 'success' || status === 'ready') {
  538. if (data.firmware_version) {
  539. await device_1.DeviceModel.update(deviceId, { firmware_version: data.firmware_version });
  540. }
  541. }
  542. if (status === 'success' || status === 'failed') {
  543. await ota_1.OTATaskModel.updateResult(taskId, status, data.error_message);
  544. }
  545. }
  546. }
  547. catch (error) {
  548. console.error('处理OTA消息出错:', error);
  549. }
  550. }
  551. async handleRelayStateMessage(deviceId, payload) {
  552. try {
  553. const data = JSON.parse(payload);
  554. this.broadcastToWebSocket('device_relay_state', {
  555. deviceId,
  556. state: data.state || data,
  557. timestamp: new Date().toISOString()
  558. });
  559. }
  560. catch (error) {
  561. console.error('处理继电器状态消息出错:', error);
  562. }
  563. }
  564. async handleRssiMessage(deviceId, payload) {
  565. try {
  566. const rssi = parseInt(payload, 10);
  567. if (!isNaN(rssi)) {
  568. await device_1.DeviceModel.update(deviceId, { rssi });
  569. this.broadcastToWebSocket('device_rssi', {
  570. deviceId,
  571. rssi,
  572. timestamp: new Date().toISOString()
  573. });
  574. }
  575. }
  576. catch (error) {
  577. console.error('处理RSSI消息出错:', error);
  578. }
  579. }
  580. async handleWifiInfoMessage(deviceId, payload) {
  581. try {
  582. const data = JSON.parse(payload);
  583. this.broadcastToWebSocket('device_wifi_info', {
  584. deviceId,
  585. ...data,
  586. timestamp: new Date().toISOString()
  587. });
  588. }
  589. catch (error) {
  590. console.error('处理WiFi信息消息出错:', error);
  591. }
  592. }
  593. async handleWifiStatusMessage(deviceId, payload) {
  594. try {
  595. const data = JSON.parse(payload);
  596. this.broadcastToWebSocket('device_wifi_status', {
  597. deviceId,
  598. ...data,
  599. timestamp: new Date().toISOString()
  600. });
  601. }
  602. catch (error) {
  603. console.error('处理WiFi状态消息出错:', error);
  604. }
  605. }
  606. async handleSensorData(deviceId, topic, payload) {
  607. try {
  608. const { SensorDataModel } = require('../models/sensorData');
  609. const data = JSON.parse(payload);
  610. await SensorDataModel.create({
  611. device_id: deviceId,
  612. topic,
  613. data_type: data.type || 'unknown',
  614. value: payload,
  615. timestamp: new Date()
  616. });
  617. this.broadcastToWebSocket('sensor_data', {
  618. deviceId,
  619. topic,
  620. data,
  621. timestamp: new Date().toISOString()
  622. });
  623. }
  624. catch (error) {
  625. console.error('处理传感器数据出错:', error);
  626. }
  627. }
  628. async publishOtaCommand(taskId) {
  629. try {
  630. const task = await ota_1.OTATaskModel.getById(taskId);
  631. if (!task)
  632. return;
  633. const firmware = await firmware_1.FirmwareFileModel.getById(task.firmware_id);
  634. if (!firmware) {
  635. await ota_1.OTATaskModel.updateResult(taskId, 'failed', '固件不存在');
  636. return;
  637. }
  638. let otaServerUrl = process.env.OTA_SERVER_URL || process.env.BACKEND_URL || `http://localhost:${process.env.PORT || 3002}`;
  639. otaServerUrl = otaServerUrl.replace(/\/$/, '');
  640. const otaCommand = {
  641. act: 'upgrade',
  642. ver: firmware.version,
  643. url: `${otaServerUrl}/api/ota/firmware/${firmware.id}`,
  644. md5: firmware.md5sum,
  645. tid: taskId,
  646. rc: 3,
  647. ri: 10000,
  648. to: 30000
  649. };
  650. this.broker.publish({
  651. topic: `device/${task.device_id}/ota`,
  652. payload: Buffer.from(JSON.stringify(otaCommand)),
  653. qos: 1,
  654. retain: false
  655. }, (err) => {
  656. if (err) {
  657. console.error(`OTA指令发布失败,任务ID: ${taskId}`, err);
  658. ota_1.OTATaskModel.updateResult(taskId, 'failed', `OTA指令发送失败: ${err.message}`);
  659. }
  660. else {
  661. console.log(`OTA指令已发布,任务ID: ${taskId}, 设备: ${task.device_id}`);
  662. }
  663. });
  664. }
  665. catch (error) {
  666. console.error('发布OTA指令出错:', error);
  667. }
  668. }
  669. async executePendingOTATasks(deviceId) {
  670. try {
  671. const incompleteTasks = await ota_1.OTATaskModel.getIncompleteTasksByDeviceId(deviceId);
  672. if (incompleteTasks.length === 0)
  673. return;
  674. console.log(`设备 ${deviceId} 上线,发现 ${incompleteTasks.length} 个未完成OTA任务`);
  675. for (const task of incompleteTasks) {
  676. if (task.id) {
  677. await this.publishOtaCommand(task.id);
  678. }
  679. }
  680. }
  681. catch (error) {
  682. console.error(`执行设备 ${deviceId} 的待处理OTA任务出错:`, error);
  683. }
  684. }
  685. startDeviceStatusSync() {
  686. this.syncInterval = setInterval(async () => {
  687. try {
  688. const allDevices = await device_1.DeviceModel.getAll();
  689. const onlineClientIds = new Set(this.connectedClients.keys());
  690. for (const device of allDevices) {
  691. const isOnline = onlineClientIds.has(device.clientid);
  692. const newStatus = isOnline ? 'online' : 'offline';
  693. if (device.status !== newStatus) {
  694. await device_1.DeviceModel.update(device.clientid, {
  695. status: newStatus,
  696. last_event_time: new Date(),
  697. last_online_time: isOnline ? new Date() : device.last_online_time,
  698. last_offline_time: !isOnline ? new Date() : device.last_offline_time
  699. });
  700. }
  701. }
  702. }
  703. catch (error) {
  704. console.error('设备状态同步出错:', error);
  705. }
  706. }, 30000);
  707. }
  708. getConnectedClients() {
  709. return Array.from(this.connectedClients.values());
  710. }
  711. getConnectedClientCount() {
  712. return this.connectedClients.size;
  713. }
  714. getBroker() {
  715. return this.broker;
  716. }
  717. disconnectClient(clientId) {
  718. const clients = this.broker.clients;
  719. const client = clients ? clients[clientId] : null;
  720. if (client) {
  721. client.close();
  722. return true;
  723. }
  724. return false;
  725. }
  726. publish(topic, payload, options) {
  727. return new Promise((resolve, reject) => {
  728. this.broker.publish({
  729. topic,
  730. payload: Buffer.isBuffer(payload) ? payload : Buffer.from(payload),
  731. qos: options?.qos || 0,
  732. retain: options?.retain || false
  733. }, (err) => {
  734. if (err)
  735. reject(err);
  736. else
  737. resolve();
  738. });
  739. });
  740. }
  741. detectPayloadFormat(payload) {
  742. try {
  743. JSON.parse(payload);
  744. return 'json';
  745. }
  746. catch {
  747. if (/^[0-9a-fA-F]+$/.test(payload))
  748. return 'bin';
  749. return 'text';
  750. }
  751. }
  752. logAuth(clientId, username, ip, operationType, result, reason) {
  753. authLog_1.AuthLogModel.create({
  754. clientid: clientId,
  755. username,
  756. ip_address: ip,
  757. operation_type: operationType,
  758. result,
  759. reason
  760. }).catch((err) => {
  761. console.error('写入认证日志失败:', err);
  762. });
  763. }
  764. broadcastToWebSocket(event, data) {
  765. try {
  766. const wsService = (0, websocketService_1.getWebSocketService)();
  767. if (wsService) {
  768. const io = wsService.io;
  769. if (io) {
  770. io.emit(event, data);
  771. }
  772. }
  773. }
  774. catch (error) {
  775. }
  776. }
  777. }
  778. exports.MqttBrokerService = MqttBrokerService;
  779. //# sourceMappingURL=mqttBrokerService.js.map