在这一节中,我将创建一个“创建用户”接口
使用工具
Postman (https://www.postman.com)
DataGrip (https://www.jetbrains.com/zh-cn/datagrip)
编写接口
生成 proto 模板
在终端中执行下面的命令
# 进入"user"项目目录,不要抄作业哦,用自己在第一节生成项目的目录
cd user
# 在"api/user/v1"目录下,生成名为"user"的proto模板
kratos proto add api/user/v1/user.proto
可以看到在user/api/user/v1下生成了一个名为user.proto的文件
文件树如下(*为生成的文件):
├── api
│ └── user
│ └── v1
│ └── user.proto*
生成文件内容如下:/api/user/v1/user.proto
syntax = "proto3";
package api.user.v1;
option go_package = "user/api/user/v1;v1";
option java_multiple_files = true;
option java_package = "api.user.v1";
service User {
rpc CreateUser (CreateUserRequest) returns (CreateUserReply);
rpc UpdateUser (UpdateUserRequest) returns (UpdateUserReply);
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserReply);
rpc GetUser (GetUserRequest) returns (GetUserReply);
rpc ListUser (ListUserRequest) returns (ListUserReply);
}
message CreateUserRequest {}
message CreateUserReply {}
message UpdateUserRequest {}
message UpdateUserReply {}
message DeleteUserRequest {}
message DeleteUserReply {}
message GetUserRequest {}
message GetUserReply {}
message ListUserRequest {}
message ListUserReply {}
一个接口的 proto 文件就生成好啦
编写 proto 模板
引入”google/api/annotations.proto”
syntax = "proto3";
package api.user.v1;
import "google/api/annotations.proto";
option go_package = "user/api/user/v1;v1";
......
保留”CreateUser”,将生成的user.proto文件中其他接口删除,并且写上注释
.......
// 用户服务
service User {
// 创建一个用户
rpc CreateUser (CreateUserRequest) returns (CreateUserReply);
}
// 创建用户请求参数
message CreateUserRequest {}
// 创建用户返回结果
message CreateUserReply {}
给CreateUser添加路由,设置访问方法为PUT,在添加一个body消息主体
如果你不知道Body是什么,可以查阅这份文档来了解它:
《HTTP消息》(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages)
// 用户服务
service User {
// 创建一个用户
rpc CreateUser (CreateUserRequest) returns (CreateUserReply) {
option(google.api.http) = {
put:"/api/user"
body:"creat_body"
};
};
}
在创建用户请求参数的消息主体(creat body)中添加name和passwd
// 创建用户请求参数
message CreateUserRequest {
message CreatBody{
string name = 1;
string passwd = 2;
}
CreatBody creat_body = 1;
}
在创建用户返回结果中添加一个id,以用来代表用户创建成功而返回创建成功后的用户id
// 创建用户返回结果
message CreateUserReply {
uint64 id = 1;
}
生成proto源码
在终端中执行下面的命令
# 在"api/user/v1"目录下,生成"user"的proto源码
kratos proto client api/user/v1/user.proto
生成成功后就可以看到如下的文件树(*为生成的文件):
├── api
│ └── user
│ └── v1
│ ├── user_grpc.pb.go*
│ ├── user_http.pb.go*
│ ├── user.pb.go*
│ └── user.proto
可以发现kratos在”api/user/v1”目录下生成了”user.pb.go”,”user_http.pb.go”和”user_grpc.pb.go”三个文件
生成service模板
在终端中执行下面的命令
kratos proto server api/user/v1/user.proto -t internal/service
执行后会生成”/internal/service/user.go”的server模板
文件树如下(*为生成的文件):
├── internal
│ ......
│ └── service
│ ├── README.md
│ ├── service.go
│ └── user.go*
生成文件内容如下:/internal/service/user.go
package service
import (
"context"
pb "user/api/user/v1"
)
type UserService struct {
pb.UnimplementedUserServer
}
func NewUserService() *UserService {
return &UserService{}
}
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserReply, error) {
return &pb.CreateUserReply{}, nil
}
我们在用户服务结构体“UserService”中添加上一节中封装好的用户数据操作“biz.UserUseCase”。(顺带加上日志log,虽然没用到)
// UserService 用户服务
type UserService struct {
pb.UnimplementedUserServer
// uc 用户操作的封装,在“/biz/user.go”中
uc *biz.UserUseCase
log *log.Helper
}
修改“NewUserService”函数的内容,给创建的“UserService”结构体初始化
func NewUserService(uc *biz.UserUseCase, logger log.Logger) *UserService {
return &UserService{
uc: uc,
log: log.NewHelper(logger),
}
}
编写用户服务中的“CreateUser”服务接口,使得它能够处理前端发送的请求
// CreateUser 创建用户服务接口
// 这里是一个给前端调用的接口
// 参数中的 req *pb.CreateUserRequest 为前端发送给后端的“创建用户请求参数”
// 如果成功返回一个 *pb.CreateUserReply 它是前端需要得到的回复“创建用户返回结果”
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserReply, error) {
// 获取前端发送的Body数据
body := req.GetCreatBody()
// 创建用户
id, err := s.uc.CreateUser(ctx, body.GetName(), body.GetPasswd())
if err != nil {
return nil, err
}
// 给前端返回数据
return &pb.CreateUserReply{
Id: uint64(id),
}, nil
}
按照惯例,让我们吧“NewUserService”函数添加到依赖提供者集中
修改“/service/service.go”文件中的“wire.NewSet()”函数,在“NewSet”函数里添加“NewUserService”
// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewUserService)
注册HTTP服务器
在HTTP服务器中注册我们写好的用户服务
打开“/server/http.go”
引入kratos 使用proto生成的源码和我们写好的用户服务
import (
......
user "user/api/user/v1"
"user/internal/service"
)
在“NewHTTPServer”函数的参数中加入我们的用户服务“userService *service.UserService”,以便wire可以正确的注入依赖
// NewHTTPServer new a HTTP server.
func NewHTTPServer(c *conf.Server, userService *service.UserService, logger log.Logger) *http.Server {
......
}
注册用户服务HTTP服务器
在“NewHTTPServer”函数的末尾加入“user.RegisterUserHTTPServer(srv, userService)”
// NewHTTPServer new a HTTP server.
func NewHTTPServer(c *conf.Server, userService *service.UserService, logger log.Logger) *http.Server {
......
srv := http.NewServer(opts...)
user.RegisterUserHTTPServer(srv, userService)
return srv
}
wire依赖注入
我们“按照惯例”的将所有创建结构体的函数都加入到wire的依赖提供者集中,但这是为啥呢?为什么要将创建结构体的函数都加入到wire的依赖提供者集中呢?如果你对wire依赖注入感兴趣不妨看一下这篇文章
《Go工程化 - 依赖注入》(https://go-kratos.dev/blog/go-project-wire)
在进行接下来的操作前,请务必确定你的wire依赖提供者集依照上文和前两节中的添加正确
在“/biz/biz.go”中
// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewUserUseCase)
在“/data/data.go”中
// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewDataBase, NewUseRepo)
在“/service/service.go”中
// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewUserService)
在终端中输入命令
# 进入项目目录
cd user
# 生成所有proto源码、wire等等
go generate ./...
测试
首先要创建好一个用于测试的数据库!并且记好数据库的名称!
以及将“/data/data.go”中“NewDataBase”函数里的“dsn”数据库链接变量修改正确
不要无脑抄作业哦,根据自己的实际情况修改数据库链接
// dsn 数据库链接
// "用户名":"密码"@tcp("IP":"端口")/"数据库名称"?charset=utf8mb4&parseTime=True&loc=Local
dsn := "test:test@tcp(localhost:3306)/kratos_demo?charset=utf8mb4&parseTime=True&loc=Local"
激动人心的时刻,我们来运行项目
在终端中输入命令
# 运行项目
kratos run
看到以下内容,我们这成功将项目运行起来了
运行kratos项目
使用「Postman」对接口进行测试
我们根据上文中对“/api/user/v1/user.proto”编写的内容,使用”PUT”方法去访问 http://127.0.0.1:8000/api/user
修改”Body”类型为”JSON”编写消息主体”Body 的内容”为
{
"name": "test",
"passwd": "test_passwd"
}
点击”Send”发送请求
使用Postman测试接口
我们可以看到 http://localhost:8000/api/user 接口返回了一个内容
{
"id": "1"
}
使用DataGrip、Navicat Premium等等数据库可视化软件查看数据库,当然只要你喜欢,你也可以使用mysql命令行
查看测试使用的数据库
使用DataGrip查看数据库
可以看到,数据库中已经正确的创建了数据
到此,就大功告成了
到此,就大功告成了
到此,就大功告成了
完成本节后你将得到如下代码
文件: /api/user/v1/user.proto
syntax = "proto3";
package api.user.v1;
import "google/api/annotations.proto";
option go_package = "user/api/user/v1;v1";
option java_multiple_files = true;
option java_package = "api.user.v1";
// 用户服务
service User {
// 创建一个用户
rpc CreateUser (CreateUserRequest) returns (CreateUserReply) {
option(google.api.http) = {
put:"/api/user"
body:"creat_body"
};
};
}
// 创建用户请求参数
message CreateUserRequest {
message CreatBody{
string name = 1;
string passwd = 2;
}
CreatBody creat_body = 1;
}
// 创建用户返回结果
message CreateUserReply {
uint64 id = 1;
}
文件: /service/user.go
// “service”目录下的文件是通过“/api”下的“*/proto”文件自动生成的
// 需要修改“UserService”的函数来完成消息的处理
package service
import (
"context"
"github.com/go-kratos/kratos/v2/log"
pb "user/api/user/v1"
"user/internal/biz"
)
// UserService 用户服务
type UserService struct {
pb.UnimplementedUserServer
// uc 用户操作的封装,在“/biz/user.go”中
uc *biz.UserUseCase
log *log.Helper
}
func NewUserService(uc *biz.UserUseCase, logger log.Logger) *UserService {
return &UserService{
uc: uc,
log: log.NewHelper(logger),
}
}
// CreateUser 创建用户服务
// 这里是一个给前端调用的接口
// 参数中的 req *pb.CreateUserRequest 为前端发送给后端的“创建用户请求参数”
// 如果成功返回一个 *pb.CreateUserReply 它是前端需要得到的回复“创建用户返回结果”
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserReply, error) {
// 获取前端发送的Body数据
body := req.GetCreatBody()
// 创建用户
id, err := s.uc.CreateUser(ctx, body.GetName(), body.GetPasswd())
if err != nil {
return nil, err
}
// 给前端返回数据
return &pb.CreateUserReply{
Id: uint64(id),
}, nil
}
文件: /server/http.go
package server
import (
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/transport/http"
user "user/api/user/v1"
"user/internal/conf"
"user/internal/service"
)
// NewHTTPServer new a HTTP server.
func NewHTTPServer(c *conf.Server, userService *service.UserService, logger log.Logger) *http.Server {
var opts = []http.ServerOption{
http.Middleware(
recovery.Recovery(),
),
}
if c.Http.Network != "" {
opts = append(opts, http.Network(c.Http.Network))
}
if c.Http.Addr != "" {
opts = append(opts, http.Address(c.Http.Addr))
}
if c.Http.Timeout != nil {
opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
}
srv := http.NewServer(opts...)
user.RegisterUserHTTPServer(srv, userService)
return srv
}
参考文档
《kratos Docs》创建项目: https://go-kratos.dev/docs/getting-started/start
《kratos Docs》Protobuf 规范: https://go-kratos.dev/docs/guide/api-protobuf
《kratos Blog》Go工程化 - 依赖注入: https://go-kratos.dev/blog/go-project-wire
《Google Cloud Service Infrastructure》Package google.api: https://cloud.google.com/service-infrastructure/docs/service-management/reference/rpc/google.api
《HTTP消息》https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages