Jager / viper配置示例

Created Fri, 17 Sep 2021 00:00:00 +0000 Modified Tue, 07 May 2024 14:11:16 +0800
1947 Words

“github.com/spf13/viper"是一个支持读取toml,yaml, ini, json,hcl等格式配置文件的golang库, 本文通过使用该库配合"github.com/spf13/pflag"和"github.com/fsnotify/fsnotify"库实现从配置文件和启动参数对服务进行配置, 并实现监听配置文件的实时改动,从而可实现不停服更新配置

flags.go

package flags

import (
	"fmt"
	"github.com/jageros/hawox/attribute"
	"github.com/jageros/hawox/contextx"
	"github.com/jageros/hawox/logx"
	"github.com/jageros/hawox/mysql"
	"github.com/jageros/hawox/redis"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	clientv3 "go.etcd.io/etcd/client/v3"
	"log"
	"strconv"
	"strings"
)

var (
	Options *Option // 全局存储
)

// 命令行启动参数和配置文件统一的key,避免多次书写出错,命令行中直接使用即可,配置文件中,点隔开代表分级
var (
	//conf.d
	keyConfig = "conf.d"
	//server
	keyId   = "server.id"
	keyMode = "server.mode"
	// log
	keyLogDir     = "log.dir"
	keyLogCaller  = "log.caller"
	keyLogRequest = "log.request"
	// http
	keyHttpIp   = "http.ip"
	keyHttpPort = "http.port"
	// rpc
	keyRpcIp   = "rpc.ip"
	keyRpcPort = "rpc.port"
	// mysql
	keyMysqlAddr = "mysql.addr"
	keyMysqlUser = "mysql.user"
	keyMysqlPwd  = "mysql.password"
	keyMysqlDB   = "mysql.database"
	// mongo
	keyMongoAddr = "mongo.addr"
	keyMongoUser = "mongo.user"
	keyMongoPwd  = "mongo.password"
	keyMongoDB   = "mongo.database"
	// etcd
	keyEtcdAddr     = "etcd.addrs"
	keyEtcdUser     = "etcd.user"
	keyEtcdPassword = "etcd.password"
	//redis
	keyRedisAddr     = "redis.addrs"
	keyRedisDB       = "redis.db"
	keyRedisUser     = "redis.user"
	keyRedisPassword = "redis.password"
	//queue
	keyQueueType = "queue.type"
	keyQueueAddr = "queue.addrs"
	//frontend
	keyFrontendAddr = "frontend.addr"
)

// Option 配置数据结构体
type Option struct {
	ID         int    // 服务id
	AppName    string // 服务名称
	Mode       string // 模式
	Configfile string // 配置文件路径

	// log配置
	LogDir     string // log目录
	LogCaller  bool   // 是否开启记录输出日志的代码文件行号
	LogRequest bool   // 是否开启http请求的日志记录

	// http服务配置
	HttpIp   string
	HttpPort int

	// rpc服务配置
	RpcIp   string
	RpcPort int

	// mysql 配置
	MysqlAddr string
	MysqlUser string
	MysqlPwd  string
	MysqlDB   string

	// MongoDB 配置
	MongoAddr string
	MongoUser string
	MongoPwd  string
	MongoDB   string

	// etcd 配置
	EtcdAddrs  string // 地址集,用;隔开
	EtcdUser   string
	EtcdPasswd string

	// redis配置
	RedisAddrs  string // 数据库地址集,用;隔开
	RedisDBNo   int    // 数据库编号
	RedisUser   string
	RedisPasswd string

	// 消息队列配置
	QueueType  string // 消息队列类型
	QueueAddrs string // 地址

	// websocket注册进etcd的地址(外网地址),将返回给客户端
	FrontendAddr string
}

// AppID 返回字符串型的appid
func AppID() string {
	return strconv.Itoa(Options.ID)
}

// Source 可用于服务注册中的key或者日志中的source可区别开不同的服务或同个服务不同的结点
func Source() string {
	return fmt.Sprintf("%s/%d", Options.AppName, Options.ID)
}

// defaultOption 返回程序默认的配置项数据
func defaultOption() *Option {
	op := &Option{
		ID:       1,
		Mode:     "debug",
		HttpIp:   "127.0.0.1",
		HttpPort: 8888,
		RpcIp:    "127.0.0.1",
		RpcPort:  9999,
	}
	return op
}

// logPath log文件输出的路径,根据AppName来命名
func logPath() string {
	if strings.HasSuffix(Options.LogDir, "/") {
		return fmt.Sprintf("%s%s.log", Options.LogDir, Options.AppName)
	}
	return fmt.Sprintf("%s/%s.log", Options.LogDir, Options.AppName)
}

