Help us shape the future of Go! Take the 2026 Go Developer Survey

generator

package
v0.8.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package generator generate.go 是编排器:把「表元数据 + 配置」组装成 TemplateData,再按约定路径渲染落盘。 与 render.go(模板渲染)和 templates.go(模板嵌入)处于同一包 generator 内,共享 TemplateData 定义。

核心流程:

TableMetadata + Config
    ↓ BuildTemplateData(类型映射、主键推导、autoFill 判定)
  TemplateData
    ↓ Render(text/template 执行)
  Java 代码字符串
    ↓ Generate(落盘 or dry-run)
  *.java 文件 / io.Writer 输出

Go 小白知识点(全文重点):

  • io.Writer 接口:只要实现了 Write(p []byte) (n int, err error) 方法,就是 Writer。 os.File、bytes.Buffer、os.Stdout 都是 Writer。Generate 只依赖接口, 测试时传 bytes.Buffer,main 传 os.Stdout,代码一字不改——这是"依赖接口"的典型好处。
  • filepath.Join:跨平台路径拼接(Windows 用 \,Unix 用 /),不要手动拼字符串。
  • os.MkdirAll:等同于 mkdir -p,目录已存在不报错。
  • os.WriteFile:覆盖写文件(Go 1.16+,非原子:中途失败可能留下截断文件),自动创建或覆盖。
  • 包级变量 since:由 main 注入,避免库代码调用 time.Now()——库内取系统时间会让测试非确定性。

Package generator 负责把元数据渲染成代码字符串(po/mapper/service/service-impl/query/converter/mapper-xml 多层)。 核心流程:embed.FS 加载模板 → text/template 解析 → Execute 写入 bytes.Buffer → 返回代码字符串(Java 或 XML)。

Package generator 负责把元数据渲染成 Java 代码字符串(po/mapper/service 三层)。 本文件通过 //go:embed 把模板目录编进二进制,实现"单文件可执行"——发布时无需附带 templates/ 目录。

Index

Constants

This section is empty.

Variables

View Source
var Layers = map[string]LayerSpec{
	"po":                      {PkgSuffix: "model.po", NameSuffix: "", Ext: ".java"},
	"mapper":                  {PkgSuffix: "mapper", NameSuffix: "Mapper", Ext: ".java"},
	"service":                 {PkgSuffix: "service", NameSuffix: "Service", Ext: ".java"},
	"service-impl":            {PkgSuffix: "service.impl", NameSuffix: "ServiceImpl", Ext: ".java"},
	"query":                   {PkgSuffix: "model.query", NameSuffix: "Query", Ext: ".java"},
	"converter":               {PkgSuffix: "converter", NameSuffix: "Converter", Ext: ".java"},
	"mapper-xml":              {PkgSuffix: "mapper", NameSuffix: "", Ext: ".xml", Resource: true},
	"req-dto":                 {PkgSuffix: "model.dto.req", NameSuffix: "ReqDto", Ext: ".java"},
	"resp-dto":                {PkgSuffix: "model.dto.resp", NameSuffix: "RespDto", Ext: ".java"},
	"query-req-dto":           {PkgSuffix: "model.dto.req", NameSuffix: "QueryReqDto", Ext: ".java"},
	"page-query-req-dto":      {PkgSuffix: "model.dto.req", NameSuffix: "PageQueryReqDto", Ext: ".java"},
	"update-by-query-req-dto": {PkgSuffix: "model.dto.req", NameSuffix: "UpdateByQueryReqDto", Ext: ".java"},
	"api":                     {PkgSuffix: "api", NameSuffix: "Api", Ext: ".java"},
	"api-impl":                {PkgSuffix: "api.impl", NameSuffix: "ApiImpl", Ext: ".java"},
}

Layers 是层 → 输出约定的全局约定表(M2-A 含非 API 的 7 层;M2-B 再补 api/dto)。

设计原则(开闭原则):新增层只需在此追加一条 LayerSpec 记录, Generate/OutputPath 等编排代码无需任何修改。

Go 小白知识点:var xxx = map[K]V{...} 是包级变量初始化, 整个程序生命周期内共享同一份 map。约定上只读;但它是导出的包级 map, 语言层面 importer 仍可改写——约定不可变即可,无需额外防护。

