diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index b751427..9fd6380 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -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, diff --git a/crates/erp-server/src/middleware/rate_limit.rs b/crates/erp-server/src/middleware/rate_limit.rs index 1acb2b4..e9ed74f 100644 --- a/crates/erp-server/src/middleware/rate_limit.rs +++ b/crates/erp-server/src/middleware/rate_limit.rs @@ -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, + req: Request, + next: Next, +) -> Response { + let identifier = req + .extensions() + .get::() + .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 +}