首页 > 数据库 >Go语言gin框架入门到精通,涵盖文件服务器、中间件、安全认证、数据库

Go语言gin框架入门到精通,涵盖文件服务器、中间件、安全认证、数据库

时间:2023-11-03 15:11:20浏览次数:46  
标签:http func -- 中间件 Context Go gin

Go语言gin框架入门到精通,涵盖文件服务器、中间件、安全认证、数据库

 

Gin

官方文档:Gin Web Framework (gin-gonic.com)

仓库地址:gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang)

官方示例:gin-gonic/examples: A repository to host examples and tutorials for Gin. (github.com)

介绍

Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。 如果您需要性能和良好的生产力,您一定会喜欢 Gin。Gin相比于Iris和Beego而言,更倾向于轻量化的框架,只负责Web部分,追求极致的路由性能,功能或许没那么全,胜在轻量易拓展,这也是它的优点。因此,在所有的Web框架中,Gin是最容易上手和学习的。

Gin是一个Web框架,并非MVC框架,MVC的功能需要开发者自行实现。这里推荐一个很优秀的GinServer端项目: gin-vue-admin | GVA 文档站,里面的项目结构,代码,路由等都很值得学习。

特性

  • 快速:基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。

  • 支持中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。

  • Crash 处理:Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。

  • JSON 验证:Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。

  • 路由组:更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。

  • 错误管理:Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。

  • 内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。

  • 可扩展性:新建一个中间件非常简单

安装

截止目前2022/11/22,gin支持的go最低版本为1.16,建议使用go mod来管理项目依赖。

go get -u github.com/gin-gonic/gin
 

导入

import "github.com/gin-gonic/gin"
 

快速开始

  1.   package main
  2.    
  3.   import (
  4.   "github.com/gin-gonic/gin"
  5.   "net/http"
  6.   )
  7.    
  8.   func main() {
  9.   engine := gin.Default() //创建gin引擎
  10.   engine.GET("/ping", func(context *gin.Context) {
  11.   context.JSON(http.StatusOK, gin.H{
  12.   "message": "pong",
  13.   })
  14.   })
  15.   engine.Run() //开启服务器,默认监听localhost:8080
  16.   }

请求URL

GET localhost:8080/ping
 

返回

  1.   HTTP/1.1 200 OK
  2.   Content-Type: application/json; charset=utf-8
  3.   Date: Tue, 22 Nov 2022 08:47:11 GMT
  4.   Content-Length: 18
  5.    
  6.   {
  7.   "message": "pong"
  8.   }
  9.   Response file saved.
  10.   > 2022-11-22T164711.200.json

教程

其实Gin官方文档里面并没有多少教程,大多数只是一些介绍和基本使用和一些例子,但是gin-gonic/ 组织下,有一个gin-gonic/examples仓库,这是一个由社区共同维护的gin示例仓库。都是全英文,更新时间并不是特别频繁,笔者也是从这里慢慢学习的gin框架。

示例仓库地址:gin-gonic/examples: A repository to host examples and tutorials for Gin. (github.com)

开始之前建议可以阅读一下HttpRouter的简单教程: HttpRouter | Go中文学习文档 (halfiisland.com)

参数解析

gin中的参数解析总共支持三种方式:路由参数,URL参数,表单参数,下面逐一讲解并结合代码示例,比较简单易懂。

路由参数

路由参数其实是封装了HttpRouter的参数解析功能,使用方法基本上与HttpRouter一致。

  1.   package main
  2.    
  3.   import (
  4.   "github.com/gin-gonic/gin"
  5.   "log"
  6.   "net/http"
  7.   )
  8.    
  9.   func main() {
  10.   e := gin.Default()
  11.   e.GET("/findUser/:username/:userid", FindUser)
  12.   e.GET("/downloadFile/*filepath", UserPage)
  13.    
  14.   log.Fatalln(e.Run(":8080"))
  15.   }
  16.    
  17.   // 命名参数示例
  18.   func FindUser(c *gin.Context) {
  19.   username := c.Param("username")
  20.   userid := c.Param("userid")
  21.   c.String(http.StatusOK, "username is %s\n userid is %s", username, userid)
  22.   }
  23.    
  24.   // 路径参数示例
  25.   func UserPage(c *gin.Context) {
  26.   filepath := c.Param("filepath")
  27.   c.String(http.StatusOK, "filepath is %s", filepath)
  28.   }

示例一

curl --location --request GET '127.0.0.1:8080/findUser/jack/001'
 
  1.   username is jack
  2.   userid is 001

示例二

curl --location --request GET '127.0.0.1:8080/downloadFile/img/fruit.png'
 
filepath is  /img/fruit.png
 

URL参数

传统的URL参数,格式就是/url?key=val&key1=val1&key2=val2。

  1.   package main
  2.    
  3.   import (
  4.   "github.com/gin-gonic/gin"
  5.   "log"
  6.   "net/http"
  7.   )
  8.    
  9.   func main() {
  10.   e := gin.Default()
  11.   e.GET("/findUser", FindUser)
  12.   log.Fatalln(e.Run(":8080"))
  13.   }
  14.    
  15.   func FindUser(c *gin.Context) {
  16.   username := c.DefaultQuery("username", "defaultUser")
  17.   userid := c.Query("userid")
  18.   c.String(http.StatusOK, "username is %s\nuserid is %s", username, userid)
  19.   }

示例一

curl --location --request GET '127.0.0.1:8080/findUser?username=jack&userid=001'
 
  1.   username is jack
  2.   userid is 001

示例二

curl --location --request GET '127.0.0.1:8080/findUser'
 
  1.   username is defaultUser
  2.   userid is

表单参数

表单的内容类型一般有application/json,application/x-www-form-urlencoded,application/xml,multipart/form-data。

  1.   package main
  2.    
  3.   import (
  4.   "github.com/gin-gonic/gin"
  5.   "net/http"
  6.   )
  7.    
  8.   func main() {
  9.   e := gin.Default()
  10.   e.POST("/register", RegisterUser)
  11.   e.POST("/update", UpdateUser)
  12.   e.Run(":8080")
  13.   }
  14.    
  15.   func RegisterUser(c *gin.Context) {
  16.   username := c.PostForm("username")
  17.   password := c.PostForm("password")
  18.   c.String(http.StatusOK, "successfully registered,your username is [%s],password is [%s]", username, password)
  19.   }
  20.    
  21.   func UpdateUser(c *gin.Context) {
  22.   var form map[string]string
  23.   c.ShouldBind(&form)
  24.   c.String(http.StatusOK, "successfully update,your username is [%s],password is [%s]", form["username"], form["password"])
  25.   }

