From 6007e96f69b2d1b47eacbb907a34ac6de9a10d59 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 15 Apr 2026 10:33:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor(config):=20=E6=B7=BB=E5=8A=A0=20Server?= =?UTF-8?q?Mode=20=E7=B1=BB=E5=9E=8B=E5=92=8C=E5=A4=9A=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ServerMode 枚举类型 (single/vhost/multi_server/auto) - 实现 migrateLegacyConfig() 自动迁移旧版 server 配置到 servers[0] - 添加 validateMode() 和 validateListenConflicts() 验证函数 - 配置加载时自动迁移,保持向后兼容 Co-Authored-By: Claude Opus 4.6 --- internal/config/config.go | 119 +++++++++++++++++++++++++++++++++--- internal/config/validate.go | 53 ++++++++++++++++ 2 files changed, 162 insertions(+), 10 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index ec09fc8..5859460 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,6 +33,23 @@ const ( DefaultPprofPath = "/debug/pprof" ) +// ServerMode 服务器运行模式类型。 +// +// 定义服务器的工作模式,支持显式配置或自动推断。 +type ServerMode string + +// ServerMode 枚举值。 +const ( + // ServerModeSingle 单服务器模式 - 只运行一个服务器实例。 + ServerModeSingle ServerMode = "single" + // ServerModeVHost 虚拟主机模式 - 多个服务器共享相同的监听地址。 + ServerModeVHost ServerMode = "vhost" + // ServerModeMultiServer 多服务器模式 - 多个服务器监听不同的地址。 + ServerModeMultiServer ServerMode = "multi_server" + // ServerModeAuto 自动模式 - 根据配置自动推断运行模式。 + ServerModeAuto ServerMode = "auto" +) + // Config 根配置结构,支持单服务器和多虚拟主机两种模式。 // // 包含服务器配置、日志配置、性能配置和监控配置等模块。 @@ -56,6 +73,7 @@ const ( // // 处理每个服务器配置 // } type Config struct { + Mode ServerMode `yaml:"mode"` Variables VariablesConfig `yaml:"variables"` Logging LoggingConfig `yaml:"logging"` Servers []ServerConfig `yaml:"servers"` @@ -1573,6 +1591,7 @@ type StreamProxySSLConfig struct { // 注意事项: // - 加载后会自动调用 Validate 进行配置验证 // - 文件不存在或格式错误都会返回错误 +// - 自动迁移旧版配置(server 转换为 servers[0]) func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { @@ -1584,6 +1603,9 @@ func Load(path string) (*Config, error) { return nil, fmt.Errorf("解析配置文件失败: %w", err) } + // 迁移旧版配置 + cfg.migrateLegacyConfig() + if err := Validate(&cfg); err != nil { return nil, fmt.Errorf("配置验证失败: %w", err) } @@ -1604,12 +1626,16 @@ func Load(path string) (*Config, error) { // // 注意事项: // - 加载后会自动调用 Validate 进行配置验证 +// - 自动迁移旧版配置(server 转换为 servers[0]) func LoadFromString(yamlStr string) (*Config, error) { var cfg Config if err := yaml.Unmarshal([]byte(yamlStr), &cfg); err != nil { return nil, fmt.Errorf("解析配置失败: %w", err) } + // 迁移旧版配置 + cfg.migrateLegacyConfig() + if err := Validate(&cfg); err != nil { return nil, fmt.Errorf("配置验证失败: %w", err) } @@ -1643,6 +1669,25 @@ func Save(cfg *Config, path string) error { return nil } +// migrateLegacyConfig 迁移旧版配置到新版。 +// +// 将旧版 server 配置自动转换为 servers[0] 格式,实现向后兼容。 +// 如果 server 有配置但 servers 为空,则将 server 添加到 servers[0]。 +// +// 注意事项: +// - 仅在检测到旧版配置时输出警告日志 +// - 迁移后清空 server 字段,统一使用 servers 格式 +// - 如果同时配置了 server 和 servers,忽略 server 配置 +func (c *Config) migrateLegacyConfig() { + // 如果配置了旧版 server 且 servers 为空,进行迁移 + if c.Server.Listen != "" && len(c.Servers) == 0 { + fmt.Fprintf(os.Stderr, "[警告] 使用旧版配置格式 server,已自动迁移到 servers[0]。请将配置更新为 servers 格式。\n") + c.Servers = []ServerConfig{c.Server} + // 清空旧版配置,避免混淆 + c.Server = ServerConfig{} + } +} + // HasServers 检查是否为多虚拟主机模式。 // // 返回值: @@ -1672,6 +1717,57 @@ func (c *Config) GetDefaultServer() *ServerConfig { return nil } +// GetMode 获取服务器运行模式。 +// +// 如果 Mode 显式设置(非 auto),返回设置的值。 +// 如果 Mode 是 auto 或未设置,根据配置自动推断: +// - servers 数量 == 1 → single +// - servers 数量 > 1 且所有 listen 地址相同 → vhost +// - servers 数量 > 1 且 listen 地址不同 → multi_server +// - servers 为空但 server 有配置 → single(兼容旧配置) +// +// 返回值: +// - ServerMode: 推断后的服务器运行模式 +func (c *Config) GetMode() ServerMode { + // 如果显式设置了非 auto 模式,直接返回 + if c.Mode != "" && c.Mode != ServerModeAuto { + return c.Mode + } + + // 自动推断模式 + serverCount := len(c.Servers) + + // servers 为空但 server 有配置 → single(兼容旧配置) + if serverCount == 0 { + if c.HasDefaultServer() { + return ServerModeSingle + } + // 理论上不会到达这里(配置验证会确保至少有一个服务器) + return ServerModeAuto + } + + // servers 数量 == 1 → single + if serverCount == 1 { + return ServerModeSingle + } + + // servers 数量 > 1,检查 listen 地址 + firstListen := c.Servers[0].Listen + allSameListen := true + for i := 1; i < serverCount; i++ { + if c.Servers[i].Listen != firstListen { + allSameListen = false + break + } + } + + // 所有 listen 地址相同 → vhost,否则 → multi_server + if allSameListen { + return ServerModeVHost + } + return ServerModeMultiServer +} + // Validate 配置验证入口。 // // 验证配置的完整性和有效性,检查是否至少配置了一个服务器, @@ -1684,22 +1780,25 @@ func (c *Config) GetDefaultServer() *ServerConfig { // - error: 验证失败时的错误信息,包含具体字段路径 // // 验证规则: -// - 必须配置 server 或 servers 中的至少一个 +// - 必须配置 servers 数组且至少包含一个服务器 // - 所有服务器配置必须通过 validateServer 验证 func Validate(cfg *Config) error { - // 至少需要一种服务器配置 - if !cfg.HasDefaultServer() && !cfg.HasServers() { - return errors.New("至少需要配置 server 或 servers") + // 必须配置 servers 且至少包含一个服务器 + if !cfg.HasServers() { + return errors.New("必须配置 servers 且至少包含一个服务器") } - // 验证默认服务器 - if cfg.HasDefaultServer() { - if err := validateServer(&cfg.Server, true); err != nil { - return err - } + // 验证模式 + if err := validateMode(cfg.Mode); err != nil { + return err } - // 验证所有虚拟主机 + // 验证监听地址冲突(multi_server 模式) + if err := validateListenConflicts(cfg.Servers, cfg.GetMode()); err != nil { + return err + } + + // 验证所有服务器 for i := range cfg.Servers { if err := validateServer(&cfg.Servers[i], false); err != nil { return fmt.Errorf("servers[%d]: %w", i, err) diff --git a/internal/config/validate.go b/internal/config/validate.go index 6c7d0e4..f86b6d8 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -28,6 +28,59 @@ import ( "rua.plus/lolly/internal/variable" ) +// validateMode 验证服务器运行模式有效值。 +// +// 检查 Mode 是否为 ServerModeSingle, ServerModeVHost, +// ServerModeMultiServer, ServerModeAuto 之一。 +// 空值视为 auto(合法)。 +// +// 参数: +// - mode: 服务器运行模式 +// +// 返回值: +// - error: 验证失败时返回错误信息,成功返回 nil +func validateMode(mode ServerMode) error { + if mode == "" || mode == ServerModeAuto { + return nil + } + validModes := []string{ + string(ServerModeSingle), + string(ServerModeVHost), + string(ServerModeMultiServer), + string(ServerModeAuto), + } + return ValidateEnum(string(mode), validModes, "mode") +} + +// validateListenConflicts 检测 servers 中监听地址冲突。 +// +// 在 multi_server 模式下,每个 server 必须有 listen 配置且不能重复。 +// 收集所有 servers[i].Listen,检查重复项并输出具体冲突信息。 +// +// 参数: +// - servers: 服务器配置列表 +// - mode: 服务器运行模式 +// +// 返回值: +// - error: 发现冲突或缺失时返回错误信息,成功返回 nil +func validateListenConflicts(servers []ServerConfig, mode ServerMode) error { + if mode != ServerModeMultiServer { + return nil + } + + seen := make(map[string]int) + for i, s := range servers { + if s.Listen == "" { + return fmt.Errorf("servers[%d]: multi_server 模式下每个 server 必须配置 listen 地址", i) + } + if idx, exists := seen[s.Listen]; exists { + return fmt.Errorf("监听地址冲突: servers[%d] 和 servers[%d] 都使用 %s", idx, i, s.Listen) + } + seen[s.Listen] = i + } + return nil +} + // ValidateEnum 验证值是否在有效枚举列表中 func ValidateEnum(value string, validValues []string, fieldName string) error { if slices.Contains(validValues, value) {