Kitex微服务开发 | 青训营

228 阅读10分钟

官方demo

前期准备

  1. 确保 GOPATH 环境变量已经被正确地定义(例如 export GOPATH=~/go)并且将$GOPATH/bin添加到 PATH 环境变量之中(例如 export PATH=$GOPATH/bin:$PATH);请勿将 GOPATH 设置为当前用户没有读写权限的目录
  2. 安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
  3. 安装 thriftgo:go install github.com/cloudwego/thriftgo@latest

示例代码

方式一:直接启动

  1. 进入示例仓库的 hello 目录

    cd kitex-examples/hello

  2. 运行 server

    go run .

  3. 运行 client

    另起一个终端后,go run ./client

运行结果:

server:

image-20230726204041879

client:

image-20230726204011217

增加一个新的方法

打开 hello.thrift,现在让我们为新方法分别定义一个新的请求和响应,AddRequestAddResponse,并在 service Hello 中增加 add 方法:

namespace go api
​
struct Request {
        1: string message
}
​
struct Response {
        1: string message
}
​
struct AddRequest {
  1: i64 first
  2: i64 second
}
​
struct AddResponse {
  1: i64 sum
}
​
service Hello {
    Response echo(1: Request req)
    AddResponse add(1: AddRequest req)
}
​
重新生成代码

运行如下命令后,kitex 工具将根据 hello.thrift 更新代码文件。

kitex -service a.b.c hello.thrift
​
# 若当前目录不在 $GOPATH/src 下,需要加上 -module 参数,一般为 go.mod 下的名字
kitex -module "your_module_name" -service a.b.c hello.thrift

执行完上述命令后,kitex 工具将更新下述文件

  1. 更新 ./handler.go,在里面增加一个 Add 方法的基本实现
  2. 更新 ./kitex_gen,里面有框架运行所必须的代码文件
更新服务端处理逻辑

上述步骤完成后,./handler.go 中会自动补全一个 Add 方法的基本实现,类似如下代码:

// Add implements the HelloImpl interface.
func (s *HelloImpl) Add(ctx context.Context, req *api.AddRequest) (resp *api.AddResponse, err error) {
        // TODO: Your code here...
        return
}

让我们在里面增加我们所需要的逻辑,类似如下代码:

// Add implements the HelloImpl interface.
func (s *HelloImpl) Add(ctx context.Context, req *api.AddRequest) (resp *api.AddResponse, err error) {
        // TODO: Your code here...
        resp = &api.AddResponse{Sum: req.First + req.Second}
        return
}
增加客户端调用

服务端已经有了 Add 方法的处理,现在让我们在客户端增加对 Add 方法的调用。

./client/main.go 中你会看到类似如下的 for 循环:

for {
        req := &api.Request{Message: "my request"}
        resp, err := client.Echo(context.Background(), req)
        if err != nil {
                log.Fatal(err)
        }
        log.Println(resp)
        time.Sleep(time.Second)
}

现在让我们在里面增加 Add 方法的调用:

for {
        req := &api.Request{Message: "my request"}
        resp, err := client.Echo(context.Background(), req)
        if err != nil {
                log.Fatal(err)
        }
        log.Println(resp)
        time.Sleep(time.Second)
        addReq := &api.AddRequest{First: 512, Second: 512}
        addResp, err := client.Add(context.Background(), addReq)
        if err != nil {
                log.Fatal(err)
        }
        log.Println(addResp)
        time.Sleep(time.Second)
}
重新运行示例代码

关闭之前运行的客户端和服务端之后

  1. 运行 server

    go run .

  2. 运行 client

    另起一个终端后,go run ./client

    现在,你应该能看到客户端在调用 Add 方法了。

运行结果:

server:

image-20230726210016105

client:

image-20230726210006547

微服务开发流程

完整微服务项目的目录结构