示例一:使用form-data

  1.   curl --location --request POST '127.0.0.1:8080/register' \
  2.   --form 'username="jack"' \
  3.   --form 'password="123456"'
successfully registered,your username is [jack],password is [123456]
 

PostForm方法默认解析application/x-www-form-urlencoded和multipart/form-data类型的表单。

示例二:使用json

  1.   curl --location --request POST '127.0.0.1:8080/update' \
  2.   --header 'Content-Type: application/json' \
  3.   --data-raw '{
  4.   "username":"username",
  5.   "password":"123456"
  6.   }'
successfully update,your username is [username],password is [123456]
 

数据解析

在大多数情况下,我们都会使用结构体来承载数据,而不是直接解析参数。在gin中,用于数据绑定的方法主要是Bind()和ShouldBind(),两者的区别在于前者内部也是直接调用的ShouldBind(),当然返回err时,会直接进行400响应,后者则不会。如果想要更加灵活的进行错误处理,建议选择后者。这两个函数会自动根据请求的content-type来进行推断用什么方式解析。

  1.   func (c *Context) MustBindWith(obj any, b binding.Binding) error {
  2.   // 调用了ShouldBindWith()
  3.   if err := c.ShouldBindWith(obj, b); err != nil {
  4.   c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // 直接响应400 badrequest
  5.   return err
  6.   }
  7.   return nil
  8.   }

如果想要自行选择可以使用BindWith()和ShouldBindWith(),例如

  1.   c.MustBindWith(obj, binding.JSON) //json
  2.   c.MustBindWith(obj, binding.XML) //xml

gin支持的绑定类型有如下几种实现:

  1.   var (
  2.   JSON = jsonBinding{}
  3.   XML = xmlBinding{}
  4.   Form = formBinding{}
  5.   Query = queryBinding{}
  6.   FormPost = formPostBinding{}
  7.   FormMultipart = formMultipartBinding{}
  8.   ProtoBuf = protobufBinding{}
  9.   MsgPack = msgpackBinding{}
  10.   YAML = yamlBinding{}
  11.   Uri = uriBinding{}
  12.   Header = headerBinding{}
  13.   TOML = tomlBinding{}
  14.   )

示例

  1.   package main
  2.    
  3.   import (
  4.   "fmt"
  5.   "github.com/gin-gonic/gin"
  6.   "net/http"
  7.   )
  8.    
  9.   type LoginUser struct {
  10.   Username string `bind:"required" json:"username" form:"username" uri:"username"`
  11.   Password string `bind:"required" json:"password" form:"password" uri:"password"`
  12.   }
  13.    
  14.   func main() {
  15.   e := gin.Default()
  16.   e.POST("/loginWithJSON", Login)
  17.   e.POST("/loginWithForm", Login)
  18.   e.GET("/loginWithQuery/:username/:password", Login)
  19.   e.Run(":8080")
  20.   }
  21.    
  22.   func Login(c *gin.Context) {
  23.   var login LoginUser
  24.   // 使用ShouldBind来让gin自动推断
  25.   if c.ShouldBind(&login) == nil && login.Password != "" && login.Username != "" {
  26.   c.String(http.StatusOK, "login successfully !")
  27.   } else {
  28.   c.String(http.StatusBadRequest, "login failed !")
  29.   }
  30.   fmt.Println(login)
  31.   }

Json数据绑定

  1.   curl --location --request POST '127.0.0.1:8080/loginWithJSON' \
  2.   --header 'Content-Type: application/json' \
  3.   --data-raw '{
  4.   "username":"root",
  5.   "password":"root"
  6.   }'
login successfully !
 

表单数据绑定

  1.   curl --location --request POST '127.0.0.1:8080/loginWithForm' \
  2.   --form 'username="root"' \
  3.   --form 'password="root"'
login successfully !
 

URL数据绑定

curl --location --request GET '127.0.0.1:8080/loginWithQuery/root/root'
 
login failed !
 

到了这里就会发生错误了,因为这里输出的content-type是空字符串,无法推断到底是要如何进行数据解析。所以当使用URL参数时,我们应该手动指定解析方式,例如:

  1.   if err := c.ShouldBindUri(&login); err == nil && login.Password != "" && login.Username != "" {
  2.   c.String(http.StatusOK, "login successfully !")
  3.   } else {
  4.   fmt.Println(err)
  5.   c.String(http.StatusBadRequest, "login failed !")
  6.   }

多次绑定

一般方法都是通过调用 c.Request.Body 方法绑定数据,但不能多次调用这个方法,例如c.ShouldBind,不可重用,如果想要多次绑定的话,可以使用

c.ShouldBindBodyWith。

  1.   func SomeHandler(c *gin.Context) {
  2.   objA := formA{}
  3.   objB := formB{}
  4.   // 读取 c.Request.Body 并将结果存入上下文。
  5.   if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
  6.   c.String(http.StatusOK, `the body should be formA`)
  7.   // 这时, 复用存储在上下文中的 body。
  8.   }
  9.   if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
  10.   c.String(http.StatusOK, `the body should be formB JSON`)
  11.   // 可以接受其他格式
  12.   }
  13.   if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
  14.   c.String(http.StatusOK, `the body should be formB XML`)
  15.   }
  16.   }
c.ShouldBindBodyWith 会在绑定之前将 body 存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。只有某些格式需要此功能,如 JSON, XML, MsgPack, ProtoBuf。 对于其他格式, 如 Query, Form, FormPost, FormMultipart 可以多次调用c.ShouldBind() 而不会造成任何性能损失 。

数据校验

gin内置的校验工具其实是github.com/go-playground/validator/v10,使用方法也几乎没有什么差别,前往Validator教程

简单示例

  1.   type LoginUser struct {
  2.   Username string `binding:"required" json:"username" form:"username" uri:"username"`
  3.   Password string `binding:"required" json:"password" form:"password" uri:"password"`
  4.   }
  5.    
  6.   func main() {
  7.   e := gin.Default()
  8.   e.POST("/register", Register)
  9.   log.Fatalln(e.Run(":8080"))
  10.   }
  11.    
  12.   func Register(ctx *gin.Context) {
  13.   newUser := &LoginUser{}
  14.   if err := ctx.ShouldBind(newUser); err == nil {
  15.   ctx.String(http.StatusOK, "user%+v", *newUser)
  16.   } else {
  17.   ctx.String(http.StatusBadRequest, "invalid user,%v", err)
  18.   }
  19.   }

