目录
- 一、gin快速入门
-
- 1 - gin简介
- 2 - gin快速入门
- 3 - gin示例原型优化
- 4 - gin的Default和New
- 5 - Gin的请求方法
- 6 - 路由分组
- 7 - 从url中获取参数
- 8 - required标记
- 二、获取表单参数
-
- 1 - get获取参数
- 2- post获取参数
- 3 - get、post混合获取
- 三、json与protobuf渲染
-
- 1 - json渲染
- 2 - protobuf渲染
- 四、表单验证
-
- 1 - 登录表单验证
- 2 - 注册表单验证
- 3 - 表单验证错误翻译成中文
一、gin快速入门
1 - gin简介
- gin官方文档地址:https://gin-gonic.com/zh-cn/docs/quickstart/
- gin的github地址:https://github.com/gin-gonic/gin
- gin与beego
- gin是一个轻量级的web框架
- beego是一个大而全的web框架,集成的东西比较多,国内人员制作的
2 - gin快速入门
- 下载并安装gin(可选):
go get -u github.com/gin-gonic/gin
- 如果我们使用的是go module的方式,就不需要手动下载,import后自动同步即可
- gin的import:
import "github.com/gin-gonic/gin"
- gin快速入门
- 启动后使用浏览器访问:http://127.0.0.1:8080/ping
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
3 - gin示例原型优化
- r.GET原型:
- param1 -> 相对访问路径
- param2 -> HandlerFunc类型,可以传入多个
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
type HandlerFunc func(*Context)
- gin.H原型:
type H map[string]any
,因为在gin太经常使用了,所以定义了H
type any = interface{}
- 根据原型改造示例代码
- 返回状态码200修改为
http.StatusOK
- 自定义HandlerFunc
- 修改监听的端口号为8088
- gin.H体验
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func pong(c *gin.Context) {
c.JSON(http.StatusOK, map[string]string{
"message": "pong",
})
}
func main() {
r := gin.Default()
r.GET("/ping", pong)
r.Run(":8088")
}
4 - gin的Default和New
- gin.Default()源码:
- 可以看到Default会默认帮我添加上Logger和Recovery的中间件,然后再New()
- 也就是说gin.New()是不会添加Logger和Recovery的中间件的
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
5 - Gin的请求方法
- 只要为对应的请求配置访问路径与HandlerFunc即可:与之前介绍的Get请求一样的实现
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
router.Run()
}
6 - 路由分组
- 路由分组:当多个请求都有相同前缀的时候就可以使用路由分组来实现
func main() {
router := gin.Default()
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8082")
}
7 - 从url中获取参数
- 提取变量:以冒号开头的方式,如
/:id/
、/:id/:action
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
goodsGroup := router.Group("/goods")
{
goodsGroup.GET("/:id/", goodsDetail)
goodsGroup.GET("/:id/:action", goodsDetailX)
goodsGroup.GET("/:id/:action/add", goodsDetailY)
}
router.Run(":8088")
}
func goodsDetail(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
})
}
func goodsDetailX(c *gin.Context) {
id := c.Param("id")
action := c.Param("action")
c.JSON(http.StatusOK, gin.H{
"id": id,
"action": action,
})
}
func goodsDetailY(c *gin.Context) {
id := c.Param("id")
action := c.Param("action")
c.JSON(http.StatusOK, gin.H{
"id": id,
"action": action,
})
}
8 - required标记
- 从测试结果分析:添加了required的TAG,如果参数不是uuid返回的是404错误
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func main() {
router := gin.Default()
router.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.Status(404)
return
}
c.JSON(http.StatusOK, gin.H{
"name": person.Name,
"id": person.ID,
})
})
router.Run(":8088")
}
- 删除掉uuid的限制,并修改ID为int属性:ID int
uri:"id" binding:"required"
type Person struct {
ID int `uri:"id" binding:"required"`
Name string `uri:"name" binding:"required"`
}
二、获取表单参数
1 - get获取参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/welcome", welcome)
router.Run(":8088")
}
func welcome(c *gin.Context) {
firstName := c.DefaultQuery("firstname", "zhp")
lastName := c.DefaultQuery("lastname", "test")
c.JSON(http.StatusOK, gin.H{
"first_name": firstName,
"last_name": lastName,
})
}
2- post获取参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/form_post", formPost)
router.Run(":8088")
}
func formPost(c *gin.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(http.StatusOK, gin.H{
"message": message,
"nick": nick,
})
}
3 - get、post混合获取
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/post", getPost)
router.Run(":8088")
}
func getPost(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.DefaultPostForm("message", "信息")
c.JSON(http.StatusOK, gin.H{
"id": id,
"page": page,
"name": name,
"message": message,
})
}
三、json与protobuf渲染
1 - json渲染
- 增加了json的TAG后:输出中就不是Name了,而是user
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/moreJSON", moreJSON)
router.Run(":8088")
}
func moreJSON(c *gin.Context) {
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "bobby"
msg.Message = "这是一个测试json"
msg.Number = 20
c.JSON(http.StatusOK, msg)
}
2 - protobuf渲染
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"test_project/gin_start/proto"
)
func main() {
router := gin.Default()
router.GET("/someProtoBuf", returnProto)
router.Run(":8088")
}
func returnProto(c *gin.Context) {
course := []string{"python", "go", "微服务"}
user := &proto.Teacher{
Name: "bobby",
Course: course,
}
c.ProtoBuf(http.StatusOK, user)
}
- user.proto:
protoc --go_out=. --go_opt=paths=source_relative *.proto
syntax = "proto3";
option go_package = ".;proto";
message Teacher {
string name = 1;
repeated string course = 2;
}
四、表单验证
1 - 登录表单验证
- gin的表单验证使用的是第三方库:github.com/go-playground/validator
- gin提供了两套方法实现表单验证:gin实际上是基于第三库的基础上进行了封装
- Must bind:如果不符合要求,就会抛出400的异常
- Method:Bind(这个仍然调用的是ShouldBind)、BindJSON。。。
- Should bind:如果不符合要求,会返回给开发人员
- Method:ShouldBind、ShouldBindJSON。。。
- 登录表单验证
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `form:"user" json:"user" xml:"user" binding:"required,min=3,max=10"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "登录成功",
})
})
router.Run(":8088")
}
2 - 注册表单验证
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type SignUpForm struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}
func main() {
router := gin.Default()
router.POST("/signup", func(c *gin.Context) {
var signUpFrom SignUpForm
if err := c.ShouldBind(&signUpFrom); err != nil {
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "注册成功",
})
})
router.Run(":8088")
}
3 - 表单验证错误翻译成中文
- 表单验证翻译实现:实现返回的错误是中文,并且是json格式
InitTrans
:主要完成翻译的初始化,将提示的对象转换成json的小写
removeTopStruct
:删除掉结构体的前缀,如LoginForm.user
-> user
package main
import (
"fmt"
"net/http"
"reflect"
"strings"
"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"
en_translations "github.com/go-playground/validator/v10/translations/en"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"`
Password string `json:"password" binding:"required"`
}
var trans ut.Translator
func InitTrans(locale string) (err error) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
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()
uni := ut.New(enT, zhT, enT)
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
switch locale {
case "en":
en_translations.RegisterDefaultTranslations(v, trans)
case "zh":
zh_translations.RegisterDefaultTranslations(v, trans)
default:
en_translations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
func removeTopStruct(fileds map[string]string) map[string]string {
rsp := map[string]string{}
for field, err := range fileds {
rsp[field[strings.Index(field, ".")+1:]] = err
}
return rsp
}
func main() {
if err := InitTrans("zh"); err != nil {
fmt.Println("初始化翻译器错误")
return
}
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
}
c.JSON(http.StatusBadRequest, gin.H{
"error": removeTopStruct(errs.Translate(trans)),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "登录成功",
})
})
router.Run(":8088")
}