Go-Zero自定义goctl实战:定制化模板,加速你的微服务开发效率(四)

前言

上一篇文章带你实现了Go-Zero和goctl:解锁微服务开发的神器,快速上手指南,本文将继续深入探讨Go-Zero的强大之处,并介绍如何使用goctl工具实现模板定制化,并根据实际项目业务需求进行模板定制化实现。

通过本文的教程,你能够亲自实践并完成goctl模板的定制化,进一步提升你的Go-Zero开发技能。

概述

goctl 代码生成是基于 go 的模板去实现数据驱动的,默认情况会选择内存中的模板进行生成,当开发需要修改模板时,就需要定制化模板,goctl为我们实现了这一功能。

实战前准备

首先需要你在本地安装goctl、protoc、go-zero,下载教学和地址点击这里,按照教程操作即可,非常简单。

下面按顺序和我操作吧,对整体开发流程不清楚的同学务必先看我前篇文章:GoZero的开发技巧 & 整体开发流程

本文重在实战,如果对goctl毫不了解的话,建议先看我前一篇文章:Go-Zero和goctl:解锁微服务开发的神器,快速上手指南

以下均以我的商业项目举例,应该对你有启发:

(后面我会把商业项目脱敏开源出来,欢迎关注我)

数据表生成Model方法脚本

首先在deploy下新增script目录,结构如下图所示。

脚本内容如下:

#!/usr/bin/env bash

# 使用方法:
# ./genModel.sh lottery lottery
# ./genModel.sh lottery prize
# 再将./genModel下的文件剪切到对应服务的model目录里面,记得改package

#生成的表名
tables=$2
#表生成的genmodel目录
modeldir=./genModel

# 数据库配置
host=127.0.0.1
port=33069
dbname=$1
username=root
passwd=PXDN93VRKUm8TeE7
template=../../goctl/1.6.1

echo "开始创建库:$dbname 的表:$2"
goctl model mysql datasource -url="${username}:${passwd}@tcp(${host}:${port})/${dbname}" -table="${tables}" -dir="${modeldir}" -cache=true --home="${template}" --style=goZero

模板定制化使用方法

相关命令使用详情,参考:官网文档
具体使用方法网上有很多文章介绍,官网也有详细步骤。这里更加注重商业项目对于模板定制化的实战,对相关操作不进行赘述,快速过一遍流程即可。

初始化模板到本地

依据前文所介绍的项目目录结构,我们将自定义模板放在deploy下面即可,并且采用的版本号为1.6.1(目录路径根据自己实际情况修改)

goctl template init --home $HOME/Desktop/lottery-backend/deploy/goctl/1.6.1

注意:如果不指定–home 他会初始化到$HOME/.goctl

这样就生成好自己版本的goctl模板啦,可以根据自己的实际需求进行模板的修改。

接下来分享我们项目中关于自定义goctl的实战。

自定义goctl实战

实战1:Model层方法定制化

很多时候我们需要对数据进行分页查询。这个方法是一个通用的方法,可以在很多地方复用,所以放入模板去生成,这样可以减少重复代码,提高开发效率。

步骤一:在model/update.tpl下面新增一个方法FindPageListByPage

方法具体实现如下

func (m *default{{.upperStartCamelObject}}Model) FindPageListByPage(ctx context.Context,builder squirrel.SelectBuilder,page ,pageSize int64,orderBy string) ([]*{{.upperStartCamelObject}},error) {

    builder = builder.Columns({{.lowerStartCamelObject}}Rows)

	if orderBy == ""{
		builder = builder.OrderBy("id DESC")
	}else{
		builder = builder.OrderBy(orderBy)
	}

	if page < 1{
		page = 1
	}
	offset := (page - 1) * pageSize

	query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
	if err != nil {
		return nil, err
	}

	var resp []*{{.upperStartCamelObject}}
	{{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}}
	err = m.conn.QueryRowsCtx(ctx,&resp, query, values...)
	{{end}}
	switch err {
	case nil:
		return resp, nil
	default:
		return nil, err
	}
}

步骤二:使用之前做好的脚本生成代码

使用GitBash打开deploy/script/mysql目录,执行脚本

此时genModel目录下面就会生成相关代码

步骤三:将生成的代码剪切到项目目录的对应位置

效果

默认模板生成的Model层方法

自定义模板生成的Model层方法

生成的FindPageListByPage方法

