[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务

[kitex + gorm-gen + hertz] 快速写出一个kitex的微服务

  • 0、目的
  • 1、环境安装
  • 2、定义 用户的 IDL
  • 3、kitex 自动代码生成
  • 4、导入goland
  • 5、Demo
    • 5.1、服务端编写handler -- 假数据
    • 5.2、运行
    • 5.3、客户端 -- 测试
    • 5.4、使用etcd来完成注册和发现
    • 5.5、项目地址
  • 6、user微服务
    • 6.1、创建用户表
    • 6.2、gorm-gen生成crud
    • 6.3、测试crud
    • 6.4、添加到demo中 - dao层
    • 6.5、service层中调用dao层的方法
      • 6.5.1、新建一个异常处理类
      • 6.5.2、创建一个createservice类
      • 6.5.3、包装类
    • 6.6、修改handler调用service方法
    • 完整代码
  • 使用Hertz来搭建一个 Golang 微服务 HTTP 框架
    • 1.1、定义IDL文件
    • 1.2、创建新项目
    • 1.3、修改handler进行rpc调用
      • 1.3.1、新建一个rpc目录并创建一个远程调用类
      • 1.3.2、在handler中调用rpc方法
      • 1.3.3、自定义响应类
    • 1.4、项目地址
    • 1.5、jwt认证
      • 1.5.1、初始化
      • 1.5.2、修改handler
      • 1.5.3、修改main
    • jwt原理

原项目: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/[email protected]

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

来同步包

[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第1张图片

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.BaseResp{StatusCode: 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.User{1, "test", "test"})
	resp.Users = users
	resp.BaseResp = &demouser.BaseResp{StatusCode: 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.BaseResp{StatusCode: 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())
	}
}

[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第2张图片

没有报错说明一起正常,下面进行客户端的编写
运行 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.CreateUserRequest{Username: "wpc", Password: "123456"})
		//resp, err := client.CheckUser(context.Background(), &demouser.CheckUserRequest{Username: "wpc", Password: "123456"})
		//resp, err := client.MGetUser(context.Background(), &demouser.MGetUserRequest{UserIds: []int64{1, 2}})
		if err != nil {
			log.Fatal(err)
			return
		}
		log.Println(resp)
		time.Sleep(time.Second)
	}
}

