13、用户web层服务(一)

前言


一、高性能日志库zap

1 - zap Quick Start

  • zap日志库官网地址https://github.com/uber-go/zap

  • zap的优点:性能高

  • Zap提供了两种类型的日志记录器—Sugared Logger和Logger

    • 在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录
    • 在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录
  • 为什么logger的效率更高:因为logger指明了类型,zap就不会启用go的反射,这样效率就比Sugared Logger更高;但即使是Sugared Logger也比一般的日志库性能高很多了

  • SugaredLogger使用

package main

import (
	"time"

	"go.uber.org/zap"
)

func main() {
	url := "https//www.baidu.com"
	logger, _ := zap.NewProduction() // 生产环境下使用
	// logger, _ := zap.NewDevelopment() // 开发环境下使用

	defer logger.Sync()     // flushes buffer, if any
	sugar := logger.Sugar() // 使用sugar的实例,更方便的记录日志
	sugar.Infow("failed to fetch URL",
		// Structured context as loosely typed key-value pairs.
		"url", url,
		"attempt", 3,
		"backoff", time.Second,
	)
	sugar.Infof("Failed to fetch URL: %s", url)
}

  • logger使用
package main

import (
	"go.uber.org/zap"
)

func main() {
	url := "https//www.baidu.com"
	logger, _ := zap.NewProduction() // 生产环境下使用
	// logger, _ := zap.NewDevelopment() // 开发环境下使用
	defer logger.Sync() // flushes buffer, if any

	// 这种效率比较高,就是因为指明了类型,zap就不会启用go的反射,这样效率就比较高
	logger.Info("failed to fetch URL",
		zap.String("url", url),
		zap.Int("nums", 3),
	)
}

2 - zap文件输出

package main

import (
	"time"

	"go.uber.org/zap"
)

func NewLogger() (*zap.Logger, error) {
	cfg := zap.NewProductionConfig()
	cfg.OutputPaths = []string{ //可以定位到多个文件中
		"./myproject.log",
		"stderr",
		"stdout",
	}
	return cfg.Build()
}

func main() {
	logger, err := NewLogger()
	if err != nil {
		panic(err)
		//panic("初始化logger失败")
	}
	su := logger.Sugar()
	defer su.Sync()
	url := "https://www.baidu.com"
	su.Info("failed to fetch URL",
		// Structured context as strongly typed Field values.
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second),
	)
}

在这里插入图片描述


二、项目集成zap和router

这里主要要做的两件事情:
1、初始化zap和初始化router分离出独立的模块
2、在模块router中实现与api的绑定

  • user_web\initialize\init_logger.go:初始化日志
package initialize

import "go.uber.org/zap"

func InitLogger() {
	logger, _ := zap.NewDevelopment()
	zap.ReplaceGlobals(logger)
}

  • user_web\initialize\init_router.go:初始化router
package initialize

import (
	"web_api/user_web/router"

	"github.com/gin-gonic/gin"
)

func Routers() *gin.Engine {
	Router := gin.Default()
	ApiGroup := Router.Group("v1")

	router.InitUserRouter(ApiGroup)
	return Router
}

  • user_web\router\router_user.go:user的RouterGroup
package router

import (
	"web_api/user_web/api"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func InitUserRouter(Router *gin.RouterGroup) {
	UserRouter := Router.Group("user")
	zap.S().Info("配置用户相关的url")
	{
		UserRouter.GET("list", api.GetUserList)
	}
}

  • user_web\api\api_user.go:api对外接口
package api

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func GetUserList(ctx *gin.Context) {
	zap.S().Debug("获取用户列表")
}

  • user_web\main.go:main
package main

import (
	"fmt"
	"web_api/user_web/initialize"

	"go.uber.org/zap"
)

func main() {

	port := 8081
	//2. 初始化logger
	initialize.InitLogger()
	//3. 初始化routers
	Router := initialize.Routers()

	/*
		1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger
		2. 日志是分级别的,debug, info , warn, error, fetal
			debug最低,fetal最高,如果配置成info,所有比info低的都不会输出
			NewProduction默认日志级别为info
			NewDevelopment默认日志级别为debug
		3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径
	*/
	zap.S().Debugf("启动服务器, 端口: %d", port)

	if err := Router.Run(fmt.Sprintf(":%d", port)); err != nil {
		zap.S().Panic("启动失败:", err.Error())
	}
}

在这里插入图片描述


三、gin调用grpc服务

1 - YApi测试

  • 启动YApi测试参考地址
    • user_web\initialize\init_router.go中将路径修改为ApiGroup := Router.Group("/u/v1")
    • YApi中注意修改端口号为8081

在这里插入图片描述

2 - proto生成

  • 拷贝user_srv项目下的user.proto,并重新生成protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";

service User{
    rpc GetUserList(PageInfo) returns (UserListResponse); // 用户列表
    rpc GetUserByMobile(MobileRequest) returns (UserInfoResponse); //通过mobile查询用户
    rpc GetUserById(IdRequest) returns (UserInfoResponse); //通过id查询用户
    rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); // 添加用户
    rpc UpdateUser(UpdateUserInfo) returns (google.protobuf.Empty); // 更新用户
    rpc CheckPassWord(PasswordCheckInfo) returns (CheckResponse); //检查密码
}

