文章目录
这部分涉及了对于底层服务的初始化和日志器的初始化两部分
// 初始化底层服务,如数据库等 lgConfig := bootstrap.NewConfig("conf/config.yaml") // 初始化日志器 lgLogger := bootstrap.NewLogger()
初始化底层服务
NewConfig
的代码如下
unc NewConfig(confFile string) *config.Configuration { // 如果配置已经生效了,就直接沿用配置 if lgConfig.Conf != nil { return lgConfig.Conf } else { // 初始化配置,包括Configuration结构体和一个同步原语 lgConfig = newLangGoConfig() // 如果用户没有指定配置文件的路径,则使用系统定义的默认路径"conf/config.yaml" if confFile == "" { lgConfig.initLangGoConfig(confFilePath) } else { //否则使用用户给定的配置文件来初始化 lgConfig.initLangGoConfig(confFile) } return lgConfig.Conf } }
函数返回类型
NewConfig
函数接收一个字符串类型,返回一个*config.Configuration
类型,关于这个类型的定义如下
type Configuration struct { App App `mapstructure:"app" json:"app" yaml:"app"` Log Log `mapstructure:"log" json:"log" yaml:"log"` Database []*plugins.Database `mapstructure:"database" json:"database" yaml:"database"` Redis *plugins.Redis `mapstructure:"redis" json:"redis" yaml:"redis"` Minio *plugins.Minio `mapstructure:"minio" json:"minio" yaml:"minio"` Cos *plugins.Cos `mapstructure:"cos" json:"cos" yaml:"cos"` Oss *plugins.Oss `mapstructure:"oss" json:"oss" yaml:"oss"` Local *plugins.Local `mapstructure:"local" json:"local" yaml:"local"` }
*config.Configuration
内部也包含了许多自定义的结构体,由许多自定义类型,后面的mapstructure
标签需对应 config.yaml
中的配置名称
APP
type App struct { Env string `mapstructure:"env" json:"env" yaml:"env"` Port string `mapstructure:"port" json:"port" yaml:"port"` AppName string `mapstructure:"app_name" json:"app_name" yaml:"app_name"` AppUrl string `mapstructure:"app_url" json:"app_url" yaml:"app_url"` }
其对应的配置文件config.yaml
中对应内容为
app: env: prod port: 8888 # 服务端口 app_name: osproxy # 服务名称 app_url: http://127.0.0.1
也就是说,我们首先在App App `mapstructure:"app" json:"app" yaml:"app"`
匹配到app这个配置,获取其中env
,port
,app_name
,app_url
等字段,然后复制给结构体APP
的变量Env
,Port
,AppName
,AppUrl
。
Redis
type Redis struct { Host string `mapstructure:"host" json:"host" yaml:"host"` Port string `mapstructure:"port" json:"port" yaml:"port"` DB int `mapstructure:"db" json:"db" yaml:"db"` Password string `mapstructure:"password" json:"password" yaml:"password"` }
redis: host: 127.0.0.1 # 服务地址 port: 6379 # 服务端口 db: 0 # 库选择 password: 123456 # 密码
对于Redis也是如此,此处对于其他结构体就不在赘述,总的来说,config.Configuration
这个结构体中包括了项目中一些基础服务的配置信息。
newLangGoConfig()函数
func newLangGoConfig() *LangGoConfig { return &LangGoConfig{ Conf: &config.Configuration{}, Once: &sync.Once{}, } } type LangGoConfig struct { Conf *config.Configuration Once *sync.Once }
newLangGoConfig函数用于初始化Configuration结构体和一个同步原语,同步原语sync.Once
只有一个导出的方法,即 Do,该方法接收一个函数参数。在 Do 方法被调用后,该函数将被执行,而且只会执行一次,即使在多个协程同时调用的情况下也是如此,提供一个优雅且并发安全的资源初始化方式。
sync.Once
内部为一个互斥锁,用于对指定函数进行加锁操作,具体源码分析在此
initLangGoConfig()函数
func (lg *LangGoConfig) initLangGoConfig(confFile string) { // 基于并发原语对初始化进行并发控制,针对并发场景下的线程安全 lg.Once.Do( func() { initConfig(lg.Conf, confFile) }, ) }
这里就是对并发原语Once的使用,它使用Do的方式使得initconfig
函数唯一执行。既确保了配置文件的初始化在多线程的环境下只进行一次,也确保了只有在初始化后,才会调用这些配置信息。
// 根据配置文件初始化底层依赖的配置 func initConfig(conf *config.Configuration, confFile string) { pflag.StringVarP(&configPath, "conf", "", filepath.Join(rootPath, confFile), "config path, eg: --conf config.yaml") if !filepath.IsAbs(configPath) { configPath = filepath.Join(rootPath, configPath) } //lgLogger.Logger.Info("load config:" + configPath) fmt.Println("load config:" + configPath) v := viper.New() v.SetConfigFile(configPath) v.SetConfigType("yaml") // 使用viper库读取配置文件 if err := v.ReadInConfig(); err != nil { //lgLogger.Logger.Error("read config failed: ", zap.String("err", err.Error())) // 读取失败并返回日志信息 fmt.Println("read config failed: ", zap.String("err", err.Error())) panic(err) } // 解析配置文件 if err := v.Unmarshal(&conf); err != nil { //lgLogger.Logger.Error("config parse failed: ", zap.String("err", err.Error())) fmt.Println("config parse failed: ", zap.String("err", err.Error())) } // 启动对于文件的监控 v.WatchConfig() // 使用viper及时加载文件的变化 v.OnConfigChange(func(in fsnotify.Event) { //lgLogger.Logger.Info("", zap.String("config file changed:", in.Name)) fmt.Println("", zap.String("config file changed:", in.Name)) defer func() { if err := recover(); err != nil { //lgLogger.Logger.Error("config file changed err:", zap.Any("err", err)) fmt.Println("config file changed err:", zap.Any("err", err)) } }() if err := v.Unmarshal(&conf); err != nil { //lgLogger.Logger.Error("config parse failed: ", zap.String("err", err.Error())) fmt.Println("config parse failed: ", zap.String("err", err.Error())) } }) lgConfig.Conf = conf }
以上代码便是将配置文件conf.yaml载入系统的过程。值得注意的是作者在这里设置了viper的WatchConfig()
函数来实现了配置的热更新。
初始化日志器
NewLogger()
的源码如下
func NewLogger() *LangGoLogger { if lgLogger.Logger != nil { return lgLogger } else { lgLogger = newLangGoLogger() lgLogger.initLangGoLogger(lgConfig.Conf) return lgLogger } }
此处定义的结构体LangGoLogger
和上面的LangGoConfig
差不多,是一个zap库的logger加上一个同步原语。
type LangGoLogger struct { Logger *zap.Logger Once *sync.Once }
接下来的代码也差不多,就是对日志库的logger对象进行初始化,并通过同步原语对其进行控制
// NewLogger 生成新Logger func NewLogger() *LangGoLogger { if lgLogger.Logger != nil { return lgLogger } else { lgLogger = newLangGoLogger() lgLogger.initLangGoLogger(lgConfig.Conf) return lgLogger } } // initLangGoLogger 初始化全局log func (lg *LangGoLogger) initLangGoLogger(conf *config.Configuration) { lg.Once.Do( func() { lg.Logger = initializeLog(conf) }, ) } func initializeLog(conf *config.Configuration) *zap.Logger { // 创建根目录 createRootDir(conf) // 设置日志等级 setLogLevel(conf) if conf.Log.ShowLine { options = append(options, zap.AddCaller()) } // 初始化zap return zap.New(getZapCore(conf), options...) }