当前位置: 首页 > wzjs >正文

网站建设重庆网站流量来源

网站建设重庆,网站流量来源,想通过做威客网站上的任务来赚,河南郑州房产网本文目录 1. 回顾2. Zap日志3. 配置4. 引入gprc梳理gRPC思路优雅关闭gRPC 1. 回顾 上篇文章我们进行了路由搭建,引入了redis,现在来看看对应的效果。 首先先把前端跑起来,然后点击注册获取验证码。 再看看控制台输出和redis是否已经有记录&…

在这里插入图片描述

本文目录

  • 1. 回顾
  • 2. Zap日志
  • 3. 配置
  • 4. 引入gprc
    • 梳理gRPC思路
    • 优雅关闭gRPC

1. 回顾

上篇文章我们进行了路由搭建,引入了redis,现在来看看对应的效果。

首先先把前端跑起来,然后点击注册获取验证码。

在这里插入图片描述

再看看控制台输出和redis是否已经有记录,验证没问题,现在redis这个环节是打通了。

在这里插入图片描述

2. Zap日志

go中原生的日志比较一般,我们可以集成一个流行的日志库进来。

这里用uber开源的zap日志库,在common路径下安装zap:go get -u go.uber.org/zap。

然后再安装一个日志分割库,go get -u github.com/natefinch/lumberjack,因为日志的存储有几种方式,比如按照日志级别将日志记录到不同的文件,按照业务来分别记录不同级别的日志,按照包结构划分记录不同级别日志。debug级别以上记录一个,info以上记录一个,warn以上记录一个

common路径下创建logs.go,然后编写对应的代码。

package logsimport ("github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore""net""net/http""net/http/httputil""os""runtime/debug""strings""time"
)var lg *zap.Loggertype LogConfig struct {DebugFileName string `json:"debugFileName"`InfoFileName  string `json:"infoFileName"`WarnFileName  string `json:"warnFileName"`MaxSize       int    `json:"maxsize"`MaxAge        int    `json:"max_age"`MaxBackups    int    `json:"max_backups"`
}// InitLogger 初始化Logger
func InitLogger(cfg *LogConfig) (err error) {writeSyncerDebug := getLogWriter(cfg.DebugFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)writeSyncerInfo := getLogWriter(cfg.InfoFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)writeSyncerWarn := getLogWriter(cfg.WarnFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)encoder := getEncoder()//文件输出debugCore := zapcore.NewCore(encoder, writeSyncerDebug, zapcore.DebugLevel)infoCore := zapcore.NewCore(encoder, writeSyncerInfo, zapcore.InfoLevel)warnCore := zapcore.NewCore(encoder, writeSyncerWarn, zapcore.WarnLevel)//标准输出consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())std := zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel)core := zapcore.NewTee(debugCore, infoCore, warnCore, std)lg = zap.New(core, zap.AddCaller())zap.ReplaceGlobals(lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可return
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.TimeKey = "time"encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderencoderConfig.EncodeDuration = zapcore.SecondsDurationEncoderencoderConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn zapcore.NewJSONEncoder(encoderConfig)
}func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   filename,MaxSize:    maxSize,MaxBackups: maxBackup,MaxAge:     maxAge,}return zapcore.AddSync(lumberJackLogger)
}// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.Next()cost := time.Since(start)lg.Info(path,zap.Int("status", c.Writer.Status()),zap.String("method", c.Request.Method),zap.String("path", path),zap.String("query", query),zap.String("ip", c.ClientIP()),zap.String("user-agent", c.Request.UserAgent()),zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),zap.Duration("cost", cost),)}
}// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
func GinRecovery(stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif ne, ok := err.(*net.OpError); ok {if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {brokenPipe = true}}}httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {lg.Error(c.Request.URL.Path,zap.Any("error", err),zap.String("request", string(httpRequest)),)// If the connection is dead, we can't write a status to it.c.Error(err.(error)) // nolint: errcheckc.Abort()return}if stack {lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),zap.String("stack", string(debug.Stack())),)} else {lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),)}c.AbortWithStatus(http.StatusInternalServerError)}}()c.Next()}
}

然后在main.go中来初始化我们的日志。

在这里插入图片描述
然后可以把对应的log地方进行更改了,比如下面的地方。
在这里插入图片描述
然后来验证一下是否能正常生成日志文件,正常生成了,没问题。
正常生成了

