前提概要
- Gin 是一款高性能的、简单轻巧的HTTP Web 框架。
- 安装方式go get -u github.com/gin-gonic/gin
库重点概述
- Gin Mode
const ( // 开发模式(默认模式) DebugMode = "debug" // 发行模式 ReleaseMode = "release" // 测试模式 TestMode = "test" )- 更改Gin 模式:
gin.SetMode(gin.ReleaseMode)
- 更改Gin 模式:
- type Engine struct:Gin框架的实例,包含复用器、中间件和配置设置。可使用New()或Default()创建Engine 实例
engine := gin.Default() //默认的engine已自带了Logger和Recovery两个中间件 engine := gin.New() //New 返回一个不附加任何中间件的新空白引擎实例 - type Context struct:Context 是Gin 最重要的部分。它允许我们在中间件之间传递变量,管理流程,验证请求的JSON并呈现JSON响应。你所需要的东西全都封装在了Context里面。
路由分组
(*gin.RouterGroup).Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroupGroup 创建一个新的路由分组,可以添加具有相同中间件或路由前缀的路由(*gin.Context).String(code int, format string, values ...any)将给定的text/plain类型字符串写入响应正文。
func boy(c *gin.Context) { //你所需要的东西全都封装在了gin.Context里面,包括http.Request和ResponseWriter
c.String(http.StatusOK, "hi boy")
}
func girl(c *gin.Context) {
c.String(http.StatusOK, "hi girl")
}
func main() {
engine := gin.Default() //默认的engine已自带了Logger和Recovery两个中间件
engine.GET("/", boy)
engine.POST("/", girl)
//路由分组
oldVersion := engine.Group("/v1")
oldVersion.GET("/student", boy) //http://localhost:5656/v1/student
oldVersion.GET("/teacher", boy) //http://localhost:5656/v1/teacher
newVersion := engine.Group("/v2")
newVersion.GET("/student", girl) //http://localhost:5656/v2/student
newVersion.GET("/teacher", girl) //http://localhost:5656/v2/teacher
engine.Run(":5656")
}
参数获取
- c.Query() 从GET 请求的URL 中获取参数。
engine.GET("/student", func(ctx *gin.Context) { name := ctx.Query("name") addr := ctx.DefaultQuery("addr", "China") //如果没传addr参数,则默认为China ctx.String(http.StatusOK, name + " live in " + addr) }) - c.Param()从Restful 风格的URL 中获取参数。
engine.GET("/student/:name/*addr", func(ctx *gin.Context) { name := ctx.Param("name") addr := ctx.Param("addr") ctx.String(http.StatusOK, name+" live in "+addr) }) - c.PostForm() 从POST 表单中获取参数。
engine.POST("/student", func(ctx *gin.Context) { name := ctx.PostForm("name") addr := ctx.DefaultPostForm("addr", "China") //如果没传addr参数,则默认为China ctx.String(http.StatusOK, name+" live in "+addr) })- 用postman 模拟一个POST 请求
- 用postman 模拟一个POST 请求
- c.FormFile() 获取上传的文件,消息类型为form-data。
engine.MaxMultipartMemory = 8 << 20//限制表单上传大小为8M,默认上限是32M engine.POST("/upload", func(ctx *gin.Context) { file, err := ctx.FormFile("file") if err != nil { fmt.Printf("get file error %v\n", err) ctx.String(http.StatusInternalServerError, "upload file failed") } else { ctx.SaveUploadedFile(file, "./data/"+file.Filename) //把用户上传的文件存到data目录下 ctx.String(http.StatusOK, file.Filename) } }) - c. MultipartForm() multipart/form-data 可以上传多个form-data 类消息并且用分隔符进行分割。多个key可以都叫files,value对应不同的文件
engine.POST("/upload_files", func(ctx *gin.Context) { form, err := ctx.MultipartForm() //MultipartForm中不止包含多个文件 if err != nil { ctx.String(http.StatusBadRequest, err.Error()) } else { //从MultipartForm中获取上传的文件 files := form.File["files"] for _, file := range files { ctx.SaveUploadedFile(file, "./data/"+file.Filename) } ctx.String(http.StatusOK, "upload "+strconv.Itoa(len(files))+" files") } }) - 提交JSON格式数据(XML、YAML同)
engine.POST("/stu/json", func(ctx *gin.Context) { var stu Student if err := ctx.ShouldBindJSON(&stu); err != nil { fmt.Println(err) ctx.String(http.StatusBadRequest, "parse paramter failed") } else { ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr) } })
生成response
c.String(code int, format string, values ...any): response Content-Type=text/plain。c.JSON(code int, obj any): response Content-Type= application/json。c.JSONP(code int, obj any):如果请求参数里有callback=xxx,则response Content-Type为application/javascript,否则response Content-Type为application/jsonc.XML(): response Content-Type= application/xml。c.HTML(): 前端写好模板,后端往里面填值。c.Redirect(): 重定向。
演示JSON()、HTML()、Redirect(),XML()和YAML()与JSON()类似不做演示
var stu struct{
Name string
Addr string
}
func json(engine *gin.Engine) {
engine.GET("/user/json1", func(c *gin.Context) {
c.JSON(http.StatusOK, stu)////response Content-Type:application/json
//也可用自带的map[string]any “gin.H”来提交信息
c.JSON(http.StatusOK, gin.H{"name": "lfd", "addr": "cd"})
})
}
func html(engine *gin.Engine) {
engine.LoadHTMLFiles("http/static/template.html")//此处填写自己html的路径
engine.GET("/user/html", func(c *gin.Context) {
//通过json往前端页面上传值
c.HTML(http.StatusOK, "template.html", gin.H{"title": "用户信息", "name": "lfd", "addr": "cd"})
})
}
func redirect(engine *gin.Engine) {
engine.GET("/not_exists", func(c *gin.Context) {//重定向到html页面
c.Redirect(http.StatusMovedPermanently, "http://localhost:5656/user/html")
})
}
func main() {
engine := gin.Default()
stu.Name = "lfd"
stu.Addr = "cd"
json(engine) //http://localhost:5656/user/json
html(engine) //http://localhost:5656/user/html
redirect(engine) //http://localhost:5656/not_exists
engine.Run(":5656")
}
参数检验
-
结构体
type Student struct { Name string `form:"name" binding:"required"` //required:必须上传name参数 Score int `form:"score" binding:"gt=0"` //score必须为正数 Enrollment time.Time `form:"enrollment" binding:"required,before_today" time_format:"2006-01-02" time_utc:"8"` //自定义验证before_today,日期格式东8区 Graduation time.Time `form:"graduation" binding:"required,gtfield=Enrollment" time_format:"2006-01-02" time_utc:"8"` //毕业时间要晚于入学时间 } -
自定义验证器
type Func func(fl FieldLevel) bool:Func 接受满足所有验证需求的字段级别接口。验证成功时,返回值应为 true。type FieldLevel interface:FieldLevel包含用于验证字段的所有信息和帮助类函数Field() reflect.Value:返回当前字段的reflect.value,对反射不了解的可查看go标准库中的reflect库
var beforeToday validator.Func = func(fl validator.FieldLevel) bool { if date, ok := fl.Field().Interface().(time.Time); ok {//将reflect.Value转换为interface后强制转换为Time today := time.Now() if date.Before(today) { return true } else { return false } } else { return false } } -
错误判断
func processErr(err error) { if err == nil { return } //给Validate.Struct()函数传了一个非法的参数 invalid, ok := err.(*validator.InvalidValidationError) if ok { fmt.Println("param error:", invalid) return } //ValidationErrors是一个错误切片,它保存了每个字段违反的每个约束信息 validationErrs := err.(validator.ValidationErrors) for _, validationErr := range validationErrs { fmt.Printf("field %s 不满足条件 %s\n", validationErr.Field(), validationErr.Tag()) } } -
注册验证器
var Validator StructValidator = &defaultValidator{}:Validator 是StructValidator 接口的默认Validatortype StructValidator interface:StructValidator 是需要实现的最小接口,以便将其用作验证器引擎,以确保请求的正确性。 Gin 为此提供了一个默认实现github.com/go-playgrou….Engine()any:返回一个能实现StructValidator 的底层validator engine
(v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error:添加具有给定tag的验证
engine := gin.Default() //注册验证器 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("before_today", beforeToday)//tag为"before_today",验证时field只有Enrollment } engine.GET("/", func(ctx *gin.Context) { var stu Student if err := ctx.ShouldBind(&stu); err != nil { processErr(err) //校验不符合时,打印出哪时不符合 ctx.String(http.StatusBadRequest, "parse parameter failed") } else { // ctx.JSON(http.StatusOK, stu) bs, _ := json.Marshal(stu) ctx.String(http.StatusOK, string(bs)) } }) //http://localhost:5656?name=lfd&score=100&enrollment=2021-08-23&graduation=2021-09-23 engine.Run(":5656")
中间件
- 计时中间件,返回gin.HandleFunc
func timeMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
begin := time.Now()
ctx.Next() //执行业务逻辑
timeElapsed := time.Since(begin)
log.Printf("request %s use %d ms\n", ctx.Request.URL.Path, timeElapsed.Milliseconds())
}
}
- 可用
engine.Use(timeMiddleWare())添加全局中间件 - 也可在请求中添加局部的中间件
engine.GET("/", limitMiddleWare(), func(ctx *gin.Context) { ctx.String(http.StatusOK, "welcome home!~") })
Cookie
- HTTP 是无状态的,即服务端不知道两次请求是否来自于同一个客户端。
- Cookie 由服务端生成,发送给客户端,客户端保存在本地。
- 客户端每次发起请求时把Cookie 带上,以证明自己的身份
- 。HTTP请求中的Cookie头只会包含name 和value 信息(服务端只能取到name 和value ),domain、path、expires 等Cookie 属性是由浏览器使用的,对服务器来说没有意义。
- Cookie 可以被浏览器禁用。
实例
- Cilent端通过login后得到Cookie 后登录cnter ,center带有Cookie 检测中间件。
Client端
func authLogin() {
if resp, err := http.Post("http://127.0.0.1:5656/login", "text/plain", nil); err != nil {
panic(err)
} else {
loginCookies := resp.Cookies() //读取服务端返回的Cookie
if req, err := http.NewRequest("POST", "http://127.0.0.1:5656/center", nil); err != nil {
panic(err)
} else {
//下次请求再带上cookie
for _, cookie := range loginCookies {
fmt.Printf("receive cookie %s = %s\n", cookie.Name, cookie.Value)
// cookie.Value += "1" //修改cookie后认证不通过
req.AddCookie(cookie)
}
client := &http.Client{}
if resp, err := client.Do(req); err != nil {
fmt.Println(err)
} else {
fmt.Println("成功登录用户中心")
}
}
}
}
Server端
var (
authMap sync.Map //并发安全的表,存储Cookie
)
//cookie name需要符合规则,否则该cookie会被Gin框架默默地丢弃掉
func genCookieName(ctx *gin.Context) string {
return base64.StdEncoding.EncodeToString([]byte(ctx.Request.RemoteAddr))
}
//登录
func login(engine *gin.Engine) {
engine.POST("/login", func(ctx *gin.Context) {
//为客户端生成cookie
cookie_key := genCookieName(ctx)//将Client 的地址当作Key
cookie_value := strconv.Itoa(1)
//服务端维护所有客户端的cookie,用于对客户端进行认证
authMap.Store(cookie_key, cookie_value)
//把cookie发给客户端
ctx.SetCookie(cookie_key, cookie_value,
3000, //maxAge,cookie的有效时间,时间单位秒
"/", //path,cookie存放目录
"localhost", //cookie从属的域名
false, //是否只能通过https访问
true, //是否允许别人通过js获取自己的cookie
)
fmt.Printf("set cookie_key %s \n cookie_value %s to client\n", cookie_key, cookie_value)
ctx.String(http.StatusOK, "登录成功")
})
}
//用户中心
func userCenter(engine *gin.Engine) {
engine.POST("/center", authMiddleWare(), func(ctx *gin.Context) { //为"/center"加个认证中间件
ctx.String(http.StatusOK, "您已通过身份认证,这里是你的私人空间")
})
}
func authMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
cookie_key := genCookieName(ctx)
var cookie_value string
//读取客户端的cookie
for _, cookie := range ctx.Request.Cookies() {
if cookie.Name == cookie_key {
cookie_value = cookie.Value
break
}
}
//验证Cookie Value是否正确
if v, ok := authMap.Load(cookie_key); !ok {
fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
ctx.Abort() //验证不通过,调用Abort
} else {
if v.(string) == cookie_value {
ctx.Next() //本中间件顺利通过
} else {
fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
ctx.Abort() //验证不通过,调用Abort
}
}
}
}
func main() {
engine := gin.Default()
//路由
login(engine)
userCenter(engine)
engine.Run("127.0.0.1:5656") //测试方法,运行http/client/main.go里的authLogin()方法
}