测试

  1.   curl --location --request POST 'http://localhost:8080/register' \
  2.   --header 'Content-Type: application/json' \
  3.   --data-raw '{
  4.   "username":"jack1"
  5.    
  6.   }'

输出

invalid user,Key: 'LoginUser.Password' Error:Field validation for 'Password' failed on the 'required' tag
 

::: tip

需要注意的一点是,gin中validator的校验tag是binding,而单独使用validator的的校验tag是validator

:::

数据响应

数据响应是接口处理中最后一步要做的事情,后端将所有数据处理完成后,通过HTTP协议返回给调用者,gin对于数据响应提供了丰富的内置支持,用法简洁明了,上手十分容易。

简单示例

  1.   func Hello(c *gin.Context) {
  2.   // 返回纯字符串格式的数据,http.StatusOK代表着200状态码,数据为"Hello world !"
  3.   c.String(http.StatusOK, "Hello world !")
  4.   }

HTML渲染

文件加载的时候,默认根路径是项目路径,也就是go.mod文件所在的路径,下面例子中的index.html即位于根路径下的index.html,不过一般情况下这些模板文件都不会放在根路径,而是会存放在静态资源文件夹中
  1.   func main() {
  2.   e := gin.Default()
  3.   // 加载HTML文件,也可以使用Engine.LoadHTMLGlob()
  4.   e.LoadHTMLFiles("index.html")
  5.   e.GET("/", Index)
  6.   log.Fatalln(e.Run(":8080"))
  7.   }
  8.    
  9.   func Index(c *gin.Context) {
  10.   c.HTML(http.StatusOK, "index.html", gin.H{})
  11.   }

测试

curl --location --request GET 'http://localhost:8080/'
 

返回

  1.   <!DOCTYPE html>
  2.   <html lang="en">
  3.    
  4.   <head>
  5.   <meta charset="UTF-8">
  6.   <title>GinLearn</title>
  7.   </head>
  8.    
  9.   <body>
  10.   <h1>Hello World!</h1>
  11.   <h1>This is a HTML Template Render Example</h1>
  12.   </body>
  13.    
  14.   </html>

快速响应

前面经常用到context.String()方法来进行数据响应,这是最原始的响应方法,直接返回一个字符串,gin中其实还内置了许多了快速响应的方法例如:

  1.   // 使用Render写入响应头,并进行数据渲染
  2.   func (c *Context) Render(code int, r render.Render)
  3.    
  4.   // 渲染一个HTML模板,name是html路径,obj是内容
  5.   func (c *Context) HTML(code int, name string, obj any)
  6.    
  7.   // 以美化了的缩进JSON字符串进行数据渲染,通常不建议使用这个方法,因为会造成更多的传输消耗。
  8.   func (c *Context) IndentedJSON(code int, obj any)
  9.    
  10.   // 安全的JSON,可以防止JSON劫持,详情了解:https://www.cnblogs.com/xusion/articles/3107788.html
  11.   func (c *Context) SecureJSON(code int, obj any)
  12.    
  13.   // JSONP方式进行渲染
  14.   func (c *Context) JSONP(code int, obj any)
  15.    
  16.   // JSON方式进行渲染
  17.   func (c *Context) JSON(code int, obj any)
  18.    
  19.   // JSON方式进行渲染,会将unicode码转换为ASCII码
  20.   func (c *Context) AsciiJSON(code int, obj any)
  21.    
  22.   // JSON方式进行渲染,不会对HTML特殊字符串进行转义
  23.   func (c *Context) PureJSON(code int, obj any)
  24.    
  25.   // XML方式进行渲染
  26.   func (c *Context) XML(code int, obj any)
  27.    
  28.   // YML方式进行渲染
  29.   func (c *Context) YAML(code int, obj any)
  30.    
  31.   // TOML方式进行渲染
  32.   func (c *Context) TOML(code int, obj interface{})
  33.    
  34.   // ProtoBuf方式进行渲染
  35.   func (c *Context) ProtoBuf(code int, obj any)
  36.    
  37.   // String方式进行渲染
  38.   func (c *Context) String(code int, format string, values ...any)
  39.    
  40.   // 重定向到特定的位置
  41.   func (c *Context) Redirect(code int, location string)
  42.    
  43.   // 将data写入响应流中
  44.   func (c *Context) Data(code int, contentType string, data []byte)
  45.    
  46.   // 通过reader读取流并写入响应流中
  47.   func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)
  48.    
  49.   // 高效的将文件写入响应流
  50.   func (c *Context) File(filepath string)
  51.    
  52.   // 以一种高效的方式将fs中的文件流写入响应流
  53.   func (c *Context) FileFromFS(filepath string, fs http.FileSystem)
  54.    
  55.   // 以一种高效的方式将fs中的文件流写入响应流,并且在客户端会以指定的文件名进行下载
  56.   func (c *Context) FileAttachment(filepath, filename string)
  57.    
  58.   // 将服务端推送流写入响应流中
  59.   func (c *Context) SSEvent(name string, message any)
  60.    
  61.   // 发送一个流响应并返回一个布尔值,以此来判断客户端是否在流中间断开
  62.   func (c *Context) Stream(step func(w io.Writer) bool) bool

对于大多数应用而言,用的最多的还是context.JSON,其他的相对而言要少一些,这里就不举例子演示了,因为都比较简单易懂,差不多都是直接调用的事情。

异步处理

在gin中,异步处理需要结合goroutine使用,使用起来十分简单。

  1.   // copy返回一个当前Context的副本以便在当前Context作用范围外安全的使用,可以用于传递给一个goroutine
  2.   func (c *Context) Copy() *Context
  1.   func main() {
  2.   e := gin.Default()
  3.   e.GET("/hello", Hello)
  4.   log.Fatalln(e.Run(":8080"))
  5.   }
  6.    
  7.   func Hello(c *gin.Context) {
  8.   ctx := c.Copy()
  9.   go func() {
  10.   // 子协程应该使用Context的副本,不应该使用原始Context
  11.   log.Println("异步处理函数: ", ctx.HandlerNames())
  12.   }()
  13.   log.Println("接口处理函数: ", c.HandlerNames())
  14.   c.String(http.StatusOK, "hello")
  15.   }

