首先我们使用 go moudle来管理依赖, go版本要求在 1.11.1 及以上
export GO111MODULE=on
export GOPROXY=https://mirrors.aliyun.com/goproxy/
初始化 mod 配置
go mod init
>在当前目录下会生成 go.mod 文件, 作为依赖配置.
build项目后会自动下载依赖
其中在实际项目中发现 goland 无法识别项目代码, 需要 在 goland 的 setting 里设置启用 Go Modules
goland Preference -> Go -> Go Modules(vgo)
-> Enable Go Modules(vgo)intergration
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// get 方式请求
r.GET("/get", func(c *gin.Context) {
c.JSON(200, "get")
})
// post 方式请求
r.POST("/post", func(c *gin.Context) {
c.JSON(200, "post")
})
// 任意类型请求
r.Any("any", func(c *gin.Context) {
c.JSON(200, "any")
})
// handle 指定方式请求
r.Handle("POST", "/handle_post", func(c *gin.Context) {
c.JSON(200, "handle post")
})
//r.Run(":8888") 监听 8888 端口
r.Run()
}
这里我们定义了常用的 get post方法, 通过 debug 可以看到 any 方式为我们创建了所有的可以使用的请求的方式, 最后使用了 handle 方式指定方法参数. gin.Default 配置默认的参数, Run 启动 http 服务, 默认监听 8080 端口
curl -XPOST http://127.0.0.1:8080/post
output:
"post"
curl -XPOST http://127.0.0.1:8080/handle_post
output:
"handle post"
gin 的路由同请求方式下不能重复定义, 否则启动将会异常退出
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// get 方式请求
r.GET("/getUser/:uid", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"uid": c.Param("uid"),
})
})
r.Run()
}
output:
curl http://127.0.0.1:8080/getUser/1451
output:
{"uid":"1451"}
http.StatusOK 引用的 net/http 包中的状态码定义, c.Param() 获取 uid 的值
在工作中我们希望路由匹配到某个前缀
r.GET("/user/*a", func(c *gin.Context) {
c.String(http.StatusOK, "hello user")
})
这样只要请求到 user 就可以拦截到了, 注意在 gin 里是不允许再设置类似 /user/admin 这样的路由了. 这里是没有路由优先级的.
请求参数默认值
r.GET("/user", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"name": c.Query("name"),
"sex": c.DefaultQuery("sex", "女"),
})
})
请求示例
curl http://127.0.0.1:8080/user?name=1
output:
{"name":"1","sex":"女"}
获取post方式请求的参数
c.String(http.StatusOK, c.PostForm("name"))
//获取name值, 默认值为小明
c.String(http.StatusOK, c.DefaultPostForm("name", "小明")
r.POST("/user", func(c *gin.Context) {
bodyBytes, error := ioutil.ReadAll(c.Request.Body)
if error != nil {
c.String(http.StatusOK, error.Error())
c.Abort()
}
c.String(http.StatusOK, string(bodyBytes))
})
curl -XPOST http://127.0.0.1:8080/user?name=1 -d {"name":1}
output:
{name:1}
但是通过 readAll 读取后再获取值是获取不到的. 解决方案就是再将值写回去.
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type requestUser struct {
Name string `form:"name"`
Uid int `form:"uid"`
Age int `form:"age"`
Date time.Time `form:"date"`
}
func main() {
r := gin.Default()
r.POST("/user", userAction)
r.Run()
}
func userAction(c *gin.Context) {
var requestUser requestUser
//这里是根据请求的content-type进行解析(可以解析json)
if err := c.ShouldBind(&requestUser); err != nil {
c.String(http.StatusOK, err.Error())
c.Abort()
}
c.String(http.StatusOK, "%v", requestUser)
}
curl -XPOST http://127.0.0.1:8080/user -d 'name=xiaoming&age=23'
output:
{xiaoming 0 23}
curl -XPOST http://127.0.0.1:8080/user -d 'name=xiaoming&date=2019-01-01'
output:
parsing time "2019-01-01" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "T"{xiaoming 0 0 0001-01-01 00:00:00 +0000 UTC}
注意我们在传递时间格式的时候解析发生错误.
这里我们需要定义时间格式结构体解决
type requestUser struct {
Name string `form:"name"`
Uid int `form:"uid"`
Age int `form:"age"`
Date time.Time `form:"date" time_format:"2006-01-02"`
}
type requestUser struct {
Name string `form:"name" binding:"required,len=10"`
Uid int `form:"uid"`
Age int `form:"age" binding:"required,gt=10"`
}
curl -H "Content-Type:application/json" -XPOST http://127.0.0.1:8080/user -d '{"name":"xiaoming234342342342342","age":11}'
output:
Key: 'requestUser.Name' Error:Field validation for 'Name' failed on the 'len' tag{xiaoming234342342342342 0 11}
更多验证规则: https://godoc.org/gopkg.in/go-playground/validator.v8
###自定义 validator
type requestUser struct {
Name string `form:"name" binding:"required,len=10"`
Uid int `form:"uid" binding:"checkUidAvailable"`
Age int `form:"age" binding:"required,gt=10"`
}
编写 checkUidAvailable
// Structure
func checkUidAvailable(field validator.FieldLevel) bool {
if fieldValue, ok := field.Field().Interface().(int); ok {
//随机概率
if rand.Intn(2) == fieldValue {
return true
}
}
return false
}
在 main方法中绑定
func main() {
r := gin.Default()
r.POST("/user", userAction)
//绑定
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("checkUidAvailable", checkUidAvailable)
}
r.Run()
}
方法执行, ShouldBind 自动验证
func userAction(c *gin.Context) {
var requestUser requestUser
if err := c.ShouldBind(&requestUser); err != nil {
c.String(http.StatusInternalServerError, err.Error())
c.Abort()
return
}
c.String(http.StatusOK, "%v", requestUser)
}
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
gin.DefaultErrorWriter = io.MultiWriter(f)
r := gin.New()
r.Use(gin.Logger(), gin.Recovery())
上面我们使用了 gin.New 实例的方式声明, r.Use使用了两个中间件, 一个为日志, 另一个为遇见 panic 抛出异常最上层, 让程序正常运行.
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
gin.DefaultErrorWriter = io.MultiWriter(f)
r := gin.Default()
默认日志是输出到控制台, 将日志和错误日志全部输出到 gin.log
func ipAuthMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
ipList := []string{
"127.0.0.1",
}
flag := false
clientIp := c.ClientIP()
for _, host := range ipList {
if clientIp != host {
flag = true
break
}
}
if !flag {
c.String(http.StatusOK, "error")
c.Abort()
}
}
}
---
r.Use(gin.Logger(), gin.Recovery(), ipAuthMiddleWare())
api := r.Group("/api")
{
api.GET("user", userAction)
}
访问 127.0.0.1:8080/api/user 路由组中引入中间件
api := r.Group("/api").Use(ipAuthMiddleWare())
{
api.GET("user", userAction)
}
同样使用 use 即可
json处理: https://colobu.com/2017/06/21/json-tricks-in-Go/#%E4%B8%B4%E6%97%B6%E5%BF%BD%E7%95%A5struct%E7%A9%BA%E5%AD%97%E6%AE%B5