func (m *defaultLotteryModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Lottery, error) {

	builder = builder.Columns(lotteryRows)

	if orderBy == "" {
		builder = builder.OrderBy("id DESC")
	} else {
		builder = builder.OrderBy(orderBy)
	}

	if page < 1 {
		page = 1
	}
	offset := (page - 1) * pageSize

	query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
	if err != nil {
		return nil, err
	}

	var resp []*Lottery
	err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
	switch err {
	case nil:
		return resp, nil
	default:
		return nil, err
	}
}

实战2:api自定义响应返回以及集成validator库校验参数

当我们希望自定义统一返回响应体以及希望每个api接口都进行参数校验时,我们可以在模板中修改handler层的代码,从而实现这些效果。

步骤一:实现自定义统一返回响应

在common目录下新建result目录和httpResult.go文件,如下图所示

具体实现代码不是本文重点,下面是提供的代码

package result

import (
	"fmt"
	"net/http"

	"looklook/common/xerr"

	"github.com/pkg/errors"
	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/rest/httpx"
	"google.golang.org/grpc/status"
)

// http返回
func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) {

	if err == nil {
		//成功返回
		r := Success(resp)
		httpx.WriteJson(w, http.StatusOK, r)
	} else {
		//错误返回
		errcode := xerr.SERVER_COMMON_ERROR
		errmsg := "服务器开小差啦,稍后再来试一试"

		causeErr := errors.Cause(err)                // err类型
		if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型
			//自定义CodeError
			errcode = e.GetErrCode()
			errmsg = e.GetErrMsg()
		} else {
			if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误
				grpcCode := uint32(gstatus.Code())
				if xerr.IsCodeErr(grpcCode) { //区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端
					errcode = grpcCode
					errmsg = gstatus.Message()
				}
			}
		}

		logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err)

		httpx.WriteJson(w, http.StatusBadRequest, Error(errcode, errmsg))
	}
}

// http 参数错误返回
func ParamErrorResult(r *http.Request, w http.ResponseWriter, err error) {
	errMsg := fmt.Sprintf("%s ,%s", xerr.MapErrMsg(xerr.REUQEST_PARAM_ERROR), err.Error())
	httpx.WriteJson(w, http.StatusBadRequest, Error(xerr.REUQEST_PARAM_ERROR, errMsg))
}

步骤二:在handler下面引入定制的validator包

关于定制validator也不是本文重点,感兴趣的同学可以关注我,留言。

package translator

import (
	"errors"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
	"looklook/app/lottery/cmd/api/internal/logic/lottery"
	"looklook/app/lottery/cmd/api/internal/types"
	"reflect"
	"strings"
)

func Validate(dataStruct interface{}) error {
	zh_ch := zh.New()
	validate := validator.New()
	// 注册一个函数,获取struct tag里自定义的label作为字段名
	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
		if name == "-" {
			return ""
		}
		return name
	})

	// 在这里注册自定义结构体/字段校验方法
	// 注册自定义结构体校验方法
	validate.RegisterStructValidation(lottery.SignUpParamStructLevelValidation, types.TestReq{})

	// 注册自定义结构体字段校验方法
	if err := validate.RegisterValidation("checkDate", lottery.CheckDate); err != nil {
		return err
	}

	uni := ut.New(zh_ch)
	trans, _ := uni.GetTranslator("zh")

	// 在这里注册自定义tag翻译
	// 注意!因为这里会使用到trans实例
	// 所以这一步注册要放到trans初始化的后面

	if err := validate.RegisterTranslation(
		"checkDate",
		trans,
		registerTranslator("checkDate", "{0}必须要晚于当前日期"),
		translate,
	); err != nil {
		return err
	}

	// 验证器注册翻译器
	zh_translations.RegisterDefaultTranslations(validate, trans)
	err := validate.Struct(dataStruct)
	if err != nil {
		for _, err := range err.(validator.ValidationErrors) {
			return errors.New(err.Translate(trans))
		}
	}
	return nil
}

// registerTranslator 为自定义字段添加翻译功能
func registerTranslator(tag string, msg string) validator.RegisterTranslationsFunc {
	return func(trans ut.Translator) error {
		if err := trans.Add(tag, msg, false); err != nil {
			return err
		}
		return nil
	}
}

// translate 自定义字段的翻译方法
func translate(trans ut.Translator, fe validator.FieldError) string {
	msg, err := trans.T(fe.Tag(), fe.Field())
	if err != nil {
		panic(fe.(error).Error())
	}
	return msg
}

步骤三:修改handler.tpl模板代码

将模板替换为以下内容

package {{.PkgName}}

