Golang Gin框架
1. 返回json
func main() {
engine := gin.Default()
// http://localhost:8080/a
engine.GET("/a", func(c *gin.Context) {
//返回JSON map
c.JSON(http.StatusOK, gin.H{"message": "Hello World", "status": "ok"})
})
type user struct {
Name string `json:"name"` //定义返回前端的json字段
Age int
Sex string
}
// http://localhost:8080/b
engine.GET("/b", func(c *gin.Context) {
user := user{
Name: "John Doe",
Age: 3600,
Sex: "man",
}
//返回JSON结构体
c.JSON(http.StatusOK, user)
})
engine.Run()
}
2. 获取querystring参数
func main() {
engine := gin.Default()
// http://localhost:8080/a?name=xx&age=xxx
engine.GET("/a", func(c *gin.Context) {
name := c.Query("name")
name := c.Query("age")
c.JSON(http.StatusOK, gin.H{"name": name, "age": age})
})
// http://localhost:8080/b
engine.GET("/b", func(c *gin.Context) {
// DefaultQuery 未匹配到会赋默认值
name := c.DefaultQuery("name", "张三")
c.JSON(http.StatusOK, name)
})
// http://localhost:8080/c?name=李四
engine.GET("/c", func(c *gin.Context) {
// GetQuery 会返回一个bool
name, ok := c.GetQuery("name")
if !ok {
name = "张三"
}
c.JSON(http.StatusOK, name)
})
engine.Run()
}
3. 获取form参数
func main() {
engine := gin.Default()
engine.POST("/a", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
c.JSON(http.StatusOK, gin.H{"username": username, "password": password})
})
engine.POST("/b", func(c *gin.Context) {
// DefaultQuery 赋默认值
username := c.DefaultQuery("username", "root")
password := c.DefaultQuery("password", "root")
c.JSON(http.StatusOK, gin.H{"username": username, "password": password})
})
engine.POST("/c", func(c *gin.Context) {
// DefaultQuery 赋默认值
username, ok := c.GetPostForm("username")
if !ok {
username = "111"
}
password, ok := c.GetPostForm("password")
if !ok {
password = "xxx"
}
c.JSON(http.StatusOK, gin.H{"username": username, "password": password})
})
engine.Run()
}
4. 获取URI路径参数
func main() {
engine := gin.Default()
//http://localhost:8080/zhangsan/18
engine.GET("/:name/:age", func(c *gin.Context) {
name := c.Param("name")
age := c.Param("age")
c.JSON(http.StatusOK, gin.H{"name": name, "age": age})
})
engine.Run()
}
5. gin参数绑定
type UserInfo struct {
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
func main() {
engine := gin.Default()
//http://localhost:8080/zhangsan/18
engine.GET("/a", func(c *gin.Context) {
var u UserInfo //声明一个UserInfo结构体变量
err := c.ShouldBind(&u) // 要传地址
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
}
c.JSON(http.StatusOK, u)
})
engine.Run()
}
6. 文件上传
func main() {
engine := gin.Default()
// 处理multipart forms提交文件的大小限制是 32 MiB
// 可以通过下列方式修改
engine.MaxMultipartMemory = 8 << 20 // 8MiB
//单文件上传
engine.POST("/uploadOne", func(c *gin.Context) {
file, err := c.FormFile("filename")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
//dst := fmt.Sprintf("./%s", file.Filename)
dst := path.Join("./", file.Filename)
err = c.SaveUploadedFile(file, dst)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
//多文件上传
engine.POST("/uploadMore", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
files := form.File["file"]
for index, file := range files {
log.Printf(file.Filename)
dst := fmt.Sprintf("./%s_%d", file.Filename, index)
err := c.SaveUploadedFile(file, dst)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
engine.Run()
}
7. gin请求重定向与转发
func main() {
engine := gin.Default()
engine.GET("/a", func(c *gin.Context) {
// 请求重定向
c.Redirect(http.StatusMovedPermanently, "https://baidu.com")
engine.HandleContext(c) //继续后续处理
})
// 请求转发
engine.GET("/b", func(c *gin.Context) {
c.Request.URL.Path = "/c"
})
engine.GET("/c", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "success"})
})
engine.Run()
}
8. gin路由和路由组及嵌套路由组
func main() {
engine := gin.Default()
//定义路由组
group := engine.Group("index")
{
group.GET("/a", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "GET"})
})
group.POST("/b", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "POST"})
})
group.PUT("/c", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "PUT"})
})
group.DELETE("/d", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
})
// 嵌套路由组
groupChild := engine.Group("child")
{
groupChild.GET("/e", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "GET"})
})
groupChild.POST("/f", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"method": "POST"})
})
}
}
engine.Any("/any", func(c *gin.Context) {
switch c.Request.Method {
case http.MethodGet:
c.JSON(http.StatusOK, gin.H{"method": "GET"})
case http.MethodPost:
c.JSON(http.StatusOK, gin.H{"method": "POST"})
// ....
}
})
// NoRoute 访问未定义的路由
engine.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "page not found"})
})
engine.Run()
}
9. gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
9.1 定义中间件
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)
}
}
9.2 注册中间件
在gin框架中,可以为每个路由添加任意数量的中间件。
9.2.1 为全局路由注册
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()
}
9.2.2 为某个路由单独注册
// 给/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!",
})
})
9.2.3 为路由组注册中间件
写法一
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) {...})
...
}
9.3 中间件注意事项
9.3.1 gin默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
9.3.2 gin中间件中使用goroutine
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。
10. gin运行多个服务
可以在多个端口启动服务,例如:
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}