跳到主要内容

Mold

mold 包是一个结构体变换引擎,基于结构体标签修改字段值。它在字段和结构体两个层级都可以操作。

工作原理

结构体字段上的 mold 标签会触发变换函数。CRUD 查询动作会在 find_onefind_allfind_pagefind_treeexport 结果返回前运行 transformer,因此响应模型可以暴露派生字段或翻译字段。

内置:字典翻译

内置 translate transformer 会通过已注册的 Translator 实现解析源字段,并把 结果写入同级 <Field>Name 字段。框架自带一个内置 translator: DictionaryTranslator,它只处理 mold:"translate=dict:status" 这样的 dict: kind。

type Order struct {
Status string `json:"status" mold:"translate=dict:status"`
StatusName string `json:"statusName" bun:",scanonly"`
}

当查询结果包含 Status = "active" 时,transformer 会向字典 resolver 查询 key status、code active,然后把显示名写入 StatusName

orm.FullAuditedModel 这类审计模型会在 CreatedByUpdatedBy 上使用 mold:"translate=user?"。这个标签是给自定义用户 translator 预留的可选 hook; 内置字典 translator 并不提供 user 翻译。

接口

Transformer

type Transformer interface {
Struct(ctx context.Context, value any) error
Field(ctx context.Context, value any, tags string) error
}

Transformer.Struct 要求传入非 nil 的 struct 指针。传入 nil、非指针、nil 指针、指向非 struct 的指针,或 time.Time 值都会返回 error。 Transformer.Field 要求传入非 nil 指针;但 tag 字符串为空或为 "-" 时是 no-op,不会检查字段值。

FieldTransformer

实现自定义字段级变换:

type FieldTransformer interface {
Tag() string
Transform(ctx context.Context, fl FieldLevel) error
}

StructTransformer

实现自定义结构体级变换:

type StructTransformer interface {
Transform(ctx context.Context, sl StructLevel) error
}

Interceptor

把变换重定向到内部值(例如 sql.NullString → 内部 string):

type Interceptor interface {
Intercept(current reflect.Value) (inner reflect.Value)
}

FieldLevel API

在字段变换器内部,FieldLevel 提供:

方法返回值用途
Transformer()Transformer访问父级变换器
Name()string当前字段名
Parent()reflect.Value父级结构体值
Field()reflect.Value当前字段值
Param()string标签中的参数(如 translate=user? 中的 user?
SiblingField(name)reflect.Value, bool按名称访问同级字段
Struct()reflect.Value当前字段所在的 struct;单独变换字段时可能是 invalid value

StructLevel 提供 Transformer()Parent()Struct(),供结构体级 transformer 使用。

函数适配器也是公开 API:

适配器作用
mold.Func用普通函数实现字段级 transformer
mold.StructLevelFunc用普通函数实现结构体级 transformer
mold.InterceptorFunc用普通函数实现 Interceptor

标签格式

mold:"function=param"

多个变换:

mold:"function1=param1,function2=param2"

mold:"-" 会跳过字段。dive 会递归处理 slice、array 或 map。对 map 来说, dive,keys,...,endkeys,... 会把 keysendkeys 之间的标签应用到 map key, 把后续标签应用到 map value。嵌套 struct 字段会自动遍历;slice 和 map 元素 只有写了 dive 才会被 transform。参数中如果包含逗号,需要写成 0x2C

内置:表达式派生字段

core runtime 会注册一个由 expression.Engine 支持的 expr field transformer。它会把当前结构体作为表达式环境,执行表达式,并把解码后的 结果写入带标签的字段:

type LineItem struct {
Price float64 `json:"price"`
Qty float64 `json:"qty"`
Total float64 `json:"total" mold:"expr=price * qty"`
}

字段按声明顺序执行,所以派生字段可以引用声明在它上方的 sibling field。 如果表达式里包含逗号,需要在 mold tag 中写成 0x2C。完整 API 见 表达式引擎

expr tag 由 expression module 通过 vef:mold:field_transformers group 提供, 不是 mold module 单独提供的默认 tag。mold module 自身提供内置 translate field transformer 和 DictionaryTranslator;其他 field transformer 需要通过同一个 group 注册,或由应用自行构造 custom transformer。

字典翻译

translate 变换器通过 Translator 接口翻译字段值。框架内置了一个 DictionaryTranslator,负责处理 dict: 前缀的 kind(例如 mold:"translate=dict:gender")。如果 kind 是 dict:status?,内置 translator 仍会认为完整字符串被支持,并解析字典 key status?;它不会自动剥掉 ? 后缀。

支持的源字段类型包括 string*string、有符号和无符号整数、这些整数的指针、 []string,以及经 mold 解引用后的 *[]string。标量目标字段必须是 string*string;slice 目标字段必须是 []string*[]string。目标字段始终是 源字段名加 Name<Field>Name)。空标量值会跳过,nil 源 slice 会保持目标 字段不变,空源 slice 会写入空目标 slice。

自定义 translator 实现:

type Translator interface {
Supports(kind string) bool
Translate(ctx context.Context, kind, value string) (string, error)
}

字典风格的 resolver / loader 接口:

type DictionaryResolver interface {
Resolve(ctx context.Context, key, code string) (string, error)
}

type DictionaryLoader interface {
Load(ctx context.Context, key string) (map[string]string, error)
}

DictionaryLoaderFunc 可以让普通函数满足 DictionaryLoader 接口。

? 的真实含义

mold:"translate=user?" 中的 ? 表示:当没有任何 translator 支持完整 kind 字符串时,安静地跳过这次翻译;如果有匹配的 translator,但它的 Translate 内部返回了 error,error 仍会向上抛 —— ? 不是“吞掉一切错误”。

也就是说,如果你希望 translate=user? 真正执行,需要在容器里注册一个 Supports("user?") 为真的自定义 Translator;没有注册时字段保持原值(不会 报错)。translate=user 这样的必需 kind 在没有 translator 支持时会返回 error。

带缓存的解析器

CachedDictionaryResolver 包装的是 DictionaryLoader(不是 DictionaryResolver),并通过订阅 mold.DictionaryChangedEvent 自动失效:

resolver := mold.NewCachedDictionaryResolver(loader, bus)

NewCachedDictionaryResolverDictionaryLoaderevent.Bus 为 nil 时会 panic。 缓存按 loader 的 key 缓存整张字典,并会合并同一 key 的并发加载。Resolve 在 key 为空、code 为空,或字典里找不到 code 时,都会无 error 地返回空字符串。

当某个字典的底层数据发生变化时,通过事件总线发布 mold.DictionaryChangedEvent{Keys: []string{"..."}} 来让对应缓存失效。

也可以使用辅助函数发布同一事件:

err := mold.PublishDictionaryChangedEvent(ctx, bus, "gender", "status")

DictionaryChangedEvent.EventType() 返回缓存失效 subscriber 使用的框架事件类型。

调用 PublishDictionaryChangedEvent(ctx, bus) 但不传 key 时,表示要求订阅方 清空整个字典缓存。

这条缓存路径上的公开 API 是 CachedDictionaryResolverDictionaryChangedEventDictionaryChangedEvent.KeysPublishDictionaryChangedEventCachedDictionaryResolver.Resolve;其中 CachedDictionaryResolver.Resolve 实现 DictionaryResolver.Resolve