Gin

 ·  ☕ 8  · 👀...

介绍

开源项目

  • fuckdb
  • gen
    一个命令生成新项目,一个命令自动生成 crud restful api,提高业务代码速度。
  • modeltools
    GO语言连接Mysql生成对应的model,包括对应字段类型、注释等。生成基础的结构体,不局限于某一个ORM。
  • AutogenerateTemplate
    自动生成库表结构体小工具。
  • ginger
    Ginger 是一个构建gin应用的脚手架。
  • gin-code-generate
    基于gin框架的mvc代码生成工具。
  • gin-gen
    生成gin项目目录结构。
  • gormt
    mysql数据库转 struct 工具,可以将mysql数据库自动生成golang sturct结构,带大驼峰命名规则。带json标签
  • govalidator
    Package of validators and sanitizers for strings, numerics, slices and structs.
  • structs
    Utilities for Go structs.
  • gojson
    Automatically generate Go (golang) struct definitions from example JSON.
  • copier
    Copier for golang, copy value from struct to struct and more.
  • gomodifytags
    Go tool to modify struct field tags.
  • go-arg
    Struct-based argument parsing in Go.
  • generate
    Generates Go (golang) Structs from JSON schema.
  • data-structures
    Go datastructures.
  • meddler
    conversion between sql and structs in go.
  • faker
    Go(Golang)用于Struct的伪数据生成器
  • toml-to-go
    立即在浏览器中将TOML转换为Go类型https://xuri.me/toml-to-go
  • gostruct
    Struct Generator
  • json2struct
    CLI tool to convert JSON to Go type definitions.
  • go-struct-generator
    A Utility to generate Go struct from mysql tables.
  • gen_gorm_struct
    生成gorm结构体。
  • GoJSObjectGenerator
    Generates stub Javascript objects from Go structs. Used for Websocket message generation and synchronization.
  • go-pg-generator
    Golang struct generator for PostgreSQL.
  • sqlstruct
    Use Go structs with database/sql using this code generator.
  • xorm_mysql_generator
    generator go struct from mysql.
  • go_get_set_generator
    Getter and setter generator for go structs
  • Go-JSON-Struct-Generator
    A JSON to Go struct generator! Will take in JSON files, and make a Go Struct with it.
  • go_json_tag_generator
    Simple tool that add json tags to go lang structs
  • tdconv
    一个golang包和工具,用于将表定义转换为SQL和Go struct等。
  • structer
    Generate structs from JSON definitions https://p.nv3.eu/structer
  • go-struct-generator
    NodeJS script that outputs valid golang structs from arbitrary JSON object strings.
  • generatemodel
    automatic generate go struct from database(MySQL) table.

基础使用

安装

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

Hello World

main.go

