标签: Go | 中间件 | chi | 多租户 | SaaS
前言
多租户SaaS的请求处理比单租户复杂得多:JWT认证、RBAC权限、租户隔离、配额检查、审计日志……
龙讯POS V2用14个中间件组成请求处理流水线,本文拆解每个中间件的实现。
一、中间件流水线
Request
→ RealIP 1. 获取真实IP(反向代理后)
→ Recoverer 2. Panic恢复(防崩溃)
→ Logger 3. 请求日志(含租户/用户信息)
→ Metrics 4. Prometheus指标
→ CORS 5. 跨域处理
→ SecurityHeaders 6. 安全头(X-Frame-Options等)
→ MaxBodySize 7. 请求体大小限制(2MB)
→ RateLimit 8. 全局限流(30 req/s)
→ JWTAuth 9. JWT认证(RS256验证)
→ RBAC 10. 角色权限检查
→ TenantMiddleware 11. 租户上下文注入
→ AuditMiddleware 12. 审计日志记录
→ QuotaCheck 13. 配额检查(门店/菜品/订单)
→ TenantRateLimit 14. 租户级限流(200 req/min)
→ Handler 业务处理
二、JWT认证中间件
func JWTAuth(rdb *redis.Client, publicKey *rsa.PublicKey) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
writeUnauthorized(w, "未提供认证令牌")
return
}
tokenStr := authHeader[7:]
claims, err := parseToken(tokenStr, publicKey)
if err != nil {
writeUnauthorized(w, "认证令牌无效")
return
}
// 检查Token黑名单(注销后加入)
blacklistKey := fmt.Sprintf("token:blacklist:%s", claims.ID)
exists, _ := rdb.Exists(r.Context(), blacklistKey).Result()
if exists > 0 {
writeUnauthorized(w, "认证令牌已失效")
return
}
// 注入上下文
ctx := context.WithValue(r.Context(), claimsKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
三、RBAC权限中间件
func RequireRole(roles ...string) func(http.Handler) http.Handler {
roleSet := make(map[string]bool)
for _, r := range roles {
roleSet[r] = true
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := GetClaims(r)
if roleSet[claims.Role] {
next.ServeHTTP(w, r)
return
}
writeForbidden(w, "权限不足")
})
}
}
// 4级角色继承
func RequirePlatformAdmin() func(http.Handler) http.Handler {
return RequireRole("platform_admin")
}
func RequireMerchantAdmin() func(http.Handler) http.Handler {
return RequireRole("platform_admin", "merchant_admin")
}
func RequireStoreAdmin() func(http.Handler) http.Handler {
return RequireRole("platform_admin", "merchant_admin", "store_admin")
}
func RequireStaff() func(http.Handler) http.Handler {
return RequireRole("platform_admin", "merchant_admin", "store_admin", "staff")
}
四、租户上下文中间件
func TenantMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := GetClaims(r)
// 检查商户状态
tenant := tenantRepo.FindByID(claims.TenantID)
if tenant == nil {
writeForbidden(w, "商户不存在")
return
}
if tenant.Status == "frozen" || tenant.Status == "closed" {
writeJSON(w, 403, model.Error(40101, "商户已冻结"))
return
}
// 注入租户ID和门店ID到上下文
ctx := context.WithValue(r.Context(), tenantIDKey, claims.TenantID)
ctx = context.WithValue(ctx, storeIDKey, claims.StoreID)
ctx = context.WithValue(ctx, userIDKey, claims.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
五、配额检查中间件
func QuotaCheck(db *gorm.DB, resource string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tid := GetTenantID(r)
var tenant model.Tenant
db.First(&tenant, tid)
switch resource {
case "store":
var count int64
db.Model(&model.Store{}).Where("tenant_id = ?", tid).Count(&count)
if count >= tenant.MaxStores {
writeJSON(w, 422, model.Error(40004, "门店数已达上限"))
return
}
case "dish":
var count int64
db.Model(&model.Dish{}).Where("tenant_id = ?", tid).Count(&count)
if count >= tenant.MaxDishes {
writeJSON(w, 422, model.Error(40004, "菜品数已达上限"))
return
}
case "order":
var count int64
today := time.Now().Format("2006-01-02")
db.Model(&model.Order{}).Where("tenant_id = ? AND DATE(created_at) = ?", tid, today).Count(&count)
if count >= tenant.MaxOrdersPerDay {
writeJSON(w, 422, model.Error(40004, "今日订单数已达上限"))
return
}
}
next.ServeHTTP(w, r)
})
}
}
六、审计日志中间件
func AuditMiddleware(auditSvc *AuditService) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 只记录写操作
if r.Method == "GET" || r.Method == "OPTIONS" {
next.ServeHTTP(w, r)
return
}
// 读取请求体
body, _ := io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewBuffer(body)) // 重新放回
// 脱敏
sanitized := sanitizeAuditData(string(body))
// 获取before_data(handler手动设置)
beforeData := r.Context().Value(auditBeforeDataKey)
// 异步记录审计日志
claims := GetClaims(r)
go auditSvc.Log(
context.Background(),
claims.TenantID,
claims.UserID,
r.Method,
r.URL.Path,
0, // recordID(handler层无法获取)
beforeData,
sanitized,
extractClientIP(r),
)
next.ServeHTTP(w, r)
})
}
}
七、限流中间件
// 全局限流
func RateLimit(rdb *redis.Client, limit int, window time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := extractClientIP(r)
key := fmt.Sprintf("ratelimit:global:%s", ip)
count, _ := rdb.Incr(r.Context(), key).Result()
if count == 1 {
rdb.Expire(r.Context(), key, window)
}
if count > int64(limit) {
writeJSON(w, 429, model.Error(429, "请求过于频繁"))
return
}
next.ServeHTTP(w, r)
})
}
}
// 租户级限流
func TenantRateLimit(rdb *redis.Client, limit int, window time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tid := GetTenantID(r)
key := fmt.Sprintf("ratelimit:tenant:%d", tid)
count, _ := rdb.Incr(r.Context(), key).Result()
if count == 1 {
rdb.Expire(r.Context(), key, window)
}
if count > int64(limit) {
writeJSON(w, 429, model.Error(42901, "租户请求过于频繁"))
return
}
next.ServeHTTP(w, r)
})
}
}
八、路由注册:中间件组合
// router.go — 中间件与路由的组合
// 公开路由(无限流、无认证)
r.Group(func(r chi.Router) {
r.Use(middleware.RateLimit(rdb, 5, time.Minute)) // 仅限流
r.Post("/api/v2/auth/login", authH.Login)
r.Post("/api/v2/tenants/register", tenantH.Register)
})
// 认证路由(JWT + RBAC)
r.Group(func(r chi.Router) {
r.Use(middleware.RateLimit(rdb, 30, time.Second))
r.Use(middleware.JWTAuth(rdb, publicKey))
r.Use(middleware.TenantMiddleware)
// store_admin+ 可访问
r.Group(func(r chi.Router) {
r.Use(middleware.RequireStoreAdmin())
r.Get("/api/v2/dishes", dishH.List)
r.Post("/api/v2/orders", orderH.Create)
})
// merchant_admin+ 可访问
r.Group(func(r chi.Router) {
r.Use(middleware.RequireMerchantAdmin())
r.Use(middleware.AuditMiddleware(auditSvc))
r.Post("/api/v2/stores", storeH.Create)
r.Use(middleware.QuotaCheck(db, "store"))
})
// platform_admin 可访问
r.Group(func(r chi.Router) {
r.Use(middleware.RequirePlatformAdmin())
r.Get("/api/v2/platform/tenants", platformH.ListTenants)
r.Get("/api/v2/platform/audit-logs", platformH.ListAuditLogs)
})
})
九、总结
| 中间件 | 职责 | 关键技术 |
|---|---|---|
| JWTAuth | 认证 | RS256 + Redis黑名单 |
| RBAC | 授权 | 4级角色继承 |
| TenantMiddleware | 租户上下文 | context注入+状态检查 |
| QuotaCheck | 配额 | 数据库计数+套餐限制 |
| AuditMiddleware | 审计 | 哈希链+脱敏+异步写入 |
| RateLimit | 限流 | Redis INCR+滑动窗口 |
| TenantRateLimit | 租户限流 | 租户级计数 |
| MustChangePwd | 强制改密 | JWT Claims检查 |
14个中间件,各司其职,组合成完整的请求处理流水线。