chenyc
2026-05-09 c7d690bc224fb84e88d3033bf324876e4a64b008
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"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 };