测试

curl --location --request GET 'http://localhost:8080/hello'
 

输出

  1.   2022/12/21 13:33:47 异步处理函数: []
  2.   2022/12/21 13:33:47 接口处理函数: [github.com/gin-gonic/gin.LoggerWithConfig.func1 github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 main.Hello]
  3.   [GIN] 2022/12/21 - 13:33:47 | 200 | 11.1927ms | ::1 | GET "/hello"

可以看到两者输出不同,副本在复制时,为了安全考虑,删掉了许多元素的值。

文件传输

文件传输是Web应用的一个不可或缺的功能,gin对于此的支持也是封装的十分简单,但其实本质上和用原生的net/http的流程都差不多。流程都是从请求体中读取文件流,然后再保存到本地。

单文件上传

  1.   func main() {
  2.   e := gin.Default()
  3.   e.POST("/upload", uploadFile)
  4.   log.Fatalln(e.Run(":8080"))
  5.   }
  6.    
  7.   func uploadFile(ctx *gin.Context) {
  8.   // 获取文件
  9.   file, err := ctx.FormFile("file")
  10.   if err != nil {
  11.   ctx.String(http.StatusBadRequest, "%+v", err)
  12.   return
  13.   }
  14.   // 保存在本地
  15.   err = ctx.SaveUploadedFile(file, "./"+file.Filename)
  16.   if err != nil {
  17.   ctx.String(http.StatusBadRequest, "%+v", err)
  18.   return
  19.   }
  20.   // 返回结果
  21.   ctx.String(http.StatusOK, "upload %s size:%d byte successfully!", file.Filename, file.Size)
  22.   }

测试

  1.   curl --location --request POST 'http://localhost:8080/upload' \
  2.   --form 'file=@"/C:/Users/user/Pictures/Camera Roll/a.jpg"'

结果

upload a.jpg size:1424 byte successfully!
 
一般情况下,上传文件的Method都会指定用POST,一些公司可能会倾向于使用PUT,前者是简单HTTP请求,后者是复杂HTTP请求,具体区别不作赘述,如果使用后者的话,尤其是前后端分离的项目时,需要进行相应的跨域处理,而Gin默认的配置是不支持跨域的 跨域配置

多文件上传

  1.   func main() {
  2.   e := gin.Default()
  3.   e.POST("/upload", uploadFile)
  4.   e.POST("/uploadFiles", uploadFiles)
  5.   log.Fatalln(e.Run(":8080"))
  6.   }
  7.    
  8.   func uploadFiles(ctx *gin.Context) {
  9.   // 获取gin解析好的multipart表单
  10.   form, _ := ctx.MultipartForm()
  11.   // 根据键值取得对应的文件列表
  12.   files := form.File["files"]
  13.   // 遍历文件列表,保存到本地
  14.   for _, file := range files {
  15.   err := ctx.SaveUploadedFile(file, "./"+file.Filename)
  16.   if err != nil {
  17.   ctx.String(http.StatusBadRequest, "upload failed")
  18.   return
  19.   }
  20.   }
  21.   // 返回结果
  22.   ctx.String(http.StatusOK, "upload %d files successfully!", len(files))
  23.   }

测试

  1.   curl --location --request POST 'http://localhost:8080/uploadFiles' \
  2.   --form 'files=@"/C:/Users/Stranger/Pictures/Camera Roll/a.jpg"' \
  3.   --form 'files=@"/C:/Users/Stranger/Pictures/Camera Roll/123.jpg"' \
  4.   --form 'files=@"/C:/Users/Stranger/Pictures/Camera Roll/girl.jpg"'

输出

upload 3 files successfully!
 

文件下载

关于文件下载的部分Gin对于原有标准库的API再一次封装,使得文件下载异常简单。

  1.   func main() {
  2.   e := gin.Default()
  3.   e.POST("/upload", uploadFile)
  4.   e.POST("/uploadFiles", uploadFiles)
  5.   e.GET("/download/:filename", download)
  6.   log.Fatalln(e.Run(":8080"))
  7.   }
  8.    
  9.   func download(ctx *gin.Context) {
  10.   // 获取文件名
  11.   filename := ctx.Param("filename")
  12.   // 返回对应文件
  13.   ctx.FileAttachment(filename, filename)
  14.   }

测试

curl --location --request GET 'http://localhost:8080/download/a.jpg'
 

结果

  1.   Content-Disposition: attachment; filename="a.jpg"
  2.   Date: Wed, 21 Dec 2022 08:04:17 GMT
  3.   Last-Modified: Wed, 21 Dec 2022 07:50:44 GMT

是不是觉得简单过头了,不妨不用框架的方法,自行编写一遍过程

  1.   func download(ctx *gin.Context) {
  2.   // 获取参数
  3.   filename := ctx.Param("filename")
  4.    
  5.   // 请求响应对象和请求对象
  6.   response, request := ctx.Writer, ctx.Request
  7.   // 写入响应头
  8.   // response.Header().Set("Content-Type", "application/octet-stream") 以二进制流传输文件
  9.   response.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) // 对文件名进行安全转义
  10.   response.Header().Set("Content-Transfer-Encoding", "binary") // 传输编码
  11.   http.ServeFile(response, request, filename)
  12.   }

其实net/http也已经封装的足够好了

一般情况下,上传文件的Method都会指定用POST,一些公司可能会倾向于使用PUT,前者是简单HTTP请求,后者是复杂HTTP请求,具体区别不作赘述,如果使用后者的话,尤其是前后端分离的项目时,需要进行相应的跨域处理,而Gin默认的配置是不支持跨域的 跨域配置

路由管理

路由管理是一个系统中非常重要的部分,需要确保每一个请求都能被正确的映射到对应的函数上。

路由组

创建一个路由组是将接口分类,不同类别的接口对应不同的功能,也更易于管理。

  1.   func Hello(c *gin.Context) {
  2.    
  3.   }
  4.    
  5.   func Login(c *gin.Context) {
  6.    
  7.   }
  8.    
  9.   func Update(c *gin.Context) {
  10.    
  11.   }
  12.    
  13.   func Delete(c *gin.Context) {
  14.    
  15.   }

假设我们有以上四个接口,暂时不管其内部实现,Hello,Login是一组,Update,Delete是一组。

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup
 

在创建分组的时候,我们也可以给分组的根路由注册处理器,不过大多数时候并不会这么做。

  1.   func main() {
  2.   e := gin.Default()
  3.   v1 := e.Group("v1")
  4.   {
  5.   v1.GET("/hello", Hello)
  6.   v1.GET("/login", Login)
  7.   }
  8.   v2 := e.Group("v2")
  9.   {
  10.   v2.POST("/update", Update)
  11.   v2.DELETE("/delete", Delete)
  12.   }
  13.   }

我们将其分成了v1,v2两个分组,其中的花括号{}仅仅只是为了规范,表名花括号内注册的处理器是属于同一个路由分组,在功能上没有任何作用。同样的,gin也支持嵌套分组,方法与上例一致,这里就不再演示。

404路由

gin 中的Engine结构体提供了一个方法NoRoute,来设置当访问的URL不存在时如何处理,开发者可以将逻辑写入此方法中,以便路由未找到时自动调用,默认会返回404状态码

func (engine *Engine) NoRoute(handlers ...HandlerFunc)
 

我们拿上个例子举例

  1.   func main() {
  2.   e := gin.Default()
  3.   v1 := e.Group("v1")
  4.   {
  5.   v1.GET("/hello", Hello)
  6.   v1.GET("/login", Login)
  7.   }
  8.   v2 := e.Group("v2")
  9.   {
  10.   v2.POST("/update", Update)
  11.   v2.DELETE("/delete", Delete)
  12.   }
  13.   // 注册处理器
  14.   e.NoRoute(func(context *gin.Context) { // 这里只是演示,不要在生产环境中直接返回HTML代码
  15.   context.String(http.StatusNotFound, "<h1>404 Page Not Found</h1>")
  16.   })
  17.   log.Fatalln(e.Run(":8080"))
  18.   }

随便发一个请求

curl --location --request GET 'http://localhost:8080/'
 
<h1>404 Page Not Found</h1>
 

405路由

Http状态码中,405代表着当前请求的方法类型是不允许的,gin中提供了如下方法

func (engine *Engine) NoMethod(handlers ...HandlerFunc)
 

来注册一个处理器,以便在发生时自动调用,前提是设置Engine.HandleMethodNotAllowed = true。

  1.   func main() {
  2.   e := gin.Default()
  3.   // 需要将其设置为true
  4.   e.HandleMethodNotAllowed = true
  5.   v1 := e.Group("/v1")
  6.   {
  7.   v1.GET("/hello", Hello)
  8.   v1.GET("/login", Login)
  9.   }
  10.   v2 := e.Group("/v2")
  11.   {
  12.   v2.POST("/update", Update)
  13.   v2.DELETE("/delete", Delete)
  14.   }
  15.   e.NoRoute(func(context *gin.Context) {
  16.   context.String(http.StatusNotFound, "<h1>404 Page Not Found</h1>")
  17.   })
  18.   // 注册处理器
  19.   e.NoMethod(func(context *gin.Context) {
  20.   context.String(http.StatusMethodNotAllowed, "method not allowed")
  21.   })
  22.   log.Fatalln(e.Run(":8080"))
  23.   }

配置好后,gin默认的header是不支持OPTION请求的,测试一下

curl --location --request OPTIONS 'http://localhost:8080/v2/delete'
 
method not allowed
 

至此配置成功

重定向

gin中的重定向十分简单,调用gin.Context.Redirect()方法即可。

  1.   func main() {
  2.   e := gin.Default()
  3.   e.GET("/", Index)
  4.   e.GET("/hello", Hello)
  5.   log.Fatalln(e.Run(":8080"))
  6.   }
  7.    
  8.   func Index(c *gin.Context) {
  9.   c.Redirect(http.StatusMovedPermanently, "/hello")
  10.   }
  11.    
  12.   func Hello(c *gin.Context) {
  13.   c.String(http.StatusOK, "hello")
  14.   }

测试

curl --location --request GET 'http://localhost:8080/'
 

输出

hello
 

中间件

gin十分轻便灵活,拓展性非常高,对于中间件的支持也非常友好。在Gin中,所有的接口请求都要经过中间件,通过中间件,开发者可以自定义实现很多功能和逻辑,gin虽然本身自带的功能很少,但是由第三方社区开发的gin拓展中间件十分丰富。

中间件本质上其实还是一个接口处理器

  1.   // HandlerFunc defines the handler used by gin middleware as return value.
  2.   type HandlerFunc func(*Context)

从某种意义上来说,每一个请求对应的处理器也是中间件,只不过是作用范围非常小的局部中间件。

  1.   func Default() *Engine {
  2.   debugPrintWARNINGDefault()
  3.   engine := New()
  4.   engine.Use(Logger(), Recovery())
  5.   return engine
  6.   }

查看gin的源代码,Default函数中,返回的默认Engine就使用两个默认中间件Logger(),Recovery(),如果不想使用默认的中间件也可以使用gin.New()来代替。

全局中间件

全局中间件即作用范围为全局,整个系统所有的请求都会经过此中间件。

  1.   func GlobalMiddleware() gin.HandlerFunc {
  2.   return func(ctx *gin.Context) {
  3.   fmt.Println("全局中间件被执行...")
  4.   }
  5.   }

先创建一个闭包函数来创建中间件,再通过Engine.Use()来注册全局中间件。

  1.   func main() {
  2.   e := gin.Default()
  3.   // 注册全局中间件
  4.   e.Use(GlobalMiddleware())
  5.   v1 := e.Group("/v1")
  6.   {
  7.   v1.GET("/hello", Hello)
  8.   v1.GET("/login", Login)
  9.   }
  10.   v2 := e.Group("/v2")
  11.   {
  12.   v2.POST("/update", Update)
  13.   v2.DELETE("/delete", Delete)
  14.   }
  15.   log.Fatalln(e.Run(":8080"))
  16.   }

测试

curl --location --request GET 'http://localhost:8080/v1/hello'
 

输出

  1.   [GIN-debug] Listening and serving HTTP on :8080
  2.   全局中间件被执行...
  3.   [GIN] 2022/12/21 - 11:57:52 | 200 | 538.9µs | ::1 | GET "/v1/hello"

局部中间件

