hertz路由处理函数必须接收两个参数:context.context和*app.requestcontext,前者用于链路级信息传递,后者仅限单次http请求生命周期,缺一不可。

h.GET 和 h.POST 的 handler 函数签名必须带两个参数:context.Context 和 *app.RequestContext
Hertz 的路由 handler 不像 Gin 那样只接受一个 <em>gin.Context</em>,它明确拆分了两个上下文:一个是标准的 Go context.Context(用于跨协程、跨 RPC 传递 trace、timeout、cancel 等链路级信息),另一个是 app.RequestContext(仅限当前 HTTP 请求生命周期,含请求解析、响应写入等能力)。漏掉任意一个,编译直接报错:cannot use func literal (type func(context.Context, *app.RequestContext)) as type app.HandlerFunc in argument to h.GET
常见错误现象:
- 复制 Gin 代码过来,删掉
<em>gin.Context</em>改成app.RequestContext,但忘了加第一个context.Context参数 - IDE 自动补全只提示
*app.RequestContext,误以为够了 - 在中间件里调用
c.Next()后,handler 里又手动传了c当第一个参数
正确写法长这样:
h.GET("/users/:id", func(ctx context.Context, c *app.RequestContext) {
id := c.Param("id")
c.JSON(consts.StatusOK, utils.H{"id": id})
})
注意:第一个参数名叫 ctx 是惯例,但不是关键字;第二个必须是 *app.RequestContext 类型,不能是别名或自定义包装。
为什么 h.Spin() 不阻塞,而 h.SERVE() 才是真正启动?
h.Spin() 是 Hertz v0.12.x 之前的老接口,v0.13+ 已标记为 deprecated,实际行为是调用 h.SERVE() 并 recover panic,但不处理退出信号。现在官方文档和脚手架生成的代码都统一用 h.SERVE()。
如果你看到日志里没输出监听地址,或者 curl @#@#@#@#@#@#@#@#@#@0 直接 connection refused,八成是用了旧写法:
-
h.Spin()在某些版本里会静默失败(比如端口被占时只打 debug 日志不 panic) -
h.SERVE()启动失败会明确 panic 并打印错误,例如:listen tcp :8888: bind: address already in use
建议始终用:
if err := h.SERVE(); err != nil {
log.Fatal(err)
}
而不是裸调 h.SERVE() 或继续用 h.Spin()。
hz 脚手架生成的代码里,internal/model 和 internal/repo 怎么联动建表?
hz 本身不负责数据库建表,它只生成 API 层骨架(handler、dto、router)。模型注册和自动迁移得靠你手动接入 GORM 或其他 ORM。
关键动作有两步:
- 在
internal/model/demo.go定义结构体时,确保 tag 里有gorm:"primaryKey"等必要声明 - 在数据库初始化位置(通常是
internal/repo/db.go或cmd/server/main.go初始化 DB 的地方),显式调用:db.AutoMigrate(&model.Demo{})
容易踩的坑:
- 只改了 model 文件,没去
AutoMigrate调用里加新 struct,表根本不会创建 -
AutoMigrate在开发期反复执行没问题,但上线后要禁用,否则可能误删字段(GORM 默认不 drop column) - 如果用 MySQL,注意
time.Time字段要加gorm:"type:datetime",否则默认变成timestamp且带时区,和本地时间对不上
GORM AutoMigrate 启动时报 ERROR: database is closed 怎么办?
这个错误说明你在调用 AutoMigrate 前,DB 连接对象已经 close 或根本没成功初始化。
典型场景:
-
gorm.Open()返回 error 没检查,直接拿nil的*gorm.DB去调AutoMigrate - 数据库配置写错(比如
host写成localhost但 Docker 里服务跑在mysql容器名下) -
db, err := gorm.Open(...)后,没把db正确注入到后续需要它的模块(比如 repo 层)
调试建议:
- 在
gorm.Open后立刻加一行:if err != nil { log.Fatal("failed to connect db:", err) } - 用
db.Debug().AutoMigrate(...)开启 GORM 日志,看它到底连的是哪个地址、发了什么 SQL - 检查环境变量是否被覆盖(比如本地 .env 里写了
DB_HOST=localhost,但 CI 环境里是DB_HOST=prod-mysql,却忘了加载)
最硬核的办法:在 AutoMigrate 前加个 ping:
sqlDB, err := db.DB()
if err != nil {
log.Fatal(err)
}
if err := sqlDB.Ping(); err != nil {
log.Fatal("failed to ping db:", err)
}
GORM 的 <em>gorm.DB</em> 是轻量对象,真正连接池在 .DB() 返回的 sql.DB 里——没 ping 通,后面所有操作都是空转。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











