webhookService.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. const AuthzCheck = require('./AuthzCheck');
  2. const Device = require('./Device');
  3. const logger = require('../logger'); // 引入日志记录器
  4. const HeaterUsageService = require('../services/HeaterUsageService');
  5. const RelayState = require('./RelayState'); // 需要新建RelayState模型
  6. class WebhookService {
  7. // 将 UTC 时间转换为北京时间(UTC+8)
  8. static convertToBeijingTime(utcTimestamp) {
  9. const date = new Date(utcTimestamp);
  10. date.setHours(date.getHours() + 8); // 转换为北京时间
  11. return date.toISOString().slice(0, 19).replace('T', ' ');
  12. }
  13. // 处理设备上下线事件
  14. static handleDeviceEvent(eventData) {
  15. const client_id = eventData.clientid || undefined;
  16. const status = eventData.event === 'client.connected' ? 'online' : 'offline';
  17. const timestamp = WebhookService.convertToBeijingTime(eventData.timestamp);
  18. const ip_address = eventData.peername || 'unknown:0';
  19. logger.info(`处理设备${status}事件: ${client_id}`);
  20. logger.debug('事件数据:', { client_id, status, timestamp, ip_address });
  21. // 查询设备的当前状态
  22. Device.getDeviceById(client_id, (err, device) => {
  23. if (err) {
  24. logger.error('查询设备失败:', err);
  25. return;
  26. }
  27. // 如果设备不存在,插入新设备
  28. if (!device) {
  29. const first_online_time = status === 'online' ? timestamp : null;
  30. logger.info(`新设备首次上线: ${client_id}`);
  31. Device.insert(
  32. client_id,
  33. status,
  34. timestamp,
  35. first_online_time,
  36. status === 'online' ? timestamp : null,
  37. ip_address,
  38. (err, result) => {
  39. if (err) {
  40. logger.error('插入新设备失败:', err);
  41. return;
  42. }
  43. logger.info(`新设备插入成功,设备ID:${result.insertId}`);
  44. }
  45. );
  46. } else {
  47. // 设备存在,更新状态
  48. if (status === 'online') {
  49. const first_online_time = (!device || !device.first_online_time) ? timestamp : device.first_online_time;
  50. logger.info(`设备 ${client_id} 上线`);
  51. Device.updateOnlineStatus(
  52. client_id,
  53. status,
  54. timestamp,
  55. first_online_time,
  56. timestamp,
  57. ip_address,
  58. (err) => {
  59. if (err) {
  60. logger.error('更新设备在线状态失败:', err);
  61. return;
  62. }
  63. logger.info(`设备 ${client_id} 在线状态更新成功`);
  64. }
  65. );
  66. } else {
  67. logger.info(`设备 ${client_id} 下线`);
  68. Device.updateOfflineStatus(
  69. client_id,
  70. status,
  71. timestamp,
  72. timestamp,
  73. (err) => {
  74. if (err) {
  75. logger.error('更新设备离线状态失败:', err);
  76. return;
  77. }
  78. logger.info(`设备 ${client_id} 离线状态更新成功`);
  79. }
  80. );
  81. }
  82. }
  83. });
  84. }
  85. // 处理温度回传事件
  86. // 在handleTemperatureEvent方法中:
  87. // 修改handleTemperatureEvent调用方式
  88. static async handleTemperatureEvent(eventData) {
  89. const topic = eventData.topic;
  90. const payload = eventData.payload;
  91. const timestamp = WebhookService.convertToBeijingTime(eventData.timestamp);
  92. const device_id = topic.split('/')[1];
  93. logger.info(`处理温度事件: 设备=${device_id}, 温度=${payload}`);
  94. const room_number = eventData.room_number || 'default_room';
  95. const room_name = eventData.room_name || '默认房间';
  96. try {
  97. await new Promise((resolve, reject) => {
  98. Device.updateTemperature(
  99. device_id,
  100. payload,
  101. timestamp,
  102. room_number, // 新增参数
  103. room_name, // 新增参数
  104. (err) => { // 保持回调函数存在
  105. if (err) return reject(err);
  106. resolve();
  107. }
  108. );
  109. });
  110. // 删除重复的updateTemperature调用
  111. const device = await new Promise((resolve) => {
  112. Device.getDeviceById(device_id, (err, device) => {
  113. resolve(device || {});
  114. });
  115. });
  116. logger.debug('开始处理加热器业务逻辑', {
  117. device_id,
  118. status: device.status,
  119. temperature: payload,
  120. switch_status: device.switch_status
  121. });
  122. await HeaterUsageService.handleHeaterUsage(
  123. device_id,
  124. device.status,
  125. payload,
  126. device.switch_status,
  127. timestamp
  128. );
  129. logger.info(`设备 ${device_id} 温度处理完成`);
  130. } catch (error) {
  131. logger.error(`温度事件处理失败: ${error.message}`, {
  132. device_id,
  133. stack: error.stack
  134. });
  135. } // 添加方法结束括号
  136. } // 此处添加方法结束括号
  137. // 处理开关指令事件
  138. static handleSwitchEvent(eventData) {
  139. const topic = eventData.topic;
  140. const payload = eventData.payload;
  141. const timestamp = WebhookService.convertToBeijingTime(eventData.timestamp);
  142. const device_id = topic.split('/')[1];
  143. logger.info(`处理开关事件: 设备=${device_id}, 状态=${payload}`);
  144. // 修正参数传递
  145. const room_number = eventData.room_number || 'N/A';
  146. const room_name = eventData.room_name || '未命名房间';
  147. Device.updateSwitchStatus(
  148. device_id,
  149. payload, // 修正为使用 payload 而不是未定义的 switch_status
  150. timestamp,
  151. room_number,
  152. room_name,
  153. (err) => {
  154. if (err) {
  155. logger.error('更新开关状态失败:', err);
  156. return;
  157. }
  158. logger.info(`设备 ${device_id} 开关状态更新成功`);
  159. }
  160. );
  161. }
  162. // 处理继电器状态事件
  163. static handleRelayStateEvent(eventData) {
  164. const topic = eventData.topic;
  165. const payload = eventData.payload;
  166. const timestamp = WebhookService.convertToBeijingTime(eventData.timestamp);
  167. const device_id = topic.split('/')[1];
  168. logger.info(`处理继电器状态事件: 设备=${device_id}, 状态=${payload}`);
  169. Device.updateSwitchStatus(device_id, payload, timestamp, (err) => {
  170. if (err) {
  171. logger.error('更新继电器状态失败:', err);
  172. return;
  173. }
  174. logger.info(`设备 ${device_id} 继电器状态更新成功: ${payload}`);
  175. });
  176. }
  177. // 处理 GPIO 状态事件
  178. static handleGpioStateEvent(eventData) {
  179. const topic = eventData.topic;
  180. const payload = eventData.payload;
  181. const timestamp = WebhookService.convertToBeijingTime(eventData.timestamp);
  182. const device_id = topic.split('/')[1];
  183. logger.info(`处理GPIO状态事件: 设备=${device_id}, 状态=${payload}`);
  184. Device.updateLevelStatus(device_id, payload, timestamp, (err) => {
  185. if (err) {
  186. logger.error('更新GPIO状态失败:', err);
  187. return;
  188. }
  189. logger.info(`设备 ${device_id} GPIO状态更新成功: ${payload}`);
  190. });
  191. }
  192. // 处理授权检查完成事件
  193. static handleAuthzCheckEvent(eventData) {
  194. const client_id = eventData.clientid || eventData.client_id;
  195. const topic = eventData.topic;
  196. const action = eventData.action;
  197. const result = eventData.result || 'deny';
  198. const timestamp = eventData.timestamp || Date.now();
  199. logger.info(`处理授权检查事件: 客户端=${client_id}, 主题=${topic}`);
  200. logger.debug('授权检查详情:', { action, result });
  201. if (!client_id) {
  202. logger.error('无效的授权检查事件: 缺少客户端ID');
  203. return;
  204. }
  205. const authzData = {
  206. client_id,
  207. topic,
  208. action,
  209. result: result === 'allow' ? 'allow' : 'deny',
  210. timestamp: WebhookService.convertToBeijingTime(timestamp)
  211. };
  212. AuthzCheck.insert(authzData, (err, result) => {
  213. if (err) {
  214. logger.error('记录授权检查事件失败:', err);
  215. return;
  216. }
  217. logger.info(`授权检查事件记录成功: ID=${result.insertId}`);
  218. });
  219. }
  220. // 处理消息传递事件
  221. static handleMessageDeliveredEvent(eventData) {
  222. const client_id = eventData.clientid || eventData.client_id || undefined;
  223. const { topic, payload, timestamp } = eventData;
  224. console.log('Message delivered:', {
  225. client_id,
  226. topic,
  227. payload,
  228. timestamp: WebhookService.convertToBeijingTime(timestamp) // 使用北京时间
  229. });
  230. }
  231. // 处理消息丢弃事件
  232. static handleMessageDroppedEvent(eventData) {
  233. const client_id = eventData.clientid || eventData.client_id || undefined;
  234. const { topic, payload, reason, timestamp } = eventData;
  235. console.log('Message dropped:', {
  236. client_id,
  237. topic,
  238. payload,
  239. reason,
  240. timestamp: WebhookService.convertToBeijingTime(timestamp) // 使用北京时间
  241. });
  242. }
  243. }
  244. // 导出 WebhookService 类
  245. module.exports = WebhookService;