feat: load explicit configuration references and remove implicit env overlays
This commit is contained in:
263
CONFIG.md
263
CONFIG.md
@@ -1,178 +1,177 @@
|
||||
# 统一配置加载
|
||||
# 配置:文件明确声明值的来源
|
||||
|
||||
v0.20 起,新生成服务的 `main.go` 调用生成器维护的 `config.Load`。项目只需要定义业务字段、默认值和校验;日志、HTTP、数据库等配置使用同一流程,配置加载时不打开数据库、监听端口或创建日志文件。
|
||||
v0.22 起只使用这一条流程:
|
||||
|
||||
```text
|
||||
代码默认值 → envDefault(仅填零值)→ 可选配置文件
|
||||
→ 完整结构体环境覆盖(含别名)→ 框架规范化 → 项目 Validate
|
||||
→ 初始化日志及项目依赖 → 启动服务
|
||||
项目 Default() → 解析选中的一个文件 → 解析字段值中的环境引用
|
||||
→ 按 Go 类型绑定已提供的字段 → 项目 Validate() → 初始化日志和依赖
|
||||
```
|
||||
|
||||
## 生成项目要维护什么
|
||||
没有环境前缀、别名表或隐式二次覆盖。业务只维护 `Config`、`Default()`、`Validate()` 和配置文件。项目 Validate 中继续调用嵌套框架配置的 Validate;生成模板已包含这些调用。验证函数只检查值,不应打开资源。
|
||||
|
||||
| 文件 | 维护方 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `main.go` | 生成器 | 启动、信号、请求排空、依赖关闭 |
|
||||
| `internal/config/loadGen.go` | 生成器 | 调用统一加载入口;纳入 `generate --check` |
|
||||
| `internal/config/config.go` | 项目 | `Config`、`Default()`、`Validate()` |
|
||||
| `internal/config/loader.go` | 项目 | `NewLoader()`,只有增加文件格式时才需修改 |
|
||||
| `etc/config.yaml` | 项目 | 部署配置;可改用 JSON/XML 并传 `-config` |
|
||||
| `internal/svc` | 项目与生成器分文件 | 初始化依赖、登记清理、AppOptions |
|
||||
|
||||
比如增加业务的批次数量:在 Config 增加一个字段,在 Validate 加范围校验;需要部署值时改配置文件或环境变量。不修改 main、Load 或 Handler。
|
||||
## 直接使用
|
||||
|
||||
```go
|
||||
// 添加到项目 Config。
|
||||
BatchSize int `json:"batchSize" envDefault:"100"`
|
||||
|
||||
// 添加到项目 Validate。
|
||||
if c.BatchSize < 1 || c.BatchSize > 1000 {
|
||||
return omniConfig.Invalid("batchSize", "must be between 1 and 1000")
|
||||
}
|
||||
```
|
||||
|
||||
自动支持 `GO_OMNI_BATCH_SIZE`。生成服务的空环境值策略为 `ignore`,0 和 false 是有效覆盖;范围由 Validate 决定。
|
||||
重新生成保留 config.go、loader.go 和 svc 的手写扩展。
|
||||
|
||||
## 手写项目用法
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"git.yuntuo.hk/k2safe/go-omni/config"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Timeout time.Duration `json:"timeout" envDefault:"3s"`
|
||||
Limit int `json:"limit" envDefault:"100"`
|
||||
Token string `json:"-" env:"APP_TOKEN,secret"`
|
||||
Server omniConfig.Config `json:"server"`
|
||||
BatchSize int `json:"batchSize"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Password string `json:"password"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
}
|
||||
func Default() Config {
|
||||
return Config{Server: omniConfig.Default(), BatchSize: 100, Enabled: true,
|
||||
Timeout: 3*time.Second}
|
||||
}
|
||||
func (c Config) Validate() error {
|
||||
if c.Timeout <= 0 {
|
||||
return config.Invalid("timeout", "must be positive")
|
||||
if err := c.Server.Validate(); err != nil {
|
||||
return omniConfig.AtField("server", err)
|
||||
}
|
||||
if c.BatchSize < 0 { return omniConfig.Invalid("batchSize", "must not be negative") }
|
||||
return nil
|
||||
}
|
||||
|
||||
// cfg, err := config.LoadProject("etc/config.yaml", Config{},
|
||||
// config.EnvOptions{Prefix: "APP_", Empty: "reject"})
|
||||
```
|
||||
|
||||
`LoadProject` 接收结构体默认值,可实现值接收器或指针接收器的 `Validate() error`。空路径表示不读文件。
|
||||
`DecodeFile` 只做文件覆盖,不做 envDefault、环境覆盖或业务 Validate;旧的两参数调用仍有效,可用结构体或结构体指针。低层 `ApplyDefaults`、`OverlayEnv`、`LoadFile` 继续存在。
|
||||
|
||||
嵌套校验用 `config.AtField("client", err)` 加路径前缀。公共原因通过 `config.Invalid` 提供,原因中不要包含密码、DSN、Token 或输入原值。
|
||||
|
||||
## JSON / YAML / XML
|
||||
|
||||
按文件扩展名选择解析器:`.json`、`.yaml`、`.yml`、`.xml`,大小写不敏感。所有格式使用 Go 结构体的 **json 字段名**,无需为 XML 复制一份结构或标签。
|
||||
以下配置等价:
|
||||
|
||||
```json
|
||||
{"client":{"timeout":"2s"},"enabled":false,"ports":[8080,8081],"optional":null}
|
||||
// 手写项目;生成项目的 Load 已自动调用此入口。
|
||||
cfg, err := omniConfig.LoadProject("etc/config.yaml", Default())
|
||||
```
|
||||
|
||||
```yaml
|
||||
client:
|
||||
timeout: 2s
|
||||
server:
|
||||
address: "${HTTP_ADDR:-127.0.0.1:8080}"
|
||||
password: "${DB_PASSWORD}"
|
||||
timeout: "${REQUEST_TIMEOUT:-3s}"
|
||||
enabled: false
|
||||
ports: [8080, 8081]
|
||||
optional: null
|
||||
```
|
||||
|
||||
文件未写 batchSize,保留 100;enabled 明确写 false,就使用 false。变量名字完全由文件决定,不需要对应 Go 字段名。配置文件可只列出部署需要变动的字段;不会自动导出/打印包含凭据的完整配置。
|
||||
|
||||
## 引用语法
|
||||
|
||||
| 文件里的值 | 含义 |
|
||||
| --- | --- |
|
||||
| `host: 127.0.0.1` | 固定值,不读取任何环境变量 |
|
||||
| `password: "${DB_PASSWORD}"` | 未设置或空字符串报错,不回退到 Default() |
|
||||
| `port: "${DB_PORT:-3306}"` | 未设置或为空时使用 3306 |
|
||||
| `sessionToken: "${SESSION_TOKEN:-}"` | 未设置或为空时使用空字符串,是否合法由目标类型和 Validate 决定 |
|
||||
| `url: "https://${HOST}:${PORT:-443}/v1"` | 普通字符串中可出现多个引用 |
|
||||
| `literal: "$${DB_PASSWORD}"` | 得到字面量 `${DB_PASSWORD}`,不读取变量 |
|
||||
| `literal: "$$cost"` | `$$` 转义一个 `$`,得到 `$cost` |
|
||||
|
||||
变量名遵循 `[A-Za-z_][A-Za-z0-9_]*`。只支持 `${NAME}`、`${NAME:-fallback}` 和 `$$` 转义,不支持 `${NAME-default}`、嵌套引用或 shell 表达式。普通单独的 `$` 保留。fallback 到第一个 `}` 结束,可包含冒号、`:-`、引号等文件格式允许的字符;需要包含 `}` 的值可使用固定值或环境变量。
|
||||
|
||||
解析只扫描文件的**已解码字段值**,不替换 YAML/JSON/XML 源文本,不扩展字段名,也不扫描 Default() 的字符串。插入的环境值和 fallback 不再展开。密码 `DDSFLKJlsf:saf:-123`、引号、换行、`${OTHER}` 均原样保留,不破坏文档结构。字符串不统一 TrimSpace;`"0"`、`"false"` 和全空格字符串均属于已提供。
|
||||
|
||||
同一变量每次加载最多读取一次;下一次加载会重新读取。没有全局变量值缓存。并发加载各自维护结果,不能把调用者正在修改的默认配置当作共享可变输入。
|
||||
|
||||
## 类型与覆盖
|
||||
|
||||
目标 Go 类型决定引用结果的转换:整数、浮点数、布尔值、`time.Duration`、字符串、指针及已有自定义 JSON/Text 反序列化。数值范围按目标位宽检查,整数不经过 float64。自定义反序列化仍由自定义类型解释文本,不猜测其业务含义。
|
||||
|
||||
- 文件缺失字段保留初始值;显式 0、false、空字符串正常覆盖,不用零值判断字段是否存在。
|
||||
- `${MISSING}` 失败就报错;需要备用值必须写 `${MISSING:-value}`。
|
||||
- 字符串编号 `"001"` 保持 `001`;绑定到 int 才转整数。环境里的 `null` 不自动变成空指针,`[1,2]` 不自动变成数组。
|
||||
- 集合直接在文件里声明,可在每个标量元素中使用引用,例如 `ports: ["${PORT_A:-8080}", "${PORT_B:-8081}"]`。
|
||||
- 数组整体替换;空 slice 保持非 nil;固定长度 Go 数组要求长度一致。map 合并键,已提供键的值整体替换。
|
||||
- 固定文件 null 沿用 JSON 语义:清空指针/map/slice/interface,普通值字段保持初始值。需要区分 null 时使用指针。
|
||||
- `any` 的引用结果保持字符串,不猜测动态类型。固定 JSON/YAML 数字使用 json.Number 保留精度。
|
||||
- 引用成功后的值不会再覆盖一次。旧 `GO_OMNI_*` 名称只有出现在文件引用中才有作用;不隐式重置日志 file/rotation。
|
||||
|
||||
普通固定字符串仍按原有严格绑定规则处理;不会为了支持变量,把所有 `"123"` 都自动转换成数字。
|
||||
|
||||
## YAML、JSON、XML 一致
|
||||
|
||||
使用 Go 的 `json` 字段名,无需维护三份 tag。以下三份文件等价:
|
||||
|
||||
```yaml
|
||||
port: "${PORT:-3306}"
|
||||
password: "${PASSWORD}"
|
||||
```
|
||||
|
||||
```json
|
||||
{"port":"${PORT:-3306}","password":"${PASSWORD}"}
|
||||
```
|
||||
|
||||
```xml
|
||||
<config>
|
||||
<client><timeout>2s</timeout></client>
|
||||
<enabled>false</enabled>
|
||||
<ports type="array"><item>8080</item><item>8081</item></ports>
|
||||
<optional type="null"/>
|
||||
</config>
|
||||
<config><port>${PORT:-3306}</port><password>${PASSWORD}</password></config>
|
||||
```
|
||||
|
||||
XML 必须使用一个 `<config>` 根;普通叶节点按目标 Go 类型转换。数组显式 `type="array"` 加 `<item>`,不根据重复元素猜测类型。`type="array"` 空元素表示空数组,`type="object"` 表示空对象,`type="null"` 表示 null。`any` 中的 XML 叶节点默认是字符串,需要动态数字/布尔时标注 `type="number"` / `type="bool"`;也可用 `type="string"`。
|
||||
XML 使用 `<config>` 根,标量占位符写在普通叶元素或 `type="string"` 元素中,由 Go 字段决定类型;显式 `type="number"` / `type="bool"` 表示文件里已经有合法数字/布尔常量。XML 数组用 `type="array"` 加 `<item>`;空对象 `type="object"`、null 用 `type="null"`。不从重复元素猜测数组。
|
||||
|
||||
类型和覆盖规则:
|
||||
`.api` 不经过配置加载器,响应模板 `${code}` 不变。**如果把响应模板写在 YAML/JSON/XML 配置文件里,须转义:**
|
||||
|
||||
- 缺失字段保留默认值;显式 0、false、空字符串正常覆盖。
|
||||
- null 清空指针、map、slice、interface;普通值字段保持默认值,沿用 Go JSON 解码语义。需要区分 null 时用指针。
|
||||
- 数组整体替换;空 slice 保持非 nil;固定长度 Go 数组要求长度一致,避免丢弃多余配置。
|
||||
- map 合并键,已提供的键整体覆盖对应值;map 中未提供的键保留。
|
||||
- duration 支持 `250ms`、`3s`;整数保持精度,动态 JSON 数字使用 `json.Number`,不先转 float64。
|
||||
- 自定义 `json.Unmarshaler` / `encoding.TextUnmarshaler` 仍可接入。这些回调是可信项目代码,必须自己保证并发和副作用安全;不要修改共享外部状态。
|
||||
- JSON `,string` 字段、嵌入字段遵循 JSON 字段选择;未知字段、重复绑定、冲突均拒绝。
|
||||
|
||||
内置解析器限制:单文件最多 1 MiB,解析最多 64 层、100000 个节点;绑定和默认值复制也有深度限制。只接受普通文件,不读取设备或命名管道。不支持多文档、重复字段、无效 UTF-8;YAML 拒绝 alias/merge/custom tag,XML 拒绝 DTD/外部实体、命名空间、业务属性和混合内容。XML 仅支持配置所需的子集,不是通用 XML 文档映射器。
|
||||
|
||||
这些限制针对启动配置,避免意外超大文件、别名扩展和模糊覆盖。自定义值解码器的内部字段校验由该解码器负责;例如现有错误目录使用其专有解码规则。
|
||||
|
||||
## 环境覆盖
|
||||
|
||||
统一入口的 EnvOptions 控制前缀、空值策略、别名和 Lookup。完整 JSON 路径自动转换为 UPPER_SNAKE_CASE:`client.timeout` → `APP_CLIENT_TIMEOUT`。
|
||||
|
||||
```go
|
||||
config.EnvOptions{
|
||||
Prefix: "GO_OMNI_", Empty: "ignore",
|
||||
Aliases: config.ServerEnvAliases("GO_OMNI_", "server"),
|
||||
}
|
||||
```yaml
|
||||
server:
|
||||
response:
|
||||
body:
|
||||
code: "$${code}"
|
||||
msg: "$${message}"
|
||||
data: "$${data}"
|
||||
```
|
||||
|
||||
生成项目同时接受完整的 `GO_OMNI_SERVER_HTTP_READ_TIMEOUT` 与短别名 `GO_OMNI_HTTP_READ_TIMEOUT`。完整名存在时优先,即便其空值被 ignore,也不回退别名。不会再二次调用 ApplyEnv 覆盖业务字段。
|
||||
加载后恢复为 `${code}` 等响应占位符,仍由响应编码器处理。
|
||||
|
||||
显式 `env:"CUSTOM_NAME"` 是绝对名称;`env:"-"` 跳过子树。`json:"-"` 只在声明显式 env 名时参与,可让密钥只能从环境加载。`secret` 标签表达用途,解析错误对所有字段都不打印值。slice/map 的环境值采用 JSON。`required` 检查字符串非空、指针/集合非 nil;复杂范围由 Validate 负责。详见 [完整业务环境覆盖](DEVELOPMENT.md#完整业务配置的环境覆盖)。
|
||||
## 生成项目与检查
|
||||
|
||||
显式环境选择日志 stdout/stderr 会清除文件 profile 的 File/Rotation,除非同时提供非空 File 覆盖。该兼容规则由框架处理。
|
||||
| 文件 | 维护方 |
|
||||
| --- | --- |
|
||||
| `main.go`、`internal/config/loadGen.go` | 生成器维护,纳入 `generate --check` |
|
||||
| `internal/config/config.go` | 项目维护 Config、Default、Validate |
|
||||
| `etc/config.yaml` 等选定文件 | 项目维护,重新生成不覆盖 |
|
||||
| `internal/config/loader.go` | 初次生成即可使用,仅添加自定义格式时才修改 |
|
||||
|
||||
## 扩展解析器
|
||||
```sh
|
||||
go run . -config etc/config.test.yaml -check-config
|
||||
go run . -config etc/config.local.yaml
|
||||
go build .
|
||||
go-omni api generate --check
|
||||
```
|
||||
|
||||
内置格式直接可用。新增格式只在 `internal/config/loader.go` 的 `NewLoader` 注册:
|
||||
`-check-config` 使用与启动完全相同的 Load,通过时退出 0、失败时非零;不打印配置,不创建日志文件、数据库、后台组件或 HTTP 监听。它只校验配置,不检测真实数据库连接或证书握手。
|
||||
|
||||
`-config` 只选一个文件,不自动叠加 local/test/default。指定文件不存在时报错;`-config ''` 只使用 Default() 后校验,不读取任何环境变量,也不寻找其他文件。示例:[examples/configuration](examples/configuration/README.md)。
|
||||
|
||||
## 安全诊断与限制
|
||||
|
||||
```text
|
||||
config "etc/config.yaml":7:9 env "DB_PORT" field "database.port": expected a valid int
|
||||
```
|
||||
|
||||
`*config.Error` 包含 File/Field/Env/Line/Column/Reason。引用错误同时保留文件与环境变量名;组合引用失败可列出多个名字。行列定位原文件字段,不把环境值伪装成源文件文本。无法取得位置时仍提供可用的文件/字段信息。
|
||||
|
||||
用 `config.Invalid(field, publicReason)` 或 `config.AtField(prefix, err)` 报业务校验错误。未知字段给出唯一且接近的已声明字段建议,不自动修改。错误默认不打印原值;自定义 Reason 不应拼入秘密值。原始 cause 仍支持 errors.Is/As,不要把 Unwrap 后的错误或整个配置写到日志。
|
||||
|
||||
文件最多 1 MiB;单个环境值/展开字段最多 1 MiB,展开字符串合计/变量缓存分别最多 4 MiB;解析/展开限制 64 层、100000 个节点与引用计数。拒绝无效 UTF-8、多文档、重复字段、未知字段、非普通文件;YAML 不接受 alias/merge/custom tag,XML 不接受 DTD/外部实体。不执行 shell,不自动加载 `.env`。
|
||||
|
||||
## 可选解析器与测试来源
|
||||
|
||||
内置格式无需项目手写加载器。需要额外格式时在已有 NewLoader 注册 Decoder,后续仍自动经过同一引用/绑定/校验流程:
|
||||
|
||||
```go
|
||||
func NewLoader() (*omniConfig.Loader, error) {
|
||||
return omniConfig.NewLoader(
|
||||
omniConfig.WithDecoder("custom", omniConfig.DecoderFunc(
|
||||
func(data []byte) (omniConfig.Document, error) {
|
||||
// 演示注册扩展名;实际可在这里调用项目选定的 TOML 等解析库。
|
||||
return (omniConfig.JSONDecoder{}).Decode(data)
|
||||
},
|
||||
)),
|
||||
omniConfig.WithDecoder("custom", omniConfig.JSONDecoder{}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Decoder 返回 Document:Value 是 map[string]any 根,子值为 string/bool/json.Number/nil/[]any/map[string]any;无自身类型的叶节点可返回 config.Text。Positions 可选,用 `client.timeout` 或 `servers[0].timeout` 标记 1-based 行列。不提供位置时仍有文件、字段、原因。
|
||||
Decoder 返回 Document(map/string/bool/json.Number/nil/[]any,XML 类标量可用 Text),可提供 Positions。实例注册无全局 init;插件必须可并发调用、不修改共享输入,并且是可信 Go 代码。
|
||||
|
||||
自己的语法错误可返回 `&config.Error{Field: "client.timeout", Line: 3, Column: 5, Reason: "expected a duration"}`。Reason 必须安全;普通第三方错误仅公开 `invalid configuration syntax`,内部 cause 通过 errors.Is/As 保留。
|
||||
测试或嵌入宿主可以用 `WithLookupEnv(func(string)(string,bool))` 替换引用查找函数。它仍然只读取文件明确引用的名称,不启用任何自动覆盖。普通业务不需要这个选项。
|
||||
|
||||
注册表属于 Loader 实例,无全局 init 注册;不能覆盖已有扩展名。Loader 可共享,但 Decoder 必须支持并发且不保留/修改输入。解析插件是可信 Go 代码,框架不是插件沙箱。新格式依赖由选择它的项目显式引入,核心不强制引入 TOML 等库。
|
||||
## v0.22 迁移:旧模式已移除
|
||||
|
||||
## 定位配置错误
|
||||
这是不兼容调整,不保留两套加载逻辑:
|
||||
|
||||
```text
|
||||
config "etc/config.yaml":4:12 field "client.timeout": expected a duration such as 5s
|
||||
config env "APP_CLIENT_TIMEOUT" field "client.timeout": expected a valid time.Duration
|
||||
```
|
||||
- 删除 `EnvOptions`、`LoadFromEnv`、`ApplyEnv`、`OverlayEnv`、`ApplyDefaults`、`ServerEnvAliases` 和 `env`/`envDefault` 标签处理;检出遗留标签会提示迁移。
|
||||
- 删除 `database.ConfigFromEnv` / `database.ApplyEnv`;数据库配置作为项目 Config 的字段绑定。DSN 字段可在文件里使用 `dsn: "${DB_DSN}"`,序列化 database.Config 不输出 DSN 原值。
|
||||
- `LoadProject(path, defaults[, loader])` 不再接收 EnvOptions;`LoadFile(path)`、`NewReloader(path, logger)` 不再接收前缀。
|
||||
- `omni.LoadConfig(path)` 的参数现在是文件路径;空路径只用框架默认值。
|
||||
|
||||
`errors.As(err, &diagnostic)` 可获取 `*config.Error` 的 File/Field/Env/Line/Column/Reason;原 `DecodeError` 名称保留为别名。`errors.Is` 可判断文件不存在等 I/O 原因。
|
||||
迁移顺序:
|
||||
|
||||
解析失败显示文件和可获取的行列;Validate 使用 Invalid/AtField 才能定位业务字段。环境覆盖导致的错误优先标环境变量,不错误地归因到文件;数组元素验证也会追溯到覆盖它的集合环境变量。默认值未出现在文件里时没有行列。YAML 语法错误通常只有行号,列显示 1。
|
||||
1. 将 envDefault 默认值搬入 Default(),移除 env/envDefault 标签。需要文件绑定的旧 `json:"-"` 密钥字段改成明确 json 名称;不打印配置对象。
|
||||
2. 按实际部署需要,在选定文件写环境引用。固定值保持固定,不按原前缀猜测。原先依赖环境切换日志输出的项目,应在文件明确配置 file/output/rotation。
|
||||
3. 配置文件中的其他 `${...}` 模板改成 `$${...}` 字面量;`.api` 无需修改。
|
||||
4. 手写项目改为 LoadProject。生成项目保留 Config/Default/Validate/NewLoader,删除旧手写 Load 后重新生成;main/loadGen 由生成器更新,业务配置文件不会被静默改写。
|
||||
5. 升级匹配的 CLI 与框架,执行 generate、generate --check、go test、go build 和 `-check-config`,再使用真实部署配置启动。
|
||||
|
||||
不打印底层错误原文或配置值;任意普通 `errors.New(...)` 校验失败的公开原因是 `validation failed`,内部原因仍在链中。项目应改用 Invalid 给出安全的原因。第三方子配置校验若没有字段元数据,只定位到对应子树,框架不会从任意错误文案猜字段。
|
||||
|
||||
不要把 Unwrap 后的原始错误、整个配置对象写入公开日志/HTTP;自定义解析器与校验器传入的 Reason 由项目负责脱敏。
|
||||
|
||||
## v0.19 及更早项目迁移
|
||||
|
||||
先提交现有代码,再升级匹配的 CLI 与框架。旧项目的 `internal/config/config.go` 含手写 Load,新生成器会明确阻止 generate/check/dry-run,不会直接覆盖它。
|
||||
|
||||
1. 保留 Config、Default、Validate、业务配置结构及辅助函数。
|
||||
2. 删除旧 Load。普通的 defaults/file/env/Validate 流程已由 LoadProject 接管;移除由此不再使用的 import。将自定义默认值和校验分别迁入 Default / Validate,自定义文件格式迁入 NewLoader。
|
||||
3. 框架只在 `loader.go` 不存在时创建 NewLoader;已有项目文件同名时需要保证它提供同签名函数。无需手写 loadGen.go。
|
||||
4. 重新生成,执行 `generate --check`、`go test ./...`、`go build .`,再用真实配置验证启动。
|
||||
|
||||
保留业务需要的显式 env 标签/规则;如果原 Load 有解密或其他项目特殊步骤,不要直接删除,应先明确其归属,必要时在自定义 Decoder 中处理。生成器不自动搬动或猜测手写行为。`api upgrade` 仍可预览较早版本的基础设施差异,但已修改的配置必须按上述步骤迁移。
|
||||
|
||||
不会移除低层配置 API;本次移除的是**新项目里重复手写的加载流程**,不删除旧项目的业务文件。
|
||||
|
||||
完整可运行例子:[examples/configuration](examples/configuration/README.md)。
|
||||
低层 `DecodeFile(path, defaults[, loader])` 也解析显式引用,但不调用 Validate;默认值和原始 Document 都不会因失败而被部分修改。日志 Reloader 仍只允许 log.level 变化,其他静态字段变更要求重启。
|
||||
|
||||
Reference in New Issue
Block a user