Kratos快速入门

前言

一、环境搭建

  • 安装go环境
    在这里插入图片描述
  • 配置GOPATH和GOPROXY环境变量:将GOPATH下的bin目录添加到系统环境变量中

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

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

  • protoc-gen-go插件安装:protoc是针对所有语言开发的,protoc-gen-go是专门针对go语言开发,因为是使用go语言开发的,所以直接使用go install就可以进行安装,安装后在GOPATH的bin沐目录下就会有对应的二进制文件
    • go install google.golang.org/protobuf/cmd/protoc-gen-go@latest,版本验证protoc-gen-go --version
    • go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest,版本验证protoc-gen-go-grpc --version
      在这里插入图片描述
  • kratos工具安装:kratos也是go语言编写的,帮助我们快速生成代码的,直接go install安装即可,安装完成也会在GOPATH的bin目录中生成二进制文件
    • 安装命令:go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
    • 版本验证:kratos -v
      在这里插入图片描述

二、验证码服务

1 - 代码仓库准备

  • 新建仓库laomadj
    在这里插入图片描述
  • 复制仓库地址,克隆到本地
    • Git Bash克隆:git clone https://gitee.com/justoso/laomadj.git
      在这里插入图片描述
  • 初始化项目
    • 为项目添加README.md

在这里插入图片描述

# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work

# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# OS General
Thumbs.db
.DS_Store

# project
*.cert
*.key
*.log
bin/

# Develop tools
.vscode/
.idea
*.swp

在这里插入图片描述

  • 目录结构基础:新建好各目录,并添加上README.md,保证是一个非空目录,不会被git给忽略掉
/README.md

# 后端,每个服务一个独立项目目录
/backend
/backend/README.md
/backend/verifyCode
/backend/service

# 管理前端
/manager
/manager/README.md

# 司机前端
/driver
/driver/README.md

# 顾客前端
/consumer
/consumer/README.md

在这里插入图片描述

LF will be replaced by CRLF the next time Git touches it
Dos/Windows平台默认换行符:回车(CR)+换行(LF),即’\r\n’
Mac/Linux平台默认换行符:换行(LF),即’\n’
企业服务器一般都是Linux系统进行管理,所以会有替换换行符的需求
解决方案1(Windows系统):提交时转换为LF,检出时转换为CRLF -> git config --global core.autocrlf true
解决方案2(Linux系统):提交时转换为LF,检出时不转换 -> git config --global core.autocrlf input
解决方案3(只适用于Windows系统):提交检出均不转换 -> git config --global core.autocrlf false

2 - kratos初始化验证码服务项目

  • 使用工具 kratos 工具完成项目创建kratos new verifyCode
    • 在 Github 上,若出现网络问题,也可以使用 Gitee 上的仓库:kratos new verifyCode -r https://gitee.com/go-kratos/kratos-layout.git

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

  • 进入项目目录,拉取依赖go mod tidy
    在这里插入图片描述

  • 安装wire工具:kratos使用了依赖注入来生成相关的代码,所以运行项目前也需要安装

    • go get github.com/google/wire/cmd/wire
    • 安装完成后运行go generate ./...:可以看到wire为我们生成了wire_gen.go
      在这里插入图片描述
  • 项目运行:项目目录下运行kratos run

    • 可以看到kratos为我们加载config的配置
    • 启动了http服务,监听8000端口
    • 启动了grpc服务,监听9000端口
  • kratos run报错

    • 方案1:go get github.com/go-kratos/kratos/v2@v2.6.2
    • 方案2:再运行一次go mod tidy
      在这里插入图片描述
  • 测试8000端口访问:浏览器访问 -> http://localhost:8000/helloworld/test

在这里插入图片描述

以上的layout的目录布局,仅仅是kratos推荐的项目目录布局方式