[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第3张图片

在这里插入图片描述
到这里我们就完成了环境的搭建

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.EndpointBasicInfo{ServiceName: "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{})
}

这将在数据库中生成
[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第4张图片

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.User{{Username: "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生成)
[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第5张图片
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.User{Username: "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(), []int64{7, 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, msg string) ErrNo {
	return ErrNo{
		ErrCode: code,
		ErrMsg:  msg,
	}
}

func (e ErrNo) WithMessage(msg string) ErrNo {
	e.ErrMsg = msg
	return e
}

var (
	Success                = NewErrNo(int64(demouser.ErrCode_SuccessCode), "Success")
	ServiceErr             = NewErrNo(int64(demouser.ErrCode_ServiceErrCode), "Service is unable to start successfully")
	ParamErr               = NewErrNo(int64(demouser.ErrCode_ParamErrCode), "Wrong Parameter has been given")
	UserAlreadyExistErr    = NewErrNo(int64(demouser.ErrCode_UserAlreadyExistErrCode), "User already exists")
	AuthorizationFailedErr = NewErrNo(int64(demouser.ErrCode_AuthorizationFailedErrCode), "Authorization failed")
)

// ConvertErr convert error to Errno
func ConvertErr(err error) ErrNo {
	Err := ErrNo{}
	if errors.As(err, &Err) {
		return Err
	}
	s := ServiceErr
	s.ErrMsg = err.Error()
	return s
}

6.5.2、创建一个createservice类

package service

import (
	"context"
	"crypto/md5"
	"fmt"
	"io"
	"myuser/demo/dal/db"
	"myuser/demo/dal/model"
	errno "myuser/demo/error"
	"myuser/demo/kitex_gen/demouser"
)

type CreateUserService struct {
	ctx context.Context
}

// NewCreateUserService new CreateUserService
func NewCreateUserService(ctx context.Context) *CreateUserService {
	return &CreateUserService{ctx: ctx}
}

func (s *CreateUserService) CreateUser(req *demouser.CreateUserRequest) error {
	users, err := db.QueryUser(s.ctx, req.Username)
	if err != nil {
		return err
	}
	if len(users) != 0 {
		return errno.UserAlreadyExistErr
	}
	//生成md5
	h := md5.New()
	if _, err = io.WriteString(h, req.Password); err != nil {
		return err
	}
	password := fmt.Sprintf("%x", h.Sum(nil))
	return db.CreateUser(s.ctx, []*model.User{{Username: req.Username, Avatar: password}})
}

6.5.3、包装类

package pack

import (
	"github.com/cloudwego/biz-demo/easy_note/cmd/user/dal/db"
	"github.com/cloudwego/biz-demo/easy_note/kitex_gen/demouser"
)

// User pack user info
func User(u *db.User) *demouser.User {
	if u == nil {
		return nil
	}
	
	return &demouser.User{UserId: int64(u.ID), Username: u.Username, Avatar: ""}
}

// Users pack list of user info
func Users(us []*db.User) []*demouser.User {
	users := make([]*demouser.User, 0)
	for _, u := range us {
		if temp := User(u); temp != nil {
			users = append(users, temp)
		}
	}
	return users
}

6.6、修改handler调用service方法

之前用的是模拟数据这里就是真实的查询数据库

package main

import (
	"context"
	errno "myuser/demo/error"
	"myuser/demo/pack"
	"myuser/demo/service"
	"myuser/kitex_gen/demouser"
)

// 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) {
	resp = new(demouser.CreateUserResponse)
	err = service.NewCreateUserService(ctx).CreateUser(req)
	if err != nil {
		resp.BaseResp = pack.BuildBaseResp(err)
		return resp, nil
	}
	resp.BaseResp = pack.BuildBaseResp(errno.Success)
	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, err := service.NewMGetUserService(ctx).MGetUser(req)
	if err != nil {
		resp.BaseResp = pack.BuildBaseResp(err)
		return resp, nil
	}

	resp.BaseResp = pack.BuildBaseResp(errno.Success)
	resp.Users = users
	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)

	uid, err := service.NewCheckUserService(ctx).CheckUser(req)
	if err != nil {
		resp.BaseResp = pack.BuildBaseResp(err)
		return resp, nil
	}

	resp.UserId = uid
	resp.BaseResp = pack.BuildBaseResp(errno.Success)
	return resp, nil
}

完整代码

https://gitee.com/wangpengchengalex/go-easynote/tree/master/demo

使用Hertz来搭建一个 Golang 微服务 HTTP 框架

很明显上面的是基于微服务的,也不方便调试,需要起一个http框架来使用

官网 https://www.cloudwego.io/docs/hertz/

1.1、定义IDL文件

在目录下创建 idl文件文件夹,写入api.thrift
idl/api.thrift

namespace go demoapi

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 {
    1: string username (api.form="username", api.vd="len($) > 0")
    2: string password (api.form="password", api.vd="len($) > 0")
}

struct CreateUserResponse {
    1: BaseResp base_resp
}

struct CheckUserRequest {
    1: string username (api.form="username", api.vd="len($) > 0")
    2: string password (api.form="password", api.vd="len($) > 0")
}

struct CheckUserResponse {
    1: BaseResp base_resp
}



service ApiService {
    CreateUserResponse CreateUser(1: CreateUserRequest req) (api.post="/user/register")
    CheckUserResponse CheckUser(1: CheckUserRequest req) (api.post="/user/login")
}

1.2、创建新项目

// GOPATH 下执行
hz new -idl idl/api.thrift

// 拉取代码到window中 整理 & 拉取依赖 
go mod tidy

1.3、修改handler进行rpc调用

这里路由都已经自动生成了
[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第6张图片
调用 方法和 client完全一致,先注册发现然后调用对应方法

1.3.1、新建一个rpc目录并创建一个远程调用类

package rpc

import (
	"context"
	"github.com/cloudwego/kitex/client"
	"github.com/kitex-contrib/obs-opentelemetry/tracing"
	etcd "github.com/kitex-contrib/registry-etcd"
	errno "myuser/demo/error"
	"myuser/kitex_gen/demouser"
	"myuser/kitex_gen/demouser/userservice"
)

var userClient userservice.Client

func initUser() {
	r, err := etcd.NewEtcdResolver([]string{"192.168.1.18:2379"})
	if err != nil {
		panic(err)
	}

	c, err := userservice.NewClient(
		"userservice",
		client.WithResolver(r),
		client.WithSuite(tracing.NewClientSuite()),
	)
	if err != nil {
		panic(err)
	}
	userClient = c
}

// CreateUser create user info
func CreateUser(ctx context.Context, req *demouser.CreateUserRequest) error {
	resp, err := userClient.CreateUser(ctx, req)
	if err != nil {
		return err
	}
	if resp.BaseResp.StatusCode != 0 {
		return errno.NewErrNo(resp.BaseResp.StatusCode, resp.BaseResp.StatusMessage)
	}
	return nil
}

// CheckUser check user info
func CheckUser(ctx context.Context, req *demouser.CheckUserRequest) (int64, error) {
	resp, err := userClient.CheckUser(ctx, req)
	if err != nil {
		return 0, err
	}
	if resp.BaseResp.StatusCode != 0 {
		return 0, errno.NewErrNo(resp.BaseResp.StatusCode, resp.BaseResp.StatusMessage)
	}
	return resp.UserId, nil
}

1.3.2、在handler中调用rpc方法

修改biz/handler/demoapi的文件

// Code generated by hertz generator.

package demoapi

import (
	"context"
	"myuser/demo/api/biz/model/demoapi"
	"myuser/demo/api/biz/rpc"
	errno "myuser/demo/error"
	"myuser/kitex_gen/demouser"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

// CreateUser .
// @router /user/register [POST]
func CreateUser(ctx context.Context, c *app.RequestContext) {
	var err error
	var req demoapi.CreateUserRequest
	err = c.BindAndValidate(&req)
	if err != nil {
		c.String(consts.StatusBadRequest, err.Error())
		return
	}
	//替换掉代码生成的
	//resp := new(demoapi.CreateUserResponse)
	err = rpc.CreateUser(context.Background(), &demouser.CreateUserRequest{
		Username: req.Username,
		Password: req.Password,
	})
	if err != nil {
		SendResponse(c, errno.ConvertErr(err), nil)
		return
	}
	//c.JSON(consts.StatusOK, resp)
	//自定义json
	SendResponse(c, errno.Success, nil)
}

// CheckUser .
// @router /user/login [POST]
func CheckUser(ctx context.Context, c *app.RequestContext) {
	var err error
	var req demoapi.CheckUserRequest
	err = c.BindAndValidate(&req)
	if err != nil {
		c.String(consts.StatusBadRequest, err.Error())
		return
	}

	//resp := new(demoapi.CheckUserResponse)
	//
	//c.JSON(consts.StatusOK, resp)
	id, err := rpc.CheckUser(context.Background(), &demouser.CheckUserRequest{
		Username: req.Username,
		Password: req.Password,
	})
	if err != nil && id == 0 {
		SendResponse(c, errno.ConvertErr(err), nil)
		return
	}
	//c.JSON(consts.StatusOK, resp)
	//自定义json
	SendResponse(c, errno.Success, nil)
}

1.3.3、自定义响应类

package demoapi

import (
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	errno "myuser/demo/error"
)

type Response struct {
	Code    int64       `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

// SendResponse pack response
func SendResponse(c *app.RequestContext, err error, data interface{}) {
	Err := errno.ConvertErr(err)
	c.JSON(consts.StatusOK, Response{
		Code:    Err.ErrCode,
		Message: Err.ErrMsg,
		Data:    data,
	})
}

[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第7张图片

1.4、项目地址

https://gitee.com/wangpengchengalex/go-easynote/tree/master/demo/api

1.5、jwt认证

1.5.1、初始化

在 biz下新建一个mw目录然后复制下面的代码,主要改Authenticator中调用rpc即可

1、自定义key和identitykey
[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第8张图片
2、这三处保持一致
[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第9张图片
3、调用rpc方法

package mw

import (
	"context"
	"net/http"
	"time"

	"github.com/cloudwego/biz-demo/easy_note/cmd/api/biz/model/demoapi"
	"github.com/cloudwego/biz-demo/easy_note/cmd/api/biz/rpc"
	"github.com/cloudwego/biz-demo/easy_note/kitex_gen/demouser"
	"github.com/cloudwego/biz-demo/easy_note/pkg/consts"
	"github.com/cloudwego/biz-demo/easy_note/pkg/errno"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/jwt"
)

var JwtMiddleware *jwt.HertzJWTMiddleware

func InitJWT() {
	JwtMiddleware, _ = jwt.New(&jwt.HertzJWTMiddleware{
		Key:           []byte(consts.SecretKey),
		TokenLookup:   "header: Authorization, query: token, cookie: jwt",
		TokenHeadName: "Bearer",
		TimeFunc:      time.Now,
		Timeout:       time.Hour,
		MaxRefresh:    time.Hour,
		IdentityKey:   consts.IdentityKey,
		IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
			claims := jwt.ExtractClaims(ctx, c)
			return &demoapi.User{
				UserID: int64(claims[consts.IdentityKey].(float64)),
			}
		},
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(int64); ok {
				return jwt.MapClaims{
					consts.IdentityKey: v,
				}
			}
			return jwt.MapClaims{}
		},
		Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
			var err error
			var req demoapi.CheckUserRequest
			if err = c.BindAndValidate(&req); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			if len(req.Username) == 0 || len(req.Password) == 0 {
				return "", jwt.ErrMissingLoginValues
			}
			//修改这里
			return rpc.CheckUser(context.Background(), &demouser.CheckUserRequest{
				Username: req.Username,
				Password: req.Password,
			})
		},
		LoginResponse: func(ctx context.Context, c *app.RequestContext, code int, token string, expire time.Time) {
			c.JSON(http.StatusOK, utils.H{
				"code":   errno.Success.ErrCode,
				"token":  token,
				"expire": expire.Format(time.RFC3339),
			})
		},
		Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
			c.JSON(http.StatusOK, utils.H{
				"code":    errno.AuthorizationFailedErr.ErrCode,
				"message": message,
			})
		},
		HTTPStatusMessageFunc: func(e error, ctx context.Context, c *app.RequestContext) string {
			switch t := e.(type) {
			case errno.ErrNo:
				return t.ErrMsg
			default:
				return t.Error()
			}
		},
	})
}

1.5.2、修改handler

// CheckUser .
// @router /user/login [POST]
func CheckUser(ctx context.Context, c *app.RequestContext) {
	mw.JwtMiddleware.LoginHandler(ctx, c)
}

1.5.3、修改main

func Init() {
	rpc.Init()
	mw.InitJWT()
}
func main() {
	Init()
	h := server.New(
		server.WithHostPorts(":8080"),
		server.WithHandleMethodNotAllowed(true), // coordinate with NoMethod
	)
	register(h)
	h.Spin()
}

[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务_第10张图片

jwt原理

https://blog.csdn.net/wzb_wzt/article/details/115730178

https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/middleware/jwt/#payloadfunc

项目中在api层调用带有jwt的user

...
v, _ := c.Get(consts.IdentityKey) //通过Get 来获取 IdentityHandler 中保存的上下文信息 UserID
notes, total, err := rpc.QueryNotes(context.Background(), &demonote.QueryNoteRequest{
	UserId:    v.(*demoapi.User).UserID, //
	SearchKey: req.SearchKey,
	Offset:    req.Offset,
	Limit:     req.Limit,
})
...

调用是需要加上Authorization的头信息

curl --location --request GET '127.0.0.1:8080/v2/note/query?offset=0&limit=20&search_key=test' \
--header 'Authorization: Bearer $token'

你可能感兴趣的:(go项目,微服务,github,架构)