gin是个小而精的web开发框架
官方文档
安装
go get -u github.com/gin-gonic/gin
最简单的起手代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
// r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
get、post、put等http方法
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/someGet", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.POST("/somePost", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.PUT("/somePut", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.DELETE("/someDelete", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.PATCH("/somePatch", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.HEAD("/someHead", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.OPTIONS("/someOptions", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.Run() // listen and serve on 0.0.0.0:8080
// r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
路由分组
package main
import (
"github.com/gin-gonic/gin"
)
func goodList(c *gin.Context) {
c.JSON(200, gin.H{
"message": "goodList",
})
}
func goodsDetail(c *gin.Context) {
c.JSON(200, gin.H{
"message": "goodsDetail",
})
}
func createGoods(c *gin.Context) {
c.JSON(200, gin.H{
"message": "createGoods",
})
}
func main() {
router := gin.Default()
goodsGroup := router.Group("/goods")
{
goodsGroup.GET("/list", goodList)
goodsGroup.GET("/1", goodsDetail)
goodsGroup.GET("/add", createGoods)
}
router.Run() // listen and serve on 0.0.0.0:8080
// r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
带参数的url
package main
import (
"github.com/gin-gonic/gin"
)
func goodList(c *gin.Context) {
c.JSON(200, gin.H{
"message": "goodList",
})
}
func goodsDetail(c *gin.Context) {
id := c.Param("id")
action := c.Param("action")
c.JSON(200, gin.H{
"message": "goodsDetail",
"id": id,
"action": action,
})
}
func createGoods(c *gin.Context) {
c.JSON(200, gin.H{
"message": "createGoods",
})
}
func main() {
router := gin.Default()
goodsGroup := router.Group("/goods")
{
goodsGroup.GET("", goodList)
goodsGroup.GET("/:id/:action", goodsDetail)
// goodsGroup.GET("/:id/*action", goodsDetail)
goodsGroup.POST("", createGoods)
}
router.Run() // listen and serve on 0.0.0.0:8080
// r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
获取路由分组的参数
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func main() {
router := gin.Default()
router.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.Status(404)
return
}
c.JSON(http.StatusOK, gin.H{
"name": person.Name,
"id": person.ID,
})
})
router.Run() // listen and serve on 0.0.0.0:8080
// r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
http://localhost:8080/sd1/8bebdad9-2829-4849-a011-c19128061822
获取get和post参数
get、post和get&post
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func welcome(c *gin.Context) {
firstName := c.DefaultQuery("firstname", "bobby")
lastName := c.Query("lastname")
c.JSON(http.StatusOK, gin.H{
"first_name": firstName,
"last_name": lastName,
})
}
func formPost(c *gin.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(http.StatusOK, gin.H{
"message": message,
"nick": nick,
})
}
func main() {
router := gin.Default()
//get请求
router.GET("/welcome", welcome)
//post请求
router.POST("/form", formPost)
//post & get
router.POST("/postsd", func(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.PostForm("message")
fmt.Printf("id: %s,page: %s,name: %s,message: %s", id, page, name, message)
})
router.Run() // listen and serve on 0.0.0.0:8080
// r.Run(":8083") // listen and serve on 0.0.0.0:8080
}
http://localhost:8080/welcome?firstname=dlkjf&lastname=sdfljk
JSON、ProtoBuf 渲染
json渲染、Prootobuf渲染和purejson渲染
package main
import (
"net/http"
"GolangStudy/Introduction/proto"
"github.com/gin-gonic/gin"
)
func moreJSON(c *gin.Context) {
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "bobby"
msg.Message = "测试json"
msg.Number = 20
c.JSON(http.StatusOK, msg)
}
func returnProto(c *gin.Context) {
user := &proto.Teacher{
Name: "bobby",
Course: []string{"python", "go", "java"},
}
c.ProtoBuf(http.StatusOK, user)
}
func main() {
router := gin.Default()
router.GET("/moreJSON", moreJSON)
router.GET("/someProtoBuf", returnProto)
router.GET("/pureJSON", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
router.Run()
}
表单验证
Gin使用 go-playground/validator 验证参数
文档
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// 绑定为json
type LoginForm struct {
User string `form:"user" json:"user" xml:"user" binding:"required,min=3,max=10"` //min最短长度,max最大长度
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "登录成功",
})
})
router.Run()
}
注册表单的验证
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// 绑定为json
type LoginForm struct {
User string `form:"user" json:"user" xml:"user" binding:"required,min=3,max=10"` //min最短长度,max最大长度
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
type SignUpForm struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}
func main() {
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "登录成功",
})
})
router.POST("/signup", func(c *gin.Context) {
var signForm SignUpForm
if err := c.ShouldBind(&signForm); err != nil {
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusBadRequest, gin.H{
"msg": "注册成功",
})
})
router.Run()
}
表单验证错误翻译成中文
package main
import (
"fmt"
"net/http"
"reflect"
"strings"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
// 绑定为json
type LoginForm struct {
User string `form:"user" json:"user" xml:"user" binding:"required,min=3,max=10"` //min最短长度,max最大长度
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
type SignUpForm struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}
func removeTopStruct(fileds map[string]string) map[string]string {
rsp := map[string]string{}
for field, err := range fileds {
rsp[field[strings.Index(field, ".")+1:]] = err
}
return rsp
}
func InitTrans(locale string) (err error) {
//修改gin的validator引擎属性,实现定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
//注册一个获取json的tag的自定义方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
zhT := zh.New() //中文翻译器
enT := en.New() //英文翻译器
//第一个是备用的,后面的是支持的语言环境
uni := ut.New(enT, zhT, enT)
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
switch locale {
case "en":
en_translations.RegisterDefaultTranslations(v, trans)
case "zh":
zh_translations.RegisterDefaultTranslations(v, trans)
default:
en_translations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
var trans ut.Translator
func main() {
if err := InitTrans("zh"); err != nil {
fmt.Println("初始化翻译器错误")
return
}
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
} else {
c.JSON(http.StatusBadRequest, gin.H{
"error": removeTopStruct(errs.Translate(trans)),
})
}
fmt.Println(err.Error())
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "登录成功",
})
})
router.POST("/signup", func(c *gin.Context) {
var signForm SignUpForm
if err := c.ShouldBind(&signForm); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
} else {
c.JSON(http.StatusBadRequest, gin.H{
"error": removeTopStruct(errs.Translate(trans)),
})
}
fmt.Println(err.Error())
return
}
c.JSON(http.StatusBadRequest, gin.H{
"msg": "注册成功",
})
})
router.Run()
}
中间件
gin.New()可以添加中间件;gin.Default()默认启动方式,包含 Logger、Recovery 中间件
添加中间件
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func MyLogger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
c.Set("example", "123456")
//让原本该执行的逻辑继续执行
c.Next()
end := time.Since(t)
fmt.Printf("耗时:%V\n", end)
status := c.Writer.Status()
fmt.Println("状态", status)
}
}
func main() {
router := gin.New()
//使用logger和recovery中间件
router.Use(MyLogger())
router.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.Run()
}
通过c.abort()实现中止中间件的执行(通过return是不能终止的,因为中间件都存储在中间件队列中,当前中间的中止会继续调用后续的中间件,直到调用完成。c.abort()会将中间件队列的索引值改为math.MaxInt8/2实现不调用后续中间件)
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func MyLogger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
c.Set("example", "123456")
//让原本该执行的逻辑继续执行
c.Next()
end := time.Since(t)
fmt.Printf("耗时:%V\n", end)
status := c.Writer.Status()
fmt.Println("状态", status)
}
}
func TokenRequired() gin.HandlerFunc {
return func(c *gin.Context) {
var token string
for k, v := range c.Request.Header {
if k == "X-Token" {
token = v[0]
}
fmt.Println(k, v, token)
}
if token != "bobby" {
c.JSON(http.StatusUnauthorized, gin.H{
"msg": "未登录",
})
c.Abort()
}
c.Next()
}
}
func main() {
router := gin.New()
//使用logger和recovery中间件
router.Use(TokenRequired())
router.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
router.Run()
}
gin加载html文件
html路径可以使用绝对路径和相对路径(推荐),使用相对路径需要先go build后运行,主要是因为直接通过编译器执行生成的可执行文件放置于系统的临时文件夹中并不抱存在当前项目的路径下,所以会出现找不到html文件。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//制定加载好相对路径
router.LoadHTMLFiles("templates/index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "mooc",
})
})
router.Run()
}
加载多html文件
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//制定加载好相对路径
//加载二级目录
router.LoadHTMLGlob("templates/**/*")
// router.LoadHTMLFiles("templates/index.tmpl", "templates/goods.html")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "mooc",
})
})
router.GET("/goods/list", func(c *gin.Context) {
c.HTML(http.StatusOK, "goods/list.html", gin.H{
"name": "微服务开发",
})
})
router.GET("/users/list", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/list.html", gin.H{
"name": "微服务开发",
})
})
router.Run()
}
优雅退出
自定义监听结束信号,方便完成关闭服务器等后续操作
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/gin-gonic/gin"
)
func main() {
//优雅推出,当我们关闭程序后需要做的处理
//微服务 启动之前或者启动之后必须做一件事:将当前的ip地址和端口号注册到注册中心
//我们当前的服务停止之后并没有告知注册中心
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "pong",
})
})
go func() {
router.Run()
}()
//接受信号
quit := make(chan os.Signal)
//syscall.SIGINT ctrl+c
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
//处理后续的逻辑
fmt.Println("关闭server中。。。")
fmt.Println("注册服务。。。")
}