3 - 使用 Protobuf 定义验证码生成接口

  • 实现步骤分析

    • 定义 protobuf 文件说明接口
    • 利用 protoc 基于 protobuf 生成必要代码
    • 将生成的代码整合到项目中
    • 完善业务逻辑
  • 增加proto文件模板:建议使用kratos命令来添加

    • 项目目录下运行:kratos proto add api/verifyCode/verifyCode.proto
      在这里插入图片描述
  • 使用goland打开项目

    • verifyCode.proto默认会帮我们生成增删改查的方法
    • 这里只需要保留GetVerifyCode的即可
      在这里插入图片描述
      在这里插入图片描述
  • 补充verifyCode.proto

syntax = "proto3";

package api.verifyCode;
// 生成的go代码所在的包
option go_package = "code/api/verifyCode;verifyCode";
// 定义 VerifyCode 服务
service VerifyCode {
	rpc GetVerifyCode (GetVerifyCodeRequest) returns (GetVerifyCodeReply);
}
// 类型常量
enum TYPE {
	DEFAULT = 0;
	DIGIT = 1;
	LETTER = 2;
	MIXED = 3;
};
// 定义 GetVerifyCodeRequest 消息
message GetVerifyCodeRequest {
	//    验证码长度
	uint32 length = 1;
	// 验证码类型
	TYPE type = 2;

}
// 定义 GetVerifyCodeReply 消息
message GetVerifyCodeReply {
	//    生成的验证码
	string code = 1;
}
  • 基于verifyCode.proto生成 client(Stub)相关代码
    • kratos proto client api/verifyCode/verifyCode.proto
    • api/verifyCode/verifyCode.pb.go:类型定义代码
    • api/verifyCode/verifyCode_grpc.pb.go:gRPC服务定义代码
      在这里插入图片描述
  • 基于verifyCode.proto文件生成 grpc服务代码
    • kratos proto server api/verifyCode/verifyCode.proto -t internal/service
      • -t 选项指定生成文件所在位置,代码会生成在internal/service目录中的internal/service/verifycode.go
      • internal/service/verifycode.go该文件定义了最基本的 VerifyCode 服务和对应的 GetVerifyCode 方法
        在这里插入图片描述
        在这里插入图片描述
package service

import (
	"context"

	pb "verifyCode/api/verifyCode"
)

type VerifyCodeService struct {
	pb.UnimplementedVerifyCodeServer
}

func NewVerifyCodeService() *VerifyCodeService {
	return &VerifyCodeService{}
}

func (s *VerifyCodeService) GetVerifyCode(ctx context.Context, req *pb.GetVerifyCodeRequest) (*pb.GetVerifyCodeReply, error) {
	return &pb.GetVerifyCodeReply{}, nil
}

  • 将生成的服务代码注册到 gRPC 服务中
    • 更新 internal/service/service.go 文件
    • 在以上的 wire.NewSet() 调用中,添加第二个参数 NewVerifyCodeService:这个函数是用来生成VerifyCodeService 服务的,定义在internal/service/verifycode.go 中
    • 以上代码的意思就是告知 wire 依赖注入系统,如果需要 VerifyCodeService 的话,使用NewVerifyCodeService 函数来构建
  • 将 VerifyCodeService 注册到 gRPC 服务中
    • 更新 internal/server/grpc.go 文件
      • 增加一个参数:verifyCodeService *service.VerifyCodeService
      • 增加一行代码:verifyCode.RegisterVerifyCodeServer(srv, verifyCodeService)
package server

import (
	v1 "verifyCode/api/helloworld/v1"
	"verifyCode/api/verifyCode"
	"verifyCode/internal/conf"
	"verifyCode/internal/service"

	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/middleware/recovery"
	"github.com/go-kratos/kratos/v2/transport/grpc"
)

// NewGRPCServer new a gRPC server.
func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, verifyCodeService *service.VerifyCodeService, logger log.Logger) *grpc.Server {
	var opts = []grpc.ServerOption{
		grpc.Middleware(
			recovery.Recovery(),
		),
	}
	if c.Grpc.Network != "" {
		opts = append(opts, grpc.Network(c.Grpc.Network))
	}
	if c.Grpc.Addr != "" {
		opts = append(opts, grpc.Address(c.Grpc.Addr))
	}
	if c.Grpc.Timeout != nil {
		opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
	}
	srv := grpc.NewServer(opts...)
	v1.RegisterGreeterServer(srv, greeter)
	// 增加下行,将 VerifyCodeService 注册到 srv 中
	verifyCode.RegisterVerifyCodeServer(srv, verifyCodeService)
	return srv
}
  • 生成依赖注入代码go generate ./...
    在这里插入图片描述

