在这一节中,我将创建一个“创建用户”接口

使用工具

Postman (https://www.postman.com)

DataGrip (https://www.jetbrains.com/zh-cn/datagrip)

编写接口

生成 proto 模板

在终端中执行下面的命令

  1. # 进入"user"项目目录,不要抄作业哦,用自己在第一节生成项目的目录
  2. cd user
  3. # 在"api/user/v1"目录下,生成名为"user"的proto模板
  4. kratos proto add api/user/v1/user.proto

可以看到在user/api/user/v1下生成了一个名为user.proto的文件

文件树如下(*为生成的文件):

  1. ├── api
  2. └── user
  3. └── v1
  4. └── user.proto*

生成文件内容如下:/api/user/v1/user.proto

  1. syntax = "proto3";
  2. package api.user.v1;
  3. option go_package = "user/api/user/v1;v1";
  4. option java_multiple_files = true;
  5. option java_package = "api.user.v1";
  6. service User {
  7. rpc CreateUser (CreateUserRequest) returns (CreateUserReply);
  8. rpc UpdateUser (UpdateUserRequest) returns (UpdateUserReply);
  9. rpc DeleteUser (DeleteUserRequest) returns (DeleteUserReply);
  10. rpc GetUser (GetUserRequest) returns (GetUserReply);
  11. rpc ListUser (ListUserRequest) returns (ListUserReply);
  12. }
  13. message CreateUserRequest {}
  14. message CreateUserReply {}
  15. message UpdateUserRequest {}
  16. message UpdateUserReply {}
  17. message DeleteUserRequest {}
  18. message DeleteUserReply {}
  19. message GetUserRequest {}
  20. message GetUserReply {}
  21. message ListUserRequest {}
  22. message ListUserReply {}

一个接口的 proto 文件就生成好啦

编写 proto 模板

引入”google/api/annotations.proto”

  1. syntax = "proto3";
  2. package api.user.v1;
  3. import "google/api/annotations.proto";
  4. option go_package = "user/api/user/v1;v1";
  5. ......

保留”CreateUser”,将生成的user.proto文件中其他接口删除,并且写上注释

  1. .......
  2. // 用户服务
  3. service User {
  4. // 创建一个用户
  5. rpc CreateUser (CreateUserRequest) returns (CreateUserReply);
  6. }
  7. // 创建用户请求参数
  8. message CreateUserRequest {}
  9. // 创建用户返回结果
  10. message CreateUserReply {}

给CreateUser添加路由,设置访问方法为PUT,在添加一个body消息主体

如果你不知道Body是什么,可以查阅这份文档来了解它:

《HTTP消息》(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages)

  1. // 用户服务
  2. service User {
  3. // 创建一个用户
  4. rpc CreateUser (CreateUserRequest) returns (CreateUserReply) {
  5. option(google.api.http) = {
  6. put:"/api/user"
  7. body:"creat_body"
  8. };
  9. };
  10. }

在创建用户请求参数的消息主体(creat body)中添加name和passwd

  1. // 创建用户请求参数
  2. message CreateUserRequest {
  3. message CreatBody{
  4. string name = 1;
  5. string passwd = 2;
  6. }
  7. CreatBody creat_body = 1;
  8. }

在创建用户返回结果中添加一个id,以用来代表用户创建成功而返回创建成功后的用户id

  1. // 创建用户返回结果
  2. message CreateUserReply {
  3. uint64 id = 1;
  4. }

生成proto源码

在终端中执行下面的命令

  1. # 在"api/user/v1"目录下,生成"user"的proto源码
  2. kratos proto client api/user/v1/user.proto

生成成功后就可以看到如下的文件树(*为生成的文件):

  1. ├── api
  2. └── user
  3. └── v1
  4. ├── user_grpc.pb.go*
  5. ├── user_http.pb.go*
  6. ├── user.pb.go*
  7. └── user.proto

可以发现kratos在”api/user/v1”目录下生成了”user.pb.go”,”user_http.pb.go”和”user_grpc.pb.go”三个文件

生成service模板

在终端中执行下面的命令

  1. kratos proto server api/user/v1/user.proto -t internal/service

执行后会生成”/internal/service/user.go”的server模板

文件树如下(*为生成的文件):

  1. ├── internal
  2. ......
  3. └── service
  4. ├── README.md
  5. ├── service.go
  6. └── user.go*

生成文件内容如下:/internal/service/user.go

  1. package service
  2. import (
  3. "context"
  4. pb "user/api/user/v1"
  5. )
  6. type UserService struct {
  7. pb.UnimplementedUserServer
  8. }
  9. func NewUserService() *UserService {
  10. return &UserService{}
  11. }
  12. func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserReply, error) {
  13. return &pb.CreateUserReply{}, nil
  14. }

我们在用户服务结构体“UserService”中添加上一节中封装好的用户数据操作“biz.UserUseCase”。(顺带加上日志log,虽然没用到)

  1. // UserService 用户服务
  2. type UserService struct {
  3. pb.UnimplementedUserServer
  4. // uc 用户操作的封装,在“/biz/user.go”中
  5. uc *biz.UserUseCase
  6. log *log.Helper
  7. }

修改“NewUserService”函数的内容,给创建的“UserService”结构体初始化

  1. func NewUserService(uc *biz.UserUseCase, logger log.Logger) *UserService {
  2. return &UserService{
  3. uc: uc,
  4. log: log.NewHelper(logger),
  5. }
  6. }

编写用户服务中的“CreateUser”服务接口,使得它能够处理前端发送的请求

  1. // CreateUser 创建用户服务接口
  2. // 这里是一个给前端调用的接口
  3. // 参数中的 req *pb.CreateUserRequest 为前端发送给后端的“创建用户请求参数”
  4. // 如果成功返回一个 *pb.CreateUserReply 它是前端需要得到的回复“创建用户返回结果”
  5. func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserReply, error) {
  6. // 获取前端发送的Body数据
  7. body := req.GetCreatBody()
  8. // 创建用户
  9. id, err := s.uc.CreateUser(ctx, body.GetName(), body.GetPasswd())
  10. if err != nil {
  11. return nil, err
  12. }
  13. // 给前端返回数据
  14. return &pb.CreateUserReply{
  15. Id: uint64(id),
  16. }, nil
  17. }