局部中间件即作用范围为局部,系统中局部的请求会经过此中间件。局部中间件可以注册到单个路由上,不过更多时候是注册到路由组上。

  1.   func main() {
  2.   e := gin.Default()
  3.   // 注册全局中间件
  4.   e.Use(GlobalMiddleware())
  5.   // 注册路由组局部中间件
  6.   v1 := e.Group("/v1", LocalMiddleware())
  7.   {
  8.   v1.GET("/hello", Hello)
  9.   v1.GET("/login", Login)
  10.   }
  11.   v2 := e.Group("/v2")
  12.   {
  13.   // 注册单个路由局部中间件
  14.   v2.POST("/update", LocalMiddleware(), Update)
  15.   v2.DELETE("/delete", Delete)
  16.   }
  17.   log.Fatalln(e.Run(":8080"))
  18.   }

测试

curl --location --request POST 'http://localhost:8080/v2/update'
 

输出

  1.   全局中间件被执行...
  2.   局部中间件被执行
  3.   [GIN] 2022/12/21 - 12:05:03 | 200 | 999.9µs | ::1 | POST "/v2/update"

中间件原理

Gin中间的使用和自定义非常容易,其内部的原理也比较简单,为了后续的学习,需要简单的了解下内部原理。Gin中的中间件其实用到了责任链模式,Context中维护着一个HandlersChain,本质上是一个[]HandlerFunc,和一个index,其数据类型为int8。在Engine.handlerHTTPRequest(c *Context)方法中,有一段代码表明了调用过程:gin在路由树中找到了对应的路由后,便调用了Next()方法。

  1.   if value.handlers != nil {
  2.   // 将调用链赋值给Context
  3.   c.handlers = value.handlers
  4.   c.fullPath = value.fullPath
  5.   // 调用中间件
  6.   c.Next()
  7.   c.writermem.WriteHeaderNow()
  8.   return
  9.   }

Next()的调用才是关键,Next()会遍历路由的handlers中的HandlerFunc 并执行,此时可以看到index的作用就是记录中间件的调用位置。其中,给对应路由注册的接口函数也在handlers内,这也就是为什么前面会说接口也是一个中间件。

  1.   func (c *Context) Next() {
  2.   // 一进来就+1是为了避免陷入递归死循环,默认值是-1
  3.   c.index++
  4.   for c.index < int8(len(c.handlers)) {
  5.   // 执行HandlerFunc
  6.   c.handlers[c.index](c)
  7.   // 执行完毕,index+1
  8.   c.index++
  9.   }
  10.   }

修改一下Hello()的逻辑,来验证是否果真如此

  1.   func Hello(c *gin.Context) {
  2.   fmt.Println(c.HandlerNames())
  3.   }

输出结果为

[github.com/gin-gonic/gin.LoggerWithConfig.func1 github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1 main.GlobalMiddleware.func1 main.LocalMiddleware.func1 main.Hello]
 

可以看到中间件调用链的顺序为:Logger -> Recovery -> GlobalMiddleware -> LocalMiddleWare -> Hello,调用链的最后一个元素才是真正要执行的接口函数,前面的都是中间件。

::: tip

在注册局部路由时,有如下一个断言

  1.   finalSize := len(group.Handlers) + len(handlers) //中间件总数
  2.   assert1(finalSize < int(abortIndex), "too many handlers")

其中abortIndex int8 = math.MaxInt8 >> 1值为63,即使用系统时路由注册数量不要超过63个。

:::

计时器中间件

在知晓了上述的中间件原理后,就可以编写一个简单的请求时间统计中间件。

  1.   func TimeMiddleware() gin.HandlerFunc {
  2.   return func(context *gin.Context) {
  3.   // 记录开始时间
  4.   start := time.Now()
  5.   // 执行后续调用链
  6.   context.Next()
  7.   // 计算时间间隔
  8.   duration := time.Since(start)
  9.   // 输出纳秒,以便观测结果
  10.   fmt.Println("请求用时: ", duration.Nanoseconds())
  11.   }
  12.   }
  13.    
  14.   func main() {
  15.   e := gin.Default()
  16.   // 注册全局中间件,计时中间件
  17.   e.Use(GlobalMiddleware(), TimeMiddleware())
  18.   // 注册路由组局部中间件
  19.   v1 := e.Group("/v1", LocalMiddleware())
  20.   {
  21.   v1.GET("/hello", Hello)
  22.   v1.GET("/login", Login)
  23.   }
  24.   v2 := e.Group("/v2")
  25.   {
  26.   // 注册单个路由局部中间件
  27.   v2.POST("/update", LocalMiddleware(), Update)
  28.   v2.DELETE("/delete", Delete)
  29.   }
  30.   log.Fatalln(e.Run(":8080"))
  31.   }

测试

curl --location --request GET 'http://localhost:8080/v1/hello'
 

输出

请求用时:  517600
 

一个简单的计时器中间件就已经编写完毕了,后续可以凭借自己的摸索编写一些功能更实用的中间件。

服务配置

光是使用默认的配置是远远不够的,大多数情况下都需求修改很多的服务配置才能达到需求。

Http配置

可以通过net/http创建Server来配置,Gin本身也支持像原生API一样使用Gin。

  1.   func main() {
  2.   router := gin.Default()
  3.   server := &http.Server{
  4.   Addr: ":8080",
  5.   Handler: router,
  6.   ReadTimeout: 10 * time.Second,
  7.   WriteTimeout: 10 * time.Second,
  8.   MaxHeaderBytes: 1 << 20,
  9.   }
  10.   log.Fatal(server.ListenAndServe())
  11.   }

静态资源配置

静态资源在以往基本上是服务端不可或缺的一部分,尽管在现在使用占比正在逐渐减少,但仍旧有大量的系统还是使用单体架构的情况。

Gin提供了三个方法来加载静态资源

  1.   // 加载某一静态文件夹
  2.   func (group *RouterGroup) Static(relativePath, root string) IRoutes
  3.    
  4.   // 加载某一个fs
  5.   func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes
  6.    
  7.   // 加载某一个静态文件
  8.   func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes
relativePath是映射到网页URL上的相对路径,root是文件在项目中的实际路径