3. 配置

日志我们用了zap做集成,算是一个改进,但是配置比较复杂, 所以我们这里需要进一步优化这个配置。

配置我们引入viper进行操作,也非常简单,直接上图和代码吧,在user里边装viper这个包。

go get github.com/spf13/viper

首先在user下面创建cofig目录,然后创建config.yaml配置文件,然后创建config.go代码读取配置。

在这里插入图片描述

config.go的代码如下所示。

package configimport ("github.com/go-redis/redis/v8""github.com/spf13/viper""log""os""test.com/project-common/logs"
)var C = InitConfig()type Config struct {viper *viper.ViperSC    *ServerConfig
}type ServerConfig struct {Name stringAddr string
}func InitConfig() *Config {conf := &Config{viper: viper.New()}workDir, _ := os.Getwd()conf.viper.SetConfigName("config")conf.viper.SetConfigType("yaml")conf.viper.AddConfigPath(workDir + "/config")conf.viper.AddConfigPath("etc/msproject/user")//读取configerr := conf.viper.ReadInConfig()if err != nil {log.Fatalln(err)}conf.ReadServerConfig()conf.InitZapLog() //初始化zap日志return conf
}func (c *Config) InitZapLog() {//从配置中读取日志配置,初始化日志lc := &logs.LogConfig{DebugFileName: c.viper.GetString("zap.debugFileName"),InfoFileName:  c.viper.GetString("zap.infoFileName"),WarnFileName:  c.viper.GetString("zap.warnFileName"),MaxSize:       c.viper.GetInt("maxSize"),MaxAge:        c.viper.GetInt("maxAge"),MaxBackups:    c.viper.GetInt("maxBackups"),}err := logs.InitLogger(lc)if err != nil {log.Fatalln(err)}
}func (c *Config) ReadServerConfig() {sc := &ServerConfig{}sc.Name = c.viper.GetString("server.name")sc.Addr = c.viper.GetString("server.addr")c.SC = sc
}// 读redis的配置
func (c *Config) ReadRedisConfig() *redis.Options {return &redis.Options{Addr:     c.viper.GetString("redis.host") + ":" + c.viper.GetString("redis.port"),Password: c.viper.GetString("redis.password"), // no password setDB:       c.viper.GetInt("db"),                // use default DB}
}

对应的redis.go中原本new一个redis客户端的代码也需要更改了,改为已有的读取配置的函数 ReadRedisConfig()

在这里插入图片描述
并且把原来main.go中关于zap的相关配置文件删除即可。
在这里插入图片描述
然后重新启动下,看看是否能够运行,ok,启动没问题。

在这里插入图片描述

4. 引入gprc

可以通过引入一个API把对应的服务连起来,可以把各种服务提出来,然后通过API进行定义。

在这里插入图片描述
api\proto下新建一个名为login.service.proto的文件,然后编写代码。

syntax = "proto3";
package login.service.v1;
option go_package = "project-user/pkg/service/login.service.v1";message CaptchaMessage {string mobile = 1;
}
message CaptchaResponse{
}
service LoginService {rpc GetCaptcha(CaptchaMessage) returns (CaptchaResponse) {}
}

然后在proto路径下,运行命令:protoc --go_out=./gen --go_opt=paths=source_relative --go-grpc_out=./gen --go-grpc_opt=paths=source_relative login_service.proto,就可以生成对应文件了。

因为是第一版,所以我们先在gen下生成,然后复制移动到service下面,防止后面不断根据功能进行修改,而导致新生成的被覆盖。

那么我们来看看这login-service_grpc.pb.go文件到底生成了什么东西。

在这里插入图片描述


LoginServiceClient 是一个接口,定义了客户端可以调用的 GetCaptcha 方法。该方法接收一个 CaptchaMessage 请求,返回一个 CaptchaResponse 响应。

loginServiceClientLoginServiceClient 的具体实现,通过 NewLoginServiceClient 函数创建。它使用 grpc.ClientConnInterface 来发起 RPC 调用。

loginServiceClient.GetCaptcha 方法中,通过 c.cc.Invoke 发起 GetCaptcha 方法的 RPC 调用。它将请求数据序列化并发送到服务器,然后等待响应。

在这里插入图片描述

LoginServiceServer 是服务器端的接口,定义了 GetCaptcha 方法。所有实现该接口的服务器端逻辑必须嵌入 UnimplementedLoginServiceServer,以确保向前兼容性。
UnimplementedLoginServiceServer 提供了一个默认的未实现方法的错误响应,返回 codes.Unimplemented 状态码。

在这里插入图片描述

也就屙是说,接口还包含一个方法 mustEmbedUnimplementedLoginServiceServer(),这是一个空方法,用于确保实现者嵌入了 UnimplementedLoginServiceServer

这是 UnimplementedLoginServiceServerGetCaptcha 方法的默认实现。它返回 nil 作为响应,并通过 status.Errorf 返回一个带有 codes.Unimplemented 状态码的错误,表明该方法未被实现。这种设计确保了即使服务实现者没有实现某些方法,调用这些方法时也不会导致程序崩溃,而是返回一个明确的错误。

mustEmbedUnimplementedLoginServiceServer() 是一个空方法,用于确保服务实现者嵌入了 UnimplementedLoginServiceServer

testEmbeddedByValue() 是一个辅助方法,用于在运行时检查 UnimplementedLoginServiceServer 是否被正确嵌入(通过值而不是指针)。这避免了在方法调用时出现空指针引用。

type LoginServiceServer interface {GetCaptcha(context.Context, *CaptchaMessage) (*CaptchaResponse, error)mustEmbedUnimplementedLoginServiceServer()
}// UnimplementedLoginServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedLoginServiceServer struct{}func (UnimplementedLoginServiceServer) GetCaptcha(context.Context, *CaptchaMessage) (*CaptchaResponse, error) {return nil, status.Errorf(codes.Unimplemented, "method GetCaptcha not implemented")
}
func (UnimplementedLoginServiceServer) mustEmbedUnimplementedLoginServiceServer() {}
func (UnimplementedLoginServiceServer) testEmbeddedByValue()                      {}

