feat: add http rate limiting

This commit is contained in:
2026-01-17 09:47:49 +08:00
parent 4f2b8ea3ad
commit c399a65d83
4 changed files with 68 additions and 16 deletions

View File

@@ -38,6 +38,13 @@ Port = 8080 # HTTP 监听端口
# AllowMethods = "GET,POST,PUT,DELETE"
# ExposeHeaders = "X-Request-Id"
# AllowCredentials = true
[Http.RateLimit]
# Enabled = true
# Max = 120 # 每窗口最大请求数
# WindowSeconds = 60 # 窗口大小(秒)
# Message = "Too Many Requests"
# SkipPaths = ["/healthz", "/readyz"]
# =========================
# Connection Multiplexer (providers/cmux)
# 用于同端口同时暴露 HTTP + gRPCcmux -> 分发到 Http/Grpc

View File

@@ -15,6 +15,7 @@ type Config struct {
BaseURI *string
Tls *Tls
Cors *Cors
RateLimit *RateLimit
}
type Tls struct {
@@ -27,6 +28,14 @@ type Cors struct {
Whitelist []Whitelist
}
type RateLimit struct {
Enabled bool
Max int
WindowSeconds int
Message string
SkipPaths []string
}
type Whitelist struct {
AllowOrigin string
AllowHeaders string

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"net"
"runtime/debug"
"strings"
"time"
log "github.com/sirupsen/logrus"
@@ -16,10 +17,13 @@ import (
"github.com/gofiber/fiber/v3/middleware/compress"
"github.com/gofiber/fiber/v3/middleware/cors"
"github.com/gofiber/fiber/v3/middleware/helmet"
"github.com/gofiber/fiber/v3/middleware/limiter"
"github.com/gofiber/fiber/v3/middleware/logger"
"github.com/gofiber/fiber/v3/middleware/recover"
"github.com/gofiber/fiber/v3/middleware/requestid"
"github.com/samber/lo"
"quyun/v2/app/errorx"
)
func DefaultProvider() container.ProviderContainer {
@@ -137,8 +141,51 @@ func Provide(opts ...opt.Option) error {
TimeZone: "Asia/Shanghai",
}))
// rate limit (enable standard headers; adjust Max via config if needed)
// engine.Use(limiter.New(limiter.Config{Max: 0}))
// rate limit (by tenant code or IP)
if config.RateLimit != nil && config.RateLimit.Enabled {
max := config.RateLimit.Max
if max <= 0 {
max = 120
}
windowSeconds := config.RateLimit.WindowSeconds
if windowSeconds <= 0 {
windowSeconds = 60
}
message := strings.TrimSpace(config.RateLimit.Message)
skipPrefixes := append([]string{"/healthz", "/readyz"}, config.RateLimit.SkipPaths...)
engine.Use(limiter.New(limiter.Config{
Max: max,
Expiration: time.Duration(windowSeconds) * time.Second,
LimitReached: func(c fiber.Ctx) error {
appErr := errorx.ErrRateLimitExceeded
if message != "" {
appErr = appErr.WithMsg(message)
}
return errorx.SendError(c, appErr)
},
Next: func(c fiber.Ctx) bool {
path := c.Path()
for _, prefix := range skipPrefixes {
if prefix == "" {
continue
}
if strings.HasPrefix(path, prefix) {
return true
}
}
return false
},
KeyGenerator: func(c fiber.Ctx) string {
if strings.HasPrefix(c.Path(), "/t/") {
if tenantCode := strings.TrimSpace(c.Params("tenantCode")); tenantCode != "" {
return "tenant:" + tenantCode
}
}
return c.IP()
},
}))
}
// static files (Fiber v3 Static helper moved; enable via filesystem middleware later)
// if config.StaticRoute != nil && config.StaticPath != nil { ... }

View File

@@ -6,7 +6,7 @@
- 认证仅使用 JWT不做 OAuth/Cookie 方案)。
- 支付集成暂不做,订单/退款仅按既有数据结构做流程与统计。
- 存储仅使用本地 FS 模拟,真实 Provider 延后统一接入。
- 多租户路由强隔离(`/t/:tenantCode/v1` + TenantResolver暂缓,后续统一优化。
- 多租户路由强隔离(`/t/:tenantCode/v1` + TenantResolver已启用,后续仅做细节优化。
## 统一原则
- 所有后端改动遵循 `backend/llm.txt` 规范与 GORM-Gen 访问方式。
@@ -157,18 +157,7 @@
## P3延后
### 10) 多租户强隔离(路由 + TenantResolver
**需求目标**
- `/t/:tenantCode/v1` 作为唯一入口,服务层强制 tenant_id 过滤。
**技术方案(后端/前端)**
- 中间件解析 tenant_code → tenant_id 并注入 ctx locals。
- 前端 Router/API base 从 URL 解析 tenantCode。
**测试方案**
- 跨租户访问被拒绝;错租户路由返回 404。
### 11) 真实存储 Provider 接入
### 10) 真实存储 Provider 接入
**需求目标**
- 接入 OSS/云存储,统一上传/访问路径策略。
@@ -178,7 +167,7 @@
**测试方案**
- 本地 FS + 真实 Provider 两套配置可用性。
### 12) 支付集成
### 11) 支付集成
**需求目标**
- 最终阶段对接真实支付。