.
├── cmd
│   ├── api
│   │   ├── handlers
│   │   └── rpc
│   ├── note
│   │   ├── dal
│   │   │   └── db
│   │   ├── output
│   │   │   ├── bin
│   │   │   └── log
│   │   │       ├── app
│   │   │       └── rpc
│   │   ├── pack
│   │   ├── rpc
│   │   ├── script
│   │   └── service
│   └── user
│       ├── dal
│       │   └── db
│       ├── output
│       │   ├── bin
│       │   └── log
│       │       ├── app
│       │       └── rpc
│       ├── pack
│       ├── script
│       └── service
├── idl
├── images
├── kitex_gen
│   ├── notedemo
│   │   └── noteservice
│   └── userdemo
│       └── userservice
└── pkg
    ├── bound
    ├── configs
    │   └── sql
    ├── constants
    ├── errno
    ├── middleware
    └── tracer

目录介绍

catalogintroduce
pkg/constantsconstant
pkg/boundcustomized bound handler
pkg/errnocustomized error number
pkg/middlewareRPC middleware微服务通用中间件
pkg/tracerinit jaeger链路追踪
dal数据库操作
pack数据装配
service业务逻辑

1、单个微服务编写

1、创建目录

make dir project_name project_name/cmd project_name/idl project_name/kitex_gen project_name/pkg

2、编写IDL文件

syntax = "proto3";
package user;
option go_package = "userdemo";
​
message BaseResp {
  int64 status_code = 1;
  string status_message = 2;
  int64 service_time = 3;
}
​
message User {
  int64 user_id = 1;
  string user_name = 2;
  string avatar = 3;
}
​
message CreateUserRequest {
  string user_name = 1;
  string password = 2;
}
​
message CreateUserResponse {
  BaseResp base_resp = 1;
}
​
message MGetUserRequest {
  repeated int64 user_ids = 1;
}
​
message MGetUserResponse {
  repeated User users = 1;
  BaseResp base_resp = 2;
}
​
message CheckUserRequest{
  string user_name = 1;
  string password = 2;
}
​
message CheckUserResponse{
  int64 user_id = 1;
  BaseResp base_resp = 2;
}
​
service UserService {
  rpc CreateUser (CreateUserRequest) returns (CreateUserResponse) {}
  rpc MGetUser (MGetUserRequest) returns (MGetUserResponse) {}
  rpc CheckUser (CheckUserRequest) returns (CheckUserResponse) {}
}

3、通过代码生成工具快速生成脚手架

#尽量使用thrift -module 包名 -service 服务名对应生成的目录名称 -gen-path string 对应生成代码存放位置 最后加上IDL文件
kitex -module easynote -service user -gen-path "../../kitex_gen/" ../../idl/user.thrift

4、添加单项微服务目录

#dal 保存数据库操作 pack 数据封装 service 具体的业务代码
mkdir dal pack service

5、编写公用包

