一 、gin 入门
1. 安装gin :下载并安装 gin包:
$ go get -u github.com/gin-gonic/gin
2. 将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
3.初始化项目
go mod init gin
4.完整代码
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()
}
5.启动项目
go run main.go # 运行 main.go 并且在浏览器中访问 0.0.0.0:8080/ping
6.效果图


二、基础知识
go get -u
参数介绍:
-d 只下载不安装
-f 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用
-fix 在获取源码之后先运行 fix,然后再去做其他的事情
-t 同时也下载需要为运行测试所需要的包
-u 强制使用网络去更新包和它的依赖包
-v 显示执行的命
三、gin测试用例
AsciiJSON 生成具有转义的非 ASCII 字符的 ASCII-only JSON。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r:=gin.Default()
r.GET("/someJson", func(c *gin.Context) {
data:=map[string]interface{}{
"lang":"go 语言",
"tag":"<br>",
}
c.AsciiJSON(http.StatusOK,data)
})
r.Run(":8081")
}



为什么 浏览器和 终端命令行curl打印出来的结果格式不一样?gin.AsciiJSON
答:编码问题
绑定HTML复选框
package main
import "github.com/gin-gonic/gin"
type myForm struct {
Colors []string `form:"colors[]"`
}
func formHandler(c *gin.Context) {
var fakeForm myForm
c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors})
}
func innerHandler(c *gin.Context) {
c.HTML(200, "form.html", nil)
}
func main() {
r := gin.Default()
r.LoadHTMLGlob("views/*")
r.GET("/", innerHandler)
r.POST("/", formHandler)
r.Run(":8082")
}
views/form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/" method="POST">
<p>Check some colors</p>
<label for="red">Red</label>
<input type="checkbox" name="colors[]" value="red" id="red">
<label for="green">Green</label>
<input type="checkbox" name="colors[]" value="green" id="green">
<label for="blue">Blue</label>
<input type="checkbox" name="colors[]" value="blue" id="blue">
<input type="submit">
</form>
</body>
</html>