所以主要是为了,确保向前兼容性:通过嵌入 UnimplementedLoginServiceServer,服务实现者可以在未来版本中添加新方法,而不会破坏现有实现。

梳理gRPC思路

首先我们实现了gRPC,那么原本的api下面的user相关的我们可以删除了。

来看看我们实现了什么。

首先main.go中的相关代码如下。

在这里插入图片描述
然后在service中我们实现了login_service.go代码,如下。

package login_service_v1import ("context""errors""fmt""go.uber.org/zap""log"common "test.com/project-common""test.com/project-common/logs""test.com/project-user/pkg/dao""test.com/project-user/pkg/repo""time"
)type LoginService struct {UnimplementedLoginServiceServercache repo.Cache
}func New() *LoginService {return &LoginService{cache: dao.Rc,}
}func (ls LoginService) GetCaptcha(ctx context.Context, msg *CaptchaMessage) (*CaptchaResponse, error) {//1.获取参数moblie := msg.Mobilefmt.Println(moblie)//2.校验参数if !common.VerifyMoblie(moblie) {return nil, errors.New("手机号不合法")}//3.生成验证码(随机4位或者6位)code := "123456"//4.调用短信平台(三方,放入go协程中执行,接口可以快速响应,短信几秒到无所谓)go func() {time.Sleep(1 * time.Second)zap.L().Info("短信平台调用成功,发送短信 INFO")logs.LG.Debug("短信平台调用成功,发送短信 debug")zap.L().Error("短信平台调用成功,发送短信 error")// redis 假设后续缓存在mysql或者mongo当中,也有可能存储在别的当中// 所以考虑用接口实现,面向接口编程“低耦合,高内聚“// 5.存储验证码redis,设置过期时间15分钟即可c, cancel := context.WithTimeout(context.Background(), 2*time.Second)defer cancel()err := ls.cache.Put(c, "REGISTER_"+moblie, code, 15*time.Minute)if err != nil {log.Printf("验证码存入redis出错,causer by :%v\n", err)}log.Printf("将手机号和验证码存入redis成功:REGISTER %s : %s", moblie, code)}()return &CaptchaResponse{}, nil
}

并且在router中更新了如下代码。

