本文的原文连接是:
https://blog.csdn.net/freewebsys/article/details/124262158
未经博主允许不得转载。
博主地址是:http://blog.csdn.net/freewebsys
Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关框架及工具。
https://go-kratos.dev/docs/
网络上面的demo并不全面,从头来一点点进行研究学习。
学到老活到老。
golang 需要使用1.18 的最新版本进行开发吧。
然后安装kratos 工具:
国内下载:
https://golang.google.cn/dl/
语法中文学习:
https://www.runoob.com/go/go-tutorial.html
golang install 加速:
https://goproxy.io/zh/
#kratos 基础工具命令
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest#golang grpc 特别好的工具,可以直接调用grpc 服务go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latestkratos new kratos-crud
cd kratos-crud
然后使用 make 工具,提供几个常用的功能,先把环境创建好。
make Usage:make [target]Targets:
init init env
config generate internal proto
api generate api proto
build build
generate generate
all generate all
help show help
protobuffer 文档:
https://developers.google.cn/protocol-buffers/docs/proto3
在 ubuntu系统上安装protoc 工具,默认的工具是3.0.0的版本太低了。
否则报错误找不到
conf/conf.proto:22:5: "google.protobuf.Duration" is not defined.
重新安装 prototc 解决问题,估计是新的库中才有google的proto文件定义。
源码下载地址:
git clone https://github.com/protocolbuffers/protobuf.git安装依赖的库:
sudo install -y autoconf automake libtool curl make g++ unzip 安装:
./autogen.sh
./configure
make && sudo make install
# 然后使用这个版本就可以了。
protoc --version
libprotoc 3.20.1-rc1
然后在执行 make all 就可以了:
go mod vendor
make all
有的时候 wire 不好用,可以直接修改成 wire 命令:
# makefile 的部分代码修改下命令:
.PHONY: generate
# generate
generate:go mod tidygo mod vendorcd cmd/kratos-crud/ && wire
项目启动特别简单:使用kratos 命令就行
$ kratos run
INFO msg=config loaded: config.yaml format: yaml
INFO msg=[gRPC] server listening on: [::]:9000
INFO msg=[HTTP] server listening on: [::]:8000
可以看到grpc 服务在9000 端口,http 在8000 端口
可以使用上面的工具命令进行安装:
#golang grpc 特别好的工具,可以直接调用grpc 服务
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
然后就可以使用了。进行grpc 服务接口的查看,调用。
#查看改地址端口服务
$ grpcurl -plaintext 127.0.0.1:9000 list
grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
helloworld.v1.Greeter
kratos.api.Metadata#查看函数
$ grpcurl -plaintext 127.0.0.1:9000 list helloworld.v1.Greeter
helloworld.v1.Greeter.SayHello#查看函数描述
$ grpcurl -plaintext 127.0.0.1:9000 describe helloworld.v1.Greeter.SayHello
helloworld.v1.Greeter.SayHello is a method:
rpc SayHello ( .helloworld.v1.HelloRequest ) returns ( .helloworld.v1.HelloReply ) {option (.google.api.http) = { get:"/helloworld/{name}" };
}#查看入参
$ grpcurl -plaintext 127.0.0.1:9000 describe .helloworld.v1.HelloRequest
helloworld.v1.HelloRequest is a message:
message HelloRequest {string name = 1;
}#调用函数,并返回
$ grpcurl -d '{"name": "zhangsan"}' -plaintext 127.0.0.1:9000 helloworld.v1.Greeter.SayHello
{"message": "Hello zhangsan"
}
https://gorm.io/zh_CN/docs/index.html
创建数据库,使用 gorm 操作数据库CRUD
使用root账号创建数据库和用户使用 demo demo 账号登录。
CREATE DATABASE IF NOT EXISTS demo DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
grant all privileges on demo.* to demo@'%' identified by 'demo';# 创建 userInfo 的用户表:
CREATE TABLE `user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_name` varchar(225) NOT NULL,`password` varchar(225) DEFAULT NULL,`age` tinyint(4) DEFAULT NULL,`phone` varchar(20) DEFAULT NULL,`address` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
发现在插入数据的时候偶尔比较慢,使用连接池解决:
2022/04/24 21:23:12 /media/test/NewDisk1/go/src/kratos-crud/internal/data/user_info_repo.go:24 SLOW SQL >= 200ms
[489.453ms] [rows:1] INSERT INTO `user_info` (`user_name`,`password`,`age`,`phone`,`address`) VALUES ('','',0,'','')
设置连接池,解决链接问题,但在更新的时候偶尔还是会有,已经比之前好多了。
sqlDB.SetMaxIdleConns(int(c.Database.MaxIdleConns))// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.sqlDB.SetMaxOpenConns(int(c.Database.MaxOpenConns))// SetMaxOpenConns sets the maximum number of open connections to the database.sqlDB.SetConnMaxLifetime(time.Second * time.Duration(c.Database.ConnMaxLifetime))// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.// 配置10 100 600 加大链接数量。
首先定义 biz 的接口:
package bizimport ("context"v1 "kratos-crud/api/helloworld/v1""time""github.com/go-kratos/kratos/v2/errors""github.com/go-kratos/kratos/v2/log"
)var (// ErrUserNotFound is user not found.ErrUserNotFound = errors.NotFound(v1.ErrorReason_USER_NOT_FOUND.String(), "user not found")
)/**
CREATE TABLE `user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(225) NOT NULL,
`password` varchar(225) DEFAULT NULL,
`age` tinyint(4) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
*/
// UserInfo model.
type UserInfo struct {Id int64UserName stringPassword stringAge uint32Phone stringAddress stringCreatedAt time.Time `gorm:"not null"`UpdatedAt time.Time `gorm:"not null"`
}func (UserInfo) TableName() string {return "user_info"
}// UserInfoRepo is a Greater repo.
type UserInfoRepoIf interface {Save(context.Context, *UserInfo) (*UserInfo, error) // save or updateDelete(context.Context, int64) errorFindByID(context.Context, int64) (*UserInfo, error)FindAll(context.Context) ([]*UserInfo, error)
}// UserInfoUsecase is a UserInfo usecase.
type UserInfoUsecase struct {repo UserInfoRepoIflog *log.Helper
}// NewUserInfoUsecase new a UserInfo usecase.
func NewUserInfoUsecase(repo UserInfoRepoIf, logger log.Logger) *UserInfoUsecase {return &UserInfoUsecase{repo: repo, log: log.NewHelper(logger)}
}// SaveUserInfo
func (uc *UserInfoUsecase) SaveUserInfo(ctx context.Context, user *UserInfo) (*UserInfo, error) {uc.log.WithContext(ctx).Infof("CreateUserInfo: %v", user.UserName)return uc.repo.Save(ctx, user)
}// DeleteUserInfo
func (uc *UserInfoUsecase) DeleteUserInfo(ctx context.Context, id int64) error {uc.log.WithContext(ctx).Infof("DeleteUserInfo: %v", id)return uc.repo.Delete(ctx, id)
}// FindUserInfoByID
func (uc *UserInfoUsecase) FindUserInfoByID(ctx context.Context, id int64) (*UserInfo, error) {uc.log.WithContext(ctx).Infof("FindUserInfoByID: %v", id)return uc.repo.FindByID(ctx, id)
}// FindAllUserInfo
func (uc *UserInfoUsecase) FindAllUserInfo(ctx context.Context) ([]*UserInfo, error) {uc.log.WithContext(ctx).Infof("FindAllUserInfo ")return uc.repo.FindAll(ctx)
}
然后对 user 表进行CRUD
package dataimport ("context""kratos-crud/internal/biz""github.com/go-kratos/kratos/v2/log"
)type userInfoRepo struct {data *Datalog *log.Helper
}// NewUserInfoRepo .
func NewUserInfoRepo(data *Data, logger log.Logger) biz.UserInfoRepoIf {return &userInfoRepo{data: data,log: log.NewHelper(logger),}
}func (repo *userInfoRepo) Save(ctx context.Context, userInfo *biz.UserInfo) (*biz.UserInfo, error) {repo.log.Debug(" Save result :", userInfo) // 返回 errorif userInfo.Id > 0 { //更新 userInfo := biz.UserInfo{}var oldUserInfo biz.UserInforesult1 := repo.data.db.First(&oldUserInfo, "id = ? ", userInfo.Id) // 通过id进行数据查询repo.log.Debug("result.Error :", result1.Error) // 返回 errorrepo.log.Debug("save userInfo :", userInfo)if result1.Error != nil { // 有错误返回return userInfo, result1.Error} else {oldUserInfo.UserName = userInfo.UserNameoldUserInfo.Password = userInfo.PasswordoldUserInfo.Age = userInfo.AgeoldUserInfo.Phone = userInfo.PhoneoldUserInfo.Address = userInfo.Addressresult := repo.data.db.Save(&oldUserInfo) // 通过数据的指针来创建repo.log.Debug("result.Error :", result.Error) // 返回 errorrepo.log.Debug("save userInfo :", userInfo)return userInfo, result1.Error}} else { // 创建result := repo.data.db.Create(userInfo) // 通过数据的指针来创建repo.log.Debug("result.Error :", result.Error) // 返回 errorrepo.log.Debug("save userInfo :", userInfo)return userInfo, result.Error}
}func (repo *userInfoRepo) Update(ctx context.Context, userInfo *biz.UserInfo) error {repo.log.Debug("Update :", userInfo) // 返回return nil
}func (repo *userInfoRepo) Delete(ctx context.Context, id int64) error {repo.log.Debug("Delete By Id :", id) // 返回result := repo.data.db.Delete(&biz.UserInfo{Id: id}) // 通过id删除数据repo.log.Debug("result.Error :", result.Error) // 返回 errorreturn result.Error
}func (repo *userInfoRepo) FindByID(ctx context.Context, id int64) (*biz.UserInfo, error) {repo.log.Debug("FindByID :", id) // 返回userInfo := biz.UserInfo{}result := repo.data.db.First(&userInfo, "id = ? ", id) // 通过id进行数据查询repo.log.Debug("result.Error :", result.Error) // 返回 errorreturn &userInfo, result.Error
}func (repo *userInfoRepo) FindAll(ctx context.Context) ([]*biz.UserInfo, error) {repo.log.Debug("FindAll :") //var userInfoList []*biz.UserInforesult := repo.data.db.Find(&userInfoList) // 通过数据查询repo.log.Debug("result.Error :", result.Error) // 返回 errorreturn userInfoList, result.Error
}
可以使用GORM 对数据进行CRUD,其中save 方法比较特殊。判断id,然后执行create 或者save 执行更新操作。
首先定义protoc 接口:
syntax = "proto3";package demo.v1;import "google/api/annotations.proto";option go_package = "kratos-crud/api/demo/v1;v1";
option java_multiple_files = true;
option java_package = "api.demo.v1";
option java_outer_classname = "UserInfoV1";// The UserInfo service definition.
service UserInfoService {rpc Save(UserInfo) returns (CommReply) {option (google.api.http) = {post: "/userInfo/save",body: "*"};}rpc Delete(IdRequest) returns (CommReply) {option (google.api.http) = {post: "/userInfo/delete",body: "*"};}rpc Get(IdRequest) returns (UserInfoReply) {option (google.api.http) = {get: "/userInfo/get/{id}"};}rpc List(ListRequest) returns (ListUserInfoReply) {option (google.api.http) = {post: "/userInfo/list",body: "*"};}}
// https://developers.google.cn/protocol-buffers/docs/proto3
// 定义一个公用类型
message UserInfo {int64 id = 1;string userName = 2;string password = 3;uint32 age = 4;string phone = 5;string address = 6;
}message IdRequest {int64 id = 1;
}message ListRequest {string name = 1;
}// return replay
message CommReply {int64 code = 1;string message = 2;
}message UserInfoReply {int64 code = 1;string message = 2;UserInfo userInfo = 3;
}message ListUserInfoReply {int64 code = 1;string message = 2;repeated UserInfo userInfoList = 3;
}
其中保存,删除,查询使用POST方法,查询单个使用GET方法。当然删除也可以使用delete方法。
然后是service实现如下:
package serviceimport ("context"v1 "kratos-crud/api/helloworld/v1""kratos-crud/internal/biz""strconv""github.com/go-kratos/kratos/v2/log"
)// UserInfoService is a UserInfo service.
type UserInfoService struct {v1.UnimplementedUserInfoServiceServeruc *biz.UserInfoUsecaselog *log.Helper
}// NewUserInfoService new a UserInfo service.
func NewUserInfoService(uc *biz.UserInfoUsecase, logger log.Logger) *UserInfoService {log := log.NewHelper(logger)return &UserInfoService{uc: uc, log: log}
}// Save
func (s *UserInfoService) Save(ctx context.Context, in *v1.UserInfo) (*v1.CommReply, error) {s.log.Info(in.GetUserName())userInfo := biz.UserInfo{}userInfo.Id = in.GetId()userInfo.UserName = in.GetUserName()userInfo.Password = in.GetPassword()userInfo.Age = in.GetAge()userInfo.Phone = in.GetPhone()userInfo.Address = in.GetAddress()g, err := s.uc.SaveUserInfo(ctx, &userInfo)if err != nil {return nil, err}return &v1.CommReply{Message: "Hello " + g.UserName}, nil
}// Delete
func (s *UserInfoService) Delete(ctx context.Context, in *v1.IdRequest) (*v1.CommReply, error) {err := s.uc.DeleteUserInfo(ctx, in.GetId())if err != nil {return nil, err}return &v1.CommReply{Message: "Delete " + strconv.FormatInt(in.GetId(), 10)}, nil
}// Get
func (s *UserInfoService) Get(ctx context.Context, in *v1.IdRequest) (*v1.UserInfoReply, error) {userInfo, err := s.uc.FindUserInfoByID(ctx, in.GetId())if err != nil {return nil, err}// 对象转换userInfoReply := &v1.UserInfo{Id: userInfo.Id,UserName: userInfo.UserName,Password: userInfo.Password,Age: userInfo.Age,Phone: userInfo.Phone,Address: userInfo.Address,}return &v1.UserInfoReply{Message: "Get " + userInfo.UserName, UserInfo: userInfoReply}, nil
}// List
func (s *UserInfoService) List(ctx context.Context, in *v1.ListRequest) (*v1.ListUserInfoReply, error) {userInfoList, err := s.uc.FindAllUserInfo(ctx)if err != nil {return nil, err}// log.Info(userList)var userInfoReplyList []*v1.UserInfo// 循环转换对象。for _, userInfo := range userInfoList {userInfoReplyList = append(userInfoReplyList,&v1.UserInfo{Id: userInfo.Id,UserName: userInfo.UserName,Password: userInfo.Password,Age: userInfo.Age,Phone: userInfo.Phone,Address: userInfo.Address,})}return &v1.ListUserInfoReply{Message: "List UserInfo", UserInfoList: userInfoReplyList}, nil
}
这里使用了一个特殊方法进行测试,就是vscode的 rest api 插件,保存一个 .rest 文件,然后直接执行就可以了:
### 增加数据
POST http://localhost:8000/userInfo/save HTTP/1.1
content-type: application/json{"userName":"Hendry","password":"pwd123456","age":25,"phone":"13811223344","address":"北京王府井1号"
}### 删除数据
POST http://localhost:8000/userInfo/delete HTTP/1.1
content-type: application/json{"id":1
}### 更新
POST http://localhost:8000/userInfo/save HTTP/1.1
content-type: application/json{"id":2,"userName":"HendryNew","password":"pwd123456New","age":35,"phone":"13811223344New","address":"北京王府井1号New"
}### 按Id查询
GET http://localhost:8000/userInfo/get/2 HTTP/1.1
content-type: application/json### 查询全部
POST http://localhost:8000/userInfo/list HTTP/1.1
content-type: application/json{"id":200
}
使用情况:
经过几天研究,终于完成了kratos的CRUD操作,解决若干问题。并上传了代码到github上。
发现网上的代码都是很简单的CRUD,并不全面。
完善了CURD的demo。
本文的原文连接是:
https://blog.csdn.net/freewebsys/article/details/124262158
博主地址是:https://blog.csdn.net/freewebsys