Go语言利用TPL完成代码生成器——struct模型

一、TPL文件在Go语言中的概念

TPL文件通常指模板文件(Template File),在Go语言中主要用于HTML或其他文本的模板化处理。Go标准库提供了text/templatehtml/template两个包来支持模板功能。

1. 使用text/template处理TPL文件

text/template适用于普通文本模板的生成。以下是一个简单的例子:

package main

import (
    "os"
    "text/template"
)

func main() {
    tpl := `Hello, {{.Name}}! Today is {{.Day}}.`
    data := struct {
        Name string
        Day  string
    }{
        Name: "John",
        Day:  "Monday",
    }

    tmpl := template.Must(template.New("example").Parse(tpl))
    tmpl.Execute(os.Stdout, data)
}

2. 使用html/template处理TPL文件

html/template提供了HTML模板功能,具有自动转义等安全特性:

package main

import (
    "html/template"
    "os"
)

func main() {
    const tpl = `
    <html>
    <head><title>{{.Title}}</title></head>
    <body>
    <h1>{{.Header}}</h1>
    </body>
    </html>`

    data := struct {
        Title  string
        Header string
    }{
        Title:  "My Page",
        Header: "Welcome to Go Templates",
    }

    tmpl := template.Must(template.New("webpage").Parse(tpl))
    tmpl.Execute(os.Stdout, data)
}

3. 从文件加载TPL模板

可以将模板内容存储在单独的.tpl文件中,然后加载:

tmpl, err := template.ParseFiles("template.tpl")
if err != nil {
    panic(err)
}
err = tmpl.Execute(os.Stdout, data)

4. 模板语法

Go模板语法支持以下功能:

  • 变量输出:{{.VariableName}}
  • 条件语句:{{if .Condition}}...{{else}}...{{end}}
  • 循环:{{range .Items}}...{{end}}
  • 模板嵌套:{{template "name" .}}
  • 函数调用:{{.MethodName arg1 arg2}}

5. 最佳实践

将复杂的模板拆分为多个文件,使用template.ParseGlob批量加载:

tmpl := template.Must(template.ParseGlob("templates/*.tpl"))

对于Web应用,可以在程序启动时预编译所有模板,避免运行时开销。

二、Mysql中的DESCRIBE

1. DESCRIBE table_name 的功能

DESCRIBE table_name 是 MySQL 中用于查看表结构的快捷命令,等同于 SHOW COLUMNS FROM table_nameEXPLAIN table_name。该命令返回表的字段名、数据类型、是否允许为空、键类型、默认值及其他属性。

2. 输出字段说明

执行 DESCRIBE table_name 后,返回的结果包含以下列:

  • Field:列名(字段名)。
  • Type:列的数据类型(如 VARCHAR(255)INT)。
  • Null:是否允许 NULL 值(YESNO)。
  • Key:列的索引类型(如 PRI 主键、UNI 唯一键、MUL 普通索引)。
  • Default:列的默认值(若未设置则显示 NULL)。
  • Extra:额外信息(如 auto_increment)。

3. 实际应用示例

假设有一个表 users

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

执行 DESCRIBE users; 的输出可能为:

FieldTypeNullKeyDefaultExtra
idINTNOPRINULLauto_increment
usernameVARCHAR(50)NONULL
emailVARCHAR(100)YESUNINULL
created_atTIMESTAMPYESCURRENT_TIMESTAMP

4. 扩展用法

  1. 简写形式
    DESC table_nameDESCRIBE 的缩写,功能完全相同。

  2. 过滤输出字段
    可通过 SHOW FULL COLUMNS FROM table_name 获取更详细的信息(如字符集、注释等)。

  3. 信息模式查询
    information_schema.COLUMNS 表中查询更灵活的表结构信息:

    SELECT * FROM information_schema.COLUMNS 
    WHERE TABLE_NAME = 'users';
    

5. 注意事项

  • 需有表的查询权限才能执行 DESCRIBE
  • 在 MySQL 8.0 以上版本,DESCRIBEEXPLAIN 在查询计划分析时可能有不同用途,但用于查看表结构时等价。

三、Go语言代码生成器

1. 自定义TPL文件

  • struct.tpl
package gen

type {{.StructName}} struct {
{{- range $index,$value := .Result }}
    {{$value.Field}}  {{$value.Type}}  {{$value.JsonForm}}
{{- end}}
}
  • struct_gorm.tpl
