package middleware import ( "errors" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/sony/gobreaker/v2" ) // CircuitBreaker 返回针对指定服务的熔断中间件。 // // 基于 gobreaker v2 实现: // - Interval=5s:CLOSED 状态下的统计窗口 // - ReadyToTrip:错误率 > 50% 触发 OPEN // - Timeout=30s:OPEN 持续 30 秒后转 HALF_OPEN // - MaxRequests=1:HALF_OPEN 仅允许 1 个探测请求 // // 仅当下游返回 5xx 视为失败;4xx 与 2xx 不计入熔断。 // 熔断打开或半开探测名额已满时返回 503 + JSON {"error":"circuit_open","retry_after":30}。 func CircuitBreaker(serviceName string) gin.HandlerFunc { cb := gobreaker.NewCircuitBreaker[struct{}](gobreaker.Settings{ Name: serviceName, MaxRequests: 1, Interval: 5 * time.Second, Timeout: 30 * time.Second, ReadyToTrip: func(counts gobreaker.Counts) bool { // 请求数为 0 时不触发,避免除零 if counts.Requests == 0 { return false } // 错误率 > 50% return counts.TotalFailures*2 > counts.Requests }, OnStateChange: func(name string, from, to gobreaker.State) { log.Printf("[circuit-breaker] service=%s state: %s -> %s", name, from, to) }, }) return func(c *gin.Context) { _, err := cb.Execute(func() (struct{}, error) { c.Next() // 下游 5xx 视为熔断失败 if c.Writer.Status() >= 500 { return struct{}{}, errors.New("downstream_error") } return struct{}{}, nil }) if err != nil { // 熔断打开或半开探测名额已满:返回 503 if errors.Is(err, gobreaker.ErrOpenState) || errors.Is(err, gobreaker.ErrTooManyRequests) { c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{ "error": "circuit_open", "retry_after": 30, }) return } // 其他情况(下游已写 5xx 响应):响应已写入,不覆盖 return } } }