// aliyun-iot.js const IotDevice = require('alibabacloud-iot-device-sdk'); const logger = require('./logger'); const { getAliyunDeviceSecret, getAliyunConfig } = require('./api'); // 固定设备信息(可被配置覆盖) const PRODUCT_KEY = 'k08fzZOZdxN'; // 可选的默认设备密钥(仅用于兼容单设备调试),实际应按设备序列号获取专属密钥 const DEFAULT_DEVICE_SECRET = null; // 存储设备实例(长连接复用) const devices = new Map(); /** * 获取或创建设备实例(长连接) * @param {string} deviceId * @return {Object|null} 设备实例 或 null(创建失败) */ // 统一封装:按设备号从你的服务获取三元组 const aliyunCfg = getAliyunConfig(); let configLogged = false; async function fetchDeviceTriple(deviceId, log) { try { (log || logger).info(`${deviceId} 请求三元组,设备号: ${deviceId}`); const resp = await getAliyunDeviceSecret(aliyunCfg.secretApiPath || 'device/info/getAliyunDeviceSecret', deviceId); const body = resp && resp.data ? resp.data : resp; // 兼容不同返回结构:{code, data:{productKey, deviceName, deviceSecret}} 或 {productKey, deviceName, deviceSecret} const payload = body && body.data ? body.data : body; const pk = payload?.productKey || aliyunCfg.productKeyOverride || PRODUCT_KEY; const dn = payload?.deviceName || deviceId; const ds = payload?.deviceSecret || null; if (!ds) { throw new Error('未获取到 deviceSecret'); } return { productKey: pk, deviceName: dn, deviceSecret: ds }; } catch (err) { (log || logger).error(`${deviceId} 获取三元组失败: ${err.message}`); return null; } } async function getOrCreateDevice(deviceId, ctx) { const log = logger.forDevice(deviceId, ctx?.ip, ctx?.clientId); if (!deviceId || typeof deviceId !== 'string') { log.error('无效的 deviceId', { deviceId }); return null; } const existingDevice = devices.get(deviceId); if (existingDevice) { return existingDevice; } try { // 尝试为该设备ID获取独立的 deviceSecret const triple = await fetchDeviceTriple(deviceId, log); if (!triple) return null; const { productKey, deviceName, deviceSecret } = triple; const region = aliyunCfg.region || 'cn-shanghai'; const host = `${productKey}.iot-as-mqtt.${region}.aliyuncs.com`; if (!configLogged) { configLogged = true; log.info('aliyun.config', { source: aliyunCfg.__meta?.source, path: aliyunCfg.__meta?.path, region, productKeyOverride: aliyunCfg.productKeyOverride || '', apiBaseUrl: aliyunCfg.apiBaseUrl, secretApiPath: aliyunCfg.secretApiPath, }); } const device = IotDevice.device({ productKey, deviceName, deviceSecret, host, port: 1883, keepalive: 60, clean: true, reconnectPeriod: 5000 // 自动重连间隔 }); device.on('connect', () => { log.info(`✅ 设备 ${deviceId} 已连接到阿里云 IoT`); }); device.on('reconnect', () => { log.warn(`🔁 设备 ${deviceId} 正在重连...`); }); device.on('error', (err) => { log.error(`❌ 设备 ${deviceId} 发生错误`, { 错误: err.message, 堆栈: err.stack }); }); device.on('offline', () => { log.warn(`⚠️ 设备 ${deviceId} 已离线`); }); // 缓存实例 devices.set(deviceId, device); return device; } catch (err) { log.error('创建设备实例失败', { 错误: err.message, 堆栈: err.stack, deviceId }); return null; } } /** * 发送数据到阿里云 IoT(高频上报,长连接) * @param {string} deviceId * @param {Object} data */ async function sendDataToAliyun(deviceId, data, ctx) { const log = logger.forDevice(deviceId, ctx?.ip, ctx?.clientId); // 参数校验 if (!deviceId || typeof deviceId !== 'string' || !data || typeof data !== 'object') { log.error('sendDataToAliyun 参数错误', { deviceId, data: typeof data }); return; } let device; try { device = await getOrCreateDevice(deviceId, ctx); if (!device) { log.error('无法创建设备实例', { deviceId }); return; } // 如果未连接,不阻塞,直接返回(可选:记录日志) if (!device.connected) { log.warn(`设备 ${deviceId} 当前未连接,跳过发送`); return; } // 发送数据(可配置是否打印 payload) if (aliyunCfg.printPayload) { console.log(`\n🚀 Aliyun IoT postProps 发送 [${deviceId}] JSON:`); try { console.log(JSON.stringify(data, null, 2)); } catch (_) { console.log(data); } } device.postProps(data, (res) => { console.log('Aliyun IoT postProps 回调:', res); if (res?.message === 'success') { // 成功:无需日志(太频繁),可注释 log.debug(`📡 数据已发送`, { deviceId }); } else { const errorMsg = res?.message || '未知错误'; log.error(`❌ 发送失败`, { deviceId, 错误: errorMsg, data }); } }); } catch (err) { log.error('sendDataToAliyun 执行异常', { 错误: err.message, 堆栈: err.stack, deviceId, data }); } } /** * 关闭所有设备连接(用于进程退出) */ function closeAllConnections() { devices.forEach((device, id) => { try { device.end(); logger.forDevice(id).info(`已关闭设备连接: ${id}`); } catch (err) { logger.forDevice(id).error(`关闭设备 ${id} 失败`, { 错误: err.message }); } }); devices.clear(); } // 优雅退出 process.on('SIGINT', closeAllConnections); process.on('SIGTERM', closeAllConnections); module.exports = { sendDataToAliyun, closeAllConnections // 可选导出,用于主程序调用 };