x-www-form-urlencodedapplication/jsonx-www-form-urlencodedjson:"fieldname"BindBindJSONBindXMLBindQueryBindYAMLMustBindWithc.AbortWithError(400, err).SetType(ErrorTypeBind)Content-Typetext/plain; charset=utf-8[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422ShouldBindShouldBindShouldBindJSONShouldBindXMLShouldBindQueryShouldBindYAMLMustBindWithBindingWithbinding:"required"
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct {
Name string `json:"name" binding:"required"` // json格式从name取值,并且该值为必须的
Age int `json:"age" binding:"required,gt=20"` // json格式从age取值,并且该值为必须的,且必须大于20
}
func main() {
router := gin.Default()
router.POST("/test", func(context *gin.Context) {
var person Person
// 这里我确定传过来的一定是JSON所以用ShouldBindJSON,否则可以用ShouldBind
if err := context.ShouldBindJSON(&person); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
context.JSON(http.StatusOK, gin.H{
"success": true,
})
})
router.Run(":3000")
}
自定义验证器:
package main
import (
"net/http"
"reflect"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8"
)
type Booking struct {
// 这里的验证方法为bookabledate
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
// gtfield=CheckIn表示大于的字段为CheckIn
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
func bookableDate(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
// 这里有两个知识点,映射和断言
// 在这里,field是一个reflect.Type的接口类型变量,通过Interface方法获得field接口类型变量的真实类型,可以理解为reflect.Value的逆操作
// 在这里,断言就是将一个接口类型的变量转化为time.Time,前提是后者必须实现了前者的接口
// 综上,这里就是将field进行了类型转换
if date, ok := field.Interface().(time.Time); ok {
today := time.Now()
if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
return false
}
}
return true
}
func main() {
route := gin.Default()
// 注册自定义验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
route.GET("/bookable", getBookable)
route.Run(":8085")
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
中间件:
中间件可以在我们接受到一个http请求时,在handle之前或者handle之后做一些处理。通常,在handle之前,我们可以通过中间件很方便地进行校验,如果再handle之后,我们可以对response进行一些调整。
// 创建一个不包含中间件的路由器 gin.New() // 使用自定义中间件或者gin提供的中间件 gin.use(gin.Logger()) 可代替: gin.Default()
其实gin默认使用了Logger和Recovery两个中间件,然后也是在内部调用了New:
// gin.go
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery()) // 使用了Logger和Recovery两个中间件
return engine
}
我们对这两个中间件做一个简单的了解:
- Logger中间件可以让我们做打印的一些自定义配置
- Recovery中间件可以让我们从崩溃中恢复
func main() {
logfile, _ := os.Create("./logs/gin.log")
// 这里将log输出到指定文件
// 注意这个配置一定要在gin.Default()之前
gin.DefaultWriter = io.MultiWriter(logfile, os.Stdout)
router := gin.Default()
// 这里分别使用两个中间件
router.Use(gin.Logger())
router.Use(gin.Recovery())
router.POST("/test", func(context *gin.Context) {
var person Person
if err := context.ShouldBind(&person); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
context.JSON(http.StatusOK, gin.H{
"success": true,
})
})
router.Run(":3000")
}
自定义中间件:
// recovery.go
这里只要返回一个HandlerFunc类型即可
func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
// gin.go
HandlerFunc就是一个参数为*context的函数
type HandlerFunc func(*Context)
我们来写一个IP鉴权的中间件,假设我们的需求是只有白名单中的ip才可以访问服务器,那么我们可以这么实现:
ipauth.go
// ipauth.go
func Auth() gin.HandlerFunc {
return func(context *gin.Context) {
// 定义ip白名单
whiteList := []string{
"127.0.0.1",
}
ip := context.ClientIP()
flag := false
for _, host := range whiteList {
if ip == host {
flag = true
break
}
}
if !flag {
context.String(http.StatusNetworkAuthenticationRequired, "your ip is not trusted: %s", ip)
context.Abort()
}
}
}
main.go
// main.go
func main() {
router := gin.New()
router.Use(ipauth.Auth())
router.GET("/test", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"success": true,
})
})
router.Run(":3000")
}
Group中使用中间件
此外,我们的中间件可以不全局使用,而只针对部分的group:
func main() {
router := gin.Default()
// 定义了group
authorized := router.Group("/auth", ipauth.Auth())
// 对上面这个group进行路由绑定
authorized.GET("/write", handle)
router.GET("/read", handle)
router.Run(":3000")
}
func handle(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"success": true,
})
}
单个路由使用中间件:
func main() {
router := gin.Default()
// 注册一个路由,使用了 middleware1,middleware2 两个中间件
router.GET("/someGet", middleware1, middleware2, handler)
// 默认绑定 :8080
router.Run()
}
func handler(c *gin.Context) {
log.Println("exec handler")
}
func middleware1(c *gin.Context) {
log.Println("exec middleware1")
//你可以写一些逻辑代码
// 执行该中间件之后的逻辑
c.Next()
}
func middleware2(c *gin.Context) {
log.Println("arrive at middleware2")
// 执行该中间件之前,先跳到流程的下一个方法
c.Next()
// 流程中的其他逻辑已经执行完了
log.Println("exec middleware2")
//你可以写一些逻辑代码
}
c.Next()c.Next()c.Next()c.Next()
在中间件中使用goroutines
在中间件或处理程序中启动新的goroutine时,你不应该使用其中的原始上下文,你必须使用只读副本
func main() {
r := gin.Default()
r.GET("/long_async", func(c *gin.Context) {
// 创建要在goroutine中使用的副本
cCp := c.Copy()
go func() {
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// 这里使用你创建的副本
log.Println("Done! in path " + cCp.Request.URL.Path)
}()
})
r.Run(":3000")
}