// load 从viper中获取解析出来的参数初始化option中的字段
func (op *Option) load(v *viper.Viper) {
	//server
	Options.ID = v.GetInt(keyId)
	Options.Mode = v.GetString(keyMode)
	//log
	Options.LogDir = v.GetString(keyLogDir)
	Options.LogCaller = v.GetBool(keyLogCaller)
	Options.LogRequest = v.GetBool(keyLogRequest)
	//http
	Options.HttpIp = v.GetString(keyHttpIp)
	Options.HttpPort = v.GetInt(keyHttpPort)
	//rpc
	Options.RpcIp = v.GetString(keyRpcIp)
	Options.RpcPort = v.GetInt(keyRpcPort)
	//mysql
	Options.MysqlAddr = v.GetString(keyMysqlAddr)
	Options.MysqlUser = v.GetString(keyMysqlUser)
	Options.MysqlPwd = v.GetString(keyMysqlPwd)
	Options.MysqlDB = v.GetString(keyMysqlDB)
	//mongo
	Options.MongoAddr = v.GetString(keyMongoAddr)
	Options.MongoUser = v.GetString(keyMongoUser)
	Options.MongoPwd = v.GetString(keyMongoPwd)
	Options.MongoDB = v.GetString(keyMongoDB)
	//etcd
	Options.EtcdAddrs = v.GetString(keyEtcdAddr)
	Options.EtcdUser = v.GetString(keyEtcdUser)
	Options.EtcdPasswd = v.GetString(keyEtcdPassword)
	//redis
	Options.RedisAddrs = v.GetString(keyRedisAddr)
	Options.RedisUser = v.GetString(keyRedisUser)
	Options.RedisPasswd = v.GetString(keyRedisPassword)
	//queue
	Options.QueueType = v.GetString(keyQueueType)
	Options.QueueAddrs = v.GetString(keyQueueAddr)
	//frontend
	Options.FrontendAddr = v.GetString(keyFrontendAddr)
	if Options.FrontendAddr == "" {
		Options.FrontendAddr = fmt.Sprintf("127.0.0.1:%d", Options.HttpPort)
	}

	// 根据配置中的mode设置log的等级
	var logLevel string
	switch Options.Mode {
	case "release":
		logLevel = logx.InfoLevel
	case "info":
		logLevel = logx.InfoLevel
	case "warn":
		logLevel = logx.WarnLevel
	case "error":
		logLevel = logx.ErrorLevel
	case "panic":
		logLevel = logx.PanicLevel
	default:
		logLevel = logx.DebugLevel
	}

	// 配置日志
	var logOpfs []logx.Option
	if Options.LogCaller {
		logOpfs = append(logOpfs, logx.SetCaller())
	}
	if Options.LogRequest {
		logOpfs = append(logOpfs, logx.SetRequest())
	}
	source := Source()
	if source != "" {
		logOpfs = append(logOpfs, logx.SetSource(source))
	}
	if Options.LogDir != "" {
		logOpfs = append(logOpfs, logx.SetFileOut(logPath()))
	}

	// 初始化日志
	logx.Init(logLevel, logOpfs...)
}

func DefaultRedis(option *Option) {
	option.RedisAddrs = "127.0.0.1:6379"
}

func DefaultClusterRedis(addrs ...string) func(*Option) {
	addr := "127.0.0.1:7001;127.0.0.1:7002;127.0.0.1:7003;127.0.0.1:7004;127.0.0.1:7005;127.0.0.1:7006"
	if len(addrs) > 0 {
		addr = strings.Join(addrs, ";")
	}
	return func(option *Option) {
		option.RedisAddrs = addr
	}
}

func DefaultMysql(option *Option) {
	option.MysqlAddr = "127.0.0.1:3306"
	option.MysqlUser = "root"
	option.MysqlPwd = "QianYin@66"
	option.MysqlDB = "hawox"
}

func DefaultMongo(option *Option) {
	option.MongoAddr = "127.0.0.1:27017"
	option.MongoDB = "attribute"
}

func DefaultEtcd(option *Option) {
	option.EtcdAddrs = "127.0.0.1:2379"
}

func DefaultQueue(option *Option) {
	option.QueueType = "kafka"
	option.QueueAddrs = "127.0.0.1:9092"
}

