gin的覆盖测试无覆盖率的问题,是因为不是直接调用的函数而是通过模拟请求来触发的函数
添加自定义验证器
// 定义方法
func CustomTimeFormat(fl validator.FieldLevel) bool {
_, err := time.Parse("15:04", fl.Field().String())
if err != nil {
return false
}
return true
}
// 将自定义验证器注册到默认的验证器实例中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("CustomTimeFormat", xvalidator.CustomTimeFormat)
}
type D struct {
ServiceTime string `json:"serviceTime" binding:"required,CustomTimeFormat"`
}
常用binding
binding:"lt=0" // 小于
binding:"gt=0" // 小于
binding:"lte=0" // 小于等于
binding:"gte=0" // 大于等于
binding:"required" // 必填
binding:"omitempty,oneof=1 2" // 1或2
更多参考:https://github.com/go-playground/validator
type appkey struct {
Appkey string `json:"appkey" form:"appkey"`
}
type QueryTest struct {
Page int `json:"page" form:"page"`
Size int `json:"size" form:"size"`
appkey
}
type FooStruct struct {
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
}
type FooBarStruct struct {
FooStruct
Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
}
type FooBarFileStruct struct {
FooBarStruct
File *multipart.FileHeader `form:"file" binding:"required"`
}
type FooBarFileFailStruct struct {
FooBarStruct
File *multipart.FileHeader `invalid_name:"file" binding:"required"`
// for unexport test
data *multipart.FileHeader `form:"data" binding:"required"`
}
type FooDefaultBarStruct struct {
FooStruct
Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"`
}
type FooStructUseNumber struct {
Foo any `json:"foo" binding:"required"`
}
type FooStructDisallowUnknownFields struct {
Foo any `json:"foo" binding:"required"`
}
type FooBarStructForTimeType struct {
TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"`
TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
UnixTime time.Time `form:"unixTime" time_format:"unix"`
}
type FooStructForTimeTypeNotUnixFormat struct {
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
UnixTime time.Time `form:"unixTime" time_format:"unix"`
}
type FooStructForTimeTypeNotFormat struct {
TimeFoo time.Time `form:"time_foo"`
}
type FooStructForTimeTypeFailFormat struct {
TimeFoo time.Time `form:"time_foo" time_format:"2017-11-15"`
}
type FooStructForTimeTypeFailLocation struct {
TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_location:"/asia/chongqing"`
}
type FooStructForMapType struct {
MapFoo map[string]any `form:"map_foo"`
}
type FooStructForIgnoreFormTag struct {
Foo *string `form:"-"`
}
type InvalidNameType struct {
TestName string `invalid_name:"test_name"`
}
type InvalidNameMapType struct {
TestName struct {
MapFoo map[string]any `form:"map_foo"`
}
}
type FooStructForSliceType struct {
SliceFoo []int `form:"slice_foo"`
}
type FooStructForStructType struct {
StructFoo struct {
Idx int `form:"idx"`
}
}
type FooStructForStructPointerType struct {
StructPointerFoo *struct {
Name string `form:"name"`
}
}
type FooStructForSliceMapType struct {
// Unknown type: not support map
SliceMapFoo []map[string]any `form:"slice_map_foo"`
}
type FooStructForBoolType struct {
BoolFoo bool `form:"bool_foo"`
}
type FooStructForStringPtrType struct {
PtrFoo *string `form:"ptr_foo"`
PtrBar *string `form:"ptr_bar" binding:"required"`
}
type FooStructForMapPtrType struct {
PtrBar *map[string]any `form:"ptr_bar"`
}
实现一个客户端长链接
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
func main() {
r := gin.Default()
r.GET("/stream", func(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("Access-Control-Allow-Origin", "*")
messageChan := make(chan string)
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
messageChan <- "This is a message"
}
}
}()
for {
message := <-messageChan
fmt.Fprintf(c.Writer, "data: %s\n\n", message)
c.Writer.Flush()
}
})
r.Run(":8080")
}