关键词:
[kitex + gorm-gen + hertz] 快速写出一个kitex的微服务
原项目:https://github.com/cloudwego/hertz
0、目的
创建一个用户的微服务用来进行添加和查询用户
1、环境安装
Kitex 安装
Kitex 目前对 Windows 的支持并不完善,建议使用虚拟机或 WSL2 进行测试。
这里我采用Ubuntu系统,来自动生成代码然后将生成代码同步到window本地的goland开发!
要开始 Kitex 开发,首先需要安装 Kitex 代码生成工具, go install
命令可被用于安装 Go 二进制工具(在此之前,请务必检查已正确设置 GOPATH
环境变量,并将 $GOPATH/bin
添加到 PATH
环境变量中)
安装依赖
go install github.com/cloudwego/kitex@latest
go install github.com/cloudwego/thriftgo@latest
go mod edit -replace=github.com/apache/thrift=github.com/apache/thrift@v0.13.0
docker 安装 相关环境
安装文件 docker-compose.yaml
启动命令 docker-compose up -d
2、定义 用户的 IDL
namespace go demouser
enum ErrCode
SuccessCode = 0
ServiceErrCode = 10001
ParamErrCode = 10002
UserAlreadyExistErrCode = 10003
AuthorizationFailedErrCode = 10004
struct BaseResp
1: i64 status_code
2: string status_message
3: i64 service_time
struct User
1: i64 user_id
2: string username
3: string avatar
struct CreateUserRequest
// length of Message should be greater than or equal to 1
1: string username (vt.min_size = "1")
2: string password (vt.min_size = "1")
struct CreateUserResponse
1: BaseResp base_resp
struct MGetUserRequest
1: list<i64> user_ids (vt.min_size = "1")
struct MGetUserResponse
1: list<User> users
2: BaseResp base_resp
struct CheckUserRequest
1: string username (vt.min_size = "1")
2: string password (vt.min_size = "1")
struct CheckUserResponse
1: i64 user_id
2: BaseResp base_resp
service UserService
CreateUserResponse CreateUser(1: CreateUserRequest req)
MGetUserResponse MGetUser(1: MGetUserRequest req)
CheckUserResponse CheckUser(1: CheckUserRequest req)
3、kitex 自动代码生成
官网
有了 IDL (可以理解为接口)以后我们便可以通过 kitex 工具生成项目代码了,执行如下命令:
$ kitex -module example -service example echo.thrift
上述命令中,-module 表示生成的该项目的 go module 名,-service 表明我们要生成一个服务端项目,后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。
生成文件说明
build.sh : 构建脚本,将代码变成一个可执行的二进制文件
kitex_gen : IDL内容相关的生成代码,主要是基础的Server/Client代码,kitex的编解码的优化会在里面,这里是生成的主要代码
main.go : 程序入口
handler.go : 用户在该文件里实现IDL service 定义的方法 可以理解为api层
4、导入goland
上面的代码生成是在Linux中的,goland中使用deployment来同步到window中goland下,然后window输入
go mod tidy
来同步包
5、Demo
5.1、服务端编写handler – 假数据
package main
import (
"context"
"log"
demouser "myuser/kitex_gen/demouser"
"time"
)
// UserServiceImpl implements the last service interface defined in the IDL.
type UserServiceImpl struct
// CreateUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CreateUser(ctx context.Context, req *demouser.CreateUserRequest) (resp *demouser.CreateUserResponse, err error)
log.Println("姓名" + req.Username + "密码" + req.Password)
resp = new(demouser.CreateUserResponse)
resp.BaseResp = &demouser.BaseRespStatusCode: 200, StatusMessage: "ok", ServiceTime: time.Time.Unix()
return resp, nil
// MGetUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) MGetUser(ctx context.Context, req *demouser.MGetUserRequest) (resp *demouser.MGetUserResponse, err error)
resp = new(demouser.MGetUserResponse)
users := make([]*demouser.User, 0)
users = append(users, &demouser.User1, "test", "test")
resp.Users = users
resp.BaseResp = &demouser.BaseRespStatusCode: 200, StatusMessage: "ok", ServiceTime: time.Time.Unix()
return resp, nil
// CheckUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CheckUser(ctx context.Context, req *demouser.CheckUserRequest) (resp *demouser.CheckUserResponse, err error)
resp = new(demouser.CheckUserResponse)
resp.UserId = 1
resp.BaseResp = &demouser.BaseRespStatusCode: 200, StatusMessage: "ok", ServiceTime: time.Time.Unix()
return resp, nil
5.2、运行
package main
import (
"log"
"myuser/kitex_gen/demouser/userservice"
)
func main()
//服务端的地址 [::]:8888,注意要和demouser下的service中名字保持一致,这里userservice
svr := userservice.NewServer(new(UserServiceImpl))
err := svr.Run()
if err != nil
log.Println(err.Error())
没有报错说明一起正常,下面进行客户端的编写
运行 sh build.sh
以进行编译,编译结果会被生成至 output
目录.
最后,运行 sh output/bootstrap.sh
以启动服务。服务会在默认的 8888 端口上开始运行。要想修改运行端口,可打开 main.go
,为 NewServer
函数指定配置参数:
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:9999")
svr := api.NewServer(new(EchoImpl), server.WithServiceAddr(addr))
5.3、客户端 – 测试
package main
import (
"context"
"github.com/cloudwego/kitex/client"
"log"
"myuser/kitex_gen/demouser"
"myuser/kitex_gen/demouser/userservice"
"time"
)
func main()
//第一个是服务名字,第二个是指定服务端的地址
client, err := userservice.NewClient("userservice", client.WithHostPorts("0.0.0.0:8888"))
if err != nil
log.Fatal(err)
for
//通过client进行调用
resp, err := client.CreateUser(context.Background(), &demouser.CreateUserRequestUsername: "wpc", Password: "123456")
//resp, err := client.CheckUser(context.Background(), &demouser.CheckUserRequestUsername: "wpc", Password: "123456")
//resp, err := client.MGetUser(context.Background(), &demouser.MGetUserRequestUserIds: []int641, 2)
if err != nil
log.Fatal(err)
return
log.Println(resp)
time.Sleep(time.Second)
到这里我们就完成了环境的搭建
5.4、使用etcd来完成注册和发现
服务端
package main
import (
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/cloudwego/kitex/server"
etcd "github.com/kitex-contrib/registry-etcd"
"log"
"myuser/kitex_gen/demouser/userservice"
)
func main()
//服务端的地址 [::]:8888,注意要和demouser下的service中名字保持一致,这里userservice
// 填写对应的ip地址和端口
r, err := etcd.NewEtcdRegistry([]string"192.168.1.18:2379") // r不应重复使用。
if err != nil
log.Fatal(err)
svr := userservice.NewServer(new(UserServiceImpl),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfoServiceName: "userservice"),
server.WithRegistry(r))
err = svr.Run()
if err != nil
log.Println(err.Error())
客户端
NewClient() 后面的第一个参数要和前面的ServiceName保持一致userservice
package main
import (
"context"
"github.com/cloudwego/kitex/client"
etcd "github.com/kitex-contrib/registry-etcd"
"log"
"myuser/kitex_gen/demouser"
"myuser/kitex_gen/demouser/userservice"
"time"
)
func main()
//第一个是服务名字,第二个是指定服务端的地址
r, err := etcd.NewEtcdResolver([]string"192.168.1.18:2379") // r不应重复使用。
if err != nil
log.Fatal(err)
client, err := userservice.NewClient("userservice", client.WithResolver(r))
....
5.5、项目地址
https://gitee.com/wangpengchengalex/go-easynote/tree/master/demo
6、user微服务
使用 gorm-gen 来快速crud 官网
6.1、创建用户表
当然也可以是SQL创建
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct
gorm.Model
Username string `json:"username"`
Avatar string `json:"avatar"` //password懒得改了
func main()
dsn := "gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"
println(dsn)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)
if err != nil
panic("failed to connect database")
// 迁移 schema
db.AutoMigrate(&User)
这将在数据库中生成
6.2、gorm-gen生成crud
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gorm"
)
func main()
// 连接数据库
db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"))
if err != nil
panic(fmt.Errorf("cannot establish db connection: %w", err))
g := gen.NewGenerator(gen.Config
OutPath: "D:\\\\goCode\\\\myuser\\\\gorm-gen\\\\query",
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
)
g.UseDB(db)
// Generate basic type-safe DAO API
g.ApplyBasic(g.GenerateAllTable()...)
g.Execute()
6.3、测试crud
package main
import (
"context"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"myuser/gorm-gen/model"
"myuser/gorm-gen/query"
)
func main()
// 连接数据库
db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"))
if err != nil
panic(fmt.Errorf("cannot establish db connection: %w", err))
query.SetDefault(db)
// 增加数据
u := query.User
ctx := context.Background()
users := []*model.UserUsername: "test1", Avatar: "test1", Username: "test2", Avatar: "test2"
u.WithContext(ctx).Create(users...)
// 查询数据 https://gorm.io/gen/query.html
seach1, _ := u.WithContext(ctx).Where(u.ID.Eq(1)).First()
log.Println("通过id查询", seach1)
// 更新数据 https://blog.csdn.net/Jeffid/article/details/126898000
u.WithContext(ctx).Where(u.Username.Eq("test2")).Update(u.Username, "wpc")
seach2, _ := u.WithContext(ctx).Where(u.Username.Eq("wpc")).First()
log.Println("更新后的查询", seach2)
6.4、添加到demo中 - dao层
规定 : 将crud的数据访问对象都放入dal文件中,下面主要三个目录 1、db 接下来定义的数据库操作 2、model
3、query (2,3都由gorm-gen生成)
1、init.go
package db
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"myuser/demo/dal/query"
)
var Q *query.Query
func Init()
var err error
db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(192.168.1.18:3306)/gorm?charset=utf8&parseTime=True&loc=Local"))
if err != nil
panic(fmt.Errorf("cannot establish db connection: %w", err))
query.SetDefault(db)
Q = query.Q
if err != nil
panic(err)
2、user.go
package db
import (
"context"
"myuser/demo/dal/model"
)
// MGetUsers multiple get list of user info
func MGetUsers(ctx context.Context, userIDs []int64) ([]*model.User, error)
return Q.WithContext(ctx).User.Where(Q.User.ID.In(userIDs...)).Find()
// CreateUser create user info
func CreateUser(ctx context.Context, users []*model.User) error
return Q.WithContext(ctx).User.Create(users...)
// QueryUser query list of user info
func QueryUser(ctx context.Context, userName string) ([]*model.User, error)
return Q.WithContext(ctx).User.Where(Q.User.Username.Eq(userName)).Find()
3、user_test.go
package db
import (
"context"
"log"
"myuser/demo/dal/model"
"testing"
)
func TestCreateUser(t *testing.T)
Init()
users := make([]*model.User, 0)
users = append(users, &model.UserUsername: "wpctest", Avatar: "123187")
CreateUser(context.Background(), users)
func TestQueryUser(t *testing.T)
Init()
user, err := QueryUser(context.Background(), "wpctest")
if err != nil
log.Fatal(err)
log.Println(user[0])
func TestMGetUsers(t *testing.T)
Init()
users, err := MGetUsers(context.Background(), []int647, 9, 10)
if err != nil
log.Fatal(err)
for _, user := range users
log.Println(user)
6.5、service层中调用dao层的方法
建一个目录service
,里面是具体的实现方法,handler.go充当api层
来获取参数和返回数据对象,具体的数据处理由service中的一个一个方法来处理
6.5.1、新建一个异常处理类
package errno
import (
"errors"
"fmt"
"myuser/demo/kitex_gen/demouser"
)
type ErrNo struct
ErrCode int64
ErrMsg string
func (e ErrNo) Error() string
return fmt.Sprintf("err_code=%d, err_msg=%s", e.ErrCode, e.ErrMsg)
func NewErrNo(code int64高性能rpc框架cloudwego-kitex内外统一的开源实践
...资深研发工程师杨芮,跟大家分享了《高性能RPC框架Kitex内外统一的开源实践》,本文根据分享整理而成。本文将从以下四个方面介绍CloudWeGo高性能RPC框架Kitex的实践及开源:1.由内至外-开源过渡;2.开源一年变更... 查看详情
kitex中consistenthashing的实现(代码片段)
一致性哈希算法(consistenthashing)kitex中一致性的很多细节都和我预先理解的不一样。这种负载均衡算法是在client侧实现的,那么client是怎么知道所有的ip的?感觉这种算法应该是做一个中间件比较好,client请... 查看详情
字节跳动开源的一个golang微服务http框架
...个不错的选择。框架特点1、高易用性在开发过程中,快速写出来正确的代码往往是更重要的。因此,在Hertz在迭代过程中,积极听取用户意见,持续打磨框架,希望为用户提供一个更好的使用体验,帮助用... 查看详情
go源码解读|如何用好errors库的errors.is()与errors.as()方法
...文章了,这段时间投注了较多的时间学习字节的开源项目Kitex/Hertz,并维护一些简单的issue,有兴趣的同学也可以去了解:www.cloudwego.io/这段时间迟迟没有更新文章,一方面是接触到了很多大佬,反观自身技术深度远远不及,变得... 查看详情
写一个函数实现快速排序(递归调用)
快速排序怎么写?
介绍快速排序有两种经典的写法,共同点是只有划分过程,都是://随机化取pivot下标,注意先在main函数里初始化“随机种子”inlineintgetPivotIndex(intleft,intright){returnrand()%(right-left+1)+left;}intpartition(vector<int>&nums,intleft,intright){... 查看详情
快速分栏(代码片段)
HTML代码:<divclass="col-2">随便写什么</div><divclass="col-2">随便写什么</div><divclass="col-2">随便写什么</div><divclass="col-2">随便写什么</div><divclass="col-2">随便写什么 查看详情
如何快速的写响应式站点
怎么快速的写响应式站点呢?从站点的构成来说:站点=HTML+css+js一:小公司方案:使用bootstrap+vscode,一直抄抄抄BS集成了HTML,CSS,和JS一体化的东西,是快速建站的最好选择。1.抄bs的布局2.修改BS的css3.新建一些js二:中型公司方... 查看详情
快速写一个babel插件(代码片段)
...插件应用而来,本文将会采用较为通俗的语言来描述如何快速写一个babel插件。一、babel的作用babel的作用 查看详情
米洛个人修炼术:快速提升能力的简单方法
最近总有朋友问我,老司机啊,怎么快速提升自己的技术水平啊?我给他支了一招:你就每天写写日记吧。工作之外,抽空写一写今天学了什么。这样就可以快速提升你的能力了。然后,那位朋友似懂非懂的说了句噢,然后,就... 查看详情
《夜深人静写算法》数论篇-(13)二分快速幂
...章目录前言一、模幂运算1、朴素算法2、循环节二、二分快速幂1、递归实现2、二进制优化实现三、模数为64位整数四、时间复杂度总结五、思考题前言这个章节会详细讲解一下二分快速幂的知识。具体是解决什么问题的,我们可... 查看详情
《夜深人静写算法》数论篇-(13)二分快速幂
...章目录前言一、模幂运算1、朴素算法2、循环节二、二分快速幂1、递归实现2、二进制优化实现三、模数为64位整数四、时间复杂度总结五、思考题前言这个章节会详细讲解一下二分快速幂的知识。具体是解决什么问题的,我们可... 查看详情
工作时间如何快速进入写公司项目代码状态?
经常在下午或者上午,人坐在工位上,但不想写代码,知道项目没有做完,但就是不想写。怎么办?把公司项目跑起来,点击几个地方看看有什么问题。有了明确的问题就有动力了。 查看详情
java在java下array.sort和(自己写的)快速排序到底那个快?
项目中无意间用到了快速排序,当时因为忘记了,上网搜了一下......可搜的搜的我思考一个问题:在java下Array.Sort和(自己写的)快速排序到底那个快?网上有很多博主讨论过这个问题,他们做出的结论都是System.Array.Sort比自己... 查看详情
自媒体快速写文章和获取素材的几大途径
...础的事情就不在这多说了(比如注册账号流程等等。)如何快速写一篇爆文?写文章流程:1、找素材点。2、扩展素材面。3、处理素材。4、编辑 查看详情
如何快速去写一个产品或者论文的引用
...统与信息技术研究所.2009. 如何根据我们自己的引用去快速写出符合规范的引用?大招来了-----百度学术 将引用的文章的名字,作者的名字输入进去,即可找出相应的内容,如 韩宣伟.基于嵌入 查看详情
对于 HDF5 的快速读/写性能推荐的压缩是啥(在 Python/pandas 中)?
】对于HDF5的快速读/写性能推荐的压缩是啥(在Python/pandas中)?【英文标题】:WhatistherecommendedcompressionforHDF5forfastread/writeperformance(inPython/pandas)?对于HDF5的快速读/写性能推荐的压缩是什么(在Python/pandas中)?【发布时间】:2015-1... 查看详情
快速写curry函数和compose函数
我一直都把编程作为一项极其富有创造性和乐趣的工作,其意义就在于我们可以接触各种迷人而富有远见的编程思想,站在巨人的肩膀上眺望未来。作为一名懒癌晚期编程工作者,任何可以让我少写代码的编程思想对我来说都是... 查看详情