rgin命令行工具--一键生成gin框架的开发脚手架

rgin命令行工具--一键生成gin框架的开发脚手架


前言

在使用 Gin 框架进行开发学习的过程中,脚手架的搭建是一个常见且必要的步骤。尽管可以提前准备一个简单的脚手架模板并通过复制粘贴来快速启动项目,但这种方式往往需要对模块名称(如 mod name)等关键命名进行手动修改。然而,由于项目名称可能与代码中的某些变量名重复,导致无法简单地通过全局搜索替换(如 Shift + Ctrl + F)来一次性完成所有修改,反而需要逐一甄别和调整,增加了不必要的繁琐操作。

鉴于 Gin 框架本身以简洁著称,并未提供一键生成项目模板的命令行工具,因此,基于上述痛点,我开发了一个可以根据个人开发习惯生成通用模板的命令行工具。该工具不仅能够快速生成符合 Gin 框架开发规范的项目结构,还能灵活适配不同的命名需求,在一定程度上提高了开发效率。

值得一提的是,本文所介绍的方法并不仅限于 Gin 框架的脚手架生成。你可以基于本文的思路和方法,开发适用于其他常用框架或脚手架的个性化命令行工具,从而为日常开发工作提供更多便利。通过这种方式,开发者可以根据自身需求定制专属的模板生成工具,进一步优化开发流程,提升工作效率。


0、快速使用

项目地址:Rosyrain/rgin at 0.0.1

如果有帮助到你,希望可以得到你的starヾ(≧▽≦*)o。

bash 复制代码
 go install github.com/rosyrain/rgin
 rgin #检查是否成功安装工具
 rgin create -n projectName #通过更改projectName去生成项目脚手架

生成的开发脚手架参考了七米老师的bluebell项目 架构如下(需要设置相关配置mysql/redis便可启动预留的示例):

go 复制代码
 ├─conf          //存放配置文件
 ├─controller    //句柄函数
 ├─dao           //数据库
 │  ├─mysql      //mysql
 │  └─redis      //redis
 ├─logger        //日志加载
 ├─logic         //逻辑处理函数
 ├─middlewares   //中间件,已配置jwt认证,令牌桶限流
 ├─models        //模型定义
 ├─pkg           //第三方库调用,jwt的具体相关功能实现,snowflask算法
 │  ├─jwt
 │  └─snowflask
 ├─router        //路由设置
 ├─settings      //配置加载
 ├─main.go       //启动入口,相关配置初始化
 ├─DOckerfile    //docker部署
 └─wait-for.sh   //docker部署时调用的验证脚本


1、项目初始化

本文将使用cobra库进行命令行工具的开发。相关cobra内容可在参考文献中查看。

bash 复制代码
 go install github.com/spf13/cobra-cli@latest
 cobra-cli init
 go mod init github.com/rosyrain/rgin  
 #Ps:此处不使用rgin作为mode名是为了方便后续通过go install实现工具的下载适配
 go mod tidy

如果提示cobra库不存在时,手动下载

bash 复制代码
 go get github.com/spf13/cobra


2、项目结构设计(预设计)

go 复制代码
 rgin/
 ├── cmd/                  // 命令行工具入口
     ├── create.go         // 子命令create定义
 │   └── root.go           // 根命令定义
 ├── internal/             // 内部实现(功能的具体实现)
 │   ├── generator/        // 代码生成逻辑
 │   │   └── generator.go
 │   ├── project/          // 项目结构定义
 │   │   └── project.go
 │   └── template/         // 模板文件加载
 |       ├──templates/     // 模板文件,放在此处是为了与embed进行适配
 |           ├──main.go.tmpl...
 │       └── template.go
 ├── main.go
 ├── go.mod
 ├── go.sum
 └── README.md


3、命令定义

①根命令

在cmd层可以进行相关的命令具体实现,项目初始化时会有一个根命令,具体设定在root.go文件下:

go 复制代码
 var rootCmd = &cobra.Command{
     Use:   "rgin",
     Short: "Generate the development scaffolding of the gin framework",
     Long: `Here's how to use it:
     go install https://github.com/rosyrain/rgin
     rgin create -n projectname`,
     // Uncomment the following line if your bare application
     // has an action associated with it:
     Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("==========\nWelcome to rgin,use `rgin create -n projectName` to create project.\n==========")
     },
 }

其中各个参数的含义如下:

  • Use: 命令名
  • Short:命令的简单功能介绍
  • Long:命令的具体功能描述及如何使用
  • Run:在使用命令时调用的功能函数

其他相关配置以及一些flags,args配置可跳转到参考文献自行学习,本文不作详细讲解

②子命令
csharp 复制代码
 cobra-cli add create  #创建子命令

此时可以在cmd层发现多了一个create.go的文件,create子命令相关的配置可在此处进行设置

go 复制代码
 var createCmd = &cobra.Command{
     Use:   "create",
     Short: "create template(by gin)",
     Long:  `rgin create -n projectname`,
     Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("create called")
        if err := generator.GenerateProject(projectName); err != nil {
           cobra.CheckErr(err)
        }
        fmt.Printf("===============\nSuccessfully created,projectName:%s\n===============", projectName)
     },
 }

