从 Flask 到 Gin —— 读取配置文件
文章目录
本文是 从 Flask 到 Gin 系列的第 3 篇。
配置文件是一个项目不可或缺的内容。在 MJP 的 Flask 版本中,我采用 JSON 格式的配置文件。在 Gin 的实现中,我决定保持这种格式不变。下面是一个简化了的配置文件内容。
配置文件范例
1{
2 "GIN_MODE": "debug",
3 "PATH": "",
4 "ADDRESS": "127.0.0.1:5005",
5 "SECRET_KEY": "YutjgVSPDERGyPayXrXbwsuF_SZWiVmUw3mD4YYD_kY=",
6 "DATABASES": {
7 "DATABASE_URI": "zrong:123456@(127.0.0.1)/data1?charset=utf8mb4&parseTime=True&loc=Local",
8 "DATABASE_BINDS": {
9 "data1": "zrong:123456@(127.0.0.1)/data1?charset=utf8mb4&parseTime=True&loc=Local",
10 "data2": "zrong:123456@(127.0.0.1)/data2?charset=utf8mb4&parseTime=True&loc=Local"
11 }
12 },
13 "REGIONALS": [
14 {
15 "name": "测试服1001",
16 "r": 1001,
17 "bind_key_db": "mjptest"
18 },
19 {
20 "name": "测试服1023",
21 "r": 1023,
22 "bind_key_db": "mjptest"
23 }
24 ]
25}
上面的配置文件命名为 config.json
,保存在项目根目录下。
配置模块封装:config.py
下面是 Flask 项目中,对 config.json
这个配置文件进行处理的模块。模块名称是 config.py
。这个模块的主要功能有下面几个:
- 提供一个
getdir
模块方法,用于返回当前文件夹下相对路径的Path
对象。 - 解析
config.json
,将其保存到模块全局变量中,便于随时使用。这里使用标准库的json
包完成,并封装了一个readjson
方法。 - 封装了一个
getcfg
模块方法,从配置文件中获取变量。 - 封装了一个
getregional
模块方法,根据REGIONAL
中的r
值,获取这个r
对应的配置。
1# -*- coding: utf-8 -*-
2"""
3config.py
4~~~~~~~~~~~~~~~~~~~
5
6初始化 app.config 中的配置
7解析 config.json 配置文件
8提供配置文件相关的读取和写入方法
9"""
10
11import os
12from pathlib import Path
13import json
14
15
16# getdir 使用
17__basedir = None
18
19# 全局变量,用于保存 config.json 载入的配置
20cfg_json = None
21
22regional_list = None
23regional_dict = {}
24regional_ids = []
25
26
27def readjson(filename, basedir=None, throw_error=False):
28 """ 读取一个 json 格式的配置文件
29
30 :param filename: 文件名
31 :param basedir: str
32 :param throw_error: boolean 若值为 True,则当文件不存在的时候抛出异常
33 :returns: 解析后的 dict
34 :rtype: dict
35 """
36 jsonf = getdir(filename, basedir=basedir)
37 if jsonf.exists():
38 return json.loads(jsonf.read_text(encoding='utf-8'), encoding='utf-8')
39 if throw_error:
40 raise FileNotFoundError('%s is not found!' % jsonf.resolve())
41 return {}
42
43
44def writejson(data_dict, filename, basedir=None):
45 """ 将一个 dict 写入成为 json 文件
46
47 :param data_dict: 要写入的配置信息
48 :type data_dict: dict
49 """
50 jsonf = getdir(filename, basedir=basedir)
51 jsonf.write_text(json.dumps(data_dict, ensure_ascii=False, indent=2))
52
53
54def getdir(*args, basedir=None):
55 """ 基于当前项目的运行文件夹,返回一个 pathlib.Path 对象
56 如果传递 basedir,就基于这个 basedir 创建路径
57 """
58 if basedir is not None:
59 return Path(basedir, *args)
60 if __basedir is None:
61 raise ValueError('please set basedir first!')
62 return Path(__basedir, *args)
63
64
65def getcfg(*args, default_value=None, data='cfg_json'):
66 """
67 递归获取 dict 中的值
68 如果不提供 data,默认使用 cfg 中的值
69 注意,getcfg 不仅可用于读取 config.yaml 的值,还可以通过传递 data 用于读取任何字典的值
70 :param args:
71 :param data:
72 :return:
73 """
74 if data is None:
75 return None
76 elif data == 'cfg_json':
77 data = cfg_json
78 if args:
79 if isinstance(data, dict):
80 return getcfg(*args[1:], data=data.get(args[0], default_value))
81 return data
82 return data
83
84
85def init_regionals():
86 global regional_list
87 # 从配置文件中读取 regional 的配置,存储到一个 list 和一个 dict 中
88 regional_list = getcfg('REGIONALS')
89 if not isinstance(regional_list, list) or len(regional_list) == 0:
90 raise ValueError('REGIONAL is unavailable!')
91 for regional in regional_list:
92 r = regional.get('r')
93 if r is None:
94 raise KeyError('REGIONALS 配置必须包含 r key!')
95 regional_ids.append(r)
96 regional_dict[r] = regional
97
98
99def getregional(r):
100 return regional_dict.get(r)
101
102
103def init(basedir=None, initr=True):
104 """ 初始化配置文件
105 :param initr: 是否初始化配置文件中的 initregionals
106 """
107 global __basedir, cfg_json
108 if basedir is None:
109 __basedir = os.getcwd()
110 else:
111 __basedir = basedir
112 cfg_json = readjson('config.json')
113 if initr:
114 init_regionals()
使用方法很简单,导入全局模块 config,调用其封装的方法即可:
1from mjp import config
2
3address = config.getcfg('ADDRESS')
显然,由于 Python 的动态特性,将 config.json
解析后变成一个 dict
,然后处理它,是一件很灵活和轻松的事情。但 Golang 是静态语言,处理起来并没有这么方便。这里我们可以复习一下 从 Flask 到 Gin —— 处理 JSON,config.json
需要映射成 Struct 才更容易使用。
接下来看看怎样在 Golang 中实现和 config.py
模块类似的效果。
使用 viper 读取配置
第一次看到 viper,是因为 Hugo。去年 8 月,我把博客从 Hexo 转到了 Hugo,接着坚定了学习 Golang 的念头。Hugo 使用 viper 也很正常,因为 viper 的作者 spf13 也是 Hugo 的主要作者之一。
我刚使用 Hugo 的时候,就因为它同时支持多种配置文件格式 config.yaml/config.toml/config.json
而感到惊讶(当然,uWSGI 也是支持多种配置文件格式的,所以我可能也并没有那么惊讶😄),我也是在这里第一次接触到了 TOML 格式。
viper 的优势很多(via):
- 为各种配置项设置默认值
- 加载并解析JSON、TOML、YAML 或 Java properties 格式的配置文件
- 可以监视配置文件的变动、重新读取配置文件
- 从环境变量中读取配置数据
- 从远端配置系统中读取数据,并监视它们
- 从命令参数中读取配置
- 从 buffer 中读取
- 调用函数设置配置信息
因为这些优势,我选择了 viper 作为配置文件的读取和解析库。
配置模块封装:config.go
下面是 Gin 项目中,对 config.json
这个配置文件进行处理的模块。模块名称是 config.go
。这个模块的主要功能有下面几个:
- 提供一个
GetDir
函数,用于返回当前文件夹下的相对路径。 - 解析
config.json
,将其内容映射到Config
这个 Struct 中,便于随时使用。 - 封装了
DatabaseBind
函数,从配置文件中获取数据库绑定情况。 - 封装了一个
Regional
函数,根据REGIONAL
中的r
值,获取这个r
对应的Regional
Struct。
1package util
2
3import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path"
8 "path/filepath"
9 "sync"
10
11 "github.com/gin-gonic/gin"
12 "github.com/spf13/viper"
13)
14
15// Databases 定义配置中的 DATABASES 字段
16type Databases struct {
17 URI string `mapstructure:"DATABASE_URI"`
18 Binds map[string]string `mapstructure:"DATABASE_BINDS"`
19}
20
21// Regional 定义一个区服的配置
22type Regional struct {
23 Name string `mapstructure:"name"`
24 R int `mapstructure:"r"`
25 BindKeyDb string `mapstructure:"bind_key_db"`
26}
27
28// String return a string about regional
29func (regional *Regional) String() string {
30 return regional.Name
31}
32
33/*
34Config parse form config.json
35*/
36type Config struct {
37 GinMode string `mapstructure:"GIN_MODE"`
38 Path string `mapstructure:"PATH"`
39 Address string `mapstructure:"ADDRESS"`
40 SecretKey string `mapstructure:"SECRET_KEY"`
41 Databases *Databases `mapstructure:"DATABASES"`
42 Regionals []*Regional `mapstructure:"REGIONALS"`
43}
44
45// String return the config as string
46func (conf *Config) String() string {
47 c := viper.AllSettings()
48 bs, err := json.Marshal(c)
49 if err != nil {
50 fmt.Println(err)
51 }
52 return string(bs)
53}
54
55// Regional return a regional in REGIONALS
56func (conf *Config) Regional(r int) *Regional {
57 return regionals[r]
58}
59
60// DatabaseBind return a database_uri in DATABASES
61func (conf *Config) DatabaseBind(bindKey string) string {
62 return conf.Databases.Binds[bindKey]
63}
64
65var regionals map[int]*Regional
66var conf *Config
67var configOnce sync.Once
68
69// load the config.json and return it
70func load(content string) (*Config, error) {
71 config := &Config{}
72
73 var err error
74
75 viper.SetConfigFile(content)
76 err = viper.ReadInConfig()
77 if err != nil {
78 return nil, err
79 }
80
81 err = viper.Unmarshal(&config)
82 if err != nil {
83 return nil, err
84 }
85 regionals = make(map[int]*Regional)
86 for _, value := range config.Regionals {
87 regionals[value.R] = value
88 }
89
90 return config, nil
91}
92
93// ConfigInstance is a signal Config
94func ConfigInstance() *Config {
95 configOnce.Do(func() {
96 configFile := GetDir("config.json")
97 inst, err := load(configFile)
98 if err != nil {
99 panic(err)
100 }
101 conf = inst
102 if conf.GinMode != "" {
103 gin.SetMode(conf.GinMode)
104 }
105 })
106 return conf
107}
108
109// GetDir return a path in pwd
110func GetDir(elem ...string) string {
111 curPath, _ := filepath.Abs(filepath.Dir(os.Args[0]))
112 args := append([]string{curPath}, elem...)
113 return path.Join(args...)
114}
上面的处理和 Python 有很大的不同。在 config.py
中,所有读入内存的配置文件数据作为一个 dict
存在,要获取其中的值,只需要使用 dict.get
并提供值的名称即可。但是在 config.go
中,我们必须把所有的值以 Struct 的形式进行预先定义和绑定。
看看怎么使用 config.go
:
1package middlewares
2
3import (
4 "mjp/util"
5 "github.com/gin-gonic/gin"
6)
7var config *util.Config
8var logger *util.Logger
9
10func init() {
11 config = util.ConfigInstance()
12 logger = util.GetAppLogger()
13}
14
15func printAddress() {
16 logger.Infof("Addrss: %s", config.Address)
17}
参考
阅读系列所有文章:从 Flask 到 Gin。
- 文章ID:2684
- 原文作者:zrong
- 原文链接:https://blog.zengrong.net/post/flask-to-gin-read-config-file/
- 版权声明:本作品采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可,非商业转载请注明出处(原文作者,原文链接),商业转载请联系作者获得授权。