按照惯例,让我们吧“NewUserService”函数添加到依赖提供者集中

修改“/service/service.go”文件中的“wire.NewSet()”函数,在“NewSet”函数里添加“NewUserService”

  1. // ProviderSet is service providers.
  2. var ProviderSet = wire.NewSet(NewUserService)

注册HTTP服务器

在HTTP服务器中注册我们写好的用户服务

打开“/server/http.go”

引入kratos 使用proto生成的源码和我们写好的用户服务

  1. import (
  2. ......
  3. user "user/api/user/v1"
  4. "user/internal/service"
  5. )

在“NewHTTPServer”函数的参数中加入我们的用户服务“userService *service.UserService”,以便wire可以正确的注入依赖

  1. // NewHTTPServer new a HTTP server.
  2. func NewHTTPServer(c *conf.Server, userService *service.UserService, logger log.Logger) *http.Server {
  3. ......
  4. }

注册用户服务HTTP服务器

在“NewHTTPServer”函数的末尾加入“user.RegisterUserHTTPServer(srv, userService)”

  1. // NewHTTPServer new a HTTP server.
  2. func NewHTTPServer(c *conf.Server, userService *service.UserService, logger log.Logger) *http.Server {
  3. ......
  4. srv := http.NewServer(opts...)
  5. user.RegisterUserHTTPServer(srv, userService)
  6. return srv
  7. }

wire依赖注入

我们“按照惯例”的将所有创建结构体的函数都加入到wire的依赖提供者集中,但这是为啥呢?为什么要将创建结构体的函数都加入到wire的依赖提供者集中呢?如果你对wire依赖注入感兴趣不妨看一下这篇文章