在调用create子命令时,会调用Run参数下的func函数。然后我们通过generator.GenerateProject(projectName)函数进行具体的功能实现。

在此之前,我们可以对子命令添加一些配置(flags):

csharp 复制代码
 func init() {
     rootCmd.AddCommand(createCmd)
 ​
     createCmd.Flags().StringVarP(&projectName, "name", "n", "rginDemo", "The name of the create project(default is rginDemo)")  //为子命令添加flag
     createCmd.MarkFlagRequired("name")  //调用命令时,必须填写的flag
 }


4、具体功能实现

①project层

首先我们在project层进行简单的模型定义

go 复制代码
 type Project struct {
     Name    string  //projectName
     RootDir string  //根目录,默认是当前目录下生成
 }

确定需要创建的目录(此处可以省略,在代码生成时创建也可以)

csharp 复制代码
 var (
     // 创建子目录
     dirs = []string{
         "conf",
         "controller",
         "dao/mysql",
         "dao/redis",
         "logger",
         "logic",
         "middlewares",
         "models",
         "pkg/jwt",
         "pkg/snowflask",
         "router",
         "settings",
     }
 )

为project模型添加Create方法,具体实现的功能是预创建所需目录

go 复制代码
 func (p *Project) Create() error {
     // 创建项目根目录
     if err := os.Mkdir(p.RootDir, os.ModePerm); err != nil {
        return err
     }
 ​
     for _, dir := range dirs {
        if err := os.MkdirAll(filepath.Join(p.RootDir, dir), os.ModePerm); err != nil {
           return err
        }
     }
 ​
     return nil
 }

对外提供一个实例创建函数

go 复制代码
 func NewProject(name string) *Project {
     return &Project{
        Name:    name,
        RootDir: name,
     }
 }
②template层

使用embed进行静态文件嵌入

原因:在使用go install安装工具时,是去构建可执行二进制文件。而在编译的时候,默认只编译go文件,模板tmpl文件会无法正常调用。因此通过embed将模板文件嵌入二进制文件,保证能够正确调用模板文件。

go 复制代码
 //go:embed templates/*
 var templateFS embed.FS
 ​
 func init() {
     // 遍历检查嵌入的模板
     err := fs.WalkDir(templateFS, ".", func(path string, d fs.DirEntry, err error) error {
        if err != nil {
           return err
        }
        fmt.Println("Embedded file:", path)
        return nil
     })
     if err != nil {
        fmt.Println("embed files failed,err: ", err)
     }
 }

对外提供模板加载功能

go 复制代码
 func LoadTemplate(name string) (*template.Template, error) {
 ​
     //tmplPath := filepath.Join("templates", name) // 使用此方法会导致错误(暂未发现原因)
     tmplPath := "templates" + name
     fmt.Println("path: ", tmplPath)
     data, err := templateFS.ReadFile(tmplPath)
     if err != nil {
        return nil, err
     }
 ​
     return template.New(name).Parse(string(data))
 }

在templates中添加模板文件(以tmpl结尾即可,如config.yaml.tmpl)

其中通过 {{ .Name }}进行内容替换,此处的 .Name 是之前定义的project模型中的Name,后续在模板渲染时会自动替换。

有关模板的相关内容可在参考文献Template中跳转查看

yaml 复制代码
 name: "{{ .Name }}"
 mode: "dev"
 port: 8888
 version: "v0.0.1"
 start_time: "2025-02-24"
 machine_id: 1
 ​
 auth:
   jwt_expire: 8760
 ​
 log:
   level: "debug"
   filename: "{{ .Name }}.log"
   max_size: 200
   max_age: 30
   max_backups: 7
 ​
 mysql:
   host: "127.0.0.1"
   port: 3306
   user: "root"
   password: "123456"
   dbname: "{{ .Name }}"
   max_open_conns: 200
   max_idle_conns: 50
 ​
 redis:
   host: "127.0.0.1"
   port: 6379
   password: ""
   db: 0
   pool_size: 100
③generator层

定义要生成的相关文件(ps:版本文件路径无需添加前缀templates,已经在template层实现了这一步)

csharp 复制代码
 var (
     // 定义一个文件生成的配置结构
     fileTemplates = []struct {
        OutputPath string // 输出文件路径
        Template   string // 模板文件路径
     }{
        {"conf/config.yaml", "/conf/config.yaml.tmpl"},
 ​
        {"controller/code.go", "/controller/code.go.tmpl"},
        {"controller/request.go", "/controller/request.go.tmpl"},
        {"controller/response.go", "/controller/response.go.tmpl"},
        {"controller/validator.go", "/controller/validator.go.tmpl"},
        {"controller/user.go", "/controller/user.go.tmpl"},
         ... ...
         ... ...
        {"main.go", "/main.go.tmpl"},
        {"go.mod", "/go.mod.tmpl"},
        {"go.sum", "/go.sum.tmpl"},
        {"Dockerfile", "/Dockerfile.tmpl"},
        {"wait-for.sh", "/wait-for.sh.tmpl"},
     }
 )

对外暴露的功能函数(即在create命令下,具体功能实现时所调用的函数)

