完善错误码的用法
This commit is contained in:
+125
-24
@@ -8,11 +8,7 @@ import (
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
func init() {
|
||||
langx.InitLangx(
|
||||
langx.SetDefaultCode(0),
|
||||
langx.SetDefaultLanguage("zh"),
|
||||
@@ -21,39 +17,144 @@ func TestError(t *testing.T) {
|
||||
"login_success": 200,
|
||||
"error": 400,
|
||||
"username": 201,
|
||||
"password": 202,
|
||||
})
|
||||
langx.RegisterTrans("zh", map[string]string{
|
||||
"login_success": "成功",
|
||||
"login_success": "登录成功",
|
||||
"error": "错误",
|
||||
"username": "你好 #name#", // 有占位符
|
||||
"username": "你好 #name#",
|
||||
"password": "密码错误: #reason#",
|
||||
})
|
||||
langx.RegisterTrans("en", map[string]string{
|
||||
"login_success": "success",
|
||||
"error": "error",
|
||||
"username": "Hello #name#", // 有占位符
|
||||
"login_success": "Login success",
|
||||
"error": "Error",
|
||||
"username": "Hello #name#",
|
||||
"password": "Password error: #reason#",
|
||||
})
|
||||
}
|
||||
|
||||
err = errorx.NewError(ctx, "error")
|
||||
// fmt.Printf("err: %v\n", err)
|
||||
t.Log(err.Error())
|
||||
val, ok := err.(errorx.ErrorInterface)
|
||||
if ok {
|
||||
t.Log(val.GetCode())
|
||||
func TestNewError_Basic(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := errorx.NewError(ctx, "error")
|
||||
t.Log(err.Error()) // 输出:错误
|
||||
|
||||
if code := err.Code(); code != 400 {
|
||||
t.Fatalf("expected code 400, got %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
err = errorx.NewErrorf(ctx, "username", map[string]string{
|
||||
func TestNewErrorf_Placeholder(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := errorx.NewErrorf(ctx, "username", map[string]string{
|
||||
"name": "yuninks",
|
||||
})
|
||||
t.Log(err.Error())
|
||||
val, ok = err.(errorx.ErrorInterface)
|
||||
if ok {
|
||||
t.Log(val.GetCode())
|
||||
t.Log(err.Error()) // 输出:你好 yuninks
|
||||
|
||||
// 设置输出语言
|
||||
val.SetLang("en")
|
||||
// 不可变:WithLang 返回新实例,原实例不变
|
||||
errEn := err.WithLang("en")
|
||||
t.Log(err.Error()) // 仍然是中文
|
||||
t.Log(errEn.Error()) // 输出:Hello yuninks
|
||||
}
|
||||
|
||||
func TestErrorCode_Predefined(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 使用预定义 ErrorCode 创建运行时错误
|
||||
err := errorx.Error.New(ctx)
|
||||
t.Log(err.Error()) // 输出:操作失败
|
||||
if code := err.Code(); code != 400 {
|
||||
t.Fatalf("expected code 400, got %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorCode_Newf(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := errorx.ErrWithMsg.Newf(ctx, map[string]string{"msg": "参数校验失败"})
|
||||
t.Log(err.Error()) // 输出:操作失败: 参数校验失败
|
||||
}
|
||||
|
||||
func TestErrorCode_Msg(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
msg := errorx.Success.Msg(ctx)
|
||||
t.Log(msg) // 输出:操作成功
|
||||
if msg != "操作成功" {
|
||||
t.Fatalf("expected '操作成功', got '%s'", msg)
|
||||
}
|
||||
|
||||
t.Log(val.Error())
|
||||
// 验证中文环境
|
||||
if code := errorx.Success.Code(); code != 200 {
|
||||
t.Fatalf("expected code 200, got %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelper_As(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := errorx.NewError(ctx, "username")
|
||||
e, ok := errorx.As(err)
|
||||
if !ok {
|
||||
t.Fatal("expected errorx.As to succeed")
|
||||
}
|
||||
t.Log(e.Key()) // username
|
||||
t.Log(e.Code()) // 201
|
||||
}
|
||||
|
||||
func TestHelper_CodeFrom(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := errorx.NewError(ctx, "error")
|
||||
if code := errorx.CodeFrom(err); code != 400 {
|
||||
t.Fatalf("expected 400, got %d", code)
|
||||
}
|
||||
|
||||
// 非 langError 返回 -1
|
||||
if code := errorx.CodeFrom(context.Canceled); code != -1 {
|
||||
t.Fatalf("expected -1 for non-langError, got %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelper_KeyFrom(t *testing.T) {
|
||||
err := errorx.NewError(context.Background(), "login_success")
|
||||
if key := errorx.KeyFrom(err); key != "login_success" {
|
||||
t.Fatalf("expected 'login_success', got '%s'", key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImmutability_CreateChain(t *testing.T) {
|
||||
// 演示不可变链式调用
|
||||
ctx := context.Background()
|
||||
|
||||
base := errorx.Error.New(ctx)
|
||||
zh := base.WithKV("msg", "中文错误")
|
||||
en := zh.WithLang("en").WithKV("msg", "English error")
|
||||
|
||||
t.Log(base.Error()) // 操作失败
|
||||
t.Log(zh.Error()) // 操作失败: 中文错误
|
||||
t.Log(en.Error()) // 操作失败: English error
|
||||
|
||||
// 各自独立,互不影响
|
||||
zh2 := zh.WithKV("extra", "additional")
|
||||
t.Log(zh.Error()) // 仍然只有 msg
|
||||
t.Log(zh2.Error()) // msg + extra 都有了
|
||||
}
|
||||
|
||||
func TestConcurrency_Safety(t *testing.T) {
|
||||
// 多个 goroutine 共享同一个 ErrorCode,各自派生独立实例
|
||||
ctx := context.Background()
|
||||
done := make(chan bool, 10)
|
||||
|
||||
for range 10 {
|
||||
go func() {
|
||||
_ = errorx.Error.New(ctx).WithKV("msg", "go routine")
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
for range 10 {
|
||||
<-done
|
||||
}
|
||||
// 无竞态即为通过
|
||||
}
|
||||
|
||||
@@ -1,62 +1,58 @@
|
||||
package errorx
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
// 定义错误常量
|
||||
|
||||
type ErrorLanguage struct {
|
||||
ErrorInterface
|
||||
}
|
||||
|
||||
// 生成错误常量
|
||||
func NewLanguage(uniKey string, code int, defaultValue string) ErrorLanguage {
|
||||
langx.AppendCode(map[string]int{uniKey: code})
|
||||
langx.AppendTrans(langx.GetDefaultLang(), map[string]string{uniKey: defaultValue})
|
||||
|
||||
l := NewStruct(context.Background(), uniKey, nil)
|
||||
|
||||
return ErrorLanguage{l}
|
||||
}
|
||||
|
||||
// Key生成错误信息
|
||||
func (l ErrorLanguage) Err() error {
|
||||
return l
|
||||
}
|
||||
|
||||
// Key生成错误信息
|
||||
func (l ErrorLanguage) Errf(format map[string]string) error {
|
||||
newLang := l.Copy()
|
||||
newLang.SetFormat(format)
|
||||
return newLang
|
||||
}
|
||||
|
||||
func (l ErrorLanguage) ErrfKV(key, value string) error {
|
||||
newLang := l.Copy()
|
||||
newLang.SetFormatKV(key, value)
|
||||
return newLang
|
||||
}
|
||||
|
||||
// 获取翻译后的错误信息
|
||||
func (l ErrorLanguage) Msg(ctx context.Context) string {
|
||||
newLang := l.Copy()
|
||||
newLang.SetCtx(ctx)
|
||||
return newLang.Error()
|
||||
}
|
||||
|
||||
// 获取翻译后的错误信息
|
||||
func (l ErrorLanguage) Msgf(ctx context.Context, format map[string]string) string {
|
||||
newLang := l.Copy()
|
||||
newLang.SetCtx(ctx)
|
||||
newLang.SetFormat(format)
|
||||
return newLang.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
Success ErrorLanguage = NewLanguage("success", 200, "操作成功")
|
||||
Error ErrorLanguage = NewLanguage("error", 400, "操作失败")
|
||||
ErrWithMsg ErrorLanguage = NewLanguage("error_with_msg", 400, "操作失败: #msg#")
|
||||
)
|
||||
package errorx
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
// ErrorCode 表示一个预定义的错误码,是纯值类型,无状态、并发安全。
|
||||
// 声明为 var 后不会被意外修改。
|
||||
type ErrorCode struct {
|
||||
key string
|
||||
code int
|
||||
}
|
||||
|
||||
// NewCode 创建一个错误码,并自动注册到 langx 全局表中。
|
||||
// - key: 唯一标识符
|
||||
// - code: 业务错误码
|
||||
// - defaultMsg: 默认语言下的消息模板
|
||||
func NewCode(key string, code int, defaultMsg string) ErrorCode {
|
||||
langx.AppendCode(map[string]int{key: code})
|
||||
langx.AppendTrans(langx.GetDefaultLang(), map[string]string{key: defaultMsg})
|
||||
return ErrorCode{key: key, code: code}
|
||||
}
|
||||
|
||||
// Key 返回唯一标识符。
|
||||
func (ec ErrorCode) Key() string { return ec.key }
|
||||
|
||||
// Code 返回业务错误码。
|
||||
func (ec ErrorCode) Code() int { return ec.code }
|
||||
|
||||
// New 创建一个携带上下文的运行时错误。
|
||||
func (ec ErrorCode) New(ctx context.Context) *langError {
|
||||
return NewError(ctx, ec.key)
|
||||
}
|
||||
|
||||
// Newf 创建一个携带占位符键值对的运行时错误。
|
||||
func (ec ErrorCode) Newf(ctx context.Context, kv map[string]string) *langError {
|
||||
return NewErrorf(ctx, ec.key, kv)
|
||||
}
|
||||
|
||||
// Msg 直接获取当前上下文语言下的翻译消息。
|
||||
func (ec ErrorCode) Msg(ctx context.Context) string {
|
||||
return langx.GetFormat(langx.GetCtxLang(ctx), ec.key, nil)
|
||||
}
|
||||
|
||||
// Msgf 直接获取带占位符替换的翻译消息。
|
||||
func (ec ErrorCode) Msgf(ctx context.Context, kv map[string]string) string {
|
||||
return langx.GetFormat(langx.GetCtxLang(ctx), ec.key, kv)
|
||||
}
|
||||
|
||||
// ---- 预定义错误码 ----------------------------------------------------
|
||||
|
||||
var (
|
||||
Success = NewCode("success", 200, "操作成功")
|
||||
Error = NewCode("error", 400, "操作失败")
|
||||
ErrWithMsg = NewCode("error_with_msg", 400, "操作失败: #msg#")
|
||||
)
|
||||
|
||||
+226
-37
@@ -2,48 +2,237 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/yuninks/errorx"
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
err := ErrorWithMsg.Error()
|
||||
|
||||
// 输出:错误
|
||||
println(err.Error())
|
||||
|
||||
err = ErrorWithMsg.Errorf(map[string]string{"msg": "错误"})
|
||||
// 输出:错误
|
||||
println(err.Error())
|
||||
|
||||
}
|
||||
|
||||
type Language string
|
||||
|
||||
// 添加key+默认语言
|
||||
func newLanguage(uniKey string, code int, defaultValue string) Language {
|
||||
langx.AppendCode(map[string]int{uniKey: code})
|
||||
langx.AppendTrans("zh_hans", map[string]string{uniKey: defaultValue})
|
||||
return Language(uniKey)
|
||||
}
|
||||
|
||||
func (l Language) String() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
func (l Language) Error() error {
|
||||
return errorx.NewError(context.Background(), l.String())
|
||||
}
|
||||
|
||||
func (l Language) Errorf(format map[string]string) error {
|
||||
return errorx.NewErrorf(context.Background(), l.String(), format)
|
||||
}
|
||||
// ---- 定义业务错误码 ---------------------------------------------------
|
||||
|
||||
// 预定义错误码,集中声明,类型安全。
|
||||
var (
|
||||
Success Language = newLanguage("success", 200, "成功")
|
||||
|
||||
Error Language = newLanguage("error", 400, "错误")
|
||||
ErrorWithMsg Language = newLanguage("error_with_msg", 400, "错误 #msg#")
|
||||
ErrLoginFailed = errorx.NewCode("login_failed", 401, "登录失败")
|
||||
ErrTokenExpired = errorx.NewCode("token_expired", 401, "令牌已过期,请重新登录")
|
||||
ErrUserNotFound = errorx.NewCode("user_not_found", 404, "用户 #name# 不存在")
|
||||
ErrParamInvalid = errorx.NewCode("param_invalid", 422, "参数校验失败: #field#")
|
||||
ErrInternal = errorx.NewCode("internal", 500, "服务器内部错误")
|
||||
ErrRateLimit = errorx.NewCode("rate_limit", 429, "请求过于频繁,请 #seconds# 秒后重试")
|
||||
)
|
||||
|
||||
// ---- 补充语言包(可通过 embed/文件 批量导入) --------------------------
|
||||
|
||||
func init() {
|
||||
langx.RegisterTrans("en", map[string]string{
|
||||
"login_failed": "Login failed",
|
||||
"token_expired": "Token expired, please re-login",
|
||||
"user_not_found": "User #name# not found",
|
||||
"param_invalid": "Parameter validation failed: #field#",
|
||||
"internal": "Internal server error",
|
||||
"rate_limit": "Too many requests, retry in #seconds# seconds",
|
||||
})
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 1:基础用法 — 创建错误并获取多语言消息
|
||||
// ======================================================================
|
||||
func exampleBasic() {
|
||||
fmt.Println("=== 示例1:基础用法 ===")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 创建带上下文的运行时错误
|
||||
err := ErrLoginFailed.New(ctx)
|
||||
fmt.Println("中文:", err.Error())
|
||||
|
||||
// 切换到英文
|
||||
errEn := err.WithLang("en")
|
||||
fmt.Println("英文:", errEn.Error())
|
||||
|
||||
// 原实例不受影响
|
||||
fmt.Println("原实例仍是中文:", err.Error())
|
||||
|
||||
// 直接从 ErrorCode 获取翻译消息(不创建 error)
|
||||
fmt.Println("直接翻译:", ErrLoginFailed.Msg(ctx))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 2:占位符替换
|
||||
// ======================================================================
|
||||
func examplePlaceholder() {
|
||||
fmt.Println("=== 示例2:占位符替换 ===")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 使用 Newf 创建带占位符的错误
|
||||
err := ErrUserNotFound.Newf(ctx, map[string]string{"name": "admin"})
|
||||
fmt.Println("中文:", err.Error())
|
||||
|
||||
// 链式派生:追加更多占位符
|
||||
err2 := err.WithKV("extra", "value")
|
||||
fmt.Println("追加占位符:", err2.Error())
|
||||
// err 不受影响
|
||||
fmt.Println("原实例不变:", err.Error())
|
||||
|
||||
// 不可变链式:中文 → 英文
|
||||
errEn := ErrRateLimit.
|
||||
Newf(ctx, map[string]string{"seconds": "30"}).
|
||||
WithLang("en")
|
||||
fmt.Println("英文:", errEn.Error())
|
||||
|
||||
// 批量替换 WithMap
|
||||
err3 := ErrParamInvalid.New(ctx).WithMap(map[string]string{
|
||||
"field": "email",
|
||||
})
|
||||
fmt.Println("WithMap:", err3.Error())
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 3:辅助函数 — 从 error 中提取信息
|
||||
// ======================================================================
|
||||
func exampleHelpers() {
|
||||
fmt.Println("=== 示例3:辅助函数 ===")
|
||||
|
||||
ctx := context.Background()
|
||||
err := ErrParamInvalid.Newf(ctx, map[string]string{"field": "age"})
|
||||
|
||||
// 类型安全提取
|
||||
e, ok := errorx.As(err)
|
||||
if ok {
|
||||
fmt.Println("Key:", e.Key())
|
||||
fmt.Println("Code:", e.Code())
|
||||
fmt.Println("Format:", e.Format())
|
||||
}
|
||||
|
||||
// 快捷函数
|
||||
fmt.Println("CodeFrom:", errorx.CodeFrom(err))
|
||||
fmt.Println("KeyFrom:", errorx.KeyFrom(err))
|
||||
|
||||
// 对非 langError 的容错处理
|
||||
fmt.Println("CodeFrom(context.Canceled):", errorx.CodeFrom(context.Canceled)) // -1
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 4:HTTP API 响应 — 模拟真实场景
|
||||
// ======================================================================
|
||||
func exampleHTTP() {
|
||||
fmt.Println("=== 示例4:HTTP API 响应 ===")
|
||||
|
||||
// 模拟从请求中获取语言
|
||||
zhCtx := langx.SetCtxLang(context.Background(), "zh")
|
||||
enCtx := langx.SetCtxLang(context.Background(), "en")
|
||||
|
||||
// 处理请求
|
||||
handleLogin := func(ctx context.Context) (int, string) {
|
||||
// 模拟登录失败
|
||||
err := ErrLoginFailed.New(ctx)
|
||||
return err.Code(), err.Error()
|
||||
}
|
||||
|
||||
codeZH, msgZH := handleLogin(zhCtx)
|
||||
codeEN, msgEN := handleLogin(enCtx)
|
||||
|
||||
fmt.Printf("中文响应: code=%d, msg=%s\n", codeZH, msgZH)
|
||||
fmt.Printf("英文响应: code=%d, msg=%s\n", codeEN, msgEN)
|
||||
|
||||
// 模拟参数校验失败
|
||||
handleParam := func(ctx context.Context, field string) (int, string) {
|
||||
err := ErrParamInvalid.Newf(ctx, map[string]string{"field": field})
|
||||
return errorx.CodeFrom(err), err.Error()
|
||||
}
|
||||
fmt.Println()
|
||||
code, msg := handleParam(zhCtx, "username")
|
||||
fmt.Printf("参数校验(中文): code=%d, msg=%s\n", code, msg)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 5:中间件 — 统一错误处理
|
||||
// ======================================================================
|
||||
func exampleMiddleware() {
|
||||
fmt.Println("=== 示例5:中间件模式 ===")
|
||||
|
||||
// writeJSON 模拟写入 HTTP JSON 响应
|
||||
writeJSON := func(err error) {
|
||||
code := errorx.CodeFrom(err)
|
||||
msg := err.Error()
|
||||
if code == -1 {
|
||||
code = 500
|
||||
msg = "未知错误"
|
||||
}
|
||||
fmt.Printf("HTTP 响应: {\"code\":%d, \"msg\":\"%s\"}\n", code, msg)
|
||||
}
|
||||
|
||||
// 场景1:业务错误
|
||||
ctx := context.Background()
|
||||
writeJSON(ErrTokenExpired.New(ctx))
|
||||
|
||||
// 场景2:带占位符的业务错误
|
||||
writeJSON(ErrUserNotFound.Newf(ctx, map[string]string{"name": "test_user"}))
|
||||
|
||||
// 场景3:非 errorx 错误也能兜底
|
||||
writeJSON(http.ErrServerClosed)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 6:error 链与 errors.Is / errors.As 兼容
|
||||
// ======================================================================
|
||||
func exampleErrorChain() {
|
||||
fmt.Println("=== 示例6:与标准 errors 包兼容 ===")
|
||||
|
||||
ctx := context.Background()
|
||||
baseErr := ErrInternal.New(ctx)
|
||||
wrappedErr := fmt.Errorf("处理订单失败: %w", baseErr)
|
||||
|
||||
// errors.Is / errors.As 仍然可用
|
||||
e, ok := errorx.As(wrappedErr)
|
||||
if ok {
|
||||
fmt.Println("从包装后的 error 中提取成功:")
|
||||
fmt.Println(" Key:", e.Key())
|
||||
fmt.Println(" Code:", e.Code())
|
||||
fmt.Println(" 消息:", e.Error())
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 示例 7:动态创建错误码(未预定义的场景)
|
||||
// ======================================================================
|
||||
func exampleDynamic() {
|
||||
fmt.Println("=== 示例7:动态创建错误码 ===")
|
||||
|
||||
// 运行时动态注册错误码
|
||||
dbError := errorx.NewCode("db_connection_failed", 503, "数据库连接失败: #detail#")
|
||||
|
||||
ctx := context.Background()
|
||||
err := dbError.Newf(ctx, map[string]string{"detail": "timeout after 30s"})
|
||||
|
||||
fmt.Println("Key:", errorx.KeyFrom(err))
|
||||
fmt.Println("Code:", errorx.CodeFrom(err))
|
||||
fmt.Println("消息:", err.Error())
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ---- main ------------------------------------------------------------
|
||||
|
||||
func main() {
|
||||
// 初始化 langx
|
||||
langx.InitLangx(
|
||||
langx.SetDefaultCode(0),
|
||||
langx.SetDefaultLanguage("zh"),
|
||||
)
|
||||
|
||||
exampleBasic()
|
||||
examplePlaceholder()
|
||||
exampleHelpers()
|
||||
exampleHTTP()
|
||||
exampleMiddleware()
|
||||
exampleErrorChain()
|
||||
exampleDynamic()
|
||||
}
|
||||
|
||||
+59
-23
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/yuninks/errorx"
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
@@ -11,44 +13,78 @@ import (
|
||||
var assetsFs embed.FS
|
||||
|
||||
func main() {
|
||||
langx.InitLangx(
|
||||
langx.SetDefaultCode(0),
|
||||
langx.SetDefaultLanguage("zh"),
|
||||
)
|
||||
|
||||
fmt.Println("=== 方式1:逐条追加(适合少量错误码) ===")
|
||||
regByAppend()
|
||||
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("=== 方式2:embed 导入 JSON 文件(适合中型项目) ===")
|
||||
regByEmbed()
|
||||
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("=== 方式3:目录文件导入(适合部署时外部管理) ===")
|
||||
regByDir()
|
||||
}
|
||||
|
||||
// 导入语言包 基于Append
|
||||
// 方式1:逐条追加 — 适合少量错误码或动态注册场景。
|
||||
func regByAppend() {
|
||||
langx.AppendCode(map[string]int{
|
||||
"success": 200,
|
||||
})
|
||||
langx.AppendTrans("zh-CN", map[string]string{
|
||||
"success": "成功",
|
||||
})
|
||||
// 先用 langx 注册语言包
|
||||
langx.AppendCode(map[string]int{"success": 200})
|
||||
langx.AppendTrans("zh-CN", map[string]string{"success": "成功!"})
|
||||
|
||||
code, msg := langx.GetTransFormat("zh-CN", "success", map[string]string{})
|
||||
fmt.Println(code, msg)
|
||||
// 然后用 errorx.NewCode 创建错误码(会自动调用 langx.Append*)
|
||||
loginErr := errorx.NewCode("login_err", 401, "用户名或密码错误")
|
||||
langx.AppendTrans("en", map[string]string{"login_err": "Invalid username or password"})
|
||||
|
||||
ctx := context.Background()
|
||||
err := loginErr.New(ctx)
|
||||
|
||||
fmt.Printf(" code=%d, msg=%s\n", err.Code(), err.Error())
|
||||
}
|
||||
|
||||
// 导入语言包 基于Embed
|
||||
// 方式2:embed 导入 — JSON 文件编译进二进制,适合中型项目。
|
||||
func regByEmbed() {
|
||||
err := langx.RegisterEmbed(assetsFs)
|
||||
fmt.Println(err)
|
||||
if err := langx.RegisterEmbed(assetsFs); err != nil {
|
||||
fmt.Println("embed 导入失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
code, msg := langx.GetTransFormat("zh", "success", map[string]string{})
|
||||
fmt.Println(code, msg)
|
||||
code, msg = langx.GetTransFormat("en", "error", map[string]string{
|
||||
"msg": "这是失败的原因",
|
||||
// JSON 中已声明的 error/success,直接用 key 创建 errorx 实例
|
||||
ctx := context.Background()
|
||||
|
||||
err1 := errorx.NewError(ctx, "error")
|
||||
fmt.Printf(" error: code=%d, msg(zh)=%s\n", err1.Code(), err1.Error())
|
||||
|
||||
err2 := errorx.NewError(ctx, "success")
|
||||
fmt.Printf(" success: code=%d, msg(zh)=%s\n", err2.Code(), err2.Error())
|
||||
|
||||
// 带占位符
|
||||
err3 := errorx.NewErrorf(ctx, "error", map[string]string{
|
||||
"msg": "数据库连接超时",
|
||||
})
|
||||
fmt.Println(code, msg)
|
||||
fmt.Printf(" error+format(zh): %s\n", err3.Error())
|
||||
|
||||
// 切换到英文
|
||||
err4 := errorx.NewErrorf(ctx, "error", map[string]string{
|
||||
"msg": "database connection timeout",
|
||||
}).WithLang("en")
|
||||
fmt.Printf(" error+format(en): %s\n", err4.Error())
|
||||
}
|
||||
|
||||
// 导入语言包 基于文件
|
||||
// 方式3:目录文件导入 — 语言文件放在外部目录,支持运行时热加载。
|
||||
func regByDir() {
|
||||
langx.RegisterDir("./lang")
|
||||
|
||||
code, msg := langx.GetTransFormat("zh", "success", map[string]string{})
|
||||
fmt.Println(code, msg)
|
||||
code, msg = langx.GetTransFormat("en", "error", map[string]string{
|
||||
"msg": "这是失败的原因",
|
||||
ctx := context.Background()
|
||||
|
||||
err := errorx.NewErrorf(ctx, "error", map[string]string{
|
||||
"msg": "磁盘空间不足",
|
||||
})
|
||||
fmt.Println(code, msg)
|
||||
fmt.Printf(" code=%d, msg=%s\n", errorx.CodeFrom(err), err.Error())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,47 @@
|
||||
# 导入资源
|
||||
# 语言包导入
|
||||
|
||||
# 通过embed导入
|
||||
errorx 支持三种方式导入多语言资源。
|
||||
|
||||
# 通过文件导入
|
||||
## 方式1:逐条追加(Append)
|
||||
|
||||
# 追加导入
|
||||
适合错误码较少、运行时动态注册的场景。
|
||||
|
||||
```go
|
||||
langx.AppendCode(map[string]int{"success": 200})
|
||||
langx.AppendTrans("zh", map[string]string{"success": "成功"})
|
||||
```
|
||||
|
||||
## 方式2:Embed 导入
|
||||
|
||||
适合中型项目,JSON 文件编译进可执行文件。
|
||||
|
||||
目录结构:
|
||||
|
||||
```
|
||||
lang/
|
||||
code.json → {"success": 200, "error": 400}
|
||||
zh.json → {"success": "成功", "error": "失败 #msg#"}
|
||||
en.json → {"success": "Success", "error": "Error #msg#"}
|
||||
```
|
||||
|
||||
Go 代码:
|
||||
|
||||
```go
|
||||
//go:embed lang
|
||||
var assetsFs embed.FS
|
||||
|
||||
langx.RegisterEmbed(assetsFs)
|
||||
```
|
||||
|
||||
## 方式3:目录导入
|
||||
|
||||
适合部署时外部管理语言包,支持不重启更新。
|
||||
|
||||
```go
|
||||
langx.RegisterDir("./lang")
|
||||
```
|
||||
|
||||
## JSON 格式
|
||||
|
||||
- `code.json`:错误码映射 `key → int`
|
||||
- `{lang}.json`:翻译映射 `key → 翻译文本`,支持 `#placeholder#` 占位符
|
||||
|
||||
+108
-61
@@ -2,95 +2,142 @@ package errorx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
// Key生成错误信息
|
||||
type ErrorInterface interface {
|
||||
Copy() ErrorInterface // 复制一个新的错误信息
|
||||
Error() string // 实现error接口&获取翻译后的错误信息
|
||||
GetCode() int // 获取翻译后的Code
|
||||
GetKey() string // 获取原Key值
|
||||
GetFormat() map[string]string // 获取附加数据
|
||||
SetFormat(format map[string]string) // 设置附加数据
|
||||
SetCtx(ctxHttp context.Context) // 设置上下文
|
||||
SetLang(lang string) // 设置语言
|
||||
SetFormatKV(key, value string) // 设置附加数据键值对
|
||||
// ErrorInfo 是对外暴露的错误信息接口,语义精简、不可变。
|
||||
type ErrorInfo interface {
|
||||
error
|
||||
Code() int
|
||||
Key() string
|
||||
Format() map[string]string
|
||||
}
|
||||
|
||||
type defaultError struct {
|
||||
// langError 是不可变的运行时错误实例,并发安全。
|
||||
// 通过 With* 方法派生新实例,永远不会修改自身。
|
||||
type langError struct {
|
||||
ctx context.Context
|
||||
key string
|
||||
format map[string]string
|
||||
}
|
||||
|
||||
func (l *defaultError) Copy() ErrorInterface {
|
||||
return &defaultError{
|
||||
ctx: l.ctx,
|
||||
key: l.key,
|
||||
format: l.format,
|
||||
// ---- 实现 error 接口 -----------------------------------------------
|
||||
|
||||
func (e *langError) Error() string {
|
||||
return langx.GetFormat(langx.GetCtxLang(e.ctx), e.key, e.format)
|
||||
}
|
||||
|
||||
// ---- 实现 ErrorInfo 接口 --------------------------------------------
|
||||
|
||||
func (e *langError) Code() int { return langx.GetCode(e.key) }
|
||||
|
||||
func (e *langError) Key() string { return e.key }
|
||||
|
||||
func (e *langError) Format() map[string]string {
|
||||
if len(e.format) == 0 {
|
||||
return nil
|
||||
}
|
||||
return e.copyFormat()
|
||||
}
|
||||
|
||||
func (e *defaultError) SetFormat(format map[string]string) {
|
||||
e.format = format
|
||||
// ---- 不可变派生方法(返回新实例,不修改原值)------------------------
|
||||
|
||||
// WithCtx 派生一个携带新上下文的错误实例。
|
||||
func (e *langError) WithCtx(ctx context.Context) *langError {
|
||||
return &langError{ctx: ctx, key: e.key, format: e.copyFormat()}
|
||||
}
|
||||
|
||||
func (l *defaultError) SetFormatKV(key, value string) {
|
||||
if l.format == nil {
|
||||
l.format = make(map[string]string)
|
||||
// WithLang 派生一个指定语言的错误实例。
|
||||
func (e *langError) WithLang(lang string) *langError {
|
||||
return &langError{ctx: langx.SetCtxLang(e.ctx, lang), key: e.key, format: e.copyFormat()}
|
||||
}
|
||||
|
||||
// WithKV 派生一个添加单个占位符键值对的错误实例。
|
||||
func (e *langError) WithKV(k, v string) *langError {
|
||||
newFmt := e.copyFormat()
|
||||
if newFmt == nil {
|
||||
newFmt = make(map[string]string)
|
||||
}
|
||||
l.format[key] = value
|
||||
newFmt[k] = v
|
||||
return &langError{ctx: e.ctx, key: e.key, format: newFmt}
|
||||
}
|
||||
|
||||
func (e *defaultError) SetCtx(ctxHttp context.Context) {
|
||||
e.ctx = ctxHttp
|
||||
}
|
||||
|
||||
func (l *defaultError) Error() string {
|
||||
errLang := langx.GetCtxLang(l.ctx)
|
||||
return langx.GetFormat(errLang, l.key, l.format)
|
||||
}
|
||||
|
||||
func (e *defaultError) GetCode() int {
|
||||
return langx.GetCode(e.key)
|
||||
}
|
||||
|
||||
func (e *defaultError) GetKey() string {
|
||||
return e.key
|
||||
}
|
||||
|
||||
func (e *defaultError) GetFormat() map[string]string {
|
||||
if e.format == nil {
|
||||
e.format = make(map[string]string)
|
||||
// WithMap 派生一个批量添加占位符键值对的错误实例(新 map 完全替换)。
|
||||
func (e *langError) WithMap(m map[string]string) *langError {
|
||||
newFmt := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
newFmt[k] = v
|
||||
}
|
||||
return e.format
|
||||
return &langError{ctx: e.ctx, key: e.key, format: newFmt}
|
||||
}
|
||||
|
||||
func (e *defaultError) SetLang(lang string) {
|
||||
e.ctx = langx.SetCtxLang(e.ctx, lang)
|
||||
}
|
||||
// ---- 内部工具 --------------------------------------------------------
|
||||
|
||||
func NewErrorf(ctx context.Context, key string, format map[string]string) error {
|
||||
return &defaultError{
|
||||
ctx: ctx,
|
||||
key: key,
|
||||
format: format,
|
||||
func (e *langError) copyFormat() map[string]string {
|
||||
if len(e.format) == 0 {
|
||||
return nil
|
||||
}
|
||||
dst := make(map[string]string, len(e.format))
|
||||
for k, v := range e.format {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func NewError(ctx context.Context, key string) error {
|
||||
return &defaultError{
|
||||
ctx: ctx,
|
||||
key: key,
|
||||
func copyMapString(src map[string]string) map[string]string {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
dst := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func NewStruct(ctx context.Context, key string, format map[string]string) ErrorInterface {
|
||||
return &defaultError{
|
||||
ctx: ctx,
|
||||
key: key,
|
||||
format: format,
|
||||
// ---- 构造函数 --------------------------------------------------------
|
||||
|
||||
// NewError 创建一个携带 ctx 的运行时错误。
|
||||
func NewError(ctx context.Context, key string) *langError {
|
||||
return &langError{ctx: ctx, key: key}
|
||||
}
|
||||
|
||||
// NewErrorf 创建一个携带占位符键值对的运行时错误。
|
||||
func NewErrorf(ctx context.Context, key string, kv map[string]string) *langError {
|
||||
return &langError{ctx: ctx, key: key, format: copyMapString(kv)}
|
||||
}
|
||||
|
||||
// ---- 辅助函数,简化 error → ErrorInfo 提取 ---------------------------
|
||||
|
||||
// As 从 error 链中提取 *langError,失败返回 false。
|
||||
func As(err error) (*langError, bool) {
|
||||
var e *langError
|
||||
ok := errors.As(err, &e)
|
||||
return e, ok
|
||||
}
|
||||
|
||||
// CodeFrom 从 error 中获取错误码;若不是 langError 则返回 -1。
|
||||
func CodeFrom(err error) int {
|
||||
if e, ok := As(err); ok {
|
||||
return e.Code()
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// KeyFrom 从 error 中获取 Key;若不是 langError 则返回空串。
|
||||
func KeyFrom(err error) string {
|
||||
if e, ok := As(err); ok {
|
||||
return e.Key()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FormatFrom 从 error 中获取占位符键值对(深拷贝);若不是 langError 则返回 nil。
|
||||
func FormatFrom(err error) map[string]string {
|
||||
if e, ok := As(err); ok {
|
||||
return e.Format()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user