"use strict"; const { requestTuple } = require("./tuple-api"); class AliyunUploader { constructor({ config, logger }) { this.config = config; this.logger = logger; this._devices = new Map(); // deviceNo → { iotDevice, tuple, lastTupleFailedAt } this._aliyunIot = null; } start() { if (!this.config.enabled) { this.logger.upload("aliyun", "init", "", "阿里云未启用,跳过"); return; } try { this._aliyunIot = require("alibabacloud-iot-device-sdk"); this.logger.upload("aliyun", "init", "", "阿里云 IoT SDK 已加载"); } catch (err) { this.logger.upload("aliyun", "init", "", `阿里云 IoT SDK 加载失败: ${err.message}`); } } async upload(deviceNo, parsedData) { if (!this.config.enabled) { return { enabled: false, ok: true, tuple: { ok: true, code: "ALIYUN_DISABLED" }, props: { ok: true, code: "ALIYUN_DISABLED" } }; } if (!deviceNo || String(deviceNo).trim() === "") { return { enabled: true, ok: false, tuple: { ok: false, code: "ALIYUN_DEVICE_NO_MISSING" }, props: { ok: false, code: "ALIYUN_PROPS_SKIP", reason: "设备号为空" } }; } const tupleResult = await this._ensureDevice(deviceNo); if (!tupleResult.ok) { return { enabled: true, ok: false, tuple: tupleResult, props: { ok: false, code: "ALIYUN_PROPS_SKIP", reason: "tuple not ready" } }; } const propsResult = await this._postProps(deviceNo, parsedData); return { enabled: true, ok: propsResult.ok, tuple: tupleResult, props: propsResult }; } async _ensureDevice(deviceNo) { const cached = this._devices.get(deviceNo); if (cached && cached.iotDevice) { return { ok: true, code: "ALIYUN_TUPLE_OK", reason: "device already registered" }; } // 三元组冷却检查 if (cached && cached.lastTupleFailedAt) { const cooldown = this.config.tupleRetryCooldownMs || 60000; if (Date.now() - cached.lastTupleFailedAt < cooldown) { return { ok: false, code: "ALIYUN_TUPLE_RETRY_COOLDOWN", reason: cached.lastTupleFailedReason || "冷却中" }; } } if (!this._aliyunIot) { return { ok: false, code: "ALIYUN_DEVICE_CREATE_FAIL", reason: "阿里云 SDK 未加载" }; } const result = await requestTuple({ baseUrl: this.config.tupleApiBaseUrl, path: this.config.tupleApiPath, autoRegister: this.config.autoRegister !== false, deviceNo, logger: this.logger, }); if (!result.ok) { this._devices.set(deviceNo, { lastTupleFailedAt: Date.now(), lastTupleFailedReason: result.reason, }); return result; } const tuple = result.data; let iotDevice; try { iotDevice = this._aliyunIot.device({ ProductKey: tuple.productKey, DeviceName: tuple.deviceName, DeviceSecret: tuple.deviceSecret, }); } catch (err) { this.logger.upload("aliyun", "device", deviceNo, `创建 SDK 实例失败: ${err.message}`); return { ok: false, code: "ALIYUN_DEVICE_CREATE_FAIL", reason: err.message }; } iotDevice.on("connect", () => { this.logger.upload("aliyun", "connect", deviceNo, "阿里云 SDK 已连接"); }); iotDevice.on("error", (err) => { this.logger.upload("aliyun", "error", deviceNo, `阿里云 SDK 错误: ${err.message || err}`); }); this._devices.set(deviceNo, { iotDevice, tuple, lastTupleFailedAt: null, lastTupleFailedReason: null }); this.logger.upload("aliyun", "device", deviceNo, "阿里云设备实例创建成功"); return { ok: true, code: "ALIYUN_TUPLE_OK", reason: "success" }; } _postProps(deviceNo, parsedData) { const POSTPROPS_TIMEOUT_MS = 10000; return new Promise((resolve) => { const cached = this._devices.get(deviceNo); if (!cached || !cached.iotDevice) { resolve({ ok: false, code: "ALIYUN_PROPS_SKIP", reason: "没有可用 SDK 实例" }); return; } let done = false; const timer = setTimeout(() => { if (done) return; done = true; this.logger.upload("aliyun", "postProps", deviceNo, "属性上报超时"); resolve({ ok: false, code: "ALIYUN_PROPS_TIMEOUT", reason: "postProps timeout" }); }, POSTPROPS_TIMEOUT_MS); cached.iotDevice.postProps(parsedData, (res) => { if (done) return; done = true; clearTimeout(timer); if (res && res.message === "success") { this.logger.upload("aliyun", "postProps", deviceNo, "属性上报成功"); resolve({ ok: true, code: "ALIYUN_PROPS_OK", reason: "success" }); } else { const reason = res ? JSON.stringify(res) : "无响应"; this.logger.upload("aliyun", "postProps", deviceNo, `属性上报失败: ${reason}`); resolve({ ok: false, code: "ALIYUN_PROPS_FAIL", reason }); } }); }); } getStatus() { let connectedCount = 0; for (const [, dev] of this._devices) { if (dev.iotDevice && dev.iotDevice._mqttClient && dev.iotDevice._mqttClient.connected) { connectedCount++; } } return { enabled: !!this.config.enabled, deviceCount: this._devices.size, connectedCount, }; } stop() { this._devices.clear(); } } module.exports = { AliyunUploader };