
本文详解 go generate 的工作原理,并通过模板替换实战演示如何基于通用模板批量生成结构体、Handler 及 CRUD 方法,大幅提升重复性数据层代码的开发效率。
本文详解 `go generate` 的工作原理,并通过模板替换实战演示如何基于通用模板批量生成结构体、handler 及 crud 方法,大幅提升重复性数据层代码的开发效率。
go generate 是 Go 官方提供的元编程辅助工具,它本身不参与编译,也不改变 Go 语言语义,而是作为“代码生成触发器”——扫描源码中形如 //go:generate command args... 的特殊注释行,并执行其后指定的命令(如 shell 脚本、自定义 Go 程序等),从而生成新的源文件或资源。它的核心价值在于:将重复、模式化的代码(如 ORM 映射、API 客户端、序列化逻辑)从手动编写转为可复用、可维护的自动化流程。
在你的场景中,目标是将含 %Model% 占位符的模板(如 templ.go)自动替换为具体模型名(如 Address),生成完整可运行的 Go 文件。这需要三要素协同工作:
- 模板文件(不可直接编译):纯文本或 .go 文件,含占位符(如 %Model%);
- 生成指令(写在可构建的 .go 文件中)://go:generate 注释调用替换命令;
- 替换工具:负责读取模板、执行字符串替换、写入新文件的脚本或程序。
下面以你的 Address 为例,给出完整可运行方案:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
✅ 步骤 1:准备模板文件(model_template.go)
// model_template.go —— 仅作模板,不参与构建
package main
import (
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// --- %Model%
type %Model% struct {
Id bson.ObjectId `bson:"_id,omitempty"`
AccountId string `bson:"account_id"`
Name string `bson:"name"`
StreetAddress string `bson:"streetaddress"`
Town string `bson:"town"`
Country string `bson:"country"`
}
// --- %Model%Handler
type %Model%Handler struct {
MS *mgo.Session
}
func New%Model%Handler(ms *mgo.Session) *%Model%Handler {
return &%Model%Handler{MS: ms.Clone()}
}
func (h *%Model%Handler) Close() {
h.MS.Close()
}
// Add
type Add%Model%Input struct {
%Model% *%Model%
}
type Add%Model%Output struct {
Error error
}
func (h *%Model%Handler) Add%Model%(in *Add%Model%Input, out *Add%Model%Output) {
ms := h.MS.Copy()
defer ms.Close()
c := ms.DB("").C("%Model%")
out.Error = c.Insert(in.%Model%)
}
// Remove
type Remove%Model%Input struct {
%Model%Id string
}
type Remove%Model%Output struct {
Error error
}
func (h *%Model%Handler) Remove%Model%(in *Remove%Model%Input, out *Remove%Model%Output) {
ms := h.MS.Copy()
defer ms.Close()
c := ms.DB("").C("%Model%")
out.Error = c.RemoveId(bson.ObjectIdHex(in.%Model%Id))
}
// Update
type Update%Model%Input struct {
%Model% *%Model%
}
type Update%Model%Output struct {
Error error
}
func (h *%Model%Handler) Update%Model%(in *Update%Model%Input, out *Update%Model%Output) {
ms := h.MS.Copy()
defer ms.Close()
c := ms.DB("").C("%Model%")
out.Error = c.UpdateId(in.%Model%.AccountId)
}
// GetAllByAccount
type Get%Model%Input struct {
AccountId string
}
type Get%Model%Output struct {
%Model% []*%Model%
Error error
}
func (h *%Model%Handler) Get%Model%(in *Get%Model%Input, out *Get%Model%Output) {
ms := h.MS.Copy()
defer ms.Close()
c := ms.DB("").C("%Model%")
out.Error = c.Find(bson.ObjectIdHex(in.AccountId)).All(&out.%Model%)
}
✅ 步骤 2:编写替换脚本(gen_model.sh)
#!/bin/bash # gen_model.sh —— 需 chmod +x gen_model.sh if [ $# -ne 3 ]; then echo "Usage: $0 <template><output_file><model_name>" exit 1 fi TEMPLATE=$1 OUTPUT=$2 MODEL=$3 sed "s/%Model%/$MODEL/g" "$TEMPLATE" > "$OUTPUT" echo "✅ Generated: $OUTPUT"</model_name></output_file></template>
✅ 步骤 3:在主包中添加 //go:generate 指令(如 main.go)
package main
import "fmt"
//go:generate ./gen_model.sh model_template.go address.go Address
func main() {
fmt.Println("Code generation complete. Run 'go build' to compile.")
}
✅ 执行生成
$ chmod +x gen_model.sh $ go generate ✅ Generated: address.go $ ls -1 address.go # ← 自动生成!含完整 Address 实现 gen_model.sh main.go model_template.go
生成的 address.go 将精确替换所有 %Model% 为 Address,并可直接参与构建(注意:需确保 address.go 中 package main 与项目一致,或按需改为 package yourpkg)。
⚠️ 关键注意事项
- //go:generate 必须位于可构建的 Go 文件中(即 package main 或 package xxx),且该文件需能被 go list 发现;
- $GOFILE、$GODIR 等环境变量可在命令中使用,但推荐显式传参提升可读性;
- 生成的文件不应手动编辑,否则下次 go generate 会覆盖;建议将其加入 .gitignore 或明确标注 // Code generated by go generate; DO NOT EDIT.;
- 复杂逻辑推荐用 Go 程序替代 shell 脚本(如 go run gen.go -t model_template.go -o user.go -m User),更健壮、跨平台、易测试;
- 运行 go generate -n 可预览将执行的命令,go generate -v 查看详细日志,便于调试。
通过这套机制,你只需维护一份高质量模板和一个轻量脚本,即可一键生成 User、Order、Product 等任意实体的完整数据访问层——真正实现“写一次,生成百次”,让 Go 的工程化能力跃升一个台阶。