Functions

func AllLayers

func AllLayers() []string

AllLayers 返回全部 14 层,固定生成顺序(po 优先、api 收尾,便于阅读产物)。

设计原则:顺序固定(而非遍历 Layers map),因为 map 遍历在 Go 中是随机序—— 如果直接 range Layers,每次调用的输出顺序不同,对用户体验(diff、日志)不友好。 用切片显式定义顺序,保证稳定输出。

func ClassName added in v0.8.0

func ClassName(layer, modelUpperCamel string, mapping map[string]string) string

ClassName 把层名解析为映射后的完整类名——模板(Cls 方法)与落盘文件名(OutputPath) 共用的唯一出口,保证文件名与文件内类声明永远一致。

  • 命中映射 → 模式代入:"I{}Service" + "SysUser" → "ISysUserService"
  • 未命中 → 约定类名:ModelUpperCamel + Layers[layer].NameSuffix(零配置零变化)

Go 小白知识点:strings.ReplaceAll(s, old, new) 替换全部出现;映射值经 config 校验 恰含一个 {},这里无需再防御。

func Generate

func Generate(cfg config.Config, meta model.TableMetadata, layers []string, dryRun bool, out io.Writer) error

Generate 渲染指定层并落盘(dryRun=false)或写入 out(dryRun=true)。

参数说明:

  • cfg:生成器全局配置(包名、输出根目录、数据源等)
  • meta:单张表的元数据(列名、类型、注释、主键标记)
  • layers:要生成的层列表,如 []string{"po","mapper","service"}
  • dryRun:true=只打印到 out,不创建目录/文件;false=落盘
  • out:dry-run 时的输出目标(io.Writer 接口);落盘模式传 nil 即可

io.Writer 接口抽象的价值(Go 小白重点):

Generate 内部只调用 fmt.Fprintf(out, ...),不关心 out 是 bytes.Buffer 还是 os.Stdout。
测试:传 &bytes.Buffer{},事后检查 buf.Len() 和 buf.String()——完全内存操作,无副作用。
main:传 os.Stdout,内容直接打印到终端。
两个场景共用同一套代码,零重复。

func OutputPath

func OutputPath(layer, basePackage, javaRoot, resourcesRoot, modelUpperCamel string, pkgMapping, clsMapping map[string]string) (string, error)

OutputPath 由约定 + 包映射 + 类名映射推导落盘路径。

  • java 层(Resource=false):javaRoot + 映射后包路径 + 映射后类名文件;包映射键 = 约定包后缀
  • resource 层(Resource=true):resourcesRoot + 映射后子目录 + 文件名;包映射键 = 层名(mapper-xml); 文件名跟随 java mapper 的类映射(MyBatis 约定:XML 与 Mapper 接口同名)

pkgMapping/clsMapping 分别与 TemplateData 的 Pkg/Cls 消费同一张表(config.PackageMapping/ClassMapping), 保证落盘位置、文件名与文件内 package 声明、类声明四者永远一致。传 nil = 全约定默认。

返回 (path, error):未知层(不在 Layers 表)返回 error,避免静默写错位置。

Go 小白知识点:多返回值 (string, error) 是 Go 惯用的错误传递方式; 调用方必须检查 error(编译器不强制,但 go vet/lint 会警告忽略 error)。

func Render

func Render(layer string, data TemplateData) (string, error)

Render 渲染指定层的代码模板,返回生成的代码字符串(Java 或 XML)。 layer 取值:"po" / "mapper" / "service" / "service-impl" / "query" / "converter" / "mapper-xml", 对应 templates/<layer>.tmpl。 Go 小白知识点:Go 惯例用 (value, error) 双返回值替代异常;调用方必须检查 error。

func ResolveResourcesRoot

func ResolveResourcesRoot(javaRoot, configured string) string

ResolveResourcesRoot 由 java 源根派生 resources 根:把末段 src/main/java 换成 src/main/resources。 若 configured 非空则直接用它(配置优先于约定,便于非标准项目结构覆盖)。

