const fs = require("fs"); const path = require("path"); const logger = require("./logger"); class PropertyMapper { constructor() { this.map = new Map(); // identifier -> { identifier, name, ... } this.loadSchema(); } loadSchema() { try { // 在普通 Node 环境下,schema.json 位于项目根目录; // 使用 pkg 打包后,优先从可执行文件同级目录读取 schema.json,便于现场替换。 const appRoot = process.pkg ? path.dirname(process.execPath) : path.join(__dirname, ".."); const schemaPath = path.join(appRoot, "schema.json"); const raw = fs.readFileSync(schemaPath, "utf8"); const json = JSON.parse(raw); if (Array.isArray(json.properties)) { for (const prop of json.properties) { if (prop.identifier) { this.map.set(prop.identifier, prop); } } } logger.info("Property schema loaded", { count: this.map.size }); } catch (err) { logger.warn("Failed to load schema.json, using empty mapping", err.message || err); } } mapData(rawData) { const result = []; if (!rawData || typeof rawData !== "object") return result; for (const [key, value] of Object.entries(rawData)) { const def = this.map.get(key) || { identifier: key, name: key }; result.push({ identifier: def.identifier, name: def.name, value }); } return result; } transformForHTTP(rawData, deviceNumber) { return { deviceNumber, properties: this.mapData(rawData) }; } transformForMQTT(rawData, deviceNumber) { return { deviceNumber, timestamp: Date.now(), properties: this.mapData(rawData) }; } transformForAliyun(rawData) { // 阿里云物模型上报通常直接使用 identifier -> value const result = {}; if (!rawData || typeof rawData !== "object") return result; for (const [key, value] of Object.entries(rawData)) { result[key] = value; } return result; } } module.exports = PropertyMapper;