文章目录
Kratos 实现用户添加功能
这里就写一个简单的用户创建服务
项目初始化
** 创建项目模板**
# 使用默认模板创建项目
kratos new user
# 如在国内环境拉取失败, 可 -r 指定源
kratos new user -r https://gitee.com/go-kratos/kratos-layout.git
cd user
拉取项目依赖
go mod tidy
获取一个纯原生,无添加的代码框架
-
删除
api/helloworld/
,使api文件为空 -
删除
internal/biz/greeter.go
-
打开
internal/biz/biz.go
,将报红内容删除
-
删除
internal/data/greeter.go
-
打开
internal/data/data.go
,将报红内容删除
-
删除
internal/service/greeter.go
-
打开
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即可)
查看数据库数据