4 - apipost测试

  • kratos启动项目
    在这里插入图片描述

  • apipost中新建目录
    在这里插入图片描述

  • 新建gRPC测试
    在这里插入图片描述

  • 导入proto文件
    在这里插入图片描述

  • 导入完成后测试

    • gRPC的接口是9000,点击调用,我们发现已经有响应了,说明之前的构建没有问题

在这里插入图片描述

  • 同理我们可以测试http接口
    在这里插入图片描述

5 - 业务逻辑代码实现

  • 业务逻辑代码实现:路径 -> backend\verifyCode\internal\service\verifycode.go
    • GetVerifyCode方法中添加code返回
    • 添加RandCode方法,返回测试字符串"result"
    • kratos启动测试
package service

import (
	"context"

	pb "verifyCode/api/verifyCode"
)

type VerifyCodeService struct {
	pb.UnimplementedVerifyCodeServer
}

func NewVerifyCodeService() *VerifyCodeService {
	return &VerifyCodeService{}
}

func (s *VerifyCodeService) GetVerifyCode(ctx context.Context, req *pb.GetVerifyCodeRequest) (*pb.GetVerifyCodeReply, error) {
	return &pb.GetVerifyCodeReply{
		Code: RandCode(int(req.Length), req.Type),
	}, nil
}

func RandCode(l int, t pb.TYPE) string {

	return "result"
}

在这里插入图片描述

  • 最简单的实现
// RandCode 开放的被调用的方法,用于区分类型
func RandCode(l int, t pb.TYPE) string {
	switch t {
	case pb.TYPE_DEFAULT:
		fallthrough
	case pb.TYPE_DIGIT:
		return randCode("0123456789", l)
	case pb.TYPE_LETTER:
		return randCode("abcdefghijklmnopqrstuvwxyz", l)
	case pb.TYPE_MIXED:
		return randCode("0123456789abcdefghijklmnopqrstuvwxyz", l)
	}
	return ""
}

// randCode 随机的核心方法
func randCode(chars string, l int) string {
	charsLen := len(chars)
	result := make([]byte, l)
	for i := 0; i < l; i++ {
		// 核心函数 生成[0,n]的整型随机数
		randIndex := rand.Intn(charsLen)
		result[i] = chars[randIndex]
	}
	return string(result)
}
  • 随机种子优化
    • 问题分析:启动一次kratos,记录2次生成的code
    • 重启kratos,再生成2次code,可以发现,与之前一次生成的code是一样的
    • 解决方案:在main启动函数中添加全局随机种子,路径:backend/verifyCode/cmd/verifyCode/main.go
    • main.go中添加代码:rand.Seed(time.Now().UnixNano())
    • 在main启动函数中添加随机种子的好处是,项目中所有有需要用到rand的都会生效
func main() {
	flag.Parse()
	logger := log.With(log.NewStdLogger(os.Stdout),
		"ts", log.DefaultTimestamp,
		"caller", log.DefaultCaller,
		"service.id", id,
		"service.name", Name,
		"service.version", Version,
		"trace.id", tracing.TraceID(),
		"span.id", tracing.SpanID(),
	)
	c := config.New(
		config.WithSource(
			file.NewSource(flagconf),
		),
	)
	defer c.Close()

	if err := c.Load(); err != nil {
		panic(err)
	}

	var bc conf.Bootstrap
	if err := c.Scan(&bc); err != nil {
		panic(err)
	}

	app, cleanup, err := wireApp(bc.Server, bc.Data, logger)
	if err != nil {
		panic(err)
	}
	defer cleanup()

	rand.Seed(time.Now().UnixNano())

	// start and wait for stop signal
	if err := app.Run(); err != nil {
		panic(err)
	}
}
  • 剖析rand.Intn
    • 查看系统源码我们发现,系统的随机函数是在int63(也就是63位的二进制数)中随机,但是我们需要随机的字符最多就是TYPE_MIXED类型的35个字符(仅需要6位的二进制数即可实现了),所以这里存在优化空间
    • TYPE_DIGIT:0-9数字,仅需要4位二进制数
    • TYPE_LETTER:A-Z字母,仅需要5位二进制数
    • TYPE_MIXED:0-9数字和A-Z字母,仅需要6位二进制数
// Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n).
// It panics if n <= 0.
func (r *Rand) Int63n(n int64) int64 {
	if n <= 0 {
		panic("invalid argument to Int63n")
	}
	if n&(n-1) == 0 { // n is power of two, can mask
		return r.Int63() & (n - 1)
	}
	max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
	v := r.Int63()
	for v > max {
		v = r.Int63()
	}
	return v % n
}
  • 优化方向:一次随机多个随机位,分部分多次使用
// RandCode 开放的被调用的方法,用于区分类型
func RandCode(l int, t pb.TYPE) string {
	switch t {
	case pb.TYPE_DEFAULT:
		fallthrough
	case pb.TYPE_DIGIT:
		return randCode("0123456789", 4, l)
	case pb.TYPE_LETTER:
		return randCode("abcdefghijklmnopqrstuvwxyz", 5, l)
	case pb.TYPE_MIXED:
		return randCode("0123456789abcdefghijklmnopqrstuvwxyz", 6, l)
	}
	return ""
}

// randCode 随机的核心方法
func randCode(chars string, idxBits, l int) string {
	// 形成掩码
	idxMask := 1<<idxBits - 1
	// 63 位可以使用的最大组次数
	idxMax := 63 / idxBits

	// 利用string builder构建结果缓冲
	sb := strings.Builder{}
	sb.Grow(l)

	// 循环生成随机数
	// i 索引
	// cache 随机数缓存
	// remain 随机数还可以用几次
	for i, cache, remain := l-1, rand.Int63(), idxMax; i >= 0; {
		// 随机缓存不足,重新生成
		if remain == 0 {
			cache, remain = rand.Int63(), idxMax
		}
		// 利用掩码生成随机索引,有效索引为小于字符集合长度
		if idx := int(cache & int64(idxMask)); idx < len(chars) {
			sb.WriteByte(chars[idx])
			i--
		}
		// 利用下一组随机数位
		cache >>= idxBits
		remain--
	}

	return sb.String()
}

三、顾客服务-验证码

1 - kratos初始化顾客服务

  • kratos初始化步骤
    • ①.cd 到backend目录
    • ②.kratos new customer -r https://gitee.com/go-kratos/kratos-layout.git
    • ③.cd 到 customer 目录,引入依赖go mod tidy
    • ④.安装wire:go get github.com/google/wire/cmd/wire
    • ⑤.生成wire_gen.go:go generate ./...
    • ⑥.修改customer的监听端口,路径backend/customer/configs/config.yaml:http修改为8100,grpc修改为9100
    • ⑦.修改后再执行一次:go mod tidy(否则会报错,目前还没有空研究为啥)
    • ⑧.启动项目:kratos run

在这里插入图片描述

2 - 顾客获取验证码proto定义

  • 添加.proto文件kratos proto add api/customer/customer.proto
  • 顾客服务定义获取验证码的proto消息
syntax = "proto3";

package api.customer;

import "google/api/annotations.proto";

option go_package = "customer/api/customer;customer";


service Customer {
  //获取验证码
  rpc GetVerifyCode(GetVerifyCodeReq) returns (GetVerifyCodeResp){
    option(google.api.http) = {
      get:"/customer/get-verify-code"
    };
  }
}

message GetVerifyCodeReq{
  string Telephone = 1;
};

message GetVerifyCodeResp{
  int32 Code = 1;
  string  Message = 2;
  string Data = 3;
};

  • 生成客户端代码kratos proto client .\api\customer\customer.proto
  • 生成服务端代码kratos proto server .\api\customer\customer.proto