message PageInfo {
    uint32 pn = 1;
    uint32 pSize = 2;
}

message UserInfoResponse {
    int32 id = 1;
    string passWord = 2;
    string mobile = 3;
    string nickName = 4;
    uint64 birthDay = 5;
    string gender = 6;
    int32 role = 7;
}

message UserListResponse {
    int32 total = 1;
    repeated UserInfoResponse data = 2;
}

message CreateUserInfo {
    string nickName = 1;
    string passWord = 2;
    string mobile = 3;
}

message MobileRequest{
    string mobile = 1;
}

message IdRequest {
    int32 id = 1;
}

message UpdateUserInfo {
    int32 id = 1;
    string nickName = 2;
    string gender = 3;
    uint64 birthDay = 4;
}

message PasswordCheckInfo {
    string password = 1;
    string encryptedPassword = 2;
}

message CheckResponse{
    bool success = 1;
}

3 - gin调用grpc服务

  • user_web\global\response\rsp_user.go:增加返回消息,用户对象的封装
package response

import (
	"fmt"
	"time"
)

type JsonTime time.Time

// 内部自动调用MarshalJSON方法
func (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 JsonTime `json:"birthday"`
	Gender   string   `json:"gender"`
	Mobile   string   `json:"mobile"`
}

  • user_web\api\api_user.go
    • 统一规范错误类型提示
    • 实现用户列表接口查询
package api

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/status"

	"web_api/user_web/global/response"
	"web_api/user_web/proto"
)