该部分位于easynote/pkg/目录下,一般所有服务共同依赖的功能封装到该部分下,demo包含了以下几个部分:

  1. bound:CPU检测

    //cpu.go
    package bound
    ​
    import (
        "context"
        "fmt"
        "net""easynote/pkg/constants"
        "easynote/pkg/errno"
        "github.com/cloudwego/kitex/pkg/klog"
        "github.com/cloudwego/kitex/pkg/remote"
        "github.com/shirou/gopsutil/cpu"
    )
    ​
    var _ remote.InboundHandler = &cpuLimitHandler{}
    ​
    type cpuLimitHandler struct{}
    ​
    func NewCpuLimitHandler() remote.InboundHandler {
        return &cpuLimitHandler{}
    }
    ​
    // OnActive implements the remote.InboundHandler interface.
    func (c *cpuLimitHandler) OnActive(ctx context.Context, conn net.Conn) (context.Context, error) {
        return ctx, nil
    }
    ​
    // OnRead implements the remote.InboundHandler interface.
    func (c *cpuLimitHandler) OnRead(ctx context.Context, conn net.Conn) (context.Context, error) {
        p := cpuPercent()
        klog.CtxInfof(ctx, "current cpu is %.2g", p)
        if p > constants.CPURateLimit {
            return ctx, errno.ServiceErr.WithMessage(fmt.Sprintf("cpu = %.2g", c))
        }
        return ctx, nil
    }
    ​
    // OnInactive implements the remote.InboundHandler interface.
    func (c *cpuLimitHandler) OnInactive(ctx context.Context, conn net.Conn) context.Context {
        return ctx
    }
    ​
    // OnMessage implements the remote.InboundHandler interface.
    func (c *cpuLimitHandler) OnMessage(ctx context.Context, args, result remote.Message) (context.Context, error) {
        return ctx, nil
    }
    ​
    func cpuPercent() float64 {
        percent, _ := cpu.Percent(0, false)
        return percent[0]
    }
    
  2. constants:全局常量(数据库,链路追踪等等配置地址)

    //constants.go
    ​
    package constants
    ​
    // 存放配置信息,可以通过Viper插件进行优化
    const (
        NoteTableName           = "note"
        UserTableName           = "user"
        SecretKey               = "secret key"
        IdentityKey             = "id"
        Total                   = "total"
        Notes                   = "notes"
        NoteID                  = "note_id"
        ApiServiceName          = "demoapi"
        NoteServiceName         = "demonote"
        UserServiceName         = "demouser"
        MySQLDefaultDSN         = "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local"
        EtcdAddress             = "127.0.0.1:2379"
        CPURateLimit    float64 = 80.0
        DefaultLimit            = 10
    )
    
  3. errno:全局错误封装

    package errno
    ​
    import (
        "errors"
        "fmt"
    )
    ​
    const (
        SuccessCode                = 0
        ServiceErrCode             = 10001
        ParamErrCode               = 10002
        UserAlreadyExistErrCode    = 10003
        AuthorizationFailedErrCode = 10004
    )
    ​
    // 通过空变量进行接口实现校验
    var _ error = &ErrNo{}
    ​
    // 自定义错误返回类型
    type ErrNo struct {
        ErrCode int64
        ErrMsg  string
    }
    ​
    // 继承error接口,实现自定义Error方法
    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{code, msg}
    }
    ​
    func (e ErrNo) WithMessage(msg string) ErrNo {
        e.ErrMsg = msg
        return e
    }
    ​
    // 常用封装错误类型
    var (
        Success                = NewErrNo(SuccessCode, "Success")
        ServiceErr             = NewErrNo(ServiceErrCode, "Service is unable to start successfully")
        ParamErr               = NewErrNo(ParamErrCode, "Wrong Parameter has been given")
        UserAlreadyExistErr    = NewErrNo(UserAlreadyExistErrCode, "User already exists")
        AuthorizationFailedErr = NewErrNo(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
    }
    
  4. middleware:中间件

    1. client客户端

      //client.go
      package middleware
      ​
      import (
          "context""github.com/cloudwego/kitex/pkg/endpoint"
          "github.com/cloudwego/kitex/pkg/klog"
          "github.com/cloudwego/kitex/pkg/rpcinfo"
      )
      ​
      var _ endpoint.Middleware = ClientMiddleware
      ​
      // ClientMiddleware client middleware print server address 、rpc timeout and connection timeout
      func ClientMiddleware(next endpoint.Endpoint) endpoint.Endpoint {
          return func(ctx context.Context, req, resp interface{}) (err error) {
              ri := rpcinfo.GetRPCInfo(ctx)
              // get server information
              klog.Infof("server address: %v, rpc timeout: %v, readwrite timeout: %v\n", ri.To().Address(), ri.Config().RPCTimeout(), ri.Config().ConnectTimeout())
              if err = next(ctx, req, resp); err != nil {
                  return err
              }
              return nil
          }
      }
      
    2. common

      //common.gopackage middleware
      ​
      import (
          "context""github.com/cloudwego/kitex/pkg/endpoint"
          "github.com/cloudwego/kitex/pkg/klog"
          "github.com/cloudwego/kitex/pkg/rpcinfo"
      )
      ​
      var _ endpoint.Middleware = CommonMiddleware
      ​
      // CommonMiddleware common middleware print some rpc info、real request and real response
      func CommonMiddleware(next endpoint.Endpoint) endpoint.Endpoint {
          return func(ctx context.Context, req, resp interface{}) (err error) {
              ri := rpcinfo.GetRPCInfo(ctx)
              // get real request
              klog.Infof("real request: %+v\n", req)
              // get remote service information
              klog.Infof("remote service name: %s, remote method: %s\n", ri.To().ServiceName(), ri.To().Method())
              if err = next(ctx, req, resp); err != nil {
                  return err
              }
              // get real response
              klog.Infof("real response: %+v\n", resp)
              return nil
          }
      }
      
    3. server服务端

      //server.go
      package middleware
      ​
      import (
          "context""github.com/cloudwego/kitex/pkg/endpoint"
          "github.com/cloudwego/kitex/pkg/klog"
          "github.com/cloudwego/kitex/pkg/rpcinfo"
      )
      ​
      var _ endpoint.Middleware = ServerMiddleware
      ​
      // ServerMiddleware server middleware print client address
      func ServerMiddleware(next endpoint.Endpoint) endpoint.Endpoint {
          return func(ctx context.Context, req, resp interface{}) (err error) {
              ri := rpcinfo.GetRPCInfo(ctx)
              // get client information
              klog.Infof("client address: %v\n", ri.From().Address())
              if err = next(ctx, req, resp); err != nil {
                  return err
              }
              return nil
          }
      }
      
  5. tracer:链路追踪

    //tracer.go
    package tracer
    ​
    import (
        "fmt""github.com/opentracing/opentracing-go"
        "github.com/uber/jaeger-client-go"
        jaegercfg "github.com/uber/jaeger-client-go/config"
    )
    ​
    func InitJaeger(service string) {
        cfg, _ := jaegercfg.FromEnv()
        cfg.ServiceName = service
        tracer, _, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
        if err != nil {
            panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
        }
        opentracing.SetGlobalTracer(tracer)
        return
    }
    

6、编写服务统一封装类

#cmd/service_name/pack
#一般编写两个封装类,一个返回值封装类,一个DAO到form(service)model对象封装类
//返回值封装resp.go
package pack
​
import (
    "easynote/kitex_gen/user"
    "easynote/pkg/errno"
    "errors"
    "time"
)
​
// 创建基本返回,通过封装统一错误
func BuildBaseResp(err error) *user.BaseResp {
    if err == nil {
        return baseResp(errno.Success)
    }
​
    e := errno.ErrNo{}
    //如果生成错误已经是封装的错误类型,则直接返回
    if errors.As(err, &e) {
        //调用生成BaseResp的接口,传入错误信息
        return baseResp(e)
    }
    //如果不是则直接调用默认服务错误类型配合对应的错误信息进行封装
    s := errno.ServiceErr.WithMessage(err.Error())
    return baseResp(s)
}
​
func baseResp(err errno.ErrNo) *user.BaseResp {
    //返回三个信息,错误代码,错误信息,服务执行时间戳
    return &user.BaseResp{StatusCode: err.ErrCode, StatusMessage: err.ErrMsg, ServiceTime: time.Now().Unix()}
}
​
//模式转换封装类user.go
package pack
​
import (
    "easynote/cmd/user/dal/db"
    "easynote/kitex_gen/user"
)
​
// pack 完成model 到 service user的转变(定义了两个结构体,类似gin中的form与model)
// User pack user info
func User(u *db.User) *user.User {
    if u == nil {
        return nil
    }
​
    return &user.User{UserId: int64(u.ID), UserName: u.UserName, Avatar: "test"}
}
​
// Users pack list of user info
func Users(us []*db.User) []*user.User {
    users := make([]*user.User, 0)
    for _, u := range us {
        if user2 := User(u); user2 != nil {
            users = append(users, user2)
        }
    }
    return users
}
#全局错误(返回值处理)处理,这个是所有服务共同调用一个接口
//easynote/pkg/errno/errno.go
package errno
​
import (
    "errors"
    "fmt"
)
​
const (
    SuccessCode                = 0
    ServiceErrCode             = 10001
    ParamErrCode               = 10002
    UserAlreadyExistErrCode    = 10003
    AuthorizationFailedErrCode = 10004
)
​
// 通过空变量进行接口实现校验
var _ error = &ErrNo{}
​
// 自定义错误返回类型
type ErrNo struct {
    ErrCode int64
    ErrMsg  string
}
​
// 继承error接口,实现自定义Error方法
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{code, msg}
}
​
func (e ErrNo) WithMessage(msg string) ErrNo {
    e.ErrMsg = msg
    return e
}
​
// 常用封装错误类型
var (
    Success                = NewErrNo(SuccessCode, "Success")
    ServiceErr             = NewErrNo(ServiceErrCode, "Service is unable to start successfully")
    ParamErr               = NewErrNo(ParamErrCode, "Wrong Parameter has been given")
    UserAlreadyExistErr    = NewErrNo(UserAlreadyExistErrCode, "User already exists")
    AuthorizationFailedErr = NewErrNo(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
}

7、编写handle.go实现对应的方法

主要实现了一下三个方法:

  1. CreateUser
  2. MGetUser
  3. CheckUser

调用关系:RPC -> service -> dal -> db

这里实现的其实是RPC调用的最表层,类似于gin中的Controller层,所以一般我们可以在这里添加对应的参数校验功能(可以封装为client级别的中间件)

#handle.go
package main
​
import (
    "context"
    "easynote/cmd/user/pack"
    "easynote/cmd/user/service"
    "easynote/kitex_gen/user"
    "easynote/pkg/errno"
)
​
// 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 *user.CreateUserRequest) (resp *user.CreateUserResponse, err error) {
    //创建返回类型变量
    resp = new(user.CreateUserResponse)
    //参数校验不通过
    if len(req.UserName) == 0 || len(req.Password) == 0 {
        //调用封装数据方法,封装参数类型错误
        resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
        return resp, nil
    }
    //每次调用业务代码会新建一个ctx,调用业务代码
    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 *user.MGetUserRequest) (resp *user.MGetUserResponse, err error) {
    // TODO: Your code here...
    //创建返回类型
    resp = new(user.MGetUserResponse)
    //参数校验
    if len(req.UserIds) == 0 {
        resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
        return resp, nil
    }
    //调用业务操作
    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 *user.CheckUserRequest) (resp *user.CheckUserResponse, err error) {
    // TODO: Your code here...
    //创建返回值
    resp = new(user.CheckUserResponse)
    //参数校验
    if len(req.UserName) == 0 || len(req.Password) == 0 {
        resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
        return resp, nil
    }
    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
}

8、编写具体的业务逻辑

改代码应位于cmd/user/service目录下,每对映一个RPC服务,应该单独写一个服务类型,这样方便通过context传递上下文信息,这里我们举例一个服务。

package service
​
import (
    "context"
    "crypto/md5"
    "easynote/cmd/user/dal/db"
    "easynote/kitex_gen/user"
    "easynote/pkg/errno"
    "fmt"
    "io"
)
​
//构造一个服务类,继承context接口
type CreateUserService struct {
    ctx context.Context
}
​
// NewCreateUserService new CreateUserService,每次服务调用根据对应的context创建一个服务类型
func NewCreateuserService(ctx context.Context) *CreateUserService {
    return &CreateUserService{ctx: ctx}
}
​
// 具体的service方法,复杂的业务逻辑可以在这里实现
func (s *CreateUserService) CreateUser(req *user.CreateUserRequest) error {
    //先查询数据库是否存在用户
    users, err := db.QueryUser(s.ctx, req.UserName)
    if err != nil {
        return err
    }
    if len(users) != 0 {
        return errno.UserAlreadyExistErr
    }
    h := md5.New()
    if _, err = io.WriteString(h, req.Password); err != nil {
        return err
    }
    //经过MD5加密,但是不能防止字典攻击
    passWord := fmt.Sprint("%x", h.Sum(nil))
    return db.CreateUser(s.ctx, []*db.User{{
        UserName: req.UserName,
        Password: passWord,
    }})
}

9、编写对应的dal操作

到目前为止,我们已经实现对了对RPC响应的部分,接下来的部分与RPC完全解耦合,与正常的web开发相同的DAO操作,你可以按照你自己喜欢的方式完成DAO层的开发,但是这里为了配合规范,我们采用官方demo的构建方式。

#此处是我们的目录规范
├── dal
   ├── db
   │   ├── init.go
   │   └── user.go
   └── init.go
1、编写封装启动类(规范)
//init.go, 此处只是为了方便调用封装了内部的启动类
package dal
​
import (
    "easynote/cmd/user/dal/db"
)
​
func Init() {
    db.Init() // mysql init
}
2、编写启动类
//db/init.go
package db
​
import (
    "easynote/pkg/constants"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    gormopentracing "gorm.io/plugin/opentracing"
)
​
var DB *gorm.DB
​
// Init init DB
func Init() {
    var err error
    DB, err = gorm.Open(mysql.Open(constants.MySQLDefaultDSN),
        &gorm.Config{
            PrepareStmt:            true,
            SkipDefaultTransaction: true,
        },
    )
    if err != nil {
        panic(err)
    }
    //添加了对应的链路追踪
    if err = DB.Use(gormopentracing.New()); err != nil {
        panic(err)
    }
}
3、编写具体的DAL操作

正常情况我们的DAO开发会有对应的一个Model包来保存对应的实体类,但是由于目前学习到的微服务主要聚焦于单实体类->对应一个service,所以定义model与对应的DAO操作我们放到一个类中编写。

//dal/user.go
// model
type User struct {
    gorm.Model
    UserName string `json:"user_name"`
    Password string `json:"password"`
}
​
//存储正确的表名,gorm会根据对应的实体类变复数然后查询该表
func (u *User) TableName() string {
    return constants.UserTableName
}
​
// MGetUsers multiple get list of user info
func MGetUsers(ctx context.Context, userIDs []int64) ([]*User, error) {
    res := make([]*User, 0)
    if len(userIDs) == 0 {
        return res, nil
    }
    //WithContext方法可以创建一个新的DBsession
    if err := DB.WithContext(ctx).Where("id in ?", userIDs).Find(&res).Error; err != nil {
        return nil, err
    }
    return res, nil
}
​
// CreateUser create user info
func CreateUser(ctx context.Context, users []*User) error {
    //返回DB封装的错误类型
    return DB.WithContext(ctx).Create(users).Error
}
​
// QueryUser query list of user info
func QueryUser(ctx context.Context, userName string) ([]*User, error) {
    res := make([]*User, 0)
    if err := DB.WithContext(ctx).Where("user_name = ?", userName).Find(&res).Error; err != nil {
        return nil, err
    }
    return res, nil
}

10、Server启动类添加配置

package mainimport (
    "easynote/cmd/user/dal"
    user "easynote/kitex_gen/user/userservice"
    "easynote/pkg/bound"
    "easynote/pkg/constants"
    "easynote/pkg/middleware"
    tracer2 "easynote/pkg/tracer"
    "github.com/cloudwego/kitex/pkg/klog"
    "github.com/cloudwego/kitex/pkg/limit"
    "github.com/cloudwego/kitex/pkg/rpcinfo"
    "github.com/cloudwego/kitex/server"
    etcd "github.com/kitex-contrib/registry-etcd"
    trace "github.com/kitex-contrib/tracer-opentracing"
    "net"
)
​
// 辅助启动类
func Init() {
    tracer2.InitJaeger(constants.UserServiceName)
    dal.Init()
}
​
func main() {
    r, err := etcd.NewEtcdRegistry([]string{constants.EtcdAddress})
    if err != nil {
        panic(err)
    }
    addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:18889")
    if err != nil {
        panic(err)
    }
    Init()
    svr := user.NewServer(new(UserServiceImpl),
        server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: constants.UserServiceName}), // 注册服务名称
        server.WithMiddleware(middleware.CommonMiddleware),                                             // 注册中间件
        server.WithMiddleware(middleware.ServerMiddleware),
        server.WithServiceAddr(addr),                                       // 绑定服务地址
        server.WithLimit(&limit.Option{MaxConnections: 1000, MaxQPS: 100}), // 连接配置
        server.WithMuxTransport(),                                          // 多路复用
        server.WithSuite(trace.NewDefaultServerSuite()),                    // 链路追踪
        server.WithBoundHandler(bound.NewCpuLimitHandler()),                // CPU监测
        server.WithRegistry(r),                                             // 服务注册
    )
    err = svr.Run()
    if err != nil {
        klog.Fatal(err)
    }
}
​

到此为止,我们对应的一个服务的Server端就已经编写完成,那么微服务还有一个重要的部分就是门户,也就是我们俗称的gateway,这里我们尝试学习和改造Kitex给我们提供的hertz版本,采用更为熟悉的GinHttp框架作为我们gateway的开发框架。