在这里插入图片描述

  • 在http服务中加入customer服务:路径 -> backend/customer/internal/server/http.go
    • NewHTTPServer添加一个参数:customerService *service.CustomerService
    • //注册customer的http服务:customer.RegisterCustomerHTTPServer(srv, customerService)
  • 处理go的相关依赖:路径 -> backend/customer/internal/service/service.go
    • 更新wire的Provider定义:var ProviderSet = wire.NewSet(NewCustomerService, NewGreeterService)
    • 更新wire工具:go get github.com/google/wire/cmd/wire@v0.5.0
    • 生成wire_gen.go:go generate ./...
      在这里插入图片描述
  • apipost测试kratos run,get请求localhost:8100/customer/get-verify-code
    在这里插入图片描述
  • 更新customer.proto
    • 为get请求添加{telephone}参数,URL路由中使用{}表示参数
    • 增加一个验证码的生成时间:verify_code_time
    • 生成客户端代码:kratos proto client .\api\customer\customer.proto
    • 生成服务端代码:kratos proto server .\api\customer\customer.proto(注意这里需要删除之前的backend/customer/internal/service/customer.go,否则是无法覆盖的)
syntax = "proto3";

package api.customer;

import "google/api/annotations.proto";

option go_package = "customer/api/customer;customer";


service Customer {
  //获取验证码
  rpc GetVerifyCode(GetVerifyCodeReq) returns (GetVerifyCodeResp){
    option(google.api.http) = {
      get:"/customer/get-verify-code/{telephone}"
    };
  }
}

message GetVerifyCodeReq{
  string telephone = 1;
};

message GetVerifyCodeResp{
  int32 code = 1;
  string  message = 2;
  string verify_code = 3;
  int64 verify_code_time = 4;
};


  • 添加手机号码验证逻辑
package service

import (
	"context"
	"regexp"

	pb "customer/api/customer"
)

type CustomerService struct {
	pb.UnimplementedCustomerServer
}

func NewCustomerService() *CustomerService {
	return &CustomerService{}
}

func (s *CustomerService) GetVerifyCode(ctx context.Context, req *pb.GetVerifyCodeReq) (*pb.GetVerifyCodeResp, error) {
	// 1、校验手机号
	pattern := `^(13\d|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18\d|19[0-35-9])\d{8}$`
	regexpPattern := regexp.MustCompile(pattern)
	if !regexpPattern.MatchString(req.Telephone) {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "电话号码格式错误",
		}, nil
	}
	return &pb.GetVerifyCodeResp{}, nil
}

  • apipost测试
    在这里插入图片描述
    在这里插入图片描述

3 - 请求验证码服务获取验证码

  • 顾客服务要调用验证码服务的要求:需要在customer服务中,使用verfiycode服务中的.proto文件,生成客户端(stub存根)代码,才可以完成grpc的远程调用
    • 在customer项目中新建verifycode目录(backend/customer/api/verifycode)
    • 拷贝verifyCode.proto到customer服务中的verifycode目录,并执行kratos proto client .\api\verifycode\verifyCode.proto来生成
  • customer.go
package service

import (
	"context"
	verifyCode "customer/api/verifycode"
	"github.com/go-kratos/kratos/v2/transport/grpc"
	"regexp"
	"time"

	pb "customer/api/customer"
)

type CustomerService struct {
	pb.UnimplementedCustomerServer
}

func NewCustomerService() *CustomerService {
	return &CustomerService{}
}

func (s *CustomerService) GetVerifyCode(ctx context.Context, req *pb.GetVerifyCodeReq) (*pb.GetVerifyCodeResp, error) {
	// 1、校验手机号
	pattern := `^(13\d|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18\d|19[0-35-9])\d{8}$`
	regexpPattern := regexp.MustCompile(pattern)
	if !regexpPattern.MatchString(req.Telephone) {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "电话号码格式错误",
		}, nil
	}
	// 2、请求验证码服务获取验证码

	// 连接目标grpc服务器
	conn, err := grpc.DialInsecure(
		context.Background(),
		grpc.WithEndpoint("localhost:9000"), // verifyCode 的grpc service地址,这里先临时写死
	)
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码服务不可用",
		}, nil
	}
	//关闭
	defer func() {
		_ = conn.Close()
	}()

	// 发送获取验证码请求
	client := verifyCode.NewVerifyCodeClient(conn)
	reply, err := client.GetVerifyCode(context.Background(), &verifyCode.GetVerifyCodeRequest{
		Length: 6,
		Type:   1,
	})
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码获取错误",
		}, nil
	}
	return &pb.GetVerifyCodeResp{
		Code:           0,
		VerifyCode:     reply.Code,
		VerifyCodeTime: time.Now().Unix(),
		VerifyCodeLife: 60,
	}, nil
}

  • 同时启动verifyCode服务和customer服务使用apipost测试