// Parse 解析配置, 启动参数有传参则忽略配置文件
func Parse(name string, opts ...func(*Option)) (ctx contextx.Context, wait func()) {
	Options = defaultOption()

	// 调用该接口时可以改变默认值,但优先顺序为 启动参数 > 配置文件 > 接口传参 > 默认值
	for _, optf := range opts {
		optf(Options)
	}

	// 启动参数:
	//conf.d
	pflag.String(keyConfig, Options.Configfile, "Config file path")
	// server
	pflag.Int(keyId, Options.ID, "Application Id")
	pflag.String(keyMode, Options.Mode, "Server mode, default: debug, optional:debug/test/release")
	// log
	pflag.String(keyLogDir, Options.LogDir, "Log dir")
	pflag.Bool(keyLogCaller, Options.LogCaller, "log caller")
	pflag.Bool(keyLogRequest, Options.LogRequest, "log request")
	// http
	pflag.String(keyHttpIp, Options.HttpIp, "Http listen ip")
	pflag.Int(keyHttpPort, Options.HttpPort, "HTTP server port")
	// rpc
	pflag.String(keyRpcIp, Options.RpcIp, "Rpc listen ip")
	pflag.Int(keyRpcPort, Options.RpcPort, "Rpc server port")
	// mysql
	pflag.String(keyMysqlAddr, Options.MysqlAddr, "mysql addr")
	pflag.String(keyMysqlUser, Options.MysqlUser, "mysql user")
	pflag.String(keyMysqlPwd, Options.MysqlPwd, "mysql password")
	pflag.String(keyMysqlDB, Options.MysqlDB, "mysql database")
	// mongo
	pflag.String(keyMongoAddr, Options.MongoAddr, "MongoDB addr")
	pflag.String(keyMongoUser, Options.MongoUser, "MongoDB user")
	pflag.String(keyMongoPwd, Options.MongoPwd, "MongoDB password")
	pflag.String(keyMongoDB, Options.MongoDB, "MongoDB database")
	// etcd
	pflag.String(keyEtcdAddr, Options.EtcdAddrs, "Etcd addrs, use ; split")
	pflag.String(keyEtcdUser, Options.EtcdUser, "Etcd user")
	pflag.String(keyEtcdPassword, Options.EtcdPasswd, "Etcd password")
	// redis
	pflag.String(keyRedisAddr, Options.RedisAddrs, "Redis addrs, use ; split")
	pflag.Int(keyRedisDB, Options.RedisDBNo, "Redis DB NO.")
	pflag.String(keyRedisUser, Options.RedisUser, "Redis user")
	pflag.String(keyRedisPassword, Options.RedisPasswd, "Redis password")
	// queue
	pflag.String(keyQueueType, Options.QueueType, "queue type, optional: nsq/kafka")
	pflag.String(keyQueueAddr, Options.QueueAddrs, "queue addrs, use ; split")
	// frontend
	pflag.String(keyFrontendAddr, Options.FrontendAddr, "frontend addr")

	pflag.Parse()

	v := viper.New()
	err := v.BindPFlags(pflag.CommandLine) // 绑定命令行参数
	if err != nil {
		log.Fatalf("v.BindPFlags err: %v", err)
	}

	// 获取命令行配置文件路径参数
	path := v.GetString(keyConfig)

	var dir, fileName, fileType string
	// 配置文件路径不为空则读取配置文件, 即命令行有传入配置文件路径时
	if path != "" {
		// 获取配置文件的后缀名
		strs := strings.Split(path, ".")
		if len(strs) < 2 {
			log.Fatal("错误的配置文件路径")
		}

		fileType = strs[len(strs)-1]
		switch fileType {
		// 支持的文件类型yaml、json、 toml、hcl, ini
		case "yaml", "yml", "json", "toml", "hcl", "ini":
			fPath := strings.Replace(path, "."+fileType, "", -1)
			strs = strings.Split(fPath, "/")
			if len(strs) < 2 {
				fileName = strs[0]
				dir = "./"
			} else {
				fileName = strs[len(strs)-1]
				dir = strings.Join(strs[:len(strs)-1], "/")
			}

			//设置读取的配置文件
			v.SetConfigName(fileName)
			//添加读取的配置文件路径
			v.AddConfigPath(dir)
			//设置配置文件类型
			v.SetConfigType(fileType)

			if err := v.ReadInConfig(); err != nil {
				log.Fatalf("v.ReadInConfig err: %v", err)
			}

			// 这部分代码为监听配置文件是否有更新,有更新则重新解析配置文件,重新解析也无法覆盖命令行参数
			// 可根据需求启用
			// 依赖库: "github.com/fsnotify/fsnotify"
			////设置监听回调函数
			//v.OnConfigChange(func(e fsnotify.Event) {
			//  logx.Sync() // 重新初始化时先同步
			//	Options.load(v)
			//	if onReloadConfigFn != nil {
			//		onReloadConfigFn()
			//	}
			//})
			////开始监听
			//v.WatchConfig()

		default:
			// 其他类型抛出错误
			log.Fatal("错误的配置文件类型")
		}
	}

	Options.AppName = name
	// 从viper解析出来的参数对option中的字段赋值
	Options.load(v)

	// 结合信号和context实现的类似与errgroup的一个库,可以根据自己项目需求设计自己的接口
	// 不可直接照搬,这里存在其他依赖库暂时未开源,所以属于伪代码
	ctx, _ = contextx.Default()

	wait = func() {
		err := ctx.Wait()
		logx.Infof("Application Stop With: %v", err)
		logx.Sync()
	}

	return
}