From 7885cede659f3255be56f77c1eef2ada7387d6f1 Mon Sep 17 00:00:00 2001
From: chenyc <501753378@qq.com>
Date: 星期日, 22 三月 2026 16:23:21 +0800
Subject: [PATCH] 初始化项目

---
 src/httpServer.js |  151 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 151 insertions(+), 0 deletions(-)

diff --git a/src/httpServer.js b/src/httpServer.js
new file mode 100644
index 0000000..636923e
--- /dev/null
+++ b/src/httpServer.js
@@ -0,0 +1,151 @@
+const express = require("express");
+const config = require("./config");
+const logger = require("./logger");
+
+function createCorsMiddleware() {
+  return (req, res, next) => {
+    if (!config.http.cors || !config.http.cors.enabled) {
+      return next();
+    }
+    const origin = req.headers.origin || "*";
+    const allowed =
+      config.http.cors.origins.includes("*") ||
+      config.http.cors.origins.includes(origin);
+
+    if (allowed) {
+      res.header("Access-Control-Allow-Origin", origin);
+      res.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
+      res.header("Access-Control-Allow-Headers", "Content-Type");
+    }
+
+    if (req.method === "OPTIONS") {
+      return res.sendStatus(204);
+    }
+
+    next();
+  };
+}
+
+function createHttpServer({ dataCache, rateLimiter, propertyMapper, deviceManager }) {
+  if (!config.http || !config.http.enabled) {
+    logger.info("HTTP server is disabled by config");
+    return null;
+  }
+
+  const app = express();
+  app.use(express.json({ limit: "1mb" }));
+  app.use(createCorsMiddleware());
+
+  app.get("/api/device/data", (req, res) => {
+    const deviceNumber = req.query.deviceNumber;
+    const mapped = String(req.query.mapped || "false").toLowerCase() === "true";
+
+    if (!deviceNumber) {
+      return res.status(400).json({ error: "deviceNumber is required" });
+    }
+
+    const limit = rateLimiter.checkLimit(
+      `device:${deviceNumber}`,
+      config.http.rateLimit.singleDeviceMs
+    );
+    if (!limit.allowed) {
+      return res.status(429).json({
+        error: "too many requests",
+        waitMs: limit.waitMs
+      });
+    }
+
+    const entry = dataCache.getDeviceData(deviceNumber);
+    if (!entry) {
+      return res.status(404).json({ error: "device not found" });
+    }
+
+    const base = mapped
+      ? propertyMapper.transformForHTTP(entry.data, deviceNumber)
+      : { deviceNumber, data: entry.data };
+
+    return res.json({ ...base, updatedAt: entry.updatedAt });
+  });
+
+  app.get("/api/device/all", (req, res) => {
+    const mapped = String(req.query.mapped || "false").toLowerCase() === "true";
+
+    const limit = rateLimiter.checkLimit(
+      "__all_devices__",
+      config.http.rateLimit.allDevicesMs
+    );
+    if (!limit.allowed) {
+      return res.status(429).json({
+        error: "too many requests",
+        waitMs: limit.waitMs
+      });
+    }
+
+    const all = dataCache.getAllDeviceData();
+    const result = {};
+
+    for (const [deviceNumber, entry] of Object.entries(all)) {
+      if (mapped) {
+        result[deviceNumber] = {
+          ...propertyMapper.transformForHTTP(entry.data, deviceNumber),
+          updatedAt: entry.updatedAt
+        };
+      } else {
+        result[deviceNumber] = {
+          deviceNumber,
+          data: entry.data,
+          updatedAt: entry.updatedAt
+        };
+      }
+    }
+
+    return res.json(result);
+  });
+
+  app.get("/api/device/list", (req, res) => {
+    return res.json(dataCache.getDeviceList());
+  });
+
+  app.get("/api/cache/stats", (req, res) => {
+    return res.json(dataCache.getStats());
+  });
+
+  app.post("/api/cache/clear", (req, res) => {
+    dataCache.clear();
+    return res.json({ ok: true });
+  });
+
+  app.get("/api/device/idle", (req, res) => {
+    const timeout = parseInt(req.query.timeout, 10);
+    const timeoutMs = Number.isFinite(timeout) && timeout > 0 ? timeout : 5 * 60 * 1000;
+    return res.json(dataCache.getIdleDevices(timeoutMs));
+  });
+
+  app.get("/api/ratelimit/stats", (req, res) => {
+    return res.json(rateLimiter.getStats());
+  });
+
+  app.post("/api/ratelimit/clear", (req, res) => {
+    rateLimiter.clear();
+    return res.json({ ok: true });
+  });
+
+  app.get("/api/health", (req, res) => {
+    res.json({
+      status: "ok",
+      tcp: "listening",
+      devices: deviceManager ? deviceManager.getStats() : []
+    });
+  });
+
+  const server = app.listen(config.http.port, config.http.host, () => {
+    logger.info("HTTP server listening", {
+      host: config.http.host,
+      port: config.http.port
+    });
+  });
+
+  return server;
+}
+
+module.exports = createHttpServer;

--
Gitblit v1.8.0