关于gin:gin框架利用中间件记录用户操作行为

背景阐明在我的项目的具体应用过程中咱们很有必要去记录那个用户操作了什么这样的需要。当然实现的形式有很多,明天咱们介绍一下具体怎么在gin框架中利用协成的个性异步去记录用户的的操作行为。在这里先介绍一下我集体给开源的gsadmin我的项目,GS Admin=gin+scui 它是golang 开发的一个企业级后盾。遵循MIT开源协定。前端框架是scui,SCUI基于 Vue3、elementPlus持续性的提供独家组件和丰盛的业务模板帮忙你疾速搭建企业级中后盾前端工作。后端框架是gin,Gin是一个golang的微框架,封装比拟优雅,具备疾速灵便,容错不便等特点。内置了权限治理、用户治理等根底模块儿,还反对了事件服务,不便业务解耦。后续会依据用户的反馈更新内容! 上面来介绍一下实现思路1、次要性能是在中间件内实现2、利用中间件来获取用户拜访的门路3、用拜访门路来匹配数据库内的路由信息4、收集须要的信息,利用协成的机制异步记录到数据库中或其余须要记录的中央 上面看代码package middlewareimport ( "encoding/json" "github.com/gin-gonic/gin" "github.com/sonhineboy/gsadmin/service/app/models" "github.com/sonhineboy/gsadmin/service/app/repositorys" "github.com/sonhineboy/gsadmin/service/global")func OperationLog() gin.HandlerFunc { return func(c *gin.Context) { cCp := c.Copy() go func() { var ( doData []byte log models.OperationLog ) method := c.Request.Method //参数 if method == "GET" { doData, _ = json.Marshal(cCp.Request.URL.Query()) } if method == "POST" { doData, _ = cCp.GetRawData() } claims, ok := repositorys.GetCustomClaims(c) if ok == true { log.UserId = claims.Id log.UserName = claims.Name } else { log.UserId = 0 } var where = make(map[string]interface{}) var d models.MenuApiList db := global.Db.Model(&models.MenuApiList{}) where["url"] = cCp.Request.URL.Path db.Preload("Menu").Where(where).First(&d) log.Method = cCp.Request.Method log.DoData = string(doData) log.Ip = cCp.ClientIP() title, ok := d.Menu.Meta["title"] if ok { log.PathName = title.(string) } log.UrlPath = cCp.Request.URL.Path global.Db.Create(&log) }() c.Next() }}演示地址喜爱我的分享或者我的项目的多点点star,咱们多多交换 ...

May 24, 2023 · 1 min · jiezi

关于gin:Gin渲染-html代码

Gin渲染html代码,参考文档:https://www.kancloud.cn/shuangdeyu/gin_book/949436 后端代码: package mainimport ( "fmt" "github.com/gin-gonic/gin" "net/http")//文档: https://www.kancloud.cn/shuangdeyu/gin_book/949436func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{ "title": "Main website", }) return }) type requestDataModel struct { Name string `json:"name"` Pass int `json:"pass"` Age int `json:"age"` } router.POST("/getData", func(c *gin.Context) { fmt.Print(c.PostForm("name")) var requestData requestDataModel if err := c.BindJSON(&requestData); err == nil { } c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": "success", "data": requestData, }) return }) // http://127.0.0.1:8080/index router.Run()}templates/index.html 代码 ...

April 26, 2023 · 1 min · jiezi

关于gin:gin-表单数据处理简单实例

package mainimport ( "encoding/json" "fmt" "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.POST("/json", func(c *gin.Context) { data,_ := c.GetRawData() var m map[string]interface{} _ = json.Unmarshal(data, &m) c.JSON(http.StatusOK,m) }) r.LoadHTMLGlob("./temp/*") r.GET("/user/add", func(c *gin.Context) { c.HTML(http.StatusOK, "useradd.html", gin.H{}) }) r.POST("/user/add", func(c *gin.Context) { username := c.PostFormArray("username") password := c.PostFormArray("password") fmt.Println(username, "goubibibi", password) c.JSON(http.StatusOK,gin.H{ "msg":"ok", "username":username, "password": password, }) }) r.Run()}<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <form action="/user/add" method="post"> <p>username: <input type="text" name="username"></p> <p>password: <input type="text" name="password"></p> <button type="submit"></button> </form></head><body></body></html> ...

February 11, 2023 · 1 min · jiezi

关于gin:gin框架学习

依据发来的申请进行解析 c.Bind()//和表单的数据进行绑定c.ShouldBindUri()//和uri的内容进行绑定go操纵数据库,用xqite框架操纵如果是对数据库的数据进行查问 Db.select("这是要操纵的表","这是sql语句")如果是对数据库的数据进行批改 Db.Exec("这是要操纵的表","这是sql语句")用go操纵redisc, err := redis.Dial("tcp", "localhost:6379")_, err = c.Do("Set", "abc", 100)r, err := redis.Int(c.Do("Get", "abc")_, err = c.Do("MSet", "abc", 100, "efg", 300)r, err := redis.Ints(c.Do("MGet", "abc", "efg"))_, err = c.Do("lpush", "book_list", "abc", "ceg", 300)r, err := redis.String(c.Do("lpop", "book_list"))_, err = c.Do("HSet", "books", "abc", 100)r, err := redis.Int(c.Do("HGet", "books", "abc"))var pool *redis.Pool //创立redis连接池pool = &redis.Pool{ //实例化一个连接池

July 8, 2022 · 1 min · jiezi

关于gin:基于Gin框架的web后端开发八-Gin框架的请求重定向

重定向分为内部重定向和外部重定向,也能够分为永恒重定向和长期重定向。 内部重定向能够应用Redirect办法来实现,举个例子,如果咱们想永恒重定向到内部的百度: package mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/index", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") }) r.Run(":9090")}运行后果就间接重定向到内部的百度了,咱们发现 外部重定向举个例子,如果想要从/a外部跳到/b,能够这样写: package mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/index", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") }) r.GET("/a", func(c *gin.Context) { //跳转到/b对应的路由处理函数 c.Request.URL.Path = "/b" r.HandleContext(c) }) r.GET("/b", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "b", }) }) r.Run(":9090")}后果如下: 参考: bilibili

June 9, 2022 · 1 min · jiezi

关于gin:基于Gin框架的web后端开发七-Gin框架的文件上传详解

    上传文件是用户将文件从客户端上传到服务器的过程,上面举个简略的上传文件的例子:    先写一个前端页面: <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>index</title></head><body><form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="f1"> <input type="submit" value="上传"></form></body></html>后果如图: 记得在html的form标签中加上这个属性:enctype="multipart/form-data" 用来二进制传文件。而后再main.go中这样写: package mainimport ( "github.com/gin-gonic/gin" "net/http" "path")func main() { r := gin.Default() r.LoadHTMLFiles("./index.html") r.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) }) r.POST("/upload", func(c *gin.Context) { //从申请中读取文件 f, err := c.FormFile("f1") if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) } else { //将文件保留在服务器 //dst := fmt.Sprintf("./%s", f.Filename)//写法1 dst := path.Join("./", f.Filename) c.SaveUploadedFile(f, dst) c.JSON(http.StatusOK, gin.H{ "status": "ok", }) } }) r.Run(":9090")}后果上传文件胜利: ...

June 8, 2022 · 1 min · jiezi

关于gin:基于Gin框架的web后端开发六-参数绑定ShouldBind详解

在前几篇文章中,数据或者参数的绑定须要一个一个的绑定,比方这样: package mainimport ( "fmt" "github.com/gin-gonic/gin" "net/http")//建设一个构造体来存储数据type UserInfo struct { username string password string}func main() { r := gin.Default() r.GET("/user", func(c *gin.Context) { username := c.Query("username") passsword := c.Query("password") u := UserInfo{ username: username, password: passsword, } fmt.Printf("%v\n", u) //给这个申请返回一个JSON数据 c.JSON(http.StatusOK, gin.H{ "message": "ok", }) }) r.Run(":9090")}后果如下:     然而一单参数略微多一点,这样一个一个的绑定就太麻烦了,这里介绍一下Gin框架中的ShouldBind(),它用于将申请携带的参数和后端的构造体绑定起来,比方下面咱们UserInfo这个构造体有username和password两个字段,如果申请中呈现这两个字段ShouldBind()就会主动帮咱们取出这两个值,而后伴咱们做一个构造体的初始化,咱们就能够失去一个UserInfo类型的变量。    ShouldBind()的应用过程须要留神: ShouldBind接管的是构造体对象的地址(&对象名字),而不是对象构造体的每一个字段首字母要大写(相似Java public申明)构造体该打标签要打,发送json格局的申请要打json标签,地址栏中发送申请要打form标签。ShouldBind模仿queryString举个例子:如果要想把http://127.0.0.1:9090/user?Us...这个链接的两个参数取到,能够这样写: package mainimport ( "fmt" "github.com/gin-gonic/gin" "net/http")//建设一个构造体来存储数据type UserInfo struct { Username string Password string}func main() { r := gin.Default() r.GET("/user", func(c *gin.Context) { //username := c.Query("username") //passsword := c.Query("password") //u := UserInfo{ // username: username, // password: passsword, //} //申明一个UserInfo类型的变量u var u UserInfo //这里把地址传过来 err := c.ShouldBind(&u) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) } else { fmt.Printf("%#v\n", u) c.JSON(http.StatusOK, gin.H{ "status": "ok", }) } }) r.Run(":9090")}后果如下: ...

June 8, 2022 · 2 min · jiezi

关于gin:基于Gin框架的web后端开发四-获取FORM表单参数

咱们罕用POST申请发送FORM表单数据,这种形式绝对于GET形式更加平安。 未完待续。。

June 7, 2022 · 1 min · jiezi

关于gin:基于Gin框架的web后端开发三-获取queryString参数

tips: go mod tidy 能够剖析代码中依赖的第三方包,而后在go.mod中将这些以来记录下来。例如:想要获取query字段中的杨超过:http://127.0.0.1:9090/web?query=杨超过 形式一:Querypackage mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/web", func(c *gin.Context) { //这里要获取浏览器那边发申请携带的query string参数 name := c.Query("query") c.JSON(http.StatusOK, gin.H{ "name": name, }) }) r.Run(":9090")}测试后果如下: 形式二:DefaultQuery这其实和形式一类似,区别在于,如果没有查问到数据,能够用设置好的default数据: package mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/web", func(c *gin.Context) { //这里要获取浏览器那边发申请携带的query string参数 //name := c.Query("query") name := c.DefaultQuery("query", "someone") c.JSON(http.StatusOK, gin.H{ "name": name, }) }) r.Run(":9090")}测试后果如下: 形式三:GetQuery这个函数有2个返回值,其实就是多了一个bool值,如果去不到参数,第二个返回的就是false: package mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/web", func(c *gin.Context) { //这里要获取浏览器那边发申请携带的query string参数 //name := c.Query("query") //name := c.DefaultQuery("query", "someone") name, ok := c.GetQuery("query") if !ok { name = "someone" } c.JSON(http.StatusOK, gin.H{ "name": name, }) }) r.Run(":9090")}我觉第三种形式,思考的比拟全面,我更喜爱应用,当然,另外两种形式也能够在适合的场景应用。 ...

June 5, 2022 · 1 min · jiezi

关于gin:基于Gin框架的web后端开发二-JSON数据生成

基于Gin框架的web开发,总的来讲有两种: 第一种是后端应用go语言模板引擎实现整个web全栈开发,返回残缺的html文件给浏览器。第二种是前后端拆散,应用JSON交互因为第一种办法消耗太多网络资源,性能差,耦合度高,目前曾经根本被第二种模式取代,这篇博客只介绍如何在Gin框架中返回JSON数据给前端或者挪动端(俗称JSON的渲染)。 生成JSON数据能够用map,也能够用构造体。上面先介绍map: 基于map的JSON数据生成举个例子,代码如下: package mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/json", func(c *gin.Context) { //这里结构服务器要返回前端的数据,一共有两种办法,第一种是:应用map将数据序列化 //,这个map的key是一个string, value是一个空的接口(这样即能够实现接管任意类型的数据) data := map[string]interface{}{ "name": "liber", "message": "hey liber~", "age": 16, } //这里要返回json格局的数据,所以用c.JSON,这样,数据就返回给申请方了 c.JSON(http.StatusOK, data) }) r.Run(":9090")}测试后果如下:因为用map来序列化json数据是很常见的写法,Gin框架的作者将这种写法封装了一下: //H is a shortcut for map[string]interface{}type H map[string]interface{}所以,咱们能够间接应用gin.H{}来代替: package mainimport ( "github.com/gin-gonic/gin" "net/http")func main() { r := gin.Default() r.GET("/json", func(c *gin.Context) { //这里结构服务器要返回前端的数据,一共有两种办法,第一种是:应用map将数据序列化 //,这个map的key是一个string, value是一个空的接口(这样即能够实现接管任意类型的数据) //data := map[string]interface{}{ // "name": "liber", // "message": "hey liber~", // "age": 16, //} data := gin.H{ "name": "libro", "message": "hey libro~", "age": 17, } //这里要返回json格局的数据,所以用c.JSON,这样,数据就返回给申请方了 c.JSON(http.StatusOK, data) }) r.Run(":9090")}测试后果如下: ...

June 5, 2022 · 1 min · jiezi

关于gin:Gin框架介绍

mac一开始装置Gin的时候还会有timeout报错,能够借鉴这篇博客解决: 未完待续。。。。

June 4, 2022 · 1 min · jiezi

关于gin:Go-nethttp-以及ioioutil包的使用

tips:这可能是最繁难的web后端开发demo 在goland中创立一个main.go而后依照http包的应用阐明编写代码如下: package mainimport ( "fmt" "net/http")func sayhello(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprint(w, "<h1>hey this is LIBERHOME!</h1>")}func main() { http.HandleFunc("/hello", sayhello) err := http.ListenAndServe(":9090", nil) //服务器启动后,监听9090端口 if err != nil { fmt.Printf("http service failed, err: %v\n", err) return }}接下来在terminal执行go run main.go而后在浏览器输出http://127.0.0.1:9090/hello即可看到 当然,间接把网页显示的内容硬编码能够改成一个txt文件的形式:首先创立一个hello.txt: <h1>hey it is LIBERHOME!</h1><h2>this may be the most simple demo~</h2><img id='i1' src='https://avatar-static.segmentfault.com/274/037/2740371703-61baf9dec42e4_huge256'>而后之前的代码稍加批改,退出ioutil包: package mainimport ( "fmt" "io/ioutil" "net/http")func sayhello(w http.ResponseWriter, r *http.Request) { b, _ := ioutil.ReadFile("./hello.txt") _, _ = fmt.Fprint(w, string(b))}func main() { http.HandleFunc("/hello", sayhello) err := http.ListenAndServe(":9090", nil) //服务器启动后, 监听9090端口 if err != nil { fmt.Printf("http service failed, err: %v\n", err) return }}运行后果如下: ...

June 4, 2022 · 1 min · jiezi

关于gin:Gin初识go语言Web框架小白向

Web框架概述齐全不理解的萌新能够先看这篇文章对一些概念有初步的意识https://cloud.tencent.com/dev... web常见名词万维网:并非某种非凡的计算机网络,万维网是一个大规模的、联机式的信息储藏所,英文简称web。万维网用链接的办法能十分不便地从因特网上的一个站点拜访另一个站点(超链技术),具备提供分布式服务的特点。万维网是一个分布式的超媒体零碎,是超文本零碎的裁减。万维网基于B/S架构工作。URL:万维网应用对立资源定位符(Uniform Resource Locator)来标记万维网上的各种文档,并使每个文档在整个因特网的范畴内具备惟一的标识符URL。HTML:为了解决“怎么使不同作者创作的不同格调的万维网文档,都能在因特网上的各种主机上显示进去,同时使用户分明地晓得在什么中央存在着链接”这一问题,万维网应用超文本标记语言(HyperText Markup Language),使得万维网页面的设计者能够很不便地用链接从页面的某处链接到因特网的任何一个万维网页面,并且可能在本人的主机品目上将这些页面显示进去。HTML与txt一样,仅仅是是一种文档,不同之处在于,这种文档专供于浏览器上为浏览器用户提供对立的界面出现的对立规约。且具备结构化的特色,这是txt所不具备的强制规定。 web开发Web开发在近年来,随着自身技术的冲破以及挪动设施的遍及,基于web畛域的开发,也呈现了明确的岗位职责分工,一个web互联网产品中,基本上会分为web UI设计、Web前端开发以及web后端开发。 对于大型的互联网公司,还会分独立的Web架构开发组,专门负责web框架的保护更新与迭代。 Web前端开发用到的编程语言次要有javascript,以及随同有标记性文本语言html和款式渲染形式CSS。后端开发(Back-End Development,也称服务端开发、服务器端开发等)是创立残缺可运行的Web利用服务端程序(服务端程序和资源合称为后端,即在服务器上运行的、不波及用户界面的局部)的过程,是Web利用程序开发的一部分。后端开发者应用Java、Golang等语言及其衍生的各种框架、库和解决方案来实现Web应用程序的外围业务逻辑,并向外提供特定的API,使得Web利用可能高效、平安、稳固地运行。 web框架随着Web最新发展趋势的一直降级,Web我的项目开发也越来越难,而且须要破费更多的开发工夫,web框架应运而生。Web框架(Web framework)或者叫做Web利用框架(Web application framework),是用于进行Web开发的一套软件架构。大多数的Web框架提供了一套开发和部署网站的形式。为Web的行为提供了一套反对反对的办法。应用Web框架,很多的业务逻辑外的性能不须要本人再去欠缺,而是应用框架已有的性能就能够。Web框次要用于动静网络开发。 web框架的作用 Gin构造组成一些基本概念幂等性 http申请 前缀树 什么是GinGo 语言最风行了两个轻量级 Web 框架别离是 Gin 和 Echo,这两个框架大同小异,都是插件式轻量级框架,背地都有一个开源小生态来提供各式各样的小插件,这两个框架的性能也都十分好,裸测起来跑的飞快。Gin 具备运行速度快,分组的路由器,良好的解体捕捉和错误处理,十分好的反对中间件和 json。总之,在 Go语言开发畛域是一款值得好好钻研的 Web 框架。 开源网址:https://github.com/gin-gonic/gin web框架的组成https://blog.csdn.net/weixin_... GinEngine 对于web服务https://zhuanlan.zhihu.com/p/... 路由什么是路由 路由框架httprouter如同web倒退到肯定阶段呈现了web框架供人们应用疾速开发,路由也有各种各样的框架。httprouter 就是其中极为优良的一种。 handle在计算机程序设计中,句柄是对资源的形象援用。当应用软件援用由另一个零碎(如数据库或操作系统)治理的内存块或对象时,就会应用句柄。资源句柄能够是一个不通明的标识符,在这种状况下,它通常是一个整数(通常是用于治理该类型资源的数组或“表”中的数组索引),也能够是一个容许拜访进一步信息的指针。常见的资源句柄有文件描述符、网络套接字、数据库连贯、过程标识符 httprouter 用法 相干链接https://www.zhihu.com/questio...https://zhuanlan.zhihu.com/p/...https://blog.csdn.net/weixin_...https://zhuanlan.zhihu.com/p/...https://blog.csdn.net/hephaes...https://cloud.tencent.com/dev...https://www.imooc.com/wiki/fl...https://www.runoob.com/http/h...https://developer.mozilla.org...https://zhuanlan.zhihu.com/p/...https://juejin.cn/post/684490...https://qiankunpingtai.cn/art...

January 29, 2022 · 1 min · jiezi

关于gin:全栈开发实战小草看书之开篇

全栈开发实战小草看书之开篇介绍小草看书,一款 gin+vue+flutter 开发的看书利用。内容含古典文学,四大名著,神话武侠,诗词歌赋等。 Android端 技术选型Web端vite2,vue3,vue-router4,vuex4,vuex-persistedstate,axios,elementplus,screenfull,less 服务端gin,gorm,logrus,file-rotatelogs,viper,jwt,authz,cors,colly,cron,riot 挪动端flutter,curved_navigation_bar,device_info_plus,dio,flutter_html,flutter_slidable,fluttertoast,hive_flutter,percent_indicator,pull_to_refresh,uuid

December 15, 2021 · 1 min · jiezi

关于gin:gin框架之日志的使用

在我的项目开发中,日志模块必不可少。Golang作为新兴的语言,第三方日志包也越来越多,其中star数最多的是logrus。云盘我的项目中应用的日志包就是logrus,上面就介绍一下该日志包的个性和应用办法。目前我的项目中应用的服务端框架是gin,日志就以gin中间件的形式来应用。 一、logrus个性:齐全兼容golang规范库日志模块:logrus提供了6中日志级别:debug、info、warn、error、fatal和panic,这是golang规范日志模块的超集,须要留神的是:fatal间接调用的os.Exit(),来不及执行defer。可扩大的hook机制:容许使用者通过hook的形式将日志发送到任意的中央,如:本地文件系统、规范输入、logstash和elasticsearch或者mq等,也能够通过hook自定义日志的内容或格局。可选的日志输入格局:logrus内置两种日志格局:JSONFormatter和TextFormatter,如果这两种格局不满足需要,能够本人手动实现接口,定义日志的输入格局。Field机制:logrus激励通过Field机制进行精细化和结构化的日志记录,而不是通过简短的音讯来记录日志。二、根本应用写入文件:先从配置中获取到日志文件的门路和日志文件名,关上文件,以追加写的模式写入日志 // 写入文件f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND, os.ModeAppend)if err != nil { fmt.Println("err", err)}创立实例、设置输入和日志输入级别 // 实例化logger := logrus.New()// 设置输入logger.Out = f// 设置日志级别logger.SetLevel(logrus.DebugLevel)设置rotatelogs // 设置rotatelogs和writeMaplogWriter, _ := rotatelogs.New( // 宰割后的文件名称 fileName+".%Y%m%d.log", // 生成软链,指向最新的日志文件 rotatelogs.WithLinkName(fileName), // 设置最长保留工夫,这里设置成7天 rotatelogs.WithMaxAge(7*24*time.Hour), // 设置日志切割间隔时间,这里设置成1天 rotatelogs.WithRotationTime(24*time.Hour),)writeMap := lfshook.WriterMap{ logrus.InfoLevel: logWriter, logrus.FatalLevel: logWriter, logrus.DebugLevel: logWriter, logrus.WarnLevel: logWriter, logrus.ErrorLevel: logWriter, logrus.PanicLevel: logWriter,}增加自定义的hook lfhook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05",})// 新增hooklogger.AddHook(lfhook)设置日志输入的格局 startTime := time.Now()// 解决申请c.Next()endTime := time.Now()// 执行工夫latencyTime := endTime.Sub(startTime)// 申请形式reqMethod := c.Request.Method// 申请路由reqUrl := c.Request.URL// 状态码statuCode := c.Writer.Status()// 申请IPclientIP := c.ClientIP()// 日志格局logger.Infof("| %3d | %13v | %15s | %s | %s", statuCode, latencyTime, clientIP, reqMethod, reqUrl,)中间件的应用:间接在gin的router配置中应用router.use(中间件名称)即可。输入的成果: ...

September 4, 2021 · 2 min · jiezi

关于gin:golang-web框架gin使用教程

InstallationTo install Gin package, you need to install Go and set your Go workspace first. The first need Go installed (version 1.12+ is required), then you can use the below Go command to install Gin.$ go get -u github.com/gin-gonic/gin Import it in your code:import "github.com/gin-gonic/gin" (Optional) Import net/http. This is required for example if using constants such as http.StatusOK.import "net/http" Quick startassume the following codes in example.go file$ cat example.go ...

December 6, 2020 · 2 min · jiezi

gin中间件的使用

在Gin框架中,中间件(Middleware)指的是可以拦截http请求-响应生命周期的特殊函数,在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行。 中间件的常见应用场景如下: 请求限速api接口签名处理权限校验...提示:如果你想拦截所有请求做一些事情都可以开发一个中间件函数去实现。1.使用中间件func main() { r := gin.New() // 通过use设置全局中间件 // 设置日志中间件,主要用于打印请求日志 r.Use(gin.Logger()) // 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉 r.Use(gin.Recovery()) // 忽略后面代码}2.自定义中间件下面我们来看一下gin中的中间件 // Use attaches a global middleware to the router. ie. the middleware attached though Use() will be// included in the handlers chain for every single request. Even 404, 405, static files...// For example, this is the right place for a logger or error management middleware.func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine}通过gin的源码,我们发现,中间件是一个gin.HandlerFunc ...

June 11, 2020 · 1 min · jiezi

gin定义统一处理错误

gin定义统一处理错误在gin中如果有错误需要响应给客户端,如果每一个都判断,并且处理返回,如果项目复杂了,需要写很多重复的代码来响应错误,今天我们来封装一个统一处理错误包装器,使用的是装饰器模式。 1.定义统一处理错误pkg/e/error.go package eimport "github.com/gin-gonic/gin"//自定义api错误结构体type ApiError struct { Status int `json:"-"` Code int `json:"code"` Message string `json:"message"`}func (err ApiError)Error() string { return err.Message}2.编写统一错误处理函数pkg/e/error_wrapper.go package eimport ( "github.com/gin-gonic/gin" "net/http")type WrapperHandle func(c *gin.Context) (interface{}, error)func ErrorWrapper(handle WrapperHandle) gin.HandlerFunc { return func(c *gin.Context) { data, err := handle(c) if err != nil { apiError := err.(ApiError) c.JSON(apiError.Status, apiError) return } c.JSON(http.StatusOK, gin.H{"data": data}) }}这里的统一处理只是一个简单的处理,介绍一种思路,具体的可以根据项目进行改造3.例子编写控制器api/err_handle.go package apiimport ( "cn.sockstack/gin_demo/pkg/e" "github.com/gin-gonic/gin" "net/http")func ErrorHandle(c *gin.Context) (interface{}, error) { query := c.Query("q") if query == "" { return nil, e.ApiError{ Status: http.StatusOK, //状态码 Code: 404, Message: "q的参数不能为空", } } if query == "test" { return nil, e.ApiError{ Status: http.StatusOK, //状态码 Code: 404, Message: "q的参数不能为test", } } return query, nil}注册路由routers/test.go ...

June 9, 2020 · 1 min · jiezi

gin请求参数处理

本章介绍Gin框架获取请求参数的方式 1.获取Get 请求参数Get请求url例子:/path?id=1234&name=Manu&value=111 获取Get请求参数的常用函数: func (c *Context) Query(key string) stringfunc (c *Context) DefaultQuery(key, defaultValue string) stringfunc (c *Context) GetQuery(key string) (string, bool)例子: func Handler(c *gin.Context) { //获取name参数, 通过Query获取的参数值是String类型。 name := c.Query("name") //获取name参数, 跟Query函数的区别是,可以通过第二个参数设置默认值。 name := c.DefaultQuery("name", "sockstack") //获取id参数, 通过GetQuery获取的参数值也是String类型, // 区别是GetQuery返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。 id, ok := c.GetQuery("id") if !ok { // 参数不存在 }}提示:GetQuery函数,判断参数是否存在的逻辑是,参数值为空,参数也算存在,只有没有提交参数,才算参数不存在。2.获取Post请求参数获取Post请求参数的常用函数: func (c *Context) PostForm(key string) stringfunc (c *Context) DefaultPostForm(key, defaultValue string) stringfunc (c *Context) GetPostForm(key string) (string, bool)例子: ...

June 9, 2020 · 1 min · jiezi

参数校验错误信息中文处理

在上一节我们介绍到,gin可以使用ShouldBind方法把参数绑定到结构体,但是没有介绍到参数校验的方式,这节我们来介绍参数校验和校验失败后转换成中文返回前端。 1.数据校验下面我们开始一个简单的例子: 在根目录的requests目录下新建一个test_request.gopackage requests//测试请求结构体 该结构体定义了请求的参数和校验规则type TestRequest struct { Username string `form:"username" binding:"required"`}在根目录的api目录下新建一个test.go的控制器,定义test控制器package apiimport ( "cn.sockstack/gin_demo/requests" "github.com/gin-gonic/gin" "net/http")func Test(c *gin.Context) { //实例化一个TestRequest结构体,用于接收参数 testStruct := requests.TestRequest{} //接收请求参数 err := c.ShouldBind(&testStruct) //判断参数校验是否通过,如果不通过,把错误返回给前端 if err != nil { c.JSON(http.StatusOK, gin.H{"error": err.Error()}) return } //校验通过,返回请求参数 c.JSON(http.StatusOK, gin.H{"params": testStruct})}在根目录的routers下定义/test路由,分别新建init.go和test.go文件初始化路由.test.go package routersimport ( "cn.sockstack/gin_demo/api" "github.com/gin-gonic/gin")func test(r *gin.Engine) { //定义/test路由 r.GET("/test", api.Test)}init.go package routersimport "github.com/gin-gonic/gin"func Init(r *gin.Engine) { //注册test路由 test(r)}在main.go中注册路由package main// 导入gin包import ( "cn.sockstack/gin_demo/pkg/config" "cn.sockstack/gin_demo/routers" "fmt" "github.com/gin-gonic/gin")// 入口函数func main() { // 初始化一个http服务对象 r := gin.Default() //注册路由 routers.Init(r) r.Run(fmt.Sprintf("%s:%d", config.Server.Address, config.Server.Port)) // 监听并在 0.0.0.0:8081 上启动服务}运行并访问localhost:8081/test,不带参数会报错{ "error": "Key: 'TestRequest.Username' Error:Field validation for 'Username' failed on the 'required' tag"}运行并访问localhost:8081/test?username=sockstack,则返回响应的参数。{ "params": { "Username": "sockstack" }}上面的例子已经可以实现参数校验和接收参数了,但是校验不通过的时候返回的提示是英文的,下面我们介绍一下怎么把错误转成中文返回。 ...

June 9, 2020 · 3 min · jiezi

gin的控制器与路由

1.概述路由是一个过程,指的是一个http请求,如何找到对应的处理器函数(也可以叫控制器函数),Gin框架的路由是基于httprouter包实现的。控制器是在路由完成了URL检测和路由检测之后,路由器会分发请求到对应的路由地址,这也是应用请求的生命周期中最重要的一个环节。在这一步骤中,完成应用的业务逻辑及数据返回。 2.路由定义2.1.http请求方法常用的http请求方法有下面4种: GETPOSTPUTDELETE2.2.url路径gin框架,url路径有三种写法: 静态url路径带路径参数的url路径带星号(*)模糊匹配参数的url路径例子: // 例子1, 静态Url路径, 即不带任何参数的url路径/users/center/user/111/food/12// 例子2,带路径参数的url路径,url路径上面带有参数,参数由冒号(:)跟着一个字符串定义。// 路径参数值可以是数值,也可以是字符串//定义参数:id, 可以匹配/user/1, /user/899 /user/xiaoli 这类Url路径/user/:id//定义参数:id, 可以匹配/food/2, /food/100 /food/apple 这类Url路径/food/:id//定义参数:type和:page, 可以匹配/foods/2/1, /food/100/25 /food/apple/30 这类Url路径/foods/:type/:page// 例子3. 带星号(*)模糊匹配参数的url路径// 星号代表匹配任意路径的意思, 必须在*号后面指定一个参数名,后面可以通过这个参数获取*号匹配的内容。//以/foods/ 开头的所有路径都匹配//匹配:/foods/1, /foods/200, /foods/1/20, /foods/apple/1 /foods/*path//可以通过path参数获取*号匹配的内容。2.3.3.分组路由在做api开发的时候,如果要支持多个api版本,我们可以通过分组路由来实现api版本处理。 router := gin.Default()// 创建v1组v1 := router.Group("/v1"){ // 在v1这个分组下,注册路由 v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint)}// 创建v2组v2 := router.Group("/v2"){ // 在v2这个分组下,注册路由 v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint)}上面的例子将会注册下面的路由信息: /v1/login/v1/submit/v1/read/v2/login/v2/submit/v2/read路由分组,其实就是设置了同一类路由的url前缀。 3.控制器路由需要配合控制器才能完成一次请求,下面我们来看一下控制器的定义。 控制器函数定义: func HandlerFunc(c *gin.Context)控制器函数接受一个上下文参数。可以通过上下文参数,获取http请求参数,响应http请求。 下面我们通过一个例子看一下控制器的定义: //实例化gin实例对象。r := gin.Default() //定义post请求, url路径为:/users, 绑定saveUser控制器函数r.POST("/users", saveUser)//定义get请求,url路径为:/users/:id (:id是参数,例如: /users/10, 会匹配这个url模式),绑定getUser控制器函数r.GET("/users/:id", getUser)//定义put请求r.PUT("/users/:id", updateUser)//定义delete请求r.DELETE("/users/:id", deleteUser)//控制器函数实现func saveUser(c *gin.Context) { ...忽略实现...}func getUser(c *gin.Context) { ...忽略实现...}func updateUser(c *gin.Context) { ...忽略实现...}func deleteUser(c *gin.Context) { ...忽略实现...}提示:实际项目开发中不要把路由定义和控制器函数都写在一个go文件,不方便维护,可以参考项目结构,规划自己的业务模块。

June 8, 2020 · 1 min · jiezi

gin项目配置

1.概述项目配置是整个项目中很重要的一部分,一般项目的配置有数据库配置,应用配置(地址,端口等),缓存配置,第三方扩展的配置,中间件配置等等,可见配置在一个项目中的地位是很重要的,但是,gin中没有提供相关的配置管理的组件,我们可以使用go的第三方包来做配置管理,集成到gin中。 常用的第三方包有: iniyamlviper....本教程主要讲解ini,其他的请执行Google 2.ini的使用目录结构我们使用前面推荐项目结构 安装gopkg.in/ini.v1go get gopkg.in/ini.v1在conf下创建app.ini配置文件[server]address = 0.0.0.0port = 8080在pkg中创建config目录,并创建server.go,配置Server配置package configimport ( "cn.sockstack/gin_demo/pkg/helper" "gopkg.in/ini.v1")var Server *servertype server struct { Address string Port int source *ini.File}func (s *server) Load(path string) *server { var err error //判断配置文件是否存在 exists, err := helper.PathExists(path) if !exists { return s } s.source, err = ini.Load(path) if err != nil { panic(err) } return s}func (s *server)Init() *server { //判断配置是否加载成功 if s.source == nil { return s } s.Address = s.source.Section("server").Key("address").MustString("0.0.0.0") s.Port = s.source.Section("server").Key("port").MustInt(8080) return s}注意:helper.PathExists(path)是pkg/helper/util.go的工具方法,实现如下 ...

June 8, 2020 · 2 min · jiezi

项目结构设置

1.概述实际项目业务功能和模块会很多,我们不可能把所有代码都写在一个go文件里面或者写在一个main入口函数里面;我们需要对项目结构做一些规划,方便维护代码以及扩展。 Gin框没有对项目结构做出限制,我们可以根据自己项目需要自行设计。2.项目结构有视图模板├── conf #项目配置文件目录│ └── config.toml #大家可以选择自己熟悉的配置文件管理工具包例如:toml、xml等等├── controllers #控制器目录,按模块存放控制器(或者叫控制器函数),必要的时候可以继续划分子目录。│ └── user.go├── models #模型目录,负责项目的数据存储部分,例如各个模块的Mysql表的读写模型。│ ├── food.go│ └── user.go├── static #静态资源目录,包括Js,css,jpg等等,可以通过Gin框架配置,直接让用户访问。│ ├── css│ ├── images│ └── js├── logs #日志文件目录,主要保存项目运行过程中产生的日志。└── views #视图模板目录,存放各个模块的视图模板,当然有些项目只有api,是不需要视图部分,可以忽略这个目录│ └── index.html├── main.go #项目入口,这里负责Gin框架的初始化,注册路由信息,关联控制器函数等。api开发├── conf #项目配置文件目录│ └── config.toml #大家可以选择自己熟悉的配置文件管理工具包例如:toml、xml等等├── controllers #控制器目录,按模块存放控制器(或者叫控制器函数),必要的时候可以继续划分子目录。│ └── user.go├── models #模型目录,负责项目的数据存储部分,例如各个模块的Mysql表的读写模型。│ ├── food.go│ ├── user.go│ └── init.go #模型初始化├── logs #日志文件目录,主要保存项目运行过程中产生的日志。├── main.go #项目入口,这里负责Gin框架的初始化,注册路由信息,关联控制器函数等。以上两种目录结构基本上大同小异,一般的项目构建都可以使用这样的项目结构,清晰明了。 3.项目结构扩展对于一般的项目都是使用上面的目录结构,但是有时候项目复杂了,会进一步拆分,把数据校验,模型定义,数据操作,服务,响应进行解耦。 数据校验->控制器->调用服务->数据操作->数据模型->数据响应目录结构如下: ├── conf #项目配置文件目录│ └── config.toml #大家可以选择自己熟悉的配置文件管理工具包例如:toml、xml、ini等等├── requests #定义入参即入参校验规则│ └── user_request.go│ └── food_request.go├── responses #定义响应的数据│ └── user_response.go│ └── food_response.go├── services #服务定义目录| └── v1 #服务v1版本│ | └── user_service.go│ | └── food_service.go| └── v2 #服务v2版本│ | └── user_service.go│ | └── food_service.go├── api #api目录,按模块存放控制器(或者叫控制器函数),必要的时候可以继续划分子目录。│ └── v1 #apiv1版本│ | └── user.go│ | └── food.go│ └── v2 #apiv2版本│ | └── user.go│ | └── food.go├── router #路由目录│ └── v1 #路由v1版本│ | └── user.go│ | └── food.go│ └── v2 #路由v2版本│ | └── user.go│ | └── food.go├── init.go #路由初始化├── pkg #自定义的工具类等│ └── e #项目统一的响应定义,如错误码,通用的错误信息,响应的结构体│ └── util #工具类目录├── models #模型目录,负责项目的数据存储部分,例如各个模块的Mysql表的读写模型。│ ├── food.go│ ├── user.go│ └── init.go #模型初始化├── repositories #数据操作层,定义各种数据操作。│ └── user_repository.go│ └── food_repository.go├── logs #日志文件目录,主要保存项目运行过程中产生的日志。├── main.go #项目入口,这里负责Gin框架的初始化,注册路由信息,关联控制器函数等。通过上面的项目结构扩展,增加了代码量,但是把各层的职责界定了,每一层的只做每一层的事情,数据解耦 ...

June 8, 2020 · 1 min · jiezi

系列-goginapi-规划目录和参数验证二

概述首先同步下项目概况: 上篇文章分享了,使用 go modules 初始化项目,这篇文章咱们分享: 规划目录结构模型绑定和验证自定义验证器制定 API 返回结构废话不多说,咱们开始吧。 规划目录结构├─ go-gin-api│ ├─ app│ ├─ config //配置文件│ ├─ config.go│ ├─ controller //控制器层│ ├─ param_bind│ ├─ param_verify│ ├─ ...│ ├─ model //数据库ORM│ ├─ proto│ ├─ ...│ ├─ repository //数据库操作层│ ├─ ...│ ├─ route //路由│ ├─ middleware│ ├─ route.go│ ├─ service //业务层│ ├─ ...│ ├─ util //工具包│ ├─ ...│ ├─ vendor //依赖包│ ├─ ...│ ├─ go.mod│ ├─ go.sum│ ├─ main.go //入口文件上面的目录结构是我自定义的,大家也可以根据自己的习惯去定义。 ...

August 28, 2019 · 2 min · jiezi

beego注解路由不生成的解决问题

首先确定app.conf内的runmode的值是否是dev,如果确定了是,那你就碰到了一个Beego到现在都没解决的bug,解决办法如下: 在main.go加入下列代码 //go:generate sh -c "echo 'package routers; import \"github.com/astaxie/beego\"; func init() {beego.BConfig.RunMode = beego.DEV}' > routers/0.go"//go:generate sh -c "echo 'package routers; import \"os\"; func init() {os.Exit(0)}' > routers/z.go"//go:generate go run $GOFILE//go:generate sh -c "rm routers/0.go routers/z.go"然后执行 go generate就会生成路由了。

August 20, 2019 · 1 min · jiezi

Snow简单易用的Go语言业务框架

项目地址中文文档changelogSnowSnow是一套简单易用的Go语言业务框架,整体逻辑设计简洁,支持HTTP服务、队列调度和任务调度等常用业务场景模式。 Goals我们致力于让PHPer更方便地切入到Go语言开发,在业务框架选择上贴合PHP主流框架的设计思想,以更低的学习成本快速熟悉框架,致力于业务逻辑的开发。 FeaturesHTTP服务:基于gin进行模块化设计,简单易用、核心足够轻量;支持平滑重启;任务调度:基于cron进行模块化设计,简单易用;队列调度:基于自研的队列调度服务worker,通过Queue接口化,解耦队列调度与底层队列驱动;支持平滑关闭;Cache: 通用的接口化设计,框架实现了redis作为缓存底层驱动,支持可扩展;Database: 使用成熟的ORM库,有丰富的数据库驱动支持和特性;Queue: 通用的接口化设计,框架实现了redis、alimns作为队列底层驱动,支持可扩展;Config: 采用toml语义化的配置文件格式,简单易用;Logger: 基于logrus进行封装,内嵌上下文通用数据采集和trace_id追踪;Request and Response:定义输入和输出数据实体格式;Curl: 简单易用的Curl请求库;Quick startRequirementsGo version>=1.12 Installationcd $GOPATH/srccd my-github/my-spacegit clone git@github.com/qit-team/snow.git my-projectcd my-projectsh build/shell/replace.sh my-github/my-space/my-projectBuild & Runsh build/shell/build.shbuild/bin/snowTest democurl "http://127.0.0.1:8000/hello" #返回json串输出Documents中文文档changelog

July 8, 2019 · 1 min · jiezi

httptest-的介绍与使用

在写完接口之后都需要对接口进行测试,在 golang 标准库中提供 httptest 包来辅助测试。 因为接口都是需要 IP 地址或域名来访问,httptest 包中默认定义了服务地址 const DefaultRemoteAddr = "1.2.3.4"重要的方法NewRequest(请求体)NewRequest 方法用来创建一个 http 的请求体。 方法定义: func NewRequest(method, target string, body io.Reader) *http.Requestmethod 参数表示测试的接口的 HTTP 方法。target 参数表示接口定义的路由。body 参数表示请求体。NewRecorder(响应体)方法定义: func NewRecorder() *ResponseRecorderNewRecorder 方法用来创建 http 的响应体。返回的类型是 *httptest.ResponseRecorder ,包含接口返回信息,等价于 http.ResponseWriter。 ResponseRecorder类型定义: type ResponseRecorder struct { // http 响应码. Code int // 头部信息 HeaderMap http.Header // 返回的 Body Body *bytes.Buffer // 是否调用 Flush 方法 Flushed bool}NewServer(http服务)方法定义: func NewServer(handler http.Handler) *ServerNewServer 方法用来创建和启动新的服务。同类的还有 NewTLSServer,用来创建带 SSL 的服务。 ...

June 24, 2019 · 2 min · jiezi

深入理解gin-framework二

gin flow 分析Handler是如何注册和传递的?路由是如何解析的?http方法又是如何处理,使其满足RESTful规范的?这些细节都需要深入到代码层面来分析辅助功能既然是框架,自然会处理各种项目中共性的问题,比如说404 Not Found。这部分框架的功能不是分析的重点。启动http服务在深入理解gin framework(一)的例子中,由r.Run(":14000")启动http服务。这里只能传入空值或者一个地址,否则会报错。源码如下,空值时默认端口为8080. 启动http server的Run函数,TLS相似,只不过是加了个证书。其实还是把address传给官方标准包http的ListenAndServe函数,此处的engine struct只需要实现Handler接口,也就是实现ServeHTTP函数即可。 engine struct中的pool就是sync.Pool,代码中可以看出来,每处理一个http请求,都会从连接池里边取出一个Context,把请求参数传递给这个Context,处理完之后,再把这个Context放回去。处理http请求从上边的源码可以看出来,所有的http请求都会走到handleHTTPRequest函数中去处理。(未完待续)

May 28, 2019 · 1 min · jiezi

SQLRESTful开源GO脚手架工具ginbrogin-and-gorms-brother-详解

安装felixgit clone https://github.com/dejavuzhou/felixcd felixgo mod downloadgo installecho "添加 GOBIN 到 PATH环境变量"echo "或者"go get github.com/dejavuzhou/felixecho "go build && ./felix -h"What is GinbroGin脚手架工具:因为工作中非常多次的使用mysql数据库 + gin + GORM 来开发RESTful API程序,所以开发一个Go语言的RESTful APIs的脚手架工具Ginbro代码来源:Ginrbo的代码迭代自github.com/dejavuzhou/ginbroSPA二进制化工具:vuejs全家桶代码二进制化成go代码,编译的时候变成二进制,运行的时候直接加载到内存中,同时和gin API在一个域名下不需要再nginx中配置rewrite或者跨域,加快API访问速度功能一:Gin+GORM_SQL RESTful 脚手架工具工作原理通过cobra 获取命令行参数使用sql参数连接数据库后去数据库表的名称和字段类型等数据库数据库边的表名和字段信息,转换成 Swagger doc 规范字段 和 GORM 模型字段使用标准库 text/template 生成swagger.yaml, GORM 模型文件, GIN handler 文件 ...使用 go fmt ./... 格式化代码使用标准库archive/zip打包*.go config.toml ...代码,提供zip文件下载(命令行模式没有)支持数据库大多数SQL数据库mysqlSQLitepostgreSQLmssql(TODO:: sqlserver)ginbro 生成app代码包含功能简介每一张数据库表生成一个RESTful规范的资源(GET<pagination>/POST/GET<one>/PATCH/DELETE)支持API-json数据分页-和总数分页缓存,减少全表扫描支持golang-内存单机缓存缓存前端代码和API公用一个服务,减少跨域OPTION的请求时间和配置时间,同时完美支持前后端分离开箱支持jwt-token认证和Bearer Token 路由中间件开箱即用的logrus数据库开箱即用的viper配置文件开箱即用的swagger API 文档开箱即用的定时任务系统项目演示地址felix sshw 网页UI演示地址 用户名和密码都是admin生成swagger API交互文档地址 http://ginbro.mojotv.cn/swagger/msql生成go代码地址bili命令行演示视频地址命令行参数详解[root@ericzhou felix]# felix ginbro -hgenerate a RESTful APIs app with gin and gorm for gophersUsage: felix ginbro [flags]示例:felix ginbro -a dev.wordpress.com:3306 -P go_package_name -n db_name -u db_username -p 'my_db_password' -d '~/thisDir'Flags: --authColumn string 使用bcrypt方式加密的用户表密码字段名称 (default "password") --authTable string 认知登陆用户表名称 (default "users") -a, --dbAddr string 数据库连接的地址 (default "127.0.0.1:3306") -c, --dbChar string 数据库字符集 (default "utf8") -n, --dbName string 数据库名称 -p, --dbPassword string 数据库密码 (default "password") -t, --dbType string 数据库类型: mysql/postgres/mssql/sqlite (default "mysql") -u, --dbUser string 数据库用户名 (default "root") -d, --dir string golang代码输出的目录,默认是当前目录 (default ".") -h, --help 帮助 -l, --listen string 生成go app 接口监听的地址 (default "127.0.0.1:5555") --pkg string 生成go app 包名称(go version > 1.12) 生成go.mod文件, eg: ginbroSon[root@ericzhou felix]# web界面对于那些喜欢使用命令行的同学,你们可以选择使用web界面来操作 ...

May 22, 2019 · 2 min · jiezi

深入理解gin-framework一

gin框架众多的golang web框架中,gin是一个比较轻量级的框架,不像beego那样,还有orm模块。接口设计,运行速度方面,gin都算是比较让人满意的。web框架的顶层设计每个语言都有自己框架的特点,golang的话,因为有官方的标准库,标准库的质量是比较可靠的,所以绝大多数框架都是基于标准库,面向实际应用场景,将底层的细节进一步地抽象,达到减少开发时间,提升代码质量的目的。所以,在顶层分析框架设计的时候,需要明确现在web框架主要的功能,以及golang标准包提供了什么工具。需求:RESTful接口。目前主流的后端设计都是围绕接口展开的,而目前主流的接口设计范式就是RESTful。也就是说,框架的设计目标应该满足以下几点: 便捷的路由分组快速调用http方法 get,post,delete,put,patch,head,options等进行资源管理良好的并发性能解析URL参数或request body便捷需要对JSON有良好的支持golang的http标准包有哪些组件,组件之间的关联又是如何?http标准包是开发web框架必不可少的原材料: 在http包的源码中,有几个重要的部件:其中struct 有 Server,Conn。interface有Handler。分别对应服务器对象,connection对象,以及路由器。http标准包的编程逻辑:需要实现Handler接口的ServeHTTP方法,相当于新建一个路由规则,当server启动,建立http连接之后,就可以按照定义的路由规则进行处理了。gin是如何整合http包的定义一个engine,实现ServeHTTP方法,这时,就可以将engine 作为Handler传给http包的ListenAndServe函数。从http标准包的角度来看,gin就是实现了一个功能强大的Handler。(未完待续)

May 19, 2019 · 1 min · jiezi

Go-Gin源码学习五

Gin路由主要流程实现经过上一篇的学习笔记,我们已经知道了Gin router的主要流程。但是我们看到代码和方法体总体很长,其中大部分是参数路由的判断。这些零散的小逻辑,让我们阅读源码的时候更难理解了一些。但是其实基数树的逻辑兵没有这么的复杂,所以我们还是按照老规矩,自己实现以下这个简单的基数树值包含主流程。代码如下: package myginimport "fmt"type Trees map[string]*nodetype node struct { path string indices string children []*node handlers HandlerList}func (n *node) addRoute(path string, handlers HandlerList) { if len(n.path) > 0 || len(n.children) > 0 { walk: for { //找到相等的index i := 0 max := min(len(path), len(n.path)) for max > i && path[i] == n.path[i] { i++ } //需要把原来的作为子node放到新node中 if i < len(n.path) { //新建node child := node{ path: n.path[i:], indices: n.indices, handlers: n.handlers, children: n.children, } n.children = []*node{&child} n.indices = string([]byte{n.path[i]}) n.path = path[:i] n.handlers = nil } // 判断子节点如果有相同开头的字符 则从新跳入循环 if i < len(path) { c := path[i] for index := 0; index < len(n.indices); index++ { if c == n.indices[index] { n = n.children[index] path = path[i:] continue walk } } //把新请求的path加入到router中 n.insertChild(path[i:], path, handlers, i) return } return } } else { //如果为空 n.path = path n.handlers = handlers }}func (n *node) insertChild(path, fullPath string, handlers HandlerList, index int) { child := node{} child.handlers = handlers child.indices = "" child.path = path n.indices += string([]byte{fullPath[index]}) n.children = append(n.children, &child)}func min(a, b int) int { if a > b { return b } return a}func (n *node) getValue(path string) (handlers HandlerList) { index := 1walk: for { fmt.Println("loop num: ", index) if len(path) > len(n.path) { path = path[len(n.path):] c := path[0] for i := 0; i < len(n.indices); i++ { if c == n.indices[i] { n = n.children[i] index++ goto walk } } } else if len(path) == len(n.path) { handlers = n.handlers return } }}总结上面的代码已经不需要太多的注释了,去掉了参数节点的代码整个流程已经很明确了。 ...

May 15, 2019 · 2 min · jiezi

Go-Gin源码学习四

基数树这次学习的是Gin中的路由,在学习源码一种我们看到了Gin的路由是它的特色。然而基础数据使用了基数树也提供了性能的保障。因为路由这部分比较独立而且逻辑相对复杂,所以需要单独学习。首先我们需要了解的是基数树,百度百科中的解释其中有一个图可以让我们更加直观的看到数据是如何存储的。基数树,相当于是一种前缀树。对于基数树的每个节点,如果该节点是确定的子树的话,就和父节点合并。基数树可用来构建关联数组。在上面的图里也可以看到,数据结构会把所有相同前缀都提取 剩余的都作为子节点。 基数树在Gin中的应用从上面可以看到基数树是一个前缀树,图中也可以看到数据结构。那基数树在Gin中是如何应用的呢?举一个例子其实就能看得出来router.GET("/support", handler1)router.GET("/search", handler2)router.GET("/contact", handler3)router.GET("/group/user/", handler4)router.GET("/group/user/test", handler5)最终的内存结构为: / (handler = nil, indices = "scg") s (handler = nil, indices = "ue") upport (handler = handler1, indices = "") earch (handler = handler2, indices = "") contact (handler = handler3, indices = "") group/user/ (handler = handler4, indices = "u") test (handler = handler5, indices = "")可以看到 router使用get方法添加了5个路由,实际存储结果就是上面显示的。我特地在后面加上了每个节点中的handler和indices。 indices是有序保存所有子节点的第一个字符形成的字符串。为什么要特意突出这个字段,因为在查找子节点下面是否包含path的时候不需要循环子节点,只需要循环这个字段就可以知道是否包含。这样的操作也可以提升一些效率。 源码查看先看一下节点的对象的定义和如何调用的,需要注意的是indices这个字段 上面已经提到了它的作用 type node struct { // 保存这个节点上的URL路径 // 例如上图中的search和support, 共同的parent节点的path="s" // 后面两个节点的path分别是"earch"和"upport" path string // 判断当前节点路径是不是参数节点, 例如上图的:post部分就是wildChild节点 wildChild bool // 节点类型包括static, root, param, catchAll // static: 静态节点, 例如上面分裂出来作为parent的s // root: 如果插入的节点是第一个, 那么是root节点 // catchAll: 有*匹配的节点 // param: 除上面外的节点 nType nodeType // 记录路径上最大参数个数 maxParams uint8 // 和children[]对应, 保存的是分裂的分支的第一个字符 // 例如search和support, 那么s节点的indices对应的"eu" // 代表有两个分支, 分支的首字母分别是e和u indices string // 保存孩子节点 children []*node // 当前节点的处理函数 handle Handle // 优先级 priority uint32}//RouterGrou实现的GET方法调用了handlerfunc (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("GET", relativePath, handlers)}func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { //方法计算出路径,把group中的basepath和relativepath 合并在一起 absolutePath := group.calculateAbsolutePath(relativePath) //合并handler 把group中添加的中间件和传入的handlers合并起来 handlers = group.combineHandlers(handlers) //调用addRoute 添加router group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj()}接下来我们需要看的是addRoute这个方法了,方法体比较长。其实大多的逻辑都在处理带参数的节点,真正核心的逻辑其实并不多。我把主要的逻辑都写上了注释应该还是比较容易理解的。如果看不懂其实一步步debug几次也能帮助理解。 ...

May 12, 2019 · 8 min · jiezi

Go-Gin源码学习三

学习目标在第一篇中看到了Gin提供了很多的获取和解析参数的方法: // **** 输入数据//从URL中拿值,比如 /user/:id => /user/johnParam(key string) string //从GET参数中拿值,比如 /path?id=johnGetQueryArray(key string) ([]string, bool) GetQuery(key string)(string, bool)Query(key string) stringDefaultQuery(key, defaultValue string) stringGetQueryArray(key string) ([]string, bool)QueryArray(key string) []string//从POST中拿数据GetPostFormArray(key string) ([]string, bool)PostFormArray(key string) []string GetPostForm(key string) (string, bool)PostForm(key string) stringDefaultPostForm(key, defaultValue string) string// 文件FormFile(name string) (*multipart.FileHeader, error)MultipartForm() (*multipart.Form, error)SaveUploadedFile(file *multipart.FileHeader, dst string) error// 数据绑定Bind(obj interface{}) error //根据Content-Type绑定数据BindJSON(obj interface{}) errorBindQuery(obj interface{}) error//--- Should ok, else return errorShouldBindJSON(obj interface{}) error ShouldBind(obj interface{}) errorShouldBindJSON(obj interface{}) errorShouldBindQuery(obj interface{}) error其中从url中获取 从get参数中获取 从post拿数据相信我们都可以想象的到,基本就是从request中的url或者body中获取数据然后返回但是其中的数据绑定我自己开始是很疑惑的,到底是怎么实现的。疑惑的是如果object中我客户端少输入了参数 或者多输入的参数会是怎么样。举个例子: ...

May 10, 2019 · 2 min · jiezi

Go-Gin源码学习一

Gin的基本使用Gin是一个比较轻量级的http框架,主要是提供了几个便于使用的功能: 简单的中间件注册,可以很方便的实现通用中间件的使用注册提供了比较方便和全面的路由注册,方便的实现RESTful接口的实现提供了便捷的获取参数的方法,包括get、post兵可以可以把数据直接转换成对象对路由的分组,Gin可以对一组路由做统一的中间件注册等操作可以手机所有错误,统一在统一的地方写日志性能方面: 是路由的基础数据格式为基数树没有使用反射,所以性能方面也是比较低消耗内存低上下文context使用了对象池,fasthttp中也同样使用了sync.pool使用方便也是比较简单的,下面有一个很简单的例子 package mainimport ( "fmt" "github.com/gin-gonic/gin" "test/gin/middleware/model")func main() { //创建router router := gin.Default() //创建组 group := router.Group("/api") //为组加中间件 group.Use(func(context *gin.Context) { fmt.Println("api group url:", context.Request.URL.String()) }) //为组加路由方法 group.GET("/test", func(context *gin.Context) { context.JSON(200, model.Message{Message:"ok"}) }) //运行 router.Run(":3333")}例子中是一个最简单的Gin框架的应用。创建了一个engine,创建了组并且为组添加了中间件之后在这个group下的路由方法都将使用这个中间件,方便对api最系统的管理对不同的api做不同的处理。在Terminal中访问可以看到下面的结果 curl http://localhost:3333/api/test{"Message":"ok"}panleiMacBook-Pro:test 主要流程的源码首先我们先看例子中的gin.Default() 返回的engine对象,这是Gin的主要对象。只对最主要的属性加了注释 type Engine struct { //路由组 RouterGroup RedirectTrailingSlash bool RedirectFixedPath bool HandleMethodNotAllowed bool ForwardedByClientIP bool AppEngine bool UseRawPath bool UnescapePathValues bool MaxMultipartMemory int64 delims render.Delims secureJsonPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain // 对象池 用来创建上下文context pool sync.Pool //记录路由方法的 比如GET POST 都会是数组中的一个 每个方法对应一个基数树的一个root的node trees methodTrees}然后我们来看Default方法,其实很简单就是创建一个engine对象并且添加默认的两个中间件,一个是做log显示,显示每次请求可以再console中看到。每次创建engine对象的时候回默认的添加一个routergroup地址为默认的"/" 代码如下: ...

May 7, 2019 · 3 min · jiezi

JWT 在 Gin 中的使用

介绍JSON Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该 Token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 Token 也可直接被用于认证,也可被加密。使用安装go get github.com/appleboy/gin-jwt引入import “github.com/appleboy/gin-jwt"我目前使用的版本是 v2.5.0.<!– more –>创建中间件设计 API 对象type API struct { App *apps.App // 业务对象 Router *gin.Engine // 路由 JWT *jwt.GinJWTMiddleware // jwt 对象}中间件对象:api.JWT = &jwt.GinJWTMiddleware{ Realm: “gin jwt”, Key: []byte(“secret key”), Timeout: time.Hour, MaxRefresh: time.Hour, PayloadFunc: func(data interface{}) jwt.MapClaims {}, Authenticator: func(c *gin.Context) (interface{}, error) {}, Authorizator: func(data interface{}, c *gin.Context) bool {}, Unauthorized: func(c *gin.Context, code int, message string) {}, TokenLookup: “header: Authorization, query: token, cookie: jwt”, // TokenLookup: “query:token”, // TokenLookup: “cookie:token”, TokenHeadName: “Bearer”, TimeFunc: time.Now, }Realm JWT标识Key 服务端密钥Timeout token 过期时间MaxRefresh token 更新时间PayloadFunc 添加额外业务相关的信息Authenticator 在登录接口中使用的验证方法,并返回验证成功后的用户对象。Authorizator 登录后其他接口验证传入的 token 方法Unauthorized 验证失败后设置错误信息TokenLookup 设置 token 获取位置,一般默认在头部的 Authorization 中,或者 query的 token 字段,cookie 中的 jwt 字段。TokenHeadName Header中 token 的头部字段,默认常用名称 Bearer。TimeFunc 设置时间函数注册阶段在注册时如果要直接返回 token,那么可以调用 TokenGenerator 来生成 token。token, expire, err := c.JWT.TokenGenerator(strconv.Itoa(user.ID), *user)TokenGenerator 的具体实现func (mw *GinJWTMiddleware) TokenGenerator(userID string, data interface{}) (string, time.Time, error) { // 根据签名算法创建 token 对象 token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm)) // 获取 claims claims := token.Claims.(jwt.MapClaims) // 设置业务中需要的额外信息 if mw.PayloadFunc != nil { for key, value := range mw.PayloadFunc(data) { claims[key] = value } } // 过期时间 expire := mw.TimeFunc().UTC().Add(mw.Timeout) claims[“id”] = userID claims[“exp”] = expire.Unix() claims[“orig_iat”] = mw.TimeFunc().Unix() // 生成 token tokenString, err := mw.signedString(token) if err != nil { return “”, time.Time{}, err } return tokenString, expire, nil}登录阶段登录时会调用 Authenticator 注册的方法。func (api *API) LoginAuthenticator(ctx *gin.Context) (interface{}, error) { var params model.UserParams if err := ctx.Bind(&params); err != nil { return “”, jwt.ErrMissingLoginValues } // 根据用户名获取用户 user, err := api.App.GetUserByName(params.Username) if err != nil { return nil, err } // 验证密码 if user.AuthPassword(params.Password) { return *user, nil } return nil, jwt.ErrFailedAuthentication}验证 Token其他接口在设置了中间件 Router.Use(api.JWT.MiddlewareFunc()) 后,通过调用 Authorizator 方法来验证。func (api *API) LoginedAuthorizator(data interface{}, c *gin.Context) bool { if id, ok := data.(string); ok { return api.App.IsExistUser(id) } return false}在业务 Hander 中可以通过方法 jwt.ExtractClaims(ctx) 来获取 payload 的信息。深入gin-jwt 依赖的 jwt 库叫做 jwt-go。下面来介绍一下这个库。核心的 Token 结构:// A JWT Token. Different fields will be used depending on whether you’re// creating or parsing/verifying a token.type Token struct { Raw string // The raw token. Populated when you Parse a token Method SigningMethod // The signing method used or to be used Header map[string]interface{} // The first segment of the token Claims Claims // The second segment of the token Signature string // The third segment of the token. Populated when you Parse a token Valid bool // Is the token valid? Populated when you Parse/Verify a token}这个Token结构体是用来生成 jwt 的 token。其中 Method 是用来表示签名使用的算法。Header 是头部jwt的信息,还有 Claims 记录额外的信息。然后是生成签名的方法,key 是服务端的密钥。func (t *Token) SignedString(key interface{}) (string, error) { var sig, sstr string var err error // 将 Header 和 Claims 转换成字符串然后 base64 之后拼接在一起。 if sstr, err = t.SigningString(); err != nil { return “”, err } // 使用签名算法加密 if sig, err = t.Method.Sign(sstr, key); err != nil { return “”, err } return strings.Join([]string{sstr, sig}, “.”), nil}解密 token 的对象叫做 Parsertype Parser struct {}// 主要解析方法func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}Parser 除了验证 Token 外,还包括解码 Header 和 Claims 的内容。资源https://jwt.io/introductionhttps://github.com/appleboy/g…https://github.com/dgrijalva/… ...

April 19, 2019 · 3 min · jiezi