[kitex+gorm-gen+hertz]快速写出一个基于go的微服务(代码片段)

Alex抱着爆米花 Alex抱着爆米花     2023-04-04     697

关键词:

[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函数

我一直都把编程作为一项极其富有创造性和乐趣的工作,其意义就在于我们可以接触各种迷人而富有远见的编程思想,站在巨人的肩膀上眺望未来。作为一名懒癌晚期编程工作者,任何可以让我少写代码的编程思想对我来说都是... 查看详情