[ 项目记录 0安装 | 青训营笔记 ]

544 阅读4分钟

[ 项目记录 0 | 青训营笔记 ]

前言:

这次冬令营教我们使用的是 kitex + hertz 虽然平常还是 gin + grpc 用的比较多,但是借此机会还是尝试一下新的框架

但是由于 (我太菜了) kitex 和 hertz 的文档写的不是很详细,这边还是遇到了很多问题记录一下。

hertz 比较好弄配置什么的就按文档走就ok

p.s. 这已经算是踩坑合集了没想到写完的时候已经两天了


配置WSL2

(我感觉我弄复杂了,让我的电脑再次负重但是能用先用)

  • 用管理员权限打开powershell,输入以打开虚拟环境
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
  • 进入 Microsoft Store 下载 Ubuntu

  • 喝茶等一会下载

  • 有可能启动ubuntu报告WslRegisterDistribution failed with error: 0x800701bc是因为没有升级linux内核使用wsl --update下载更新即可

  • 打开ubuntu出现 Error code: Wsl/Service/0x8007273d,以管理员身份启动powershell , 运行

    netsh winsock reset
    
  • 在 powershell 里输入 wsl --set-default-version 2 使用wsl2

ubuntu 配置 go

  • 下载安装包 sudo wget https://golang.google.cn/dl/go1.19.linux-amd64.tar.gz

  • 解压 sudo tar -zxvf go1.19.linux-amd64.tar.gz

  • 环境配置 打开vim ~/.bash_profile 后 按 i 输入

    export GOPATH=/home/linxx/go
    export GOROOT=/usr/local/go
    export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
    export GOPROXY=https://goproxy.cn
    

    esc + :wq 保存退出

2023-01-26-16-36-37-image.png source .bash_profile使环境变量生效

  • 下载 kitex + thriftgo

    go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
    go install github.com/cloudwego/thriftgo@latest
    

docker-compose

这里另外补充一个docker-compose

  • 安装docker desktop

  • 如果你会发现 docker is starting... 或者运行 docker ps

    error during connect: This error may indicate that the docker daemon is not running.: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/containers/json": open //./pipe/docker_engine: The system cannot find the file specified.
    
  • 管理员 powershell 下运行

    dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
    dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
    
  • 如果是

    Error response from daemon: open \.\pipe\docker_engine_windows: The system cannot find the file specified.
    
  • 右键小鲸鱼 switch to linux containers

  • 此外这里说明一下确实是不需要 Hyper-v 的

  • 进去之后在设置的 Docker Engine 中配置镜像

    {
      "registry-mirrors": [
        "http://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn"
      ],
      "insecure-registries": [],
      "debug": true,
      "experimental": false
    }
    
  • docker desktop 里自带 docker compose 运行 docker compose up 就可以部署容器辣


echo demo

wait,先不急写项目先实现一下文档的demo

创建 idl 文件,定义服务

namespace go api

struct Request {
  1: string message
}

struct Response {
  1: string message
}

service Echo {
    Response echo(1: Request req)
}

在ubuntu里输入 kitex -module "awesome" -thrift frugal_tag -service helloserver ../idl/hello.thrift 这里 awesomego mod 里 module的名字

tree /f 可以输出项目的结构

│  build.sh     // 构建脚本
│  kitex.yaml   
│  main.go      // 程序入口
│  handler.go   // 实现方法
│
├─kitex_gen     // 生成的sever/client代码别动就对了
│  └─api
│      │  hello.go
│      │  k-consts.go
│      │  k-hello.go
│      │
│      └─hello           
│              client.go
│              server.go
│              invoker.go
│              hello.go
│
└─script
        bootstrap.sh 

在 handler 编写好方法后,运行服务端

kitex 工具已经帮我们生成好了编译和运行所需的脚本:

$ sh build.sh

执行上述命令后,会生成一个 output 目录,里面含有我们的编译产物。运行:

$ sh output/bootstrap.sh

2023-01-26-16-11-41-image.png

创建一个客户端

func main() {
    // 创建
    c, err := echo.NewClient("awesome", client.WithHostPorts("0.0.0.0:8888"))
    if err != nil {
        log.Fatal(err)
    }
    req := &api.Request{Message: "my request"}
    // 发起调用
    resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(1*time.Second))
    if err != nil {
        log.Fatal(err)
    }
    log.Println(resp)
}

这里是文档的解释:

echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址

上述代码中,我们首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。

其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为,你可以在后续章节中找到如何使用它。

其第二个参数为本次调用的请求。

其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout 用于指定此次调用的超时。

在同一系统下 $ go run main.go 就可以看到返回值了

2023-01-26-16-11-54-image.png


*gorm gen

安装 go get -u gorm.io/gen

使用Gen提供的gen.NewGenerator函数就可以生成对应的model gorm 自动生成 参考资料二

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gen"
    "gorm.io/gorm"
    "strings"
    "testing"
)

const MysqlConfig = "root:123456@(localhost:3306)/gen_test?charset=utf8mb4&parseTime=True&loc=Local"

func TestGEN(t *testing.T) {
    // 连接数据库
    db, err := gorm.Open(mysql.Open(MysqlConfig))
    if err != nil {
        panic(fmt.Errorf("cannot establish db connection: %w", err))
    }

    // 生成实例
    g := gen.NewGenerator(gen.Config{
        // 相对执行`go run`时的路径, 会自动创建目录
        OutPath: "./query",

        // WithDefaultQuery 生成默认查询结构体(作为全局变量使用), 即`Q`结构体和其字段(各表模型)
        // WithoutContext 生成没有context调用限制的代码供查询
        // WithQueryInterface 生成interface形式的查询代码(可导出), 如`Where()`方法返回的就是一个可导出的接口类型
        Mode: gen.WithDefaultQuery | gen.WithQueryInterface,

        // 表字段可为 null 值时, 对应结体字段使用指针类型
        FieldNullable: true, // generate pointer when field is nullable

        // 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段.
        // 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交.
        // 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便.
        FieldCoverable: false, // generate pointer when field has default value, to fix problem zero value cannot be assign: https://gorm.io/docs/create.html#Default-Values

        // 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型
        FieldSignable: false, // detect integer field's unsigned type, adjust generated data type
        // 生成 gorm 标签的字段索引属性
        FieldWithIndexTag: false, // generate with gorm index tag
        // 生成 gorm 标签的字段类型属性
        FieldWithTypeTag: true, // generate with gorm column type tag
    })
    // 设置目标 db
    g.UseDB(db)

    // 自定义字段的数据类型
    // 统一数字类型为int64,兼容protobuf
    dataMap := map[string]func(detailType string) (dataType string){
        "tinyint":   func(detailType string) (dataType string) { return "int64" },
        "smallint":  func(detailType string) (dataType string) { return "int64" },
        "mediumint": func(detailType string) (dataType string) { return "int64" },
        "bigint":    func(detailType string) (dataType string) { return "int64" },
        "int":       func(detailType string) (dataType string) { return "int64" },
    }
    // 要先于`ApplyBasic`执行
    g.WithDataTypeMap(dataMap)

    // 自定义模型结体字段的标签
    // 将特定字段名的 json 标签加上`string`属性,即 MarshalJSON 时该字段由数字类型转成字符串类型
    jsonField := gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
        toStringField := `balance, `
        if strings.Contains(toStringField, columnName) {
            return columnName + ",string"
        }
        return columnName
    })
    // 将非默认字段名的字段定义为自动时间戳和软删除字段;
    // 自动时间戳默认字段名为:`updated_at`、`created_at, 表字段数据类型为: INT 或 DATETIME
    // 软删除默认字段名为:`deleted_at`, 表字段数据类型为: DATETIME
    autoUpdateTimeField := gen.FieldGORMTag("update_time", "column:update_time;type:int unsigned;autoUpdateTime")
    autoCreateTimeField := gen.FieldGORMTag("create_time", "column:create_time;type:int unsigned;autoCreateTime")
    softDeleteField := gen.FieldType("delete_time", "soft_delete.DeletedAt")
    // 模型自定义选项组
    fieldOpts := []gen.ModelOpt{jsonField, autoCreateTimeField, autoUpdateTimeField, softDeleteField}

    // 创建模型的结构体,生成文件在 model 目录; 先创建的结果会被后面创建的覆盖
    // 这里创建个别模型仅仅是为了拿到`*generate.QueryStructMeta`类型对象用于后面的模型关联操作中
    User := g.GenerateModel("user")
    // 创建全部模型文件, 并覆盖前面创建的同名模型
    allModel := g.GenerateAllTable(fieldOpts...)

    // 创建有关联关系的模型文件
    // 可以用于指定外键
    //Score := g.GenerateModel("score",
    //    append(
    //        fieldOpts,
    //        // user 一对多 address 关联, 外键`uid`在 address 表中
    //        gen.FieldRelate(field.HasMany, "user", User, &field.RelateConfig{GORMTag: "foreignKey:UID"}),
    //    )...,
    //)

    // 创建模型的方法,生成文件在 query 目录; 先创建结果不会被后创建的覆盖
    g.ApplyBasic(User)
    g.ApplyBasic(allModel...)

    g.Execute()
}

生成了两个文件夹,其中model文件夹下面是数据表对应的结构体,query文件夹下面则是生成的查询函数。


好了,demo可以实现了,但是还是先不开始写项目,先研究一下范例 easy-note

先运行起来

大概梳理一下运行的逻辑,ok可以写项目了