今天开始写Golang之Gin框架的基本使用

299 阅读7分钟

color.png

依照学习路径图,本文进入Web框架部分。

本文将对Gin框架的基础进行讲解,希望你已经具备一定的知识,这样才能从本文中获得最大化的收益。

简介

Gin框架是用 Go (Golang) 编写的 Web 框架,主要是基于httprouter,它的性能非常优秀,但是只能在简单的场景使用。因为一旦需求非常庞大,Gin的路由配置会让你崩溃。

Hello World

第一个Gin程序我们还是选择Hello World,因为这是亘古不变的规矩了。

hold on,你不会还没有下载gin的依赖吧?

依赖安装

这一步应该在go.mod文件存在时才能操作,所以建议先在goland创建好项目,再在控制台操作。

安装gin:

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

如果你感到网速很慢建议你配置镜像:

这里推荐七牛云:goproxy.cn/

Go 1.13 及以上版本可以直接打开你的终端并执行:

$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct

Hello World的实现

func main() {
   r := gin.Default()
   r.GET("/get", func(context *gin.Context) {
      context.String(http.StatusOK, "Hello World")
   })
   r.Run(":8099")
}

从代码中依次分析:

第一步 我们需要创建一个gin的默认实例

第二步 通过实例创建Get方法,Get方法第一个参数为路径,第二个参数为一个方法,是handle,执行回调或者处理逻辑,context是gin的上下文逻辑,通过context实现返回不同类型的结果。

第三步 将服务挂载到8099端口

一个基本的Hello World就已经完成,当你访问 http://localhost:8099/get 时 你就能看到页面显示了 Hello World。

基本的路由操作

Gin 的核心就是路由,所以所有操作都伴随路由的创建。

如何实现基本路由并能使其能够交互呢,我们下面继续讲解。

在Hello World中,我们的程序已经是单向数据交互了,如果想要接收客户端传来的数据就需要进行对参数的接收和处理。

Get方法

路径参数

一般样式:http://localhost:port/get/{:param}

其中在{:param}

: 只能匹配1个,* 可以匹配任意个数

: 方式的参数获取

此规则能够匹配/user/xxx这种格式,但不能匹配/user/ 或 /user这种格式)

访问方式:http://localhost:8099/get/TT

r.GET("/get/:name", func(context *gin.Context) {
   context.String(http.StatusOK,"Hello "+context.Param("name")) //Hello TT
})

*方式的参数获取

此规则既能匹配 /user/xxx/ 格式也能匹配 /user/xxx/other1/other2 这种格式

访问方式:http://localhost:8099/getMulti/TT/To/Learning

r.GET("/getMulti/:name/*action", func(context *gin.Context) {
   context.String(http.StatusOK, context.Param("name")+context.Param("action")) // Result: TT/To/Learning
})

非路径参数(URL参数)

URL参数可以通过DefaultQuery()Query()方法获取

DefaultQuery()若参数不存在,返回默认值,Query()若不存在,返回空串

r.GET("/getParam", func(context *gin.Context) {
   // 获取name字段,如果不存在则用默认值代替
   name := context.DefaultQuery("name", "normal")
   //获取具体值
   age := context.Query("age")
   context.String(http.StatusOK, fmt.Sprintf("hello %s, your age is %s", name, age))
})

Post方法

通过form-data传递参数

r.POST("/post", func(context *gin.Context) {
   context.JSON(http.StatusOK, gin.H{ //返回JSON
      "username": context.PostForm("username"),
   })
})

使用json传递数据获取

r.POST("/post", func(context *gin.Context) {
   json := make(map[string]interface{}) //使用map接收json
   context.BindJSON(&json)
   username := json["username"]
   context.JSON(http.StatusOK, gin.H{
      "username": username,
   })
})

返回值类型

最常见的就是直接返回字符串

    context.String(http.StatusOK,"Hello "+context.Param("name"))

如果想要返回一个json则是使用:

   context.JSON(http.StatusOK, gin.H{
      "username": username,
   })

gin.H会创建一个符合json的数据。

分组路由

相信看到这里的你已经发现,如果这样的单个路由很多会让代码越来越混乱,自然也就一些常用的解决方法出现。

分组路由是指将路由依据功能不同进行划分,一般它遵循这样的文件目录。