假设项目的目录如下

  1.   root
  2.   |
  3.   |-- static
  4.   | |
  5.   | |-- a.jpg
  6.   | |
  7.   | |-- favicon.ico
  8.   |
  9.   |-- view
  10.   |
  11.   |-- html
  1.   func main() {
  2.   router := gin.Default()
  3.   // 加载静态文件目录
  4.   router.Static("/static", "./static")
  5.   // 加载静态文件目录
  6.   router.StaticFS("/view", http.Dir("view"))
  7.   // 加载静态文件
  8.   router.StaticFile("/favicon", "./static/favicon.ico")
  9.    
  10.   router.Run(":8080")
  11.   }

跨域配置

Gin本身是没有对于跨域配置做出任何处理,需要自行编写中间件来进行实现相应的需求,其实难度也不大,稍微熟悉HTTP协议的人一般都能写出来,逻辑基本上都是那一套。

  1.   func CorsMiddle() gin.HandlerFunc {
  2.   return func(c *gin.Context) {
  3.   method := c.Request.Method
  4.   origin := c.Request.Header.Get("Origin")
  5.   if origin != "" {
  6.   // 生产环境中的服务端通常都不会填 *,应当填写指定域名
  7.   c.Header("Access-Control-Allow-Origin", origin)
  8.   // 允许使用的HTTP METHOD
  9.   c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
  10.   // 允许使用的请求头
  11.   c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
  12.   // 允许客户端访问的响应头
  13.   c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
  14.   // 是否需要携带认证信息 Credentials 可以是 cookies、authorization headers 或 TLS client certificates
  15.   // 设置为true时,Access-Control-Allow-Origin不能为 *
  16.   c.Header("Access-Control-Allow-Credentials", "true")
  17.   }
  18.   // 放行OPTION请求,但不执行后续方法
  19.   if method == "OPTIONS" {
  20.   c.AbortWithStatus(http.StatusNoContent)
  21.   }
  22.   // 放行
  23.   c.Next()
  24.   }
  25.   }

将中间件注册为全局中间件即可

会话控制

在目前的时代中,流行的三种Web会话控制总共有三种,cookie,session,JWT。

Cookie

ookie中的信息是以键值对的形式储存在浏览器中,而且在浏览器中可以直接看到数据

优点:

  • 结构简单

  • 数据持久

缺点:

  • 大小受限

  • 明文存储

  • 容易受到CSRF攻击

  1.   import (
  2.   "fmt"
  3.    
  4.   "github.com/gin-gonic/gin"
  5.   )
  6.    
  7.   func main() {
  8.    
  9.   router := gin.Default()
  10.    
  11.   router.GET("/cookie", func(c *gin.Context) {
  12.    
  13.   // 获取对应的cookie
  14.   cookie, err := c.Cookie("gin_cookie")
  15.    
  16.   if err != nil {
  17.   cookie = "NotSet"
  18.   // 设置cookie 参数:key,val,存在时间,目录,域名,是否允许他人通过js访问cookie,仅http
  19.   c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
  20.   }
  21.    
  22.   fmt.Printf("Cookie value: %s \n", cookie)
  23.   })
  24.    
  25.   router.Run()
  26.   }

单纯的cookie在五六年前用的比较多,不过作者一般很少使用单纯的cookie来做会话控制,这样做确实不太安全。

Session

session存储在服务器中,然后发送一个cookie存储在浏览器中,cookie中存储的是session_id,之后每次请求服务器通过session_id可以获取对应的session信息

优点:

  • 存储在服务端,增加安全性,便于管理

缺点:

  • 存储在服务端,增大服务器开销,降低性能

  • 基于cookie识别,不安全

  • 认证信息在分布式情况下不同步

Session与Cookie是不分家的,每次要用到Session,默认就是要用到Cookie了。Gin默认是不支持Session的,因为Cookie是Http协议里面的内容,但Session不是,不过有第三方中间件支持,安装依赖即可,仓库地址:gin-contrib/sessions: Gin middleware for session management (github.com)

go get github.com/gin-contrib/sessions
 

支持cookie,Redis,MongoDB,GORM,PostgreSQL

  1.   func main() {
  2.   r := gin.Default()
  3.   // 创建基于Cookie的存储引擎
  4.   store := cookie.NewStore([]byte("secret"))
  5.   // 设置Session中间件,mysession即session名称,也是cookie的名称
  6.   r.Use(sessions.Sessions("mysession", store))
  7.   r.GET("/incr", func(c *gin.Context) {
  8.   // 初始化session
  9.   session := sessions.Default(c)
  10.   var count int
  11.   // 获取值
  12.   v := session.Get("count")
  13.   if v == nil {
  14.   count = 0
  15.   } else {
  16.   count = v.(int)
  17.   count++
  18.   }
  19.   // 设置
  20.   session.Set("count", count)
  21.   // 保存
  22.   session.Save()
  23.   c.JSON(200, gin.H{"count": count})
  24.   })
  25.   r.Run(":8000")
  26.   }

一般不推荐通过Cookie存储Sesison,推荐使用Redis,其他例子还请自行去官方仓库了解。

JWT

优点:

  • 基于JSON,多语言通用

  • 可以存储非敏感信息

  • 占用很小,便于传输

  • 服务端无需存储,利于分布式拓展

缺点:

  • Token刷新问题

  • 一旦签发则无法主动控制

自从前端革命以来,前端程序员不再只是一个“写页面的”,前后端分离的趋势愈演愈烈,JWT是最适合前后端分离和分布式系统来做会话控制的,具有很大的天然优势。考虑到JWT已经完全脱离Gin的内容,且没有任何中间件支持,因为JWT本身就是不局限于任何框架任何语言,在这里就不作细致的讲解,可以前往另一篇教程:[JWT使用教程](JWT | Go中文学习文档 (halfiisland.com))

日志管理

Gin默认使用的日志中间件采用的是os.Stdout,只有最基本的功能,毕竟Gin只专注于Web服务,大多数情况下应该使用更加成熟的日志框架,不过这并不在本章的讨论范围内,而且Gin的拓展性很高,可以很轻易的整合其他框架,这里只讨论其自带的日志服务。

控制台颜色

gin.DisableConsoleColor() // 关闭控制台日志颜色
 

除了在开发的时候,大多数时候都不建议开启此项

日志写入文件

  1.   func main() {
  2.   e := gin.Default()
  3.   // 关掉控制台颜色
  4.   gin.DisableConsoleColor()
  5.   // 创建两个日志文件
  6.   log1, _ := os.Create("info1.log")
  7.   log2, _ := os.Create("info2.log")
  8.   // 同时记录进两个日志文件
  9.   gin.DefaultWriter = io.MultiWriter(log1, log2)
  10.   e.GET("/hello", Hello)
  11.   log.Fatalln(e.Run(":8080"))
  12.   }

