GO语言基础之Gin框架入门

232 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

Gin框架快速入门

一、gin框架介绍

Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍,是GO语言中最流行的Web框架

gin的安装

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

将gin导入到代码中

import "github.com/gin-gonic/gin"

注:go语言应在1.13版本以上

二、第一个gin程序

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    // GET:请求方式;/hello:请求的路径
    // 当客户端以GET方法请求/hello路径时,会执行后面的函数
    //为方便,此处用匿名函数
    r.GET("/hello", func(c *gin.Context) {
        // c.JSON:返回JSON格式的数据
        // code:返回码
        c.JSON(200, gin.H{
            "message": "Hello world!",
        })
    })
    // 启动HTTP服务,默认在8080端口启动服务
    r.Run()
}
​

将上面的代码编译并执行,然后到浏览器打开127.0.0.1:8080/hello,可以看到一串JSON字符串

image-20230207104847108

同时,我们可以在运行窗口中看到返回码、url路径、get方法、请求时间

image-20230207105104180

三、gin路由

路由引擎

在gin框架中, 路由引擎 是一个 一个结构体,其中包含了路由组、中间件、页面渲染接口、等相关内容

engine1 = gin.Default() // 创建默认的 路由引擎
engine2 = gin.New() // 创建一个新的 路由引擎
  • gin.Default其实也使用gin.New()创建engine实例,但是会默认使用Logger和Recovery中间件
  • Logger是负责进行打印并输出日志的中间件,方便开发者进行程序调试
  • Recovery中间件的作用是如果程序执行过程中遇到panic中断了服务,则Recovery会恢复程序执行,并返回服务器500内部错误

1.普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})

2.路由组

func main() {
    r := gin.Default()
    userGroup := r.Group("/user")
    {
        userGroup.GET("/index", func(c *gin.Context) {...})
        userGroup.GET("/login", func(c *gin.Context) {...})
        userGroup.POST("/login", func(c *gin.Context) {...})
​
    }
    shopGroup := r.Group("/shop")
    {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        shopGroup.GET("/cart", func(c *gin.Context) {...})
        shopGroup.POST("/checkout", func(c *gin.Context) {...})
    }
    r.Run()
}
​

嵌套路由组

func ActiveRouterList(e *gin.Engine) {
​
    group := e.Group("/aaa")
    { 
        group.GET("/b1", new(controller.Testing).HandGetTest)
        group.GET("/b2", new(controller.Testing).HandGetTest)
​
        group2 := group.Group("b3")
        {
            group2.GET("/c1", new(controller.Testing).HandGetTest)
            group2.GET("/c2", new(controller.Testing).HandGetTest)
        }
    }
​
    // 路由组访问地址: /aaa/b1   和  /aaa/b3/c1 
}
​

3.静态路由

为静态文件指定固定的访问路径

r.Static("static", "./static")

在该项目中,需要创建一个static文件夹,来保存静态文件

四、中间件

gin框架允许开发者在 处理请求 的过程中,加入用户自己的 钩子函数。这个 钩子函数 就是 中间件。 中间件适合处理一些 公共的 业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等

中间件的定义

一般中间件都建议定义在middleware包中,方便管理

package middleware
​
import (
    "crypto/sha1"
    "encoding/hex"
    "github.com/gin-gonic/gin"
)
​
func SHA1(s string) string {
​
    o := sha1.New()
​
    o.Write([]byte(s))
​
    return hex.EncodeToString(o.Sum(nil))
}
​
func SHAMiddleWare() gin.HandlerFunc {
    return func(context *gin.Context) {
        password := context.Query("password")
        if password == "" {
            password = context.PostForm("password")
        }
        context.Set("password", SHA1(password))
        context.Next()
    }
}
​

中间件的注册

其实就是为中间件注册路由,可以注册单路由、路由组,还可以全局注册

func InitDouyinRouter() *gin.Engine {
    models.InitDB()
    r := gin.Default()
​
    r.Static("static", "./static")
​
    baseGroup := r.Group("/douyin")
    //根据灵活性考虑是否加入JWT中间件来进行鉴权,还是在之后再做鉴权
​
    // basic apis
    baseGroup.GET("/feed/", video.FeedVideoListHandler)
    baseGroup.GET("/user/", middleware.JWTMiddleWare(), user_info.UserInfoHandler)
    baseGroup.POST("/user/login/", middleware.SHAMiddleWare(), user_login.UserLoginHandler)
    baseGroup.POST("/user/register/", middleware.SHAMiddleWare(), user_login.UserRegisterHandler)
    baseGroup.POST("/publish/action/", middleware.JWTMiddleWare(), video.PublishVideoHandler)
    baseGroup.GET("/publish/list/", middleware.NoAuthToGetUserId(), video.QueryVideoListHandler)
​
    //extend 1
    baseGroup.POST("/favorite/action/", middleware.JWTMiddleWare(), video.PostFavorHandler)
    baseGroup.GET("/favorite/list/", middleware.NoAuthToGetUserId(), video.QueryFavorVideoListHandler)
    baseGroup.POST("/comment/action/", middleware.JWTMiddleWare(), comment.PostCommentHandler)
    baseGroup.GET("/comment/list/", middleware.JWTMiddleWare(), comment.QueryCommentListHandler)
​
    //extend 2
    baseGroup.POST("/relation/action/", middleware.JWTMiddleWare(), user_info.PostFollowActionHandler)
    baseGroup.GET("/relation/follow/list/", middleware.NoAuthToGetUserId(), user_info.QueryFollowListHandler)
    baseGroup.GET("/relation/follower/list/", middleware.NoAuthToGetUserId(), user_info.QueryFollowerHandler)
​
    //baseGroup.GET("/publish/List", video.List)
​
    return r
}