绑定查询字符串或表单数据
package main
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
//表单字符串
type PersonString struct {
Name string `form:"name"`
Address string `form:"address" `
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
//请求json格式
type PersonJson struct {
Name string `json:"name"`
Address string `json:"address"`
Birthday time.Time `json:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func startPage(c *gin.Context) {
var person PersonString
err := c.Bind(&person)
//ShouldBind 与 Bind 打印结果一样
//err:=c.ShouldBind(&person)
if err == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
log.Println("Binding success...")
} else {
log.Println("Binding failed...")
log.Println(err)
}
var personJson PersonJson
errJson := c.BindJSON(&personJson)
if errJson == nil {
log.Println(personJson.Name)
log.Println(personJson.Address)
//log.Println(personJson.Birthday)
log.Println("BindJson success...")
} else {
log.Println("BindJson failed...")
log.Println(errJson)
}
c.String(200, "success")
}
func main() {
log.Println("Hello World")
route := gin.Default()
route.GET("/testing", startPage)
route.Run(":8083")
}
请求:
string:
curl -X GET "http://0.0.0.0:8083/testing?name=appleboy&address=xyz&birthday=2023-03-15"
打印:结果
success

josn:
curl -X GET "http://0.0.0.0:8083/testing" --data '{"name":"JJ", "address":"xyz"}' -H "Content-Type:application/json"
结果:success

效果图:

绑定uri
package main
import "github.com/gin-gonic/gin"
type Person struct {
ID string `uri:"id" building:"required,uuid"`
Name string `uri:"name" building:"required"`
}
func main() {
r := gin.Default()
r.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err})
return
}
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
})
r.Run(":8080")
}

func main() {
r := gin.Default()
r.GET("/:name/:id", func(c *gin.Context) {
c.JSON(200, gin.H{
"name": c.Param("name"),
"id": c.Param("id"),
})
})
r.Run()
}

控制log 高亮输出,默认开启高亮
//强制log 高亮
gin.ForceConsoleColor()
//关闭log高亮
//gin.DisableConsoleColor()

6.自定义http配置:(监听端口,读写时间,最大读写字节数)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
//两行作用一样,谁先执行谁生效
//r.Run(":8080")
//http.ListenAndServe(":8081",r)
s := &http.Server{
Addr: ":8084",
Handler: r, //实例句柄
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}
自定义log文件
func main() {
r := gin.New()
r.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
params.ClientIP,
params.TimeStamp.Format(time.RFC3339),
params.Method,
params.Path,
params.Request.Proto,
params.StatusCode,
params.Latency,
params.Request.UserAgent(),
params.ErrorMessage,
)
}))
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
r.Run(":8080")
}

自定义中间件
//自定义中间件
func logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
//设置变量
c.Set("lxw", "this is string")
c.Set("lxwInt", 123456789)
//请求前
c.Next()
//请求后
latency := time.Since(t)
log.Println(latency) //花费时间
//获取发送的code
statusCode := c.Writer.Status()
log.Println(statusCode)
}
}
func main() {
r := gin.New()
r.Use(logger()) //引用(相当于include)
r.GET("/test", func(c *gin.Context) {
/*** MustGet returns the value for the given key if it exists, otherwise it panics.
返回给定键的值(如果存在),否则会死机*/
varInt := c.MustGet("lxwInt").(int) //int 是对变量lxwInt 的限制要求,若不是int则报错
varString := c.GetString("lxw")
log.Println(varInt,varString)
})
r.Run(":8080")
}

自定义验证器
//自定义验证器
//输入和输出日期必填;输出日期大于输入日期;输入和输出日期符合自定的验证器(日期需在今天日期之后)
type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn bookabledate" time_format:"2006-01-02"`
}
//验证器
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
date, ok := fl.Field().Interface().(time.Time)
if ok {
today := time.Now()
if today.After(date) {
return false
}
}
return true
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "book data is validate"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
r.GET("/bookable", getBookable)
r.Run(":8080")
}

请求地址:输入和输出日期必填;输出日期大于输入日期;输入和输出日期符合自定的验证器(日期需在今天日期之后)
http://0.0.0.0:8080/bookable?check_in=2023-02-08&check_out=2023-03-09
定义路由日志格式
//定义路由日志格式
func main() {
r := gin.Default()
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
}
r.POST("/aaa", func(c *gin.Context) {
c.JSON(http.StatusOK, "aaa")
})
r.GET("/bbb", func(c *gin.Context) {
c.JSON(http.StatusOK, "bbb")
})
r.GET("/ccc", func(c *gin.Context) {
c.JSON(http.StatusOK, "ccc")
})
r.Run()
}

优雅的关机或重启
//优雅的关机或重启
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "welcome server")
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
//服务连接:打印链接错误信息
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s \n", err)
}
}()
//等待中断信号优雅关闭服务器(设置5秒的超时时间)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("shutdown server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
//关机失败原因
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server shutdown:", err)
}
log.Println("server exiting")
}

路由组
func main() {
r := gin.Default()
v1 := r.Group("/v1")
{
v1.GET("/aa", loginEndpoint)
v1.GET("/bb", exit)
}
r.Run()
}
func loginEndpoint(c *gin.Context) {
c.String(200,"hello")
}
func exit( context2 *gin.Context) {
context2.String(200,"world")
}


记录日志到文件和控制台(默认直接打印在控制台)
func main() {
//禁用控制台颜色,将日志写入文件时不需要控制台颜色(加不加无所谓)
//gin.DisableConsoleColor()
//创建日志文件
fileName, _ := os.Create("gin.log")
//将日志并发写入文件
gin.DefaultWriter = io.MultiWriter(fileName)
//将日志同时写入文件和控制台
//gin.DefaultWriter = io.MultiWriter(fileName, os.Stdout)
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
r.Run()
}