gin自带的日志支持写入多个文件,但内容是相同的,使用起来不太方便,并且不会将请求日志写入文件中。

  1.   func main() {
  2.   router := gin.New()
  3.   // LoggerWithFormatter 中间件会写入日志到 gin.DefaultWriter
  4.   // 默认 gin.DefaultWriter = os.Stdout
  5.   router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
  6.   //TODO 写入对应文件的逻辑
  7.   ......
  8.   // 输出自定义格式
  9.   return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
  10.   param.ClientIP,
  11.   param.TimeStamp.Format(time.RFC1123),
  12.   param.Method,
  13.   param.Path,
  14.   param.Request.Proto,
  15.   param.StatusCode,
  16.   param.Latency,
  17.   param.Request.UserAgent(),
  18.   param.ErrorMessage,
  19.   )
  20.   }))
  21.   router.Use(gin.Recovery())
  22.   router.GET("/ping", func(c *gin.Context) {
  23.   c.String(200, "pong")
  24.   })
  25.   router.Run(":8080")
  26.   }

通过自定义中间件,可以实现日志写入文件中

路由调试日志格式

这里修改的只是启动时输出路由信息的的日志

  1.   func main() {
  2.   e := gin.Default()
  3.   gin.SetMode(gin.DebugMode)
  4.   gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
  5.   if gin.IsDebugging() {
  6.   log.Printf("路由 %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
  7.   }
  8.   }
  9.   e.GET("/hello", Hello)
  10.   log.Fatalln(e.Run(":8080"))
  11.   }

输出

2022/12/21 17:19:13 路由 GET /hello main.Hello 3
 

结语:Gin算是Go语言Web框架中最易学习的一种,因为Gin真正做到了职责最小化,只是单纯的负责Web服务,其他的认证逻辑,数据缓存等等功能都交给开发者自行完成,相比于那些大而全的框架,轻量简洁的Gin对于初学者而言更适合也更应该去学习,因为Gin并没有强制使用某一种规范,项目该如何构建,采用什么结构都需要自行斟酌,对于初学者而言更能锻炼能力。

 

   

标签:http,func,--,中间件,Context,Go,gin
From: https://www.cnblogs.com/add1188/p/17807630.html

相关文章

  • Go语言百万高薪开发架构师一线大牛亲授+企业项目实战+云原生架构核心
    Go语言百万高薪开发架构师一线大牛亲授+企业项目实战+云原生架构核心 第1章云原生架构1.1云计算的历史1.1.3容器的横空出世和容器编排大战上一次我们提到了PaaS,PaaS的开源产品Docker对云计算领域产生了深远的影响,从虚拟机到容器,整个云计算市场发生了一次重大变革。容......
  • Go语言Golang DevOps运维开发实战集训营,高级运维必修
    Go语言GolangDevOps运维开发实战集训营,高级运维必修Go语言简介Go语言,也称为Golang,是一门由Google开发的开源编程语言。它的设计目标是提供一种高效、简洁、安全且支持并发的编程语言,适用于构建可靠且高性能的软件系统。Go语言在短短的时间内迅速走红,成为开发者们喜爱的选择,因......
  • nginx 302问题
    nginx抓包显示302访问的ip端口有发生变化踩坑需配置location/abc{proxy_passhttp://192.168.146.64:7118/;proxy_intercept_errorson;#捕捉错误error_page301302307=@handle_redirects;}......
  • gin常用API
    gin常用API获取路由引擎r=gin.Default()//返回路由引擎engine这里命名为rGET请求//r.GET(路由地址,回调函数)r.GET("/get_request",func(c*gin.Context){//c.Query("请求参数")获取GET请求参数name:=c.Query("name")//c.JSON(请求状态码,......
  • 无涯教程-MongoDB - 简介
    MongoDB是一种面向文档的数据库管理系统,用C++等语言撰写而成,以解决应用程序开发社区中的大量现实问题。MongoDB由MongoDBInc.(当时是10gen团队)于2007年10月开发,2009年2月首度推出,现以服务器端公共许可(SSPL)分发。本教程适用于愿意通过简单的步骤学习MongoDB数据库的软件专业人员,它......
  • 宝塔 nginx 运行 vue项目
    宝塔安装nginxnginx根目录:/www/server/nginx/html修改nginx配置下滑到70多行,添加server对象内容,内容如下server{listen82;server_nametest2;location/{roothtml/test2;indexindex.htmlindex.htm;......
  • 在虚拟机(Linux)中Docker中部署Nginx成功,但是在宿主机无法访问Nginx站点?
    1.问题本文是基于黑马程序员Docker基础--常见命令一课中部署Nginx时遇到的问题作出解答。在虚拟机(Linux)中Docker中部署Nginx成功,但是在宿主机无法访问Nginx站点如图,Nginx服务已经启动成功但是我们在宿主机的浏览器试图访问的时候却总是报错:2.解决思路2.1查看端口号是否映......
  • MegEngine 9-10 双月报:新版本发布,AI 生态升级,不容错过!
    ●v1.13.2新版本发布<https://github.com/MegEngine/MegEngine/releases/tag/v1.13.2>●MegCC新版本发布<https://github.com/MegEngine/MegCC/releases/tag/v0.1.6>●DataFunSummit2023:AI基础软件架构峰会《MegEngine训练性能优化与AI编译实践》主题演讲<https://w......
  • Django实战项目-学习任务系统-发送邮件通知
    接着上期代码内容,继续完善优化系统功能。 本次增加发送邮件通知功能,学习任务系统发布的任务,需要及时通知到学生用户知晓。由于目前智能手机普及,人人都离不开手机,所以手机端接收通知信息更加及时有效。 其中微信使用频率最多,本来想使用微信通知功能,但是经过网上搜集资料测试......
  • Go可以做到同等并发能力么?具体代码如何写?
     原创 磊丰 Go语言圈 2023-09-0508:30 发表于广东收录于合集#学Go语言哪些事儿231个MySQL大牛带你全面剖析与系统梳理数据库(mysql等)知识分享,总结数据库技巧和方法,提升你的技术技能。45篇原创内容公众号JetBrains全家桶正式版激活码&账号开通授......