Kratos快速入门
目录
- 前言
- 一、环境搭建
- 二、验证码服务
- 1 - 代码仓库准备
- 2 - kratos初始化验证码服务项目
- 3 - 使用 Protobuf 定义验证码生成接口
- 4 - apipost测试
- 5 - 业务逻辑代码实现
- 三、顾客服务-验证码
- 1 - kratos初始化顾客服务
- 2 - 顾客获取验证码proto定义
- 3 - 请求验证码服务获取验证码
- 4 - 验证码临时存储
- 5 - 规范代码
前言
-
b站学习地址:https://www.bilibili.com/video/BV1Zu411A7bF?p=3&spm_id_from=pageDriver&vd_source=ff00b0b2c6df149ad18dded4512730fa
-
kratos官方文档地址:https://go-kratos.dev/docs/
一、环境搭建
- 安装go环境
- 配置GOPATH和GOPROXY环境变量:将GOPATH下的bin目录添加到系统环境变量中
- Protoc:
- 下载二进制文件:https://github.com/protocolbuffers/protobuf/releases下拉到最下方下载系统对应的版本
- 将解压出来bin目录下的protoc.exe拷贝到GOPATH的bin目录下
- 查看protoc版本:
protoc --version
- 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
- Git Bash克隆:
- 初始化项目
- 为项目添加README.md
- 编写必要的.gitignore忽略文件
- 参考地址:https://github.com/github/gitignore/blob/main/Go.gitignore
vi .gitignore
,完成后提交到仓库
# 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
- 在 Github 上,若出现网络问题,也可以使用 Gitee 上的仓库:
-
进入项目目录,拉取依赖:
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
- 方案1:
-
测试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)
- 增加一个参数:
- 更新 internal/server/grpc.go 文件
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)
- NewHTTPServer添加一个参数:
- 处理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 ./...
- 更新wire的Provider定义:
- 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,否则是无法覆盖的)
- 为get请求添加
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 ./...