goctl

goctl-api

  1. goctl为我们避免了手工创建各级目录等,也会为我们生成简单http的代码,我们只需要编写api文件即可。
    这是一个hello.api文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //api语法版本
    syntax = "v1"

    //请求路径
    type Request {
    Name string `path:"name,options=you|me"`
    }
    //返回值类型
    type Response {
    Message string `json:"message"`
    }

    // 拦截器以及请求方式
    service hello-api {
    @handler HelloHandler
    get /from/:name (Request) returns (Response)
    }
    编写好api文件后,执行以下命令就会自动帮我们生成代码以及目录
    1
    2
    // 这个表示为所有api文件生成代码,指定文件可将*号换成指定api文件, dir .. 创建的文件在上一级目录下
    goctl api go -api *.api dir ../ --style=gozero
    结果:
  2. 基础代码生成后,别忘记下载依赖
    1
    2
    //该命令可以一键下载依赖
    go mod tidy

goctl生成代码执行流程

先看目录结构

这里的入口文件即是hello.go文件,所以我们先看入口文件

  1. hello.go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package main

    import (
    "flag"
    "fmt"

    "hello/internal/config"
    "hello/internal/handler"
    "hello/internal/svc"

    "github.com/zeromicro/go-zero/core/conf"
    "github.com/zeromicro/go-zero/rest"
    )
    //这里是将配置文件导入
    var configFile = flag.String("f", "etc/hello-api.yaml", "the config file")

    func main() {
    //先解析配置文件
    flag.Parse()
    //将配置文件给到自定义的配置文件类型的变量c
    var c config.Config
    conf.MustLoad(*configFile, &c)
    //获取http服务
    server := rest.MustNewServer(c.RestConf)
    //服务延迟结束
    defer server.Stop()
    //将上下文信息传给ctx
    ctx := svc.NewServiceContext(c)
    //将http请求和上下文信息发给拦截器
    handler.RegisterHandlers(server, ctx)

    fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
    server.Start()
    }
  2. 可以看到入口程序会先将配置文件加载进来,接着根据配置文件会获取一个server,server里面会包含http请求路径和参数等,之后会调用svc目录下的servercontext.go文件中的函数NewServiceContext,我们需要着重看这个文件,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package svc

    import (
    "hello/internal/config"
    )
    //这里配置的是上下文可能用到的功能,例如配置文件,数据库,redis,消息队列等,这里只展示了配置文件
    type ServiceContext struct {
    Config config.Config
    }
    // 这个函数会将这些功能返回到主函数中,再由主函数将获取的上下文功能传给hander
    func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
    Config: c,
    }
    }
  3. 在主函数中获取到了上下文的对象(里面包含了可能用到的功能),并将上下文对象和server传给了路由(routes.go),由路由转发拦截器
    接着看路由:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package handler

    import (
    "net/http"

    "hello/internal/svc"

    "github.com/zeromicro/go-zero/rest"
    )

    func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
    server.AddRoutes(
    []rest.Route{
    {
    Method: http.MethodGet,
    //路径,修改路径在types.go文件中
    Path: "/from/:name",
    Handler: HelloHandler(serverCtx),
    },
    },
    )
    }
    可以看到主函数中调用的就是路由中的函数,我这里只写了一个接口,所有只有一个请求路径,当满足这个路径时,会调用拦截器里的函数。
    拦截器:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package handler

    import (
    "net/http"

    "github.com/zeromicro/go-zero/rest/httpx"
    "hello/internal/logic"
    "hello/internal/svc"
    "hello/internal/types"
    )

    func HelloHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
    //获取请求变量
    var req types.Request
    // 如果请求有误,则返回空
    if err := httpx.Parse(r, &req); err != nil {
    httpx.ErrorCtx(r.Context(), w, err)
    return
    }
    // 将上下文对象和请求信息传给业务逻辑
    l := logic.NewHelloLogic(r.Context(), svcCtx)
    // 获取业务逻辑处理后的结果
    resp, err := l.Hello(&req)
    if err != nil {
    httpx.ErrorCtx(r.Context(), w, err)
    } else {
    httpx.OkJsonCtx(r.Context(), w, resp)
    }
    }
    }
    可以看到在拦截器中,会将请求信息和上下文信息都传给了业务逻辑层的函数。
    业务逻辑函数(logic目录下):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package logic

    import (
    "context"

    "hello/internal/svc"
    "hello/internal/types"

    "github.com/zeromicro/go-zero/core/logx"
    )

    type HelloLogic struct {
    logx.Logger
    ctx context.Context
    svcCtx *svc.ServiceContext
    }

    func NewHelloLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HelloLogic {
    return &HelloLogic{
    Logger: logx.WithContext(ctx),
    ctx: ctx,
    svcCtx: svcCtx,
    }
    }

    func (l *HelloLogic) Hello(req *types.Request) (resp *types.Response, err error) {
    // todo: add your logic here and delete this line
    // 这里是具体的逻辑,例如增删改查数据库,redis等
    resp = new(types.Response)
    resp.Message = req.Name
    return
    }
    至此整个http的请求过程就完毕了

goctl-model

goctl-model 可以帮我们快速生成对单表的增删改查,类似于mybatis-plus。
执行以下命令即可生成big_event数据库中user表的增删改查

1
goctl model mysql datasource -url="root:12345@tcp(127.0.0.1:3306)/big_event" -table="user"  -dir="model" -cache=true --style=goZero

效果:

goctl 一键安装环境

使用以下命令可以一键安装缺少的依赖等,但是goctl版本要大于1.3.3

1
goctl env check -i -f

gorm

gorm框架类似于JAVA中的mybatis-plus,是用于操作数据库的

  • zero整合gorm
    1. 安装grom
      1
      2
      3
      go get -u gorm.io/gorm

      go get -u gorm.io/driver/mysql
    2. yaml文件加入数据库连接池,并导入到配置文件中
    3. 在svc目录下,上下文文件中加入gorm
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
       type ServiceContext struct {
      Config config.Config
      Model model.UserModel
      //上下文添加gorm类型变量,方便使用
      DB *gorm.DB
      }

      func NewServiceContext(c config.Config) *ServiceContext {
      var db *gorm.DB
      //进行连接数据库
      db, _ = gorm.Open(mysql.Open(c.DB.DataSource), &gorm.Config{})
      return &ServiceContext{
      Config: c,
      DB: db,
      }
      }
  1. 在handler获取DB,进行增删改查
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 定义一个用户变量
    var user models.User

    // 打印更详细的查询信息
    result := l.svcCtx.DB.Table("user").Where("id = ?", req.Id).First(&user)
    l.Logger.Infof("查询结果: %+v", user)
    if result.Error != nil {
    l.Logger.Errorf("查询失败: %v", result.Error)
    return nil, result.Error
    }

    resp = &types.Response{
    Id: user.Id, // 确保 models.User 结构体中的字段名与数据库字段对应
    Username: user.Username,
    Password: user.Password,
    }

Etcd

etcd是用于go微服务的服务注册和服务发现功能,他更像是一个加强版的redis。

一般我们会将rpc服务注册到etcd中,然后api接口对前端调用,在同api调用rpc服务。
先来看一个简单的rpc服务:

  • 我们先通过proto文件快速创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    syntax = "proto3";

    package user;

    option go_package = "./user";

    message IdRequest {
    string id = 1;
    }

    message UserResponse {
    // 用户id
    string id = 1;
    // 用户名称
    string username = 2;

    }

    service User {
    rpc getUser(IdRequest) returns(UserResponse);
    }

    // goctl rpc protoc user/rpc/user.proto --go_out=user/rpc/types --go-grpc_out=user/rpc/types --zrpc_out=user/rpc/
    通过这个文件可快速生成一个由id查询用户的rpc服务,以下是生成的目录结构:
  • rpc服务和普通的api服务一样,再yaml文件中添加mysql的连接配置,接着注册到config文件里,再添加到svc上下文中,并在逻辑文件里写入根据id查询数据并返回即可
    yaml文件:
    1
    2
    3
    4
    5
    6
    7
    8
    Name: user.rpc
    ListenOn: 0.0.0.0:8080
    Etcd:
    Hosts:
    - 127.0.0.1:2379
    Key: user.rpc
    Mysql:
    DataSource: root:12345@tcp(127.0.0.1:3306)/big_event?charset=utf8mb4&parseTime=True&loc=Local
    查询数据逻辑:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
    // todo: add your logic here and delete this line
    var resq user.UserResponse
    l.svcCtx.DB.Table("user").Select("id,username").Where("id=?", in.Id).First(&resq)
    return &user.UserResponse{
    Id: resq.Id,
    Username: resq.Username,
    }, nil
    }
  • api调用rpc
    yaml:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Name: video
    Host: 0.0.0.0
    Port: 8888
    UserRpc:
    Etcd:
    Hosts:
    - 127.0.0.1:2379
    Key: user.rpc
    Mysql:
    DataSource: root:12345@tcp(127.0.0.1:3306)/big_event?charset=utf8mb4&parseTime=True&loc=Local
  1. 同样config中加入rpc
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package config

    import (
    "github.com/zeromicro/go-zero/rest"
    "github.com/zeromicro/go-zero/zrpc"
    )

    type Config struct {
    rest.RestConf
    UserRpc zrpc.RpcClientConf
    Mysql struct {
    DataSource string
    }
    }
  2. svc中加入rpc
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package svc

    import (
    "github.com/zeromicro/go-zero/zrpc"
    "gorm.io/gorm"
    "project/user/rpc/userclient"
    "project/video/api/internal/config"
    )

    type ServiceContext struct {
    Config config.Config
    UserRpc userclient.User

    }

    func NewServiceContext(c config.Config) *ServiceContext {


    return &ServiceContext{
    Config: c,
    UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
    }
    }
  3. 接着就是再逻辑文件中调用rpc即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func (l *GetVideoLogic) GetVideo(req *types.VideoReq) (resp *types.VideoRes, err error) {
    // todo: add your logic here and delete this line
    user1, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{
    Id: req.Id,
    })
    fmt.Println(resp, err)
    return &types.VideoRes{
    Id: req.Id,
    Username: user1.Username,
    }, nil
    }
    最后同时启动rpc和api服务,然后调用api接口即可