html渲染模版
默认的解析标签是{{}},可以用 r.Delims("{", "}")修改
LoadHTMLGlob 加载的目录下不能有图片等非模版文件,可以用tmpl,html,htm
存在同名文件时,必须用define 关键字进行文件开头申明{{define "user/a.html"}},{{end}}结束申明,否则报错文件未申明;html/template: "user/a.html" is undefined
func main() {
r := gin.Default()
//LoadHTMLGlob 加载的目录下不能有图片等非模版文件
//r.LoadHTMLGlob("views/*")
//r.LoadHTMLGlob("user/*")
//加载不是同一个目录下的模版文件
//r.LoadHTMLFiles("views/a.html","user/a.html")
//自定义变量解析符号
r.Delims("{", "}")
r.SetFuncMap(template.FuncMap{
"customDate2": customDate,
})
r.LoadHTMLFiles("views/a.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "this is title",
"body": "this is views/index.tmpl",
})
})
r.GET("/a", func(c *gin.Context) {
c.HTML(http.StatusOK, "a.html", gin.H{
"title": "this is a title",
"body": "this is views/a.html",
})
})
r.GET("/date", func(c *gin.Context) {
c.HTML(http.StatusOK, "a.html", map[string]interface{}{
"now_time": time.Date(2023, 02, 02, 22, 33, 44, 55, time.UTC),
})
})
//存在同名文件时,必须用define 关键字进行文件开头申明{{define "user/a.html"}},{{end}}结束申明
//否则报错文件未申明;html/template: "user/a.html" is undefined
r.GET("/aa", func(c *gin.Context) {
c.HTML(http.StatusOK, "user/a.html", gin.H{
"title": "this is a title",
"body": "this is user/a.html",
})
})
r.Run()
}
//自定义时间
func customDate(t time.Time) string {
y, m, d := t.Date()
return fmt.Sprintf("%d-%02d-%02d", y, m, d)
//y, m, d ,h,i,s:=time.Date()
//return fmt.Sprintf("%d-%02d-%02d %d:%d:%d", y, m, d,h,s,i)
}
view/a.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- 自定义变量解析符号-->
<title>{.title}</title>
</head>
<body>
<h2>{.body}</h2>
Date: {.now_time}
<br>
Date2: {.now_time|customDate2}
</body>
</html>
user/a.html
{{define "user/a.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{.title}}</title>
</head>
<body>
<h2>{{.body}}</h2>
</body>
</html>
{{end}}

http2 server 推送
jsonp 发送数据
//jsonp
func main() {
r := gin.Default()
r.GET("/jsonp", func(c *gin.Context) {
data := map[string]interface{}{
"name": "lxw",
}
c.JSONP(http.StatusOK, data)
})
r.Run()
}
//context包中callback 默认写法
func (c *Context) JSONP(code int, obj any) {
callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
}
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
}


16. Multipart/Urlencoded 绑定,参数绑定验证(登录验证是使用)
type LoginForm struct {
User string `form:"user" binding:"required"`
Pwd string `form:"password" binding:"required"`
}
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
var form LoginForm
if err := c.ShouldBind(&form); err == nil {
//验证用户密码是否匹配
if form.User == "张三" && form.Pwd == "123456" {
c.JSON(200, gin.H{"msg": "login ok!"})
} else {
c.JSON(401, gin.H{"msg": "login fail"})
}
} else {
c.JSON(401, gin.H{"msg": "参数缺失", "err": err.Error()})
}
})
r.Run()
}

传入空数据

如果给错字段名:password 给错password2

Multipart/Urlencoded 表单:使用场景哪些?
func main() {
r := gin.Default()
r.POST("/form_post", func(c *gin.Context) {
message := c.PostForm("message")
nickName := c.DefaultPostForm("nick", "默认名")
c.JSON(200, gin.H{"status": http.StatusOK, "message": message, "nick": nickName})
})
r.Run()
}

