Gin框架

文档

官方文档地址:介绍 | Gin Web Framework (gin-gonic.com)

1. 安装

要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。

1.下载并安装 gin:

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

2.将 gin 引入到代码中:

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

3.(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:

import "net/http"
image-20240712201342883

2. Gin使用

2.1 接口

使用gin编写一个接口:

package main

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

func main() {
//1.创建一个默认的路由
router := gin.Default()
//2.绑定路由规则和路由函数,访问/index的路由,将有对应的函数去处理
router.GET("/index", func(context *gin.Context) { //回调函数
context.String(200, "hello world") //返回一个东西
})
//3.启动监听,访问0.0.0.0:8088的地址,0.0.0.0代表本机所有ip
router.Run(":8088")
}
  1. router:=gin.Default():这是默认的服务器。使用gin的Default方法创建一个路由Handler
  2. 然后通过Http方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把requestresponse都封装到了gin.Context的上下文环境中。
  3. 最后启动路由的Run方法监听端口。还可以用http.ListenAndServe(":8080", router),或者自定义Http服务器配置。
// 启动方式一
router.Run(":8000")
// 启动方式二 ,用原生http服务的方式, router.Run本质就是http.ListenAndServe的进一步封装
http.ListenAndServe(":8000", router)
//修改ip为内网ip
router.Run("0.0.0.0:8000")

2.2 响应

状态码: 200(http.StatusOK),正常响应

响应JSON

响应xml和yaml

响应html

文件响应

重定向

package main

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

// 重定向
func redirect(ctx *gin.Context) {
ctx.Redirect(301, "https://www.baidu.com")
}

func main() {
router := gin.Default() // 路由初始化 default和new的区别:default多了两个中间件:日志和错误恢复。
router.GET("/", func(ctx *gin.Context) {
ctx.String(200, "你好,小溪")
})
router.GET("/baidu", redirect)
router.Run(":80")
}

2.3 Restful

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

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

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

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

3. 请求参数

3.1 查询参数

Query:查询已有的参数

func _query(c *gin.Context) {
fmt.Println(c.Query("user"))
fmt.Println(c.GetQuery("user"))
fmt.Println(c.QueryArray("user")) // 拿到多个相同的查询参数
fmt.Println(c.DefaultQuery("addr", "四川省"))
}

3.2 动态参数

Param:路径可变化

func _param(c *gin.Context) {
fmt.Println(c.Param("user_id"))
fmt.Println(c.Param("book_id"))
}

router.GET("/param/:user_id/", _param)
router.GET("/param/:user_id/:book_id", _param)

// ?param/12
// ?param/12/123

3.3 URL参数

  • URL参数可以通过DefaultQuery()或Query()方法获取
  • DefaultQuery()若参数不存在,返回默认值,Query()若不存在,返回空串
  • API ? name=zs
package main

import (
"fmt"
"net/http"

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

func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
//指定默认值
//http://localhost:8080/user 才会打印出来默认的值
name := c.DefaultQuery("name", "枯藤")
c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
})
r.Run()
}

3.4 表单

PostForm():文件上传,方法默认解析的是x-www-form-urlencoded或from-data格式的参数

http常见的传输格式为四种:

  • application/json
  • application/x-www-form-urlencoded
  • application/xml
  • multipart/form-data

4.validator库

参考:https://www.liwenzhou.com/posts/Go/validator-usages/

在web开发中一个不可避免的环节就是对请求参数进行校验,通常我们会在代码中定义与请求参数相对应的模型(结构体),借助模型绑定快捷地解析请求中的参数,例如 gin 框架中的BindShouldBind系列方法。本文就以 gin 框架的请求参数校验为例,介绍一些validator库的实用技巧。

gin框架使用github.com/go-playground/validator进行参数校验,目前已经支持github.com/go-playground/validator/v10了,我们需要在定义结构体时使用 binding tag标识相关校验规则,可以查看validator文档查看支持的所有 tag。

首先来看gin框架内置使用validator做参数校验的基本示例。

package main

import (
"net/http"

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

type SignUpParam struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

func main() {
r := gin.Default()

r.POST("/signup", func(c *gin.Context) {
var u SignUpParam
if err := c.ShouldBind(&u); err != nil {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
return
}
// 保存入库等业务逻辑代码...

c.JSON(http.StatusOK, "success")
})

_ = r.Run(":8999")
}

翻译校验错误提示信息

validator库本身是支持国际化的,借助相应的语言包可以实现校验错误提示信息的自动翻译。下面的示例代码演示了如何将错误提示信息翻译成中文,翻译成其他语言的方法类似。

package main

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

// 定义一个全局翻译器T
var trans ut.Translator

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
// 修改gin框架中的Validator引擎属性,实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

zhT := zh.New() // 中文翻译器
enT := en.New() // 英文翻译器

// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)

// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}

// 注册翻译器
switch locale {
case "en":
err = enTranslations.RegisterDefaultTranslations(v, trans)
case "zh":
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}

type SignUpParam struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

func main() {
if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

r := gin.Default()

r.POST("/signup", func(c *gin.Context) {
var u SignUpParam
if err := c.ShouldBind(&u); err != nil {
// 获取validator.ValidationErrors类型的errors
errs, ok := err.(validator.ValidationErrors)
if !ok {
// 非validator.ValidationErrors类型错误直接返回
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
return
}
// validator.ValidationErrors类型错误则进行翻译
c.JSON(http.StatusOK, gin.H{
"msg":errs.Translate(trans),
})
return
}
// 保存入库等具体业务逻辑代码...

c.JSON(http.StatusOK, "success")
})

_ = r.Run(":8999")
}
curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123.com"}' http://127.0.0.1:8999/signup
// 请求
// 输出结果
{"msg":{"SignUpParam.Email":"Email必须是一个有效的邮箱","SignUpParam.Password":"Password为必填字段","SignUpParam.RePassword":"RePassword为必填字段"}}

自定义错误提示信息的字段名

上面的错误提示看起来是可以了,但是还是差点意思,首先是错误提示中的字段并不是请求中使用的字段,例如:RePassword是我们后端定义的结构体中的字段名,而请求中使用的是re_password字段。如何是错误提示中的字段使用自定义的名称,例如jsontag指定的值呢?

只需要在初始化翻译器的时候像下面一样添加一个获取json tag的自定义方法即可。

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
// 修改gin框架中的Validator引擎属性,实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

// 注册一个获取json tag的自定义方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

zhT := zh.New() // 中文翻译器
enT := en.New() // 英文翻译器

// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)

// ... liwenzhou.com ...
}
{"msg":{"SignUpParam.email":"email必须是一个有效的邮箱","SignUpParam.password":"password为必填字段","SignUpParam.re_password":"re_password为必填字段"}}
//输出

可以看到现在错误提示信息中使用的就是我们结构体中jsontag设置的名称了。

但是还是有点瑕疵,那就是最终的错误提示信息中心还是有我们后端定义的结构体名称——SignUpParam,这个名称其实是不需要随错误提示返回给前端的,前端并不需要这个值。我们需要想办法把它去掉。

定义一个去掉结构体名称前缀的自定义方法:

func removeTopStruct(fields map[string]string) map[string]string {
res := map[string]string{}
for field, err := range fields {
res[field[strings.Index(field, ".")+1:]] = err
}
return res
}

我们在代码中使用上述函数将翻译后的errors做一下处理即可:

if err := c.ShouldBind(&u); err != nil {
// 获取validator.ValidationErrors类型的errors
errs, ok := err.(validator.ValidationErrors)
if !ok {
// 非validator.ValidationErrors类型错误直接返回
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
return
}
// validator.ValidationErrors类型错误则进行翻译
// 并使用removeTopStruct函数去除字段名中的结构体名称标识
c.JSON(http.StatusOK, gin.H{
"msg": removeTopStruct(errs.Translate(trans)),
})
return
}
{"msg":{"email":"email必须是一个有效的邮箱","password":"password为必填字段","re_password":"re_password为必填字段"}}
//输出

自定义结构体校验方法

框架介绍

GoWeb框架

0.序言

我们需要实现一个 Web 应用,第一反应是应该使用哪个框架。

在设计一个框架之前,我们需要回答框架核心为我们解决了什么问题。

我们先看看标准库net/http如何处理一个请求。

func main() {
http.HandleFunc("/", handler) //鉴权:没有分组/统一鉴权的能力,需要在每个路由映射的handler中实现。
http.HandleFunc("/count", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
即监听端口,映射静态路由,解析HTTP报文。
  • 路由(Routing):将请求映射到函数,支持动态路由。例如'/hello/:name
  • 模板(Templates):使用内置模板引擎提供模板渲染机制。
  • 工具集(Utilites):提供对 cookies,headers 等处理机制。
  • 插件(Plugin):Bottle本身功能有限,但提供了插件机制。可以选择安装到全局,也可以只针对某几个路由生效。

1.Gee框架

标准库启动Web服务

Go语言内置了 net/http库,封装了HTTP网络编程的基础的接口,我们实现的Gee Web 框架便是基于net/http

用 curl 这个工具测试

实现http.Handler接口