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
必须是urluri
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))