package gen

type {{.StructName}}_Gorm struct {
{{- range $index,$value := .Result }}
    {{$value.Field}}  {{$value.Type}}  {{$value.Gorm}}
{{- end}}
}
  • message.tpl
syntax = "proto3";

message {{.MessageName}}{
{{- range $index,$value := .Result}}
    {{$value.Type}} {{$value.MessageField}}  = {{Add $index 1}};
{{- end}}
}

2. 数据库连接

工具通过 connectMysql 函数建立与 MySQL 数据库的连接,使用 GORM 作为 ORM 框架。以下是连接函数的实现:

// connectMysql 用于建立与MySQL数据库的连接。
// 返回值是一个*gorm.DB实例,用于后续的数据库操作。
func connectMysql() *gorm.DB {
	// 配置MySQL连接参数
	username := "root"
	password := "xxxxx"
	host := "127.0.0.1"
	port := 3309
	dbName := "xxxx"

	// 构造MySQL的DSN(Data Source Name),用于数据库连接
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", username, password, host, port, dbName)

	// 使用gorm库打开与MySQL的连接
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		log.Fatalf("连接数据库失败, error=%v", err)
	}
	return db
}

3. 结构体定义

// Result 结构体用于描述字段的多种标签和属性。
type Result struct {
	Field        string // 字段的原始名称
	MessageField string // 用于在错误消息中引用字段名称
	Type         string // 字段的数据类型
	Gorm         string // 字段在Gorm库中的标签名称
	Json         string // 字段在JSON序列化时的标签名称
	Form         string // 字段在处理表单数据时的标签名称
	JsonForm     string // 结合了JSON和表单处理的标签
}

// StructResult 结构体用于汇总结构体的验证结果。
type StructResult struct {
	StructName string    // 结构体的名称
	Result     []*Result // 字段的验证结果
}

4. 结构体生成

GenStruct 函数根据指定的数据库表生成对应的 Go 结构体代码。它使用 DESCRIBE SQL 查询获取表结构,并将字段名称转换为大驼峰命名,同时生成 GORM 和 JSON 标签。以下是具体实现:

// GenStruct 根据数据库表结构生成Go结构体代码。
// table: 数据库表名。
// structName: 生成的Go结构体名。
func GenStruct(table, structName string) {
	// 连接MySQL数据库。
	db := connectMysql()

	// 定义一个Result类型的切片来存储查询结果。
	var results []*Result
	// 执行SQL查询以获取表结构信息,并将结果扫描到results变量中。
	if err := db.Raw(fmt.Sprintf("DESCRIBE %s", table)).Scan(&results).Error; err != nil {
		// 如果查询失败,则记录错误信息并终止程序执行。
		log.Fatalf("查询表结构失败: %v", err)
	}

	// 遍历查询结果,处理每个字段以生成相应的Go结构体字段。
	for _, v := range results {
		// 保存原始字段名。
		field := v.Field
		// 将字段名转换为大驼峰命名法。
		v.Field = Name(field)
		// 根据数据库字段类型获取对应的Go类型。
		v.Type = getType(v.Type)
		// 生成JSON标签。
		v.Json = fmt.Sprintf("`json:\"%s\"`", field)
		// 生成包含form和json标签的字符串。
		v.JsonForm = fmt.Sprintf("`json:\"%s\" form:\"%s\"`", field, field)
		// 生成Gorm标签,指定数据库列名。
		v.Gorm = fmt.Sprintf("`gorm:\"column:%s\"`", field)
	}

	// 调用函数生成并保存Go结构体文件。
	generateStructFile(structName, results)
	// 调用函数生成并保存Gorm结构体文件。
	generateGormStructFile(structName, results)
}

5. 模板渲染

工具使用 Go 的 text/template 包来处理结构体和 Protobuf 消息的模板渲染。以下是生成 Go 结构体代码文件的实现:

// generateStructFile 生成结构体文件
// 该函数根据给定的结构体名称和结果集,生成一个Go语言结构体定义文件
// structName - 结构体的名称	
// results - 一个Result对象的切片,包含要生成的结构体的字段信息
func generateStructFile(structName string, results []*Result) {
    // 创建StructResult实例,封装结构体名称和字段结果集
    sr := StructResult{StructName: structName, Result: results}
    
    // 解析模板文件,用于生成结构体代码
    tmpl, err := template.ParseFiles("./struct.tpl")
    if err != nil {
        log.Fatalf("解析模板失败: %v", err)
    }

    // 构造文件路径,生成的文件位于./gen目录下,文件名基于结构体名称
    filePath := fmt.Sprintf("./gen/%s.go", strings.ToLower(structName))
    
    // 确保./gen目录存在,如果不存在则创建
    if err := ensureDir("./gen"); err != nil {
        log.Fatalf("创建目录失败: %v", err)
    }

    // 创建结构体定义文件
    file, err := os.Create(filePath)
    if err != nil {
        log.Fatalf("创建文件失败: %v", err)
    }
    defer file.Close()

    // 使用模板和结构体结果集填充文件,生成结构体定义
    if err := tmpl.Execute(file, sr); err != nil {
        log.Fatalf("执行模板失败: %v", err)
    }
}

// generateGormStructFile 根据结构体名称和查询结果生成GORM结构体文件。
// structName - 是结构体的名称,将用于生成文件名和结构体名。
// results - 是查询结果的切片,包含要生成结构体的字段信息。
func generateGormStructFile(structName string, results []*Result) {
    // 创建 StructResult 实例,封装结构体名称和查询结果。
    sr := StructResult{StructName: structName, Result: results}

    // 解析模板文件,用于生成结构体代码。
    tmpl, err := template.ParseFiles("./struct_gorm.tpl")
    if err != nil {
        // 如果解析模板失败,记录错误并退出。
        log.Fatalf("解析模板失败: %v", err)
    }

    // 根据结构体名称生成文件路径。
    filePath := fmt.Sprintf("./gen/%s_gorm.go", strings.ToLower(structName))
    file, err := os.Create(filePath)
    if err != nil {
        // 如果创建文件失败,记录错误并退出。
        log.Fatalf("创建文件失败: %v", err)
    }
    // 确保文件在函数结束后关闭。
    defer file.Close()

    // 使用解析的模板和结构体结果生成代码并写入文件。
    if err := tmpl.Execute(file, sr); err != nil {
        // 如果执行模板失败,记录错误并退出。
        log.Fatalf("执行模板失败: %v", err)
    }
}

6. 代码类型映射

工具内置了 getMessageType 和 getType 函数,用于将数据库字段类型映射到 Go 类型和 Protobuf 类型,确保生成的代码与数据库结构一致。以下是类型映射的实现:

// getType 根据数据库字段类型返回对应的Go类型。
func getType(t string) string {
	switch {
	case strings.Contains(t, "bigint"):
		return "int64"
	case strings.Contains(t, "varchar"), strings.Contains(t, "text"):
		return "string"
	case strings.Contains(t, "tinyint"):
		return "int"
	case strings.Contains(t, "int") && !strings.Contains(t, "bigint"):
		return "int"
	case strings.Contains(t, "double"), strings.Contains(t, "decimal"):
		return "float64"
	default:
		return "unknown"
	}
}

7. 命名转换

TFName 和 Name 函数用于将下划线命名转换为小驼峰和大驼峰命名,增强代码可读性。以下是实现:

// Name 将下划线分隔的名字转换为驼峰命名。
// 该函数接收一个字符串参数name,将其按'_下划线'进行分割,
// 并将每个分割后的部分首字母大写,最终合并为一个新的字符串返回。
// 这个函数主要用于将数据库列名或者其他以下划线分隔的字符串
// 转换为符合Go语言命名规范的驼峰命名。
func Name(name string) string {
    var sb strings.Builder
    names := strings.Split(name, "_")

    for _, part := range names {
        sb.WriteString(strings.Title(part)) // 每个部分大写
    }

    return sb.String()
}

8. 创建文件目录

// ensureDir 确保目录存在,如果不存在则创建。
func ensureDir(dir string) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		return os.Mkdir(dir, 0755)
	}
	return nil
}

9. 错误处理

在整个过程中,工具通过日志记录和错误处理,确保在出现问题时能够及时反馈,提升了代码的健壮性。每个函数都包含了错误检查,确保数据库连接、文件创建和模板执行等操作的成功。

10. 生成struct模型

// GenModel 生成struct模型
func GenModel(table, name string) {
	GenStruct(table, name)
}
// 调用GenStruct函数,用于验证结构体代码生成的正确性。
// 该测试用例通过调用GenStruct函数来生成一个名为"Member"的结构体代码。
func main() {
	GenModel("member", "Member")
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值