第24关 web层开发-gin基础项目架构

网友投稿 1291 2022-10-13

第24关 web层开发-gin基础项目架构

第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 配置文件 - viper​​​​24-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/zap​​​​zap_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小时内删除侵权内容。

上一篇:failure是用于处理应用程序错误的实用程序包(failure和error的区别)
下一篇:NYOJ 191 && POJ 1012 Joseph(约瑟夫环问题)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~