golang轻量级框架-Gin入门

本来自己打算继续学下beanFactory源码的,但是放假了自己也没什么精神,看源码又要求注意力很集中,所以想着看点简单点的内容吧,然后就想到了golang的另一个框架-Gin。假期过后可能就要开启加班生活了,不是很开心,昨天收到老大邮件,我原来项目组基本上解散了,人员分到了不同项目组,而我到了ebay项目组去做微服务(如果不用加班我还是期待的),自己浪了一个月也该收收心了。还是回归正题,gin框架和前面学习的beego框架都是比较流行的框架,但是beego比较传统,模块多功能全,而gin可以看作是一个单独模块的框架,官方介绍说的是:Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 martini-like API 框架, 由于 httprouter,速度提高了近 40 倍。 如果你是性能和高效的追求者, 那么你会爱上 Gin。自己感觉gin更像是beego中的controller,主要针对用户的request和response。gin官网,个人感觉文档稍显粗糙,不过胜在支持中文,还是很良心的。

一、安装和开始

要想使用gin必须要下载和安装它,切换到自己的工作空间,执行go命令

go get -u github.com/gin-gonic/gin

但是因为网络问题可能会失败,实在不行就直接通过github下载也可以。安装好之后就可以直接使用了,打开ide创建一个新的项目helloGin,创建main.go

