【Go】十一、标准化请求返回与viper管理配置文件的简单使用

时间:2024-06-03 07:48:54

请求返回的返回方法

这里指的是:传参的方式,类似与Java的r.setData()

创建目录:

user-web

global

response

user.go

定义一个结构体用于接收返回值,这里的 json 属于将对象转换为 json 时的规则定义。

时间处理

方法一:在方法返回处处理(不完全)

这种处理方式还不完全,其只能将时间转换为 标准时间格式

user.go:

package response

import "time"

type UserResponse struct {
	Id       int32     `json:"id"`
	NickName string    `json:"name"`
	Birthday time.Time `json:"birthday"`
	Gender   string    `json:"gender"`
	Mobile   string    `json:"mobile"`
}

在使用这个结构体时,应该对 rsp.Data 进行处理:

但此时,返回的数据是 :“0001-01-01T00:00:00Z” 类似于这样的标准时间格式

api:

	// 构建请求结果
	result := make([]interface{}, 0)
	for _, value := range rsp.Data {

		user := response.UserResponse{
			Id:       value.Id,
			NickName: value.NickName,

			Birthday: time.Time(time.Unix(Int64(value.BirthDay), 0)),
			Gender: value.Gender,
			Mobile: value.Mobile,
		}

		result = append(result, user)
	}

	// 利用上下文的 JSON 转换返回结果,在这里将结果返回给请求
	ctx.JSON(http.StatusOK, result)

方法二:传入string 在方法返回处处理时间字符串

所以这里我们采用 string 的传递处理时间的格式问题,将 Birthday 作为 string 类型进行传递,在输出返回的时候进行 Format

user.go

package response

type UserResponse struct {
	Id       int32  `json:"id"`
	NickName string `json:"name"`
	//Birthday time.Time `json:"birthday"`
	Birthday string
	Gender   string `json:"gender"`
	Mobile   string `json:"mobile"`
}

api:

	// 构建请求结果
	result := make([]interface{}, 0)
	for _, value := range rsp.Data {
		//data := make(map[string]interface{}) // 创建一个 map
		//data["id"] = value.Id
		//data["name"] = value.NickName
		//data["birth"] = value.BirthDay
		//data["gender"] = value.Gender
		//data["mobile"] = value.Mobile

		var user = response.UserResponse{
			Id:       value.Id,
			NickName: value.NickName,
			Birthday: time.Time(time.Unix(int64(value.BirthDay), 0)).Format("2006-01-02"),
			Gender: value.Gender,
			Mobile: value.Mobile,
		}
		result = append(result, user)
	}

方法三:利用别名重写 MarshalJSON 的方式直接在返回结构体中处理

另外,如果希望 response 的类型直接是 time.Time 的话,就实现 jsonTime 方法来对json格式的时间进行转换

user.go

package response

import (
	"fmt"
	"time"
)

type JsonTime time.Time // 给 time.Time 类型定义一个别名:JsonTime

func (j JsonTime) Marsha1JSON() ([]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
	Gender string `json:"gender"`
	Mobile string `json:"mobile"`
}

api:

	// 构建请求结果
	result := make([]interface{}, 0)
	for _, value := range rsp.Data {
		//data := make(map[string]interface{}) // 创建一个 map
		//data["id"] = value.Id
		//data["name"] = value.NickName
		//data["birth"] = value.BirthDay
		//data["gender"] = value.Gender
		//data["mobile"] = value.Mobile

		var user = response.UserResponse{
			Id:       value.Id,
			NickName: value.NickName,
			Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)),	
			Gender: value.Gender,
			Mobile: value.Mobile,
		}
		result = append(result, user)
	}

传入的 time.Time (JsonTime)类型输出为

{0 63852321095 0x132ba20}

time.Time(j) 进行标准化之后输出为:

2024-05-26 19:51:35 +0800 CST

这主要是由于 time.Time 定义了 String 方法,可以被 fmt.Print 友好的打印,而 JsonTime 是一个别名,其没有继承其方法,所以其展现仅仅是内存中的表现形式,内容其实是一样的。

这里要注意的是,我们定义 JsonTime的原因是:我们无法重写修改源代码中的 time.Time,需要通过别名作为跳板

而 []byte(str) 的一种原因是,JSON 格式的转换需要使用 二进制作为跳板(可以先这样理解,具体原因有待考究)

配置文件管理

简单条件下的配置文件管理

这里选用,生态最好,使用最广泛的 yaml 作为配置文件来管理配置信息

同时 使用 viper 库对yaml 进行管理,这里的 viper 库是一个强大的配置文件管理库,其不仅仅可以管理 ymal,也支持 java properties、JSON、TOML、HCL、envfile 的管理

文件目录:

viper_test

test

main.go

config.yaml

简单获取配置文件信息

config.yaml:

name: "user-webbbb"

main.go:

package main

import (
	"fmt"
	"github.com/spf13/viper"
)

/*
	1. 创建 viper 对象
	2. 设置配置文件路径
	3. 读取配置文件
	4. 使用配置
*/

func main() {
	v := viper.New()
	// 注意这里的文件配置路径要根据 go build 中的 Edit Config 来考量,不可以根据当前文件来考量
	v.SetConfigFile("other_test/viper_test/ch01/config.yaml")
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	fmt.Println(v.Get("name"))
}

