gin和gorm进阶功能(2) | 青训营笔记

261 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

使用viper读取YML配置

配置

go get github.com/spf13/viper

优点

yml文件的好处,天然的树状结构,一目了然,实质上跟properties是差不多的;并且后面可以增加热部署的功能。

代码

config/config.go

初始化配置,以及小例子

package config  
  
import (  
   "github.com/spf13/viper"  
   "go_douyin/global/variable"   "log"   "os")  
  
func Init() {  
   variable.Config = viper.New()  
   //获取项目的执行路径  
   var err error  
   variable.BasePath, err = os.Getwd()  
   if err != nil {  
      log.Fatal(err)  
   }  
   variable.Config.AddConfigPath(variable.BasePath + "\\config") //设置读取的文件路径  
   variable.Config.SetConfigName("config")                       //设置读取的文件名  
   variable.Config.SetConfigType("yml")                          //设置文件的类型  
   //尝试进行配置读取  
   if err := variable.Config.ReadInConfig(); err != nil {  
      log.Fatal(err)  
   }  
   ////打印文件读取出来的内容:  
   //fmt.Println(config)   //fmt.Println(config.GetString("redis.host"))  
}

接口限流

配置

go get github.com/juju/ratelimit

原理

令牌桶算法的原理也比较简单,我们可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。 系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

另一种说法:

  • 系统以恒定的速率产生令牌,然后将令牌放入令牌桶中
  • 令牌桶有一个容量,当令牌桶满了的时候,再向其中放入的令牌就会被丢弃
  • 每次一个请求过来,需要从令牌桶中获取一个令牌,假设有令牌,那么提供服务;假设没有令牌,那么拒绝服务

代码

middleware/ratelimt/ratelimit.go

package ratelimit  
  
import (  
   "github.com/gin-gonic/gin"  
   "github.com/juju/ratelimit"   "net/http")  
  
//限流频率为每秒5次请求(分别代表每秒产生的令牌数量和令牌桶大小)  
var rl = ratelimit.NewBucketWithRate(5, 5)  
  
func RateLimiter() gin.HandlerFunc {  
   return func(c *gin.Context) {  
      if rl.TakeAvailable(1) < 1 {  
         c.String(http.StatusForbidden, "请勿频繁访问")  
         c.Abort()  
         return  
      }  
      c.Next()  
   }  
}

router.go

// 社交组:关注,粉丝相关信息  
v2 := router.Group("/douyin/relation")  
{  
   v2.Use(ratelimit.RateLimiter())  
   v2.POST("action", followController.FollowAction)  
}

日志功能

配置

go get go.uber.org/zap
go get -u gopkg.in/natefinch/lumberjack.v2

知识点

ZapLog是Go语言中一个用于记录日志的库。以下是一些常用的ZapLog函数:

  • Debug(msg string, fields ...zap.Field) : 以debug级别记录日志。
  • Info(msg string, fields ...zap.Field) : 以info级别记录日志。
  • Warn(msg string, fields ...zap.Field) : 以warn级别记录日志。
  • Error(msg string, fields ...zap.Field) : 以error级别记录日志。
  • DPanic(msg string, fields ...zap.Field) : 以DPanic级别记录日志。当DPanic级别日志记录时会导致程序崩溃。
  • Panic(msg string, fields ...zap.Field) : 以Panic级别记录日志。当Panic级别日志记录时会导致程序崩溃。
  • Fatal(msg string, fields ...zap.Field) : 以Fatal级别记录日志。当Fatal级别日志记录时会导致程序崩溃。

ZapLog还支持Fields类型的附加字段,可以通过传递给上述函数的fields参数来使用。这些附加字段可以用来记录调用者的文件名、行号等信息,并且可以更好的进行日志过滤和查询。

代码

utils/zap_factory.go

package zap_factory  
  
import (  
   "go.uber.org/zap"  
   "go.uber.org/zap/zapcore"   "go_douyin/global/variable"   "gopkg.in/natefinch/lumberjack.v2"   "log"   "time")  
  
// 系统运行日志钩子函数  
// 1.单条日志就是一个结构体格式,本函数拦截每一条日志,您可以进行后续处理,例如:推送到阿里云日志管理面板、ElasticSearch 日志库等  
  
