当前位置:首页 > 技术 > 正文内容

使用GORM自动生成Go结构体文件的工具

访客 技术 2026年7月24日 1

在Go开发中,我们经常需要根据数据库表结构生成对应的Go结构体。虽然网上有很多现成的包,但大多过于复杂或不符合我们的需求。本文介绍一个简单实用的工具,可以根据MySQL数据库表自动生成Go结构体文件。

package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
	"gorm.io/gorm"
	"gorm.io/driver/mysql"
)

var dbConnection *gorm.DB

func main() {
	var dbHost string
	var dbUser string
	var dbPassword string
	var dbName string
	var targetTable string
	var outputPackage string
	var dbPort string

	flag.StringVar(&dbHost, "host", "", "数据库主机地址")
	flag.StringVar(&dbPassword, "pwd", "", "数据库密码")
	flag.StringVar(&dbUser, "user", "", "数据库用户名")
	flag.StringVar(&dbName, "db", "", "数据库名称")
	flag.StringVar(&targetTable, "table", "", "目标表名")
	flag.StringVar(&dbPort, "port", "3306", "数据库端口")
	flag.StringVar(&outputPackage, "pkg", "", "输出包名")
	flag.Parse()

	dataSourceName := fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s?parseTime=True&loc=Local",
		dbUser,
		dbPassword,
		dbHost,
		dbPort,
		dbName,
	)
	
	var err error
	dbConnection, err = gorm.Open(mysql.Open(dataSourceName), &gorm.Config{})
	if err != nil {
		fmt.Printf("连接数据库失败: %v\n", err)
		return
	}

	type ColumnInfo struct {
		ColumnName string `gorm:"column:Column_Name"`
		DataType string `gorm:"column:Data_Type"`
		ColumnComment string `gorm:"column:Column_Comment"`
	}

	var columns []ColumnInfo
	packageHeader := fmt.Sprintf("package %s\n\n", outputPackage)
	structName := convertToPascalCase(targetTable)
	structStart := fmt.Sprintf("type %s struct {", structName)
	content := packageHeader + structStart

	query := fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_COMMENT as Column_Comment, DATA_TYPE as Data_Type FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s';", dbName, targetTable)
	result := dbConnection.Raw(query).Scan(&columns)
	
	if result.Error != nil {
		fmt.Printf("查询表结构失败: %v\n", result.Error)
		return
	}

	for _, column := range columns {
		field := fmt.Sprintf("\n\t%s %s `json:\"%s\"` // %s", 
			convertToPascalCase(column.ColumnName), 
			mapToGoType(column.DataType), 
			column.ColumnName, 
			column.ColumnComment)
		content += field
	}

	content += "\n}"
	tableNameMethod := fmt.Sprintf("\n\nfunc (%s) TableName() string {\n\treturn \"%s\"\n}", structName, targetTable)
	content += tableNameMethod

	outputFile := fmt.Sprintf("%s.go", structName)
	
	// 检查文件是否已存在
	if _, err := os.Stat(outputFile); err == nil {
		// 文件存在且不为空,则不允许覆盖
		if file, _ := os.Open(outputFile); file != nil {
			defer file.Close()
			if stat, _ := file.Stat(); stat.Size() > 0 {
				fmt.Println("目标文件已存在且不为空,请先删除或重命名")
				return
			}
		}
	}

	file, err := os.Create(outputFile)
	if err != nil {
		fmt.Printf("创建文件失败: %v\n", err)
		return
	}
	defer file.Close()

	_, err = io.WriteString(file, content)
	if err != nil {
		fmt.Printf("写入文件失败: %v\n", err)
		return
	}

	fmt.Printf("成功生成文件: %s\n", outputFile)
}

// 将下划线命名转换为帕斯卡命名
func convertToPascalCase(input string) string {
	var nextUpper bool
	result := ""
	
	for i, char := range input {
		if i == 0 {
			result += strings.ToUpper(string(char))
			continue
		}
		
		if string(char) == "_" {
			nextUpper = true
			continue
		}
		
		if nextUpper {
			result += strings.ToUpper(string(char))
			nextUpper = false
		} else {
			result += string(char)
		}
	}
	
	return result
}

// 将MySQL数据类型映射为Go类型
func mapToGoType(mysqlType string) string {
	typeIndex := strings.Index(mysqlType, "(")
	if typeIndex < 0 {
		typeIndex = len(mysqlType) - 1
	}

	baseType := mysqlType[:typeIndex]
	
	typeMapping := map[string]string{
		"tinyint": "int64",
		"smallint": "int64",
		"mediumint": "int64",
		"int": "int64",
		"bigint": "int64",
		"float": "float64",
		"double": "float64",
		"decimal": "string",
		"bit": "string",
		"year": "string",
		"time": "string",
		"date": "string",
		"datetime": "string",
		"timestamp": "string",
		"char": "string",
		"varchar": "string",
		"tinytext": "string",
		"text": "string",
		"mediumtext": "string",
		"longtext": "string",
		"enum": "string",
	}
	
	if goType, exists := typeMapping[baseType]; exists {
		return goType
	}
	
	return "string"
}

使用示例:

go run main.go -host 127.0.0.1 -port 3306 -user test -pwd password -db mydb -table users -pkg models

该工具会根据指定的数据库连接信息和表名,在当前目录下生成一个Go结构体文件,文件名与表名对应(首字母大写),并包含指定的包名和表名方法。

工具特点:

  1. 简单易用,无需复杂配置
  2. 支持命令行参数配置
  3. 自动将数据库字段类型转换为Go类型
  4. 自动添加JSON标签和注释
  5. 防止覆盖已存在的非空文件

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。