refactor(config): 添加 ServerMode 类型和多服务器模式支持

- 新增 ServerMode 枚举类型 (single/vhost/multi_server/auto)
- 实现 migrateLegacyConfig() 自动迁移旧版 server 配置到 servers[0]
- 添加 validateMode() 和 validateListenConflicts() 验证函数
- 配置加载时自动迁移,保持向后兼容

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-15 10:33:40 +08:00
parent 11956a0f83
commit 6007e96f69
2 changed files with 162 additions and 10 deletions

View File

@ -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)

View File

@ -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) {