func ZapLogHandler(entry zapcore.Entry) error {  
  
   // 参数 entry 介绍  
   // entry  参数就是单条日志结构体,主要包括字段如下:  
   //Level      日志等级  
   //Time       当前时间  
   //LoggerName  日志名称  
   //Message    日志内容  
   //Caller     各个文件调用路径  
   //Stack      代码调用栈  
  
   //这里启动一个协程,hook丝毫不会影响程序性能,  
   go func(paramEntry zapcore.Entry) {  
      //fmt.Println(" GoSkeleton  hook ....,你可以在这里继续处理系统日志....")  
      //fmt.Printf("%#+v\n", paramEntry)   }(entry)  
   return nil  
}  
  
func CreateZapFactory(entry func(zapcore.Entry) error) *zap.Logger {  
  
   // 获取程序所处的模式:  开发调试 、 生产  
   appDebug := variable.Config.GetBool("AppDebug")  
  
   // 判断程序当前所处的模式,调试模式直接返回一个便捷的zap日志管理器地址,所有的日志打印到控制台即可  
   if appDebug == true {  
      if logger, err := zap.NewDevelopment(zap.Hooks(entry)); err == nil {  
         return logger  
      } else {  
         log.Fatal("创建zap日志包失败,详情:" + err.Error())  
      }  
   }  
  
   // 以下才是 非调试(生产)模式所需要的代码  
   encoderConfig := zap.NewProductionEncoderConfig()  
  
   timePrecision := variable.Config.GetString("Logs.TimePrecision")  
   var recordTimeFormat string  
   switch timePrecision {  
   case "second":  
      recordTimeFormat = "2006-01-02 15:04:05"  
   case "millisecond":  
      recordTimeFormat = "2006-01-02 15:04:05.000"  
   default:  
      recordTimeFormat = "2006-01-02 15:04:05"  
  
   }  
   encoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {  
      enc.AppendString(t.Format(recordTimeFormat))  
   }  
   encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder  
   encoderConfig.TimeKey = "created_at" // 生成json格式日志的时间键字段,默认为 ts,修改以后方便日志导入到 ELK 服务器  
  
   var encoder zapcore.Encoder  
   switch variable.Config.GetString("Logs.TextFormat") {  
   case "console":  
      encoder = zapcore.NewConsoleEncoder(encoderConfig) // 普通模式  
   case "json":  
      encoder = zapcore.NewJSONEncoder(encoderConfig) // json格式  
   default:  
      encoder = zapcore.NewConsoleEncoder(encoderConfig) // 普通模式  
   }  
  
   //写入器  
   fileName := variable.BasePath + variable.Config.GetString("Logs.GoDouYinLogName")  
   lumberJackLogger := &lumberjack.Logger{  
      Filename:   fileName,                                  //日志文件的位置  
      MaxSize:    variable.Config.GetInt("Logs.MaxSize"),    //在进行切割之前,日志文件的最大大小(以MB为单位)  
      MaxBackups: variable.Config.GetInt("Logs.MaxBackups"), //保留旧文件的最大个数  
      MaxAge:     variable.Config.GetInt("Logs.MaxAge"),     //保留旧文件的最大天数  
      Compress:   variable.Config.GetBool("Logs.Compress"),  //是否压缩/归档旧文件  
   }  
   writer := zapcore.AddSync(lumberJackLogger)  
   // 开始初始化zap日志核心参数,  
   //参数一:编码器  
   //参数二:写入器  
   //参数三:参数级别,debug级别支持后续调用的所有函数写日志,如果是 fatal 高级别,则级别>=fatal 才可以写日志  
   zapCore := zapcore.NewCore(encoder, writer, zap.InfoLevel)  
   return zap.New(zapCore, zap.AddCaller(), zap.Hooks(entry), zap.AddStacktrace(zap.WarnLevel))  
}

global/variable/variable.go

var (  
   // 全局日志指针  
   ZapLog *zap.Logger
)

config/config.go

func Init() {  
   //3.初始化全局日志句柄,并载入日志钩子处理函数  
   variable.ZapLog = zap_factory.CreateZapFactory(zap_factory.ZapLogHandler)  
}

main.go

func main() {  
   // 初始化全局变量  
   variable.Init()  
   // 注意初始化数据库  
   database.SqlClient()  
   // 4.初始化配置,读取配置  
   config.Init()  
  
   variable.ZapLog.Info("程序正在运行")  
   r := router.SetupRouter()  
   r.Run(":8081")  
}

展望

后续还有许多细节可以添加和完善