feat(server): BLE 网关独立限流 — 每网关 60 req/60s

为 /health/gateway 路由添加 gateway_id 级别的速率限制,
网关认证(API Key)→ 限流检查 → handler 三层中间件。
Redis 不可达时同样遵循 fail_close 策略。
This commit is contained in:
iven
2026-05-11 10:24:22 +08:00
parent 0f67f1c21f
commit 533a2b6a8e
2 changed files with 35 additions and 0 deletions

View File

@@ -750,6 +750,10 @@ async fn main() -> anyhow::Result<()> {
.nest(
"/health/gateway",
erp_health::HealthModule::gateway_routes()
.layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::rate_limit::rate_limit_by_gateway,
))
.layer(axum::middleware::from_fn_with_state(
state.clone(),
erp_health::gateway_auth::gateway_auth_middleware,

View File

@@ -268,3 +268,34 @@ fn extract_client_ip(headers: &axum::http::HeaderMap) -> String {
})
.unwrap_or_else(|| "unknown".to_string())
}
/// BLE 网关级别的限流中间件。
///
/// 从 GatewayAuthContext 中读取 gateway_id 作为标识符。
/// 限制每个网关设备 60 req/60s。
/// 必须在 gateway_auth_middleware 之后挂载(需要认证上下文)。
pub async fn rate_limit_by_gateway(
State(state): State<AppState>,
req: Request<Body>,
next: Next,
) -> Response {
let identifier = req
.extensions()
.get::<erp_health::gateway_auth::GatewayAuthContext>()
.map(|ctx| ctx.gateway_id.clone())
.unwrap_or_else(|| "unknown_gateway".to_string());
let fail_close = state.config.rate_limit.fail_close;
apply_rate_limit(
RateLimitParams {
redis_client: &state.redis,
fail_close,
max_requests: 60,
window_secs: 60,
prefix: "gateway",
},
&identifier,
req,
next,
)
.await
}