为什么 go-zero 生成的 model 文件没有 _model 后缀?

你观察得完全对:默认 gozero 风格下,goctl 吐出来的文件名是 usermodel_gen.go,而不是 user_model.go,更不会是 xxx_model.go 这种「带 _model 后缀」的样子。这篇把「为什么没有」讲到底——核心是 goctl 怎么拼基础名、以及 --style 怎么二次格式化它。

一句话结论 你看到的真相 基础名怎么拼 为什么没 _model 真后缀 vs 拼接词 对照表 怎么才能看到 相关阅读

默认风格下,_model 这个下划线被「风格格式化器」吃掉了

goctl 并不是「给 model 文件统一加 _model 后缀」;相反,它先把「表名 + _model」拼成一个基础名,再把这个基础名整个丢进 FileNamingFormat--style 重新拼写。默认 gozero 是「无分隔符连写」,于是下划线没了 → usermodel_gen.go

记住这三条就够了:

① model 文件的基础名 = 表名 + "_model"(goctl 内部拼的),不是「文件名 + 后缀」。
② 这个基础名会整体过一遍 --style 格式化:gozero 把所有词连写 → 下划线消失;go_zero 才保留下划线。
③ 所以默认生成的文件是 usermodel_gen.go(连写),只有当你显式用 --style go_zero 时才会变成 user_model_gen.go(带下划线)。

你用默认 go-zero 生成时,看到的真实文件名

以一张叫 user 的表、一个叫 login 的 @handler 为例,什么参数都不加(即默认 gozero) 跑出来的文件:

model/ ├── usermodel_gen.go ← 自动生成,可覆盖 ├── usermodel.go ← 你手写扩展放这 └── vars.go internal/handler/ ├── loginhandler.go ← 没有 _handler 后缀 └── routes.go internal/logic/ └── loginlogic.go ← 没有 _logic 后缀
重点:这里从头到尾没有 _model_handler_logic 这种「带下划线的后缀」。唯一出现的 _gen 是真后缀(详见第 ④ 节),它跟 style 无关、永远在。

goctl 到底怎么拼出基础名?

三个目录(model / handler / logic)的「基础名」来源完全不同,这是理解「为什么没下划线」的关键。下面三段都来自 go-zero 源码。

① model:基础名 = 表名 + "_model"

goctl model 的生成代码里,文件名是这样来的:

modelFilename, _ := format.FileNamingFormat(g.cfg.NamingFormat,
    fmt.Sprintf("%s_model", tn.Source()))   // 表名 + "_model" 拼成基础名
name := util.SafeString(modelFilename) + "_gen.go"   // 末尾再接 _gen.go

注意:goctl 是先自己把 _model 拼进基础名的,这张表叫 user,基础名就是 user_model。这个 _model 是「内部拼接」出来的,不是给文件加的「类型后缀」。

② handler / logic:基础名 = handler名 + "Handler"/"Logic"(驼峰连写)

goctl api 的 gogen 里,handler、logic 的文件名基础串是驼峰拼接的(没有下划线):

// @handler login  → 基础名 loginHandler(login + Handler 连写)
// @handler login  → 基础名 loginLogic  (login + Logic  连写)
filename := format.FileNamingFormat(style, baseName)  // baseName 形如 loginHandler

所以 Handler / Logic 也是「拼进基础名的词」,不是独立后缀。gozero 下连写 → loginhandlergo_zero 下在词界插下划线 → login_handler

③ 结构体名:走另一条路,永远 PascalCase

同一个生成过程里,Go 标识符和文件名是分开算的。结构体名来自 ToCamel()

"upperStartCamelObject": in.Name.ToCamel(),   // user → UserModel(PascalCase)
"lowerStartCamelObject": stringx.From(in.Name.ToCamel()).Untitle(),

所以无论文件名变成 usermodel_gen.go 还是 user_model_gen.go,结构体始终叫 UserModel。文件名怎么拼,和代码里怎么命名是两码事。

「为什么没有 _model 后缀」的两个原因

合起来就解释了你的观察。

原因 1:默认风格是 gozero
源码里 DefaultFormat = "gozero",即「无分隔符连写小写」。基础名 user_model 过一遍 → 词全连起来 → usermodel。下划线是风格的产物,默认风格把它吞了。网上有些旧文档说「默认是 go_zero」,那是错的。
原因 2:_model 不是「后缀」而是「基础名的一部分」
goctl 是「表名 + _model」先拼好再整体格式化。它从来没有「在文件名末尾统一贴 _model 后缀」这一步。所以不存在「_model 后缀被去掉」这回事——它从一开始就被并进连写里了。
表名 user 来自数据库 user_model goctl 拼基础名 FileNamingFormat 按 --style 二次拼写 切词 → 重新拼接 gozero → usermodel go_zero → user_model

图:基础名 user_model 经过 FileNamingFormat 后,下划线「存不存在」完全由 style 决定。

真后缀 vs 拼接词:别把两件事混为一谈

很多人以为 go-zero 给各类文件统一加了 _model / _handler / _logic 后缀。其实只有 _gen 是真正的「固定后缀」。

这串字符性质说明
_gen真后缀(固定)FileNamingFormat 之后用 + "_gen.go" 拼上,不受 style 影响,永远有。标记「这是生成物、可被覆盖」。
model(在基础名里)拼接词goctl 把 表名 + "_model" 拼进基础名,再整体被 style 格式化。gozero 下它和表名连写 → usermodel
Handler / Logic拼接词gogen 把 handler 名和 Handler/Logic 驼峰连写拼成基础名。gozero 下连写 → loginhandler / loginlogic
所以准确的说法是:默认 gozero 风格下,model / handler / logic 的「词」都和主体连写,没有任何带下划线的后缀;唯一恒在的后缀是 _gen(且它紧跟在风格化之后的名字上,如 usermodel_gen.go)。你看到没有 _model 后缀,完全正确。

同一份代码,两种风格下的文件名

user、@handler login

生成物gozero(默认)go_zero
model 生成文件usermodel_gen.gouser_model_gen.go
model 手写扩展usermodel.gouser_model.go
handler 文件loginhandler.gologin_handler.go
logic 文件loginlogic.gologin_logic.go
结构体 / 类型名UserModel / LoginLogicUserModel / LoginLogic(一样)
看清楚:_model 只有在 go_zero 风格下才「重新现身」成 user_model_gen.gogozero 下它和表名粘成 usermodel。而 Handler / Logic 同理——go_zero 才会在词间插下划线。

想看到带 _model 下划线的文件名?

--style go_zero 显式指定即可。它是「每次生成时传参」,不会改你已经手写的代码。

# api 服务
goctl api go -api user.api -dir . --style go_zero

# mysql model
goctl model mysql ddl -src user.sql -dir . --style go_zero

# 或写进 goctl.yaml 固定项目默认(推荐团队统一)
# namingFormat: go_zero
但注意:切换 style 不会自动重命名已有的老文件。如果你中途改风格,旧文件(usermodel_gen.go)和新生成规则(user_model_gen.go)会共存、冲突。所以一个项目从一开始就定死一种风格。