func main()  {    // Engin    router := gin.Default()    //router := gin.New()    router.GET("/hello", func(context *gin.Context) {        log.Println(">>>> hello gin start <<<<")        context.JSON(200,gin.H{            "code":200,            "success":true,        })    })    // 指定地址和端口号    router.Run("localhost:9090")

在main函数里面首先通过调用gin.Default()函数返回的是一个Engin指针,Engin代表的是整个框架的一个实例,它包含了多路复用、中间件和配置的设置,其实就是封装了我们需要的内容。一般创建Engin都是使用Default()或者New(),当然Default()本身内部也是调用的New()函数。接着调用Engin的GET方法,这个方法两个参数,一个是相对路径,一个是多个handler,即针对用户一个请求地址,我可以指定多个handler来处理用户请求。但是一般情况下我们都是一个handler处理一个请求。上面的代码里使用了一个匿名函数处理"/hello"请求。然后以JSON格式的数据响应用户请求,这个方法有两个参数,第一个是状态,第二个是结果。我这里直接指定200,表示成功,或者也可以用http包的常量值http.StatusOK;gin.H其实是一个map的数据结构,然后将其转成json格式输出。最后是router.Run("localhost:9090"),这个方法是指定服务的主机和端口号,不过一般直接指定端口号就行了。下面启动项目,并访问"localhost:9090/hello",访问结果如下图所示:

图-1.png二、创建demo

接下来创建项目来学习gin的使用,主要就是controller的使用,即将用户请求和handler进行映射,然后获取不同方式请求参数。构建项目结构如下所示

图-2.png

config主要是配置相关的文件;controller包主要放handler;database包数据库相关代码,因为我这里没有用ORM框架,所以只是数据库连接的代码;main包下只有main.go一个文件;model就是数据模型,即自己定义的一些结构体;static下放置的是静态文件;template包下是html页面。

刚才上面处理"hello"请求使用的是一个匿名函数,下面为非匿名函数来处理,代码修改成下面:

func main()  {    // Engin    router := gin.Default()    router.GET("/hello", hello) // hello函数处理"/hello"请求    // 指定地址和端口号    router.Run(":9090")}func hello(context *gin.Context) {    println(">>>> hello function start <<<<")        context.JSON(http.StatusOK,gin.H{        "code":200,        "success":true,    })}

这样好了一点点,但是想想spring controller,一般会在类上加上一个@requestMapping注解,然后方法上也会加上一个@requestMapping注解,之所以在类上加@requestMapping主要是这个controller处理的是同一类型问题,比如和用户相关的controller,请求路径都是/user/....,同样gin也支持,这就是路由组,我们看下官方文档的示例:

func main() {    router := gin.Default()    // Simple group: v1    v1 := router.Group("/v1")    {        v1.POST("/login", loginEndpoint)        v1.POST("/submit", submitEndpoint)        v1.POST("/read", readEndpoint)    }    // Simple group: v2    v2 := router.Group("/v2")    {        v2.POST("/login", loginEndpoint)        v2.POST("/submit", submitEndpoint)        v2.POST("/read", readEndpoint)    }    router.Run(":8080")}

根据这个事例,将代码重新构建,这里构建两个路由组。并且在controller包下新建了UserController和FileController文件,分别处理不同路由组请求,分别作一些不同的操作,另外将每个路由对应的函数按照路由组进行划分,另外有两个静态的html页面,做form表单提交的操作。gin提供了两个方法用户加载静态html,即LoadHTMLGlob()或LoadHTMLFiles(),第一个方法制定一个通配符路径即可,而后面的方法则是需要指定所有需要加载的html文件名称。修改后代码如下:

func main()  {    // Engin    //router := gin.Default()    router := gin.New()        // 加载html文件,即template包下所有文件    router.LoadHTMLGlob("template/*")    router.GET("/hello", hello)    // 路由组    user := router.Group("/user")    {   // 请求参数在请求路径上        user.GET("/get/:id/:username",controller.QueryById)        user.GET("/query",controller.QueryParam)        user.POST("/insert",controller.InsertNewUser)        user.GET("/form",controller.RenderForm)// 跳转html页面        user.POST("/form/post",controller.PostForm)        //可以自己添加其他,一个请求的路径对应一个函数        // ...    }    file := router.Group("/file")    {        // 跳转上传文件页面        file.GET("/view",controller.RenderView) // 跳转html页面        // 根据表单上传        file.POST("/insert",controller.FormUpload)        file.POST("/multiUpload",controller.MultiUpload)        // base64上传        file.POST("/upload",controller.Base64Upload)    }    // 指定地址和端口号    router.Run(":9090")}

关于获取用户请求参数我还是写了几种情况,一是传统的URL查询参数,例如:localhost:9090/user/query?id=2&name=hello;另外一种就是URL路径参数,例如localhost:9090/user/2/hello(也是id=2,name=hello)。上面这两种是get请求,post请求我也写了两种形式,一种就是传统的form表单提交,另外就是json格式参数提交,等下通过代码看下。下面是UserController的代码内容:

func init()  {    log.Println(">>>> get database connection start <<<<")    db = database.GetDataBase()}// localhost:9090/user/query?id=2&name=hellofunc QueryParam(context *gin.Context) {    println(">>>> query user by url params action start <<<<")    id := context.Query("id")    name := context.Request.URL.Query().Get("name")    var u model.User    context.Bind(&u)    println(u.Username)    rows := db.QueryRow("select username,address,age,mobile,sex from t_user where id = $1 and username = $2",id,name)    var user model.User    err := rows.Scan(&user.Username,&user.Address,&user.Age,&user.Mobile,&user.Sex)    checkError(err)    checkError(err)    context.JSON(200,gin.H{        "result":user,    })}// localhost:9090/user/get/2/hellofunc QueryById (context *gin.Context) {    println(">>>> get user by id and name action start <<<<")    // 获取请求参数    id := context.Param("id")    name := context.Param("username")    // 查询数据库    rows := db.QueryRow("select username,address,age,mobile,sex from t_user where id = $1 and username = $2",id,name)    var user model.User    err := rows.Scan(&user.Username,&user.Address,&user.Age,&user.Mobile,&user.Sex)    checkError(err)    context.JSON(200,gin.H{        "result":user,    })}// json格式数据func InsertNewUser (context *gin.Context) {    println(">>>> insert controller action start <<<<")    var user model.User    // 使用ioutile读取二进制数据    //bytes,err := ioutil.ReadAll(context.Request.Body)    //if err != nil {    //  log.Fatal(err)    //}    //err = json.Unmarshal(bytes,&user)    // 直接将结构体和提交的json参数作绑定    err := context.ShouldBindJSON(&user)    // 写入数据库    res,err := db.Exec("insert into t_user (username,sex,address,mobile,age) values ($1,$2,$3,$4,$5)",        &user.Username,&user.Sex,&user.Address,&user.Mobile,&user.Age)    var count int64    count,err = res.RowsAffected()    checkError(err)    if count != 1 {        context.JSON(200,gin.H{            "success":false,        })    } else {        context.JSON(200,gin.H{            "success":true,        })    }}// form表单提交func PostForm(context *gin.Context) {    println(">>>> bind form post params action start <<<<")    var u model.User        // 绑定参数到结构体    context.Bind(&u)    res,err := db.Exec("insert into t_user (username,sex,address,mobile,age) values ($1,$2,$3,$4,$5)",        &u.Username,&u.Sex,&u.Address,&u.Mobile,&u.Age)    var count int64    count,err = res.RowsAffected()    checkError(err)    if count != 1 {        context.JSON(200,gin.H{            "success":false,        })    } else {        //context.JSON(200,gin.H{        //  "success":true,        //})        // 重定向        context.Redirect(http.StatusMovedPermanently,"/file/view")    }}// 跳转htmlfunc RenderForm(context *gin.Context) {    println(">>>> render to html action start <<<<")    context.Header("Content-Type", "text/html; charset=utf-8")    context.HTML(200,"insertUser.html",gin.H{})}func checkError(e error) {    if e != nil {        log.Fatal(e)    }}

UserController里面定义一个init方法,主要获取数据库连接,一边后面的函数对数据库进行操作。在QueryParam函数中,获取URL查询参数其实用多种方法,一种直接使用context.Query("参数名称"),另外就是context.Request.URL.Query().Get("参数名称"),但是明显第二个更麻烦一点。此外还有一种就是将参数绑定到结构体,context.Bind()或者context.ShouldBind()或者ShouldBindQuery(),然后对结构体进行操作就行了,需要注意一点就是ShouldBindQuery()只能绑定GET请求的查询参数,POST请求不行。其实使用哪种方式还是看个人习惯,参数少的话感觉第一种更直观一些。QueryById函数获取的是URL路径参数,和QueryParam获取方法不同,可以通过context.Param("参数名称")获取,后来看gin文档,发现也提供了一种参数绑定的方法,即context.ShouldBindUri(),这个方法也会把结构体和URL路径参数做一个绑定。InsertNewUser函数,获取的是提交的JSON格式参数,使用rest client可以模拟,获取参数也不止一种,可以使用比较基础的方法获取,即使用ioutil.ReadAll(context.Request.Body),读取字节流,然后使用go内置的json库将数据绑定到结构体。最简单方法就是调用ShouldBindJSON(),将用户提交的JSON参数绑定结构体。PostForm函数就是一个传统的form表单提交,使用context.Bind()或者context.ShouldBind()就好了。关于Bind和ShouldBind,其实这两个方法基本上都是一样的,根据具体的请求头选择不同绑定引擎去处理,比如用户请求的Content-Type为"application/json",那么就由JSON的绑定引擎处理,如果为为"application/xml",就由XML绑定引擎处理。这两个方法的差别在于ShouldBind方法不会将response状态值设为400,当请求的json参数无效的时候,即请求参数无法绑定到结构体。RenderForm函数主要是跳转到html页面,当时这里遇到一个问题,就是context.HTML方法,指定具体html页面,因为main函数使用时是router.LoadHTMLGlob("template/*"),我觉得可以理解指定了具体html的前缀,所以跳转时只需要html的相对template的路径即可。

FileController主要是处理文件上传,其实也没什么特别内容,无非就是单个上传还是多个上传的问题,另外就是使用base64上传图片。代码如下:

const BASE_NAME = "./static/file/"func RenderView (context *gin.Context) {    println(">>>> render to file upload view action start <<<<")    context.Header("Content-Type", "text/html; charset=utf-8")    context.HTML(200,"fileUpload.html",gin.H{})}// 单个文件上传func FormUpload (context *gin.Context) {    println(">>>> upload file by form action start <<<<")    fh,err := context.FormFile("file")    checkError(err)    //context.SaveUploadedFile(fh,BASE_NAME + fh.Filename)    file,err := fh.Open()    defer file.Close()    bytes,e := ioutil.ReadAll(file)    e = ioutil.WriteFile(BASE_NAME + fh.Filename,bytes,0666)    checkError(e)    if e != nil {        context.JSON(200,gin.H{            "success":false,        })    } else {        context.JSON(200,gin.H{            "success":true,        })    }}// 多个文件上传func MultiUpload(context *gin.Context) {    println(">>>> upload file by form action start <<<<")    form,err := context.MultipartForm()    checkError(err)    files := form.File["file"]    var er error    for _,f := range files {        // 使用gin自带保存文件方法        er = context.SaveUploadedFile(f,BASE_NAME + f.Filename)        checkError(err)    }    if er != nil {        context.JSON(200,gin.H{            "success":false,        })    } else {        context.JSON(200,gin.H{            "success":true,        })    }}func Base64Upload (context *gin.Context) {    println(">>>> upload file by base64 string action start <<<<")    bytes,err := ioutil.ReadAll(context.Request.Body)    if err != nil {        log.Fatal(err)    }    strs := strings.Split(string(bytes),",")    head := strs[0]    body := strs[1]    println(head + " | " + body)    start := strings.LastIndex(head,"/")    end := strings.LastIndex(head,";")    tp := head[start + 1:end]    err = ioutil.WriteFile(BASE_NAME + strconv.Itoa(time.Now().Nanosecond()) + "." + tp,[]byte(body),0666)    checkError(err)    //bys,err := base64.StdEncoding.DecodeString(string(bytes))    //err = ioutil.WriteFile("./static/file/" + strconv.Itoa(time.Now().Nanosecond()),bys,0666)    if err != nil {        context.JSON(200,gin.H{            "success":false,        })    } else {        context.JSON(200,gin.H{            "success":true,        })    }}

FormUpload函数处理单个文件上传,先从context.FormFile("file")获取文件,获取到的是一个FileHeader指针,FileHeader封装了文件内容、名称、类型、大小等信息,结构如下:

type FileHeader struct {    Filename string    Header   textproto.MIMEHeader    Size     int64    content []byte    tmpfile string}

保存文件可以直接使用SaveUploadedFile方法,也可以使用ioutil相关方法进行保存。MultiUpload多文件上传,先通过context.MultipartForm()获取Form对象,然后根据参数名获取到多个FileHeader指针,接下去保存文件和单个上传是一样的。Base64Upload函数本来是想通过使用base64上传图片,函数内先获取整个字符串,然后分割成head和body,然后判断图片类型,最后使用ioutil.WriteFile保存文件,但是实际操作好像出了点问题,文件保存到本地打开显示内容丢失,不知道是怎么回事。

三、总结

当然gin内容不止这些,还有一些中间件的内容也是值得一看的,比如BasicAuth、Logger等等,但是总感觉gin似乎太轻了一点,基本上就是一个MVC框架,还是要结合其他框架使用。beego感觉更好一些,但是MVC这部分好像gin更强大点,总之都很优秀吧,毕竟GitHub上star那么多。今天的学习就到这里了,本次学习的代码已经上传到我的GitHub,我已经很久没有提交过代码了.......

Go语言gin框架从入门到精通

1. 简介

1.1. 介绍

Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范

1.2. 安装

使用Mod依赖管理工具,在项目根目录执行 :

go mod init

然后项目根目录下会生成 go.mod文件,修改文件添加gin相关依赖

require github.com/gin-gonic/gin v1.6.3

运行项目时会自动下载相关依赖。

1.3. 第一个程序Hello World!

package mainimport (    "github.com/gin-gonic/gin"    "net/http")func main() {    //1.创建路由    r := gin.Default()    //2.绑定路由规则,执行的函数    r.GET("/", func(context *gin.Context) {        context.String(http.StatusOK, "Hello World!")    })    //3.监听端口,默认8080    r.Run(":8080")}

编译运行,项目会自动下载gin依赖。在浏览器输入 http://127.0.0.1:8080,出现以下页面说明运行成功:

Go语言gin框架从入门到精通

2 Gin的路由

2.1基本路由

gin 框架中采用的路由库是基于httprouter做的地址为:https://github.com/julienschmidt/httprouter
func main() {    r := gin.Default()    r.GET("/", func(context *gin.Context) {        context.String(http.StatusOK, "Hello")    })    r.POST("/xxxpost",func())    r.PUT("/xxxput",func())    r.Run(":8080")}

2.1 Restful风格的API

gin支持Restful风格的API即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化",是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作。如:

1.获取文章 /blog/getXxx Get blog/Xxx

2.添加 /blog/addXxx POST blog/Xxx

3.修改 /blog/updateXxx PUT blog/Xxx

4.删除 /blog/delXxxx DELETE blog/Xxx

2.2 API参数

可以通过Context的Param方法获取API参数localhost:8080/xxx/you
func main() {    r := gin.Default()    r.GET("user/:name/*action", func(context *gin.Context) {        name := context.Param("name")        action := context.Param("action")        fmt.Println(action)        //  截取/        action = strings.Trim(action, "/")        context.String(http.StatusOK, name+" is "+action)    })    r.Run(":8080")}

Go语言gin框架从入门到精通

2.3 URL参数

URL参数可以通过DefaultQuery()或Query()方法获取DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串

go func main() { r := gin.Default() r.GET("user", func(context *gin.Context) { //指定默认值 //http://127.0.0.1/user name := context.DefaultQuery("name", "you") context.String(http.StatusOK, fmt.Sprintf("hello %s", name)) }) //默认为:8080 r.Run() }

编译,请求输出如下:

不带参数,显示默认参数:

Go语言gin框架从入门到精通

带参数 name=thinks:

Go语言gin框架从入门到精通

2.4 表单参数

表单传输为post请求,http常见的传输格式为四种:application/jsonapplication/x-www-form-urlencodedapplication/xmlmultipart/form-data表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数

Html代码如下:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>Document</title></head><body><form action="http://127.0.0.1:8080/form" method="post">    用户名:<input type="text" name="username" placeholder="请输入你的用户名"> <br>    密   码:<input type="password" name="password" placeholder="请输入你的密码"> <br>    <input type="submit" value="提交"></form></body></html>

Go语言代码如下:

func main() {    r := gin.Default()    r.POST("/form", func(context *gin.Context) {        types := context.DefaultPostForm("type", "post")        username := context.PostForm("username")        password := context.PostForm("password")        context.String(http.StatusOK, fmt.Sprintf("username:%s , password:%s , types:%s", username, password, types))    })    r.Run()}

编译请求输出:

Go语言gin框架从入门到精通

Go语言gin框架从入门到精通

2.5 单文件上传

multipart/form-data格式用于文件上传gin文件上传与原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中

代码如下:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>单文件上传</title></head><body><form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">    上传文件:<input type="file" name="file">    <input type="submit" value="提交"></form></body></html>func main() {    r := gin.Default()    //限制上传文件大小  8M    r.MaxMultipartMemory = 8 << 20    r.POST("/upload", func(context *gin.Context) {        file, err := context.FormFile("file")        if err != nil {            context.String(500, "上传图片出错")        }        // c.JSON(200, gin.H{"message": file.Header.Context})        context.SaveUploadedFile(file, file.Filename)        context.String(http.StatusOK, file.Filename)    })    r.Run()}

运行效果如下:

Go语言gin框架从入门到精通

Go语言gin框架从入门到精通

2.6 多文件上传

HTML页面加入multiple关键字

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>多文件上传</title></head><body><form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">    上传文件:<input type="file" name="files" multiple>    <input type="submit" value="提交"></form></body></html>

go语言代码

func main() {    // 创建路由    r := gin.Default()    // 限制表单上传大小8MB,默认为32MB    r.MaxMultipartMemory = 8 << 20    r.POST("/upload", func(context *gin.Context) {        form, err := context.MultipartForm()        if err != nil {            context.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))        }        //获取所有文件        files := form.File["files"]        //遍历所有文件        for _, file := range files {            if err := context.SaveUploadedFile(file, file.Filename); err != nil {                context.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))                return            }        }        context.String(200, fmt.Sprintf("upload ok %d files", len(files)))    })    r.Run(":8080")}

效果展示:

Go语言gin框架从入门到精通

Go语言gin框架从入门到精通

2.7 routes group

routes group是为了管理一些相同的URL

举个例子:

func main() {    //创建路由    r := gin.Default()    //路由组1,处理get请求    v1 := r.Group("/v1")    {        v1.GET("/login", login)        v1.GET("/submit", submit)    }    v2 := r.Group("/v2")    {        v2.POST("/login", login)        v2.POST("/submit", submit)    }    r.Run(":8080")}func login(c *gin.Context) {    name := c.DefaultQuery("name", "jack")    c.String(200, fmt.Sprintf("hello %s\n", name))}func submit(c *gin.Context) {    name := c.DefaultQuery("name", "lily")    c.String(200, fmt.Sprintf("hello %s\n", name))}

在浏览器中分别输入:

http://127.0.0.1:8080/v1/login?name=you

http://127.0.0.1:8080/v1/submit?name=you

输出 hello you

使用windows自带的curl工具进行post请求,在cmd中输入

curl http://127.0.0.1:8080/v2/login -X POST

输出 hello jack

2.8 路由拆分与注册

2.8.1 基本的路由注册

下面最基础的gin路由注册方式,适用于路由条目比较少的简单项目或者项目demo。以上列子都是基本路由注册

func helloHandler(c *gin.Context) {    c.JSON(http.StatusOK, gin.H{        "message": "Hello www.topgoer.com!",    })}func main() {    r := gin.Default()    r.GET("/topgoer", helloHandler)    if err := r.Run(); err != nil {        fmt.Println("startup service failed, err:%v\n", err)    }}

2.8.2 路由拆分成单独的文件或包

当项目的规模增大后就不太适合继续在项目的main.go文件中去实现路由注册相关逻辑了,我们会倾向于把路由部分的代码都拆分出来,形成一个单独的文件或包。

我我们在routers.go文件中定义并注册路由信息:

package mainimport (    "github.com/gin-gonic/gin"    "net/http")func helloHandler(c *gin.Context) {    c.JSON(http.StatusOK, gin.H{        "message": "Hello Gin",    })}func setupRouter() *gin.Engine {    r := gin.Default()    r.GET("/user", helloHandler)    return r}

此时main.go中调用上面定义好的setupRouter函数:

func main() {    r := setupRouter()    if err := r.Run(); err != nil {        fmt.Println("startup service failed, err:%v\n", err)    }}

此时的目录结构:

LearnGin├── go.mod├── go.sum├── main.go└── routers.go

把路由部分的代码单独拆分成包的话也是可以的,拆分后的目录结构如下:

LearnGin├── go.mod├── go.sum├── main.go└── routers    └── routers.go

routers/routers.go 需要注意此时setupRouter需要改成首字母大写:

// SetupRouter 配置路由信息func SetupRouter() *gin.Engine {    r := gin.Default()    r.GET("/topgoer", helloHandler)    return r}

2.8.3 路由拆分成多个文件

当我们的业务规模继续膨胀,单独的一个routers文件或包已经满足不了我们的需求了,

func SetupRouter() *gin.Engine {    r := gin.Default()    r.GET("/user", helloHandler)    r.GET("/login", xxHandler1)    ...    r.GET("/register", xxHandler30)    return r}

因为我们把所有路由注册都写在一个SetupRouter函数中的话就会太复杂了。

我们可以分开定义多个路由文件,例如

LearnGin├── go.mod├── go.sum├── main.go└── routers    ├── blog.go    └── shop.go

routers/shop.go中添加一个LoadShop的函数,将shop相关的路由注册到指定的路由器

package routersimport "github.com/gin-gonic/gin"func LoadShop(e *gin.Engine)  {    e.GET("/hello", helloHandler)    e.GET("/goods", goodsHandler)    e.GET("/checkout", checkoutHandler)}func helloHandler(c *gin.Context)  {}func goodsHandler(c*gin.Context)  {}func checkoutHandler(c*gin.Context)  {}

routers/blog.go中添加一个LoadBlog的函数,将blog相关的路由注册到指定的路由器

package routersimport "github.com/gin-gonic/gin"func LoadBlog(e *gin.Engine) {    e.GET("/post", postHandler)    e.GET("/comment", commentHandler)}func postHandler(c *gin.Context) {}func commentHandler(c *gin.Context) {}

在main函数中实现最终的注册逻辑如下:

func main() {    r := gin.Default()    routers.LoadBlog(r)    routers.LoadShop(r)    if err := r.Run(); err != nil {        fmt.Println("startup service failed, err:%v\n", err)    }}

2.8.4 路由拆分到不同的APP

有时候项目规模实在太大,那么我们就更倾向于把业务拆分的更详细一些,例如把不同的业务代码拆分成不同的APP。

因此我们在项目目录下单独定义一个app目录,用来存放我们不同业务线的代码文件,这样就很容易进行横向扩展。大致目录结构如下:

LearnGin├── app│   ├── blog│   │   ├── handler.go│   │   └── router.go│   └── shop│       ├── handler.go│       └── router.go├── go.mod├── go.sum├── main.go└── routers    └── routers.go

其中app/blog/router.go用来定义post相关路由信息,具体内容如下:

func Routers(e *gin.Engine) {    e.GET("/post", postHandler)    e.GET("/comment", commentHandler)}

app/shop/router.go用来定义shop相关路由信息,具体内容如下:

func Routers(e *gin.Engine) {    e.GET("/goods", goodsHandler)    e.GET("/checkout", checkoutHandler)}

routers/routers.go中根据需要定义Include函数用来注册子app中定义的路由,Init函数用来进行路由的初始化操作:

type Option func(*gin.Engine)var options = []Option{}// 注册app的路由配置func Include(opts ...Option) {    options = append(options, opts...)}// 初始化func Init() *gin.Engine {    r := gin.New()    for _, opt := range options {        opt(r)    }    return r}

main.go中按如下方式先注册子app中的路由,然后再进行路由的初始化:

func main() {    // 加载多个APP的路由配置    routers.Include(shop.Routers, blog.Routers)    // 初始化路由    r := routers.Init()    if err := r.Run(); err != nil {        fmt.Println("startup service failed, err:%v\n", err)    }}