Kratos 实现用户添加功能

Kratos 实现用户添加功能

这里就写一个简单的用户创建服务

项目初始化

** 创建项目模板**

# 使用默认模板创建项目
kratos new user

# 如在国内环境拉取失败, 可 -r 指定源
kratos new user -r https://gitee.com/go-kratos/kratos-layout.git

cd user

拉取项目依赖

go mod tidy

获取一个纯原生,无添加的代码框架

  1. 删除api/helloworld/,使api文件为空

  2. 删除internal/biz/greeter.go

  3. 打开internal/biz/biz.go,将报红内容删除
    在这里插入图片描述

  4. 删除internal/data/greeter.go

  5. 打开internal/data/data.go,将报红内容删除
    在这里插入图片描述

  6. 删除internal/service/greeter.go

  7. 打开internal/service/service.go,将报红内容删除
    在这里插入图片描述

连接数据库

安装gorm

# 进入项目目录
cd user
# 安装GORM
go get -u gorm.io/gorm
# 安装GORM的mysql驱动
go get -u gorm.io/driver/mysql

使用gorm

服务内部目录中的data负责业务数据访问,包含 cache、db 等封装,实现了 biz 的 repo 接口。

我们所有的处理数据逻辑全部写在internal/data

  • 打开/internal/data.go

在这里插入图片描述

我们可以看到一行显眼的TODO,它示意我们将数据库客户端添加到这里

// Data 封装的数据库客户端
type Data struct {
    DataBase *gorm.DB //数据库连接
}

NewData函数中添加*gorm.DB参数使得我们创建出来的Data结构体中带有数据库客户端

在这里插入图片描述

新建一个NewDataBase函数去连接mysql并且返回*gorm.DB

func NewDataBase(c *conf.Data) (*gorm.DB, error) {
    dsn := "root:123456@tcp(127.0.0.1:3306)/2201a?charset=utf8mb4&parseTime=True&loc=Local"
    // 连接数据库
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       return nil, err
    }

    return db, nil
}
  • 修改/data/data.go文件中的wire.NewSet()函数,在NewSet函数里添加刚刚写得NewDataBase

完整代码展示

文件/internal/data/data.go

package data

import (
    "gorm.io/driver/mysql"
    "user/internal/biz"
    "user/internal/conf"

    "github.com/go-kratos/kratos/v2/log"
    "github.com/google/wire"
    "gorm.io/gorm"
)

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

// Data 封装的数据库客户端
type Data struct {
    DataBase *gorm.DB //数据库连接
}

// NewData .在NewData函数中添加*gorm.DB参数使得我们创建出来的Data结构体中带有数据库客户端
func NewData(c *conf.Data, logger log.Logger, db *gorm.DB) (*Data, func(), error) {
    cleanup := func() {
       log.NewHelper(logger).Info("closing the data resources")
    }
    return &Data{
       DataBase: db,
    }, cleanup, nil
}

// NewDataBase 初始化数据库
func NewDataBase(c *conf.Data) (*gorm.DB, error) {
    dsn := "root:123456@tcp(127.0.0.1:3306)/2201a?charset=utf8mb4&parseTime=True&loc=Local"
    // 连接数据库
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       return nil, err
    }

    return db, nil
}

模型定义

自己定义一个用户的数据模型,用于对数据库进行操作

  • 新建:在/biz目录下创建一个名为user.go的文件

internal/biz/user.go文件里添加如下内容

// User 用户信息
type User struct {
    gorm.Model
    Mobile   string `gorm:"varchar(15);not nul;comment:账号或手机号"`
    NickName string `gorm:"varchar(30);not null;comment:昵称"`
    Password string `gorm:"varchar(150);not null;comment:密码"`
    Birthday int32  `gorm:"int(10);not null;comment:生日"`
    Gender   int32  `gorm:"tinyint;not null;comment:性别(0男 1女 2人妖)"`
    Grade    int32  `gorm:"tinyint;not null;default:0;comment:等级(0普通游客 1会员 2商家 3管理)"`
}

为了让GORM知道我们定义了一个数据模型我们需要对internal/data/data.go文件进行修改

在这里插入图片描述

到这里,我们就完成了用户数据模型的定义,并且让GORM知道我们定义了这个数据模型

repo接口的定义

在repo接口中,我们需要定义一些基础操作数据库修改数据的函数

  • 回到internal/biz/user.go文件,继续敲代码
// UserRepo 将“用户数据模型”在数据库上的基础操作封装为接口
type UserRepo interface {
    // CreateUser 创建用户
    // 参数 model 为 User用户数据模型
    CreateUser(ctx context.Context, model *User) (uint, error)
}

data层中实现repo接口

  • /data目录下创建user.go文件,继续码代码

我们创建一个私有的结构体去实现/biz/user.go中的biz.UserRepo接口