1
2
3
4
5
6
7
8
9
func main() {
    r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

指定端口运行

1
2
3
server := gin.Default()
...
server.Run(":8090")

路由(Route)

基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
> func main() {
	router := gin.Default()

	router.GET("/someGet", handle)
	router.POST("/somePost", handle)
	router.PUT("/somePut", handle)
	router.DELETE("/someDelete", handle)
	router.PATCH("/somePatch", handle)
	router.HEAD("/someHead", handle)
	router.OPTIONS("/someOptions", handle)
    
    router.ANY("/any", handle)
    
	router.Run()
}

func handle(context *gin.Context) {
	context.String(http.StatusOK, "hello world")
}

无参数

1
2
3
r.GET("/", func(c *gin.Context) {
    c.String(http.StatusOK, "Who are you?")
})

解析路径参数

1
2
3
4
5
// 匹配 /user/wanglibing
r.GET("/user/:name", func(c *gin.Context) {
	name := c.Param("name")
	c.String(http.StatusOK, "Hello %s", name)
})

获取Query参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 匹配users?name=xxx&role=xxx,role可选
r.GET("/users", func(c *gin.Context) {
	name := c.Query("name")
	role := c.DefaultQuery("role", "teacher")
	c.String(http.StatusOK, "%s is a %s", name, role)
})

#### 获取POST参数

```go
r.POST("/form", func(c *gin.Context) {
	username := c.PostForm("username")
	password := c.DefaultPostForm("password", "000000") // 可设置默认值

	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"password": password,
	})
})
1
> curl http://localhost:9999/form  -X POST -d 'username=wanglibing&password=1234'

Query和POST混合参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// GET 和 POST 混合
r.POST("/posts", func(c *gin.Context) {
	id := c.Query("id")
	page := c.DefaultQuery("page", "0")
	username := c.PostForm("username")
	password := c.DefaultPostForm("username", "000000") // 可设置默认值

	c.JSON(http.StatusOK, gin.H{
		"id":       id,
		"page":     page,
		"username": username,
		"password": password,
	})
})
1
> curl "http://localhost:9999/posts?id=9876&page=7"  -X POST -d 'username=wanglibing&password=1234'

Map参数

1
2
3
4
5
6
7
8
9
r.POST("/post", func(c *gin.Context) {
	ids := c.QueryMap("ids")
	names := c.PostFormMap("names")

	c.JSON(http.StatusOK, gin.H{
		"ids":   ids,
		"names": names,
	})
})
1
> curl -g "http://localhost:9999/post?ids[Jack]=001&ids[Tom]=002" -X POST -d 'names[a]=Sam&names[b]=David'

重定向

1
2
3
4
5
6
7
8
r.GET("/redirect", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "/index")
})

r.GET("/goindex", func(c *gin.Context) {
	c.Request.URL.Path = "/"
	r.HandleContext(c)
})

分组路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
defaultHandler := func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"path": c.FullPath(),
	})
}
// group: v1
v1 := r.Group("/v1")
{
	v1.GET("/posts", defaultHandler)
	v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
	v2.GET("/posts", defaultHandler)
	v2.GET("/series", defaultHandler)
}
1
2
> curl http://localhost:9999/v1/posts
> curl http://localhost:9999/v2/posts

上传文件

单文件

1
2
3
4
5
r.POST("/upload1", func(c *gin.Context) {
	file, _ := c.FormFile("file")
	// c.SaveUploadedFile(file, dst)
	c.String(http.StatusOK, "%s uploaded!", file.Filename)
})

多文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
r.POST("/upload2", func(c *gin.Context) {
	// Multipart form
	form, _ := c.MultipartForm()
	files := form.File["upload[]"]

	for _, file := range files {
		log.Println(file.Filename)
		// c.SaveUploadedFile(file, dst)
	}
	c.String(http.StatusOK, "%d files uploaded!", len(files))
})

HTML模版(Template)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type student struct {
	Name string
	Age  int8
}

r.LoadHTMLGlob("templates/*")

stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/arr", func(c *gin.Context) {
	c.HTML(http.StatusOK, "arr.tmpl", gin.H{
		"title":  "Gin",
		"stuArr": [2]*student{stu1, stu2},
	})
})
1
2
3
4
5
6
7
8
9
<!-- templates/arr.tmpl -->
<html>
<body>
    <p>hello, {{.title}}</p>
    {{range $index, $ele := .stuArr }}
    <p>{{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old</p>
    {{ end }}
</body>
</html>
  • Gin默认使用模板Go语言标准库的模板text/template和html/template,语法与标准库一致,支持各种复杂场景的渲染。
  • 参考官方文档text/templatehtml/template

中间件(Middleware)

定义中间件

1
2
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

使用中间件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}
//Log中间件
func Logger() HandlerFunc {
    return LoggerWithConfig(LoggerConfig{})
}
//Recovery中间件
func Recovery() HandlerFunc {
    return RecoveryWithWriter(DefaultErrorWriter)
}

自定义中间件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func RequestInfos() gin.HandlerFunc {
    return func(context *gin.Context) {
        path := context.FullPath()
        method := context.Request.Method
        fmt.Println("请求Path:", path)
        fmt.Println("请求Method:", method)
    }
}

func main() {

    engine := gin.Default()
    engine.Use(RequestInfos())

    engine.GET("/query", func(context *gin.Context) {
        context.JSON(200, map[string]interface{}{
            "code": 1,
            "msg":  context.FullPath(),
        })
    })
    engine.Run(":9000")
}

context.Next函数

context.Next函数可以将中间件代码的执行顺序一分为二,Next函数调用之前的代码在请求处理之前之前,当程序执行到context.Next时,会中断向下执行,转而先去执行具体的业务逻辑,执行完业务逻辑处理函数之后,程序会再次回到context.Next处,继续执行中间件后续的代码。具体用法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
    engine := gin.Default()
    engine.Use(RequestInfos())
    engine.GET("/query", func(context *gin.Context) {
        fmt.Println(" 中间件的使用方法  ")
        context.JSON(404, map[string]interface{}{
            "code": 1,
            "msg":  context.FullPath(),
        })
    })
    engine.Run(":9000")
}

func RequestInfos() gin.HandlerFunc {
    return func(context *gin.Context) {
        path := context.FullPath()
        method := context.Request.Method
        fmt.Println("请求Path:", path)
        fmt.Println("请求Method:", method)
        context.Next()
        fmt.Println(context.Writer.Status())
    }
}

中间件作用域

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 作用于全局
r.Use(gin.Logger())
r.Use(gin.Recovery())

// 作用于单个路由
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

// 作用于某个组
authorized := r.Group("/")
authorized.Use(AuthRequired())
{
	authorized.POST("/login", loginEndpoint)
	authorized.POST("/submit", submitEndpoint)
}

定义中间件示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()
		// 给Context实例设置一个值
		c.Set("wanglibing", "1111")
		// 请求前
		c.Next()
		// 请求后
		latency := time.Since(t)
		log.Print(latency)
	}
}

热加载调试(Hot Reload)

github.com/codegangsta/gin

1
2
3
4
5
##### 1. 安装
> go get github.com/codegangsta/gin
##### 2. 验证
> gin -h
##### 3. 使用gin替代 go run main.go

github.com/pilu/fresh

1
2
3
##### 1. 安装
> go get -v -u github.com/pilu/fresh
##### 2. 安装好后,只需要将go run main.go命令换成fresh即可。每次更改源文件,代码将自动重新编译(Auto Compile)。

服务端设置跨域访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func Cors() gin.HandlerFunc {
    return func(context *gin.Context) {
        method := context.Request.Method
        origin := context.Request.Header.Get("Origin")
        var headerKeys []string
        for k, _ := range context.Request.Header {
            headerKeys = append(headerKeys, k)
        }
        headerStr := strings.Join(headerKeys, ",")
        if headerStr != "" {
            headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
        } else {
            headerStr = "access-control-allow-origin, access-control-allow-headers"
        }

        if origin != "" {
            context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
            context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域
            context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
            context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
            context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
            context.Header("Access-Control-Max-Age", "172800")
            context.Header("Access-Control-Allow-Credentials", "false")
            context.Set("content-type", "application/json") //// 设置返回格式是json
        }

        if method == "OPTIONS" {
            context.JSON(http.StatusOK, "Options Request!")
        }
        //处理请求
        context.Next()
    }
}

// 设置跨域调用
func main(){
    ...
    app := gin.Default()
    app.Use(Cors())
    ...
}

使用MySQL

安装MySQL驱动

1
> go get "github.com/go-sql-driver/mysql"

引入mysql驱动程序

1
import _ "github.com/go-sql-driver/mysql"

拼接链接字符

1
connStr := "root:12345678@tcp(127.0.0.1:3306)/ginsql"

使用sql.Open创建数据库连接

1
2
3
4
5
db, err := sql.Open("mysql", connStr)
    if err != nil {
        log.Fatal(err.Error())
        return
    }

操作数据库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 创建数据库表
  _, err = db.Exec("create table person(" +
"id int auto_increment primary key," +
"name varchar(12) not null," +
"age int default 1" +
");")
if err != nil {
log.Fatal(err.Error())
return
}

// 向数据库中插入数据
  _, err = db.Exec("insert into person(name,age) "+
"values(?,?);", "Lily", 15)
if err != nil {
log.Fatal(err.Error())
return
} else {
fmt.Println("数据插入成功")
}

// 查询数据库记录
rows, err := db.Query("select id,name,age from person")
if err != nil {
log.Fatal(err.Error())
return
}
scan:
if rows.Next() {
//columns, err := rows.Columns()
//fmt.Println(columns)
person := new(Person)
err = rows.Scan(&person.Id, &person.Name, &person.Age)
if err != nil {
log.Fatal(err.Error())
return
}
fmt.Println(person.Id, person.Name, person.Age)
goto scan
}

使用MongoDB

1

配置工具

介绍

  • encoding/json 标准库中的包,可以处理JSON配置文件,缺点是不能加注释。
  • gcfg 处理INI配置文件。
  • toml 处理TOML配置文件。
  • viper 处理JSON, TOML, YAML, HCL以及Java properties配置文件。

集成Swagger

介绍

安装Swagger

1
> go get github.com/swaggo/swag/cmd/swag

初始化

1
> swag init

以上命令会生成如下三个文件:

  • /docs/docs.go
  • /docs/swagger.json
  • /docs/swagger.yaml

安装gin-swagger

1
2
> go get -u github.com/swaggo/gin-swagger
> go get -u github.com/swaggo/gin-swagger/swaggerFiles

配置swagger

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/swaggo/gin-swagger"                         // gin-swagger middleware
	"github.com/swaggo/gin-swagger/swaggerFiles"            // swagger embed files
    _ "github.com/swaggo/gin-swagger/example/basic/docs"    // docs is generated by Swag CLI, you have to import it.
)

// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host petstore.swagger.io
// @BasePath /v2
func main() {
	router:=gin.Default()
	// use ginSwagger middleware to
	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
	router.Run()
}

controller

1

validator

介绍

  • Github
    Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving.

安装

1
> go get github.com/go-playground/validator/v10
1
import "github.com/go-playground/validator/v10"

Govendor

安装Govendor

1
> go get github.com/kardianos/govendor

创建项目并且 cd 到项目目录中

1
> mkdir -p $GOPATH/src/project && cd "$_"

使用 govendor 初始化项目,并且引入 gin

1
2
> govendor init
> govendor fetch github.com/gin-gonic/gin@v1.3

复制启动文件模板到项目目录中

1
> curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go

启动项目

1
> go run main.go

使用jsoniter编译

Gin 使用 encoding/json 作为默认的 json 包,但是你可以在编译中使用标签将其修改为 jsoniter

1

govendor常用命令

命令 说明
govendor init 生成vendor目录
govendor add +external 将外部依赖一同加入vendor中
govendor update +external 更新外部所有依赖
govendor update $PACKAGE_NAME 更新指定外部依赖

常见问题

安装gin框架报错解决办法

错误描述

1
2
3
4
5
6
7
8
$ go get gopkg.in/gin-gonic/gin.v1
# cd .; git clone https://github.com/ugorji/go /Users/iamwlb/Documents/Workspace/go/src/github.com/ugorji/go
Cloning into '/Users/iamwlb/Documents/Workspace/go/src/github.com/ugorji/go'...
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed
package github.com/ugorji/go/codec: exit status 128

错误原因

这个错误是因为项目太久,tag资源文件太大。

解决办法

1
> git config --global http.postBuffer 524288000

参考

Swagger

工具

gorm


Wanglibing
Wanglibing
Engineer,Lifelong learner