kratos使用日记

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kratos使用日记相关的知识,希望对你有一定的参考价值。

参考技术A kratos是bilibili的一个微服务开源框架,里面封装了很多功能,可以很方便的进行grpc和http接口的开发。

kratos 生成 proto文件名.pb.validate.go 文件的时候会同时生成参数验证的相关方法,

// Server is an server logging middleware.

放入中间件

验证是否登录,身份信息等,同时向上下文传递用户身份信息。
// Token is an token login middleware.

根据kratos官方文档操作:
https://go-kratos.dev/docs/component/metadata
坑:metadata默认名字问题

1.服务端元数据的名字必须是x-md-开头

2.客户端元数据的名字必须是x-md-global-开头

如果不想使用kratos官方的元数据获取方式,也可以自己实现这两个方法

链路追踪可以看到服务的整个调用链路,可以很好的排查问题,当然有一定的性能损耗。
准备工作:本地装好jaeger
1.先安装好docker
2.运行下面的命令获取jaeger镜像并启动一个本地的容器
docker run -d -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest
3.客户端上报地址
http://127.0.0.1:14268/api/traces

4.正常启动容器后浏览器访问
http://localhost:16686/
可以看到一个前端界面

引入中间件

向服务发送http请求,链路就会被采集,打开刚刚的前端界面,可以看到:

引入插件:
import "github.com/go-kratos/swagger-api/openapiv2"

安装protoc插件
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2

然后执行命令 protoc -I . --openapiv2_out ./ --openapiv2_opt logtostderr=true --openapiv2_opt json_names_for_fields=false xxxx文件名.proto
会在当前目录生成文档

kratos学习从零使用kratos创建一个CRUD的demo,是gorm做数据库操作,经过几天研究,调试代码开源放到github上,还是非常方便的。服务也实现了CRUD的http接口。

目录

前言


本文的原文连接是:
https://blog.csdn.net/freewebsys/article/details/124262158

未经博主允许不得转载。
博主地址是:http://blog.csdn.net/freewebsys

1,关于kratos


Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关框架及工具。

https://go-kratos.dev/docs/

网络上面的demo并不全面,从头来一点点进行研究学习。
学到老活到老。

2,使用


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@latest

kratos 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 tidy
	go mod vendor
	cd 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 端口

4,使用 grpcul进行服务查看调用


可以使用上面的工具命令进行安装:
#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"

5,创建数据CRUD使用gorm


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 biz

import (
	"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        int64
	UserName  string
	Password  string
	Age       uint32
	Phone     string
	Address   string
	CreatedAt 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 update
	Delete(context.Context, int64) error
	FindByID(context.Context, int64) (*UserInfo, error)
	FindAll(context.Context) ([]*UserInfo, error)


// UserInfoUsecase is a UserInfo usecase.
type UserInfoUsecase struct 
	repo UserInfoRepoIf
	log  *log.Helper


// NewUserInfoUsecase new a UserInfo usecase.
func NewUserInfoUsecase(repo UserInfoRepoIf, logger log.Logger) *UserInfoUsecase 
	return &UserInfoUsecaserepo: 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 data

import (
	"context"
	"kratos-crud/internal/biz"

	"github.com/go-kratos/kratos/v2/log"
)

type userInfoRepo struct 
	data *Data
	log  *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) // 返回 error

	if userInfo.Id > 0  //更新	userInfo := biz.UserInfo
		var oldUserInfo biz.UserInfo
		result1 := repo.data.db.First(&oldUserInfo, "id = ? ", userInfo.Id) // 通过id进行数据查询
		repo.log.Debug("result.Error :", result1.Error)                     // 返回 error
		repo.log.Debug("save userInfo :", userInfo)
		if result1.Error != nil  // 有错误返回
			return userInfo, result1.Error
		 else 
			oldUserInfo.UserName = userInfo.UserName
			oldUserInfo.Password = userInfo.Password
			oldUserInfo.Age = userInfo.Age
			oldUserInfo.Phone = userInfo.Phone
			oldUserInfo.Address = userInfo.Address

			result := repo.data.db.Save(&oldUserInfo)      // 通过数据的指针来创建
			repo.log.Debug("result.Error :", result.Error) // 返回 error
			repo.log.Debug("save userInfo :", userInfo)
			return userInfo, result1.Error
		

	 else  // 创建
		result := repo.data.db.Create(userInfo)        // 通过数据的指针来创建
		repo.log.Debug("result.Error :", result.Error) // 返回 error
		repo.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.UserInfoId: id) // 通过id删除数据
	repo.log.Debug("result.Error :", result.Error)       // 返回 error
	return 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)         // 返回 error
	return &userInfo, result.Error


func (repo *userInfoRepo) FindAll(ctx context.Context) ([]*biz.UserInfo, error) 
	repo.log.Debug("FindAll   :") //
	var userInfoList []*biz.UserInfo

	result := repo.data.db.Find(&userInfoList)     // 通过数据查询
	repo.log.Debug("result.Error :", result.Error) // 返回 error
	return userInfoList, result.Error


可以使用GORM 对数据进行CRUD,其中save 方法比较特殊。判断id,然后执行create 或者save 执行更新操作。

6,做CRUD接口


首先定义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 service

import (
	"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.UnimplementedUserInfoServiceServer
	uc  *biz.UserInfoUsecase
	log *log.Helper


// NewUserInfoService new a UserInfo service.
func NewUserInfoService(uc *biz.UserInfoUsecase, logger log.Logger) *UserInfoService 

	log := log.NewHelper(logger)

	return &UserInfoServiceuc: 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.CommReplyMessage: "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.CommReplyMessage: "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.UserInfoReplyMessage: "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.ListUserInfoReplyMessage: "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


使用情况:

7,总结


经过几天研究,终于完成了kratos的CRUD操作,解决若干问题。并上传了代码到github上。
发现网上的代码都是很简单的CRUD,并不全面。
完善了CURD的demo。

本文的原文连接是:
https://blog.csdn.net/freewebsys/article/details/124262158

博主地址是:https://blog.csdn.net/freewebsys

以上是关于kratos使用日记的主要内容,如果未能解决你的问题,请参考以下文章

使用 Microsoft 登录的 Ory / Kratos 登录页面?

源码 kratos 配置热加载分析

kratos学习从零使用kratos创建一个CRUD的demo,是gorm做数据库操作,经过几天研究,调试代码开源放到github上,还是非常方便的。服务也实现了CRUD的http接口。

kratos学习从零使用kratos创建一个CRUD的demo,是gorm做数据库操作,经过几天研究,调试代码开源放到github上,还是非常方便的。服务也实现了CRUD的http接口。

通过 layout 探索 kratos 运行原理

没有在 nginx ssl 后面发送 ory kratos csrf cookie