一个go的支持多语言的error自动生成插件
大家好,我是peachesTao,今天给大家推荐一个go的支持多语言的error自动生成的插件,插件主页可以访问下方链接。
在一个多语言国际化的项目中,后端接口返回给前端的错误描述也需要国际化,我们来看一下后端给前端返回多语言错误描述的实现方式有哪些。
常规实现
服务端将错误码和不同语言的错误描述硬编码在代码中,通过前端从http head中传过来的language来决定是返回中文还是英文。
1、定义Error结构体
该结构体实现标准库的error接口,实现自定义error
type Error struct {Code intMsg string
}func (e *Error) Error() string {return fmt.Sprintf("%d,%s", e.Code, e.Msg)
}
2、定义错误码和错误描述map
const (Err_Code_Success = 0Err_Code_UnKnown = -1Err_Code_InValid_Phone = 10001
)const (Language_Chinese = 0 //中文Language_Enligh = 1 //英文
)//不同语言对应的错误描述
var errMap = map[int]map[int]string{Language_Chinese: {Err_Code_Success: "成功",Err_Code_InValid_Phone: "手机号格式不正确",Err_Code_UnKnown: "未知错误",},Language_Enligh: {Err_Code_Success: "success",Err_Code_InValid_Phone: "invalid phone no",Err_Code_UnKnown: "unknown err",},
}
3、申明一个用户注册的api
根据客户端传过来的http header中的language的值决定返回中文还是英文的错误描述
func main() {http.HandleFunc("/user/register", func(w http.ResponseWriter, r *http.Request) {languageStr := r.Header.Get("language")language, _ := strconv.Atoi(languageStr)values, _ := url.ParseQuery(r.URL.RawQuery)phone := values["phone"][0]err := checkPhone(phone)response(w, language, err)})http.ListenAndServe(":8080", nil)
}func response(w http.ResponseWriter, language int, err error) {e := &Error{Code: Err_Code_Success}if err != nil {var ok boolif e, ok = err.(*Error); !ok {e = &Error{Code: Err_Code_UnKnown}}}msg := errMap[language][e.Code]res := make(map[string]interface{})res["code"] = e.Coderes["msg"] = msgjson, _ := json.Marshal(res)w.WriteHeader(200)w.Write(json)
}func checkPhone(phoneNo string) error {if len(phoneNo) != 11 {return &Error{Code: Err_Code_InValid_Phone}}return nil
}
我们通过curl命令来看看效果
语言设置为中文时:
curl -H "language:0" "http://127.0.0.1:8080/user/register?phone=187111111112"
{"code":10001,"msg":"手机号格式不正确"}
语言设置为英文时:
curl -H "language:1" "http://127.0.0.1:8080/user/register?phone=187111111112"
{"code":10001,"msg":"invalid phone no"}
这种实现方式确实能满足业务需求,但是有下面几个缺点:
- 当要将手机号格式不正确的描述改时需要修改代码
- 当添加新的错误时需要改动多个地方代码:添加新的错误码和在errMap中添加对应语言的错误描述,容易遗漏
- 当添加新的语言时要向errMap添加所有错误码的新语言错误描述,容易遗漏
一旦涉及到修改代码就存在出现bug的风险,有没有一种更优雅的方案,尽量减少修改代码?
有人会想到将错误描述放在json文件中维护,这种方案只是在修改错误描述时比较便利,不需要改动业务代码,但在新增错误和新语言时存在同样的问题。
下面我们来看看通过go-error-generator插件的方法来实现
更优雅的实现
go-error-generator是一个通过protobuf文件的Enum对象自动生成Error的插件,通过在扩展的EnumValueOptions中定义多个option轻松实现error的多语言。
它包含如下功能:
- 根据Enum定义的errCode和msg自动生成error;
- 支持定义多个EnumValueOption,实现多语言;
- 支持error合并功能;
- 支持自定义Error结构体、error Code和Msg的名称;
关于插件的原理和其他细节可以访问github主页了解。
我们回到刚才那个需求,用插件的方式怎么实现错误多语言
1、定义error模板
删除代码中的的Error结构体,取代的是在protobuf中定义,新建一个protobuf文件,取名为error.proto,在这里自定义error结构体和语言标识。
其中:
- msg:默认的语言标识,在错误码定义文件中没有定义其他语言的错误描述时就用它的错误描述
- msg_english:英文标识,当然你也可以取别的名字
syntax = "proto3";
package errors;
option go_package = "github.com/classtorch/go-error-generator-examples/internal/errors";
import "google/protobuf/descriptor.proto";message Error {int32 code = 1;string msg = 2;
};
extend google.protobuf.EnumValueOptions {string msg = 1108;string msg_english = 1109;
}
2、定义错误码和错误描述
新建一个protobuf文件,取名为account.proto
导入上面定义好的error.proto,自定义msg和msg_english对应的错误描述
syntax = "proto3";
package uclass.service.account;
option go_package = "/golang/account";
import "errors/errors.proto";enum ErrorCode {SUCCESS = 0 [(errors.msg) = "成功", (errors.msg_english) = "success"]; // 成功UnKnown = -1 [(errors.msg) = "未知错误", (errors.msg_english) = "unknown err"]; // 账号不存在InValid_Phone = 10001 [(errors.msg) = "手机号格式不正确", (errors.msg_english) = "invalid phone no"]; // 登录失效,请重新登录
}
3、通过插件生成代码
该插件需要安装go和protobuf运行环境
- go
- protoc
- protoc-gen-go
安装好运行环境后再安装go-error-generator插件
go install github.com/classtorch/go-error-generator/protoc-gen-go-error-generator
安装好后执行下面脚本生成代码
protoc --go-error-generator_out=:. \--go-error-generator_opt descriptor_file=errors/errors.proto \--go-error-generator_opt merge_error=false \--go-error-generator_opt merge_error_path=golang/errors \--go_out=. -I . account.proto
插件自动生成的代码如下,包含error对象和error map
var (SUCCESS = &errors.Error{Code: 0, Msg: "成功"} //成功UnKnown = &errors.Error{Code: -1, Msg: "未知错误"} //未知错误InValid_Phone = &errors.Error{Code: 10001, Msg: "手机号格式不正确"} //手机号格式不正确
)var (Msg = map[int32]*errors.Error{0: &errors.Error{Code: 0, Msg: "成功"},-1: &errors.Error{Code: -1, Msg: "未知错误"},10001: &errors.Error{Code: 10001, Msg: "手机号格式不正确"},}Msg_English = map[int32]*errors.Error{0: &errors.Error{Code: 0, Msg: "success"},-1: &errors.Error{Code: -1, Msg: "unknown err"},10001: &errors.Error{Code: 10001, Msg: "invalid phone no"},}
)
4、使用生成的error对象
使用生成的error对象和error map改写response和checkPhone方法
func response(w http.ResponseWriter, language int, err error) {e := account.SUCCESSvar ok boolif err != nil {if e, ok = err.(*errors.Error); !ok {e = account.UnKnown}}if language == Language_Chinese {if e, ok = account.Msg[e.Code]; !ok {e = account.UnKnown}} else if language == Language_Enligh {if e, ok = account.Msg_English[e.Code]; !ok {e = account.UnKnown}}res := make(map[string]interface{})res["code"] = e.Coderes["msg"] = e.Msgjson, _ := json.Marshal(res)w.WriteHeader(200)w.Write(json)
}func checkPhone(phoneNo string) error {if len(phoneNo) != 11 {return account.InValid_Phone}return nil
}
完整的代码可以访问go-error-generator-examples项目进行了解
我们来看下这是实现方式的优点
- 当我们需要修改某个错误描述时直接在account.proto文件中修改,无须修改代码
- 当需要增加新的错误时直接在account.proto文件中定义,生成代码后直接在业务代码中引用即可
- 当添加新的语言时只需要在error.proto中增加新的语言标识即,然后在account.proto中引入即可
可以看出对于第一个和和第三个需求来说只需要修改protobuf文件,重新生成代码就可以,无须修改业务代码。第二个需求也只是简单的引入新的错误对象。
由于该插件是基于protobuf实现的,如果项目中没有使用prorobuf技术栈的话会带来一些引入成本。不过这点成本相对于频繁修改业务代码还是值得的。
相关链接
go-error-generator
go-error-generator-examples
相关文章:
一个go的支持多语言的error自动生成插件
大家好,我是peachesTao,今天给大家推荐一个go的支持多语言的error自动生成的插件,插件主页可以访问下方链接。 在一个多语言国际化的项目中,后端接口返回给前端的错误描述也需要国际化,我们来看一下后端给前端返回多语…...
wireshark抓包新手使用教程(超详细)
一、简介 Wireshark是一款非常流行的网络封包分析软件,可以截取各种网络数据包,并显示数据包详细信息。 为了安全考虑,wireshark只能查看封包,而不能修改封包的内容,或者发送封包。 wireshark能获取HTTP,也…...
平均列顺序对列排斥能的影响
( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点,AB训练集各由5张二值化的图片组成,让A有6个1,B有4个1,并且让这10个1的位置没有重合。比较迭代次数的顺序。 其中有9组数据 差值结构 A-B 迭代次数 构造平均列 …...
微信小程序-处理ios无法播放语音的问题
背景 框架:tarovue3 问题:今天搞小程序语音播放功能,开放工具播放正常,但是到ios手机上调试时无法播放,在网上找到个好办法 解决方案 核心代码 Taro.setInnerAudioOption({obeyMuteSwitch: false // 解决有一些IOS无…...
区块链 2.0笔记
区块链 2.0 以太坊概述 相对于比特币的几点改进 缩短出块时间至10多秒ghost共识机制mining puzzle BTC:计算密集型ETH:memory-hard(限制ASIC) proof of work->proof of stake对智能合约的支持 BTC:decentralized currencyETH:decentral…...
深入理解Vue响应式系统:数据绑定探索
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
web流程自动化详解
今天给大家带来Selenium的相关解释操作 一、Selenium Selenium是一个用于自动化Web浏览器操作的开源工具和框架。它提供了一组API(应用程序接口),可以让开发人员使用多种编程语言(如Java、Python、C#等)编写测试脚本&…...
什么是框架?为什么要学框架?
一、什么是框架 框架是整个或部分应用的可重用设计,是可定制化的应用骨架。它可以帮开发人员简化开发过程,提高开发效率。 项目里有一部分代码:和业务无关,而又不得不写的代码>框架 项目里剩下的部分代码:实现业务…...
什么是 Sass?
Sass 介绍 什么是 Sass? 官方标语 世界上最成熟、最稳定、最强大的专业级 CSS 扩展语言。怎么理解这句话呢?我们平时写的 CSS 代码可以理解为静态样式语言,而 Scss 就是动态样式语言,何为动态?就是让你写 CSS 跟写 …...
Kotlin~Memento备忘录模式
概念 备忘录模式是一种行为型设计模式,用于捕获和存储对象的内部状态,并在需要时将对象恢复到之前的状态。 备忘录模式允许在不暴露对象内部实现细节的情况下,对对象进行状态的保存和恢复。 角色介绍 Originator:原发器&#x…...
单链表的多语言表达:C++、Java、Python、Go、Rust
单链表 是一种链式数据结构,由一个头节点和一些指向下一个节点的指针组成。每个节点包含一个数据元素和指向下一个节点的指针。头节点没有数据,只用于表示链表的开始位置。 单链表的主要操作包括: 添加元素:在链表的头部添加新…...
微信小程序 background-image直接设置本地图片路径,编辑器正常显示,真机运行不显示解决方法
项目场景 微信小程序,设置background-image直接设置本地图片路径。 问题描述 编辑器正常显示,真机运行不显示 原因分析 background-image只能用网络url或者base64图片编码。 解决方案 1、将本地图片转为网络url后设置到background-image上 例如&…...
SQLite Studio 连接 SQLite数据库
1、在SQLite中创建数据库和表 1.1、按WINR,打开控制台,然后把指引到我们的SQLite的安装路径,输入D:,切换到D盘,cd 地址,切换到具体文件夹,输入“sqlite3”,启动服务 1.2、创建数据库…...
【业务功能篇58】Springboot + Spring Security 权限管理 【中篇】
4.2.3 认证 4.2.3.1 什么是认证(Authentication) 通俗地讲就是验证当前用户的身份,证明“你是你自己”(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系统里录入的指纹相匹配时&…...
Docker挂载目录失败问题解决
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...
css中隐藏页面中某一个元素有什么方法?
我们可以使用css的z-index属性,将元素的-index去给它设置一个负值,使它隐藏在其他元素的后面。使用css样式进行隐藏我们可以使用display这个属性。(1)使用display:none完全进行隐藏元素,并且不占据空间也不会影响页面布…...
Unity 多语言问题C#篇
DateTime.ToString()不同语言环境问题 问题描述:PlayerPrefs.SetString("timeKey", DateTime.Now.ToString());切换系统语言后DateTime.Parse(PlayerPrefs.GetString("timeKey"));报错FormatException: String was not recognized as a valid D…...
深度学习和神经网络
人工神经网络分为两个阶段: 1 :接收来自其他n个神经元传递过来的信号,这些输入信号通过与相应的权重进行 加权求和传递给下个阶段。(预激活阶段) 2:把预激活的加权结果传递给激活函数 sum :加权 f:激活…...
在CSDN学Golang云原生(Kubernetes Volume)
一,Volume 与 configMap Kubernetes 中的 Volume 和 ConfigMap 都是 Kubernetes 中常用的资源对象。它们可以为容器提供持久化存储和配置文件等。 Volume 可以将容器内部的文件系统挂载到宿主机上,也可以将多个容器间共享一个 Volume,并且 …...
第十五章 友元 异常和其他
RTTI RTTI是什么 RTTI是运行阶段类型识别,通过运行时类型识别,程序能够使用基类的指针或者引用来检查这些指针或者引用所指向的对象的实际派生类型。 RTTI的三个元素 dynamic_cast运算符 dynamic_cast概念: dynamic_cast运算符能够将基…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...
Appium下载安装配置保姆教程(图文详解)
目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...
深入理解 React 样式方案
React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…...