func HandleGrpcErrorToHttp(err error, c *gin.Context) {
	//将grpc的code转换成http的状态码
	if err != nil {
		if e, ok := status.FromError(err); ok {
			switch e.Code() {
			case codes.NotFound:
				c.JSON(http.StatusNotFound, gin.H{
					"msg": e.Message(),
				})
			case codes.Internal:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg:": "内部错误",
				})
			case codes.InvalidArgument:
				c.JSON(http.StatusBadRequest, gin.H{
					"msg": "参数错误",
				})
			case codes.Unavailable:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg": "用户服务不可用",
				})
			default:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg": e.Code(),
				})
			}
			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.WithTransportCredentials(insecure.NewCredentials()))
	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 {
		user := response.UserResponse{
			Id:       value.Id,
			NickName: value.NickName,
			//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(http.StatusOK, result)
}

  • 测试结论
    • YApi的docker需要开启
    • user_srv需要开启,端口50051
    • user_web的http端口,8081

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


四、viper配置管理

1 - viper简介

  • Viper简介:Viper适用于Go应用程序的完整配置解决方案;它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式
  • Viper的特性
    • 设置默认值
    • 从JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件读取配置信息
    • 实时监控和重新读取配置文件(可选)
    • 从环境变量中读取
    • 从远程配置系统(etcd或Consul)读取并监控配置变化
    • 从命令行参数读取配置
    • 从buffer读取配置
    • 显式配置值
  • Viper地址https://github.com/spf13/viper
  • config.yaml
name: 'user-web'
mysql:
  host: '127.0.0.1'
  port: 3306
package main

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

type ServerConfig struct {
	ServiceName string `mapstructure:"name"`
	Port        int    `mapstructure:"port"`
}

func main() {
	v := viper.New()
	//文件的路径如何设置
	v.SetConfigFile("viper_test/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", v.Get("name"))
}

2 - viper环境隔离与动态监控

  • 需求思考:如何实现,不用改任何代码而且线上和线上的配置文件能隔离开
  • 解决方案
    • 采用环境变量的方式来确定是生产环境还是开发环境
    • 使用fsnotify文件变化通知库来实现动态监控
      在这里插入图片描述
  • main.go
package main

import (
	"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("DEV_CONFIG")
	configFilePrefix := "config"
	configFileName := fmt.Sprintf("viper_test/%s_pro.yaml", configFilePrefix)
	if debug {
		configFileName = fmt.Sprintf("viper_test/%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)
}

  • yaml
//config_pro.yaml
name: 'user-web'
mysql:
  host: '127.0.0.2'
  port: 3309

//config_debug.yaml
name: 'user-web2'
mysql:
  host: '127.0.0.1'
  port: 3306

在这里插入图片描述

3 - gin集成viper

  • 需要集成的配置包含
    • user_srv的服务配置信息
    • user_web的服务配置信息
  • yaml
//config_debug.yaml
name: 'user-web'
port: '8081'
user_srv:
  host: '127.0.0.1'
  port: '50051'
//config_pro.yaml
name: 'user-web'
port: '8031'
user_srv:
  host: '127.0.0.1'
  port: '50052'
  • user_web\config\config.go:添加user_srv和user_web的配置struct
package config

type UserSrvConfig struct {
	Host string `mapstructure:"host" json:"host"`
	Port int    `mapstructure:"port" json:"port"`
}

type ServerConfig struct {
	Name        string        `mapstructure:"name" json:"name"`
	Port        int           `mapstructure:"port" json:"port"`
	UserSrvInfo UserSrvConfig `mapstructure:"user_srv" json:"user_srv"`
}

  • user_web\global\global.go:为了让ServerConfig可以在其他文件中读取,在global.go中添加全局变量
package global

import "web_api/user_web/config"

var (
	ServerConfig *config.ServerConfig = &config.ServerConfig{}
)

  • user_web\initialize\init_config.go:初始化读取配置信息
package initialize

import (
	"fmt"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"go.uber.org/zap"

	"web_api/user_web/global"
)

func GetEnvInfo(env string) bool {
	viper.AutomaticEnv()
	return viper.GetBool(env)
	//刚才设置的环境变量 想要生效 我们必须得重启goland
}

func InitConfig() {
	debug := GetEnvInfo("DEV_CONFIG")
	configFilePrefix := "config"
	configFileName := fmt.Sprintf("%s_pro.yaml", configFilePrefix)
	if debug {
		configFileName = fmt.Sprintf("%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", global.ServerConfig)

	//viper的功能 - 动态监控变化
	v.WatchConfig()
	v.OnConfigChange(func(e fsnotify.Event) {
		zap.S().Infof("配置文件产生变化: &s", e.Name)
		_ = v.ReadInConfig()
		_ = v.Unmarshal(&global.ServerConfig)
		zap.S().Infof("配置信息: &v", global.ServerConfig)
	})
}

  • main.go
    • 添加viper的初始化:initialize.InitConfig()
    • 服务启动端口修改为全局的对象:global.ServerConfig.Port
package main

import (
	"fmt"
	"web_api/user_web/global"
	"web_api/user_web/initialize"

	"go.uber.org/zap"
)

func main() {
	//1. 初始化logger
	initialize.InitLogger()
	//2. 初始化配置文件
	initialize.InitConfig()
	//3. 初始化routers
	Router := initialize.Routers()

	/*
		1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger
		2. 日志是分级别的,debug, info , warn, error, fetal
			debug最低,fetal最高,如果配置成info,所有比info低的都不会输出
			NewProduction默认日志级别为info
			NewDevelopment默认日志级别为debug
		3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径
	*/
	zap.S().Debugf("启动服务器, 端口: %d", global.ServerConfig.Port)

	if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil {
		zap.S().Panic("启动失败:", err.Error())
	}
}

在这里插入图片描述

五、完整源码

源码地址:https://download.csdn.net/download/qq23001186/86261620

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值