17.只绑定 url 查询字符串(只打印绑定的参数)
type Person12 struct {
Name string `form:"name"`
Addr string `form:"addr"`
}
func startPage12(c *gin.Context) {
var person Person12
if err:=c.ShouldBind(&person);err == nil {
log.Println("只打印get请求参数:",person.Name, person.Addr)
}else{
log.Printf("错误:%v",err.Error())
}
c.String(200, "success")
}
func main() {
r := gin.Default()
r.Any("/test", startPage12)
r.Run()
}

18.获取路径中的参数
func main() {
r:=gin.Default()
// http://0.0.0.0:8080/user/lxw
r.GET("/user/:name", func(c *gin.Context) {
name:=c.Param("name")
c.String(http.StatusOK,"hello %s",name)
})
//匹配:http://0.0.0.0:8080/user/lxw/read
r.GET("/user/:name/:action", func(c *gin.Context) {
name:=c.Param("name")
action:=c.Param("action")
//message:=name+action
c.String(http.StatusOK,name+" is "+action)
})
r.Run()
}



purejson(没有明确这个东西存在意义是啥?区别?)
func main() {
r:=gin.Default()
// 提供 unicode 实体
r.GET("/test", func(c *gin.Context) {
c.JSON(200,gin.H{
"echo":"<h1>hello lxw</h1>",
})
})
// 提供字面字符
r.GET("/test2", func(c *gin.Context) {
c.PureJSON(200,gin.H{
"echo":"<h1>hello lxw</h1>",
})
})
r.Run()
}


17.post 表单中的url参数
//post 表单中的url参数
func main() {
r:=gin.Default()
r.POST("/test", func(c *gin.Context) {
id:=c.Query("id")
page:=c.DefaultQuery("page","0")
name:=c.PostForm("name")
fmt.Printf("id: %s; page: %s; name: %s", id, page, name)
fmt.Println("ok")
c.JSON(200,"log ok")
})
r.Run()
}

18.重定向(http 重定向&路由重定向)
func main() {
r:=gin.Default()
//http 重定向
r.GET("/test4", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently,"http://www.baidu.com/")
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(200,gin.H{"11":"11"})
c.Request.URL.Path="/test3"
r.HandleContext(c)
c.JSON(200,gin.H{"111":"22"})
})
//http://0.0.0.0:8081/test2 {"11":"11"}{"name":"lxw"}{"111":"22"}
r.GET("/test3", func(c *gin.Context) {
c.JSON(200,gin.H{"name":"lxw"})
})
r.Run(":8081")
}
http://0.0.0.0:8081/test4 重定向跳转到baidu.com 页面

19.SecureJSON
func main() {
r := gin.Default()
//r.SecureJsonPrefix("test_") //输出:test_["lxw","zh","cha"]
r.GET("/test", func(c *gin.Context) {
names := []string{"lxw", "zh", "cha"}
c.SecureJSON(http.StatusOK, names)
})
//不带前缀:
/*while(1);[
"lxw",
"zh",
"cha"
]*/
r.Run(":8081")
}


20.从reader读取数据
func main() {
r := gin.Default()
r.GET("/read", func(c *gin.Context) {
//读取文件到response
response, err := http.Get("https://p0.ssl.qhimgs1.com/sdr/400__/t01043eec7c54708f40.jpg")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
}
//获取文件主体 长度 类型
reader := response.Body
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
//生成文件头
extraHeader := map[string]string{
"Content-Disposition": `attachment;filename="gopher.jpg"`, //给文件重命名
}
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeader)
})
r.Run(":8081")
}

21.静态文件服务
func main() {
r := gin.Default()
r.Static("/static", "./views") //访问view文件夹下的所有文件
r.StaticFS("/more_static", http.Dir("views")) //访问view文件夹下的所有文件
r.StaticFile("/1.jpeg", "./1.jpeg") //z访问单个文件
r.Run()
}
参考:https://learnku.com/docs/gin-gonic/1.7/examples-bind-uri/11391