package routerimport ("github.com/gin-gonic/gin""google.golang.org/grpc""log""net""test.com/project-user/config"loginServiceV1 "test.com/project-user/pkg/service/login.service.v1"
)// Router 接口
type Router interface {Route(r *gin.Engine)
}type RegisterRouter struct {
}func New() *RegisterRouter {return &RegisterRouter{}
}func (*RegisterRouter) Route(ro Router, r *gin.Engine) {ro.Route(r)
}var routers []Routerfunc InitRouter(r *gin.Engine) {for _, ro := range routers {ro.Route(r)}
}type gRPCConfig struct {Addr         stringRegisterFunc func(*grpc.Server)
}func RegisterGrpc() *grpc.Server {c := gRPCConfig{Addr: config.C.GC.Addr,RegisterFunc: func(g *grpc.Server) {//注册loginServiceV1.RegisterLoginServiceServer(g, loginServiceV1.New())}}s := grpc.NewServer()c.RegisterFunc(s)lis, err := net.Listen("tcp", c.Addr)if err != nil {log.Println("cannot listen")}//把服务放到协程里边go func() {err = s.Serve(lis)if err != nil {log.Println("server started error", err)return}}()return s
}

在这里插入图片描述

好,有点复杂,这里我们画图梳理下关系。

首先在router.go文件中,我们声明了RegisterGrpc(),这是gRPC服务的入口点,主要是配置grpc配置,包括服务地址还有注册函数,并且创建gRPC实例,然后注册登录服务,最后是启动gRPC服务器(在goroutine中运行的。)

login_service.go 中:LoginService 结构体:实现了 gRPC 服务接口,New() 函数:创建 LoginService 实例,GetCaptcha() 方法:实现具体的验证码获取业务逻辑。

所以调用关系如下。
在这里插入图片描述
在这里插入图片描述
所以为什么要使用协程?因为如果不使用协程,s.Serve(lis)会阻塞主线程,导致后续代码无法继续运行,这样可以运行gRPC服务器与HTTP服务器(gin)同时运行。

gRPC可以独立运行,不影响主程序的其他功能。


优雅关闭gRPC

在main函数中,还有个stop,这是闭包函数,说实话这是第一次看到闭包函数的使用场景,首先我们捕获了外部变量gc,gc也就是gRPC服务器实例,然后定义了服务关闭的具体行为,也就是停止gRPC服务,作为参数传给srv.Run。

当Run函数接受一个stop函数作为参数,注释一种依赖注入的设计模式,当收到指令之后,会把gRPC给关闭了。

虽然 stop 函数被传入,但它并不会立即执行,代码会在 <-quit 这行被阻塞。只有当程序收到 SIGINT 或 SIGTERM 信号时(比如按 Ctrl+C),才会继续往下执行,然后才会检查 stop != nil 并执行 stop 函数。

http://www.dtcms.com/wzjs/40478.html

相关文章:

  • 扁平化设计网站建设合肥seo推广排名
  • 企业形象网站用什么语言开发产品营销网站建设
  • 欧美网站建设排名大全百度站长平台官网登录入口
  • 做家教的正规网站b2b多平台一键发布
  • 网站被k是怎么回事google推广教程
  • 做视频背景音乐网站2023年国家免费技能培训
  • 高端网站建设公司服务好吗seo的定义
  • 网站建设改版目的西安网络推广运营公司
  • 重庆网站建设选承越磁力链 ciliba
  • 一般做网站是在什么网站找素材网站快速排名
  • 专题活动是在官方网站还是在其他网站做实时疫情最新消息数据
  • 成都网站建设 四川冠辰科技深圳网络推广团队
  • 招聘代做网站代刷网站推广链接免费
  • 高清视频素材下载网站免费的网站推广软件
  • 学校门户网站建设管理办法网络推广方案
  • 个体工商户年报入口官网百度热搜seo
  • 网站建设网站自助建设seo独立站
  • pc网站开发工具今日新闻播报
  • vps 安装 wordpress外贸网站seo
  • 雨岑信息科技有限公司做企业型网站做的怎么样_公司规模如何西安seo外包公司
  • 企通互联的网站建设失败房地产销售工作内容
  • 网站ico在后台哪里找到今日头条最新消息
  • ppt做会动彩字网站舆情监控系统
  • 农业信息网站建设意义windows优化工具
  • 空间链接制作网站浏览器地址栏怎么打开
  • 做阿里巴巴网站需要多少钱谷歌seo技巧
  • 衡水网站建设网络广告电话
  • 定制版网站建设详细报价单建立网站需要什么技术
  • 怎样修改wordpress密码长沙seo搜索
  • seo网站建设企业网站多少钱一年