为了保护服务器性能和防止恶意请求,系统实现了基于设备号的请求限流功能。相同的设备号在指定时间间隔内只能发起一次请求。
{
"enabled": true,
"port": 8080,
"host": "0.0.0.0",
"cors": {
"enabled": true,
"allowOrigin": "*"
},
"rateLimit": {
"enabled": true,
"interval": 5000
}
}
| 参数 | 说明 | 默认值 |
|---|---|---|
rateLimit.enabled |
是否启用限流 | true |
rateLimit.interval |
限流时间间隔(毫秒) | 5000 (5秒) |
{
"rateLimit": {
"enabled": false
}
}
{
"rateLimit": {
"enabled": true,
"interval": 10000
}
}
{
"rateLimit": {
"enabled": true,
"interval": 2000
}
}
客户端请求设备 D001
↓
检查 D001 的最后请求时间
↓
是否超过 5 秒?
├─ 是 → 允许请求,返回 200/404
└─ 否 → 拒绝请求,返回 429 (Too Many Requests)
GET /api/device/data?deviceNumber=D001
成功响应 (HTTP 200/404):json { "code": 0, "message": "success", "data": { "deviceNumber": "D001", "data": {...} } }
被限流响应 (HTTP 429):json { "code": 429, "message": "请求过于频繁,请在 3245ms 后再试", "timestamp": "2025-12-05T10:30:45.234Z" }
GET /api/ratelimit/stats
响应示例:json { "code": 0, "message": "success", "data": { "enabled": true, "interval": 5000, "totalIdentifiers": 3, "records": { "D001": { "lastRequestTime": 1734000645123, "count": 5 }, "D002": { "lastRequestTime": 1734000640000, "count": 3 } } }, "timestamp": "2025-12-05T10:30:45.234Z" }
| 字段 | 说明 |
|---|---|
enabled |
限流是否启用 |
interval |
限流间隔(毫秒) |
totalIdentifiers |
记录的设备总数 |
records |
详细的限流记录 |
POST /api/ratelimit/clear
响应示例:json { "code": 0, "message": "success", "data": { "message": "限流记录已清空" }, "timestamp": "2025-12-05T10:30:45.234Z" }
node rateLimitTest.js
这会执行 9 个测试用例,验证:
1. ✅ 获取限流配置
2. ✅ 清空限流记录
3. ✅ 首次请求成功
4. ✅ 立即重复请求被限流
5. ✅ 部分间隔后仍被限流
6. ✅ 完整间隔后请求成功
7. ✅ 不同设备限流独立
8. ✅ 获取限流统计
9. ✅ 清空后可立即请求
node rateLimitTest.js stress
这会快速发送 100 个请求到同一设备,展示限流效果。
🚀 开始测试限流功能...
============================================================
【测试 1】获取限流配置信息
✅ API 文档获取成功
限流启用: true
限流间隔: 5000ms
【测试 3】首次请求设备 D001(应该成功)
状态: 404
✅ 首次请求成功 (设备不存在返回 404,但不受限流限制)
【测试 4】立即再次请求设备 D001(应该被限流)
状态: 429
✅ 请求被限流 (HTTP 429)
错误信息: 请求过于频繁,请在 4987ms 后再试
【测试 6】再等待 3 秒后请求设备 D001(总计 6s,应该成功)
状态: 404
✅ 限流已解除,请求成功
============================================================
✅ 限流功能测试完成
const http = require('http');
function queryDevice(deviceNumber, retryInterval = 5000) {
return new Promise((resolve, reject) => {
const url = new URL(`http://localhost:8080/api/device/data?deviceNumber=${deviceNumber}`);
const req = http.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
const result = JSON.parse(data);
if (res.statusCode === 429) {
// 被限流,解析剩余等待时间
const match = result.message.match(/(\d+)ms/);
const remainingTime = match ? parseInt(match[1]) : retryInterval;
console.log(`限流中,${remainingTime}ms 后重试...`);
setTimeout(() => {
queryDevice(deviceNumber, retryInterval)
.then(resolve)
.catch(reject);
}, remainingTime);
} else {
resolve(result);
}
});
});
req.on('error', reject);
});
}
// 使用示例
queryDevice('D001')
.then(result => console.log('设备数据:', result.data))
.catch(err => console.error('查询失败:', err));
import requests
import time
def query_device(device_number, retry_limit=3):
"""查询设备数据,自动处理限流重试"""
url = 'http://localhost:8080/api/device/data'
params = {'deviceNumber': device_number}
retry_count = 0
while retry_count < retry_limit:
try:
response = requests.get(url, params=params)
if response.status_code == 429:
# 被限流,解析等待时间
message = response.json().get('message', '')
import re
match = re.search(r'(\d+)ms', message)
wait_time = int(match.group(1)) / 1000 if match else 5
print(f'限流中,{wait_time}s 后重试...')
time.sleep(wait_time)
retry_count += 1
continue
# 成功响应
data = response.json()
return data.get('data', {}).get('data', {})
except Exception as e:
print(f'请求失败: {e}')
return None
print('重试次数已用尽')
return None
# 使用示例
device_data = query_device('D001')
if device_data:
print('设备数据:', device_data)
else:
print('获取设备数据失败')
# 基础请求
curl "http://localhost:8080/api/device/data?deviceNumber=D001"
# 快速重复请求(会被限流)
for i in {1..5}; do
curl "http://localhost:8080/api/device/data?deviceNumber=D001"
echo "请求 $i 完成"
done
# 查看限流状态
curl "http://localhost:8080/api/ratelimit/stats"
# 清空限流记录
curl -X POST "http://localhost:8080/api/ratelimit/clear"
{
"rateLimit": {
"interval": 10000 // 生产环境建议 10 秒
}
}
定期检查限流统计,了解请求模式:
# 每 10 秒检查一次限流状态
while true; do
curl "http://localhost:8080/api/ratelimit/stats" | jq .
sleep 10
done
应用端应实现指数退避重试:
async function queryWithRetry(deviceNumber, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(
`http://localhost:8080/api/device/data?deviceNumber=${deviceNumber}`
);
if (response.status === 429) {
const data = await response.json();
const message = data.message;
const match = message.match(/(\d+)ms/);
const waitTime = match ? parseInt(match[1]) : 5000;
// 等待后重试
await new Promise(r => setTimeout(r, waitTime));
continue;
}
return await response.json();
} catch (err) {
console.error(`重试 ${i + 1} 失败:`, err);
}
}
throw new Error('所有重试都失败了');
}
查看服务器日志中的限流信息:
# 查看限流日志
tail -f logs/combined.log | grep "限流\|429"
| 指标 | 值 |
|---|---|
| 检查时间 | < 1ms |
| 内存占用 | ~1KB/设备 |
| CPU 开销 | 可忽略 |
A: 在 httpConfig.json 中设置:json { "rateLimit": { "enabled": false } }
A: 不是。目前只对 /api/device/data 接口限流。其他接口(如 /api/device/all、/api/device/list 等)不受限流限制。
A: 当前版本不支持。建议在应用端实现设备级的限流逻辑。
A: 是的。限流记录存储在内存中,服务器重启后会清空。
A: 调用 /api/ratelimit/stats 接口查看,或查看程序日志中的⏱️记录。
A: 无关。限流只针对 HTTP API 接口,MQTT 和阿里云上传不受影响。
排查步骤:
1. 检查 httpConfig.json 中的 rateLimit.interval 配置
2. 调用 /api/ratelimit/clear 清空限流记录
3. 检查系统时钟是否正确
排查步骤:
1. 确认 httpConfig.json 中 rateLimit.enabled 为 true
2. 调用 /api/ratelimit/stats 确认限流已启用
3. 检查是否在 5 秒内进行了重复请求
排查步骤:
1. 查看服务器日志
2. 检查是否有其他网络问题
3. 尝试清空限流记录后重试
文档版本: 1.0
更新时间: 2025年12月
支持的 API 版本: 1.0+