下载安装Gin:
go get -u github.com/gin-gonic/gin
使用示例:
package main
import "github.com/gin-gonic/gin"
func main() {
//创建一个默认的路由引擎
r := gin.Default()
//GET:请求方式,/hello:请求路径
//当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
r.GET("/hello", func(c *gin.Context) {
// c.JSON:返回JSON格式的数据
c.JSON(200, gin.H{
"message": "hello gin",
})
})
//启动服务,默认端口是8080
r.Run(":9090")
}
启动服务,浏览器访问:
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
GET
用来获取资源POST
用来新建资源PUT
用来更新资源DELETE
用来删除资源。Gin框架支持开发RESTful API的开发。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
// 创建默认引擎
r := gin.Default()
//请求编写
//查询
r.GET("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "GET",
})
})
//创建
r.POST("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "POST",
})
})
//修改
r.PUT("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "PUT",
})
})
//删除
r.DELETE("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "DELETE",
})
})
//启动HTTP服务
r.Run()
}
Go语言内置了文本模板引擎text/template
和用于HTML文档的html/template
。它们的作用机制可以简单归纳如下:
.tmpl
和.tpl
为后缀(也可以使用其他的后缀),必须使用UTF8
编码。{{
和}}
包裹和标识需要传入的数据。.
)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。{{
和}}
包裹的内容外,其他内容均不做修改原样输出。Go语言模板引擎的使用可以分为三部分:定义模板文件、解析模板文件和模板渲染.
上面定义好了模板文件之后,可以使用下面的常用方法去解析模板文件,得到模板对象:
func (t *Template) Parse(src string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)
也可以使用func New(name string) *Template
函数创建一个名为name
的模板,然后对其调用上面的方法去解析模板字符串或模板文件。
渲染模板简单来说就是使用数据去填充模板,当然实际上可能会复杂很多。
func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("json", func(c *gin.Context) {
// 方法一:使用map
//data := map[string]interface{}{
// "name": "Weiyuxin",
// "message": "hello gin",
// "age": 18,
//}
// gin.H预定义成map[string]interface{}
data := gin.H{
"name": "Weiyuxin",
"message": "hello gin",
"age": 18,
}
c.JSON(http.StatusOK, data)
})
r.GET("/json1", func(c *gin.Context) {
//方法2:结构体
type msg struct {
Name string `json:"name"` //不能小写,否则无法访问,要想返回的json是小写,需要设置
Message string
Age int8
}
data := msg{"weiyuexin", "hello gin json", 90}
c.JSON(http.StatusOK, data) //json序列化使用反射
})
r.Run(":9090")
}
querystring指的是URL中?后面携带的参数,例如:`/user/search?username=小王子&address=沙河
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
//获取参数
func main() {
r := gin.Default()
//GET请求URL 的?后面的是query string参数
// 通过key-value格式,多个key-value通过 & 连接
r.GET("/query", func(c *gin.Context) {
//获取浏览器发送过来的请求携带的 query string
name := c.Query("name") //通过Query获取请求中携带的querystring参数
age := c.Query("age") //通过Query获取请求中携带的querystring参数
//name := c.DefaultQuery("name", "wyx") //查到的话就用查到的值,查不到就用默认值
//name, ok := c.GetQuery("name") //返回取到的值和是否取到
//if !ok {
// c.JSON(http.StatusOK, gin.H{
// "ok": "请输入参数",
// })
// return
//}
c.JSON(http.StatusOK, gin.H{
"ok": "hello," + name + ",age=" + age,
})
})
r.Run(":9090")
}
运行结果:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
//获取form表单提交的参数
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
//username:=http.PostForm("username") //获取form表单提交的数据
//username := c.DefaultPostForm("username", "wyx")//获取不到的话就使用默认值
username, ok1 := c.GetPostForm("username") //获取数据和获取结果
password, ok2 := c.GetPostForm("password")
msg := "登录成功"
data := make(map[string]string, 2)
status := 200
if !ok1 {
msg = "请输入用户名"
status = 500
}
if !ok2 {
msg = "请输入密码"
status = 500
}
data["username"] = username
data["password"] = password
c.JSON(http.StatusOK, gin.H{
"code": status,
"msg": msg,
"data": data,
})
})
r.Run(":9090")
}
运行结果:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
//获取URL路径参数
func main() {
r := gin.Default()
r.GET("/user/:userId/:username", func(c *gin.Context) {
//获取路径参数
userId := c.Param("userId")
username := c.Param("username")
c.JSON(http.StatusOK, gin.H{
"userId": userId,
"username": username,
})
})
r.Run(":9090")
}
运行结果:
package main
import (
"encoding/json"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/json", func(c *gin.Context) {
data, _ := c.GetRawData()
//定义map或结构体
var m map[string]interface{}
//反序列化
_ = json.Unmarshal(data, &m)
c.JSON(http.StatusOK, m)
})
r.Run(":9090")
}
运行结果:
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type
识别请求数据类型并利用反射机制自动提取请求中QueryString
、form表单
、JSON
、XML
等参数到结构体中。 下面的示例代码演示了.ShouldBind()
强大的功能,它能够基于请求自动提取JSON
、form表单
和QueryString
类型的数据,并把值绑定到指定的结构体对象。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type User struct {
Username string `form:"username" json:"username" `
Password string `form:"password" json:"password" `
}
//绑定参数
func main() {
r := gin.Default()
//绑定query-string参数
r.GET("/user", func(c *gin.Context) {
var user User //声明一个User类型的变量user
err := c.ShouldBind(&user) //绑定参数到结构体,这里传的一定得是地址
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"msg": "hello",
"data": user,
})
}
})
//绑定form参数
r.POST("/form", func(c *gin.Context) {
var user User //声明一个User类型的变量user
err := c.ShouldBind(&user) //绑定参数到结构体,这里传的一定得是地址
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"msg": "hello",
"data": user,
})
}
})
//绑定json数据
r.POST("/json", func(c *gin.Context) {
var user User
err := c.ShouldBind(&user)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
c.JSON(http.StatusOK, gin.H{
"msg": "hello",
"data": user,
})
}
})
r.Run(":9090")
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AVYMTpiu-1690859834109)(https://cdn.jsdelivr.net/gh/weiyuexin/blogimg@latest/img/2023/08/01/20230801111636.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QPkI9rcV-1690859834110)(./assets/image-20230315105106348.png)]
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
)
//文件上传
func main() {
r := gin.Default()
//上传单个文件
r.POST("/upload", func(c *gin.Context) {
//从请求中读取文件
file, err := c.FormFile("file")
if err != nil { //读取失败
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
//将读取到的文件保存到服务器,dst是保存的位置
dst := fmt.Sprintf("C:\\Users\\30224\\Desktop\\GoLearn\\gin\\gin_06_upload_file\\%s", file.Filename)
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"msg": "upload success",
"filePath": dst,
})
}
})
//上传多个文件
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/uploads", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("C:\\Users\\30224\\Desktop\\GoLearn\\gin\\gin_06_upload_file\\%d_%s", index, file.Filename)
//上传文件到指定目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"msg": fmt.Sprintf("%d files upload!", len(files)),
})
})
r.Run(":9090")
}
HTTP 重定向很容易。 内部、外部重定向均支持。
//http重定向
r.GET("/index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})
运行结果:直接跳转到新页面
路由重定向,使用HandleContext
:
//路由重定向
r.GET("/a", func(c *gin.Context) {
//跳转到 /b 对应的路由函数
c.Request.URL.Path = "/b" //把请求的URI修改cheng /b
r.HandleContext(c) //继续后续的处理
c.JSON(http.StatusOK, gin.H{
"msg": "a",
})
})
r.GET("/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "b",
})
})
运行结果:浏览器显示的地址没有变,先执行 /b 然后再执行 /a 的后续操作
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
//请求方法集合,匹配所以请求方法
r.Any("/user", func(c *gin.Context) {
switch c.Request.Method {
case "GET":
c.JSON(http.StatusOK, gin.H{"method": "GET"})
case http.MethodPost:
c.JSON(http.StatusOK, gin.H{"method": "POST"})
case http.MethodDelete:
c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
}
})
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html
页面。
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "404 not found"})
})
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}
包裹同组的路由,这只是为了看着清晰,你用不用{}
包裹功能上没什么区别。
videoGroup := r.Group("/video")
{
videoGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/index"})
})
videoGroup.GET("/xx", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/xx"})
})
videoGroup.GET("/oo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/video/oo"})
})
}
路由组也是支持嵌套的,例如:
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/shop/index"})
})
xx := shopGroup.Group("/xx")
{
xx.GET("/oo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "/shop/xx/oo"})
})
}
}
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
Gin中的中间件必须是一个gin.HandlerFunc
类型。
// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
// 调用该请求的剩余处理程序
c.Next()
// 不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
log.Println(cost)
}
}
//记录响应体的中间件
type bodyLogWriter struct {
gin.ResponseWriter // 嵌入gin框架ResponseWriter
body *bytes.Buffer // 我们记录用的response
}
// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b) // 我们记录一份
return w.ResponseWriter.Write(b) // 真正写入响应
}
// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
c.Writer = blw // 使用我们自定义的类型替换默认的
c.Next() // 执行业务逻辑
fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}
在gin框架中,我们可以为每个路由添加任意数量的中间件。
func main() {
// 新建一个没有任何默认中间件的路由
r := gin.New()
// 注册一个全局中间件
r.Use(StatCost())
r.GET("/test", func(c *gin.Context) {
name := c.MustGet("name").(string) // 从上下文取值
log.Println(name)
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
})
r.Run()
}
// 给/test2路由单独注册中间件(可注册多个)
r.GET("/test2", StatCost(), func(c *gin.Context) {
name := c.MustGet("name").(string) // 从上下文取值
log.Println(name)
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
})
//方法一
shopGroup := r.Group("/shop", StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
//方法二
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。