//私有结构体
type userRepo struct {
    data *Data // 数据库客户端的集合
    log  *log.Helper
}
  • 编写一个NewUseRepo函数来创建结构体userRepo
// 只要我们的私有结构体userRepo实现了“/biz/user.go”内 biz.UserRepo 接口内的方法
// 那么 userRepo 就是“继承”了 biz.UserRepo
func NewUseRepo(data *Data, logger log.Logger) biz.UserRepo {
   return &userRepo{
      data: data,
      log:  log.NewHelper(logger),
   }
}

这个时候ide是不是已经爆红报错了?安心咯,这只是因为我们的userRepo还没有实现biz.UserRepo接口内的方法

现在我们给userRepo实现这个方法

  • /data/user.go的添加
// CreateUser 创建用户  
func (u userRepo) CreateUser(cxt \*context.Context, model \*biz.User) (uint, error) {  
    // 数据库操作  
    tx := u.data.DataBase.Create(model)  
    if tx.Error != nil {  
       return 0, tx.Error  
    }  
    // 返回数据  
    return model.ID, nil  
}
  • 修改/data/data.go文件中的wire.NewSet()函数,在NewSet函数里添加NewUseRepo
// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewDataBase, NewUseRepo)

逻辑处理

对用户的操作封装成UserUseCase结构体

  • 回到/biz/user.go添加UserUseCase结构体
// UserUseCase 将对用户的操作封装
type UserUseCase struct {
}

在结构体中添加repo成员,其类型是我们编写的repo接口

在结构体中添加log成员,*log.Helper类型,虽然在这里不会使用到,但也还是加上

// UserUseCase 将对用户的操作封装
type UserUseCase struct {
    repo UserRepo // 我们需要使用Repo来对数据库进行基础操作
    log  *log.Helper
}

编写一个NewUserUseCase去创建一个UserUseCase,并将它返回,以便于之后的wine依赖注入

// NewUserUseCase 创建用户信息
func NewUserUseCase(repo UserRepo, logger log.Logger) *UserUseCase {
    return &UserUseCase{
       repo: repo,
       log:  log.NewHelper(logger),
    }
}

以上的杂七杂八编写完后,接下来我们要正是开始编写逻辑代码!!!

  • /biz/user.go中继续编写,创建一个UserUseCase的方法(成员函数),命名为CreateUser
func (c *UserUseCase) CreateUser(ctx context.Context,mobile, name, passwd string) (uint, error) {
}

根据我们上面定义的模型,添加用户信息我们只需要接受mobile手机号、name名称(昵称)、passwd密码即可

接下来我们在CreateUser方法中创建一个用户数据模型,并且调用UserRepo去完成用户数据的创建

// CreateUser 创建用户
func (c *UserUseCase) CreateUser(ctx *context.Context, mobile, name, passwd string) (uint, error) {
    // 将“创建用户”函数的参数转换为“用户数据模型”
    u := &User{
       Mobile:   mobile,
       NickName: name,
       Password: passwd,
    }

    // 调用UserRepo接口实现中的CreateUser函数去创建用户
    return c.repo.CreateUser(ctx, u)
}

然后,没了,业务逻辑写完了。没错就是这么少,多来几个业务就多了,不着急

  • 最后修改/biz/biz.go文件中的wire.NewSet()函数,在NewSet函数里添加NewUserUseCase
    在这里插入图片描述

完整代码示例

文件: /data/data.go

package data

import (
    "gorm.io/driver/mysql"
    "user/internal/biz"
    "user/internal/conf"

    "github.com/go-kratos/kratos/v2/log"
    "github.com/google/wire"
    "gorm.io/gorm"
)

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

// Data 封装的数据库客户端
type Data struct {
    DataBase *gorm.DB //数据库连接
}

// NewData .在NewData函数中添加*gorm.DB参数使得我们创建出来的Data结构体中带有数据库客户端
func NewData(c *conf.Data, logger log.Logger, db *gorm.DB) (*Data, func(), error) {
    cleanup := func() {
       log.NewHelper(logger).Info("closing the data resources")
    }
    return &Data{
       DataBase: db,
    }, cleanup, nil
}

// NewDataBase 初始化数据库
func NewDataBase(c *conf.Data) (*gorm.DB, error) {
    dsn := "root:123456@tcp(127.0.0.1:3306)/2201a?charset=utf8mb4&parseTime=True&loc=Local"
    // 连接数据库
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       return nil, err
    }
    // 自动迁移
    db.AutoMigrate(&biz.User{})

    return db, nil
}

文件: /data/user.go

package data  
  
import (  
    "context"  
    "github.com/go-kratos/kratos/v2/log"    "user/internal/biz")  
  
// userRepo 实现 biz.UserRepo 接口  
// 私有的结构体  
// 对“/biz/user.go”中的UserRepo进行实现,  
// 完成对“用户数据模型”的数据库基础操作。  
type userRepo struct {  
    data \*Data // 数据库客户端的集合  
    log  \*log.Helper  
}  
  
