一个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运算符能够将基…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...