一 、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")
}
控制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}}
参考:https://learnku.com/docs/gin-gonic/1.7/examples-bind-uri/11391