├── MultiRouter
│   │
│   ├── V1 (分组路由V1)
│   │   ├── Handle.go (事件触发函数)
│   │   └── router.go (路由分组及声明)
│   │
│   ├── V2
│   │   ├── Handle.go
│   │   └── router.go
│   │
│   └── setup_router.go (批量注册路由)
│   
└── main.go (通过setup_router注册路由并开启服务)

分组路由后将会在自定的路径前加上组的信息,例如:

原来:http://localhost:8099/get

现在(v1分组下):http://localhost:8099/v1/get

V1

V1 分组代码:

router.go

func LoadRouter(e *gin.Engine) {
   v1 := e.Group("v1")
   {
      v1.GET("/get", getHandle)
      v1.POST("/post", postHandle)
   }
}

Handle.go

func getHandle(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{
      "result": "V1 Get",
   })
}

func postHandle(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{
      "result": "V1 Post",
   })
}

V2

router.go

func LoadRouter(e *gin.Engine) {
   v2 := e.Group("v2")
   {
      v2.GET("/get", getHandle)
      v2.POST("/post", postHandle)
   }

}

Handle.go

func getHandle(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{
      "result": "V2 get",
   })
}

func postHandle(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{
      "result": "V2 post",
   

setup_router.go (批量注册路由)

type Register func(engine *gin.Engine)

func Init(route ...Register) *gin.Engine {
   //注册路由
   rs := append([]Register{}, route...)

   r := gin.New()

   for _, register := range rs {
      register(r)
   }

   return r
}

main.go

func main() {
   r := MultiRouter.Init(
      V1.LoadRouter,
      V2.LoadRouter,
   )
   r.Run(":8000")
}

SessionControl 会话控制

Cookie

func main() {
   r := gin.Default()

   r.Run(":8099")
}

func cookies(r *gin.Engine) {
   r.GET("/getCookie", func(c *gin.Context) {
      // 获取客户端是否携带cookie
      cookie, err := c.Cookie("key_cookie")
      if err != nil {
         cookie = "cookie"
         c.SetCookie("key_cookie", "value_cookie", // 参数1、2: key & value
            60,          // 参数3: 生存时间(秒)
            "/",         // 参数4: 所在目录
            "localhost", // 参数5: 域名
            false,       // 参数6: 安全相关 - 是否智能通过https访问
            true,        // 参数7: 安全相关 - 是否允许别人通过js获取自己的cookie
         )
      }
      fmt.Printf("cookie的值是: %s\n", cookie)
   })

}

Session

func main() {
   r := gin.Default()

   // 注意该密钥不要泄露了
   store := cookie.NewStore([]byte("secret"))
   //路由上加入session中间件
   r.Use(sessions.Sessions("mySession", store))

   r.GET("/setSession", func(c *gin.Context) {
      // 设置session
      session := sessions.Default(c)
      session.Set("key", "value")
      session.Save()
   })

   r.GET("/getSession", func(c *gin.Context) {
      // 获取session
      session := sessions.Default(c)
      v := session.Get("key")
      fmt.Println(v)
   })

   r.Run(":8080")
}

middleware 中间件

中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的。

默认中间件

在Gin中给出的中间件很多,基本涵盖了简单使用,如果你有特殊需求也可以进行自定义。

func BasicAuth(accounts Accounts) HandlerFunc // 身份认证
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
func ErrorLogger() HandlerFunc       //错误日志处理
func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
func Logger() HandlerFunc //日志记录
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件

在使用gin.default时默认使用了Logger(), Recovery()全局作用了两个中间件.

使用中间件

全局使用

func main() {
   r := gin.Default()

   //配置中间件
   r.Use(...)

   r.GET("/get", func(context *gin.Context) {
      context.String(http.StatusOK, "I get it")
   })

   r.Run(":8000")
}

在路由分组中使用

func main() {
	r := gin.Default()
	v1 := r.Group("/v1", gin.Logger(), gin.Recovery())
	{
		v1.GET("/", func(c *gin.Context) {
			c.JSON(200, gin.H{"name": "m1"})
		})
		v1.GET("/test", func(c *gin.Context) {
			c.JSON(200, gin.H{"name": "m1 test"})
		})
	}
	
	r.Run()
}

或者使用这样的方式也是对的

v1.Use(MyLogMiddleWare())

在单个路由中使用

func main() {
	r := gin.Default()
	r.GET("/", gin.Recovery(), gin.Logger(), func(c *gin.Context) {
		c.JSON(200, gin.H{"name": "m1"})
	})
	r.Run()
}

自定义中间件

自定义中间件是在无法满足需求的情况下,自己定制化的中间件。一般来说只要它返回的是gin.HandlerFunc 就是自定义中间件。

func MyMiddleware() gin.HandlerFunc {
   return func(context *gin.Context) {
      fmt.Println("[MyLog] IP:", context.ClientIP())
   }
}

Abort()和Next()

next()函数会跳过当前中间件中next()后的逻辑,当下一个中间件执行完成后再执行剩余的逻辑

abort()函数执行终止当前中间件以后的中间件执行,但是会执行当前中间件的后续逻辑

简单实现一个记录函数执行的时间中间件

func nextControl(router *gin.Engine) {
   router.GET("/mid", TimeMiddleware, handler)

}

// 记录函数执行的时间中间件
func TimeMiddleware(ctx *gin.Context) {
   fmt.Println("------------TimeMiddleware 计时开始------------")
   start := time.Now()
   ctx.Next()
   Since := time.Since(start)
   fmt.Println(Since)
   fmt.Println("++++++++++++TimeMiddleware 计时结束++++++++++++")
}
func handler(ctx *gin.Context) {
   fmt.Println("#############正常的handler执行开始#############")
   ctx.JSON(http.StatusOK, gin.H{
      "ziop": "ziop",
   })
}

FileUpdate 文件上传

单文件

func single(r *gin.Engine) {
   // 给表单限制上传大小 (默认 32 MiB)
   r.MaxMultipartMemory = 8 << 20 // 8 MiB
   r.POST("/upload", func(c *gin.Context) {
      file, err := c.FormFile("file")
      if err != nil {
         c.String(500, "上传文件出错")
      }

      // 上传到指定路径
      c.SaveUploadedFile(file, "~/Desktop/"+file.Filename)
      c.String(http.StatusOK, "fileName:", file.Filename)
   })

}

多文件

func multi(r *gin.Engine) {
   r.MaxMultipartMemory = 8 << 20 // 8 MiB
   r.POST("/uploadMulti", func(c *gin.Context) {
      // 获取MultipartForm
      form, err := c.MultipartForm()
      if err != nil {
         c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
      }

      // 获取所有文件
      files := form.File["files"]
      for _, file := range files {
         // 逐个存
         fmt.Println(file.Filename)
      }
      c.String(200, fmt.Sprintf("upload ok %d files", len(files)))

   })
}

与Mysql交互

依赖包

_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"

定义数据库基本信息

var (
   UserName string = "root" //用户名
   PassWord string = "123456" //密码
   IP       string = "localhost" 
   Port     int    = 3306
   DbName   string = "go_db"
   CharSet  string = "utf8mb4"

   Db  *sql.DB
   err error
)

创建一个链接

func connectDataBase() *sqlx.DB {
   connect := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", UserName, PassWord, IP, Port, DbName, CharSet)
   Db, _ := sqlx.Open("mysql", connect)
   return Db
}

获取全部用户

func GetUserAll() (userSlice []pojo.User) {
   Db := connectDataBase()
   sql := "select * from user"
   Db.Select(&userSlice, sql)
   defer Db.Close() //异步关闭链接
   return userSlice
}

通过ID获取

func GetUserById(id int) pojo.User {
   Db := connectDataBase()
   sql := "select * from user where id= ?"
   var u pojo.User
   Db.Get(&u, sql, id)
   defer Db.Close()
   return u

}

通过id更新数据

func UpdateById(name string, id int) (int64, error) {
   Db := connectDataBase()
   sql := "update user set name = ? where id = ?"
   ret, _ := Db.Exec(sql, name, id)
   defer Db.Close()
   return ret.RowsAffected()
}

新增用户

func AddUser(name string, age int) (int64, error) {
   Db := connectDataBase()
   sql := "insert into user(name,age) values (?,?)"
   ret, _ := Db.Exec(sql, name, age)
   defer Db.Close()
   return ret.LastInsertId()
}

删除用户

func DeleteById(id int) (int64, error) {
   Db := connectDataBase()
   sql := "delete from user where id=?"
   ret, _ := Db.Exec(sql, id)
   defer Db.Close()
   return ret.RowsAffected()
}

以上就是gin框架的简单使用。

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