请求返回的返回方法
这里指的是:传参的方式,类似与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()
...
}