修改推送数据格式

This commit is contained in:
2025-07-02 21:05:33 +08:00
parent 318c6b5cc1
commit f347e1bd9c
9 changed files with 137 additions and 22 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea .idea
node_modules node_modules
.DS_Store .DS_Store
.env

4
app.js
View File

@ -14,6 +14,8 @@ const pushRouter = require('./routes/push');
const {sendHeartbeat} = require('./lib/sse'); const {sendHeartbeat} = require('./lib/sse');
const config = require('./config'); const config = require('./config');
const timestamp = require('./utils/timeFormatter');
const app = express(); const app = express();
// 中间件 // 中间件
@ -33,7 +35,7 @@ const errorHandler = (err, req, res, next) => {
res.status(500).json({ res.status(500).json({
code: 500, code: 500,
error: 'Internal Server Error', error: 'Internal Server Error',
timestamp: new Date().toISOString() timestamp: timestamp.formatTime()
}); });
}; };
app.use(errorHandler); app.use(errorHandler);

View File

@ -12,11 +12,19 @@ module.exports = {
env: process.env.NODE_ENV || 'development' env: process.env.NODE_ENV || 'development'
}, },
sse: { sse: {
heartbeatInterval: 15000, // 15 heartbeatInterval: 30000, // 30
allowedOrigins: '*', //process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3001'] allowedOrigins: '*', //process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3001']
}, },
rateLimit: { rateLimit: {
windowMs: 15 * 60 * 1000, // 15分钟 windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100个请求 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
} }
}; };

View File

@ -8,6 +8,7 @@
// 引入客户端管理模块,用于维护连接的客户端 // 引入客户端管理模块,用于维护连接的客户端
const clients = require('./clients'); const clients = require('./clients');
const timestamp = require('../utils/timeFormatter');
/** /**
* 设置 SSEServer-Sent Events响应头确保客户端能够正确接收事件流 * 设置 SSEServer-Sent Events响应头确保客户端能够正确接收事件流
@ -27,7 +28,7 @@ function setupSSEHeaders(res) {
function sendHeartbeat() { function sendHeartbeat() {
const data = { const data = {
event: 'heartbeat', // 事件类型为 heartbeat event: 'heartbeat', // 事件类型为 heartbeat
time: new Date().toISOString() // 当前时间的 ISO 字符串格式 time: timestamp.formatTime() // 当前时间的 ISO 字符串格式
}; };
return clients.broadcast(data); // 广播给所有连接的客户端 return clients.broadcast(data); // 广播给所有连接的客户端
} }

86
package-lock.json generated
View File

@ -15,9 +15,70 @@
"express-sse": "^1.0.0", "express-sse": "^1.0.0",
"helmet": "^8.1.0", "helmet": "^8.1.0",
"moment-timezone": "^0.6.0", "moment-timezone": "^0.6.0",
"redis": "^5.5.6",
"uuid": "^11.1.0" "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": { "node_modules/accepts": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@ -89,6 +150,15 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/content-disposition": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@ -690,6 +760,22 @@
"node": ">= 0.8" "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": { "node_modules/router": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",

View File

@ -10,6 +10,7 @@
"express-sse": "^1.0.0", "express-sse": "^1.0.0",
"helmet": "^8.1.0", "helmet": "^8.1.0",
"moment-timezone": "^0.6.0", "moment-timezone": "^0.6.0",
"redis": "^5.5.6",
"uuid": "^11.1.0" "uuid": "^11.1.0"
} }
} }

View File

@ -8,33 +8,38 @@
const express = require('express'); const express = require('express');
const clients = require('../lib/clients'); const clients = require('../lib/clients');
const moment = require('moment-timezone') const timeFormat = require('../utils/timeFormatter');
const router = express.Router(); const router = express.Router();
router.post('/', (req, res) => { router.post('/', (req, res) => {
const {message, clientId} = req.body; const { id, type, data, timestamp: ts, client_id } = req.body;
if (!message) { if (!type || !data) {
return res.status(400).json({error: 'Message is required'}); 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 (client_id) {
const clientRes = clients.get(client_id);
if (clientId) {
const clientRes = clients.get(clientId);
if (!clientRes) { 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`); clientRes.write(`data: ${JSON.stringify(payload)}\n\n`);
return res.json({message: 'Message delivered', clientId}); return res.json({ message: 'Message delivered', client_id });
} }
const count = clients.broadcast(data); const count = clients.broadcast(payload);
return res.json({message: 'Message broadcasted', clients: count}); return res.json({ message: 'Message broadcasted', delivered_to: count });
}); });
module.exports = router; module.exports = router;

View File

@ -10,7 +10,7 @@ const express = require('express');
const { v4: uuidV4 } = require('uuid'); const { v4: uuidV4 } = require('uuid');
const { setupSSEHeaders } = require('../lib/sse'); const { setupSSEHeaders } = require('../lib/sse');
const clients = require('../lib/clients'); const clients = require('../lib/clients');
const moment = require('moment-timezone'); const timestamp = require('../utils/timeFormatter');
const router = express.Router(); const router = express.Router();
@ -19,13 +19,11 @@ router.get('/', (req, res) => {
setupSSEHeaders(res); setupSSEHeaders(res);
const shanghaiTime = moment().tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
// 立即发送确认 // 立即发送确认
res.write(`data: ${JSON.stringify({ res.write(`data: ${JSON.stringify({
status: 'connected', status: 'connected',
clientId, clientId,
time: shanghaiTime time: timestamp.formatTime()
})}\n\n`); })}\n\n`);
clients.add(clientId, res); clients.add(clientId, res);

13
utils/timeFormatter.js Normal file
View File

@ -0,0 +1,13 @@
/**
* 时间格式化工具
* @author Yk <yk_9001@icloud.com>
* @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};