refactor!: 错误值改为不可变,修复并发崩溃与兜底缺陷
BREAKING CHANGE: 相对 v0.0.1 的 API 变更 - 删除全部 Set* 方法,改为返回新值的 With*;错误值不可变,包级常量可并发使用 - 错误值不再持有 context,只保存语言标识(WithLang),渲染时再传 ctx - 删除兼容别名 Errf / ErrfKV / Msgf / NewLanguage / NewStruct 与空操作 Copy() - Msgf 改为 MsgWith,NewErrorf 改为 NewErrorWith;ErrorLanguage 不再实现 ErrorInterface 修复 - Copy 浅拷贝共享 format map,并发下 fatal error: concurrent map writes - 包级常量被 Set* 污染,请求之间互相改写错误文案 - langx.RegisterCode / RegisterTrans 覆盖注册表后,运行时错误码退化为默认码、文案退化为裸 key - 未注册 key 静默返回默认码 + 裸 key,现返回 500 + 未注册提示 - NewError(nil, ...) 与非 string 的 Accept-Language 触发 panic 新增 - Wrap / Unwrap / errors.Is 按 key 匹配、调用栈(惰性解析)、%+v 输出 cause 与栈 - 错误常量自带默认文案兜底 + errorx 侧常量注册表(不依赖 langx 可变状态) - 根 README、3 个可运行示例、17 个断言测试(语句覆盖率 97.2%)、基准测试 依赖 - github.com/yuninks/langx v0.0.7 -> v0.1.0
This commit is contained in:
+79
-29
@@ -1,54 +1,104 @@
|
||||
// Package main 演示 langx 的三种资源加载方式,以及它们和 errorx 的配合。
|
||||
//
|
||||
// 运行:go run ./example/imports
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/yuninks/errorx"
|
||||
"github.com/yuninks/langx"
|
||||
)
|
||||
|
||||
//go:embed lang
|
||||
var assetsFs embed.FS
|
||||
|
||||
// errorx 定义的错误常量:code 与默认文案都跟着常量走
|
||||
var (
|
||||
ErrSuccess = errorx.New("success", 200, "操作成功")
|
||||
ErrError = errorx.New("error", 400, "操作失败")
|
||||
)
|
||||
|
||||
func main() {
|
||||
regByAppend()
|
||||
regByEmbed()
|
||||
regByDir()
|
||||
withErrorx()
|
||||
}
|
||||
|
||||
// 导入语言包 基于Append
|
||||
// 1) Append:零散追加,不会覆盖已有资源,推荐业务代码使用
|
||||
func regByAppend() {
|
||||
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)
|
||||
fmt.Println("=== 1) Append:追加注册 ===")
|
||||
langx.AppendCode(map[string]int{"append_key": 1001})
|
||||
langx.AppendTrans("zh", map[string]string{"append_key": "追加的错误"})
|
||||
|
||||
fmt.Println("zh:", trans("zh", "append_key", nil))
|
||||
fmt.Println("en:", trans("en", "append_key", nil), "(en 没有,回退到默认语言)")
|
||||
}
|
||||
|
||||
// 导入语言包 基于Embed
|
||||
// 2) Embed://go:embed 把 lang 目录打进二进制
|
||||
func regByEmbed() {
|
||||
err := langx.RegisterEmbed(assetsFs)
|
||||
fmt.Println(err)
|
||||
|
||||
code, msg := langx.GetTransFormat("zh", "success", map[string]string{})
|
||||
fmt.Println(code, msg)
|
||||
code, msg = langx.GetTransFormat("en", "error", map[string]string{
|
||||
"msg": "这是失败的原因",
|
||||
})
|
||||
fmt.Println(code, msg)
|
||||
fmt.Println()
|
||||
fmt.Println("=== 2) Embed:打包资源 ===")
|
||||
if err := langx.RegisterEmbed(assetsFs); err != nil {
|
||||
fmt.Println("RegisterEmbed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("zh:", trans("zh", "success", nil))
|
||||
fmt.Println("en:", trans("en", "error", map[string]string{"msg": "disk full"}))
|
||||
fmt.Println("第 1 步的 append_key 仍在:", trans("zh", "append_key", nil), "(RegisterEmbed 是合并,不覆盖)")
|
||||
}
|
||||
|
||||
// 导入语言包 基于文件
|
||||
// 3) Dir:运行时从目录读取,适合运维改文案不用重新编译
|
||||
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": "这是失败的原因",
|
||||
})
|
||||
fmt.Println(code, msg)
|
||||
fmt.Println()
|
||||
fmt.Println("=== 3) Dir:读取目录 ===")
|
||||
dir := langDir()
|
||||
if err := langx.RegisterDir(dir); err != nil {
|
||||
fmt.Println("RegisterDir:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("zh:", trans("zh", "success", nil), "(dir =", dir+")")
|
||||
}
|
||||
|
||||
// 4) 与 errorx 组合。
|
||||
// langx 有两种注册语义(v0.1.0 起):
|
||||
// - RegisterCode / RegisterTrans:整体替换对应注册表
|
||||
// - RegisterEmbed / RegisterDir / RegisterFS:按键合并,不动已有资源
|
||||
//
|
||||
// 上面用的是后者,所以 errorx 在 init 阶段追加的内置资源还在。
|
||||
// 即使用户改用 RegisterCode/RegisterTrans 覆盖掉,错误常量自带 code 与默认文案,
|
||||
// 也不会退化成裸 key —— code 永远来自常量自身。
|
||||
func withErrorx() {
|
||||
fmt.Println()
|
||||
fmt.Println("=== 4) 与 errorx 组合 ===")
|
||||
ctx := context.Background()
|
||||
enCtx := langx.SetCtxLang(ctx, "en")
|
||||
|
||||
// code 来自错误常量自身,msg 优先用 langx 资源里的文案
|
||||
fmt.Println("code :", ErrError.Err().GetCode())
|
||||
fmt.Println("zh(资源优先) :", ErrError.ErrKV("msg", "磁盘满了").Msg(ctx))
|
||||
fmt.Println("en(资源优先) :", ErrError.ErrKV("msg", "disk full").Msg(enCtx))
|
||||
fmt.Println("资源里的 key :", errorx.NewError(ctx, "error"))
|
||||
fmt.Println("成功 :", ErrSuccess.Err().GetCode(), ErrSuccess.Msg(ctx))
|
||||
// error_with_msg 没有写进资源文件,回退到错误常量自带的默认文案
|
||||
fmt.Println("en(未注册回退):", errorx.ErrWithMsg.ErrKV("msg", "disk full").Msg(enCtx))
|
||||
}
|
||||
|
||||
func trans(lang, key string, format map[string]string) string {
|
||||
code, msg := langx.GetTransFormat(lang, key, format)
|
||||
return fmt.Sprintf("[%d] %s", code, msg)
|
||||
}
|
||||
|
||||
// langDir 兼容两种运行方式:仓库根目录 go run ./example/imports,或进入目录 go run .
|
||||
func langDir() string {
|
||||
for _, p := range []string{"lang", "example/imports/lang"} {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return "example/imports/lang"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
# 导入资源
|
||||
# langx 资源加载示例
|
||||
|
||||
# 通过embed导入
|
||||
本目录演示 langx 的三种资源加载方式,以及它们与 errorx 的配合。
|
||||
|
||||
# 通过文件导入
|
||||
| 方式 | 函数 | 语义 |
|
||||
| --- | --- | --- |
|
||||
| 追加 | `langx.AppendCode` / `langx.AppendTrans` | 按键合并,**业务代码推荐** |
|
||||
| 打包 | `langx.RegisterEmbed(fs)` | 按键合并,用 //go:embed 把 lang 目录打进二进制,随程序发布 |
|
||||
| 目录 | `langx.RegisterDir(dir)` | 按键合并,运行时读目录,改文案不用重新编译 |
|
||||
|
||||
# 追加导入
|
||||
## 资源文件格式
|
||||
|
||||
- `code.json`:`{"key": 业务码}`
|
||||
- `zh.json` / `en.json`:`{"key": "文案 #占位符#"}`
|
||||
- 子目录的第一段是语言名:`zh/common.json`、`zh/errors.json` 都会并入 `zh`
|
||||
|
||||
## 注意两种注册语义(langx v0.1.0 起)
|
||||
|
||||
- `RegisterCode` / `RegisterTrans`:**整体替换**对应的注册表,会冲掉 `errorx.New` 在 init 阶段追加的内置文案;
|
||||
- `RegisterEmbed` / `RegisterDir` / `RegisterFS`:**按键合并**,不影响已经注册的资源。
|
||||
|
||||
即使被整体替换,errorx 的错误常量自带 code 与默认文案,查不到时会自动回填,
|
||||
所以 `errorx.Error.Err().GetCode()` 永远是 400、文案永远可读,不会退化成裸 key。
|
||||
但如果直接用 `langx.GetTransFormat` 读这些 key,就会看到裸 key。
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
go run ./example/imports
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user