Go 小白知识点:filepath.Join 跨平台拼路径;strings.HasSuffix 检查字符串是否以某子串结尾。 拼路径永远用 filepath.Join,不要手写 "/" 或 "\\"——Windows/Unix 共用同一套代码。

func SelectLayers

func SelectLayers(onlyTableModify, withoutApi bool) []string

SelectLayers 按两个开关对全集做交集过滤:

  • 默认(两者 false):返回全 14 层。
  • onlyTableModify=true:仅保留"改表影响层"(po/req-dto/resp-dto/mapper-xml/query/query-req-dto)。
  • withoutApi=true:仅保留"非 API 层"(service/service-impl/po/query/mapper/mapper-xml)。
  • 两者同时 true:取交集(po/query/mapper-xml 这 3 层)。

Go 小白知识点:用 map 做集合(值恒 true)做 O(1) 成员判断,按 AllLayers() 全集顺序过滤 以保持稳定输出顺序(map 遍历是随机序,切片遍历是稳定序)。

func SetSince

func SetSince(s string)

SetSince 供 main 包调用,设置生成文件的日期戳(yyyy-MM-dd)。 测试时无需调用——since="" 时模板里 @since 注释为空白,不影响功能正确性。

func ValidateNoClassNameConflict added in v0.8.0

func ValidateNoClassNameConflict(layers []string, modelUpperCamel string, clsMapping map[string]string) error

ValidateNoClassNameConflict 校验本次选中的各层类名两两互异。

为什么需要它:改动前类名由 Layers 表的固定 NameSuffix 派生,13 层天然互异, 这条不变量是白送的;class-mapping 把类名变成用户输入后,白送的保证没了, 而 config 的 validateClassMapping 只做逐值孤立校验(恰一个 {}、前后段字符合法), 看不到「代入后各层是否互相撞名」。两种故障同源于重名:

  • 同名不同包 → 产出两条同名 single-type import,javac 直接失败
  • 同名同包 → 落盘路径相同,按层顺序后者静默覆盖前者,工程凭空少一个类

只查类名即可覆盖两者:路径 = 映射后包 + 映射后类名,故路径相同 ⟹ 类名相同。

只遍历非 Resource 层:mapper-xml 没有自己的类,其文件名刻意跟随 mapper 的类名 (MyBatis 约定 XML 与 Mapper 接口同名),算设计而非冲突。 注意这里隐含依赖「Layers 中只有一个 Resource 层」——当前成立(仅 mapper-xml)。 将来若新增第二个 resource 层,两者可能撞同一个落盘路径而本函数看不见,需一并扩展。

另需说明保证的边界:只覆盖「单次调用内选中的层」。跨次调用不在保证范围内—— 例如先 --with-api 生成了 req-dto,再改映射后跑 --sync-schema(层集更窄), 上一次的产物会作为孤儿文件留在工程里,本函数无从察觉。

Go 小白知识点:map 的迭代顺序是随机的(Go 故意打乱,防止代码依赖顺序), 所以这里用切片 seen 按 layers 的给定顺序收集,保证同样的输入永远报同一条错误—— 错误信息可复现,测试才不会时绿时红。

Types

type LayerSpec

type LayerSpec struct {
	PkgSuffix string // 包后缀,如 model.po;resource 层用作子目录(mapper → resources/mapper/)
	// NameSuffix 约定类名在 ModelUpperCamel 后追加的后缀,如 ServiceImpl;po 层为空
	// (类名即 ModelUpperCamel)。唯一读取点是 ClassName 的「未命中 class-mapping」分支。
	//
	// Resource 层此字段无意义,一律留空:resource 层没有自己的类,ClassName 永远不会
	// 以 resource 层名被调用(模板里没有 Cls "mapper-xml",ClassMappingKeys 也刻意排除它),
	// 其文件名由 OutputPath 直接走 mapper 的类映射。若在此填值,改它不会有任何效果,
	// 而测试全绿、产物纹丝不动——白白浪费下一个维护者一轮调试,所以宁可空着并写明缘由。
	NameSuffix string
	Ext        string // 文件扩展名:.java 或 .xml
	Resource   bool   // true 表示落 resources 根(如 mapper-xml),false 落 java 根
}

LayerSpec 描述一层的输出约定:包后缀、文件名在 ModelUpperCamel 后追加的后缀、扩展名、是否落 resources 根。

