gin框架
原创大约 7 分钟
快速开始
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 自定义函数
func pong(c *gin.Context) {
// gin.H可以用map[string]interface{}替代
//c.JSON(http.StatusOK, map[string]interface{}{
// "message": "pong",
//})
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
func main() {
// 创建一个服务实例
r := gin.Default()
// 以匿名函数的方式运行,也可以自定义函数,就像这样
// r.GET("/ping", pong)
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
// 不设置的话默认为8080端口
err := r.Run(":9090")
if err != nil {
return
}
}
路由相关
package main
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"gogin/routine/proto"
"net/http"
)
/**
* 声明参数约束
*
*/
type User struct {
// 如果是 required,uuid 那么中间不能有空格,否则报500异常
// id必须是int类型
Id int `json:"userid" uri:"id" binding:"required"`
Name string `json:"username" uri:"name" binding:"required"`
}
/**
* 路由分组
*
*/
func main() {
routine := gin.Default()
// 会员相关路由
member := routine.Group("/member")
{
member.GET("/index", memberIndex)
member.GET("/level", memberLevel)
member.POST("/register", memberRegister)
member.POST("/login", memberLogin)
member.GET("/:name/:id", memberInfo)
}
// 交易相关路由
trade := routine.Group("/trade")
{
// 带*号会匹配`/*all`后面所有的路径,不管all是什么,都会取出来
trade.GET("/goods/list/:id/*all", goodsList)
trade.GET("/goods/:id/:action", goodsInfo)
trade.GET("/goods/order", goodsOrder)
trade.POST("/payment", payment)
}
err := routine.SetTrustedProxies([]string{"127.0.0.1"})
if err != nil {
return
}
err = routine.Run(":9090")
if err != nil {
return
}
}
/**
* 输出JSON格式
*
*/
func memberLevel(context *gin.Context) {
var data struct {
info string
extra string
}
data.info = "data info"
data.extra = "data extra"
var message struct {
Name string `json:"user"`
Code string
Data struct {
info string
extra string
}
}
message.Name = "lixingyun"
message.Code = "200"
message.Data = data
context.JSON(http.StatusOK, message)
}
/**
* 获取GET方法中的参数
*
*/
func memberIndex(context *gin.Context) {
// username := context.DefaultQuery("username", "lixingyun")
username := context.Query("username")
from := context.Query("from")
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"username": username,
"from": from,
})
}
/**
* 获取POST方法中的参数
*
*/
func memberRegister(context *gin.Context) {
// username := context.DefaultPostForm("username", "lixingyun")
username := context.PostForm("username")
password := context.PostForm("password")
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"username": username,
"password": password,
})
}
/**
* 既获取POST方法中的参数,又获取GET方法中的参数
*
*/
func memberLogin(context *gin.Context) {
username := context.DefaultQuery("username", "lixingyun")
password := context.DefaultQuery("password", "123456")
size := context.DefaultPostForm("size", "10")
offset := context.DefaultPostForm("offset", "1")
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"username": username,
"password": password,
"size": size,
"offset": offset,
})
}
/**
* 获取URL路径中传递的变量
*
*/
func memberInfo(context *gin.Context) {
var user User
// URL路径必须符合前面定义的匹配约束
if err := context.ShouldBindUri(&user); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
// 从url中取出变量
context.JSON(http.StatusOK, gin.H{
"id": user.Id,
"name": user.Name,
})
}
/**
* 输出PureJSON格式
* 它会将特殊的HTML字符替换为对应的Unicode字符,比如将 原样输出为
*
*/
func goodsList(context *gin.Context) {
// 从url中取出变量
id := context.Param("id")
// 拿到后面所有的路径内容
all := context.Param("all")
context.PureJSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "success",
"data": fmt.Sprintf("id = %s, all = %s", id, all),
})
}
func goodsInfo(context *gin.Context) {
// 从url中取出变量
id := context.Param("id")
action := context.Param("action")
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "success",
"data": fmt.Sprintf("id = %s, action = %s", id, action),
})
}
/**
* 输出ProtoBuf格式
*
*/
func goodsOrder(context *gin.Context) {
user := &proto.User{
Id: "123456",
Roles: []string{"admin", "user"},
}
context.ProtoBuf(http.StatusOK, user)
}
// 输出String格式
func payment(context *gin.Context) {
var message struct {
// 配置json tag,字段输出时被转换为user
Name string `json:"user"`
Code string `json:"pwd"`
}
message.Name = "lixingyun"
message.Code = "123456"
// 将struct转换成string
bytes, _ := json.Marshal(message)
context.String(http.StatusOK, string(bytes))
}
表单验证
package main
import (
"fmt"
"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"
entranslations "github.com/go-playground/validator/v10/translations/en"
zhtranslations "github.com/go-playground/validator/v10/translations/zh"
"net/http"
"reflect"
"strings"
)
var trans ut.Translator
/**
* 登录表单
*
*/
type LoginForm struct {
// 给字段指定各种tag,它规定了在传递各种类型参数时的参数名
Username string `form:"username" json:"username" binding:"required,min=6,max=18"`
Password string `form:"password" json:"password" binding:"required,min=6,max=18"`
}
/**
* 注册表单
*
*/
type RegisterForm struct {
Username string `form:"username" json:"username" binding:"required,min=6,max=18"`
Password string `form:"password" json:"password" binding:"required,min=6,max=18"`
RePassword string `form:"repassword" json:"repassword" binding:"required,min=6,max=18,eqfield=Password"` // 跨字段验证
Email string `form:"email" json:"email" binding:"required,email"`
Phone string `form:"phone" json:"phone" binding:"required,e164"`
Age int `form:"age" json:"age" binding:"required,gte=18"`
Gender int `form:"gender" json:"gender" binding:"required,oneof=0 1"`
}
func replacePrefixName(fields map[string]string) map[string]string {
rsp := map[string]string{}
for field, err := range fields {
// 找到 LoginForm.username 中 . 位置,然后取出它后面的内容
res := strings.Index(field, ".") + 1
// temp的内容为json字符串
temp := field[res:]
switch temp {
case "username":
rsp["用户名"] = err
case "password":
rsp["密码"] = err
case "repassword":
rsp["确认密码"] = err
case "email":
rsp["邮箱"] = err
case "phone":
rsp["手机号"] = err
case "age":
rsp["年龄"] = err
case "gender":
rsp["性别"] = err
}
}
return rsp
//var result = make(map[string]string)
//fmt.Println(rsp)
//// 再将字段名替换为中文
//// rsp内容为:map[password:password长度必须至少为6个字符 repassword:repassword必须等于Password]
//for k, v := range rsp {
// switch k {
// case "username":
// result["用户名"] = strings.Replace(rsp[v], "username", "用户名", 1)
// case "password":
// result["密码"] = strings.Replace(rsp[v], "password", "密码", 1)
// case "repassword":
// result["确认密码"] = strings.Replace(rsp[v], "repassword", "确认密码", 1)
// case "email":
// result["邮箱"] = strings.Replace(rsp[v], "email", "邮箱", 1)
// case "phone":
// result["手机号"] = strings.Replace(rsp[v], "phone", "手机号", 1)
// }
//}
//return result
}
/**
* InitTranslator 验证器注册翻译器
*
*/
func InitTranslator(locale string) (err error) {
// 修改默认的validator引擎属性
if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json中tag的函数
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
// 创建中文翻译器
cnTrans := zh.New()
// 创建英文翻译器
enTrans := en.New()
// 第一个参数是备用语言环境,后面是应该支持的语言环境
uni := ut.New(enTrans, cnTrans, enTrans)
if trans, ok = uni.GetTranslator(locale); ok {
switch locale {
case "en":
err = entranslations.RegisterDefaultTranslations(validate, trans)
if err != nil {
return
}
case "zh":
err = zhtranslations.RegisterDefaultTranslations(validate, trans)
if err != nil {
return
}
default:
err = entranslations.RegisterDefaultTranslations(validate, trans)
if err != nil {
return
}
}
} else {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
}
return err
}
/**
* 路由分组
*
*/
func main() {
// 初始化翻译器
if err := InitTranslator("zh"); err != nil {
fmt.Println("翻译器初始化失败")
return
}
routine := gin.Default()
// 会员相关路由
member := routine.Group("/member")
{
member.POST("/login", memberLogin)
member.POST("/register", memberRegister)
}
_ = routine.SetTrustedProxies([]string{"127.0.0.1"})
err := routine.Run(":9090")
if err != nil {
return
}
}
/**
* 登录验证
*
*/
func memberLogin(context *gin.Context) {
if err := context.ShouldBind(new(LoginForm)); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
context.JSON(http.StatusBadRequest, gin.H{
"msg": err.Error(),
})
return
}
context.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"msg": replacePrefixName(errs.Translate(trans)),
})
return
}
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "登录成功",
})
}
/**
* 注册验证
*
*/
func memberRegister(context *gin.Context) {
var registerForm RegisterForm
if err := context.ShouldBind(®isterForm); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
context.JSON(http.StatusBadRequest, gin.H{
"msg": err.Error(),
})
return
}
context.JSON(http.StatusBadRequest, gin.H{
"code": http.StatusBadRequest,
"msg": replacePrefixName(errs.Translate(trans)),
})
return
}
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "注册成功",
})
}
切面AOP
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func main() {
//// 手动调用gin服务
//routine := gin.New()
//// gin默认使用logger和recovery中间件
//routine.Use(gin.Logger(), gin.Recovery())
routine := gin.Default()
//// 全路由调用
//routine.Use(Logger())
// 根路由
root := routine.Group("/")
// 切入Logger()服务,将服务应用于 / 路由的所有子路由
root.Use(Logger())
{
root.GET("/index", memberLogin)
}
_ = routine.SetTrustedProxies([]string{"127.0.0.1"})
err := routine.Run(":9090")
if err != nil {
return
}
}
func memberLogin(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "ok",
})
}
/**
* 自定义切面服务(本质上就是一个闭包)
*
*/
func Logger() gin.HandlerFunc {
return func(context *gin.Context) {
now := time.Now()
context.Set("start", now)
// 打印请求头和token
token := context.GetHeader("token")
for k, v := range context.Request.Header {
if k == "Token" {
token = v[0]
}
fmt.Println("请求头:", k, v, token)
}
for i := 0; i < 10; i++ {
time.Sleep(10 * time.Millisecond)
}
if token != "lixingyun" {
context.JSON(http.StatusUnauthorized, gin.H{
"code": http.StatusUnauthorized,
"msg": "token错误",
})
/**
* 光靠return不能终止,它仍然会在Postman中输出如下内容:
* {
* "code": 401,
* "msg": "token错误"
* }{
* "code": 200,
* "msg": "ok"
* }
*
* 光靠Abort()也无法终止,它仍然会在控制台打印出后续的内容:
*
* 执行状态: 401
* 执行耗时: 106.172773ms
*
* 必须通过Abort()配合return的方式才能彻底终止后续逻辑的执行,在Postman和控制台都输出正确的内容
*/
context.Abort()
return
}
status := context.Writer.Status()
fmt.Println("执行状态:", status)
// 执行后续逻辑
context.Next()
end := time.Since(now)
fmt.Println("执行耗时:", end)
}
}
优雅退出
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"os"
"os/signal"
"syscall"
)
func main() {
routine := gin.Default()
routine.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": any("hello world"),
})
})
_ = routine.SetTrustedProxies([]string{"127.0.0.1"})
// 优雅退出
go func() {
_ = routine.Run(":9090")
}()
// 接收到退出信号
quitSignal := make(chan os.Signal)
signal.Notify(quitSignal, syscall.SIGINT, syscall.SIGTERM)
<-quitSignal
// 后续逻辑
fmt.Println("关闭服务...")
fmt.Println("服务关闭")
}
让程序在命令行启动,然后强制杀死,测试是否执行后续逻辑。
在Goland的Terminal
界面中输入如下命令。
> cd ~/workspace-go/gogin/quit
> go build main.go
# windows下执行main.exe,macos或linux执行main即可
> main
# 运行时按`ctrl + c`强制终止,即可看到如下输出。
^C关闭服务...
服务关闭
需要注意的是,如果使用kill -9
命令,那么程序什么信号都收不到,因此只能使用kill
命令。
感谢支持
更多内容,请移步《超级个体》。