gin框架基础

一个简易示范

package router

import (
    "fmt"
    "github.com/gin-gonic/gin"               //导入gin包
    "github.com/gin-gonic/gin/binding"       //自定义验证器需要
    "github.com/go-playground/validator/v10" //导入自定义验证器
    "reflect"                                //验证器定义错误需要
    "time"
)

// 响应json结构体,函数
func _Json(ctx *gin.Context) {
    type apicalss struct {
        Stuk  int
        Msg   string
        Is_ok string
    }
    jieguo := apicalss{200, "你好呀", "ok"} //指定jieguo变量类型为apicalss结构体,并赋值
    ctx.JSON(200, jieguo)
}

// 响应xml结构体,函数
func _Xml(ctx *gin.Context) {
    type apicalss struct {
        Stuk  int
        Msg   string
        Is_ok string
    }
    jieguo := apicalss{200, "你好呀", "ok"} //指定jieguo变量类型为apicalss结构体,并赋值
    ctx.XML(200, jieguo)
}

// 响应查询参数
func _Query(c *gin.Context) {
    user := c.Query("user")        //获取user参数的内容
    name, _ := c.GetQuery("name")  //获取name参数,并判断是否传入了该参数,第二个返回值是获取的结果
    names := c.QueryArray("names") //获取多个相同参数名的值,获得的值是数组
    var name2 string               //定义空字符
    for _, name1 := range names {  //遍历数组
        name2 = name2 + name1 //拼接字符串
    }
    // name3 := c.DefaultQuery("name","小明") //获取name的参数没如果为空则使用默认值小明
    c.String(200, "你输入的user是:"+user+",name是:"+name+"names是:"+name2)
}

// 响应路由参数
func _Param(c *gin.Context) {
    page := c.Param("page") //获取路由参数
    name := c.Param("name")
    c.JSON(200, gin.H{
        "Page": page,
        "Name": name,
    })
}

// 异步携程
func _Async(c *gin.Context) {
    // 创建在 goroutine 中使用的副本
    cCp := c.Copy() //复制上下文
    go func() {     //
        // time.Sleep(5 * time.Second)  //假设这是用时很长的任务
        // 在写成中如果还要访问gin的请求上下文信息,记得使用 "cCp"副本,这一点很重要
        fmt.Print(cCp.Query("user"))
    }()
}

// 响应表单
func _Form(c *gin.Context) {
    // name := c.PostForm("ceshi") //获取表单name为ceshi的值
    // names := c.PostFormArray("ceshi") //获取多个同名字段的值,获得的是数组
    // name2 := c.DefaultPostForm("ceshi","小明") //获取表单字段值,如果没有传入则使用默认值
    forms, err := c.MultipartForm()

    // c.String(200, forms.ceshi)
    fmt.Println(forms, err)
}

// 设置cookie
func _SetCookie(c *gin.Context) {
    cookie, err := c.Cookie("ceshi_cookie")
    //判断cookie是否存在
    if err != nil {
        //如果读取未获取到
        c.SetCookie("ceshi_cookie", "test1", 3600, "/", "", false, true) //设置cookie,部署时必须指定域名,开发环境留空本地测试
        c.String(200, "设置成功")
    } else {
        c.String(200, "已经存在cookie:"+cookie)
    }
}

// 删除cookie
func _DelCookie(c *gin.Context) {
    c.SetCookie("ceshi_cookie", "", -1, "/", "", false, true) //删除cookie
    c.String(200, "删除成功")
}

// 定义user验证器结构体
// `form:"user" binding:"required"`
type User struct {
    Name       string    `form:"name" json:"name" xml:"name" binding:"required" msg:"用户名不能为空"`         //用户名
    Password   string    `json:"password" binding:"required,min=5,max=10" msg:"密码不符合规范,5-10个字符"`       //密码,必填,小于5,大于10位数
    Repassword string    `json:"repassword" binding:"eqfield=Password" msg:"repassword重复密码错误"`         //重复密码
    Like       []string  `json:"like" binding:"dive,required" msg:"like字段鄙俗是数组"`                       //爱好,数组
    Date       time.Time `json:"date" binding:"required,defdate" time_format:"2006-01-02" msg:"已超过时间"` //时间,time_format是指定时间格式
}

