大家好,我是peachesTao,今天給大家推薦一個(gè)go的支持多語(yǔ)言的error自動(dòng)生成的插件,插件主頁(yè)可以訪問(wèn)下方鏈接。
在一個(gè)多語(yǔ)言國(guó)際化的項(xiàng)目中,后端接口返回給前端的錯(cuò)誤描述也需要國(guó)際化,我們來(lái)看一下后端給前端返回多語(yǔ)言錯(cuò)誤描述的實(shí)現(xiàn)方式有哪些。
常規(guī)實(shí)現(xiàn)
服務(wù)端將錯(cuò)誤碼和不同語(yǔ)言的錯(cuò)誤描述硬編碼在代碼中,通過(guò)前端從http head中傳過(guò)來(lái)的language來(lái)決定是返回中文還是英文。
1、定義Error結(jié)構(gòu)體
該結(jié)構(gòu)體實(shí)現(xiàn)標(biāo)準(zhǔn)庫(kù)的error接口,實(shí)現(xiàn)自定義error
type Error struct {
Code int
Msg string
}
func (e *Error) Error() string {
return fmt.Sprintf("%d,%s", e.Code, e.Msg)
}
2、定義錯(cuò)誤碼和錯(cuò)誤描述map
const (
Err_Code_Success = 0
Err_Code_UnKnown = -1
Err_Code_InValid_Phone = 10001
)
const (
Language_Chinese = 0 //中文
Language_Enligh = 1 //英文
)
//不同語(yǔ)言對(duì)應(yīng)的錯(cuò)誤描述
var errMap = map[int]map[int]string{
Language_Chinese: {
Err_Code_Success: "成功",
Err_Code_InValid_Phone: "手機(jī)號(hào)格式不正確",
Err_Code_UnKnown: "未知錯(cuò)誤",
},
Language_Enligh: {
Err_Code_Success: "success",
Err_Code_InValid_Phone: "invalid phone no",
Err_Code_UnKnown: "unknown err",
},
}
3、申明一個(gè)用戶注冊(cè)的api
根據(jù)客戶端傳過(guò)來(lái)的http header中的language的值決定返回中文還是英文的錯(cuò)誤描述
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 bool
if e, ok = err.(*Error); !ok {
e = &Error{Code: Err_Code_UnKnown}
}
}
msg := errMap[language][e.Code]
res := make(map[string]interface{})
res["code"] = e.Code
res["msg"] = msg
json, _ := 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
}
我們通過(guò)curl命令來(lái)看看效果
語(yǔ)言設(shè)置為中文時(shí):
curl -H "language:0" "http://127.0.0.1:8080/user/register?phone=187111111112"
{"code":10001,"msg":"手機(jī)號(hào)格式不正確"}
語(yǔ)言設(shè)置為英文時(shí):
curl -H "language:1" "http://127.0.0.1:8080/user/register?phone=187111111112"
{"code":10001,"msg":"invalid phone no"}
這種實(shí)現(xiàn)方式確實(shí)能滿足業(yè)務(wù)需求,但是有下面幾個(gè)缺點(diǎn):
- 當(dāng)要將手機(jī)號(hào)格式不正確的描述改時(shí)需要修改代碼
- 當(dāng)添加新的錯(cuò)誤時(shí)需要改動(dòng)多個(gè)地方代碼:添加新的錯(cuò)誤碼和在errMap中添加對(duì)應(yīng)語(yǔ)言的錯(cuò)誤描述,容易遺漏
- 當(dāng)添加新的語(yǔ)言時(shí)要向errMap添加所有錯(cuò)誤碼的新語(yǔ)言錯(cuò)誤描述,容易遺漏
一旦涉及到修改代碼就存在出現(xiàn)bug的風(fēng)險(xiǎn)。
有人會(huì)想到將錯(cuò)誤描述放在json文件中維護(hù),這種方案只是在修改錯(cuò)誤描述時(shí)比較便利,不需要改動(dòng)業(yè)務(wù)代碼,但在新增錯(cuò)誤和新語(yǔ)言時(shí)存在同樣的問(wèn)題。
還有一種方案是通過(guò)翻譯包將文本翻譯為目標(biāo)語(yǔ)言,但這種方法翻譯的結(jié)果不可控,且性能比較低。
有沒(méi)有一種更優(yōu)雅的方案,盡量減少修改代碼,且有足夠的靈活性?
下面我們來(lái)看看通過(guò)go-error-generator插件的方法來(lái)實(shí)現(xiàn)
更優(yōu)雅的實(shí)現(xiàn)
go-error-generator是一個(gè)通過(guò)protobuf文件的Enum對(duì)象自動(dòng)生成Error的插件,通過(guò)在擴(kuò)展的EnumValueOptions中定義多個(gè)option輕松實(shí)現(xiàn)error的多語(yǔ)言。
它包含如下功能:
- 根據(jù)Enum定義的errCode和msg自動(dòng)生成error;
- 支持定義多個(gè)EnumValueOption,實(shí)現(xiàn)多語(yǔ)言;
- 支持error合并功能;
- 支持自定義Error結(jié)構(gòu)體、error Code和Msg的名稱;
關(guān)于插件的原理和其他細(xì)節(jié)可以訪問(wèn)github主頁(yè)了解。
我們回到剛才那個(gè)需求,用插件的方式怎么實(shí)現(xiàn)錯(cuò)誤多語(yǔ)言
1、定義error模板
刪除代碼中的的Error結(jié)構(gòu)體,取代的是在protobuf中定義,新建一個(gè)protobuf文件,取名為error.proto,在這里自定義error結(jié)構(gòu)體和語(yǔ)言標(biāo)識(shí)。
其中:
- msg:默認(rèn)的語(yǔ)言標(biāo)識(shí),在錯(cuò)誤碼定義文件中沒(méi)有定義其他語(yǔ)言的錯(cuò)誤描述時(shí)就用它的錯(cuò)誤描述
- msg_english:英文標(biāo)識(shí),當(dāng)然你也可以取別的名字
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、定義錯(cuò)誤碼和錯(cuò)誤描述
新建一個(gè)protobuf文件,取名為account.proto
導(dǎo)入上面定義好的error.proto,自定義msg和msg_english對(duì)應(yīng)的錯(cuò)誤描述
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) = "未知錯(cuò)誤", (errors.msg_english) = "unknown err"]; // 賬號(hào)不存在
InValid_Phone = 10001 [(errors.msg) = "手機(jī)號(hào)格式不正確", (errors.msg_english) = "invalid phone no"]; // 登錄失效,請(qǐng)重新登錄
}
3、通過(guò)插件生成代碼
該插件需要安裝go和protobuf運(yùn)行環(huán)境
- go
- protoc
- protoc-gen-go
安裝好運(yùn)行環(huán)境后再安裝go-error-generator插件
go install github.com/classtorch/go-error-generator/protoc-gen-go-error-generator
安裝好后執(zhí)行下面腳本生成代碼
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
插件自動(dòng)生成的代碼如下,包含error對(duì)象和error map
var (
SUCCESS = &errors.Error{Code: 0, Msg: "成功"} //成功
UnKnown = &errors.Error{Code: -1, Msg: "未知錯(cuò)誤"} //未知錯(cuò)誤
InValid_Phone = &errors.Error{Code: 10001, Msg: "手機(jī)號(hào)格式不正確"} //手機(jī)號(hào)格式不正確
)
var (
Msg = map[int32]*errors.Error{
0: &errors.Error{Code: 0, Msg: "成功"},
-1: &errors.Error{Code: -1, Msg: "未知錯(cuò)誤"},
10001: &errors.Error{Code: 10001, Msg: "手機(jī)號(hào)格式不正確"},
}
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對(duì)象
使用生成的error對(duì)象和error map改寫(xiě)response和checkPhone方法
func response(w http.ResponseWriter, language int, err error) {
e := account.SUCCESS
var ok bool
if 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.Code
res["msg"] = e.Msg
json, _ := json.Marshal(res)
w.WriteHeader(200)
w.Write(json)
}
func checkPhone(phoneNo string) error {
if len(phoneNo) != 11 {
return account.InValid_Phone
}
return nil
}
完整的代碼可以訪問(wèn)go-error-generator-examples項(xiàng)目進(jìn)行了解
我們來(lái)看下這是實(shí)現(xiàn)方式的優(yōu)點(diǎn)
- 當(dāng)我們需要修改某個(gè)錯(cuò)誤描述時(shí)直接在account.proto文件中修改,無(wú)須修改代碼
- 當(dāng)需要增加新的錯(cuò)誤時(shí)直接在account.proto文件中定義,生成代碼后直接在業(yè)務(wù)代碼中引用即可
- 當(dāng)添加新的語(yǔ)言時(shí)只需要在error.proto中增加新的語(yǔ)言標(biāo)識(shí)即,然后在account.proto中引入即可
可以看出對(duì)于第一個(gè)和和第三個(gè)需求來(lái)說(shuō)只需要修改protobuf文件,重新生成代碼就可以,無(wú)須修改業(yè)務(wù)代碼。第二個(gè)需求也只是簡(jiǎn)單的引入新的錯(cuò)誤對(duì)象。
由于該插件是基于protobuf實(shí)現(xiàn)的,如果項(xiàng)目中沒(méi)有使用prorobuf技術(shù)棧的話會(huì)帶來(lái)一些引入成本。不過(guò)這點(diǎn)成本相對(duì)于頻繁修改業(yè)務(wù)代碼還是值得的。
相關(guān)鏈接
go-error-generator文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-618977.html
go-error-generator-examples文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-618977.html
到了這里,關(guān)于一個(gè)go的支持多語(yǔ)言的error自動(dòng)生成插件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!