HTTP handler 必须从 r.Context() 提取 context.Context 并透传至 service、repository 各层,DB 和 HTTP 调用需使用带 Context 的方法,Value 传递须用自定义 key 类型且只存轻量只读数据,cancel 必须 defer 调用,禁止使用 context.Background() 替代请求上下文。

必须在函数签名第一参数位置加 context.Context,否则中间件、超时、取消都无法生效。
HTTP handler 函数必须接收 context.Context
Go 的 http.Handler 接口本身不暴露 context.Context,但实际请求处理中,http.Request 自带 r.Context()。你不能跳过它直接写业务逻辑:
- 错误写法:
func handleUser(w http.ResponseWriter, r *http.Request)—— 后续无法注入超时、取消、用户ID等上下文信息 - 正确写法:
func handleUser(w http.ResponseWriter, r *http.Request)内部立即提取:ctx := r.Context(),后续所有子调用都传这个ctx - 若需增强上下文(如加 traceID、userID),用
context.WithValue()封装新 ctx,**不要修改原r.Context()**
service 层函数签名必须显式声明 context.Context 参数
接口层(handler)拿到 ctx 后,必须一路透传到 service、repository 层。任何漏掉 context.Context 的函数,都会成为 context 链路的“断点”:
- 错误示例:
func (s *UserService) GetUser(id int64) (*User, error)—— 无法响应上游取消,DB 查询可能永远卡住 - 正确示例:
func (s *UserService) GetUser(ctx context.Context, id int64) (*User, error) - 调用 DB 时,必须把
ctx传给db.QueryContext(ctx, ...)或tx.QueryRowContext(ctx, ...),否则超时控制失效 - 若调用下游 HTTP 服务,用
http.NewRequestWithContext(ctx, ...),而非http.NewRequest(...)
Value 传递只用于轻量元数据,且必须用自定义类型作 key
context.WithValue() 不是通用状态容器,滥用会导致类型混乱和 nil panic:
立即学习“go语言免费学习笔记(深入)”;
- key 必须是未导出的自定义类型(如
type userIDKey string),避免与其他包 key 冲突 - 禁止用字符串字面量当 key:
context.WithValue(ctx, "user_id", 123)—— 类型不安全,易拼错 - 值应是只读、小体积数据(int64、string、uuid);**不要传 struct 指针或 map**,因
Value()不保证线程安全 - 取值必须用 ok 判断:
if id, ok := ctx.Value(userIDKey).(int64); ok { ... },不可直接断言
中间件中 cancel 函数必须 defer 调用,且不可复用 ctx
常见错误是把同一个 ctx 多次传给不同 WithXXX 构造函数,导致 cancel 行为不可预测:
- 错误:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second); ctx = context.WithValue(ctx, key, val)——cancel()只取消第一个 ctx,第二个无 effect - 正确:先 WithValue,再 WithTimeout,且 cancel 只作用于最终 ctx:
ctx := context.WithValue(r.Context(), key, val); ctx, cancel := context.WithTimeout(ctx, 5*time.Second); defer cancel() - HTTP 中间件里,
cancel必须defer,确保无论 handler panic 还是正常返回都执行 - 不要把带 cancel 的 ctx 存入全局变量或结构体字段——它是一次性、短生命周期的
最常被忽略的一点:**context.Background() 只用于主函数、init 或测试,绝不该出现在 handler 或 service 中**。一旦用了,整条链路就脱离了请求生命周期,超时和取消全部失效。


















