洞察探索open banking如何通过小程序容器技术助力金融企业实现数据安全和数字化转型
1291
2022-10-13
第24关 web层开发-gin基础项目架构
逆着风,大步冲,输了也有收获。 ——佚名
第24关 web层开发-gin基础项目架构
24-1 新建项目和目录结构构建24-2 go高性能日志库 - zap使用24-3 zap的文件输出24-4-5 集成zap和理由初始到gin的启动过程24-6-7 gin调用grpc服务24-8 配置文件 - viper24-9 viper的配置环境开发环境和生产环境隔离24-10 viper集成到gin的web服务中
24-1 新建项目和目录结构构建
本小节我们将来通过go语言结合gin,为我们的用户服务提供对外的HTTP的接口,我们go语言相对的功能比我们底层的功能会更多一些。
新建项目,并规划好目录结构。 新建user-web,这个目录里边的文件夹会多一些。 然后给user-web新建一个叫api的目录,业务的调用逻辑会写在这里边,对应到Python里边的handler是一样的, 然后给user-web新建一个叫config的目录,主要是用于存放相关的配置信息, 然后给user-web新建一个叫forms的目录,主要是用来做表单验证的, 然后给user-web新建一个叫global的目录,主要是用于存放全局的变量, 然后给user-web新建一个叫initialize的目录,主要是用于启动gin的一些初始化的工作, 然后给user-web新建一个叫middlewares的目录,主要是用于我们自定义的中间件, 然后给user-web新建一个叫proto的目录,主要是用来存放我们的proto文件, 然后给user-web新建一个叫router的目录,主要是用来存放我们路由, 然后给user-web新建一个叫utils的目录,主要是用来存放公用的函数, 然后给user-web新建一个叫validator的目录,主要是用来存放表单验证 自定义的 验证规则, 然后新建 main.go文件。
这就是大致的目录结构。
后边如果有改动的话,或者说有新文件需要加入进来,具体用到的时候会再来给它配置。
这就是目录架构。
24-2 go高性能日志库 - zap使用
本小节介绍非常流行也是性能非常高的go语言的日志库叫zap。 安装和基本使用:go get -u go.uber.org/zapzap_test/main.go
package mainimport ( "go.uber.org/zap")func main() { logger, _ := zap.NewProduction() // 生产环境 defer logger.Sync() // flushes buffer, if any url := " sugar := logger.Sugar() sugar.Infow("failed to fetch URL", // Structured context as loosely typed key-value pairs. "url", url, "attempt", 3, ) sugar.Infof("Failed to fetch URL: %s", url)}
看看怎么使用它的。
每一行代码是什么意思?第8行:logger, _ := zap.NewProduction() 是用于生产环境的。
与之相对的是测试环境logger, _ := zap.NewDevelopment() // 测试环境。
我们点进 NewProduction 源码看一下:
我们点进 NewDevelopment 源码看一下:
分解问题,分解任务,各个击破。
NewProduction 和 NewDevelopment 无非就是一些配置的区别而已。
比如 NewDevelopment : ta的日志等级Level是 DebugLevel 级别, 它的日志输出格式是console,
比如NewProduction : 它的日志等级Level是 InfoLevel 级别, 它的日志输出格式是json。
第10行:defer logger.Sync() 刷新缓存, 也就是它在执行最后在return之前把defer logger.Sync()执行一遍,把缓存刷到指定的地方。
第12行: 这里边有一个Sugar,在真正操作日日志之前,它的翻译是糖, 如果我们使用Sugar这样的实例的话,它的使用会非常的简单,近乎于直接使用fmt.Println()。
可以看到官方文档里边给了修改有两种方法,分别是:sugar.Infow 和 sugar.Infof。 这两种方法的作用是什么? 有什么区别?
sugar.Infow 在输出的时候会有个msg,
然后
类似于dict,key和value中间用逗号隔开,再来一个这里边的 key和value:
sugar.Infof: 格式化输出,跟fmt.Printf()差不多。
运行上述main.go的代码: 输出如下:{"level":"info","ts":1537430179.916213,"caller":"zap_test/main.go:13","msg":"failed to fetch URL","url":"to fetch URL: 的,输出的格式是json。
打印了level,日志等级,
打印了ts,时间戳,
打印了caller,是哪个函数调用的,
打印了msg,我们具体定义的信息,
然后后面是我们指定的url是什么。
第二行对应的输出是sugar.Infof的。
然后
图方便,对性能要求没有到极致,使用Sugared Logger,
对性能要求到极致,使用Logger,看一下Logger是如何使用的。
然后再看代码就有数了:
package mainimport ( "go.uber.org/zap")func main() { logger, _ := zap.NewProduction() // 生产环境 zap.NewDevelopment() defer logger.Sync() // flushes buffer, if any url := " logger.Info("failed to fetch URL", zap.String("url", url), zap.Int("nums", 3), )}
这就是zap包的简单用法。
24-3 zap的文件输出
本小节我们来讲解如何将zap的日志输出到文件当中。
代码1:
package mainimport ( "go.uber.org/zap" "time")func NewLogger()(*zap.Logger,error){ cfg := zap.NewProductionConfig() cfg.OutputPaths = []string{ "./mylog.log", "stderr", "stdout", } return cfg.Build()}func main() { // logger, _ := zap.NewProduction() // 生产环境 logger,err := NewLogger() if err != nil{ panic(err) } sugar := logger.Sugar() defer sugar.Sync() url := " sugar.Info("failed to fetch URL", zap.String("url",url), zap.Int("attempt",3), zap.Duration("backoff",time.Second), )}
然后
如果想让"./mylog.log"生效,即跟当前的main.go在同级的目录下,你得这样做: 你得先用打开terminal, 然后进入到当前的main.go的目录下, 执行go build main.go 没问题再执行 ./main.go
这样"./mylog.log"的路径才会跟当前的main.go在同级的目录下。
关键就在使用 zap.NewProductionConfig()。
这就是zap的文件输出的配置。
24-4-5 集成zap和理由初始到gin的启动过程
mxshop-api/user-web/api/user.go
package apiimport ( "github.com/gin-gonic/gin" )func GetUserList(ctx *gin.Context){ // 发连接 // 取数据 // 返回数据 zap.S().Debug("获取用户列表页")}
mxshop-api/user-web/initialize/logger.go
package initializeimport "go.uber.org/zap"func InitLogger(){ logger,_ := zap.NewDevelopment() zap.ReplaceGlobals(logger)}
mxshop-api/user-web/initialize/router.go
package initializeimport ( "github.com/gin-gonic/gin" router2 "mxshop-api/user-web/router")func Routers() *gin.Engine{ Router := gin.Default() ApiGroup := Router.Group("/v1") router2.InitUserRouter(ApiGroup) return Router}
mxshop-api/user-web/router/user.go
package routerimport ( "github.com/gin-gonic/gin" "mxshop-api/user-web/api")func InitUserRouter(Router *gin.RouterGroup){ UserRouter := Router.Group("user") { UserRouter.GET("list",api.GetUserList) }}
mxshop-api/user-web/main.go
package mainimport ( "fmt" "go.uber.org/zap" "mxshop-api/user-web/initialize")func main(){ port := 8031 // 初始化 logger initialize.InitLogger() // 初始化 routers Router := initialize.Routers() zap.S().Debugf("启动服务器,端口:%d",port) if err := Router.Run(fmt.Sprintf(":%d",port));err != nil{ zap.S().Panic("启动失败:",err.Error()) }}
然后启动main.go
你可能会遇到这样的问题:【解决问题的通用逻辑之前的关卡已经讲烂了,方法用对了,解决问题很简单】
go get github.com/gin-gonic/gin@v1.7.4
gin调用grpc服务
本小节我们来完成获取用户列表的web接口。
接口的测试都是在yapi里面完成的。
关于yapi,之前已经配置好了:
responsetype UserResponse struct { Id int32 `json:"id"` NickName string `json:"name"` //BirthDay time.Time `json:"birthday"` BirthDay string `json:"birthday"` Gender string `json:"gender"` Mobile string `json:"mobile"`}
代码2:
package apiimport ( "fmt" "github.com/gin-gonic/gin" "go.uber.org/zap" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "mxshop-api/user-web/global/response" proto "mxshop-api/user-web/proto" "net/ "time")func HandleGrpcErrorToHttp(err error,c *gin.Context){ // 将 grp c的 code 转化成 if err != nil { if e,ok := status.FromError(err);ok{ switch e.Code() { case codes.NotFound: c.JSON( "msg":e.Message(), }) case codes.Internal: c.JSON( "msg":"内部错误", }) case codes.InvalidArgument: c.JSON( "msg":"参数错误", }) default: c.JSON( "msg":"其他错误", }) } return } }}func GetUserList(ctx *gin.Context){ // 发连接 ip := "127.0.0.1" port := 50051 // 拨号连接用户 grpc 服务 userConn,err := grpc.Dial(fmt.Sprintf("%s:%d",ip,port),grpc.WithInsecure()) if err != nil { zap.S().Errorw("[GetUserList]连接【用户服务失败】", "msg",err.Error(), ) } // 生成 grpc 的 client 并调用接口 userSrvClient := proto.NewUserClient(userConn) rsp , err := userSrvClient.GetUserList(context.Background(),&proto.PageInfo{ Pn: 0, PSize: 0, }) if err != nil{ zap.S().Errorw("[GetUserList] 查询 【用户列表】失败") HandleGrpcErrorToHttp(err,ctx) return } result := make([]interface{},0) for _,value := range rsp.Data{ //data := make(map[string]interface{}) user := response.UserResponse{ Id: value.Id, NickName: value.NickName, //BirthDay: time.Unix(int64(value.BirthDay),0), BirthDay:time.Time(time.Unix(int64(value.BirthDay),0)).Format("2006-01-02"), Gender: value.Gender, Mobile: value.Mobile, } result = append(result,user) } ctx.JSON( // 取数据 // 返回数据 zap.S().Debug("获取用户列表页")}
此时代码3:
package responseimport ( "fmt" "time")type JsonTime time.Timefunc (j JsonTime) MarshalJSON()([]byte,error){ var stmp = fmt.Sprintf("\"%s\"",time.Time(j).Format("2006-01-02")) return []byte(stmp),nil}type UserResponse struct { Id int32 `json:"id"` NickName string `json:"name"` //BirthDay time.Time `json:"birthday"` //BirthDay string `json:"birthday"` BirthDay JsonTime `json:"birthday"` Gender string `json:"gender"` Mobile string `json:"mobile"`}
此时代码4:
package apiimport ( "fmt" "github.com/gin-gonic/gin" "go.uber.org/zap" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "mxshop-api/user-web/global/response" proto "mxshop-api/user-web/proto" "net/ "time")func HandleGrpcErrorToHttp(err error,c *gin.Context){ // 将 grp c的 code 转化成 if err != nil { if e,ok := status.FromError(err);ok{ switch e.Code() { case codes.NotFound: c.JSON( "msg":e.Message(), }) case codes.Internal: c.JSON( "msg":"内部错误", }) case codes.InvalidArgument: c.JSON( "msg":"参数错误", }) default: c.JSON( "msg":"其他错误", }) } return } }}func GetUserList(ctx *gin.Context){ // 发连接 ip := "127.0.0.1" port := 50051 // 拨号连接用户 grpc 服务 userConn,err := grpc.Dial(fmt.Sprintf("%s:%d",ip,port),grpc.WithInsecure()) if err != nil { zap.S().Errorw("[GetUserList]连接【用户服务失败】", "msg",err.Error(), ) } // 生成 grpc 的 client 并调用接口 userSrvClient := proto.NewUserClient(userConn) rsp , err := userSrvClient.GetUserList(context.Background(),&proto.PageInfo{ Pn: 0, PSize: 0, }) if err != nil{ zap.S().Errorw("[GetUserList] 查询 【用户列表】失败") HandleGrpcErrorToHttp(err,ctx) return } result := make([]interface{},0) for _,value := range rsp.Data{ //data := make(map[string]interface{}) user := response.UserResponse{ Id: value.Id, NickName: value.NickName, //BirthDay: time.Unix(int64(value.BirthDay),0), //BirthDay:time.Time(time.Unix(int64(value.BirthDay),0)).Format("2006-01-02"), BirthDay: response.JsonTime(time.Unix(int64(value.BirthDay),0)), Gender: value.Gender, Mobile: value.Mobile, } result = append(result,user) } ctx.JSON( // 取数据 // 返回数据 zap.S().Debug("获取用户列表页")}
24-8 配置文件 - viper
本小节来介绍go语言中的配置文件的管理库叫viper , viper 它实际上是一个非常方便,非常简单的配置文件的管理库。
配置文件熟悉格式比如说JSON,比如说YAML等等一系列的配置文件。
这里边我们选择YAML作为go语言的配置文件。
这里边我们选择YAML的配置文件格式,主要是基于YAML: 第一是它的接受度比较高,第二比如说大家在网上去搜资料,发现它的资料会比较多, 选择YAML作为我们的配置文件,并不是说YAML它是最好的一种配置文件。
在这里就选择YAML来作为我们系统的配置文件。
viper 它实际上是一个库,它能够读取各种配置文件,也就是说它支持的不光是YAML配置文件, 我们选择 viper 主要基于它的一个特性: 第一它是可以设置默认值, 第二它几乎支持所有主流的配置文件的格式, 第三就是它可以实时监控我们配置文件的改动,比如说我现在要改一个配置文件,很多时候我们并不希望去重启我们的系统才生效,用了 viper 它就能够监听我们配置文件的变动。
还有它就是从我们的环境变量中去读取一些配置,还有它可以从远程系统中去读取一些配置,比如说 etcd 、Consul,读取并监听配置文件的变化, 还有就是从命令行中它也可以读取配置文件, 从buffer也可以读取配置文件, 也可以显示配置值等等,
我们可以看到它的特性是比较多的,所以说在这里边就基于viper来完成我们配置文件的读写。
YAML教程: 官方仓库: 我们把它解析成我们内部的一个struct来使用,这样的话它使用起来比较方便。 第二个就是 viper 它是很容易就支持将 YAML 的配置文件直接映射成struct。
我们需要知道的是如何从它里边去读取配置文件,并把它显示出来。
上菜【写代码】:yaml
name: 'user-web'mysql: host: '127.0.0.1' port: 3306
main.go
package mainimport ( "fmt" "github.com/spf13/viper")type ServerConfig struct { ServiceName string `mapstructure:"name"` Port int `mapstructure:"port"`}func main() { v := viper.New() //文件的路径如何设置 v.SetConfigFile("config.yaml") if err := v.ReadInConfig(); err != nil { panic(err) } serverConfig := ServerConfig{} if err := v.Unmarshal(&serverConfig); err != nil { panic(err) } fmt.Println(serverConfig) fmt.Printf("%V\n", v.Get("name"))}
基于 viper 读取出来文件,再来把变量给它读取出来。
如何来读取文件?
配置很简单,
viper.New() viper 有个方法叫New,
New返回的是 Viper 它的指针,
然后我们拿到它之后,第二步就是我们给它设置文件的名称,这个文件的名称我们如何来设置的,我们这样设置v.SetConfigFile("viper_test/ch01/config.yaml"),
文件路径其实在goland/pycharm是一个问题,就是解释器不一定能够找到它。
实际上在前面我们当时也遇到过这个问题,就是我们使用go语言来运行它的时候,这个文件的名称,它的路径成了一个头疼的问题。
认识问题—>解决问题:
工作目录实际上就是图中框起来的部分。
你想获取相对路径,实际上就是以图中框起来的部分开始的。
当然,图中框起来的部分可以手动修改。
我们知道了为什么会出现路径找不到的问题,知道了之后后面再出现就明白怎么解决了。 总之 运行方法如下,而不是在编辑器里运行:
//cd 到xxx.go的当前目录,然后执行以下命令go build xxx.go./xxx
拿到数据:
有了上面的认知,那么:
【在编辑器中运行也可以,将这行代码改成这样v.SetConfigFile("viper_test/ch01/config.yaml")】
viper 给我们提供了功能,可以直接把YAML配置文件给它映射成我们的 struct,
go语言其实几乎很多东西都可以把它映射成struct,
如何给它配置json tag,它就能将我YAML文件里的 key 取出来放到 struct 的字段里边来?
认识一下mapstructure。 你要把你配置文件里边的 name 所对应的值给我放到 struct 的 Name中,就这么配置即可。
看图说话:
这就是将配置文件映射成struct的配置指令。
24-9 viper的配置环境开发环境和生产环境隔离
注意:YAML文件里的空格是2个空格。
代码1:
name: 'user-web2'mysql: host: '127.0.0.1' port: 3306
代码2:
name: 'user-web'mysql: host: '127.0.0.2' port: 3308
代码3:
package mainimport ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" "time")//如何将线上和线下的配置文件隔离//不用改任何代码而且线上和线上的配置文件能隔离开type MysqlConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"`}type ServerConfig struct { ServiceName string `mapstructure:"name"` MysqlInfo MysqlConfig `mapstructure:"mysql"`}func GetEnvInfo(env string) bool { viper.AutomaticEnv() return viper.GetBool(env) //刚才设置的环境变量 想要生效 我们必须得重启goland}func main() { debug := GetEnvInfo("MXSHOP_DEBUG") configFilePrefix := "config" configFileName := fmt.Sprintf("viper_test/ch02/%s-pro.yaml", configFilePrefix) if debug { configFileName = fmt.Sprintf("viper_test/ch02/%s-debug.yaml", configFilePrefix) } v := viper.New() //文件的路径如何设置 v.SetConfigFile(configFileName) if err := v.ReadInConfig(); err != nil { panic(err) } serverConfig := ServerConfig{} if err := v.Unmarshal(&serverConfig); err != nil { panic(err) } fmt.Println(serverConfig) fmt.Printf("%v", v.Get("name")) //viper的功能 - 动态监控变化 v.WatchConfig() v.OnConfigChange(func(e fsnotify.Event) { fmt.Println("config file channed: ", e.Name) _ = v.ReadInConfig() _ = v.Unmarshal(&serverConfig) fmt.Println(serverConfig) }) time.Sleep(time.Second * 300)}
讲解嵌套的配置。
如何将线上和线下的配置文件隔离?
试想一下,如果我现在只有一个配置文件的话,就意味着我每次要上线我都得改,改来改去其实是很麻烦的,而且极容易出错。
所以,我们一般的做法就是它做两个配置文件,这个配置文件,比如说它放到线上去,放到线上去运行,那么它就使用线上的配置文件, 如果它是一个我们本地的环境,它就使用一个本地的测试文件。
所以我们的需求就是: 不用改代码,线上和线下的配置文件能隔离开。这也是常见的需求。如何做呢?
一般情况下都是在我们的电脑当中给它配置一个环境变量了。
比如在Windows下边:
给它设置为1,表示是本地开启,然后0本地不开启,
比如macOS下面:
./.bash_profile然后追加 export MXSHOP_DEBUG=truesource ./.bash_profileecho $MXSHOP_DEBUG*/
给它设置为true,表示是本地开启,然后false本地不开启,
如果这样设置的话,viper 能不能读到我的环境变量?
可以的,但是得退出goland重新进来。
注意把%d改成%v。
问题来了,mac环境下配置的环境变量在goland中没有生效。 解决办法: 或者参考 viper集成到gin的web服务中
这小节代码量有点大。需要理解为什么要把viper集成到gin的web服务中? viper主要是把yaml文件中的内容映射到go语言的结构体当中。 这样我们该配置信息就只需到yaml文件中改就可以了。 跟配置相关的内容放到config层, 跟全局变量相关的内容放到global层, 跟初始化相关的内容放到initialize层。 分层的目的就是解耦,同时也是不把鸡蛋放到同一个篮子里,方便自己维护。
咱们想极端点,我啥都不用分层,我就只用一个main.go文件完成业务逻辑,也是可以的,只不过代码量太大,导致main.go的肚子臃肿肥大,不方便维护。你要记忆哪一行的哪一块代码大概是什么配置,方便自己检索,但是后期你有添加了配置,你又得重新记忆,甚至你不需要记忆,你需要采用穷举法,一行一行地快速查找。 这个场景就好像你需要找一个文件,你在电脑上全盘搜索,电脑用的是穷举法,时间效率可想而知。
好,现在分层,分盘,分门别类,你大概知道这个文件在D盘的某个文件夹下,然后来到这个文件夹下搜索,时间效率是不是比你穷举法要高得多。
分层就是分解,将大问题分解,即使出现了问题,我们快速定位到某个小问题,针对性解决就行。是一种方法,也是一种思想。
有了这块之后,上代码;
user-web/api/user.go
package apiimport ( "fmt" "github.com/gin-gonic/gin" "go.uber.org/zap" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "mxshop-api/user-web/global" "mxshop-api/user-web/global/response" proto "mxshop-api/user-web/proto" "net/ "time")func HandleGrpcErrorToHttp(err error, c *gin.Context) { // 将 grp c的 code 转化成 if err != nil { if e, ok := status.FromError(err); ok { switch e.Code() { case codes.NotFound: c.JSON(gin.H{ "msg": e.Message(), }) case codes.Internal: c.JSON(gin.H{ "msg": "内部错误", }) case codes.InvalidArgument: c.JSON(gin.H{ "msg": "参数错误", }) default: c.JSON(gin.H{ "msg": "其他错误", }) } return } }}func GetUserList(ctx *gin.Context) { // 发连接 // 拨号连接用户 grpc 服务 userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", global.ServerConfig.UserSrvInfo.Host, global.ServerConfig.UserSrvInfo.Port), grpc.WithInsecure()) if err != nil { zap.S().Errorw("[GetUserList]连接【用户服务失败】", "msg", err.Error(), ) } // 生成 grpc 的 client 并调用接口 userSrvClient := proto.NewUserClient(userConn) rsp, err := userSrvClient.GetUserList(context.Background(), &proto.PageInfo{ Pn: 0, PSize: 0, }) if err != nil { zap.S().Errorw("[GetUserList] 查询 【用户列表】失败") HandleGrpcErrorToHttp(err, ctx) return } result := make([]interface{}, 0) for _, value := range rsp.Data { //data := make(map[string]interface{}) user := response.UserResponse{ Id: value.Id, NickName: value.NickName, //BirthDay: time.Unix(int64(value.BirthDay),0), //BirthDay:time.Time(time.Unix(int64(value.BirthDay),0)).Format("2006-01-02"), BirthDay: response.JsonTime(time.Unix(int64(value.BirthDay), 0)), Gender: value.Gender, Mobile: value.Mobile, } result = append(result, user) } ctx.JSON(result) // 取数据 // 返回数据 zap.S().Debug("获取用户列表页")}
user-web/config/config.go
package configtype UserSrvConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"`}type ServerConfig struct { Name string `mapstructure:"name"` Port int `mapstructure:"port"` UserSrvInfo UserSrvConfig `mapstructure:"user_srv"`}
user-web/global/response/user.go
package responseimport ( "fmt" "time")type JsonTime time.Timefunc (j JsonTime) MarshalJSON()([]byte,error){ var stmp = fmt.Sprintf("\"%s\"",time.Time(j).Format("2006-01-02")) return []byte(stmp),nil}type UserResponse struct { Id int32 `json:"id"` NickName string `json:"name"` //BirthDay time.Time `json:"birthday"` //BirthDay string `json:"birthday"` BirthDay JsonTime `json:"birthday"` Gender string `json:"gender"` Mobile string `json:"mobile"`}
user-web/global/global.go
package globalimport "mxshop-api/user-web/config"var ( // 指针,控制地址,副本变,原本变 ServerConfig *config.ServerConfig = &config.ServerConfig{})
user-web/initialize/config.go
package initializeimport ( "fmt" "go.uber.org/zap" "mxshop-api/user-web/global" "github.com/fsnotify/fsnotify" "github.com/spf13/viper")func GetEnvInfo(env string) bool { viper.AutomaticEnv() return viper.GetBool(env) //刚才设置的环境变量 想要生效 我们必须得重启goland}func InitConfig(){ debug := GetEnvInfo("MXSHOP_DEBUG") configFilePrefix := "config" configFileName := fmt.Sprintf("user-web/%s-pro.yaml", configFilePrefix) if debug { configFileName = fmt.Sprintf("user-web/%s-debug.yaml", configFilePrefix) } v := viper.New() //文件的路径如何设置 v.SetConfigFile(configFileName) if err := v.ReadInConfig(); err != nil { panic(err) } // 这个对象如何在其他文件中使用 if err := v.Unmarshal(global.ServerConfig); err != nil { panic(err) } zap.S().Infof("配置信息:%v\n",global.ServerConfig) //viper的功能 - 动态监控变化 v.WatchConfig() v.OnConfigChange(func(e fsnotify.Event) { zap.S().Infof("配置文件产生变化:%v\n",e.Name) _ = v.ReadInConfig() _ = v.Unmarshal(global.ServerConfig) zap.S().Infof("配置信息:%v\n",global.ServerConfig) })}
user-web/config-debug.yaml
name: 'user-web2'port: 8031user_srv: host: '127.0.0.1' port: 50051
user-web/config-pro.yaml
name: 'user-web-pro'user_srv: host: '127.0.0.1' port: 50052
user-web/main.go
package mainimport ( "fmt" "go.uber.org/zap" "mxshop-api/user-web/global" "mxshop-api/user-web/initialize")func main(){ // 初始化 logger initialize.InitLogger() // 初始化配置文件 initialize.InitConfig() // 初始化 routers Router := initialize.Routers() zap.S().Debugf("启动服务器,端口:%d",global.ServerConfig.Port) if err := Router.Run(fmt.Sprintf(":%d",global.ServerConfig.Port));err != nil{ zap.S().Panic("启动失败:",err.Error()) }}
最后我们启动
python server.py 和
user-web/main.go
并通过yapi测试:
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~