Documentation
¶
Overview ¶
Package mw 提供 CloudWeGo Hertz 服务的自研标准中间件栈。
本包是模块 github.com/oekit/middleware 的根包,保持响应格式、错误处理、 可观测性的一致性。本包只含自研核心能力;hertz-contrib 社区库桥接 (JWT/Sessions/Swagger/CSRF/pprof/Lark 等)见 contrib 子包。
组件总览 ¶
- DefaultMiddleware — 一键标准栈(TraceID → Recovery → Response → AccessLog,零指标强制; StackConfig.DisableResponseWrap 响应自管形态、AccessLogOptions 透传落库 sink)
- MinimalMiddleware — 最小安全栈(TraceID → Recovery;零指标/零日志,供自定义栈下限)
- chainmw 子包 — transport 感知的框架无关中间件内核(github.com/oekit/middleware/chainmw; AccessLog/Recovery/MetadataPropagate——本包 Recovery/AccessLog 为其薄壳,kitex 桥接复用同一份实现)
- ResponseMiddleware — 统一 JSON 响应格式 {code, data, message, trace_id} (支持 WithSkipPaths / WithSkipPrefixes 声明式跳过 + SkipResponseWrap handler 级逃生舱)
- hmw.Middleware — trace_id 生成/透传(hmw 子包,路径 github.com/oekit/middleware/hmw)
- AccessLogMiddleware — 请求访问日志(method, path, status, latency, ip; WithAccessLogSink 同一份记录旁路落库/上报,sink 故障隔离不影响主链路; WithAccessLogSilent 只采不写 / WithAccessLogLogger 注入宿主 logger 避免双写)
- PrometheusMetrics — Prometheus HTTP 指标采集(请求数 / 延迟 / 并发 / 体积;显式拼装)
- RecoveryMiddleware — panic recovery(可选 SentryReporter 监控钩子)
- RateLimitMiddleware — 令牌桶限流(默认按 RemoteAddr 分键)
- SheddingMiddleware — CPU 自适应降载(opt-in,不加入 DefaultMiddleware 栈; Allow 拒绝 → 503/body code 50300,放行按响应状态码回填 Promise)
- BearerTokenAuth — Bearer Token 认证(Authorizer 鉴权钩子)
- I18n — Accept-Language 国际化(LangFromCtx / Translate)
- Fail / ErrorResp / SuccessResp — handler 层统一错误处理与响应写入
快速接入 ¶
在 main.go 中注册中间件(推荐用 DefaultMiddleware 一键注册,或手动按顺序):
h := server.Default(...)
for _, m := range mw.DefaultMiddleware(mw.StackConfig{SkipPaths: []string{"/healthz"}}) {
h.Use(m)
}
// 顺序: TraceID → Recovery → Response → AccessLog
适用边界:响应体自管的服务(#18) ¶
DefaultMiddleware 面向「响应契约统一、由中间件包装」的服务。如果你的响应体由 各 handler 自己构造(与既有 API 对齐、自定义字段名、null 字段整个不出现等), 不要让中间件接管响应——ResponseMiddleware 对顶层无 "code" 的裸 JSON 会包壳 (顶层已含 "code" 的原样放行),没必要押在这条启发式判定上:
// 一键:TraceID → Recovery → AccessLog,响应构造权完全留在 handler
h.Use(mw.DefaultMiddleware(mw.StackConfig{DisableResponseWrap: true})...)
// 手拼:最小安全栈 + 按需挂访问日志
h.Use(mw.MinimalMiddleware()...)
h.Use(mw.AccessLogMiddleware())
访问日志要落到自家 access_log 表时用 WithAccessLogSink(同一份记录旁路交给 DAO,c 可读鉴权中间件注入的 userId;sink 出错/panic 只记 Warn,不影响响应):
h.Use(mw.DefaultMiddleware(mw.StackConfig{
DisableResponseWrap: true,
AccessLogOptions: []mw.AccessLogOption{mw.WithAccessLogSink(
func(ctx context.Context, c *app.RequestContext, rec mw.AccessRecord) error {
uid, _ := c.Get("userId")
return accessLogDAO.Save(ctx, uid, rec)
})},
})...)
宿主自带 logger 输出同一份记录时,用 WithAccessLogSilent 关闭内建日志 (只采不写,采集行为不变)或用 WithAccessLogLogger 把日志改道到宿主 logger, 避免双写(#38):
h.Use(mw.AccessLogMiddleware(
mw.WithAccessLogSilent(),
mw.WithAccessLogRedactPathPrefixes("/api/v2/webhook/alert"),
mw.WithAccessLogSink(func(ctx context.Context, c *app.RequestContext, rec mw.AccessRecord) error {
return accessLogDAO.Save(ctx, rec)
}),
))
中间件可按路由组装配——Hertz 的 Use/Group 接受变参,栈切片直接展开,组内再叠 自家 JWT / ApiKey 鉴权:
api := h.Group("/api", mw.DefaultMiddleware(cfg)...)
user := api.Group("/user", auth.Required(svc))
任何形态 Recovery 都是硬性要求(Hertz 无默认 PanicHandler,缺失则 panic 直接断连); RecoveryMiddleware(WithRecoveryErrorHandler(...)) 可换成自己的 500 响应体。
需要 HTTP 指标时显式拼在栈最前(v0.2.0 起 Prometheus 从默认栈拆出):
metrics.Init("my-service")
pm := mw.NewPrometheusMetrics(mw.PrometheusConfig{
Namespace: "myapp",
SkipPaths: []string{"/metrics", "/ping"},
Registry: metrics.Registry,
})
h.Use(pm.Middleware())
r.GET("/metrics", hertz.MetricsHandler()) // 暴露 /metrics 端点
CORS 与安全响应头 ¶
CORS 与安全响应头直接使用 hertz-contrib 社区实现(功能更完整、维护更活跃), ekit 不自研。推荐的严格 CSP 策略见 contrib.DefaultCSP()。
import (
"github.com/hertz-contrib/cors"
"github.com/hertz-contrib/secure"
contrib "github.com/oekit/middleware/contrib"
)
// CORS(如需前端直连)
h.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET","POST","PUT","DELETE","OPTIONS","PATCH"},
AllowHeaders: []string{"Origin","Content-Type","Authorization","X-Trace-Id","X-Request-Id"},
}))
// 安全响应头(CSP / 防点击劫持 / nosniff / HSTS)
h.Use(secure.New(
secure.WithContentSecurityPolicy(contrib.DefaultCSP()),
secure.WithFrameDeny(true),
secure.WithContentTypeNosniff(true),
secure.WithReferrerPolicy("no-referrer"),
))
如需请求体/响应体大小指标:
pm := mw.NewPrometheusMetrics(mw.PrometheusConfig{
Namespace: "myapp",
Registry: metrics.Registry,
EnableSizeMetrics: true, // 开启 request_size_bytes / response_size_bytes
})
如需在指标中添加 service 标签(多服务共享同一 Prometheus 时):
pm := mw.NewPrometheusMetrics(mw.PrometheusConfig{
Namespace: "myapp",
ServiceName: "wechat", // 所有指标自动添加 service="wechat" 标签
Registry: metrics.Registry,
})
提供的指标 ¶
- <namespace>_requests_total{method, path, status_code} — 请求计数
- <namespace>_request_duration_seconds{method, path} — 请求延迟(秒)
- <namespace>_requests_in_flight{method, path} — 当前并发请求数
- <namespace>_request_size_bytes{method, path} — 请求体大小(需 EnableSizeMetrics)
- <namespace>_response_size_bytes{method, path} — 响应体大小(需 EnableSizeMetrics)
路径标签使用路由模板(如 /api/user/:id),未匹配路由使用 "unnamed",避免高基数标签爆炸。
在 handler 中写入响应:
mw.SuccessResp(c, userData) mw.ErrorResp(c, 400, "参数错误") mw.SuccessRespMsg(c, nil, "创建成功")
在 service 层返回业务错误:
return nil, errors.NewNotFoundError("项目不存在")
return nil, errors.NewBizError(1001, "余额不足")
响应格式约定 ¶
所有 API 返回统一的 JSON 结构:
{
"code": 0, // 0=成功, 非0=业务错误码(400/404/500 等)
"data": ..., // 业务数据,失败时为 null
"message": "success",
"trace_id": "a1b2c3d4e5f6" // 链路追踪 ID,用于日志关联
}
ResponseMiddleware 会自动拦截 handler 返回的裸 JSON(不含 code 字段), 包装成上述格式。如果 handler 已通过 SuccessResp/ErrorResp 写入了带 code 的响应, 中间件不会重复包装。
Index ¶
- func AccessLogMiddleware(opts ...AccessLogOption) app.HandlerFunc
- func BearerTokenAuth(opts ...BearerTokenOption) app.HandlerFunc
- func DefaultMiddleware(cfg StackConfig) []app.HandlerFunc
- func ErrorResp(c *app.RequestContext, code int, message string)
- func Fail(ctx context.Context, c *app.RequestContext, err error, opts ...FailOption)
- func FailWithData(ctx context.Context, c *app.RequestContext, err error, data any, ...)
- func I18n(opts ...I18nOption) app.HandlerFunc
- func LangFromCtx(ctx context.Context) string
- func MinimalMiddleware() []app.HandlerFunc
- func RateLimitMiddleware(opts ...RateLimitOption) app.HandlerFunc
- func RateLimitMiddlewareWithLimiter(keyed *ratelimit.KeyedLimiter, opts ...RateLimitOption) app.HandlerFunc
- func RecoveryMiddleware(opts ...RecoveryOption) app.HandlerFunc
- func ResponseMiddleware(opts ...ResponseOption) app.HandlerFunc
- func SetStatusMapper(m StatusMapper)
- func SheddingMiddleware(opts ...SheddingOption) app.HandlerFunc
- func SkipResponseWrap(c *app.RequestContext)
- func SuccessResp(c *app.RequestContext, data any)
- func SuccessRespMsg(c *app.RequestContext, data any, message string)
- func Translate(ctx context.Context, key string) string
- type AccessLogOption
- type AccessLogOptions
- type AccessRecord
- type AccessSink
- type Authorizer
- type BearerTokenOption
- func WithAuthAuthorizer(a Authorizer) BearerTokenOption
- func WithAuthSkipFunc(fn func(path string) bool) BearerTokenOption
- func WithAuthSkipPaths(paths ...string) BearerTokenOption
- func WithAuthSubjectExtractor(fn SubjectExtractor) BearerTokenOption
- func WithBearerToken(token string) BearerTokenOption
- type BearerTokenOptions
- type BundleLoader
- type Catalog
- type FailOption
- type I18nOption
- type I18nOptions
- type NotFoundErrChecker
- type PrometheusConfig
- type PrometheusMetrics
- type RateLimitOption
- type RateLimitOptions
- type RecoveryOption
- type RecoveryOptions
- type Response
- type ResponseOption
- type SentryReporter
- type SheddingOption
- type SheddingOptions
- type StackConfig
- type StatusMapper
- type Subject
- type SubjectExtractor
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AccessLogMiddleware ¶
func AccessLogMiddleware(opts ...AccessLogOption) app.HandlerFunc
AccessLogMiddleware 记录每个 HTTP 请求的访问日志。
特性:
- 使用 scoped logger 输出结构化日志,自动携带 trace_id
- 可通过 WithAccessLogSkipPaths 跳过健康检查等高频路径(支持 glob 模式)
- 可通过 WithAccessLogRedactPathPrefixes 对「URL 路径即凭证」的路由 (webhook/{token} 类)做路径脱敏,token 不落日志
- 可通过 WithAccessLogSink 把同一份记录(AccessRecord)旁路交给落库/上报 消费者;sink 出错或 panic 只记 Warn,不影响主链路(#18)
- panic 请求同样产出记录(#20):恢复点记 Warn 日志(status 按 Recovery 缺省 500)并喂 sink(AccessRecord.Panic 标记)后按原值重抛,外层 Recovery 照常处理——落库场景 panic 请求同样留痕
- 可通过 WithAccessLogSilent 关闭内建日志输出(只采不写,采集行为不变) 或通过 WithAccessLogLogger 注入宿主 logger(避免宿主再写一份造成双写) (#38)
- 无参调用即启用默认配置(不跳过任何路径、不脱敏、不落库)
实现说明(#14) ¶
本中间件是 chainmw.AccessLog 内核的 Hertz 薄壳:SkipPaths 匹配、路径 脱敏、耗时采集、sink 隔离执行收敛在内核单点维护(kitex 侧经 ToKitex 桥接 同步获得同等能力),本层只注入 hertz→transport 服务端上下文并保持既有日志 格式(logger、消息、字段与顺序不变)。下游 handler 可经 transport.FromServerContext 读取协议无关传输信息。
使用方式:
// 默认配置(不跳过任何路径)
h.Use(mw.AccessLogMiddleware())
// 跳过健康检查和指标端点(支持 glob 模式)
h.Use(mw.AccessLogMiddleware(mw.WithAccessLogSkipPaths("/metrics", "/ping", "/health", "/internal/*")))
// webhook 凭证路径脱敏
h.Use(mw.AccessLogMiddleware(mw.WithAccessLogRedactPathPrefixes("/api/v2/webhook/alert", "/api/v2/task/trigger")))
// 同一份记录落到自家 access_log 表
h.Use(mw.AccessLogMiddleware(mw.WithAccessLogSink(func(ctx context.Context, c *app.RequestContext, rec mw.AccessRecord) error {
return dao.SaveAccessLog(ctx, rec)
})))
// 宿主自带 logger 输出同一份记录:静默关闭内建日志,只采不写(#38)
h.Use(mw.AccessLogMiddleware(
mw.WithAccessLogSilent(),
mw.WithAccessLogRedactPathPrefixes("/api/v2/webhook/alert"),
mw.WithAccessLogSink(func(ctx context.Context, c *app.RequestContext, rec mw.AccessRecord) error {
return dao.SaveAccessLog(ctx, rec)
}),
))
// 或把内建日志改道到宿主的 logger(宿主不再自己写一份)
h.Use(mw.AccessLogMiddleware(mw.WithAccessLogLogger(hostLogger)))
func BearerTokenAuth ¶
func BearerTokenAuth(opts ...BearerTokenOption) app.HandlerFunc
BearerTokenAuth 返回一个 Bearer Token 认证中间件,可选配鉴权钩子。
校验流程:
- Token 为空 → 直接放行(认证关闭)
- 路径在 SkipPaths 或命中 SkipFunc → 放行
- 请求头 Authorization: Bearer <token> 与配置 token 做常量时间比较 - 不匹配 → 401
- Authorizer 不为 nil → 提取 Subject 调 Authorize - 拒绝 → 403 - 错误 → 500
- 全部通过 → c.Next(ctx)
使用方式:
// 纯 BearerToken(无鉴权)
h.Use(mw.BearerTokenAuth(mw.WithBearerToken(token)))
// BearerToken + casbin 鉴权
h.Use(mw.BearerTokenAuth(
mw.WithBearerToken(token),
mw.WithAuthAuthorizer(&casbinAuth{...}),
mw.WithAuthSubjectExtractor(func(c) mw.Subject {
return mw.Subject{UserID: jwtClaims["user_id"].(string), ...}
}),
))
func DefaultMiddleware ¶
func DefaultMiddleware(cfg StackConfig) []app.HandlerFunc
DefaultMiddleware 返回标准化中间件栈:TraceID → Recovery → Response → AccessLog。
适用边界(#18) ¶
本栈面向「响应契约统一、由中间件包装」的服务:handler 用 SuccessResp/ErrorResp 或返回裸 JSON,ResponseMiddleware 统一成 {code, data, message, trace_id}。 如果你的响应体由各 handler 自己构造(与既有 API 对齐、自定义字段名、 null 字段整个不出现等),不要让中间件接管响应——两条路:
- 一键:DefaultMiddleware(StackConfig{DisableResponseWrap: true}),得到 TraceID → Recovery → AccessLog,响应构造权完全留在 handler;
- 手拼:MinimalMiddleware()(TraceID → Recovery)再按需 h.Use(AccessLogMiddleware(...))。
中间件可按路由组装配——Hertz 的 Use/Group 都接受变参,栈切片直接展开:
h.Use(mw.DefaultMiddleware(cfg)...) // 全局
api := h.Group("/api", mw.DefaultMiddleware(cfg)...) // 仅 /api 子树
admin := api.Group("/admin", auth.Required(), auth.ApiKey()) // 组内再叠自家鉴权
无论哪种形态 Recovery 都是硬性要求:Hertz 无默认 PanicHandler,缺失则 panic 直接断连。
指标 ¶
零指标强制(v0.2.0 起 Prometheus 中间件从默认栈拆出)——需要 HTTP 指标时 用 NewPrometheusMetrics 显式拼装在栈最前(精确记录请求耗时):
pm := mw.NewPrometheusMetrics(mw.PrometheusConfig{
Namespace: "fuxi",
ServiceName: "fuxi_engine",
SkipPaths: []string{"/metrics", "/healthz"},
Registry: emetrics.Registry, // 缺省 nil = ekit 统一注册表
})
h.Use(pm.Middleware())
for _, m := range mw.DefaultMiddleware(mw.StackConfig{
SkipPaths: []string{"/metrics", "/healthz"},
}) {
h.Use(m)
}
顺序说明:
- TraceID 生成 trace_id 并注入 context(供后续所有中间件与 handler 使用)
- Recovery 捕获 handler panic(引擎无默认 PanicHandler,缺失则 panic 直接断连)
- Response 包装响应格式(从 context 读取 trace_id 写入响应体;DisableResponseWrap 时不挂)
- AccessLog 记录请求日志(含 trace_id;AccessLogOptions 可追加落库 sink / 脱敏)
只要安全下限、不需要统一包壳/访问日志时用 MinimalMiddleware。
指标一步形态(middleware#16):StackConfig.Prometheus 非 nil 时 Prometheus 中间件保证挂栈首(精确耗时),SkipPaths 单点传入;nil = 维持零指标。 旧两步写法(NewPrometheusMetrics + 手动 h.Use 在前)保留可用。
func ErrorResp ¶
func ErrorResp(c *app.RequestContext, code int, message string)
ErrorResp 写入错误响应并中断后续处理。
响应格式: {"code": <code>, "data": null, "message": "<message>", "trace_id": "..."} HTTP 状态码由 StatusMapper 从业务码推导(默认:HTTP 对齐码直接复用, 自定义码归为 400)。这样网关/监控能基于 HTTP 状态码正确识别错误率。 调用后会自动 c.Abort(),后续 handler 不再执行。
func Fail ¶
func Fail(ctx context.Context, c *app.RequestContext, err error, opts ...FailOption)
Fail 是 handler 统一错误处理, 跟 fuxi 7 service 'fail()' 同 3-tier 模式:
- err 是 *ekiterrors.BizError -> writeBizError(c, bizErr, nil) (BizCode 入 body, HTTPCode 决定 HTTP 状态)
- err 匹配 NotFoundErrChecker -> ErrorResp(c, 404, "resource not found")
- err 包装 registry.ErrNoInstances -> ErrorResp(c, 503, "service unavailable") (依赖服务无可用实例是依赖不可用而非本服务 bug,Warn 级日志避免误告警)
- 其他 err -> failLog.CtxError(ctx, "internal error", "err", err) + ErrorResp(c, 500, "internal server error")
500 fallback 路径必 log (unexpected server error), BizError / not-found / 503 走 4xx/5xx 语义码是 expected 状态, 不打 error noise.
ctx 为 Hertz handler 的 context.Context,500 路径通过 CtxError 自动携带 trace_id。 *app.RequestContext 不实现 context.Context,因此 ctx 需显式传入。
用法:
if err := service.X(); err != nil {
middleware.Fail(ctx, c, err)
return
}
func FailWithData ¶
func FailWithData(ctx context.Context, c *app.RequestContext, err error, data any, opts ...FailOption)
FailWithData 与 Fail 同 3-tier 语义,BizError 路径的响应体额外携带 data: 202 业务态(如激活状态机的 VERIFYING——请求已受理、结果未知)需要按统一封套 返回 BizCode + HTTPCode + message + trace_id,并附会话数据供调用方继续轮询。 非 BizError 路径(not-found / 503 / 500 fallback)与 Fail 完全一致——data 不透传, 内部错误细节不对外暴露。其余约定见 Fail。
用法:
res, bizErr := service.X(ctx)
if bizErr != nil {
middleware.FailWithData(ctx, c, bizErr, toView(res))
return
}
func I18n ¶
func I18n(opts ...I18nOption) app.HandlerFunc
I18n 返回 hertz 中间件,从 Accept-Language header 选语言放进 ctx, handler 通过 LangFromCtx(c) 或 Translate(c, key) 取翻译。
流程:
- catalog 为空且配置了 BundleLoader → lazy load 一次(同步,失败记 Warn 后续请求重试)
- 读 Accept-Language header(支持 q-value,q=0 视为不可接受; 语言 tag 大小写不敏感)
- 匹配 Catalog 里已注册的语言:精确 → 连字符边界前缀(en 匹配 en-US, 不匹配 encyclopedia;多个候选取字典序最小,结果确定)
- fallback 到 DefaultLang
- 语言 + 本实例 catalog 放 ctx
业务使用:
h.Use(mw.I18n(
mw.WithI18nCatalog(mw.Catalog{
"en": {"welcome": "Welcome"},
"zh": {"welcome": "欢迎"},
}),
mw.WithI18nDefaultLang("en"),
))
func handler(c context.Context, ctx *app.RequestContext) {
lang := mw.LangFromCtx(ctx) // "en" / "zh" / ...
msg := mw.Translate(ctx, "welcome") // "Welcome" / "欢迎"
}
官方依据:https://www.cloudwego.io/zh/docs/hertz/tutorials/third-party/middleware/i18n/
func LangFromCtx ¶
LangFromCtx 从 ctx 读 i18n 语言(由 I18n 中间件设置);ctx 缺失时 fallback 到 i18n default(I18n() 同步注入 ctx),再 fallback 全局 const "en"。 业务使用:
lang := mw.LangFromCtx(ctx)
func MinimalMiddleware ¶
func MinimalMiddleware() []app.HandlerFunc
MinimalMiddleware 返回最小安全中间件栈:TraceID → Recovery。
为不需要 DefaultMiddleware 全家桶(统一响应包壳 / 访问日志)的消费者 提供安全下限:trace_id 注入 + panic 恢复,零指标、零日志中间件。
Recovery 是硬性要求——Hertz 无默认 PanicHandler(engine.PanicHandler nil 时 ServeHTTP 不挂 recover),netpoll 连接层同样不 recover, 缺失 Recovery 的 handler panic 会断连甚至带崩进程。任何自定义中间件 栈(包括自行拼装的)都必须包含 RecoveryMiddleware 或直接使用本栈。
使用方式:
for _, m := range mw.MinimalMiddleware() {
h.Use(m)
}
func RateLimitMiddleware ¶
func RateLimitMiddleware(opts ...RateLimitOption) app.HandlerFunc
RateLimitMiddleware 返回一个基于令牌桶的限流中间件。
每个 key 维护独立的 rate.Limiter,超限返回 429 Too Many Requests。 使用 golang.org/x/time/rate(Go 标准库扩展)。 Rate/Burst 必须通过 WithRateLimit 设置(否则所有请求都会被 429), 未设置时在构造阶段直接 panic。
生命周期说明:内部 KeyedLimiter 的过期清理 goroutine 随进程生命周期 存活 —— Hertz 中间件没有 Close 钩子,本函数无法自动停止它。每个中间件 实例只常驻一个清理 goroutine(每 LimiterTTL/2 tick 一次,空闲开销极小); 需要显式生命周期控制(如优雅退出时统一 Close)的调用方,请使用 RateLimitMiddlewareWithLimiter 自建并持有 KeyedLimiter。
使用方式:
h.Use(mw.RateLimitMiddleware(
mw.WithRateLimit(100, 200), // 每秒 100 个请求,突发允许 200
))
func RateLimitMiddlewareWithLimiter ¶
func RateLimitMiddlewareWithLimiter(keyed *ratelimit.KeyedLimiter, opts ...RateLimitOption) app.HandlerFunc
RateLimitMiddlewareWithLimiter 与 RateLimitMiddleware 相同,但接受调用方 自建的 KeyedLimiter:调用方持有其引用并负责在适当时机(如优雅退出) 调用 Close() 停止清理 goroutine,实现显式的生命周期管理。
Rate/Burst 已内建于 keyed 的工厂函数,options 仅用于配置 KeyFunc 等 请求侧行为(默认按 client IP 限流)。keyed 为 nil 时 panic。
使用方式:
keyed := ratelimit.NewKeyedLimiter(
func() ratelimit.Limiter { return ratelimit.NewTokenBucket(100, 200) },
10*time.Minute,
)
h.Use(mw.RateLimitMiddlewareWithLimiter(keyed))
// 优雅退出时:keyed.Close()
func RecoveryMiddleware ¶
func RecoveryMiddleware(opts ...RecoveryOption) app.HandlerFunc
RecoveryMiddleware 返回一个 panic recovery 中间件。
捕获 handler 中的 panic,记录堆栈日志,调用 PanicReporter(sentry 等), 返回 500 错误响应。
为什么必须有 RecoveryMiddleware ¶
Hertz **没有默认 PanicHandler**:engine.PanicHandler 为 nil 时 ServeHTTP 不会挂 defer recover(hertz pkg/route/engine.go: `if engine.PanicHandler != nil { defer engine.recv(ctx) }`, server.New 不设默认值),netpoll 连接层 onProcess/onRequest 也 不做 recover(cloudwego/netpoll connection_onevent.go)——handler panic 直接冲垮连接(客户端 connection reset,无 500/无统一错误体/ 无 access log),最坏情况带崩整个进程。因此**任何自定义 HTTP 中间件 栈都必须包含本中间件**(或 mw.MinimalMiddleware / mw.DefaultMiddleware, 均已内置)。见 AGENTS.md 硬规则。
实现说明(#14) ¶
本中间件是 chainmw.Recovery 内核的 Hertz 薄壳:panic 捕获/日志/上报 隔离收敛在内核单点维护(kitex 侧经 ToKitex 桥接同步获得同等能力), 本层只做 SentryReporter 签名适配(带 *app.RequestContext)与响应写出。 日志文案、字段与顺序、reporter panic 隔离语义与 v0.8.0 前实现一致。
使用方式:
// 默认配置(仅 logx 记录 + 500 响应)
h.Use(mw.RecoveryMiddleware())
// 接入 Sentry
h.Use(mw.RecoveryMiddleware(mw.WithPanicReporter(sentryAdapter)))
// 自定义错误处理
h.Use(mw.RecoveryMiddleware(mw.WithRecoveryErrorHandler(func(c *app.RequestContext, r any, stack string) {
mw.ErrorResp(c, 500, "panic occurred")
})))
func ResponseMiddleware ¶
func ResponseMiddleware(opts ...ResponseOption) app.HandlerFunc
ResponseMiddleware 是统一响应格式中间件。
功能:
- 从 RequestContext 中读取 trace_id(由 TraceIDMiddleware 注入),写入响应体
- 拦截 handler 返回的裸 JSON(不含 "code" 字段),自动包装为 {code, data, message, trace_id}
- 如果 handler 已通过 SuccessResp/ErrorResp 写入了带 "code" 的响应,不会重复包装
- 跳过非 JSON 响应、非 200 状态码、空 body
声明式跳过(与 SkipResponseWrap 并存):
- WithSkipPaths — 路径精确匹配,命中则整请求不包装
- WithSkipPrefixes — 路径前缀匹配(如 "/.well-known/"、"/mcp"),命中则整请求不包装
- SkipResponseWrap — handler 级逃生舱(写响应前调用),单响应跳过包装
注意: 本中间件不负责生成 trace_id。请确保已在其之前注册 hmw.Middleware()。
注册位置: 放在 hmw.Middleware() 之后、路由中间件链中,确保所有子路由都经过。
func SetStatusMapper ¶
func SetStatusMapper(m StatusMapper)
SetStatusMapper 设置全局业务码 → HTTP 状态码映射器。 应在服务初始化阶段(main 中、注册路由前)调用。
func SheddingMiddleware ¶
func SheddingMiddleware(opts ...SheddingOption) app.HandlerFunc
SheddingMiddleware 返回 CPU 自适应降载(load shedding)中间件,消费 concurrency/shedding 包的 Shedder 接口。
行为:
- Allow(ctx) 返回任何 error(ErrServiceOverloaded / ctx 已取消等) 一律按过载拒绝处理:BizError{BizCode: 50300, HTTPCode: 503} 经 Fail 写出 —— HTTP 503 + body code 50300(50300 与 Fail 的 ErrNoInstances 503 "service unavailable" 区分:50300 特指本服务 过载主动丢弃,而非依赖不可用),JSON 响应结构与现有错误路径一致。
- 放行 → c.Next(ctx) 后按响应状态码回填 Promise:>= 500 调 Fail() (4xx 是调用方错误,与服务容量无关,计 Pass),否则 Pass()。
- handler panic:defer 内 recover 后先 Fail() 再原样 re-panic —— 不吞 panic、panic 值原样透传,交由外层 RecoveryMiddleware 捕获 (Recovery 在外/在内两种注册顺序均能保证 Fail 被回填:后者经 500 状态码分类)。未注册 Recovery 时行为与未挂本中间件一致 (引擎无默认 PanicHandler,断连)。
**opt-in,不加入 DefaultMiddleware 栈**:会丢弃请求的中间件不能默认开。 需要时显式 h.Use(mw.SheddingMiddleware(...))。
生命周期说明:缺省降载器首次读到 CPU 采样会惰性启动进程级唯一的 后台采样 goroutine(随进程存活,无法被中间件停止;对齐上游 shedding 包设计)。非 linux 平台 CPU 采样恒错误 → 按不过载处理(fail-open), 降载自动禁用、全放行。需要丢弃诊断/监控时,经 shedding.WithShedderRejectHandler 构造降载器后用 WithShedder 注入 (本包不强制接 logx/metrics)。
使用方式:
h.Use(mw.SheddingMiddleware()) // 缺省 CPU 阈值 900
h.Use(mw.SheddingMiddleware(
mw.WithShedderCPUThreshold(800), // 80% 过载阈值
))
shed := shedding.NewAdaptiveShedder(shedding.WithShedderRejectHandler(func(reason string) {
logx.Warn("shedding drop", "reason", reason)
}))
h.Use(mw.SheddingMiddleware(mw.WithShedder(shed))) // 显式降载器(CPU 阈值选项失效)
func SkipResponseWrap ¶
func SkipResponseWrap(c *app.RequestContext)
SkipResponseWrap 标记本响应跳过统一业务包壳(RFC 标准载荷端点用: OIDC /token、/userinfo、discovery、JWKS 等,第三方按规范解析顶层字段)。 必须在写响应体之前调用。
标记同时写入 RequestContext kv(首选,外部不可见)与 X-Oekit-No-Wrap 兼容头;旧名 X-Zeus-No-Wrap 仍被读取一个版本后移除。 与 ResponseMiddleware 的 WithSkipPaths/WithSkipPrefixes 声明式跳过并存: 本函数是 handler 级逃生舱(单响应),声明式跳过是路由级(整请求)。
func SuccessResp ¶
func SuccessResp(c *app.RequestContext, data any)
SuccessResp 写入成功响应。
响应格式: {"code": 0, "data": <data>, "message": "success", "trace_id": "..."} 由于已经包含 "code" 字段,ResponseMiddleware 不会重复包装。
func SuccessRespMsg ¶
func SuccessRespMsg(c *app.RequestContext, data any, message string)
SuccessRespMsg 写入成功响应(自定义 message)。
用法: mw.SuccessRespMsg(c, nil, "创建成功")
Types ¶
type AccessLogOption ¶
type AccessLogOption func(*AccessLogOptions)
AccessLogOption 配置 AccessLogMiddleware。
func WithAccessLogLogger ¶ added in v0.13.0
func WithAccessLogLogger(l logx.Logger) AccessLogOption
WithAccessLogLogger 注入访问日志的输出 logger(#38)。
默认用内建 logx.Delegating("access");注入后访问记录仍由本中间件写出 (消息 "request" 与字段 method/path/status/latency_ms/ip 不变),只是落到 宿主的后端——宿主不必再写一份,避免双写。nil 被忽略(保持默认 logger)。
宿主用 zap 等非 logx 后端时,可自行实现 logx.Logger 接口做适配;若只是 想换后端而不改字段,用 logx.SetDefault 整体替换更省事。
func WithAccessLogRedactPathPrefixes ¶ added in v0.5.1
func WithAccessLogRedactPathPrefixes(prefixes ...string) AccessLogOption
WithAccessLogRedactPathPrefixes 设置需要脱敏的路径前缀列表。 命中前缀的路径在日志中只保留前缀、其余替换为 "/***",如前缀 "/api/v2/webhook/alert" 命中 "/api/v2/webhook/alert/tk-123" 记为 "/api/v2/webhook/alert/***"。
func WithAccessLogSilent ¶ added in v0.13.0
func WithAccessLogSilent() AccessLogOption
WithAccessLogSilent 开启「只采不写」模式(#38)。
中间件不再写任何日志:访问日志本身、以及 sink 失败/panic 的告警日志都 不写。**采集行为完全不变**——SkipPaths 判定、路径脱敏、sink 调用(时机与 顺序不变)、panic 请求的记录产出(AccessRecord.Panic=true)与按原值重抛 都照常,因此库的 redaction / skip 能力在静默下依然可用。
典型用法:宿主有自己的 logger(含业务字段、按状态码分级),想复用库的 脱敏与跳过能力、但不想让库再写一行造成双写——此时由 sink 或宿主自己的 采集逻辑输出记录,中间件只负责采集:
h.Use(mw.AccessLogMiddleware(
mw.WithAccessLogSilent(),
mw.WithAccessLogRedactPathPrefixes("/api/v2/webhook/alert"),
mw.WithAccessLogSink(func(ctx context.Context, c *app.RequestContext, rec mw.AccessRecord) error {
return dao.SaveAccessLog(ctx, rec)
}),
))
与 WithAccessLogLogger 同时使用时静默优先:注入的 logger 只用于 sink 告警, 静默下也不写。
func WithAccessLogSink ¶ added in v0.11.0
func WithAccessLogSink(sink AccessSink) AccessLogOption
WithAccessLogSink 注册访问记录旁路消费者——配合调用方自己的 access_log 表 落库、或上报到审计/统计系统(#18)。可多次调用追加;SkipPaths 命中的请求 不触发(跳过 = 不记录,日志与落库口径一致)。
h.Use(mw.AccessLogMiddleware(mw.WithAccessLogSink(
func(ctx context.Context, c *app.RequestContext, rec mw.AccessRecord) error {
uid, _ := c.Get("userId") // 自家鉴权中间件注入的业务键
return accessLogDAO.Save(ctx, uid, rec) // 落库失败只记 Warn,不影响响应
})))
func WithAccessLogSkipPaths ¶
func WithAccessLogSkipPaths(paths ...string) AccessLogOption
WithAccessLogSkipPaths 设置不需要记录日志的路径列表。
type AccessLogOptions ¶
type AccessLogOptions struct {
// SkipPaths 是不需要记录日志的路径列表,如 ["/metrics", "/ping", "/health"]。
// 健康检查和监控端点通常请求频繁,跳过可减少日志噪音。
SkipPaths []string
// RedactPathPrefixes 是需要脱敏的路径前缀列表。命中前缀的路径只保留
// 前缀本身、其余部分替换为 "/***"——「URL 路径即凭证」的路由
// (webhook/{token} 类:钉钉/企微 access_token、GitHub webhook 自配路径)
// 若整条落盘,token 作为调用凭证会随日志进入采集/存储体系
// ([#12](https://github.com/oekit/middleware/issues/12))。
// 前缀按字节匹配;前缀命中的脱敏优先于 path 字段的记录,SkipPaths
// 判定仍使用原始路径(跳过规则不受脱敏影响)。
RedactPathPrefixes []string
// Sinks 是访问记录的旁路消费者列表(落库 / 上报),见 WithAccessLogSink。
Sinks []AccessSink
// Silent 开启「只采不写」模式(#38):本中间件不写任何日志(访问日志
// 本身与 sink 故障告警都不写),但采集行为完全不变——SkipPaths 判定、
// 路径脱敏、sink 调用、panic 请求的记录产出与原值重抛都照常。
// 适用于宿主自带 logger 输出同一份记录、不想双写的场景。
Silent bool
// Logger 是访问日志的输出 logger;nil 表示用内建 logx.Delegating("access")
// (默认行为)。注入宿主自己的 logger 后,访问记录仍由本中间件写出
// (消息与字段不变),只是落到宿主的后端——宿主无需再写一份造成双写
// (#38)。宿主用 zap 等非 logx 后端时可自行实现 logx.Logger 适配,
// 或改用 logx.SetDefault 整体替换后端。
Logger logx.Logger
}
AccessLogOptions 是访问日志中间件的配置。
type AccessRecord ¶ added in v0.11.0
type AccessRecord struct {
// Method 请求方法。
Method string
// Path 请求路径(已脱敏,与日志 path 字段同值)。
Path string
// Status 响应状态码(handler 链执行完毕后读取)。
Status int
// Latency handler 链耗时。
Latency time.Duration
// ClientIP 客户端 IP(c.ClientIP(),尊重 X-Forwarded-For 等代理头配置)。
ClientIP string
// UserAgent 请求 User-Agent。
UserAgent string
// TraceID hmw 注入的 trace_id;未挂 hmw.Middleware 时为空串。
TraceID string
// Panic 为 true 表示本记录来自 panic 恢复点(#20):请求已被记日志/喂 sink
// 后按原值重抛给外层 Recovery;Status 按 Recovery 缺省 500 记。
Panic bool
}
AccessRecord 是 AccessLogMiddleware 单次请求的 HTTP 采集结果,与日志字段 同源同值(Path 已按 RedactPathPrefixes 脱敏)。纯值类型,可安全跨 goroutine 持有——异步落库时只拿它、不要带 *app.RequestContext。
type AccessSink ¶ added in v0.11.0
type AccessSink func(ctx context.Context, c *app.RequestContext, rec AccessRecord) error
AccessSink 接收单次请求的访问记录([#18](https://github.com/oekit/middleware/issues/18)), 在日志写出后、同请求 goroutine 内同步调用。c 为当前请求上下文——可读 c.Get 里鉴权中间件注入的 userId 等业务键、请求头、路由模板——但 **仅在本次 调用内有效**(Hertz RequestContext 池化复用),需异步处理时只持有 rec。
返回 error 或 panic 都只记 Warn 日志,不影响主链路(响应早已写出,访问记录 是旁路;落库失败不能把请求拖垮)。多个 sink 彼此隔离、按注册顺序调用。
type Authorizer ¶
type Authorizer interface {
Authorize(ctx context.Context, c *app.RequestContext, sub Subject) (bool, error)
}
Authorizer 是 BearerTokenAuth 的鉴权钩子接口,token 验证通过后调起。 返回 (false, nil) → 业务拒绝,中间件返 403。 返回 (false, err) → 鉴权后端故障,中间件返 500(避免掩盖业务真实错误)。
设计:ekit 自身不依赖 casbin/jwt 等大 SDK,业务写 adapter 实现接口; P4 模式(SentryReporter)同款。
业务使用(casbin adapter,伪代码):
type casbinAuth struct{ e *casbin.Enforcer }
func (c *casbinAuth) Authorize(ctx, _, sub) (bool, error) {
return c.e.Enforce(sub.UserID, sub.Path, sub.Method)
}
h.Use(mw.BearerTokenAuth(
mw.WithBearerToken(token),
mw.WithAuthAuthorizer(&casbinAuth{e: enforcer}),
))
官方依据:https://www.cloudwego.io/zh/docs/hertz/tutorials/third-party/middleware/casbin/
type BearerTokenOption ¶
type BearerTokenOption func(*BearerTokenOptions)
BearerTokenOption 配置 BearerTokenAuth。
func WithAuthAuthorizer ¶
func WithAuthAuthorizer(a Authorizer) BearerTokenOption
WithAuthAuthorizer 设置 token 验证通过后的鉴权钩子(casbin 等)。 不设置时 BearerToken 通过即放行(无鉴权)。
func WithAuthSkipFunc ¶
func WithAuthSkipFunc(fn func(path string) bool) BearerTokenOption
WithAuthSkipFunc 设置自定义跳过逻辑。 返回 true 表示该路径不需要认证。
func WithAuthSkipPaths ¶
func WithAuthSkipPaths(paths ...string) BearerTokenOption
WithAuthSkipPaths 设置不需要认证的路径列表。
func WithAuthSubjectExtractor ¶
func WithAuthSubjectExtractor(fn SubjectExtractor) BearerTokenOption
WithAuthSubjectExtractor 设置从 RequestContext 提取 Subject 的钩子。 默认不读任何请求头(UserID 为空),需要身份信息时必须显式注入。
func WithBearerToken ¶
func WithBearerToken(token string) BearerTokenOption
WithBearerToken 设置预共享的静态 token。
type BearerTokenOptions ¶
type BearerTokenOptions struct {
// Token 预共享的静态 token。为空时中间件不做校验,直接放行。
Token string
// SkipPaths 不需要认证的路径列表,如 ["/ping", "/healthz", "/metrics"]。
SkipPaths []string
// SkipFunc 自定义跳过逻辑。返回 true 表示该路径不需要认证。
// 与 SkipPaths 可同时使用,任一命中即跳过。
SkipFunc func(path string) bool
// Authorizer token 验证通过后调起的鉴权钩子(可选)。
// 不设置时,BearerToken 通过即放行(仅认证无鉴权)。
Authorizer Authorizer
// SubjectExtractor 从 RequestContext 提取 Subject 传给 Authorizer。
// 默认:从 X-User-Id header 读,Method/Path 自动设置。
SubjectExtractor SubjectExtractor
}
BearerTokenOptions 是 Bearer Token 认证中间件的配置。
type BundleLoader ¶
BundleLoader 是动态加载 Catalog 的钩子(可选)。 首个请求到来时调用一次(在请求路径上执行,非后台异步),结果缓存到 实例;加载失败记录 Warn 日志,后续请求会重试。 与 WithI18nCatalog 同时配置时,Catalog 优先(Loader 不执行)。
type FailOption ¶
type FailOption func(*failOptions)
FailOption 配置 Fail 函数的行为。
func WithNotFoundChecker ¶
func WithNotFoundChecker(checker func(error) bool) FailOption
WithNotFoundChecker 设置 Fail 的"资源未找到"错误检查器。 传入 nil 时使用全局默认 checker。
type I18nOption ¶
type I18nOption func(*I18nOptions)
I18nOption 配置 I18n。
func WithI18nBundleLoader ¶
func WithI18nBundleLoader(fn BundleLoader) I18nOption
WithI18nBundleLoader 设置动态加载钩子,首个请求时调用一次。 与 WithI18nCatalog 同时配置时 Catalog 优先(Loader 不执行)。
func WithI18nCatalog ¶
func WithI18nCatalog(c Catalog) I18nOption
WithI18nCatalog 设置内存翻译(多语言 map,key 为 lang)。 catalog 属于本 I18n() 实例(多实例互不干扰),业务使用:
I18n(WithI18nCatalog(Catalog{
"en": {"welcome": "Welcome", "bye": "Bye"},
"zh": {"welcome": "欢迎", "bye": "再见"},
}))
func WithI18nDefaultLang ¶
func WithI18nDefaultLang(lang string) I18nOption
WithI18nDefaultLang 设置默认语言,默认 "en"。
type I18nOptions ¶
type I18nOptions struct {
// DefaultLang 默认语言,Accept-Language 缺失或不匹配时使用。默认 "en"。
DefaultLang string
// Catalog 内存翻译 KV(可选),优先级高于 BundleLoader。
Catalog Catalog
// BundleLoader 动态加载 Catalog(可选),首个请求时调用一次。
BundleLoader BundleLoader
}
I18nOptions 是 I18n 中间件的配置。
type NotFoundErrChecker ¶
NotFoundErrChecker 是可注入的"资源未找到"错误检查函数。 用于 Fail 的第二层判定,将特定错误映射为 HTTP 404。 默认返回 false(无特殊 not-found 判定)。 通过 WithNotFoundChecker option 按调用注入,或在构造时设置默认值。
type PrometheusConfig ¶
type PrometheusConfig struct {
// Namespace 是指标名称前缀,如 "fuxi" 会生成 fuxi_requests_total。
// 留空则默认为 "http"。
Namespace string
// ServiceName 用于在指标中添加 service 标签(如 "wechat"、"moyu-server")。
// 留空则不添加 service 标签。适用于需要在同一个 Prometheus 中区分多个服务实例的场景。
ServiceName string
// SkipPaths 是不需要采集指标的路径列表,如 ["/metrics", "/ping", "/health"]。
SkipPaths []string
// Registry 是自定义的 Prometheus 注册器。
// 传入 nil 则使用 ekit 统一注册表 metrics.Registry(/metrics 端点默认
// 暴露的就是它);需要隔离时可显式传入其他 Registerer。
Registry prom.Registerer
// Buckets 自定义 Histogram 的桶边界。
// 留空则使用 prom.DefBuckets(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 秒)。
Buckets []float64
// EnableSizeMetrics 开启请求体/响应体大小的 Histogram 指标。
// 开启后会注册 <ns>_request_size_bytes 和 <ns>_response_size_bytes。
EnableSizeMetrics bool
}
PrometheusConfig 是 Prometheus 指标采集的配置。
type PrometheusMetrics ¶
type PrometheusMetrics struct {
// RequestsTotal 按 method/path/status_code 统计请求总数。
RequestsTotal *prom.CounterVec
// RequestDuration 按 method/path 记录请求延迟分布(秒)。
RequestDuration *prom.HistogramVec
// RequestsInFlight 记录当前正在处理的并发请求数。
RequestsInFlight *prom.GaugeVec
// RequestSize 记录请求体大小分布(字节),仅在 EnableSizeMetrics 时注册。
RequestSize *prom.HistogramVec
// ResponseSize 记录响应体大小分布(字节),仅在 EnableSizeMetrics 时注册。
ResponseSize *prom.HistogramVec
// contains filtered or unexported fields
}
PrometheusMetrics 管理 HTTP 请求的 Prometheus 指标。
提供的核心指标:
- <namespace>_requests_total{method, path, status_code} — 请求计数 Counter
- <namespace>_request_duration_seconds{method, path} — 请求延迟 Histogram
- <namespace>_requests_in_flight{method, path} — 当前并发请求数 Gauge
- <namespace>_request_size_bytes{method, path} — 请求体大小 Histogram(需 EnableSizeMetrics)
- <namespace>_response_size_bytes{method, path} — 响应体大小 Histogram(需 EnableSizeMetrics)
当 ServiceName 非空时,所有指标自动添加 service 标签。 路径标签使用路由模板(如 /api/user/:id),未匹配路由使用 "unnamed",避免高基数标签爆炸。
使用方式:
pm := mw.NewPrometheusMetrics(mw.PrometheusConfig{
Namespace: "fuxi",
SkipPaths: []string{"/metrics", "/ping"},
Registry: metrics.Registry,
})
h.Use(pm.Middleware())
func NewPrometheusMetrics ¶
func NewPrometheusMetrics(cfg PrometheusConfig) *PrometheusMetrics
NewPrometheusMetrics 根据配置创建并注册 Prometheus 指标。 创建后调用 .Middleware() 获取 Hertz 中间件函数。
func (*PrometheusMetrics) Middleware ¶
func (m *PrometheusMetrics) Middleware() app.HandlerFunc
Middleware 返回 Hertz 中间件,在每个请求结束后记录 Prometheus 指标。 SkipPaths 中的路径会被跳过(避免 /metrics 自身被采集)。
路径标签使用 c.FullPath()(路由模板),未匹配路由使用 "unnamed"。
type RateLimitOption ¶
type RateLimitOption func(*RateLimitOptions)
RateLimitOption 配置 RateLimitMiddleware。
func WithLimiterTTL ¶
func WithLimiterTTL(ttl time.Duration) RateLimitOption
WithLimiterTTL 设置 limiter 空闲过期时间。
func WithRateLimit ¶
func WithRateLimit(r rate.Limit, burst int) RateLimitOption
WithRateLimit 设置每秒允许的请求数与突发容量。
func WithRateLimitKeyFunc ¶
func WithRateLimitKeyFunc(fn func(c *app.RequestContext) string) RateLimitOption
WithRateLimitKeyFunc 设置限流 key 提取函数。
type RateLimitOptions ¶
type RateLimitOptions struct {
// Rate 每秒允许的请求数。必填。
Rate rate.Limit
// Burst 突发容量。必填。
Burst int
// KeyFunc 从请求中提取限流 key。
// 默认按 client IP 限流。
KeyFunc func(c *app.RequestContext) string
// LimiterTTL limiter 空闲过期时间,默认 10 分钟。
// 内部 KeyedLimiter 会以 TTL/2 为间隔自动清理过期 limiter。
LimiterTTL time.Duration
}
RateLimitOptions 是限流中间件的配置。
type RecoveryOption ¶
type RecoveryOption func(*RecoveryOptions)
RecoveryOption 配置 RecoveryMiddleware。
func WithPanicReporter ¶
func WithPanicReporter(r SentryReporter) RecoveryOption
WithPanicReporter 注册监控后端报告器(sentry/datadog 等)。 panic 发生时,在 ErrorHandler 之前调用 Reporter.Report 上报。
业务使用:
type mySentry struct{ /* ... */ }
func (s *mySentry) Report(...) { /* sentry.CaptureException */ }
h.Use(mw.RecoveryMiddleware(mw.WithPanicReporter(&mySentry{...})))
官方依据:https://www.cloudwego.io/zh/docs/hertz/tutorials/third-party/middleware/sentry/ (ekit 不直接 import sentry-go,避免大依赖,业务自行实现 adapter)
func WithRecoveryErrorHandler ¶
func WithRecoveryErrorHandler(fn func(c *app.RequestContext, r any, stack string)) RecoveryOption
WithRecoveryErrorHandler 设置自定义 panic 处理器。 r 为 recover 到的值,stack 为格式化后的调用栈。
type RecoveryOptions ¶
type RecoveryOptions struct {
// ErrorHandler 处理 panic 后的响应写入。
// 默认使用 ErrorResp 写入 500 响应。
ErrorHandler func(c *app.RequestContext, r any, stack string)
// PanicReporter 监控后端报告器(sentry/datadog 等),先于 ErrorHandler 调用,
// 即使 ErrorHandler 写入响应,Report 仍执行(上报不应被响应影响)。
PanicReporter SentryReporter
}
RecoveryOptions 是 panic recovery 中间件的配置。
type Response ¶
type Response struct {
Code int `json:"code"` // 业务状态码:0=成功,非0=失败
Data any `json:"data"` // 业务数据,失败时为 null
Message string `json:"message"` // 描述信息,如 "success"、"参数错误"
TraceID string `json:"trace_id"` // 链路追踪 ID,贯穿请求日志,用于问题排查
}
Response 是所有 HTTP API 的统一响应结构。 ResponseMiddleware 会自动将 handler 返回的裸数据包装为此格式。 Handler 也可直接通过 SuccessResp/ErrorResp 写入此结构。
type ResponseOption ¶
type ResponseOption func(*responseSkipConfig)
ResponseOption 配置 ResponseMiddleware 的函数选项。
func WithSkipPaths ¶
func WithSkipPaths(paths ...string) ResponseOption
WithSkipPaths 声明精确匹配的跳过路径:命中即整请求不做统一包壳。 适合 OIDC/JWKS 等 RFC 标准端点的静态路径清单。
用法:
mw.ResponseMiddleware(mw.WithSkipPaths("/token", "/jwks.json"))
func WithSkipPrefixes ¶
func WithSkipPrefixes(prefixes ...string) ResponseOption
WithSkipPrefixes 声明前缀匹配的跳过路径:命中即整请求不做统一包壳。 适合 /.well-known/、/mcp 这类按前缀成组的标准端点。
用法:
mw.ResponseMiddleware(mw.WithSkipPrefixes("/.well-known/", "/mcp"))
type SentryReporter ¶
type SentryReporter interface {
Report(ctx context.Context, c *app.RequestContext, panicVal any, stack string)
}
SentryReporter 是 ekit recovery 中间件对接 Sentry/Datadog 等监控后端的接口。
设计目标:ekit 自身不依赖 sentry-go(避免大依赖),业务自己写实现适配 监控 SDK,然后通过 WithPanicReporter 注入 recovery。
典型实现(业务侧,伪代码):
type sentryReporter struct{ client *sentry.Client }
func (r *sentryReporter) Report(ctx context.Context, c *app.RequestContext, panicVal any, stack string) {
hub := r.client.CaptureException(fmt.Errorf("%v", panicVal), &sentry.EventHint{Stacktrace: ...})
_ = hub
}
h.Use(mw.RecoveryMiddleware(mw.WithPanicReporter(&sentryReporter{client: sentryClient})))
type SheddingOption ¶
type SheddingOption func(*SheddingOptions)
SheddingOption 配置 SheddingMiddleware(函数选项模式,禁止裸 Option)。
func WithShedder ¶
func WithShedder(s shedding.Shedder) SheddingOption
WithShedder 注入调用方自建的降载器(如经 shedding.WithShedderClock/ WithShedderRejectHandler 定制后 NewAdaptiveShedder 的实例,或测试 fake)。
优先级:显式 Shedder 胜出 —— 此时 WithShedderCPUThreshold 变为 no-op (含越界值也不会 panic)。
func WithShedderCPUThreshold ¶
func WithShedderCPUThreshold(milli int64) SheddingOption
WithShedderCPUThreshold 定制缺省自适应降载器的 CPU 过载阈值 (千分毫核记法,900 即 90%)。仅在本中间件未通过 WithShedder 显式 指定降载器时生效;合法性校验(须在 (0, 1000) 开区间)延迟到 SheddingMiddleware 构造缺省降载器时执行 —— 越界在构造期 panic (fail-fast,启动即暴露,非首次请求)。
type SheddingOptions ¶
type SheddingOptions struct {
// Shedder 降载器(nil = 用 CPU 阈值构造缺省自适应降载器)。
Shedder shedding.Shedder
// contains filtered or unexported fields
}
SheddingOptions 是降载中间件的配置。
type StackConfig ¶
type StackConfig struct {
// SkipPaths 是跳过标准栈处理的路径(如 /metrics、/healthz、OpenAPI)——
// v0.8.0 起贯通 AccessLog 与 Response(原实现只作用于 AccessLog,
// 导致 /metrics 被包成 {"code":0,...} 信封,Prometheus 解析失败)。
SkipPaths []string
// Prometheus 指标配置(middleware#16,nil = 维持零指标,行为与现状一致)。
// 非 nil 时 Prometheus 中间件保证挂**栈首**(先于 TraceID 才能精确记录
// 耗时——手抄两步装配时最常见的就是忘挂栈首或 SkipPaths 两处传参不一致),
// SkipPaths 单点传入同步作用于指标中间件,杜绝半手抄状态。
Prometheus *PrometheusConfig
// DisableResponseWrap 为 true 时栈内不挂 ResponseMiddleware(#18,
// 「响应体自管」场景一键形态:TraceID → Recovery → AccessLog)。
//
// 适用:响应由各 handler 自己构造、与既有 API 契约对齐(如 {code,msg,data}
// 自定义字段名、null 字段整个不出现等),不希望任何中间件接管响应构造。
// 不设时裸 JSON(顶层无 "code" 字段)会被包成 {code,data,message,trace_id};
// 顶层已含 "code" 的响应本来就原样放行,见 ResponseMiddleware 文档。
DisableResponseWrap bool
// AccessLogOptions 透传给栈内 AccessLogMiddleware 的额外选项(追加在
// SkipPaths 之后):WithAccessLogSink 落库、WithAccessLogRedactPathPrefixes
// 脱敏等。SkipPaths 仍由上面的字段单点传入,此处勿重复设置。
AccessLogOptions []AccessLogOption
}
StackConfig 是中间件栈配置。
type StatusMapper ¶
StatusMapper 将业务码映射为 HTTP 状态码。 默认实现 DefaultStatusMapper 将 HTTP 对齐的业务码(100-599)直接用作 HTTP 状态码, 其余自定义业务码(如 1001)映射为 400 Bad Request。 可通过 SetStatusMapper 自定义映射规则。
type Subject ¶
type Subject struct {
// UserID 用户唯一标识(由 SubjectExtractor 填充;默认为空——X-User-Id
// 是客户端完全可控的请求头,v0.28.1 起默认不再读取,需要时用
// WithAuthSubjectExtractor 显式注入提取逻辑)。
UserID string
// Roles 角色列表(给 RBAC 用,casbin.Enforce(sub, obj, act) 第 1 参数)。
Roles []string
// Claims 透传上游认证结果(例如 JWT claims),业务 Authorizer 内部自取。
Claims map[string]any
// Method HTTP 方法,给 Authorizer 用于 path-pattern 路由。
Method string
// Path 请求路径,给 Authorizer 用于 path-pattern 路由。
Path string
}
Subject 描述当前请求的认证主体,供 Authorizer 决策。 默认 UserID 为空(纯 BearerToken 模式不解析),业务可借 WithAuthSubjectExtractor 从 JWT claims 填充。
type SubjectExtractor ¶
type SubjectExtractor func(c *app.RequestContext) Subject
SubjectExtractor 是从 RequestContext 提取 Subject 的钩子, 业务可从 JWT claims / session 等任意来源填充。 默认不读任何请求头(UserID 为空;X-User-Id 等客户端可控头不可作为 默认 subject 来源,v0.28.1 安全修复)。
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package chainmw 提供 transport 感知的框架无关中间件(基于 core/chain)。
|
Package chainmw 提供 transport 感知的框架无关中间件(基于 core/chain)。 |
|
Package contrib 集中承载 hertz-contrib 社区库的桥接适配。
|
Package contrib 集中承载 hertz-contrib 社区库的桥接适配。 |
|
Package hmw 提供 trace_id 的 Hertz 中间件实现。
|
Package hmw 提供 trace_id 的 Hertz 中间件实现。 |