这是我参与「第五届青训营 」伴学笔记创作活动的第 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")
}
展望
后续还有许多细节可以添加和完善