1 创建一个gin项目
配置 GoSDK的 root
1.1 创建项目目录
直接创建一个文件夹使用GoLand打开
1.2 生成mod文件
终端执行
go mod init 项目名称
1.3 安装gin的相关包
- 配置代理
setting – go – module
GOPROXY=https://goproxy.cn,direct
- 安装gin相关包
终端执行命令
go get -u github.com/gin-gonic/gin
// 等待安装完成
2 简单的启动文件
2.1 创建启动文件
在项目目录下面创建启动文件 main.go
package main
// 导入 gin使用的相关包
import (
"github.com/gin-gonic/gin"
)
func Index(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "this is index of gin !",
"data": "cmcc",
})
}
func main() {
// 1 创建一个gin项目的启动对象
ginServer := gin.Default()
// 2 创建一个访问路由
// 放回一些数据
ginServer.GET("/hello", func(context *gin.Context) {
context.JSON(200, gin.H{
"msg": "this is go for gin !",
})
})
// 路由绑定到一个函数上面
ginServer.GET("/index", Index)
// 3 启动gin的服务
ginServer.Run("127.0.0.1:8080")
}
2.2 创建服务的步骤
- 1 导入相关使用的包
import(
"github.com/gin-gonic/gin"
)
- 2 创建一个gin服务对象
ginServer := gin.Default()
- 3 绑定一个路由和处理函数
func Index(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "this is index of gin !",
"data": "cmcc",
})
}
----------------
ginServer.GET("/hello", func(context *gin.Context) {
context.JSON(200, gin.H{
"msg": "this is go for gin !",
})
})
// 路由绑定到一个函数上面
ginServer.GET("/index", Index)
- 4 配置并启动服务
ginServer.Run("127.0.0.1:8080")
2.3 服务结果
2.4 添加使用一个icon
2.4.1 安装指定的包
go get github.com/thinkerou/favicon
2.4.2 指定使用的icon
// 添加一个图标 - 本地地址的一个图标
ginServer.Use(favicon.New("./favicon.ico"))
效果:
2.3 总结 – 服务步骤
- 1 创建一个路由(可以自定义也可以使用默认的)
- 2 创建一个匹配的路由并且绑定该路由的处理函数(功能一般在处理函数中来实现)
- 3 监听地址和端口号(启动这个服务)
3 视图
3.1 状态码
表示服务其状态的一个编码
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func index(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"date": "20230408",
"msg": "Hello World!",
})
// 状态码 go中自定义的常见的常态码 通过 http.状态名 来导出 PS:http.StatusOK 相当于 200
/*
const (
StatusContinue = 100 // RFC 9110, 15.2.1
StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2
StatusProcessing = 102 // RFC 2518, 10.1
StatusEarlyHints = 103 // RFC 8297
StatusOK = 200 // RFC 9110, 15.3.1
StatusCreated = 201 // RFC 9110, 15.3.2
StatusAccepted = 202 // RFC 9110, 15.3.3
StatusNonAuthoritativeInfo = 203 // RFC 9110, 15.3.4
StatusNoContent = 204 // RFC 9110, 15.3.5
StatusResetContent = 205 // RFC 9110, 15.3.6
StatusPartialContent = 206 // RFC 9110, 15.3.7
StatusMultiStatus = 207 // RFC 4918, 11.1
StatusAlreadyReported = 208 // RFC 5842, 7.1
StatusIMUsed = 226 // RFC 3229, 10.4.1
StatusMultipleChoices = 300 // RFC 9110, 15.4.1
StatusMovedPermanently = 301 // RFC 9110, 15.4.2
StatusFound = 302 // RFC 9110, 15.4.3
StatusSeeOther = 303 // RFC 9110, 15.4.4
StatusNotModified = 304 // RFC 9110, 15.4.5
StatusUseProxy = 305 // RFC 9110, 15.4.6
_ = 306 // RFC 9110, 15.4.7 (Unused)
StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
StatusPermanentRedirect = 308 // RFC 9110, 15.4.9
StatusBadRequest = 400 // RFC 9110, 15.5.1
StatusUnauthorized = 401 // RFC 9110, 15.5.2
StatusPaymentRequired = 402 // RFC 9110, 15.5.3
StatusForbidden = 403 // RFC 9110, 15.5.4
StatusNotFound = 404 // RFC 9110, 15.5.5
StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6
StatusNotAcceptable = 406 // RFC 9110, 15.5.7
StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8
StatusRequestTimeout = 408 // RFC 9110, 15.5.9
StatusConflict = 409 // RFC 9110, 15.5.10
StatusGone = 410 // RFC 9110, 15.5.11
StatusLengthRequired = 411 // RFC 9110, 15.5.12
StatusPreconditionFailed = 412 // RFC 9110, 15.5.13
StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14
StatusRequestURITooLong = 414 // RFC 9110, 15.5.15
StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16
StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
StatusExpectationFailed = 417 // RFC 9110, 15.5.18
StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused)
StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20
StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21
StatusLocked = 423 // RFC 4918, 11.3
StatusFailedDependency = 424 // RFC 4918, 11.4
StatusTooEarly = 425 // RFC 8470, 5.2.
StatusUpgradeRequired = 426 // RFC 9110, 15.5.22
StatusPreconditionRequired = 428 // RFC 6585, 3
StatusTooManyRequests = 429 // RFC 6585, 4
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
StatusInternalServerError = 500 // RFC 9110, 15.6.1
StatusNotImplemented = 501 // RFC 9110, 15.6.2
StatusBadGateway = 502 // RFC 9110, 15.6.3
StatusServiceUnavailable = 503 // RFC 9110, 15.6.4
StatusGatewayTimeout = 504 // RFC 9110, 15.6.5
StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
StatusInsufficientStorage = 507 // RFC 4918, 11.5
StatusLoopDetected = 508 // RFC 5842, 7.2
StatusNotExtended = 510 // RFC 2774, 7
StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)
*/
}
func main() {
r := gin.Default()
r.GET("/index", index)
http.ListenAndServe("0.0.0.0:8080", r)
}
3.2 响应
3.2.1 String
相应的是直接是谁string类型的数据 – 直接展示在页面上
// 路由
r.GET("/string", _string)
// 处理函数
func _string(c *gin.Context) {
// 状态码和响应的 字符串数据
c.String(http.StatusOK, "this is response string data!")
}
3.2.2 JSON
gin将数据转成JSON类型的数据返回
- 方法1 字节直接写JSON类型的数据
// 路由
r.GET("/json2", _json2)
// 处理函数
// 不通过结构体构造json数据
func _json2(c *gin.Context) {
// 通过放在 gin.H 中构造出一个json数据
c.JSON(200, gin.H{
"data": "111111",
"age": "2222",
})
}
- 方法2 通过构造结构体的形式,返回一个对象型的数据 – json类型数据
// 路由
r.GET("/json", _json)
// 处理函数
func _json(c *gin.Context) {
// 创建一个相应的结构体
type User struct {
Name string `json:"username"`
Age int `json:"age"`
Password string `json:"-"`
}
// Name string `json:"username"` 标识转成json类型后字段命名 Name -- > username
// Password string `json:"-"` - 转成json数据后不显示 隐藏掉(保密数据)
// 实例化结构体
user := User{"JJJJSON", 20, "123456"}
c.JSON(http.StatusOK, user)
}
- 方法3 直接响应一个map
// 路由
r.GET("/json_map", _jsonMap)
// 处理函数
func _jsonMap(c *gin.Context) {
// 直接响应一个map
userMap := make(map[string]interface{})
userMap["name"] = "AAAAAA"
userMap["age"] = 30
userMap["password"] = "123456"
c.JSON(200, userMap)
}
3.2.3 HTML
响应一个HTML文件 – 也可以返回数据
// 1 先加载HTML文件
// 响应模板数据
// 先要使用 LoadHTMLGlob()或者LoadHTMLFiles()方法来加载模板文件
r.LoadHTMLGlob("templates/index/*")
// 2 指定路由
r.GET("/html", _html)
// 3 处理函数
// 加载html
func _html(c *gin.Context) {
// 响应HTML 文件名 携带的数据
c.HTML(200, "index.html", gin.H{
"name": "HTML",
"age": 99,
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This is Template</title>
</head>
<body>
<h1>姓名 {{ .name }}</h1>
<h1>姓名 {{ .age }}</h1>
</body>
</html>
效果
3.2.4 文件响应
响应一个或一些静态文件
// 配置固定的文件访问路径
r.StaticFS("/static", http.Dir("./static/static"))
// 第一个是浏览器访问路径 第二个是资源路径 -- 没有定义的是无法访问呢的
r.StaticFile("img.jpg", "./static/img.jpg")
文件目录
访问 配置的图标片路径
访问 指定的文件内容
3.2.5 重定向
// 重定向 -- 路由
r.GET("/redirect", _redirect)
// _redict 重定向处理函数
func _redirect(c *gin.Context) {
// 第一个从定向的状态码 第二个从定向的地址位置(可以是本地也可以是一个网络资源)
//c.Redirect(302, "https:www.baidu.com")
c.Redirect(302, "/html")
}
3.3 请求
3.3.1 查询参数(query)
http://127.0.0.1:8080/query?name=fenghan&id=10&id=100
通过查询将数据传入到后端
// 路由
// Query 的查询参数
r.GET("/query", _query)
===============================
// 查询参数处理函数
func _query(c *gin.Context) {
// 通过阐述路径传入的查询参数接收
// c.getQuery("key") 获取指定的key的参数值 返回 value 和ok 标记是否有这个查询参数
// http://127.0.0.1:8080/query?name=fenghan&id=10&id=100
value, ok := c.GetQuery("name") // ok 标记的是这个参数是是否存在
if ok {
fmt.Println("name`s value is ", value)
}
// name`s value is fenghan
// 多个名字相同的参数获取所有的值
// c.getQueryArray("id") 获取到所有查询参数key 位 id的值 返回一个string 切片
values, ok := c.GetQueryArray("id")
if ok {
fmt.Println("id`s all value is ", values)
}
// id`s all value is [10 100]
}
3.3.2 动态参数(param)
存在与查询路径上的参数 – 接受的参数都是 string 类型
http://127.0.0.1:8080/param/zzzJHFMJ<JHFKDS3/125
// 路由和路径参数
// 动态参数
// 路径上接收位 :接收变量名 除路径字符外所有的字符
r.GET("/param/:user_id/:book_id", _param)
// 处理函数
// 动态参数
func _param(c *gin.Context) {
// 接收动态参数
// c.Param("user_id") 接收指定参数名的参数 如果指定参数没有传,则为空
// 结果是一个字符串
user_id := c.Param("user_id")
book_id := c.Param("book_id")
fmt.Println("user_id: ", user_id)
fmt.Println("book_id: ", book_id)
if book_id == "125" {
fmt.Println("shhhhhhhhhhhhhhhhhhhh")
}
fmt.Println()
//user_id: zzzJHFMJ<JHFKDS3
//book_id: 125
3.3.3 表单参数(form)
填写表单传入的表单参数
// 路由
r.POST("/form", _form)
======================================
// 处理函数
func _form(c *gin.Context) {
// 接收和处理表单参数
//
name := c.PostForm("name") // 接收以恶个名为 name的表单字段
names := c.PostFormArray("name") // 接收多个key位name的表单字段 返回一个字符串切片
adrr := c.DefaultPostForm("adrr", "什么都没有!") // 接受表单参数中指定key的值 没有 给默认值
fs, err := c.MultipartForm() // 接收form 表单中的所有key-value字段
fmt.Println(name)
fmt.Println(names)
fmt.Println(adrr)
if err != nil {
fmt.Println(err)
}
fmt.Println(fs)
//XXXX
//[XXXX zZZZZ]
//什么都没有!
//&{map[accc:[1215313] name:[XXXX zZZZZ]] map[]}
}
传入的表单
3.3.4 解析原始数据
**
解析原始数据使用 c.GetRawData() 获取到提交到后台的body中的数据 c.GetHeader(“content-type”) 获取到请求头的数据
**
- 通过解析原始数据将提交的json数据转成结构体类型的数据
// 路由
// JSON 数据的解析
r.POST("/json_l", _json_l)
====================================================
接收穿过来的json数据
func _json_l(c *gin.Context) {
// c.GetRawData() 来获传递过来的body的数据 返回的是一个byte 的数组
body, _ := c.GetRawData()
// 通过 拿到请求头中的Content-Type 确定是那种数据
contentType := c.GetHeader("content-type")
// 判断是不是json数据
switch {
case contentType == "application/json":
// 将数据转成指定的结构体的数据
type Stu struct {
Name string `json:"name"` // 绑定的json字段名要和传入的数据字段一样
Age int64 `json:"age"`
Gender string `json:"gender"`
}
// 实例化
stu := new(Stu)
// 将json数据解析乘结构体
err := json.Unmarshal(body, &stu)
if err != nil {
fmt.Println(err)
}
fmt.Println("This is after struct data ==", stu)
// This is after json data == &{zZZZZ 30 男}
}
}
- 通过封装 来实现数据的简单绑定 – 封装解析函数 只需要传入gin对象和 目标结构体对象
// 路由
r.POST("/json_bind", _json_bind)
===============================================================
// 封装的解析函数
// 接收一个*gin.Context 和一个目标结构体
func json_bind(c *gin.Context, obj any) error {
body, _ := c.GetRawData()
// 通过 拿到请求头中的Content-Type 确定是那种数据
contentType := c.GetHeader("content-type")
// 判断是不是json数据
switch {
case contentType == "application/json":
err := json.Unmarshal(body, &obj)
if err != nil {
return err
}
}
return nil
}
// 处理函数
func _json_bind(c *gin.Context) {
// 创建一个结构体
type Stu struct {
Name string `json:"name"` // 绑定的json字段名要和传入的数据字段一样
Age int64 `json:"age"`
Gender string `json:"gender"`
}
// 实例化
stu := new(Stu)
err := json_bind(c, stu)
if err != nil {
fmt.Println(err)
}
fmt.Println("this is json_bind res_data = ", stu)
// this is json_bind res_data = &{zZZZZ 30 男}
}
3.4 请求方式
gin框架空中推荐restful风格的接口实现 ----- 四大请求方式
- GET 查询
- POST 添加
- PUT 修改
- DELETE 删除
3.4.1 模拟restful风格的例子
package main
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
type ArticleModel struct {
Title string `json:"title"`
Price float64 `json:"price"`
Description string `json:"description"`
}
// 创建一个保存 很多文章的切片
var articles = []ArticleModel{
{"《西游记》", 109.99, "取经"},
{"《三国演义》", 88.56, "中国古代各国割据的故事"},
{"《水浒传》", 96.64, "古代版的黑帮招安"},
}
// 创建一个响应的对象
type Response struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
}
// 所有文章列表
func _getList(c *gin.Context) {
c.JSON(http.StatusOK, Response{200, articles, "列表请求成功"})
}
// 查询单个文章的信息
func _getArticle(c *gin.Context) {
// 获取动态参数的
id_str := c.Param("id")
id, _ := strconv.ParseInt(id_str, 10, 64)
fmt.Println(articles[id])
c.JSON(http.StatusOK, Response{200, articles[id], "单个文章数据!"})
}
func json_bind_new(c *gin.Context, obj any) error {
body, _ := c.GetRawData()
contentType := c.GetHeader("content-type")
if contentType == "application/json" {
err := json.Unmarshal(body, &obj)
if err != nil {
fmt.Println(err)
return err
}
}
return nil
}
// 添加一个article
func _addArticle(c *gin.Context) {
// 接收传过来的数据信息
art := new(ArticleModel)
err := json_bind_new(c, &art)
if err != nil {
c.JSON(http.StatusBadRequest, Response{http.StatusBadRequest, nil, "添加错误"})
}
articles := append(articles, *art)
c.JSON(http.StatusOK, Response{200, articles, "添加成功!"})
}
// 修改一个文章的信息
func _editArticle(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
art := new(ArticleModel)
err := json_bind_new(c, &art)
if err != nil {
fmt.Println(err)
} else {
articles[id] = *art
}
c.JSON(http.StatusOK, Response{200, articles, "修改成功!"})
}
func _deleteArticle(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
articles = append(articles[:id], articles[id+1:]...)
c.JSON(http.StatusOK, Response{200, articles, "删除成功!"})
}
func main() {
// 模拟一个文章的增删查改的Restful请求
r := gin.Default()
// 获取文章列表
r.GET("/article_list", _getList)
// 获取 查询单个文章的信息
r.GET("/article/:id", _getArticle)
// 添加一个书的信息
r.POST("/article", _addArticle)
// 修改一个书的信息
r.PUT("/article/:id", _editArticle)
// 删除一个书的信息
r.DELETE("/delete_article/:id", _deleteArticle)
r.Run(":8080")
}
3.4.2 查询书籍列表 – 结果
3.4.3 查询单个书籍的信息 – 结果
3.4.4 添加一个书籍信息 – 结果
3.4.5 修改一个书籍的信息 – 结果
3.4.6 删除一个书籍 – 结果
3.5 请求头
发送一个网络请求的时候会产生一个携带请求头数据,一起传入后端
3.5.1 获取请求头数据
// 路由
r.GET("/header", _header)
=====================================================
func _header(c *gin.Context) {
// 通过 getHeader(Key) 获取到指定请求头的数据 --- 这个里面的key不区分大小写,但是单词之前要用 - 链接
user_agent := c.GetHeader("user-agent") // 获取请求头中user-agent的数据
fmt.Println("请求头中的user-agent数据:", user_agent)
// 获取到 请求头中所有的数据
// c.Request.Header -- 返回:map[string][]string key = 所有值的切片
headers := c.Request.Header
fmt.Println("这是所有的请求头信息:", headers)
// 获取到的所有信息也可以通过通过Get方法拿到指定的值 -- []string一个字符串切片
get_user_agent := headers.Get("user-agent") // key不区分大小写
fmt.Println("通过Get方式获取到user-agent:", get_user_agent)
// 通过map形式取值
map_user_agent := headers["User-Agent"] //使用map方式取值 key 区分大小写
fmt.Println("通过map的形式取值:", map_user_agent)
//请求头中的user-agent数据: zzzzzzzzz
//这是所有的请求头信息: map[Accept:[*/*] Accept-Encoding:[gzip, deflate, br] Connection:[255313] Content-Length:[40] Content-Type:[ajja] User-Agent:[zzzzzzzzz]]
//通过Get方式获取到user-agent: zzzzzzzzz
//通过map的形式取值: [zzzzzzzzz]
}
3.5.2 使用header模拟拦截爬虫
自定义一个header的user-agen 假设含哟python的是爬虫程序
// 路由
// 模拟区分用户和爬虫程序 按照 python 位例子
r.GET("/intercept_header", _interceptHeader)
================================
// 拦截非法的请求头信息 -- 模拟爬虫和用户区别对待
func _interceptHeader(c *gin.Context) {
// 1 获取到 请求头的User-Agent
user_agent := c.GetHeader("User-Agent")
// 2 判断User-Agent 中是否包含python字段类判断是否是 爬虫程序
if strings.Contains(user_agent, "python") {
// 返回一个针对爬虫的处理数据
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "这是针对爬虫程序返回的数据!"})
return
}
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "这是针对正常登录用户返回的数据!"})
}
- 爬虫程序
- 正常用户
3.6 响应头
设置响应的响应头
// 路由
// 响应头
r.GET("/response_header", _responseHeader)
// _responseHeader 处理响应信息的
func _responseHeader(c *gin.Context) {
//这设立设置一些响应的信息
c.Header("Token", "akjsgh&slfjdao789ab") // 设置响应头的数据
// 也可以通过内置的SetXXX来设置一些信息 ps
c.JSON(200, gin.H{"msg": "关于响应头!"})
}
==================================
-
设置响应头的SetXXX方法
-
结果
4 参数绑定
前端以各种形式传来的参数,绑定到结构体中,
gin中的bind可以很方便的将 前端传递 来的数据与 结构体 进行 参数绑定 ,以及参数校验
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Student struct {
Name string `json:"name" form:"name" uri:"name"`
Age int `json:"age" form:"age" uri:"age"`
Gender string `json:"gender" form:"gender" uri:"gender"`
}
func main() {
// 这里参数绑定 和结构体数据的绑定
// -- 参数绑定的时候需要将 结构体数据加上响应的标签,给不同不请求使用的把标签也不同
// -- 标签的名字要和传入数据字段名相同
// 推荐使用Should Bind 可以绑定 json,form,xml,yaml,uri等类型数据
// 绑定函数会自动校验参数,如果不通过会报错
r := gin.Default()
// ShouldBindJSON 绑定传入的json数据
// 使用的tag 是 `json`
r.POST("/shouldBindJSON", _shouldBindJSON)
// ShouldBindQuery 绑定传入的查询参数
// 使用的参数tag 是 `form`
r.GET("/shouldBindQuery", _shouldBindQuery)
// 便规定路径阐述 参数卸载路径上的 使用 tag是 uri
r.GET("/shouldBindUri/:name/:age/:gender", _shouldBindUri)
// 绑定 data_form x-www-form-urlencode 类型的数据 -- 使用的tag -- form
r.POST("/shouldBind", _shouldBind)
r.Run(":8080")
}
// 绑定json数据
func _shouldBindJSON(c *gin.Context) {
// 创建按一个结构体实例
var stu Student
// 绑定数据
err := c.ShouldBindJSON(&stu) // 将传入的json数据绑定到 stu这个结构体中 -- 相当于之前自己封装的函数
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
// 绑定的是查询阐述
func _shouldBindQuery(c *gin.Context) {
// 创建一个结构体的实例
var stu Student
// 绑定参数
err := c.ShouldBindQuery(&stu) // 使用tag是 form
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
// 绑定路径参数 param/ZAAA/20/男
func _shouldBindUri(c *gin.Context) {
// 创建结构体的实例
var stu Student
// 绑定数据 -- 使用的tag是uri
err := c.ShouldBindUri(&stu)
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
// 绑定data-form类型的数据
func _shouldBind(c *gin.Context) {
var stu Student
err := c.ShouldBind(&stu) // 使用的tag 也是 form字段
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
4.1 ShouldBindJSON
- 绑定的是自定义的json数据
- 使用的tag是
json
// 路由
// ShouldBindJSON 绑定传入的json数据
// 使用的tag 是 `json`
r.POST("/shouldBindJSON", _shouldBindJSON)
=============================================================
// 处理函数
// 绑定json数据
func _shouldBindJSON(c *gin.Context) {
// 创建按一个结构体实例
var stu Student
// 绑定数据
err := c.ShouldBindJSON(&stu) // 将传入的json数据绑定到 stu这个结构体中 -- 相当于之前自己封装的函数
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
- 结果
4.2 ShouldBindQuery
- 绑定的是查询参数
- 使用tag是
form
// 路由
// ShouldBindQuery 绑定传入的查询参数
// 使用的参数tag 是 `form`
r.GET("/shouldBindQuery", _shouldBindQuery)
===================================
// 绑定的是查询阐述
func _shouldBindQuery(c *gin.Context) {
// 创建一个结构体的实例
var stu Student
// 绑定参数
err := c.ShouldBindQuery(&stu) // 使用tag是 form
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
- 结果
4.3 ShouldBindUri
- 绑定路径参数
- 使用tag是
uri
// 路由
// 便规定路径阐述 参数卸载路径上的 使用 tag是 uri
r.GET("/shouldBindUri/:name/:age/:gender", _shouldBindUri)
==================
// 处理函数
// 绑定路径参数 param/ZAAA/20/男
func _shouldBindUri(c *gin.Context) {
// 创建结构体的实例
var stu Student
// 绑定数据 -- 使用的tag是uri
err := c.ShouldBindUri(&stu)
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
- 结果
4.4 ShouldBind
- 可以绑定 data_form,x-www-form-urlencode 类型的数据
- 使用tag是
form
// 路由
// 绑定 data_form x-www-form-urlencode 类型的数据 -- 使用的tag -- form
r.POST("/shouldBind", _shouldBind)
===================================
// 处理函数
// 绑定data-form类型的数据
func _shouldBind(c *gin.Context) {
var stu Student
err := c.ShouldBind(&stu) // 使用的tag 也是 form字段
if err != nil {
c.JSON(http.StatusOK, gin.H{"msg": "传入的阐述有问题!"})
}
c.JSON(http.StatusOK, stu)
}
- 结果
5 验证器 - 校验参数
校验参数是否符合规则
5.1 常用的验证器
作用与结构体
使用binding
的tag进行绑定验证
常用的数据校验绑定
// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"
// 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"
// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"
// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值
- 忽略字段,如:binding:"-" 该字段不进行匹配
如果不符合以上的规则将会报错
package main
import "github.com/gin-gonic/gin"
func main() {
// 验证器 -- 校验传入的阐述是否符合规范,作用与 绑定结结构体上
// 使用的是某个tag 并且指定这个tag的值
r := gin.Default()
// 常用的验证器
r.POST("/usually_check", _uauallyCheck)
r.Run(":8080")
}
func _uauallyCheck(c *gin.Context) {
type Users struct {
Name string `json:"name" binding:"required,min=3,max=12,len=8"`
// binding:"required" -> 传入的name字段不能是空,也不能不传
// binding:"min=3,max=12,len=8" -> 限制 最短:3 最长:12 长度:8
Age int `json:"age"`
// 针对与 数字
// eq 等于,如:binding:"eq=3"
// ne 不等于,如:binding:"ne=12"
// gt 大于,如:binding:"gt=10"
// gte 大于等于,如:binding:"gte=10"
// lt 小于,如:binding:"lt=10"
// lte 小于等于,如:binding:"lte=10"
Password string `json:"password"`
RePassword string `json:"re_password" binding:"eqfield=password"` // re_password要等与password(但是可以为空)
// 针对同级字段的
// eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
// nefield 不等于其他字段的值
}
var user Users
err := c.ShouldBindJSON(&user) // 绑定数据
if err != nil {
c.JSON(200, gin.H{"msg": err.Error})
return
}
c.JSON(200, user)
}
5.2 gin 内置验证器
// 枚举 只能是red 或green
oneof=red green
// 字符串
contains=fengfeng // 包含fengfeng的字符串
excludes // 不包含
startswith // 字符串前缀
endswith // 字符串后缀
// 数组
dive // dive后面的验证就是针对数组中的每一个元素
// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径
// 日期验证 1月2号下午3点4分5秒在2006年
datetime=2006-01-02
5.3 自定义错误验证
//路由
// 自定义响应错误信息
r.POST("/self_errors", _selfErrors)
=====================================
// 验证器错误信息处理器
// 接受一个 err和一个结构体的地址 返回一个错误信息
func selfCheckMsg(err error, obj any) string {
// 通过获取错误信息的类型
errType := reflect.TypeOf(obj)
// 通过断言来判定errType的内容
if errs, ok := err.(validator.ValidationErrors); ok {
// 如果断言成功就进行解析数据的错误 -- 循环遍历错误列表
for _, e := range errs {
// 通过的个错误的实例 解析错误信息
// 通过错误信息找到抛出错误的结构体字段 e.Field() --> 错误的名字
// errType.Elem().FieldByName() 拿到整个结构团体类型的数据 传入一个字段,找到相对相应字段的 一行信息
if f, is := errType.Elem().FieldByName(e.Field()); is {
// 通过结构体数据的一行数据信息 f.Tag.Get("msg") 获取具体Tag的的信息内容 --> msg的内容
errMsg := f.Tag.Get("msg")
return errMsg
}
}
}
return err.Error()
}
func _selfErrors(c *gin.Context) {
// 自定义错误的的验证
type UserInfo struct {
// 后面的msg -> 是自定义的tag 之后出错想要提示的信息内容
Name string `json:"name" binding:"min=3,max=12" msg:"姓名验证错误!"`
Age int `json:"age" binding:"gt=17" msg:"年龄信息验证错误!"`
}
var user UserInfo
// 绑定JSON数据
err := c.ShouldBindJSON(&user)
if err != nil {
// 如果存在错误信息,说明绑定出现了问题 - 系一个函数处理这个错误信息
c.JSON(http.StatusOK, gin.H{
"msg": selfCheckMsg(err, &user),
})
return
}
c.JSON(http.StatusOK, user)
}
- 结果
5.4 自定义验证器
// 注册验证器
// binding.Validator.Engine().(*validator.Validate) 断言这个验证器
if engine, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 主车一个名为 sing的验证其 验证函数是 checkValidate
engine.RegisterValidation("sign", checkValidate)
... 可以注册多个
}
// 路由
// 自定义验证器
r.POST("/self_check", _selfCheck)
=====================================
// 处理函数
func _selfCheck(c *gin.Context) {
// 自定义一个验证器
type SelfUser struct {
Name string `json:"name" binding:"sign" msg:"姓名已经存在!"`
}
var selfuser SelfUser
err := c.ShouldBindJSON(&selfuser)
if err != nil {
c.JSON(200, gin.H{"msg": selfCheckMsg(err, &selfuser)})
return
}
c.JSON(200, selfuser)
}
// 自定义数据验证函数
// 接收一个 f validator.FieldLevel
// 返回一个bool值
func checkValidate(f validator.FieldLevel) bool {
// 如果名字在这个里面就已经注册过,就报错
var nameList = []string{"aaa", "bbb", "ccc", "ddd"}
for _, name := range nameList {
// 断言传入的数据是string类型
nameStr := f.Field().Interface().(string)
if nameStr == name {
return false
}
}
return true
}