diff --git a/.gitignore b/.gitignore index b934aa6..d2c112b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea node_modules -.DS_Store \ No newline at end of file +.DS_Store +.env \ No newline at end of file diff --git a/app.js b/app.js index 2320341..36c9816 100644 --- a/app.js +++ b/app.js @@ -14,6 +14,8 @@ const pushRouter = require('./routes/push'); const {sendHeartbeat} = require('./lib/sse'); const config = require('./config'); +const timestamp = require('./utils/timeFormatter'); + const app = express(); // 中间件 @@ -33,7 +35,7 @@ const errorHandler = (err, req, res, next) => { res.status(500).json({ code: 500, error: 'Internal Server Error', - timestamp: new Date().toISOString() + timestamp: timestamp.formatTime() }); }; app.use(errorHandler); diff --git a/config/index.js b/config/index.js index 2e79851..a0bbf74 100644 --- a/config/index.js +++ b/config/index.js @@ -12,11 +12,19 @@ module.exports = { env: process.env.NODE_ENV || 'development' }, sse: { - heartbeatInterval: 15000, // 15秒 + heartbeatInterval: 30000, // 30秒 allowedOrigins: '*', //process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3001'] }, rateLimit: { windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 每个IP限制100个请求 + }, + redis: { + url: process.env.REDIS_URL || 'redis://localhost:6379', + database: process.env.REDIS_DB || 1, // 默认使用DB1 + socket: { + reconnectStrategy: (retries) => Math.min(retries * 100, 5000) + }, + ttl: 86400 } }; \ No newline at end of file diff --git a/lib/sse.js b/lib/sse.js index d535d41..793d8a0 100644 --- a/lib/sse.js +++ b/lib/sse.js @@ -8,6 +8,7 @@ // 引入客户端管理模块,用于维护连接的客户端 const clients = require('./clients'); +const timestamp = require('../utils/timeFormatter'); /** * 设置 SSE(Server-Sent Events)响应头,确保客户端能够正确接收事件流 @@ -27,7 +28,7 @@ function setupSSEHeaders(res) { function sendHeartbeat() { const data = { event: 'heartbeat', // 事件类型为 heartbeat - time: new Date().toISOString() // 当前时间的 ISO 字符串格式 + time: timestamp.formatTime() // 当前时间的 ISO 字符串格式 }; return clients.broadcast(data); // 广播给所有连接的客户端 } diff --git a/package-lock.json b/package-lock.json index 2844f61..4999608 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,70 @@ "express-sse": "^1.0.0", "helmet": "^8.1.0", "moment-timezone": "^0.6.0", + "redis": "^5.5.6", "uuid": "^11.1.0" } }, + "node_modules/@redis/bloom": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.5.6.tgz", + "integrity": "sha512-bNR3mxkwtfuCxNOzfV8B3R5zA1LiN57EH6zK4jVBIgzMzliNuReZXBFGnXvsi80/SYohajn78YdpYI+XNpqL+A==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.5.6" + } + }, + "node_modules/@redis/client": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.5.6.tgz", + "integrity": "sha512-M3Svdwt6oSfyfQdqEr0L2HOJH2vK7GgCFx1NfAQvpWAT4+ljoT1L5S5cKT3dA9NJrxrOPDkdoTPWJnIrGCOcmw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.5.6.tgz", + "integrity": "sha512-AIsoe3SsGQagqAmSQHaqxEinm5oCWr7zxPWL90kKaEdLJ+zw8KBznf2i9oK0WUFP5pFssSQUXqnscQKe2amfDQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.5.6" + } + }, + "node_modules/@redis/search": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.5.6.tgz", + "integrity": "sha512-JSqasYqO0mVcHL7oxvbySRBBZYRYhFl3W7f0Da7BW8M/r0Z9wCiVrdjnN4/mKBpWZkoJT/iuisLUdPGhpKxBew==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.5.6" + } + }, + "node_modules/@redis/time-series": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.5.6.tgz", + "integrity": "sha512-jkpcgq3NOI3TX7xEAJ3JgesJTxAx7k0m6lNxNsYdEM8KOl+xj7GaB/0CbLkoricZDmFSEAz7ClA1iK9XkGHf+Q==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.5.6" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -89,6 +150,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -690,6 +760,22 @@ "node": ">= 0.8" } }, + "node_modules/redis": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.5.6.tgz", + "integrity": "sha512-hbpqBfcuhWHOS9YLNcXcJ4akNr7HFX61Dq3JuFZ9S7uU7C7kvnzuH2PDIXOP62A3eevvACoG8UacuXP3N07xdg==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.5.6", + "@redis/client": "5.5.6", + "@redis/json": "5.5.6", + "@redis/search": "5.5.6", + "@redis/time-series": "5.5.6" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/package.json b/package.json index bb6393e..ac31843 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "express-sse": "^1.0.0", "helmet": "^8.1.0", "moment-timezone": "^0.6.0", + "redis": "^5.5.6", "uuid": "^11.1.0" } } diff --git a/routes/push.js b/routes/push.js index 8af0962..89c6478 100644 --- a/routes/push.js +++ b/routes/push.js @@ -8,33 +8,38 @@ const express = require('express'); const clients = require('../lib/clients'); -const moment = require('moment-timezone') +const timeFormat = require('../utils/timeFormatter'); const router = express.Router(); router.post('/', (req, res) => { - const {message, clientId} = req.body; + const { id, type, data, timestamp: ts, client_id } = req.body; - if (!message) { - return res.status(400).json({error: 'Message is required'}); + if (!type || !data) { + return res.status(400).json({ error: 'Missing required fields: type or data' }); } - const shanghaiTime = moment().tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss'); + const payload = { + id: id || crypto.randomUUID(), + type, + data, + timestamp: ts || Math.floor(Date.now() / 1000), + client_id: client_id || null, + server_time: timeFormat.formatTime() + }; - const data = {message, time: shanghaiTime}; - - if (clientId) { - const clientRes = clients.get(clientId); + if (client_id) { + const clientRes = clients.get(client_id); if (!clientRes) { - return res.status(404).json({error: 'Client not found'}); + return res.status(404).json({ error: `Client ${client_id} not found` }); } - clientRes.write(`data: ${JSON.stringify(data)}\n\n`); - return res.json({message: 'Message delivered', clientId}); + clientRes.write(`data: ${JSON.stringify(payload)}\n\n`); + return res.json({ message: 'Message delivered', client_id }); } - const count = clients.broadcast(data); - return res.json({message: 'Message broadcasted', clients: count}); + const count = clients.broadcast(payload); + return res.json({ message: 'Message broadcasted', delivered_to: count }); }); module.exports = router; \ No newline at end of file diff --git a/routes/stream.js b/routes/stream.js index 6b5ac2f..c637752 100644 --- a/routes/stream.js +++ b/routes/stream.js @@ -10,7 +10,7 @@ const express = require('express'); const { v4: uuidV4 } = require('uuid'); const { setupSSEHeaders } = require('../lib/sse'); const clients = require('../lib/clients'); -const moment = require('moment-timezone'); +const timestamp = require('../utils/timeFormatter'); const router = express.Router(); @@ -19,13 +19,11 @@ router.get('/', (req, res) => { setupSSEHeaders(res); - const shanghaiTime = moment().tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss'); - // 立即发送确认 res.write(`data: ${JSON.stringify({ status: 'connected', clientId, - time: shanghaiTime + time: timestamp.formatTime() })}\n\n`); clients.add(clientId, res); diff --git a/utils/timeFormatter.js b/utils/timeFormatter.js new file mode 100644 index 0000000..72dd1a8 --- /dev/null +++ b/utils/timeFormatter.js @@ -0,0 +1,13 @@ +/** + * 时间格式化工具 + * @author Yk + * @param {string} timezone - 时区 + * @returns {string} 格式化后的时间字符串 + */ +function formatTime(timezone = 'Asia/Shanghai') { + const moment = require('moment-timezone'); + + return moment().tz(timezone).format('YYYY-MM-DD HH:mm:ss'); +} + +module.exports = {formatTime}; \ No newline at end of file