跳至主要內容

Gin

LiCheng大约 3 分钟

Gin

介绍

  • 2022/9/23

安装

  • go get -u github.com/gin-gonic/gin
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

优雅停机


var R = gin.New()

// Init 初始化
func Init() {
	//WxText()
	//其他初始化,必须在此之上

	var port = ":" + YmlConfig["port"].(string)
	var url = "http://" + util.GetTargetMaskIp("24")[0] + port
	log.Println("Gin: 初始化,地址: " + url)

	//下面都是优雅停机方式
	srv := &http.Server{
		Addr:    port,
		Handler: R,
	}

	go func() {
		// 服务连接
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()
	// 等待中断信号以优雅地关闭服务器(设置 10 秒的超时时间)
	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)
	<-quit
	log.Println("关闭服务器...")
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("服务器关闭:", err)
	}
	log.Println("服务器退出")
}

中间件

异常处理

package middleware

import (
	"api/entity"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"runtime/debug"
)

// Recover 全局异常处理器
func Recover(c *gin.Context) {
	defer func() {
		if r := recover(); r != nil {
			debug.PrintStack() //打印错误堆栈信息
			c.JSON(http.StatusOK, entity.Fail(errorToString(r)))
			c.Abort() //终止后续接口调用,不加的话,还会继续接口后续代码
		}
	}()
	c.Next()
}

// NewServiceError 使用关键字 panic 抛出异常,否则无法识别到
func NewServiceError(text string) error {
	return &ServiceError{text}
}

type ServiceError struct {
	s string
}

func (e *ServiceError) Error() string {
	return e.s
}

// recover错误,转string
func errorToString(r interface{}) string {
	switch v := r.(type) {
	case *ServiceError:
		return v.Error()
	default:
		return fmt.Sprint(v)
	}
}


cors跨域

package config

import (
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

func init() {
	initCors()
}
func initCors() {
	log.Println("跨域处理器!")
	R.Use(Cors())
}

// Cors 跨域配置
func Cors() gin.HandlerFunc {
	handlerFunc := cors.New(cors.Config{
		AllowMethods:     []string{"*"},
		AllowHeaders:     []string{"Authentication"}, //此处设置非默认之外的请求头(自定义请求头),否则会出现跨域问题,必须制定名称
		AllowAllOrigins:  true,
		AllowCredentials: true,
		MaxAge:           24 * time.Hour,
	})
	return handlerFunc
}

缓存redis

package config

import (
	"gin-gin/entity"
	"github.com/gin-gonic/gin"
	"go-util/util"
	"log"
	"net/http"
	"time"
)

var CachePath []string

// 需要配合Redis进行使用!
func init() {
	//initCache()
}

func initCache() {
	log.Println("缓存处理器!")
	R.Use(Cache())
}

// Cache Redis缓存中间件
func Cache() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := util.GinUrlPath()
		for _, i := range CachePath {
			//需要缓存该返回结果!
			if i == path {
				cacheStr := RedisGetString(path)
				log.Println("触发缓存:" + path)
				if cacheStr == "" {
					c.Next()
					return
				}
				var r entity.R
				util.JsonToObj(cacheStr, &r)
				c.JSON(http.StatusOK, r)
				c.Abort() //终止访问
				break
			}
		}
	}
}

// CacheData 添加需要缓存的路径,默认30秒
func CacheData(k, v string) {
	isExistPath := false
	for _, s := range CachePath {
		if s == k {
			log.Println("触发缓存:" + k)
			isExistPath = true
			break
		}
		isExistPath = false
	}
	if !isExistPath {
		CachePath = append(CachePath, k)
	}
	log.Println(CachePath)

	RedisSetExpiration(k, v, 30*time.Second)
}

// CachePathUrlData 根据路径进行缓存 /file/list?xx=aa => /file/list
func CachePathUrlData(v string) {
	CacheData(util.GinUrlPath(), v)
}

// GetParamsUrlPahData 根据路径+参数进行缓存 /file/list?xx=aa => /file/list?xx=aa
func GetParamsUrlPahData(v string) {
	CacheData(util.GinParamsUrlPah(), v)
}

认证处理器

package config

import (
	"context"
	"gin-gin/common"
	"gin-gin/entity"
	"github.com/gin-gonic/gin"
	"go-util/util"
	"log"
	"net/http"
	"strconv"
	"strings"
)

// Authorization 请求头key
var Authorization = "Authorization"

// 排除路径
var excludePath []interface{}

// go的加载特性,是从字母顺序进行加载文件的!
func init() {
	//initAuth()
}

func initAuth() {
	log.Println("认证处理器!")
	excludePath = YmlConfig["excludePath"].([]interface{})
	R.Use(Auth())
}

// Auth 认证拦截器
func Auth() gin.HandlerFunc {
	return func(c *gin.Context) {
		//保存到上下文中
		util.GinSetContext(c)

		path := util.GinUrlPath()
		//log.Println(path)
		//排除路径处理
		for _, item := range excludePath {
			// 排除路径例如: /test/** , 排除匹配 /test/ 的所有拦截
			var ePath = item.(string)
			//检查是否存在**号
			if strings.HasSuffix(ePath, "**") || path == ePath {
				var prefix = strings.ReplaceAll(ePath, "*", "")
				if strings.HasPrefix(path, prefix) {
					return
				}
			}

		}

		//登入认证
		query := c.GetHeader(Authorization)
		if query == "" {
			log.Println("账号没有登入!")
			c.JSON(http.StatusOK, entity.Fail("没有登入!"))
			c.Abort() //终止访问
			return
		}

		//权限认证-还可以做其他的认证!
		var user entity.User
		userId, _ := strconv.ParseInt(util.GetTokenParse(query), 10, 8)
		common.Db.Where(entity.User{Id: userId}).First(&user)
		if user.Deleted == 1 {
			c.JSON(http.StatusOK, entity.Fail("用户已禁用!"))
			c.Abort() //终止访问
			return
		}
		Ctx = context.WithValue(Ctx, util.UserIdKey, userId)
		c.Next()
		//拦截后
	}
}

工具类

对象封装

package entity

type R struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}

// OkData 成功的数据返回
func OkData(data interface{}) R {
	return R{Code: 0, Data: data}
}

// OkMsg 成功的消息返回
func OkMsg(msg string) R {
	return R{Code: 0, Msg: msg}
}

// Fail 失败的消息返回
func Fail(msg string) R {
	return R{Code: -1, Msg: msg}
}