修改推送数据格式
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
4
app.js
4
app.js
@ -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);
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
// 引入客户端管理模块,用于维护连接的客户端
|
// 引入客户端管理模块,用于维护连接的客户端
|
||||||
const clients = require('./clients');
|
const clients = require('./clients');
|
||||||
|
const timestamp = require('../utils/timeFormatter');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置 SSE(Server-Sent Events)响应头,确保客户端能够正确接收事件流
|
* 设置 SSE(Server-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
86
package-lock.json
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
@ -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
13
utils/timeFormatter.js
Normal 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};
|
Reference in New Issue
Block a user