// CreateUser 创建用户  
func (u userRepo) CreateUser(cxt \*context.Context, model \*biz.User) (uint, error) {  
    // 数据库操作  
    tx := u.data.DataBase.Create(model)  
    if tx.Error != nil {  
       return 0, tx.Error  
    }  
    // 返回数据  
    return model.ID, nil  
}  
  
func NewUseRepo(data \*Data, logger log.Logger) biz.UserRepo {  
    return &userRepo{  
       data: data,  
       log:  log.NewHelper(logger),  
    }  
}

文件: /biz/user.go

package biz  
  
import (  
    "context"  
    "github.com/go-kratos/kratos/v2/log"    "gorm.io/gorm")  
  
// User 用户信息  
type User struct {  
    gorm.Model  
    Mobile   string \`gorm:"varchar(15);not nul;comment:账号或手机号"\`  
    NickName string \`gorm:"varchar(30);not null;comment:昵称"\`  
    Password string \`gorm:"varchar(150);not null;comment:密码"\`  
    Birthday int32  \`gorm:"int(10);not null;comment:生日"\`  
    Gender   int32  \`gorm:"tinyint;not null;comment:性别(0男 1女 2人妖)"\`  
    Grade    int32  \`gorm:"tinyint;not null;default:0;comment:等级(0普通游客 1会员 2商家 3管理)"\`  
}  
  
// UserRepo 用户信息仓库  
type UserRepo interface {  
    // CreateUser 创建用户  
    CreateUser(cxt \*context.Context, model \*User) (uint, error)  
}  
  
// UserUseCase 将对用户的操作封装  
type UserUseCase struct {  
    repo UserRepo  
    log  \*log.Helper  
}  
  
// NewUserUseCase 创建用户信息  
func NewUserUseCase(repo UserRepo, logger log.Logger) \*UserUseCase {  
    return &UserUseCase{  
       repo: repo,  
       log:  log.NewHelper(logger),  
    }  
}  
  
// CreateUser 创建用户  
func (uc \*UserUseCase) CreateUser(ctx \*context.Context, mobile, name, passwd string) (uint, error) {  
    // 将“创建用户”函数的参数转换为“用户数据模型”  
    u := &User{  
       Mobile:   mobile,  
       NickName: name,  
       Password: passwd,  
    }  
  
    // 调用UserRepo接口实现中的CreateUser函数去创建用户  
    return uc.repo.CreateUser(ctx, u)  
}

编写 proto 模板

  • 引入google/api/annotations.proto

编写一个接口

生成proto模板

打开终端,执行以下指令

# 进入"user"项目目录
cd user
# 在"api/user/v1"目录下,生成名为"user"的proto模板
kratos proto add api/user/v1/user.proto

进入到api中,我们会看到以下目录结构,* 为生成文件

├── api
│   └── user
│       └── v1
│           └── user.proto*

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

  
syntax \= "proto3";  
  
// 定义包名  
package api.user.v1;  
  
// 定义服务  
option go\_package \= "user1/api/user/v1;v1";  
// 生成java文件  
option java\_multiple\_files \= true;  
// 生成java文件的包名  
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 模板

  • 引入google/api/annotations.proto

在这里插入图片描述

  • 因为我们只写了用户添加模块,所以保留CreateUser,将生成的user.proto文件中其他接口删除

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

// 用户服务
service User {
  rpc CreateUser (CreateUserRequest) returns (CreateUserReply){
   option (google.api.http) = {
    post: "/v1/user",
	body:"creat_body",
   };
  };
}
  • 在创建用户请求参数的消息主体(creat body)中添加name、passwd和mobile
// 创建用户请求参数
message CreateUserRequest {
  message CreatBody{
   string mobile = 1;
   string password = 2;
   string nick_name = 3;
  }
  CreatBody creat_body = 1;
}
  • 在创建用户返回结果中添加一个id,以用来代表用户创建成功而返回创建成功后的用户id
message CreateUserReply {
  int64 id = 1;
}

生成proto源码

打开终端,执行以下指令

# 在"api/user/v1"目录下,生成"user"的proto源码
kratos proto client api/user/v1/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依赖提供者集依照上文和前两节中的添加正确

  • 在“/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 ./...

测试

根据上面的步骤来到这里,我们的用户添加操作就完成了。接下来我们要对自己写的接口进行测试,看看是否能跑通

在终端中输入命令

# 运行项目
kratos run

看到以下内容就可以了

在这里插入图片描述

这里我们要注意,8000是http访问的端口号,9000是grpc的端口号

在这里我使用的是ApiPost7对接口进行测试,使用POST访问127.0.0.1:8000/v1/user(grpc访问就直接访问127.0.0.1:9000即可)

在这里插入图片描述

查看数据库数据
在这里插入图片描述

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值