//定义一个需要验证器的路由
func _Sign(c *gin.Context) {
    var json User                  //定义变量user为User结构体
    err := c.ShouldBindJSON(&json) //执行json绑定

    if err != nil { //如果出现错误
        c.JSON(400, gin.H{"msg": GetValidMsg(err, &json)}) //用json返回错误信息,这里调用了自定义错误函数
        return                                             //停止运行
    }
    //下面是编写验证器通过后的逻辑
    c.JSON(200, gin.H{"data": json})
}

// 自定义验证器的处理方法,定义变量类型为验证器,方法,返回布尔
func Defdate(fl validator.FieldLevel) bool {
    date, ok := fl.Field().Interface().(time.Time) //尝试将获取的时间字段转换为时间类型
    if ok {                                        // 检查是否成功转换为时间类型
        today := time.Now()    // 获取当前时间
        if today.After(date) { // 如果当前时间晚于验证的日期时间,返回 false 表示验证失败
            return false
        }
    }
    return true //返回true则代表通过
}

// 自定义验证器结构体的错误信息,接收错误对象和结构体的指针,返回str类型
func GetValidMsg(err error, obj interface{}) string {
    //获取结构体的类型信息
    getObj := reflect.TypeOf(obj)
    // 检查错误是否属于validator.ValidationErrors类型
    if errs, ok := err.(validator.ValidationErrors); ok {
        for _, e := range errs {
            // 检查是否可以通过字段名获取结构体字段信息
            if f, exist := getObj.Elem().FieldByName(e.Field()); exist {
                return f.Tag.Get("msg") //获取自定义的结构体msg参数
            }
        }
    }
    // 如果无法通过验证器错误获取自定义消息,返回默认错误消息
    return err.Error()
}

func Router() *gin.Engine {
    r := gin.Default()              //初始化gin
    r.LoadHTMLGlob("templates/*")   //加载html模板全局,指定templates目录下的所有文件
    r.Static("/static", "./static") //静态文件处理,参数:路由,本地路径

    //注册自定义验证器 "defdate" 到 Gin 验证器引擎
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("defdate", Defdate)
    }

    r.GET("/", func(ctx *gin.Context) { //定义了一个路由和方法,定义了一个*gin.Context的上下文参数,类似django视图的request参数
        // ctx.GetHeader("User-Agent")  //获取某个请求头的值,首字母不用区分大小写,都可以
        // fmt.Println(ctx.Request.Header)  //取所有请求头,返回一个map对象
        ctx.HTML(200, "index.html", gin.H{"nihao": "哈哈"}) //指定了index.html文件和需要传给模板的变量
    })

    r.GET("/param/:page/:name", _Param) //传参路由
    r.POST("/form", _Form)              //表单路由
    r.GET("/cookie", _SetCookie)        //设置cookie
    r.GET("/delcookie", _DelCookie)     //删除cookie
    user := r.Group("/user")            //创建分组路由
    r.POST("/sign", _Sign)              //注册路由
    {
        user.POST("/post", func(ctx *gin.Context) { //继承分组路由,此时这里应是/user/post
            ctx.String(200, "这是post") //返回字符串,第一个参数是状态码,第二参数是返回的字符串
        })

        user.GET("/", func(ctx *gin.Context) {
            ctx.JSON(200, gin.H{"类型": "GET"}) //这里的gin.H方法是包装一个类似map有键名和兼值的对象,使用json方法返回
        })

    }

    api := r.Group("/api")
    {
        api.GET("/json", _Json)   //调用json结构体
        api.GET("/xml", _Xml)     //调用mxl结构体
        api.GET("/query", _Query) //调用Query函数方法
    }

    // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
    // r.MaxMultipartMemory = 32 << 20 // 32

    r.POST("/upload", func(c *gin.Context) {
        // 单文件
        file, _ := c.FormFile("ceshi2")           //取得文件
        dst := "./static/upload/" + file.Filename //指定上传路径和文件名
        // 上传文件至指定的完整文件路径
        c.SaveUploadedFile(file, dst) //保存或更新文件
        c.Redirect(307, "/")          //重新定向到路由
    })

    r.GET("/async", _Async) //使用Goroutine协程,异步线程操作

    return r
}

强制结束程序,终止程序

c.Abort()

验证器

// 定义user验证器结构体
//可以是form,json,xml的传递,名字对应提交的名字
type User struct {
    Name   string `form:"name" json:"name" xml:"name" binding:"required"` //用户名
    Password string `json:"password" binding:"required,min=5,max=10"`       //密码,必填,小于5,大于10位数
    Email    string //邮箱
}

func _Sign(c *gin.Context) {
    var json User                  //定义变量user为User结构体
    err := c.ShouldBindJSON(&json) //执行绑定

    if err != nil { //如果出现错误
        c.JSON(200, gin.H{"msg": err.Error()}) //用json返回错误信息
        return                                 //停止运行
    }
    c.JSON(200, gin.H{"data": json})
}

参数会根据前后来执行,注意参数顺序,不然可能造成逻辑错误,例如omitempty
binding绑定参数:

  • required 必填

  • omitempty 可不填,允许空,该参数必须在其他字段之前,

  • dive 数组如果不写这个,就不会深入对数组进行效验了

  • min 最小长度

  • max 最大长度

  • len 指定长度

  • eq 等于(数字类型)

  • ne 不等于(数字类型)

  • gt 大于(数字类型)

  • gte 大于等于(数字类型)

  • lt 小于(数字类型)

  • lte 小于等于(数字类型)

  • eqfield 等于其他字段的值,例如重复密码,注意这里对应的是结构体中的变量名

  • nefield 不等于其他字段的值

  • gtfield 验证字段的值必须大于另一个字段的值,可以是数字,时间

  • ltfield 验证字段的值必须小于另一个字段的值,可以是数字,时间

  • - 忽略字段

  • oneof 枚举,例如binding:"oneof=男 女" //只能是指定的值

  • contains 包含

  • excludes 不包含

  • startswith 指定内容的前缀

  • endswith 指定内容的后缀

  • email 必须是邮箱地址,指定类型的一般自带必填属性,下面的都是

  • ip 必须是ip地址

  • ipv4

  • ipv6

  • url 必须是url

  • uri

  • datetime 日期,需指定格式例如binding:"datetime=2006-03-02"

  • alpha 字段的值必须全部由字母组成。

  • numeric 字段的值必须全部由数字组成。

  • alphanum 字段的值必须由字母和数字组成。

  • trim 字段的值必须去掉前后的空格。

  • lowercase 字段的值必须转换为小写。

  • uppercase 字段的值必须转换为大写。

  • alphanumunicode 字段的值必须由字母、数字和 Unicode 字符组成。

  • alphanumspace 字段的值必须由字母、数字和空格组成。

前端模板语法


权限控制

    //创建分组路由,并指定权限
    user := r.Group("/user", gin.BasicAuth(gin.Accounts{
        //假设这里是从数据库获取数据,前面是账号,后面是密码
        "muxiao":   "abc",
        "lili":     "sdgf",
        "kunpeng":  "dsd",
        "tuantaun": "dsd",
    }))
    {
        user.POST("/register", userauth.User_register) //继承分组路由,此时这里应是/user/post
        user.GET("/", controllers.User_post)

    }


// 定义一个需要权限验证的路由方法
func User_post(c *gin.Context) {
    user := c.MustGet(gin.AuthUserKey).(string)
    if userinfo, ok := moniuserinfo[user]; ok { //这里的moniuserinfo假设是查询到的账户信息,然后会进行对比是否在
        c.JSON(200, gin.H{"user": user, "信息": userinfo})
    } else {
        c.JSON(200, gin.H{"user": user, "信息": "不在用户组"})
    }

}

// 模拟一些账户数据信息,假设这是数据库的账户表
var moniuserinfo = gin.H{
    "muxiao":  gin.H{"name": "foo@bar.com", "lv": "0"},
    "lili":    gin.H{"name": "austin@example.com", "lv": "1"},
    "kunpeng": gin.H{"name": "austin@example.com", "lv": "2"},
}

记录日志

func main() {
    // 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
    gin.DisableConsoleColor()

    // 记录到文件。
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)

    // 如果需要同时将日志写入文件和控制台,请使用以下代码和上面二选一。
    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    router.Run(":8080")
}

跳转,重新定向

c.Redirect(307, "/")

构建优化

gin构建生产模式
$env:GIN_MODE='release'

恢复gin开发环境
$env:GIN_MODE='debug'

查看gin环境
$env:GIN_MODE

排除错误

在服务端打印客户端的完整请求:

// 打印原始请求体数据
    rawData, err := c.GetRawData()
    if err != nil {
        log.Printf("Error reading raw data: %v", err)
        c.JSON(500, gin.H{"msg": "无法读取请求数据"})
        return
    }
    log.Printf("这是原始数据: %s", string(rawData))

    // 重新将请求体数据放回上下文,以便后续的 BindJSON 使用
    c.Request.Body = io.NopCloser(bytes.NewBuffer(rawData))