本文档面向 Go 小白(有 Java 后端经验)。按代码生成的实际执行顺序逐站串讲整个代码库,每站附「Java 对照」与「Go 小白知识点」。
用户执行 base-code gen --config base-code.yaml --tables sys_user
│
▼
┌────────────┐
│ config │ 加载 base-code.yaml,填充约定默认值
└─────┬──────┘
│
▼
┌────────────┐
│ dialect │ "mysql" / "postgresql" → SqlDialect 常量
└─────┬──────┘
│
▼
┌────────────┐
│ scanner │ database/sql 执行 SQL,扫描列 / 主键 / 表注释
└─────┬──────┘
│ TableMetadata(原始数据库视角)
▼
┌────────────┐
│ typemap │ dbType → JavaType / JdbcType(接口隐式实现)
└─────┬──────┘
│
▼
┌────────────┐
│ naming │ snake_case → 驼峰 / kebab 转换
└─────┬──────┘
│
▼
┌────────────┐
│ model │ FindSinglePrimaryKey:无主键/复合主键快速失败
└─────┬──────┘
│ TemplateData(Java 视角)
▼
┌────────────────────────────────────────┐
│ generator │
│ BuildTemplateData → Render(embed.FS │
│ + text/template + FuncMap)→ Generate │
│ → OutputPath → 落盘 / dry-run │
└────────────────────────────────────────┘
│ *.java / *.xml × 14 层
▼
目标工程 src/main/java(或 resources)
它做什么:读取 base-code.yaml,解析为 Config 结构体,对缺省字段填入约定值,确保后续阶段不需要做 nil 判断。
关键文件:internal/config/config.go
// internal/config/config.go:15-27
type Config struct {
Tables []string `yaml:"tables"` // 生成目标表;flag(CSV)与配置文件(列表)二选一,合并后必填
BasePackage string `yaml:"base-package"`
OutputRoot string `yaml:"output-root"`
ResourcesRoot string `yaml:"resources-root"` // 可选;mapper-xml 输出根,缺省由 OutputRoot 推导
Author string `yaml:"author"`
UseJakarta *bool `yaml:"use-jakarta"` // 指针:区分「未配置」与「配置为 false」
DateType string `yaml:"date-type"`
WithApi *bool `yaml:"with-api"` // 指针:区分「未配置」(缺省 false,不生成 API 层)与「显式 true」
Api Api `yaml:"api"`
Datasource Datasource `yaml:"datasource"`
AutoFill AutoFill `yaml:"auto-fill"`
}配置来源不止 YAML 文件——base-code gen 还支持 18 个内联 flag(--tables、--base-package、--db-host、--with-api 等,见站 8)。Overrides 是「命令行显式提供了什么」的快照,全部用指针字段:
// internal/config/config.go:62-81
type Overrides struct {
Tables *[]string
BasePackage *string
OutputRoot *string
ResourcesRoot *string
Author *string
UseJakarta *bool
DateType *string
WithApi *bool
Dialect *string
DbHost *string
DbPort *int
DbUser *string
DbPassword *string
DbName *string
ServiceName *string
BasePath *string
AutoFillInsert *[]string
AutoFillUpdate *[]string
}ServiceName/BasePath 对应 CLI 的 --api-service-name/--api-base-path(yaml 键仍是 api.service-name/api.base-path——只有 flag 名加了 api- 前缀防止顶层撞名,字段名/yaml 键未变)。
这是 UseJakarta *bool 那个「指针区分未配置/零值」教学点的自然延伸:nil 表示「命令行未提供该项」,非 nil(哪怕指向 ""/0/false)表示「显式提供」。所以 --db-port 0 与「不传 --db-port」是两码事——前者会真的把端口设为 0,后者才会触发方言派生(3306/5432)。
Load 现在只是薄封装(保持既有调用方兼容——必须有文件、无内联覆盖):
// internal/config/config.go:116-118
func Load(path string) (Config, error) {
return LoadWithOverrides(path, true, Overrides{})
}真正的加载入口是 LoadWithOverrides(path, requireFile, ov),优先级为 flag > 配置文件 > 约定默认值:
requireFile=true(用户显式--config):文件缺失直接报错;requireFile=false(纯 flag 模式,Load不会走到这一态):文件缺失是合法状态,从零配置起步。- 依次执行:
applyOverrides叠加非 nil 的ov字段 →applyDefaults补约定默认值(含方言→端口派生)→ 用ov.DbPort再覆盖一次端口(防止显式--db-port 0被 3306/5432 派生值顶掉)→validate校验必填项,报错带 flag 名与可复制的完整命令样例。 - 必填项共 3 项:
tables、base-package、db-name,全部由validate在合并 flag+配置文件后的生效值上裁决(cobra 层不做MarkFlagRequired——那发生在读配置文件之前,会误杀「配置文件已提供」的合法用法);任一来源提供即算提供。数据库连接、API 层标识等其余项全部约定默认值下沉,agent 三参直达。
约定默认值(applyDefaults):
UseJakarta:nil →true(Spring Boot 3+ 默认 jakarta)DateType:空 →"modern"(使用java.time.*)Author:空 →gitUserName()(读 git config)WithApi:nil →false(默认不生成 API 层,api/api-impl需显式--with-api或with-api: true)AutoFill.InsertColumns:空 →["created_at","updated_at","created_by","updated_by"]AutoFill.UpdateColumns:空 →["updated_at","updated_by"]Datasource.Dialect:空 →"mysql";Datasource.Host:空 →"127.0.0.1";Datasource.Username:空 →"root"OutputRoot:空 →"./src/main/java"Datasource.Port:0→ 按方言派生(mysql→3306,postgresql→5432;dialect 缺省必须先于此步补齐)Api.ServiceName/Api.BasePath:空 → 从BasePackage末段派生(如com.example.demo→demo//demo)
Java 对照
Java 版通过 Spring Boot
@ConfigurationProperties+application.yml加载配置,由 Spring 容器注入。Go 版没有 IoC 框架,手动用gopkg.in/yaml.v3解析 YAML 并调用applyDefaults填充默认值,等价于 Java 的@PostConstruct初始化逻辑。Java
Optional<T>区分「无值」与「false」;Go 用*bool指针——nil表示未配置,非 nil 表示明确设置(包括false)。
Go 小白知识点
- 结构体标签
`yaml:"base-package"`告诉 yaml 库把 YAML 键base-package映射到BasePackage字段。这类标签是反引号包裹的字符串,在运行时通过反射读取。*bool是指向 bool 的指针。Go 无原生「三态 bool」,用指针模拟:nil表示缺省,&true/&false表示显式设置。
它做什么:把配置文件里的字符串方言值("mysql" / "postgresql")转为类型安全的常量,防止后续代码直接比较裸字符串。
关键文件:internal/dialect/dialect.go
// internal/dialect/dialect.go:8-15
type SqlDialect string // 具名字符串类型(named type),底层是 string
const (
MySQL SqlDialect = "mysql"
PostgreSQL SqlDialect = "postgresql"
)
// FromValue 解析配置字符串为方言,未知值返回 error
func FromValue(s string) (SqlDialect, error) { ... }Java 对照
Java 版用枚举:
// com.example.basecode.enums.SqlDialect public enum SqlDialect { MYSQL("mysql", "MySQL数据库"), POSTGRESQL("postgresql", "PostgreSQL数据库"); public static SqlDialect fromValue(String value) { ... } }Go 没有
enum关键字,惯用模式是type X string+const块。与 Java 枚举相比,Go 方案同样类型安全,但不能像枚举那样穷举 switch(编译器不强制)。
Go 小白知识点
type SqlDialect string是「具名类型」(named type)而非类型别名(type X = string)。具名类型与底层类型不能直接赋值,需显式转换:SqlDialect("mysql")合法,但var d SqlDialect = "mysql"需要字面量兼容。这为编译期类型安全提供保障。
它做什么:连接数据库,执行 SQL 读取表的列信息、主键、表注释,返回 model.TableMetadata(数据库原始视角)。
关键文件:
internal/scanner/scanner.go:TableScanner接口 +For工厂函数internal/scanner/mysql.go:MySQL 实现(mySQLScanner)internal/scanner/postgresql.go:PostgreSQL 实现(postgreSQLScanner)
// internal/scanner/scanner.go:25-28
type TableScanner interface {
ScanTable(table string) (model.TableMetadata, error)
}
func For(d dialect.SqlDialect, db *sql.DB) (TableScanner, error) { ... }// internal/scanner/mysql.go:47-48 第一段:SHOW FULL COLUMNS
rows, err := s.db.Query(fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`", table))
// SHOW FULL COLUMNS 9 列:Field, Type, Collation, Null, Key, Default, Extra, Privileges, Comment
// internal/scanner/mysql.go:86-88 第二段:查表注释
_ = s.db.QueryRow(
"SELECT table_comment FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1", table,
).Scan(&tableComment)PostgreSQL 把列信息、约束信息、对象注释分散在不同系统表里,需要三段独立查询:
// internal/scanner/postgresql.go:42-47 第一段:列查询(含列注释)
const pgColumnSQL = `SELECT column_name, data_type, ...,
COALESCE(col_description(pgc.oid, pa.attnum), '') as column_comment
FROM information_schema.columns c
LEFT JOIN pg_class pgc ON pgc.relname = c.table_name
...WHERE c.table_name = $1 AND c.table_schema = 'public'`
// 第二段:主键查询(information_schema.table_constraints)
// 第三段:表注释查询(obj_description)Java 对照
Java 版用
DatabaseTableScanner接口(interface DatabaseTableScanner),MySQL 实现为MysqlTableScanner,PostgreSQL 实现为PostgreSqlTableScanner,通过TableScannerFactory工厂按方言分发(对应 Go 的scanner.For)。Java 使用ResultSet(JDBC 游标),Go 使用*sql.Rows(database/sql 游标),模式完全对称。
Go 小白知识点
- 隐式接口:Go 接口是「隐式实现」——
mySQLScanner有ScanTable方法就自动满足TableScanner接口,无需implements关键字。rows.Next()遍历:db.Query()返回*sql.Rows(游标),必须调用defer rows.Close()归还连接;rows.Next()移动游标,rows.Scan(&v1, &v2...)读取当前行,必须传指针。- MySQL
?vs PostgreSQL$1:MySQL 驱动用?占位符,pgx 驱动(pgx/v5/stdlib)用$1, $2...有序编号占位符。sql.NullString/sql.NullInt64:接收数据库 NULL 值的标准类型,Valid=false表示 NULL,Valid=true时字段有意义。go-sqlmock单元测试:github.com/DATA-DOG/go-sqlmock注册一个假sql.Driver,无需启动真实数据库即可测试ScanTable——预设期望 SQL 与返回行,断言结果(见internal/scanner/mysql_test.go/postgresql_test.go)。类比 Java 的 Mockito mockResultSet或 H2 内存库。
它做什么:把数据库列类型字符串(如 "bigint")映射为 Java 类型名("Long")和 JDBC 类型名("BIGINT"),供模板渲染使用。
关键文件:
internal/typemap/typemap.go:TypeMapper接口 +For工厂internal/typemap/mysql.go:MySQL 实现,含modern/legacy日期类型分支internal/typemap/postgresql.go:PostgreSQL 实现
// internal/typemap/typemap.go:14-19
type TypeMapper interface {
MapToJavaType(dbType string) string
MapToJdbcType(dbType string) string
}
func For(d dialect.SqlDialect, dateType string) (TypeMapper, error) { ... }dateType 分支(mysql.go:60-68):
"modern"(默认):date→java.time.LocalDate,datetime/timestamp→java.time.LocalDateTime"legacy":date→java.util.Date,datetime/timestamp→java.util.Date
FQN 免 import:非 java.lang 类型使用全限定名(Full Qualified Name),模板直接展开无需额外 import:
decimal→java.math.BigDecimal(FQN)varchar→String(java.lang,自动引入)
Java 对照
Java 版:
TypeMapper接口定义在com.example.basecode.mapper.TypeMapper,方法签名为String mapToJavaType(String dbType)和JDBCType mapToJdbcType(String dbType)(注意 Java 版mapToJdbcType返回java.sql.JDBCType枚举,Go 版返回字符串)。实现类为MySqlTypeMapper和PostgreSqlTypeMapper,由TypeMapperFactory按方言分发。
Go 小白知识点
Go 接口通过「结构类型系统」(structural typing)实现:只要结构体有接口声明的全部方法签名,就自动满足接口,无需显式声明。这与 Java 的
implements关键字(名义类型系统)完全不同,带来极大的灵活性——可以为任何已有类型补充接口实现,甚至为第三方库类型「伪实现」接口。
它做什么:把数据库下划线命名(sys_user)转成 Java 驼峰命名(sysUser / SysUser)以及 URL 路径用的 kebab 命名(sys-user)。
关键文件:internal/naming/naming.go
// internal/naming/naming.go:7-11 导出的命名转换函数
func Camel(s string) string { return camel(s, false) } // sys_user → sysUser
func UpperCamel(s string) string { return camel(s, true) } // sys_user → SysUser
func Kebab(s string) string { ... } // SysUser → sys-user(仅接受大驼峰输入)
func Capitalize(s string) string { ... } // userName → UserName(仅首字母)camel 内部实现:遍历 rune,遇 _ 或 - 则将下一字符大写;upperFirst 控制首字符。
Java 对照
Java 版在
BaseCodeApplication中以私有实例方法实现(toCamelCase(String input)、toUpperCamelCase(String input)、toKebabCase(String input)、capitalize(String input)),散落在业务逻辑类里。Go 版提取为独立的naming包,函数首字母大写(导出),便于独立测试和跨包复用。
Go 小白知识点
range s(s 为 string)按 **Unicode 码点(rune)**遍历,而非字节。这对中文等多字节字符安全。- Go 没有字符类型
char,字符用rune(int32的别名)表示。strings.Builder是 Go 1.10+ 高效字符串拼接工具,避免每次+=产生临时字符串(对应 JavaStringBuilder)。- 表驱动测试(table-driven test):Go 惯用模式,用
[]struct{input, want}切片集中组织测试用例,通过for range循环调用t.Run,见internal/naming/naming_test.go。
它做什么:定义三个核心数据结构(数据库视角 ColumnMetadata、TableMetadata;Java 视角 FieldMetadata)以及单主键校验函数。
关键文件:internal/model/model.go
// internal/model/model.go:10-15 数据库视角
type ColumnMetadata struct {
ColumnName string
ColumnType string
ColumnComment string
IsPrimaryKey bool
}
// internal/model/model.go:19-27 Java 视角
type FieldMetadata struct {
JavaType string
JdbcType string
Name string // 小驼峰字段名,如 userName
TableField string // 原始列名,如 user_name
Comment string
AutoFill string // "" / "insert" / "update" / "insertUpdate"
IsPrimaryKey bool
}
// internal/model/model.go:38-56 单主键校验(快速失败)
func FindSinglePrimaryKey(fields []FieldMetadata, tableName string) (FieldMetadata, error) {
// 无主键 → error;复合主键 → error;恰好一列 → 返回该列
}Java 对照
Java 版:
ColumnMetadata、TableMetadata、FieldMetadata均位于com.example.basecode.model包,用 Lombok@Data生成 getter/setter。Go 版结构体直接访问导出字段,无需 getter/setter(Go 惯用「扁平结构」,不封装简单数据载体)。Java 版主键校验抛
IllegalStateException,Go 版FindSinglePrimaryKey返回(FieldMetadata, error),调用方显式检查——这是 Go「error as value」哲学,错误是普通返回值,不是异常。
Go 小白知识点
- Go 结构体字段首字母大写 = 导出(
public),小写 = 包私有(package-private)。text/template通过反射访问字段,只能访问导出字段——这是为什么TemplateData和FieldMetadata的每个字段都首字母大写。- Go 没有
throws声明,错误通过多返回值(T, error)传播,调用方必须在调用点检查error(go vet会警告忽略错误)。
它做什么:这是流水线的核心枢纽,分为三个文件:
templates.go://go:embed把 14 个.tmpl文件编进二进制render.go:Render函数执行模板渲染,返回代码字符串generate.go:BuildTemplateData(组装上下文)、Generate(编排 + 落盘)、Layers(层定义表)、SelectLayers(层过滤)
// internal/generator/templates.go:11-12
//go:embed all:templates
var templateFS embed.FS// internal/generator/render.go:60-74
func Render(layer string, data TemplateData) (string, error) {
name := layer + ".tmpl"
tmpl, err := template.New(name).Funcs(funcMap).
ParseFS(templateFS, "templates/"+name)
...
var buf bytes.Buffer
tmpl.Execute(&buf, data)
return buf.String(), nil
}
// FuncMap 注入命名转换函数到模板
var funcMap = template.FuncMap{
"camel": naming.Camel, "upperCamel": naming.UpperCamel,
"kebab": naming.Kebab, "capitalize": naming.Capitalize,
}模板片段示例(po.tmpl 使用 range .Fields):
{{range .Fields}}
@TableField(value = "{{.TableField}}")
private {{.JavaType}} {{.Name}};
{{end}}
{{if $.UseJakarta}}@Serial{{end}}
(此处省略 fill = FieldFill.INSERT/UPDATE/INSERT_UPDATE 的 autoFill 条件分支,完整见 templates/po.tmpl)
range .Fields内部.指向当前FieldMetadata;要访问父级上下文(TemplateData)字段,需用$.UseJakarta而非.UseJakarta。
// internal/generator/generate.go:57-72
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: "Mapper", Ext: ".xml", Resource: true},
// ... 7 个 DTO/API 层
}LayerSpec.Resource=true 表示 XML 文件落 resources/mapper/ 而非 src/main/java。
// internal/generator/generate.go:315-328
func SelectLayers(onlyTableModify, withoutApi bool) []string {
// 从 AllLayers()(全 14 层稳定顺序)按两个开关过滤:
// onlyTableModify=true → 仅保留 po/req-dto/resp-dto/mapper-xml/query/query-req-dto(6 层,对应 --sync-schema)
// withoutApi=true → 仅保留 6 层后端核心:po/mapper/mapper-xml/service/service-impl/query(对应「未传 --with-api」)
// 两者同时 true → 取交集(po/mapper-xml/query 这 3 层,即 --sync-schema 且未传 --with-api)
}调用方 cmd/gen.go 把 --with-api 取反传给 withoutApi 参数:generator.SelectLayers(flagSyncSchema, !*cfg.WithApi)——with-api 已归入 config(flag > 文件 > 缺省 false),sync-schema 是纯运行时开关,不入配置文件。
// internal/generator/generate.go:143-211
func BuildTemplateData(meta model.TableMetadata, cfg config.Config) (TemplateData, error) {
// 1. 解析方言 → 2. 获取 TypeMapper → 3. 遍历列构建 FieldMetadata
// 4. FindSinglePrimaryKey(快速失败)
// 5. IdType = pk.JavaType(从主键列类型动态推导,如 bigint→Long)
// 6. PkFieldUpperCamel = naming.Capitalize(pk.Name)(如 id→Id)
}Java 对照
Java 版(
BaseCodeApplication)使用 Thymeleaf(org.thymeleaf.TemplateEngine)作为模板引擎,模板以.tmpl后缀存放在resources/templates/下,Spring Boot 打包后通过 ClassPath 加载。模板语法:变量用[[${basePackageName}]],循环用[# th:each="field : ${fields}"]...[/],条件用[# th:if="${...}"]...[/]。Go 版用//go:embed将.tmpl文件编译进二进制(单文件分发),模板引擎换为标准库text/template,语法不同但设计意图完全对应。
Go 小白知识点
//go:embed:编译指令,必须紧贴var声明(中间不能有空行)。all:templates嵌入templates/目录下全部文件(包含隐藏文件)。text/template:Go 标准库模板引擎,{{.Field}}访问结构体字段,{{range .Slice}}...{{end}}遍历切片,{{if .Bool}}...{{end}}条件渲染。对应 Java Thymeleaf 的[[${field}]]/[# th:each ...],但两者语法不同——Go 用双花括号,Thymeleaf 用双方括号或属性标签。io.Writer接口:Generate的out io.Writer参数。只要实现Write([]byte) (int, error),就是Writer。测试传&bytes.Buffer{},CLI 传os.Stdout,代码一字不改——这是「依赖接口,不依赖实现」的典型体现。map[string]bool当集合:Go 没有Set<T>,惯用map[K]bool,O(1)成员判断(onlyTableModifySet[layer])。
它做什么:CLI 入口,组织命令树(base-code gen),解析 flag,按方言选驱动和 DSN,调用前面所有站的函数完成流水线。
关键文件:
cmd/root.go:rootCmd(base-code)+Execute()cmd/gen.go:genCmd(base-code gen)+ flag 绑定 + 流水线调用
// cmd/gen.go:31-37(完整 var 块还有 15 个内联配置 flag,延伸至 56 行)
var (
flagConfig string // --config:配置文件路径
flagTables string // --tables:逗号分隔的表名(与配置文件 tables 合并后必填)
flagDialect string // --dialect:覆盖配置文件中的方言
flagDryRun bool // --dry-run:只打印到终端,不落盘
flagSyncSchema bool // --sync-schema:改表后只重新生成受表结构影响的层(po/req-dto/resp-dto/mapper-xml/query/query-req-dto)
flagWithApi bool // --with-api:额外生成 API 层 api/api-impl
...
)这 6 个变量里 flagTables/flagDialect/flagWithApi 也对应 Overrides 字段;连同下方另 15 个内联配置 flag,共 18 个内联 flag(--base-package、--output-root、--db-host、--with-api 等,与 base-code.yaml 各配置键一一对应)不在本文重复列出,完整清单见 README.md 的「Flag 说明」节(按生成目标/数据库连接/API 层/生成行为四组渲染)——单处维护,避免和这里脱节。
genCmd.RunE 把「哪些内联 flag 要装进 config.Overrides」交给 fs.Changed(*pflag.FlagSet 方法)判断,而不是看值是否为零值:
// cmd/gen.go:122-124
if fs.Changed("db-port") {
ov.DbPort = &flagDbPort
}这与 config 站的 *bool/Overrides 指针语义呼应:fs.Changed 报告某个 flag 是否在命令行出现过,因此 --db-port 0、--use-jakarta=false 这类「显式传零值」也会被正确识别为「用户提供了」,不会被误判成「未提供,走缺省」。
// cmd/gen.go:248-263
func dbDriverAndDSN(d dialect.SqlDialect, cfg config.Config) (string, string) {
switch d {
case dialect.PostgreSQL:
return "pgx", fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", ...)
default: // MySQL
return "mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true", ...)
}
}MySQL 和 PostgreSQL 驱动通过空白导入注册:
// cmd/gen.go:10,16(完整 import 块为 3-26,中间是驱动注册机制的详细注释)
import (
_ "github.com/go-sql-driver/mysql" // 注册 "mysql" 驱动
_ "github.com/jackc/pgx/v5/stdlib" // 注册 "pgx" 驱动
)rootCmd.SilenceErrors = true,由 main.go 统一处理:若 Execute() 返回 error,打印一次错误并以非零退出码退出(os.Exit(1))。genCmd 使用 RunE(Run with Error),返回 nil 则正常退出(exit 0)。
Java 对照
Java 版
BaseCodeApplication是 Spring Boot 应用,通过@SpringBootApplication+CommandLineRunner接收命令行参数,没有显式的命令树结构。参数解析依赖手工args[0]、args[1]拆分。Go 版用 cobra(类似 Java 的 picocli / JCommander),支持多级子命令、自动生成--help文档、flag 类型安全解析。
Go 小白知识点
_ "pkg"(空白导入):database/sql使用注册机制——驱动包的init()调用sql.Register("mysql", ...)注册自身。空白导入触发init()但不引入任何符号。若不导入,sql.Open("mysql", dsn)报unknown driver "mysql"。- 日期格式:Go 使用「参考时间」而非占位符:
time.Now().Format("2006-01-02")等价于 Java 的new SimpleDateFormat("yyyy-MM-dd")。Go 的参考时间是Mon Jan 2 15:04:05 MST 2006(Go 诞生时刻),月=01,日=02,年=06。defer:函数返回时(包含所有 return 路径)必然执行,是 Go 确保资源释放(文件/连接关闭)的惯用方式,对应 Java 的try-finally或try-with-resources。
以下层名与 internal/generator/generate.go 中 Layers map 的键完全一致,文件名格式为 {ModelUpperCamel}{NameSuffix}{Ext}。
| 层名 | 包后缀(PkgSuffix) | 文件名示例(sys_user 表) |
扩展名 | 落盘根 |
|---|---|---|---|---|
po |
model.po |
SysUser.java |
.java |
java 根 |
mapper |
mapper |
SysUserMapper.java |
.java |
java 根 |
mapper-xml |
mapper |
SysUserMapper.xml |
.xml |
resources 根 |
service |
service |
SysUserService.java |
.java |
java 根 |
service-impl |
service.impl |
SysUserServiceImpl.java |
.java |
java 根 |
query |
model.query |
SysUserQuery.java |
.java |
java 根 |
converter |
converter |
SysUserConverter.java |
.java |
java 根 |
req-dto |
model.dto.req |
SysUserReqDto.java |
.java |
java 根 |
resp-dto |
model.dto.resp |
SysUserRespDto.java |
.java |
java 根 |
query-req-dto |
model.dto.req |
SysUserQueryReqDto.java |
.java |
java 根 |
page-query-req-dto |
model.dto.req |
SysUserPageQueryReqDto.java |
.java |
java 根 |
update-by-query-req-dto |
model.dto.req |
SysUserUpdateByQueryReqDto.java |
.java |
java 根 |
api |
api |
SysUserApi.java |
.java |
java 根 |
api-impl |
api.impl |
SysUserApiImpl.java |
.java |
java 根 |
层过滤快速参考(SelectLayers 函数):
| flag 组合 | 输出层 |
|---|---|
默认(无 --with-api) |
6 层后端核心:po / mapper / mapper-xml / service / service-impl / query |
--with-api |
全 14 层 |
--sync-schema(无 --with-api) |
3 层:po / mapper-xml / query(6 层后端核心与「改表影响层」集合的交集) |
--sync-schema --with-api |
6 层:po / req-dto / resp-dto / mapper-xml / query / query-req-dto |
生成的全部代码不依赖任何目标工程预置类,拿到产物即可编译(中央组件依赖除外)。这是 v0.2.0 起的能力,此前曾要求目标工程预置 ApiConstants 与通用 PageQueryReqDto 两个公共类,现已消除:
ApiConstants→ 值内联:@FeignClient服务名与 API 基础路径由配置api:节(service-name/base-path,缺省从 base-package 末段派生)直接渲染进产物,不再 import 任何ApiConstants。- 通用
PageQueryReqDto→ 改签名消除:pageAll端点从 POST +@RequestBody PageQueryReqDto改为 GET + 两个@RequestParam(current/size),不再需要通用分页基类。注意:各表生成的XxxPageQueryReqDto(继承XxxQueryReqDto)是独立的表相关 DTO,不受影响。
v0.4.0 起
api/api-impl默认不生成(--with-api时才生成);即便生成,上述自包含性同样成立。
详情参见 README.md 的"自包含"节。
- 使用
code-learner技能可生成「带注释的逐文件 Go 讲解」。 - 使用
codetour-teacher技能可生成「交互式代码导览(CodeTour 格式)」,在 VS Code 中按站点点击高亮代码。 - 阅读
*_test.go文件——每个包都有测试,是了解函数契约的最佳方式。