import (
	"net/http"

	"looklook/common/result"

	"github.com/zeromicro/go-zero/rest/httpx"
	{{.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.ErrorCtx(r.Context(), w, err)
			return
		}

		validateErr := translator.Validate(&req)
        if validateErr != nil {
            result.ParamErrorResult(r, w, validateErr)
            return
        }

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

		result.HttpResult(r, w, {{if .HasResp}}resp{{else}}nil{{end}}, err)
	}
}

步骤四:生成对应的代码

注意生成handler后需要手动点开生成的handler文件,导入translator包,否则服务会报错!!!

# 使用自定义的goctl 生成api
goctl api go -api main.api -dir ../  --style=goZero --home=../../../../../deploy/goctl/1.6.1

修改后的响应体

{
  "code": 200,
  "msg": "OK",
  "data": {
    "message": ""
  }
}

模板自定义规则

  1. 在 goctl 提供的有效数据范围内修改,即不支持外部变量
  2. 不支持新增模板文件
  3. 不支持变量修改

总结

本文介绍了如何使用Go-Zero的goctl工具进行自定义模板的实战,并提供了一个具体的案例来演示定制化模板的过程。

如果你需要详细的命令使用详情,可以参考官方文档中的相关内容。模板定制化 | go-zero Documentation

我将继续更新Go-Zero系列文章,如果你对Go语言或者微服务感兴趣,欢迎关注我,也欢迎直接私信我。

gozero&微服务交流群

我将继续更新Go-Zero系列文章,如果你对Go语言或者微服务感兴趣,欢迎关注我,也欢迎直接私信我。

微信:wangzhongyang1993

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/609730.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSP-j 计算机硬件

计算机系统 计算机系统由计算机硬件和软件两部分组成。硬件包括中央处理器、存储器和外部设备等&#xff1b;软件是计算机的运行程序和相应的文档。计算机系统具有接收和存储信息、按程序快速计算和判断并输出处理结果等功能。 主要技术指标 字长&#xff1a;字长是指CPU能够同…

浅谈冯诺依曼体系与Linux操作系统

目录 前言 1.1冯诺依曼体系下的存储器 二、操作系统 1.关于操作系统 2.关于管理方式 总结 前言 不知道你是否有着这样的疑问&#xff1a; 什么是内存&#xff1f;什么是磁盘&#xff1f;他们有什么区别&#xff1f;可不可以相互替代&#xff1f; 操作系统是如何对数据进行管…

聚类分析 | 基于GA遗传算法优化kmeans聚类(Matlab)

聚类分析 | 基于GA遗传算法优化kmeans聚类&#xff08;Matlab&#xff09; 目录 聚类分析 | 基于GA遗传算法优化kmeans聚类&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 GA-kmeans聚类算法&#xff0c;通过GA遗传算法优化kmeans聚类&…

vue2实现右键菜单功能——vue-diy-rightmenu——基础积累

五一之前遇到一个需求&#xff0c;就是关于要实现自定义右键菜单的功能&#xff0c;普通的右键展示的菜单有【返回/前进/重新加载/另存为】等&#xff0c;希望实现的效果就是右键出现自定义的菜单&#xff0c;比如【编辑/删除/新增】等。 遇到这种的需求&#xff0c;可以直接去…

C#进阶-OleDb操作Excel和数据库

在C#编程中&#xff0c;使用OleDb可以方便地实现对Excel文件和数据库的操作。本文探讨了在C#中使用OleDb技术操作Excel和数据库的策略。文章详述了OleDb的定义、配置环境的步骤&#xff0c;并通过实际代码示例演示了如何高效读写Excel文件和交互数据库。文中还评估了OleDb技术的…

【C++初阶】第十站:vector 中通用函数的模拟实现

目录 vector中的三个重要迭代器 默认成员函数 构造函数(无参构造) 构造函数(函数模板) 构造函数(带有默认参数) size_t int 拷贝构造函数 赋值重载 析构函数 迭代器相关函数 begin和end 容量和大小相关函数 size capacity resize 修改容器内容相关函数 reser…

Django项目之电商购物商城 -- 创建收货地址

Django项目之电商购物商城 – 创建收货地址 一. 在users中创建新的视图与路由用于创建收货地址 # 设置收货地址 class AddressView(View):def get(self , request):return render(request , "user_center_site.html")# 设置收货地址path(user_center_site/, views.…

web前端学习笔记9

9. HTML5新增元素及属性 9.1 HTML5新增结构元素 HTML5引入了几个新的结构元素,极大地改善了网页的组织和结构方式。以下是HTML5中的一些关键新结构元素: 标签说明<header>页面或页面中某一个区块的页眉,通常是一些引导和导航信息<nav>可以作为页面导航的链接组&…