五、JSON渲染

由于是前后端分离的架构,所以后端返回给前端的数据是用JSON格式返回的,可以自己拼接json也可以使用结构体,一般在业务代码中,基本是访问的数据库内容,所以需要定义一个结构体来存储数据库中的内容

func (p *ProxyFeedVideoList) FeedVideoListOk(videoList *video.FeedVideoList) {
    p.JSON(http.StatusOK, FeedResponse{
        CommonResponse: models.CommonResponse{
            StatusCode: 0,
        },
        FeedVideoList: videoList,
    },
    )
}

models包

package models
​
type CommonResponse struct {
    StatusCode int32  `json:"status_code"`
    StatusMsg  string `json:"status_msg,omitempty"`
}

六、获取参数

获取从前端返回的数据

1.获取querystring参数

querystring是指URL后面的参数,/user/search?username=123&password=123456

func Register(c *gin.Context) {
    username := c.Query("username")
    password := c.Query("password")
​
    token := username + password
​
    if _, exist := usersLoginInfo[token]; exist {
        c.JSON(http.StatusOK, UserLoginResponse{
            CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: "User already exist"},
        })
    } else {
        atomic.AddInt64(&userIdSequence, 1)
        newUser := models.User{
            Id:   userIdSequence,
            Name: username,
        }
        usersLoginInfo[token] = newUser
        c.JSON(http.StatusOK, UserLoginResponse{
            CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: "User already exist"},
            UserId:         userIdSequence,
            Token:          username + password,
        })
    }
}

2.获取form参数

当前端请求的数据通过form表单提交时,即发送的是POST请求,获取数据的方法

func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.POST("/user/search", func(c *gin.Context) {
        // DefaultPostForm取不到值时会返回指定的默认值
        //username := c.DefaultPostForm("username", "小王子")
        username := c.PostForm("username")
        address := c.PostForm("address")
        //输出json结果给调用方
        c.JSON(http.StatusOK, gin.H{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })
    r.Run(":8080")
}

七、上传文件

单文件上传

func main() {
    router := gin.Default()
    // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
    router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // 单文件
        file, _ := c.FormFile("file")
        log.Println(file.Filename)
​
        dst := "./" + file.Filename
        // 上传文件至指定的完整文件路径
        c.SaveUploadedFile(file, dst)
​
        c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
    })
    router.Run(":8080")
}

多文件上传

// PublishVideoHandler 发布视频,并截取一帧画面作为封面
func PublishVideoHandler(c *gin.Context) {
    //准备参数
    rawId, _ := c.Get("user_id")
​
    userId, ok := rawId.(int64)
    if !ok {
        PublishVideoError(c, "解析UserId出错")
        return
    }
​
    title := c.PostForm("title")
​
    form, err := c.MultipartForm()
    if err != nil {
        PublishVideoError(c, err.Error())
        return
    }
​
    //支持多文件上传
    files := form.File["data"]
    for _, file := range files {
        suffix := filepath.Ext(file.Filename)    //得到后缀
        if _, ok := videoIndexMap[suffix]; !ok { //判断是否为视频格式
            PublishVideoError(c, "不支持的视频格式")
            continue
        }
        name := util.NewFileName(userId) //根据userId得到唯一的文件名
        filename := name + suffix
        savePath := filepath.Join("./static", filename)
        err = c.SaveUploadedFile(file, savePath)
        if err != nil {
            PublishVideoError(c, err.Error())
            continue
        }
        //截取一帧画面作为封面
        err = util.SaveImageFromVideo(name, true)
        if err != nil {
            PublishVideoError(c, err.Error())
            continue
        }
        //数据库持久化
        err := video.PostVideo(userId, filename, name+util.GetDefaultImageSuffix(), title)
        if err != nil {
            PublishVideoError(c, err.Error())
            continue
        }
        PublishVideoOk(c, file.Filename+"上传成功")
    }
}