在这里插入图片描述

4 - 验证码临时存储

  • 临时存储方案
    • 为了在登录时校验验证码是否正确,需要将其存储,因为验证码是有有效期的,临时性存储即可,可以快速判定验证码是否有效,选择使用redis(key是电话号码,value是验证码,设置60s的有效期)
  • 启动redis服务:使用go-redis完成redis操作,安装:go get github.com/redis/go-redis/v9
    在这里插入图片描述
package service

import (
	"context"
	verifyCode "customer/api/verifycode"
	"github.com/go-kratos/kratos/v2/transport/grpc"
	"github.com/redis/go-redis/v9"
	"regexp"
	"time"

	pb "customer/api/customer"
)

type CustomerService struct {
	pb.UnimplementedCustomerServer
}

func NewCustomerService() *CustomerService {
	return &CustomerService{}
}

func (s *CustomerService) GetVerifyCode(ctx context.Context, req *pb.GetVerifyCodeReq) (*pb.GetVerifyCodeResp, error) {
	// 1、校验手机号
	pattern := `^(13\d|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18\d|19[0-35-9])\d{8}$`
	regexpPattern := regexp.MustCompile(pattern)
	if !regexpPattern.MatchString(req.Telephone) {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "电话号码格式错误",
		}, nil
	}
	// 2、请求验证码服务获取验证码

	// 连接目标grpc服务器
	conn, err := grpc.DialInsecure(
		context.Background(),
		grpc.WithEndpoint("localhost:9000"), // verifyCode 的grpc service地址,这里先临时写死
	)
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码服务不可用",
		}, nil
	}
	//关闭
	defer func() {
		_ = conn.Close()
	}()

	// 发送获取验证码请求
	client := verifyCode.NewVerifyCodeClient(conn)
	reply, err := client.GetVerifyCode(context.Background(), &verifyCode.GetVerifyCodeRequest{
		Length: 6,
		Type:   1,
	})
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码获取错误",
		}, nil
	}

	//
	options, err := redis.ParseURL("redis://192.168.149.128:6379/1?dial_timeout=1")
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "Redis配置解析错误",
		}, nil
	}
	// NewClient不会立即连接,只是建立客户端,需要执行命令时才会连接
	rdb := redis.NewClient(options)
	// 设置key
	status := rdb.Set(context.Background(), "CVC:"+req.Telephone, reply.Code, 60*time.Second)
	if _, err := status.Result(); err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码存储错误,redis的set错误",
		}, nil
	}

	return &pb.GetVerifyCodeResp{
		Code:           0,
		VerifyCode:     reply.Code,
		VerifyCodeTime: time.Now().Unix(),
		VerifyCodeLife: 60,
	}, nil
}

5 - 规范代码

目标:使用internal/data目录完成数据(数据库、缓存、文件、OSS、云服务器)的操作,上面的redis就属于此类

  • 使用配置文件完成redis配置信息的初始化
    • 在internal/data/data.go完成redis客户端的初始化
    • NewData(c *conf.Data, logger log.Logger)中的*conf.Data我们跳到定义可以看到它实际上是一个struct,里面就有Redis的对象,对应的配置就是customer/configs/config.yaml中的配置
type Data struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Database *Data_Database `protobuf:"bytes,1,opt,name=database,proto3" json:"database,omitempty"`
	Redis    *Data_Redis    `protobuf:"bytes,2,opt,name=redis,proto3" json:"redis,omitempty"`
}
// Data .
type Data struct {
	// TODO wrapped database client
	Rdb *redis.Client
}

