page contents

golang轻量级框架-Gin入门

本文讲述了golang轻量级框架-Gin入门!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

attachments-2022-02-nXS23NA762143e7d56872.png

gin框架和前面学习的beego框架都是比较流行的框架,但是beego比较传统,模块多功能全,而gin可以看作是一个单独模块的框架,官方介绍说的是:Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 martini-like API 框架, 由于 httprouter,速度提高了近 40 倍。 如果你是性能和高效的追求者, 那么你会爱上 Gin。自己感觉gin更像是beego中的controller,主要针对用户的request和response。gin官网,个人感觉文档稍显粗糙,不过胜在支持中文,还是很良心的。

一、安装和开始

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

goget-u github.com/gin-gonic/gin

但是因为网络问题可能会失败,实在不行就直接通过github下载也可以。

安装好之后就可以直接使用了,打开ide创建一个新的项目helloGin,创建main.go

funcmain(){

// 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",访问结果如下图所示:

attachments-2022-02-oesw2vLe62158a7323f1d.png

图-1.png

二、创建demo

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

attachments-2022-02-8DexA0VY62158a8fb5c2e.png

图-2.png

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

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

funcmain(){

// Engin

router := gin.Default()

router.GET("/hello", hello)// hello函数处理"/hello"请求

// 指定地址和端口号

router.Run(":9090")

}

funchello(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也支持,这就是路由组,我们看下官方文档的示例:

funcmain(){

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文件名称。修改后代码如下:

funcmain(){

// 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的代码内容:

funcinit(){

log.Println(">>>> get database connection start <<<<")

db = database.GetDataBase()

}

// localhost:9090/user/query?id=2&name=hello

funcQueryParam(context *gin.Context){

println(">>>> query user by url params action start <<<<")

id := context.Query("id")

name := context.Request.URL.Query().Get("name")

varu 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)

varuser 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/hello

funcQueryById(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)

varuser 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格式数据

funcInsertNewUser(context *gin.Context){

println(">>>> insert controller action start <<<<")

varuser 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)

varcountint64

count,err = res.RowsAffected()

checkError(err)

ifcount !=1{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

// form表单提交

funcPostForm(context *gin.Context){

println(">>>> bind form post params action start <<<<")

varu 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)

varcountint64

count,err = res.RowsAffected()

checkError(err)

ifcount !=1{

context.JSON(200,gin.H{

"success":false,

})

}else{

//context.JSON(200,gin.H{

//  "success":true,

//})

// 重定向

context.Redirect(http.StatusMovedPermanently,"/file/view")

}

}

// 跳转html

funcRenderForm(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{})

}

funccheckError(e error){

ife !=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上传图片。代码如下:

constBASE_NAME ="./static/file/"

funcRenderView(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{})

}

// 单个文件上传

funcFormUpload(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()

deferfile.Close()

bytes,e := ioutil.ReadAll(file)

e = ioutil.WriteFile(BASE_NAME + fh.Filename,bytes,0666)

checkError(e)

ife !=nil{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

// 多个文件上传

funcMultiUpload(context *gin.Context){

println(">>>> upload file by form action start <<<<")

form,err := context.MultipartForm()

checkError(err)

files := form.File["file"]

varer error

for_,f :=rangefiles {

// 使用gin自带保存文件方法

er = context.SaveUploadedFile(f,BASE_NAME + f.Filename)

checkError(err)

}

ifer !=nil{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

funcBase64Upload(context *gin.Context){

println(">>>> upload file by base64 string action start <<<<")

bytes,err := ioutil.ReadAll(context.Request.Body)

iferr !=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)

iferr !=nil{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

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

typeFileHeaderstruct{

Filenamestring

Header   textproto.MIMEHeader

Sizeint64

content []byte

tmpfilestring

}

保存文件可以直接使用SaveUploadedFile方法,也可以使用ioutil相关方法进行保存。

MultiUpload多文件上传,先通过context.MultipartForm()获取Form对象,然后根据参数名获取到多个FileHeader指针,接下去保存文件和单个上传是一样的。

Base64Upload函数本来是想通过使用base64上传图片,函数内先获取整个字符串,然后分割成head和body,然后判断图片类型,最后使用ioutil.WriteFile保存文件,但是实际操作好像出了点问题,文件保存到本地打开显示内容丢失,不知道是怎么回事。

更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-06-2ccl9t3f62a0611a4727c.jpeg


  • 发表于 2022-02-22 09:38
  • 阅读 ( 1386 )
  • 分类:Golang

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
轩辕小不懂
轩辕小不懂

2403 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1474 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章