全局常量应集中放在 pkg/consts/consts.go,通过导出函数访问;字典数据需用 sync.rwmutex 封装并支持热更新;禁止使用 gin.engine.set() 存储业务字典;配置驱动字典推荐 json 与数据库结合加载。

全局常量该放哪?别塞进 main.go 或路由文件里
直接在 main.go 里定义 const API_VERSION = "v1" 或 var SYSTEM_NAME = "admin" 看似简单,但很快会失控:不同模块要改同一个常量时得满项目搜替换;测试时无法 mock;编译时无法按环境注入。真正可维护的做法是把常量集中到独立包里,比如 pkg/consts,再用小写字母开头的变量(如 apiVersion)配合导出函数封装访问逻辑。
常见错误现象:多人协作时发现 TIMEOUT_SECONDS 在三个地方重复定义,值还不一致;CI 构建失败提示 “redeclared in this block”。
- 所有常量统一放在
pkg/consts/consts.go,只导出必要字段(如consts.APIVersion()),不暴露原始变量 - 环境相关常量(如数据库超时、重试次数)用
init()+os.Getenv()动态加载,避免硬编码 - 禁止在 handler 函数里直接引用
"user_status_active"这类 magic string,必须走consts.UserStatusActive
字典数据(如状态码、枚举映射)怎么热更新?别用全局 map 初始化就完事
把用户状态码映射写成 var StatusMap = map[int]string{1: "active", 2: "inactive"} 确实能跑,但一旦业务要求“状态 3 新增为 pending”,就得重启服务——这在灰度发布或线上紧急修复时不可接受。Gin 本身不提供字典热加载能力,得靠组合设计。
使用场景:权限系统里的角色类型、订单状态流转、多语言文案 ID 映射。
- 用
sync.RWMutex包裹字典 map,读多写少时性能损耗极小 - 通过 HTTP 接口(如
POST /admin/dict/reload)触发 reload,配合中间件鉴权,避免被恶意调用 - 初始化时从 JSON 文件或数据库加载,而不是写死在代码里;文件路径建议用
consts.DictPath统一管理 - 对外暴露只读接口,例如
dict.GetStatusName(code),内部做mutex.RLock(),别让调用方操心并发
为什么不能把字典塞进 gin.Engine 的 Set() 里?
有人图省事用 r.Set("status_dict", myMap),然后在 handler 里 r.Get("status_dict") 取出来用。问题在于:Set() 是给框架内部用的,不是为业务字典设计的;它不支持并发安全;更关键的是,你没法在中间件里统一 reload —— 每次 reload 都得手动遍历所有已注册的 Engine 实例(如果有多个的话)。
性能影响:每次 Get() 都涉及类型断言和 interface{} 转换,比直接调用函数慢 2~3 倍;内存上还会多一层间接引用。
-
gin.Engine.Set()仅适合临时存生命周期明确的上下文对象(如 trace ID),不适合长期驻留的业务字典 - 若真要用框架存储,应基于
gin.Context的Set()在请求级存,而非全局级 - 字典变更频率低、读取频繁,更适合用单例 + 读写锁,而不是依赖框架容器
配置驱动的字典初始化:JSON 文件 vs 数据库选哪个?
本地 JSON 文件加载快、调试方便,但上线后修改要发版;数据库查一次慢点,但支持运营后台实时编辑。实际项目里往往两者结合:启动时从 DB 加载主字典,再用 JSON 补充开发环境专用项(如 mock 状态)。
容易踩的坑:JSON 文件路径写相对路径(如 "./config/dict.json"),结果 Docker 容器里找不到;或者没加 json:"code" tag 导致结构体字段解析失败。
- 统一用
pkg/dict/loader.go封装加载逻辑,对外只暴露LoadFromDB()和LoadFromFile(path string) - JSON 文件字段名必须小写 + snake_case,Go 结构体用
json:"status_code"显式声明 - 数据库加载失败时 fallback 到内置默认字典(硬编码在代码里),保证服务不因 DB 暂不可用而崩











