110 lines
4.5 KiB
Go
110 lines
4.5 KiB
Go
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// Validate 校验配置完整性与取值合法性(#16)。
|
||
// 在 Manager.Load 解析后自动调用,把"运行时第一次请求才暴露"的配置错误
|
||
// 提前到进程启动期。返回的 error 描述具体字段,便于定位。
|
||
func (c *Config) Validate() error {
|
||
if c == nil {
|
||
return fmt.Errorf("配置为空")
|
||
}
|
||
var problems []string
|
||
|
||
// Server
|
||
if c.Server.Port < 0 || c.Server.Port > 65535 {
|
||
problems = append(problems, fmt.Sprintf("server.port 超出范围(0-65535): %d", c.Server.Port))
|
||
}
|
||
if c.Server.TLS.Enabled {
|
||
if strings.TrimSpace(c.Server.TLS.CertFile) == "" || strings.TrimSpace(c.Server.TLS.KeyFile) == "" {
|
||
problems = append(problems, "server.tls 启用后必须同时配置 cert_file 与 key_file")
|
||
}
|
||
}
|
||
if !validDuration(c.Server.ReadTimeout) || !validDuration(c.Server.WriteTimeout) ||
|
||
!validDuration(c.Server.IdleTimeout) || !validDuration(c.Server.ShutdownTimeout) {
|
||
problems = append(problems, "server 的 timeout 配置不能为负值")
|
||
}
|
||
|
||
// JWT:仅当配置了 secret 时校验(未启用 jwt 的项目可留空)
|
||
if c.JWT.Secret != "" {
|
||
if len(c.JWT.Secret) < 32 {
|
||
problems = append(problems, fmt.Sprintf("jwt.secret 长度不足 32 字节(当前 %d),HMAC 密钥过短不安全", len(c.JWT.Secret)))
|
||
}
|
||
if c.JWT.Expire < 0 || c.JWT.RefreshExpire < 0 {
|
||
problems = append(problems, "jwt.expire / jwt.refresh_expire 不能为负值")
|
||
}
|
||
}
|
||
|
||
// Database:出现任一数据库字段时视为启用;driver 为空按 MySQL 兼容处理。
|
||
if c.Database.isConfigured() {
|
||
driver := strings.TrimSpace(c.Database.Driver)
|
||
if driver != "" {
|
||
if _, ok := LookupDSNBuilder(driver); !ok {
|
||
problems = append(problems, fmt.Sprintf("database.driver 未注册: %s", driver))
|
||
}
|
||
}
|
||
if strings.TrimSpace(c.Database.CustomDSN) == "" && strings.TrimSpace(c.Database.Host) == "" {
|
||
problems = append(problems, "database.host 启用数据库后必填")
|
||
}
|
||
if strings.TrimSpace(c.Database.CustomDSN) == "" && strings.TrimSpace(c.Database.Name) == "" {
|
||
problems = append(problems, "database.name 启用数据库后必填")
|
||
}
|
||
if strings.TrimSpace(c.Database.CustomDSN) == "" && (c.Database.Port <= 0 || c.Database.Port > 65535) {
|
||
problems = append(problems, fmt.Sprintf("database.port 超出范围(1-65535): %d", c.Database.Port))
|
||
}
|
||
// L-config-3:连接池配置交叉校验。MaxOpenConns>0 时 MaxIdleConns 不应超过它,
|
||
// 否则 database/sql 会按 MaxOpenConns 截断空闲连接,配置意图与实际不符。
|
||
if c.Database.MaxOpenConns > 0 && c.Database.MaxIdleConns > c.Database.MaxOpenConns {
|
||
problems = append(problems, fmt.Sprintf(
|
||
"database.max_idle_conns(%d) 不应大于 max_open_conns(%d)", c.Database.MaxIdleConns, c.Database.MaxOpenConns))
|
||
}
|
||
// M-config-2:Postgres SSLMode 合法性(非空时校验,空由 PostgresDSN 默认 prefer)。
|
||
if sslmode := strings.TrimSpace(c.Database.SSLMode); sslmode != "" {
|
||
if !validPostgresSSLMode(sslmode) {
|
||
problems = append(problems, fmt.Sprintf(
|
||
"database.ssl_mode 非法: %s(允许: disable/allow/prefer/require/verify-ca/verify-full)", sslmode))
|
||
}
|
||
}
|
||
// M-config-2:TLSRootCA 仅在 TLS=true 时生效,单独配置而无 tls:true 视为配置不一致。
|
||
if strings.TrimSpace(c.Database.TLSRootCA) != "" && !c.Database.TLS {
|
||
problems = append(problems, "database.tls_root_ca 需配合 tls: true 才生效")
|
||
}
|
||
}
|
||
|
||
// Redis:仅当配置了 host 时校验
|
||
if strings.TrimSpace(c.Redis.Host) != "" {
|
||
if c.Redis.Port <= 0 || c.Redis.Port > 65535 {
|
||
problems = append(problems, fmt.Sprintf("redis.port 超出范围(1-65535): %d", c.Redis.Port))
|
||
}
|
||
}
|
||
|
||
// Trace:仅当启用时校验采样比例。ExporterType/Propagator 的枚举校验委托 trace.Init
|
||
// (随 OTel 版本可能扩展,避免 config 与 trace 双源枚举漂移)。
|
||
if c.Trace.Enabled {
|
||
if c.Trace.SampleRatio < 0 || c.Trace.SampleRatio > 1 {
|
||
problems = append(problems, fmt.Sprintf("trace.sample_ratio 超出范围(0.0-1.0): %v", c.Trace.SampleRatio))
|
||
}
|
||
}
|
||
|
||
if len(problems) > 0 {
|
||
return fmt.Errorf("配置校验失败: %s", strings.Join(problems, "; "))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// validDuration 校验 Duration 非负(0 表示未配置/用默认,合法)。
|
||
func validDuration(d time.Duration) bool { return d >= 0 }
|
||
|
||
// validPostgresSSLMode 校验 PostgreSQL sslmode 取值(M-config-2)。
|
||
func validPostgresSSLMode(s string) bool {
|
||
switch s {
|
||
case "disable", "allow", "prefer", "require", "verify-ca", "verify-full":
|
||
return true
|
||
}
|
||
return false
|
||
}
|