使用结构体直接映射配置信息

config.yml

name: "user-webbbb"
port: 8021

main.go

package main

import (
	"fmt"
	"github.com/spf13/viper"
)

/*
	简单读取配置文件
	1. 创建 viper 对象
	2. 设置配置文件路径
	3. 读取配置文件
	4. 使用配置
*/

/*
	配置文件映射为 struct
	1. 创建对象
	2. 设置路径
	3. 读取配置文件
	4. 创建对应的结构体对象
	5. 使用 v.unmarsshal 进行反解,传入地址
*/

type ServerConfig struct {
	ServiceName string `mapstructure:"name"` // 使用 mapstructure 来反解配置文件
	Port        int64  `mapstructure:"port"`
}

func main() {
	v := viper.New()
	// 注意这里的文件配置路径要根据 go build 中的 Edit Config 来考量,不可以根据当前文件来考量
	v.SetConfigFile("other_test/viper_test/ch01/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.Println(v.Get("name"))
}

复杂条件下的配置管理

若遇到多层 yml 的情况,只需要嵌套 yml 即可

config.yaml:

name: 'user-web'
mysql:
  host: '127.0.0.1'
  port: 3306

main.go:

package main

import (
	"fmt"
	"github.com/spf13/viper"
)

type MysqlConfig struct {
	Host string `mapstructure:"host"`
	Port int64  `mapstructure:"port"`
}

type ServerConfig struct {
	ServerName  string      `mapstructure:"name"`
	ServerMysql MysqlConfig `mapstructure:"mysql"`
}

func main() {
	v := viper.New()
	v.SetConfigFile("other_test/viper_test/ch02/config.yaml")
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	serverConfig := ServerConfig{}
	if err := v.Unmarshal(&serverConfig); err != nil {
		panic(err)
	}
	fmt.Print(serverConfig)
}

配置文件的隔离性管理

实现配置文件在不同情况下的不同选择,其原理是识别系统的环境变量,若系统的xxx环境变量为true 则为xxx环境,使用对应的配置文件

// 获取环境变量
func GetEnvInfo(env string) bool {
	viper.AutomaticEnv()
	return viper.GetBool(env)
}

func main() {
	configFileName := "other_test/viper_test/ch02/config-prod.yaml"
	debug := GetEnvInfo("MXSHOP-DEBUG")
	
	if debug {
		configFileName = "other_test/viper_test/ch02/config-dev.yaml"
	}

	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.Print(serverConfig)

}

此时,我们本地是有这个环境变量的,系统会自动帮我们用我们自己的配置文件,但服务器上是没有这个环境变量的,所以我们的配置文件就会自动被识别为生产环境的配置文件

配置文件的实时识别

	v.WatchConfig()
	// 此处是固定写法,当监听到文件信息改变时,会触发下面的匿名函数
	v.OnConfigChange(func(e fsnotify.Event) {
		fmt.Println("config file channed: ", e.Name) // e.Name 是文件名
		_ = v.ReadInConfig()
		_ = v.Unmarshal(&serverConfig)
		fmt.Println(serverConfig)
	})

	// 此处阻塞了主线程,上面的监听还可以继续的根本原因是 viper 中的监听是启用了一个 goroutine进行的,所以主线程的阻塞不妨碍监听进程的持续运行
	time.Sleep(time.Second * 300)

配置文件集成到项目中

目录结构:

mxshop-api

user-web

api

router

config

config.go (记录匹配过来的配置文件信息)

initialize

config.go

global

global.go

config-debug.yaml

config-pro.yaml

config-debug.yaml

name: "user-webb"
user_srv:
  host: '127.0.0.1'
  port: 50051

global.go

package global

import "mxshop-api/user-web/config"

// 全局变量
var (
	ServerConfig *config.ServerConfig = &config.ServerConfig{}
)

添加初始化信息:

config.go

package initialize

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"mxshop-api/user-web/global"
)

// 获取bool型环境变量的方法
func GetenvInfo(env string) bool {
	viper.AutomaticEnv()
	var rs bool
	rs = viper.GetBool(env)
	return rs
}

func InitConfig() {
	configFileName := "user-web/config-pro.yaml"
	debug := GetenvInfo("MXSHOP-DEBUG")
	if debug {
		configFileName = "user-web/config-debug.yaml"
	}
	v := viper.New()
	v.SetConfigFile(configFileName)
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	// 注意这里应该是全局变量,全局变量的部署应该是在 global 目录中
	//serverConfig := config.ServerConfig{}
	if err := v.Unmarshal(global.ServerConfig); err != nil {
		panic(err)
	}
	zap.L().Info(fmt.Sprintf("配置信读取:%v", global.ServerConfig))

	v.WatchConfig()
	v.OnConfigChange(func(e fsnotify.Event) {
		zap.S().Infof("配置文件产生变化:%s", e.Name)
		v.ReadInConfig()
		v.Unmarshal(global.ServerConfig)
		zap.L().Info(fmt.Sprintf("修改了配置信息:%v\n", global.ServerConfig))
	})

}

在主程序中将初始化信息添加输出文件内容

main.go:

func main() {
    ...
	// 调用配置文件伛
	initialize.InitConfig()
	...

}