《Go工程化 - 依赖注入》(https://go-kratos.dev/blog/go-project-wire)

在进行接下来的操作前,请务必确定你的wire依赖提供者集依照上文和前两节中的添加正确

在“/biz/biz.go”中

  1. // ProviderSet is biz providers.
  2. var ProviderSet = wire.NewSet(NewUserUseCase)

在“/data/data.go”中

  1. // ProviderSet is data providers.
  2. var ProviderSet = wire.NewSet(NewData, NewDataBase, NewUseRepo)

在“/service/service.go”中

  1. // ProviderSet is service providers.
  2. var ProviderSet = wire.NewSet(NewUserService)

在终端中输入命令

  1. # 进入项目目录
  2. cd user
  3. # 生成所有proto源码、wire等等
  4. go generate ./...

测试

首先要创建好一个用于测试的数据库!并且记好数据库的名称!

以及将“/data/data.go”中“NewDataBase”函数里的“dsn”数据库链接变量修改正确

不要无脑抄作业哦,根据自己的实际情况修改数据库链接

  1. // dsn 数据库链接
  2. // "用户名":"密码"@tcp("IP":"端口")/"数据库名称"?charset=utf8mb4&parseTime=True&loc=Local
  3. dsn := "test:test@tcp(localhost:3306)/kratos_demo?charset=utf8mb4&parseTime=True&loc=Local"

激动人心的时刻,我们来运行项目

在终端中输入命令

  1. # 运行项目
  2. kratos run

看到以下内容,我们这成功将项目运行起来了

4、Kratos v2 初步学习 4-编写第一个接口 - 图1

运行kratos项目

使用「Postman」对接口进行测试

我们根据上文中对“/api/user/v1/user.proto”编写的内容,使用”PUT”方法去访问 http://127.0.0.1:8000/api/user

修改”Body”类型为”JSON”编写消息主体”Body 的内容”为

  1. {
  2. "name": "test",
  3. "passwd": "test_passwd"
  4. }

点击”Send”发送请求

4、Kratos v2 初步学习 4-编写第一个接口 - 图2

使用Postman测试接口

我们可以看到 http://localhost:8000/api/user 接口返回了一个内容

  1. {
  2. "id": "1"
  3. }

使用DataGrip、Navicat Premium等等数据库可视化软件查看数据库,当然只要你喜欢,你也可以使用mysql命令行

查看测试使用的数据库

4、Kratos v2 初步学习 4-编写第一个接口 - 图3

使用DataGrip查看数据库

可以看到,数据库中已经正确的创建了数据

到此,就大功告成了

到此,就大功告成了

到此,就大功告成了

完成本节后你将得到如下代码

文件: /api/user/v1/user.proto

  1. syntax = "proto3";
  2. package api.user.v1;
  3. import "google/api/annotations.proto";
  4. option go_package = "user/api/user/v1;v1";
  5. option java_multiple_files = true;
  6. option java_package = "api.user.v1";
  7. // 用户服务
  8. service User {
  9. // 创建一个用户
  10. rpc CreateUser (CreateUserRequest) returns (CreateUserReply) {
  11. option(google.api.http) = {
  12. put:"/api/user"
  13. body:"creat_body"
  14. };
  15. };
  16. }
  17. // 创建用户请求参数
  18. message CreateUserRequest {
  19. message CreatBody{
  20. string name = 1;
  21. string passwd = 2;
  22. }
  23. CreatBody creat_body = 1;
  24. }
  25. // 创建用户返回结果
  26. message CreateUserReply {
  27. uint64 id = 1;
  28. }

文件: /service/user.go

  1. // “service”目录下的文件是通过“/api”下的“*/proto”文件自动生成的
  2. // 需要修改“UserService”的函数来完成消息的处理
  3. package service
  4. import (
  5. "context"
  6. "github.com/go-kratos/kratos/v2/log"
  7. pb "user/api/user/v1"
  8. "user/internal/biz"
  9. )
  10. // UserService 用户服务
  11. type UserService struct {
  12. pb.UnimplementedUserServer
  13. // uc 用户操作的封装,在“/biz/user.go”中
  14. uc *biz.UserUseCase
  15. log *log.Helper
  16. }
  17. func NewUserService(uc *biz.UserUseCase, logger log.Logger) *UserService {
  18. return &UserService{
  19. uc: uc,
  20. log: log.NewHelper(logger),
  21. }
  22. }
  23. // CreateUser 创建用户服务
  24. // 这里是一个给前端调用的接口
  25. // 参数中的 req *pb.CreateUserRequest 为前端发送给后端的“创建用户请求参数”
  26. // 如果成功返回一个 *pb.CreateUserReply 它是前端需要得到的回复“创建用户返回结果”
  27. func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserReply, error) {
  28. // 获取前端发送的Body数据
  29. body := req.GetCreatBody()
  30. // 创建用户
  31. id, err := s.uc.CreateUser(ctx, body.GetName(), body.GetPasswd())
  32. if err != nil {
  33. return nil, err
  34. }
  35. // 给前端返回数据
  36. return &pb.CreateUserReply{
  37. Id: uint64(id),
  38. }, nil
  39. }

文件: /server/http.go

  1. package server
  2. import (
  3. "github.com/go-kratos/kratos/v2/log"
  4. "github.com/go-kratos/kratos/v2/middleware/recovery"
  5. "github.com/go-kratos/kratos/v2/transport/http"
  6. user "user/api/user/v1"
  7. "user/internal/conf"
  8. "user/internal/service"
  9. )
  10. // NewHTTPServer new a HTTP server.
  11. func NewHTTPServer(c *conf.Server, userService *service.UserService, logger log.Logger) *http.Server {
  12. var opts = []http.ServerOption{
  13. http.Middleware(
  14. recovery.Recovery(),
  15. ),
  16. }
  17. if c.Http.Network != "" {
  18. opts = append(opts, http.Network(c.Http.Network))
  19. }
  20. if c.Http.Addr != "" {
  21. opts = append(opts, http.Address(c.Http.Addr))
  22. }
  23. if c.Http.Timeout != nil {
  24. opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
  25. }
  26. srv := http.NewServer(opts...)
  27. user.RegisterUserHTTPServer(srv, userService)
  28. return srv
  29. }

参考文档

《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