Gin 是一个 Go (Golang) 编写的轻量级 web 框架,运行速度非常快,擅长 Api 接口的高并发,如果项目的规模不大,业务相对简单,这个时候我们也推荐您使用 Gin,特别适合微服务框架。
我自己也是Go开发方面的菜鸟,额外的就不多废话了。
简单路由配置
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 配置路由
r.GET("/", func(c *gin.Context) {
aid := c.Query("aid")
c.JSON(200, gin.H{
"username": "name1",
"aid": aid,
"data": []string{"hello", "world"},
})
})
// 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
r.Run()
}
运行起来以后,在浏览器输入http://127.0.0.1:8080/?aid=xyz,即可获取到 url 请求的结果
{"aid":"xyz","data":["hello","world"],"username":"name1"}
动态路由
所谓动态路由,其实就是将传参作为 url 的一部分,这样的话,url 就不再是固定不变的,而是随着传参的变化而变化,像 Ruby 等其他语言也有类似的用法。
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{
"username": "name1",
"id": id,
"data": []string{"hello", "world"},
})
})
r.Run()
}
请求 url:http://127.0.0.1:8080/user/looking
请求 result:
{"data":["hello","world"],"id":"looking","username":"name1"}
结果响应
c.String()
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/news", func(c *gin.Context) {
c.String(200, "Hello world")
})
r.Run()
}
c.JSON()
大部分时候,我们直接返回 json 的数据格式要更多一些。数据返回我们可以使用 gin.H 的 map 形式,也可以直接用 struct 的形式,不过使用结构体的话,记得要给字段标注好 json 对应的 tag,方便直接将结构体实例解析成对应的 json 数据。
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/structJson", func(c *gin.Context) {
// 结构体方式
var msg struct {
Username string `json:"username"`
Msg string `json:"msg"`
Age string `json:"age"`
}
msg.Username = "name1"
msg.Msg = "msg1"
msg.Age = "18"
c.JSON(200, msg)
})
r.Run()
}
c.JSONP()
这个暂时用的比较少
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/JSONP", func(c *gin.Context) {
data := map[string]interface{}{
"foo": "bar",
}
c.JSONP(http.StatusOK, data)
})
r.Run()
}
请求 url:http://127.0.0.1:8080/JSONP?callback=x
请求 result:
x({"foo":"bar"});
c.HTML()
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>这是一个 html 模板</h1>
<h3>{{.title}}</h3>
</body>
</html>
渲染之前,先对文件进行 load 加载,框架会自动将变量替换到 html 文件里进行渲染
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.LoadHTMLFiles("./templates/index.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(
http.StatusOK, "index.html",
map[string]interface{}{"title": "前台首页"})
})
r.Run()
}
http://127.0.0.1:8080/index
请求传值
get查询和动态路由前面已经有示例了,我们看下其他类型的传值。
post获取表单数据
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/doAddUser", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
age := c.DefaultPostForm("age", "20")
c.JSON(200, gin.H{
"usernmae": username, "password": password, "age": age,
})
})
r.Run()
}
post/get传值绑定到结构体
传值绑定结构体估计是我们正常开发时最常用的参数解析方式之一了(至少我周围同事大部分都用这种形式传值)
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
type Userinfo struct {
Username string `form:"username" json:"user"`
Password string `form:"password" json:"password"`
}
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
var userinfo Userinfo
if err := c.ShouldBind(&userinfo); err == nil {
fmt.Printf("userinfo: %+v\n", userinfo) // userinfo: {Username:zhangsan Password:123456}
c.JSON(http.StatusOK, userinfo)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
r.Run()
}
http://127.0.0.1:8080/?username=zhangsan&password=123456
{"user":"zhangsan","password":"123456"}
同理,POST请求等也可以将请求参数绑定到结构体中
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Userinfo struct {
Username string `form:"username" json:"user"`
Password string `form:"password" json:"password"`
}
func main() {
r := gin.Default()
r.POST("/doLogin", func(c *gin.Context) {
var userinfo Userinfo
if err := c.ShouldBind(&userinfo); err == nil {
c.JSON(http.StatusOK, userinfo)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
r.Run()
}
post获取xml数据
一般请求传递 xml 格式数据的遇到的不多,不过也可以试试。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
type Article struct {
Title string `json:"title" xml:"title"`
Content string `json:"content" xml:"content"`
}
func main() {
r := gin.Default()
r.POST("/xml", func(ctx *gin.Context) {
var article Article
if err := ctx.ShouldBindXML(&article); err == nil {
fmt.Printf("article: %+v\n", article)
ctx.JSON(http.StatusOK, article)
}else {
ctx.JSON(http.StatusBadRequest, gin.H {
"err": err.Error()})
}
})
r.Run()
}
可以使用 Apifox 发送请求尝试,可以直观看到接口返回的结果
路由分组
路由分组即将相关的路由加上相同的前缀,用以和其他路由进行区分和辨别(我自己理解是这样,分组依据一般可以按照业务等进行划分)。
package main
import (
"github.com/gin-gonic/gin"
)
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/", func(ctx *gin.Context) {
ctx.String(200, "api")
})
apiRouter.GET("articles", func(ctx *gin.Context) {
ctx.String(200, "/api/articles")
})
}
}
func main() {
r := gin.Default()
ApiRouter(r)
r.Run()
}
路由分离
路由分离可以将不相关的路由解耦,分离到单独的文件进行维护。
在项目新建文件夹 router
, 然后在router
目录下创建apiRouter.go
和adminRouter.go
router/apiRouter.go
package router
// apiRouter.go
import "github.com/gin-gonic/gin"
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/", func(ctx *gin.Context) {
ctx.String(200, "api")
})
apiRouter.GET("articles", func(ctx *gin.Context) {
ctx.String(200, "/api/articles")
})
}
}
router/apiAdmin.go
package router
// adminRouter.go
import (
"net/http"
"github.com/gin-gonic/gin"
)
func AdminRouter(r *gin.Engine) {
adminRouter := r.Group("/admin")
{
adminRouter.GET("/", func(ctx *gin.Context) {
ctx.String(200, "admin")
})
adminRouter.GET("list", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "admin/list")
})
}
}
然后在 main.go
中引入路由模块并使用即可(在真实的开发中,main.go 中的内容其实很少,一般只是一个启动整个服务的入口)
package main
import (
"github.com/gin-gonic/gin"
"github.com/test/router"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 引入路由模块
router.AdminRouter(r)
router.ApiRouter(r)
// 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
r.Run()
}
自定义控制器
当我们的项目比较大的时候有必要对我们的控制器进行分组 , 业务逻辑放在控制器中(有的喜欢把业务逻辑处理部分所在的包称为 handler)。
新建 controller/api/userController.go
package api
import "github.com/gin-gonic/gin"
func UserIndex(c *gin.Context) {
c.String(200, "api UserIndex")
}
func UserAdd(c *gin.Context) {
c.String(200, "api UserAdd")
}
func UserList(c *gin.Context) {
c.String(200, "api UserList")
}
func UserUpdate(c *gin.Context) {
c.String(200, "api UserUpdate")
}
func UserDelete(c *gin.Context) {
c.String(200, "api UserDelete")
}
router/apiRouter.go
package router
// apiRouter.go
import (
"github.com/gin-gonic/gin"
"github.com/test/controller/api"
)
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/")
apiRouter.GET("/users", api.UserIndex)
apiRouter.GET("/users/:id", api.UserList)
apiRouter.POST("/users", api.UserAdd)
apiRouter.PUT("/users/:id", api.UserUpdate)
apiRouter.DELETE("/users", api.UserDelete)
}
}
控制器继承
要让控制器可以继承,最好将控制器做成方法的形式(一般默认是函数的形式),这样的话,就可以根据结构体的匿名字段,实现对继承结构体的方法进行很方便的调用。
controller/api/baseController.go
package api
import "github.com/gin-gonic/gin"
type BaseController struct {
}
func (con BaseController) Success(c *gin.Context) {
c.String(200, "success")
}
func (con BaseController) Error(c *gin.Context) {
c.String(200, "failed")
}
controller/api/userController.go
package api
import "github.com/gin-gonic/gin"
type UserController struct {
BaseController
}
func (con UserController) UserIndex(c *gin.Context) {
// c.String(200, "api UserIndex")
con.Success(c)
}
func (con UserController) UserAdd(c *gin.Context) {
c.String(200, "api UserAdd")
}
func (con UserController) UserList(c *gin.Context) {
c.String(200, "api UserList")
}
func (con UserController) UserUpdate(c *gin.Context) {
c.String(200, "api UserUpdate")
}
func (con UserController) UserDelete(c *gin.Context) {
c.String(200, "api UserDelete")
}
apiRouter.go
package router
// apiRouter.go
import (
"github.com/gin-gonic/gin"
"github.com/test/controller/api"
)
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/")
apiRouter.GET("/index", api.UserController{}.UserIndex)
apiRouter.GET("/users", api.UserController{}.UserList)
apiRouter.GET("/users/:id", api.UserController{}.UserList)
apiRouter.POST("/users", api.UserController{}.UserAdd)
apiRouter.PUT("/users/:id", api.UserController{}.UserUpdate)
apiRouter.DELETE("/users", api.UserController{}.UserDelete)
}
}
To be continued