完善错误码的用法

This commit is contained in:
yun
2026-07-22 00:05:37 +08:00
parent 8eaa8a6933
commit 4e81b83ab6
6 changed files with 620 additions and 211 deletions
+226 -37
View File
@@ -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("=== 示例4HTTP 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()
}
// ======================================================================
// 示例 6error 链与 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
View File
@@ -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("=== 方式2embed 导入 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
// 方式2embed 导入 — 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())
}
+44 -4
View File
@@ -1,7 +1,47 @@
# 导入资源
# 语言包导入
# 通过embed导入
errorx 支持三种方式导入多语言资源。
# 通过文件导入
## 方式1:逐条追加(Append
# 追加导入
适合错误码较少、运行时动态注册的场景。
```go
langx.AppendCode(map[string]int{"success": 200})
langx.AppendTrans("zh", map[string]string{"success": "成功"})
```
## 方式2Embed 导入
适合中型项目,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#` 占位符