// NewData .
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
	data := &Data{}
	redisUrl := fmt.Sprintf("redis://%s/1?dial_timeout=%d", c.Redis.Addr, 1)
	options, err := redis.ParseURL(redisUrl)
	if err != nil {
		data.Rdb = nil
	}
	data.Rdb = redis.NewClient(options)

	cleanup := func() {
		// 清理了 Redis连接
		_ = data.Rdb.Close()
		log.NewHelper(logger).Info("closing the data resources")
	}
	return data, cleanup, nil
}
  • 创建data中用于完成数据操作的对象(实体)
    • 在internal/data/customer.go中定义与数据操作相关的代码
    • 在internal/data/data.go中添加wire的依赖注入,注意还需要使用go generate ./...构建
      • var ProviderSet = wire.NewSet(NewData, NewGreeterRepo, NewCustomerData)
package data

type CustomerData struct {
	data *Data
}

func NewCustomerData(data *Data) *CustomerData {
	return &CustomerData{data: data}
}
  • internal/data/data.go添加设置验证码的方法
package data

import (
	"context"
	"time"
)

type CustomerData struct {
	data *Data
}

func NewCustomerData(data *Data) *CustomerData {
	return &CustomerData{data: data}
}

func (cd *CustomerData) SetVerifyCode(telephone string, code string, ex int64) error {
	// 设置key
	status := cd.data.Rdb.Set(context.Background(), "CVC:"+telephone, code, time.Duration(ex)*time.Second)
	if _, err := status.Result(); err != nil {
		return err
	}
	return nil
}

  • 更新backend/customer/internal/service/customer.go
    • 更新CustomerService的定义与CustomerData建立关联
    • 使用CustomerData的设置验证码方法替换原有逻辑
package service

import (
	"context"
	verifyCode "customer/api/verifycode"
	"customer/internal/data"
	"github.com/go-kratos/kratos/v2/transport/grpc"
	"regexp"
	"time"

	pb "customer/api/customer"
)

type CustomerService struct {
	pb.UnimplementedCustomerServer
	cd *data.CustomerData
}

func NewCustomerService(cd *data.CustomerData) *CustomerService {
	return &CustomerService{
		cd: cd,
	}
}

func (s *CustomerService) GetVerifyCode(ctx context.Context, req *pb.GetVerifyCodeReq) (*pb.GetVerifyCodeResp, error) {
	// 1、校验手机号
	pattern := `^(13\d|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18\d|19[0-35-9])\d{8}$`
	regexpPattern := regexp.MustCompile(pattern)
	if !regexpPattern.MatchString(req.Telephone) {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "电话号码格式错误",
		}, nil
	}
	// 2、请求验证码服务获取验证码

	// 连接目标grpc服务器
	conn, err := grpc.DialInsecure(
		context.Background(),
		grpc.WithEndpoint("localhost:9000"), // verifyCode 的grpc service地址,这里先临时写死
	)
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码服务不可用",
		}, nil
	}
	//关闭
	defer func() {
		_ = conn.Close()
	}()

	// 发送获取验证码请求
	client := verifyCode.NewVerifyCodeClient(conn)
	reply, err := client.GetVerifyCode(context.Background(), &verifyCode.GetVerifyCodeRequest{
		Length: 6,
		Type:   1,
	})
	if err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码获取错误",
		}, nil
	}
	const life = 60 // 有效时间60s
	if err := s.cd.SetVerifyCode(req.Telephone, reply.Code, life); err != nil {
		return &pb.GetVerifyCodeResp{
			Code:    1,
			Message: "验证码存储错误,redis的set错误",
		}, nil
	}

	return &pb.GetVerifyCodeResp{
		Code:           0,
		VerifyCode:     reply.Code,
		VerifyCodeTime: time.Now().Unix(),
		VerifyCodeLife: 60,
	}, nil
}

  • 重新生成依赖注入,因为修改了ProviderSet
    • go get github.com/google/wire/cmd/wire@v0.5.0
    • go generate ./...
      在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无休止符

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

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

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

打赏作者

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

抵扣说明:

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

余额充值