【c++】线程池的原理及实现

&#x1f4bb;文章目录 &#x1f4c4;前言线程池的原理概念工作原理 线程池的实现线程池的基础结构任务队列的实现工作线程的实现 线程池的应用与拓展线程池的拓展 &#x1f4d3;总结 &#x1f4c4;前言 不知道各位是否有试过点进限时抽奖网站、抢票网站呢&#xff1f;你是否好…

第19讲:Ceph集群CrushMap规则定制与调优:从基础到高级应用

文章目录 1.CrushMap规则拓扑结构1.1.集群默认的CrushMap规则拓补图1.2.自定义的CrushMap规则拓补图 2.定制CrushMap规则的方法以及注意事项3.通过二进制文件编写一套CrushMap规则3.1.将系统默认的CrushMap规则导出3.2.根据需求编写CrushMap规则3.3.将编写好的规则导入到集群中…

Fastapi+docker+tortoise-orm+celery

因为项目是后期引入celery,所以导致构建docker的时候只有fastapi的项目&#xff0c;celery的重启比较麻烦 1.docker安装celery pip install celery安装celery的时候注意python版本与celery版本的适配&#xff0c;有些celery的版本不支持python的版本&#xff0c;具体的版本请看…

计算机毕业设计 | vue+springboot汽车销售管理系统(附源码)

1&#xff0c;项目介绍 本项目基于spring boot以及Vue开发&#xff0c;前端实现基于PanJiaChen所提供的开源后台项目vue-element-admin改造。 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能&#xff0c;提供经理和销售两种角色进行管理。 2&…

记录如何查询域名txt解析是否生效

要查询域名的TXT记录&#xff0c;可以使用nslookup命令。具体步骤如下&#xff1a;12 打开命令行终端。输入命令 nslookup -qttxt 域名&#xff0c;将"域名"替换为你要查询的实际域名。执行命令后&#xff0c;nslookup会返回域名的TXT记录值。 如何查询域名txt解析是…

面试题库-项目

1.项目主要实现了哪些功能&#xff1f; 本项目是专门为校园食堂窗口定制的一款软件产品&#xff0c;包括系统管理后台和客户端两部分。其中系统管理后台主要提供给食堂内部员工使用&#xff0c;可以对餐厅的菜品、套餐、订单、员工等进行管理维护。客户端主要提供给学生及校职…

什么是分库分表?代表性框架有哪些?

在互联网系统开发过程中&#xff0c;所谓的分库分表并不是一个新概念。或者说&#xff0c;对于很多开发人员而言&#xff0c;说起分库分表&#xff0c;大家都或多或少有所了解&#xff0c;也都知道数据量大了就需要进行分库分表。但是究竟如何实现分库分表呢&#xff1f; 要想…

创建Spring Boot项目及配置

目录 一、创建项目所需要的插件 1、安装插件 二、创建项目 三、创建项目所面临的常见问题。 1、IDEA不能识别 2、无效的发行版本 3、确认jar包是否下载成功 一、创建项目所需要的插件 1、安装插件 首先需要在IDEA插件里面搜索Spring&#xff0c;选择Spring Boot Helper…

什么是短信群发上行和下行

短信群发是一种广泛应用于商业和个人通信的技术&#xff0c;通过一次多条的方式&#xff0c;可以快速高效地传递信息。在实际的群发过程中&#xff0c;会涉及到上行和下行的概念。本文将详细介绍什么是短信群发上行和下行&#xff0c;并解释它们的应用。 什么是短信群发上行 群…

Dbeaver连接一段时间不操作后断开的问题

右键数据库连接点击编辑连接点击初始化将连接保持改成60s

BW4HANA混合建模 用ADSO的哪个视图?

写日志的ADSO除了1,2,3表之外。还会有6,7,8view。8view是上了BW4HANA2.0之后激活ADSO就会生成的。如果旧版本没有8&#xff0c;那就RSDG_ADSO_ACTIVATE激活一下。 如果勾了外部HANA视图&#xff0c;那就等于说还有一个HANA view。 首先咱知道ADSO是BW里面用来物理存储&#xf…

做一个属于自己的软件-pyside6快速上手教程

首先环境需要安装python3和pip&#xff0c;软件使用pycharm&#xff0c;安装也都很简单 首先需要安装pyside6,在终端执行&#xff1a; pip install pyside6 然后进入可视化编辑界面 pyside6-designer 进入后创建即可 可以从左侧点击鼠标拉组件进入到中间的工作区&#xff…