go 复制代码
 // create project template
 func GenerateProject(projectName string) (err error) {
     // create project struct
     // 创建项目结构
     proj := project.NewProject(projectName)
     //调用proj的Create方法,预创建目录
     if err = proj.Create(); err != nil {
        return fmt.Errorf("create project struct failed: %v", err)
     }
 ​
     // 生成文件
     if err = generateFiles(proj); err != nil {
        return fmt.Errorf("生成文件失败: %v", err)
     }
 ​
     return err
 }

生成代码文件的逻辑函数

go 复制代码
 func generateFiles(proj *project.Project) error {
     for _, fileConfig := range fileTemplates {
        // 从配置中提取模板路径和输出路径
        tmplPath := fileConfig.Template
        outputPath := fileConfig.OutputPath
 ​
        // 调用 generateFromTemplate 函数生成文件
        if err := generateFromTemplate(proj, tmplPath, outputPath); err != nil {
           return fmt.Errorf("failed to generate file %s: %w", outputPath, err)
        }
     }
     return nil
 }

依据模板生成代码文件的具体实现

go 复制代码
 func generateFromTemplate(proj *project.Project, tmplPath, outputPath string) error {
     // 调用template提供的方法,加载模板文件
     tmpl, err := template.LoadTemplate(tmplPath)
     if err != nil {
        return err
     }
     // 得到输出文件的路径
     outputFile := filepath.Join(proj.RootDir, outputPath)
     // 创建所需目录,确保存在
     if err := os.MkdirAll(filepath.Dir(outputFile), os.ModePerm); err != nil {
        return err
     }
     
     // 创建空文件
     file, err := os.Create(outputFile)
     fmt.Println("create file:", outputFile)
     if err != nil {
        return err
     }
     defer file.Close()
     
     // 将模板文件的内容渲染之后,加载到输出文件当中
     return tmpl.Execute(file, proj)
 }


5、go install配置

①推送项目至远程仓库(github)

工具实现功能,完成调试:go build构建文件,rgin.exe create -n demo或者./rgin create -n demo

通过构建二进制文件并正确的执行相关命令,最好将二进制文件复制到空白目录下进行测试,防止模板文件没有正确嵌入的问题。

通过git上传至公开的代码管理平台(如github),相关命令:

csharp 复制代码
 git init  #初始化git
 git add .
 git commit -m "project init"
 git remote add origin "https://github.com/rosyrain/rgin" #需要提前创建好rgin仓库
 git push -u origin main

如果出现下述问题,原因:本地仓库的分支名称与远程仓库的分支名称不匹配。GitHub 默认的主分支名称是 main,但你的本地仓库可能仍然使用旧的默认分支名称 master

vbnet 复制代码
 error: src refspec main does not match any error: failed to push some refs to 'https://github.com/rosyrain/rgin'

解决方法

perl 复制代码
 git branch #查看分支
 git branch -m master main #将master分支重命名为main分支
 git branch
 git push -u origin main #重新推送
②测试
bash 复制代码
 go install github.com/rosyrain/rgin
 rgin
 rgin create -n demo

ps:完整上传项目的所有代码,包括模板文件,go.mod,go.sum等文件。

通过go install安装的工具存储在GOPATH下,是可执行的二进制文件,如果不需要工具可手动删除二进制文件即可。


结语

通过该项目的完成,可以参考此方法去实现自己个人的脚手架生成工具。如果有问题或者建议,欢迎到项目的仓库中提issue。


项目地址:Rosyrain/rgin at 0.0.1

本文当中的代码可能会有残缺,各位如果直接复制可能无法运行,可以去我的 GitHub 仓库:rgin工具-一键生成gin框架开发脚手架https://github.com/Rosyrain/rgin。大家可以进行参考,提问,提建议。同时欢迎大家Follow/Star/Fork ,点个star再走吧( •̀ ω •́ )✧


参考文献

golang常用库包:cli命令行/应用程序生成工具-cobra使用 - 九卷 - 博客园

Template · Go语言中文文档

go:embed 用法详解:如何将静态资源文件打包进二进制文件中? - 阿小信的博客

相关推荐
newerp11 小时前
Go net/http 标准库基础
后端·程序员·go
26519405112648011 小时前
01-整体架构与高可用
go
程序员爱钓鱼13 小时前
Go if 判断详解
前端·后端·go
名字还没想好☜1 天前
Go 的 unsafe.Pointer 实战:零拷贝 []byte↔string 转换与三条铁律
开发语言·后端·golang·go·unsafe
阿里云云原生1 天前
阿里云联合 Datadog,补齐 Go 可观测性最后短板
云原生·go
可观测性用观测云1 天前
零码改造!Go 语言应用上报观测云完整最佳实践
go
斐波那契数列1 天前
参考react 实现一个golang gui
go
程序员爱钓鱼2 天前
Go 运算符详解
后端·面试·go
angryshan2 天前
goLang配置断点流程
go·bug
名字还没想好☜2 天前
Go 的 sync.Cond 实战:用条件变量做等待/通知,比忙轮询省 CPU
开发语言·数据库·后端·golang·go