2SOMEone | Go-Zero后端框架

by CUNOE, April 13, 2024

Go-Zero减少了重复开发,让程序员更关注到业务设计与逻辑代码实现上。

开发流程

前期准备

  • goctl环境准备
  • 新建工程
  • 创建项目目录(可参考下方项目目录结构)

设计

  • 数据库设计
  • 业务设计

开发

API层

  • 编写.api文件
  • 代码生成
  • 中间件实现
  • Config添加RPC配置
  • ServiceContext添加RPC
  • 调用RPC接口实现相关逻辑

RPC层

  • 编写.proto文件
  • 代码生成
  • Config添加数据库链接
  • ServiceContext添加数据库交互类
  • 调用数据库交互、完成数据处理

ORM数据库层

  • 编写数据库结构表
  • 编写数据库访问事务

Goctl 环境

GoctlVersion: 1.6.3
ProtocVersion: libprotoc 25.2
ProtocGenGoVersion: 1.33.0
ProtocGenGoGrpcVersion: 1.3.0

goctl是go-zero框架的代码生成工具,可以通过goctl生成API、RPC、Model等代码。

  • 安装goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
  • protoc 下载地址
https://github.com/protocolbuffers/protobuf/releases/tag/v25.2

安装protoc-gen-go

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@1.3.0

项目结构参考

$ tree -d
.
├── common    // 存放全局通用的库
│   ├── ctxdata    // 从context中获取信息
│   ├── util    // 一些自定义的工具类
│   │   ├── xdb
│   │   ├── xmath
│   │   └── ......
│   ├── xerrors    // 自定义错误处理
│   └── xtypes    // 自定义类型
│       ├── ......
│       ├── xcode    // 自定义错误码
│       └── xerr    // 自定义错误类
├── docker    // Docker相关的配置文件存放于此 Dockerfile/docker-compose.yml 等
├── etc    // 所有API、RPC的配置文件存放于此
├── flyio    // Flyio 配置
├── restful    // 该文件夹下存放所有的API
│   └── bubblebox
│       ├── api_file    // 存放该项目所有的API定义
│       │   ├── ......
│       │   └── user
│       ├── etc
│       └── internal
│           ├── config
│           ├── handler
│           ├── logic    // 业务逻辑
│           │   ├── userLogin
│           │   ├── userNickname
│           │   └── ......
│           ├── middleware    // 中间件
│           ├── svc    // 上下文
│           └── types
├── script    // 脚本存放位置
├── service    // 各种Service存放位置 RPC/Store
│   ├── ......
│   ├── oss
│   └── user
│       ├── core    // 数据库表结构定义
│       ├── danmuauth-saas
│       ├── rpc
│       │   ├── etc
│       │   ├── internal
│       │   │   ├── config
│       │   │   ├── logic    // 业务逻辑
│       │   │   ├── server
│       │   │   └── svc
│       │   ├── pb
│       │   │   └── user
│       │   └── userservice
│       └── store    // 数据库交互
└── worker    // 一些额外的 Worker
└── guarder

开发实用脚本

生成API代码

#!/usr/bin/env bash
# 合并API文件
./script/merge_api_files.sh bubblebox.api
# 生成API代码
goctl api format --dir ./restful/bubblebox/bubblebox.api
goctl api go -api ./restful/bubblebox/bubblebox.api  -dir ./restful/bubblebox -style gozero
# 生成RPC代码
goctl rpc protoc ./service/post/rpc/post.proto --go_out=./service/post/rpc/pb --go-grpc_out=./service/post/rpc/pb --zrpc_out=./service/post/rpc
goctl rpc protoc ./service/user/rpc/user.proto --go_out=./service/user/rpc/pb --go-grpc_out=./service/user/rpc/pb --zrpc_out=./service/user/rpc
# 更多...

合并API文件

在项目体量较大时,我们可能会有很多的API文件,我们可以通过脚本将这些API文件合并成一个文件,然后通过goctl生成代码。

通过拆分api文件以避免出现一个文件上千行的代码的情况。

添加script/merge_api_files.sh文件

#!/usr/bin/env bash

CRT_DIR=$(pwd)

# 定义工作的路径
work_path="$CRT_DIR/restful/bubblebox" # 请修改为你的项目API路径

# 零散的 api 文件所在的目录
api_files_path="$work_path/api_file"

# 定义输出文件路径
# output_file="bubblebox.api"
# 第一步:从命令行参数中获取输出文件名
output_file="$1"

# 第二步:检查是否提供了输出文件名
if [ -z "$output_file" ]; then
  echo "Usage: $0 <output_file>"
  exit 1
fi
output_file="$work_path/$output_file"


# 定义需要放在开头的文件
header_file="HEADER_INFO"
header_file="$api_files_path/$header_file"

# 第一步:检查 header 文件是否存在
if [ -f "$header_file" ]; then
  # 如果存在,首先将 header 文件的内容写入到输出文件中
  cat "$header_file" > "$output_file"
else
  # 如果 header 文件不存在,创建一个空的输出文件
  > "$output_file"
fi

# 第二步:合并其他文件
# 使用 for 循环遍历目录下的所有 txt 文件
find "$api_files_path" -type f -name "*.api" | while read -r file; do
  # 跳过 header 文件,避免重复合并
  if [ "$file" != "$header_file" ]; then
    # 添加一个空行到 output_file
    echo "" >> "$output_file"
    # 使用 >> 操作符将内容追加到 output_file
    cat "$file" >> "$output_file"
  fi

  # 检查文件最后一行是否为空行
  last_line=$(tail -n 1 "$file")
  if [ -n "$last_line" ]; then
      # 如果最后一行不为空,添加一个空行
    echo "" >> "$output_file"
  fi
done

goctl api format -dir $output_file

Goctl 模板

参考:https://go-zero.dev/docs/tutorials/customization/template

实现统一响应结构体

新建leaper-one/pkg包,添加https/response/response.go

package response

import (
    "github.com/zeromicro/go-zero/rest/httpx"
    "net/http"
)

type Body struct {
    Code int         `json:"code"`
    Msg  string      `json:"msg"`
    Data interface{} `json:"data"`
}

func Response(w http.ResponseWriter, resp interface{}, err error) {
    var body Body
    if err != nil {
        body.Code = -1
        body.Msg = err.Error()
    } else {
        body.Code = 0
        body.Msg = "success"
        body.Data = resp
    }
    httpx.OkJson(w, body)
}

如果本地没有~/.goctl/${goctl版本号}/api/handler.tpl文件,可以通过模板初始化命令goctl template init进行初始化

vim ~/.goctl/${goctl版本号}/api/handler.tpl

Windows则打开

C:\Users\{用户名}\.goctl\${goctl版本号}\api\handler.tpl

编辑为如下

package {{.PkgName}}

import (
        "net/http"

        "github.com/zeromicro/go-zero/rest/httpx"
        "github.com/leaper-one/pkg/https/response"
        {{.ImportPackages}}
)

func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
                {{if .HasRequest}}var req types.{{.RequestType}}
                if err := httpx.Parse(r, &req); err != nil {
                        httpx.Error(w, err)
                        return
                }

                {{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
                {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
                {{if .HasResp}}response.Response(w, resp, err){{else}}response.Response(w, nil, err){{end}}
        }
}

API IDL

🔗Link

总结

Go-Zero框架的引入,使得2SOMEone的后端开发更加高效,减少了重复开发,让我们更关注到业务设计与逻辑代码实现上。