Go 小白知识点:结构体(struct)是 Go 中聚合多字段的基本方式,类似 Java 的 POJO。 这里用值类型(非指针),因为 LayerSpec 是不可变的配置描述,复制开销极低。

type TemplateData

type TemplateData struct {
	// Author 代码作者,填入 @author 注释。
	Author string
	// Since 生成日期,填入 @since 注释。
	Since string
	// TableName 数据库表名(原始下划线命名),填入 @TableName。
	TableName string
	// BasePackage Java 基础包名,如 com.dahaoshen.demo。
	BasePackage string
	// ServiceName Feign 服务名(注册中心应用名),填入 @FeignClient(name=...)。来自 config api.service-name。
	ServiceName string
	// BasePath 所有 API 端点的基础路径前缀,如 /admin-api/hello。来自 config api.base-path。
	BasePath string
	// ModelUpperCamel 大驼峰实体类名,如 SysUser。
	ModelUpperCamel string
	// ModelCamel 小驼峰变量名,如 sysUser。
	ModelCamel string
	// ModelKebab 中划线名,如 sys-user,用于 URL 路径等场景。
	ModelKebab string
	// ModelComment 中文表注释,填入 Javadoc 与 @Schema。
	ModelComment string
	// PkFieldUpperCamel 主键字段的大驼峰名(如 Id),用于拼 getter 方法名。
	PkFieldUpperCamel string
	// IdType 主键 Java 类型(如 Long / String),模板里直接展开。
	IdType string
	// UseJakarta 为 true 时引入 jakarta 包并生成 @Serial 注解(Spring Boot 3+ 用 jakarta 而非 javax)。
	UseJakarta bool
	// IsWithAutoFill 为 true 时 import 整个 annotation 包(含 FieldFill),用于自动填充场景。
	IsWithAutoFill bool
	// Fields 表的全部字段元数据,模板中用 range .Fields 遍历。
	// Go 小白知识点:range 内 "." 指向当前元素(FieldMetadata),父级字段要用 "$." 访问(如 $.UseJakarta)。
	Fields []model.FieldMetadata
	// contains filtered or unexported fields
}

TemplateData 是模板渲染上下文,所有字段首字母大写(导出),才能被 text/template 的 {{.Field}} 访问。 Go 小白知识点:text/template 通过反射读取结构体字段,未导出(小写)字段对模板不可见。

func BuildTemplateData

func BuildTemplateData(meta model.TableMetadata, cfg config.Config) (TemplateData, error)

BuildTemplateData 把表元数据 + 配置组装成模板上下文(TemplateData)。

步骤:

  1. 解析方言,获取 TypeMapper(类型映射器)
  2. 遍历 Columns,映射 Java/JDBC 类型、转换字段名、判定 autoFill
  3. 查找单列主键(无主键 / 复合主键 → error,快速失败)
  4. 判定 IsWithAutoFill(任意字段有 autoFill 注解)
  5. 填充 TemplateData 所有字段

错误情形:

  • 未知方言
  • 方言无对应 TypeMapper
  • 无主键 / 复合主键(框架 @TableId 仅支持单列主键)

func (TemplateData) Cls added in v0.8.0

func (d TemplateData) Cls(layer string) string

Cls 把层名解析为映射后的完整类名——模板(类声明/跨层引用/XML namespace)与 落盘文件名(OutputPath)共用同一 ClassName helper,两者天然一致。 变量名不走此出口({{.ModelCamel}} 保持模型派生,见设计决策)。

func (TemplateData) Pkg added in v0.8.0

func (d TemplateData) Pkg(suffix string) string

Pkg 把约定包后缀解析为映射后的完整包名——模板(package/import/XML namespace) 与落盘路径(OutputPath)共用的唯一出口,两者天然一致。

  • 命中映射 → base-package + 映射值
  • 未命中 → base-package + 约定后缀(零配置零变化)
  • 空值 → 直接返回 base-package(类落 base-package 下)

Go 小白知识点:text/template 支持带参数的方法调用 {{.Pkg "model.po"}}—— 方法必须导出(大写开头)、接收者为值或指针均可,参数由模板按字面量传入。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL