【Go】八、Gin 入门使用简介

GIN

GIN 是一个高性能,简单易用的轻量级 WEB 框架

快速尝试

package mainimport ("github.com/gin-gonic/gin""net/http"
)func pong(c *gin.Context) {// 这里的 gin.H 是 map[string]interface{} 的缩写c.JSON(http.StatusOK, gin.H{"message": "pong",})// 我们也可以写成//c.JSON(http.StatusOK, map[string]interface{//	"message": "pong",//	}{}func main() {// 实例化一个 gin 对象r := gin.Default()r.GET("/ping", pong)r.Run(":8080") // 默认是 8080
}

路由分组

对于 WEB 开发来讲,不同的接口需要被不同的模块调用,而我们一般把URL 路径的前缀作为区分模块的基础,例如:

localhost:8080/goods/list 和 localhost:8080/goods/detail 就同属于一个模块,而我们每次配置 GIN 时都需要写它的路径,这样无疑增加了我们的工作量,我们可以通过路由分组,来让相同模块的 URL 分到一起并设置一个共有前缀,进而解决这个问题

示例:

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()goodsGroup := router.Group("/goods") // 这里传入的是公共前缀{goodsGroup.GET("/list", goodsList)goodsGroup.GET("/1", goodsDetail)goodsGroup.POST("/add")}router.Run(":8080")}func goodsDetail(context *gin.Context) {}func goodsList(context *gin.Context) {context.JSON(http.StatusAccepted, gin.H{"message": "WIN",})
}

如果我们有动态的变量需要传入:

使用冒号进行区分

 package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()goodsGroup := router.Group("/goods") // 这里传入的是公共前缀{goodsGroup.GET("/list", goodsList)goodsGroup.GET("/:id/:action", goodsDetail)// 注意这里有一个特例:*action:是一个特殊的路由规则,其会匹配所有的路径,例如我们访问:http://127.0.0.1:8080/goods/256/delete/asd/sad/asd// 就会返回 /256/delete/asd/sad/asd	这样的带有所有路径的字符串,很少使用,有时用来匹配静态资源goodsGroup.POST("/add")}router.Run(":8080")}func goodsDetail(context *gin.Context) {id := context.Param("id")action := context.Param("action")context.JSON(http.StatusOK, gin.H{"id":      id,"action":  action,"message": "OK",})
}func goodsList(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"message": "WIN",})
}

但是,我们还需要对传入的参数进行控制,对参数的类型进行约束

package mainimport ("github.com/gin-gonic/gin""net/http"
)type Person struct {ID   int    `uri:"id" binding:"required"` // 要求必填、必须是uuid格式Name string `uri:"name" binding:"required"`
}func main() {router := gin.Default()router.GET("/:name/:id", func(c *gin.Context) {var person Personif err := c.ShouldBindUri(&person); err != nil {c.Status(404)return}c.JSON(http.StatusOK, gin.H{"name": person.Name,"id":   person.ID,})})router.Run(":8083")
}

参数获取

我们很一般会分为从 GET 和 POST 类型的请求中获取参数:

另外我们也有同时从 url 路径中以及 body 中获取参数的情况,这个时候就需要我们使用 POST 请求

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()router.GET("/getname", GetName)router.POST("/getname", PostGetName)// 混合获取router.POST("bothgetname", GetPostname)router.Run(":8083")
}// 通过 post 请求同时获取 url 与请求体中的数据
func GetPostname(context *gin.Context) {id := context.Query("id")page := context.DefaultQuery("page", "0")message := context.PostForm("message")nick := context.DefaultPostForm("nick", "anonymous")context.JSON(http.StatusOK, gin.H{"id":      id,"page":    page,"message": message,"nick":    nick,})
}// 这里的要从 Body 中发送数据,而不是从 URL 中发送数据
func PostGetName(context *gin.Context) {message := context.PostForm("message")nick := context.DefaultPostForm("nick", "anonymous")context.JSON(http.StatusOK, gin.H{"message": message,"nick":    nick,})}// http://127.0.0.1:8080/getname?firstname=James&lastname=Yang
func GetName(context *gin.Context) {firstname := context.DefaultQuery("firstname", "Guest")lastname := context.DefaultQuery("lastname", "Guest")context.JSON(http.StatusOK, gin.H{"first_name": firstname,"last_name":  lastname,})
}

表单验证

GIN 引入了 validate 开源项目进行表单验证:

package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)type LoginForm struct {User     string `form:"user" binding:"required,min=3,max=10"`Password string `form:"password" binding:"required"`
}type SignForm struct {Age        uint8  `json:"age" binding:"required,gte=1,lte=130"`Name       string `json:"name" binding:"required,min=2"`Email      string `json:"email" binding:"required,email"`Password   string `json:"password" binding:"required,min=6,max=20"`RePassword string `json:"repassword" binding:"required,eqfield=Password"` // 要求和 Password 字段相同,跨域校验,类似的其他跨域校验还有许多
}func main() {router := gin.Default()router.POST("/loginJSON", func(context *gin.Context) {var loginForm LoginFormif err := context.ShouldBind(&loginForm); err != nil {fmt.Println(err.Error())context.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"status": "验证成功",})})router.POST("/signUp", func(context *gin.Context) {var signForm SignFormif err := context.ShouldBind(&signForm); err != nil {context.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"message": "成功!!!!!",})})router.Run(":8083")
}

错误信息配置成中文:

package mainimport ("fmt""net/http""reflect""strings""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""github.com/go-playground/locales/en""github.com/go-playground/locales/zh"ut "github.com/go-playground/universal-translator""github.com/go-playground/validator/v10"en_translations "github.com/go-playground/validator/v10/translations/en"zh_translations "github.com/go-playground/validator/v10/translations/zh"
)type LoginForm struct {User     string `form:"user" binding:"required,min=3,max=10"`Password string `form:"password" binding:"required"`
}type SignForm struct {Age        uint8  `json:"age" binding:"required,gte=1,lte=130"`Name       string `json:"name" binding:"required,min=2"`Email      string `json:"email" binding:"required,email"`Password   string `json:"password" binding:"required,min=6,max=20"`RePassword string `json:"repassword" binding:"required,eqfield=Password"` // 要求和 Password 字段相同,跨域校验,类似的其他跨域校验还有许多
}// 在最后返回错误时调用,用来将返回中的对象名去掉
func removeTopStruct(fields map[string]string) map[string]string {rsp := map[string]string{}for field, err := range fields {rsp[field[strings.Index(field, ".")+1:]] = err // 将map中的 key 中的 . 前面的信息去掉}return rsp
}var trans ut.Translator// 修改 gin 中的 validate,实现定制,这里处理语言问题
func InitTrans(locale string) (err error) {if v, ok := binding.Validator.Engine().(*validator.Validate); ok {// 这段会将 错误信息中的 字段改为 json 中的字段名,而不是结构体中的字段名v.RegisterTagNameFunc(func(fld reflect.StructField) string {name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]if name == "-" {return ""}return name})zhT := zh.New()enT := en.New()uni := ut.New(enT, zhT, enT) // 这里的第一个参数是备用语言,后两个是支持的语言trans, ok = uni.GetTranslator(locale)if !ok {return fmt.Errorf("uni.GetTranslator(%s)", locale)}switch locale {case "en":en_translations.RegisterDefaultTranslations(v, trans)case "zh":zh_translations.RegisterDefaultTranslations(v, trans)default:en_translations.RegisterDefaultTranslations(v, trans)}}return
}func main() {if err := InitTrans("zh"); err != nil {fmt.Println("初始化翻译器错误")fmt.Println(err)return}router := gin.Default()router.POST("/loginJSON", func(context *gin.Context) {var loginForm LoginFormif err := context.ShouldBind(&loginForm); err != nil {// 转换错误类型errs, ok := err.(validator.ValidationErrors)if !ok {context.JSON(http.StatusOK, gin.H{"msg": err.Error(),})return}fmt.Println(err.Error())context.JSON(http.StatusBadRequest, gin.H{// 去掉前面的结构体信息"error": removeTopStruct(errs.Translate(trans)),})return}context.JSON(http.StatusOK, gin.H{"status": "验证成功",})})router.POST("/signUp", func(context *gin.Context) {var signForm SignFormif err := context.ShouldBind(&signForm); err != nil {context.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"message": "成功!!!!!",})})router.Run(":8083")
}

将配置翻译成中文的具体步骤:

配置如下函数与全局变量:

// 在最后返回错误时调用,用来将返回中的对象名去掉
func removeTopStruct(fields map[string]string) map[string]string {rsp := map[string]string{}for field, err := range fields {rsp[field[strings.Index(field, ".")+1:]] = err // 将map中的 key 中的 . 前面的信息去掉}return rsp
}0var trans ut.Translator// 修改 gin 中的 validate,实现定制,这里处理语言问题
func InitTrans(locale string) (err error) {if v, ok := binding.Validator.Engine().(*validator.Validate); ok {// 这段会将 错误信息中的 字段改为 json 中的字段名,而不是结构体中的字段名v.RegisterTagNameFunc(func(fld reflect.StructField) string {name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]if name == "-" {return ""}return name})zhT := zh.New()enT := en.New()uni := ut.New(enT, zhT, enT) // 这里的第一个参数是备用语言,后两个是支持的语言trans, ok = uni.GetTranslator(locale)if !ok {return fmt.Errorf("uni.GetTranslator(%s)", locale)}switch locale {case "en":en_translations.RegisterDefaultTranslations(v, trans)case "zh":zh_translations.RegisterDefaultTranslations(v, trans)default:en_translations.RegisterDefaultTranslations(v, trans)}}return
}

其中,要注意可能需要添加的包:

	en_translations "github.com/go-playground/validator/v10/translations/en"zh_translations "github.com/go-playground/validator/v10/translations/zh"

在 main 函数的最一开始配置 翻译器的初始化:

	if err := InitTrans("zh"); err != nil {fmt.Println("初始化翻译器错误")fmt.Println(err)return}

在发生错误的地方进行配置:

if err := context.ShouldBind(&loginForm); err != nil {// 第一步:将错误类型进行转换// 转换错误类型errs, ok := err.(validator.ValidationErrors)// 这里的 ok 是转换是否失败,转换失败不代表请求失败,故返回的是成功if !ok {context.JSON(http.StatusOK, gin.H{"msg": err.Error(),})// 语言转换失败就直接弹出了,不用再继续看了return}// 在控制台输出错误信息,可选fmt.Println(err.Error())// 将翻译之后的信息写入返回context.JSON(http.StatusBadRequest, gin.H{"error": removeTopStruct(errs.Translate(trans)),})return}

gin 的中间件

中间件是帮助我们解决一些代码中解决比较繁琐或难以解决的情况的,其不侵入代码,可以让代码在保持高度简洁性的同时完成任务。

示例,记录运行时间:

package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)/*
*
定义一个中间件,该中间件必须返回对应的函数,即:return func(context *gin.Context) {}
在这个返回的函数中,可以对请求进行处理,比如:记录请求的时间、请求的路径等
*/
func MyLogger() gin.HandlerFunc {return func(context *gin.Context) {t := time.Now()context.Set("example", "123456")// 执行原本的逻辑context.Next()end := time.Since(t)fmt.Printf("耗时:%V\n", end)// 同样的,我们也需要获取其状态status := context.Writer.Status()fmt.Println("状态码:", status)}
}func TokenRequired() gin.HandlerFunc {return func(context *gin.Context) {var token string// 取出请求头for k, v := range context.Request.Header {// token 会存放在一个 key 叫做 X-Token 的请求头中if k == "X-Token" {token = v[0] // 注意,token中的 v 是一个 slice,就算只有一个元素,其也会被存储为 slice,所以我们取第一个元素就可以取到了}fmt.Println(k, "--------", v, token)}if token != "bobby" {context.JSON(http.StatusUnauthorized, gin.H{"msg": "未登录",})// return 无法阻止后续逻辑的执行,只有 context.Abort() 才能阻止后续逻辑的执行//returncontext.Abort()}context.Next()}
}func main() {// 使用 gin.Default() 会直接调用中间件router := gin.Default()// 使用 gin.New() 不会调用中间件//router := gin.New()// 使用 logger 和 recovery 中间件//router.Use(gin.Logger(), gin.Recovery())router.Use(MyLogger(), TokenRequired())// 自定义中间件 AuthRequired,该中间件只会在 /goods 路由中调用authorized := router.Group("/goods"){authorized.GET("/list", func(conotext *gin.Context) {time.Sleep(3 * time.Second)conotext.JSON(http.StatusOK, gin.H{"message": "list",})})}authorized.Use(AuthRequired)router.Run(":8083")
}func AuthRequired(context *gin.Context) {}

中间件原理解析:

在上面的代码中,我们就算在 context.Next() 前加了 return,最终的返回仍然会是正确的返回,没有被打断

其原理在于:我们所有的中间件会被放置在 gin 的一条队列中依次执行,调用 context.Next() 只会让其执行队列中下一个中间件(也就是队列中的索引 + 1),return 也只会停止当前中间件的执行,并不会停止中间件队列的依次执行,就算我们不使用 context.Next() gin底层也会自动调用,让队列中的中间件依次执行,我们想要大端并停止后面的所有逻辑,就必须使用 context.Abort() 指令

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/772754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud Gateway 3.x 获取body中的数据鉴权

前言 SpringCloud Gateway建立在Spring Framework5、Project Reactor和Spring Boot2.0之上,使用WebFlux非阻塞API 什么是WebFlux? 官网:https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html 传统的Web框架&…

YOLOv9改进策略:注意力机制 | 动态稀疏注意力的双层路由方法BiLevelRoutingAttention | CVPR2023

💡💡💡本文改进内容: CVPR2023 动态稀疏注意力的双层路由方法BiLevelRoutingAttention,强烈推荐,涨点很不错,同时被各个领域的魔改次数甚多,侧面验证了性能。 💡&#x1…

我们该如何优化迭代自己?

哈喽,你好啊,我是雷工! 一款软件如果想变得完美,那么肯定需要不断的试运行和更新迭代。 我们和软件一样,生活中难免会有错误的决策,失误的事件,为了能够解决我们自身存在的BUG,我们该…

设计用于驱动12 V汽车接地负载,VN5E160ASTR、VND5E160MJTR、VND5E025AKTR、VND5E050ACKTR 单/双通道高侧驱动器

摘要 意法半导体VIPower系列高侧开关符合汽车应用要求,内嵌先进的控制功能,其新型保护机制适用于各种负载类型及额定功率。 此类开关是汽车系统的理想选择,如:接线盒、内部/外部照明、直流电机驱动等,并适用于任何需…

[JAVA]12.ArrayList

一、ArrayList 1.1ArrayList类概述 - 什么是集合 ​ 提供一种存储空间可变的存储模型,存储的数据容量可以发生改变 - ArrayList集合的特点 ​ 底层是数组实现的,长度可以变化 - 泛型的使用 ​ 用于约束集合中存储元素的数据类型 1.2ArrayList类常…

基于nodejs+vue基于协同过滤算法的私人诊python-flask-django-php

实现后的私人诊所管理系统基于用户需求分析搭建的,并且会有个人中心,患者管理,医生管理,科室管理,出诊医生管理,预约挂号管理,预约取消管理,病历信息管理,药品信息管理&a…

qt事件机制学习笔记

实现闹钟功能 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(this)) //给语音播报者实例化空间 {ui->setupUi(this); }Widget::~Widget() {delete …

【GameFramework框架内置模块】18、界面(UI)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录:…

Spark SQL 数据源

Spark SQL 数据源 Spark SQL支持读取很多种数据源,比如parquet文件,json文件,文本文件,数据库等。下面列出了具体的一些数据源: Parquet文件Orc文件Json文件Hive表JDBC 先把people.json导入到hdfs的tmp目录下。peop…

多模态模型学习1——CLIP对比学习 语言-图像预训练模型

多模态模型学习1——CLIP对比学习 语言-图像预训练模型 1.背景介绍 随着互联网的快速发展,图像和文本数据呈现爆炸式增长。如何有效地理解和处理这些多模态数据,成为人工智能领域的一个重要研究方向。多模态模型学习旨在通过联合学习图像和文本表示&am…

WebGIS开发应该从哪些方面做准备

工程化思想 环境配置项目构建npm:Node包管理器,是 JavaScript 运行时 Node.js 的默认程序包管理器。 https://www.freecodecamp.org/chinese/news/what-is-npm-a-node-package-manager-tutorial-for-beginners/新建一个前端工程项目:前端框…

在项目中数据库如何优化?【MySQL主从复制(创建一个从节点复制备份数据)】【数据库读写分离ShardingJDBC(主库写,从库读)】

MySQL主从复制 MySQL主从复制介绍MySQL复制过程分成三步:1). MySQL master 将数据变更写入二进制日志( binary log)2). slave将master的binary log拷贝到它的中继日志(relay log)3). slave重做中继日志中的事件,将数据变更反映它自…

Vue 02 组件、Vue CLI

Vue学习 Vue 0201 组件引入概念组件的两种编写形式① 非单文件组件基本使用使用细节组件嵌套组件本质 VueComponent重要的内置关系 ② 单文件组件 02 Vue CLI介绍 & 文档安装使用步骤脚手架结构render默认配置ref 属性props配置mixin配置项插件scoped 样式案例:…

MySQL将id相同的两行数据合并group_concat

MySQL将id相同的两行数据合并 group_concat这个函数能将相同的行组合起来,省老事了。 MySQL中group_concat函数 完整的语法如下: group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator ‘分隔符’]) 1.基本查询 Sql代码 2.…

MYSql通过FULLTEXT实现全文检索

FULLTEXT 是关系型数据库管理系统(如 MySQL)中用于全文检索的功能。它允许用户在表中的文本列上执行全文搜索。以下是 FULLTEXT 索引的工作原理和实现全文检索的方法: 1. **创建全文索引**: 在关系型数据库中,你可以为…

java Web会议信息管理系统 用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 jsp 会议信息管理系统是一套完善的web设计系统,对理解JSP java SERLVET mvc编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发,数据库为Mysql5.0&am…

ActiveMQ-04如何搭建一个完美的ActiveMQ集群

集群架构是一个很大的话题,官网就给我们介绍了几种 客户端:队列消费者集群-Queue Consumer Clusters服务端:Broker集群 - Broker Clusters 静态发现动态发现 服务端:Master-Slave 主从集群 Shared File System Master SlaveJDBC …

Github 2024-03-26 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-03-26统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目3TypeScript项目3Jupyter Notebook项目2C++项目1GDScript项目1Lua项目1Solidity项目1Open Interpreter: 本地代码运行和自然语言界面…

Android release 混淆编译 private、protected自动变public问题解决

场景: 用assembleRelease编译aar的时候,成员变量private、protected自动变public; 用assembleDebug编译aar,则正常,于是对比了build.gradle,发现debug没启用混淆, release版本启动了混淆 解决…

力扣hot100:207. 课程表

这是一道拓扑排序问题,也可以使用DFS判断图中是否存在环。详情请见:官方的BFS算法请忽略,BFS将问题的实际意义给模糊了,不如用普通拓扑排序思想。 数据结构:图的拓扑排序与关键路径 拓扑排序: class Sol…