This commit is contained in:
2025-11-14 12:11:44 +08:00
commit 39ebf61572
88 changed files with 9999 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
package config
import (
"errors"
"fmt"
"net/url"
"strings"
"time"
)
var supportedHubTypes = map[string]struct{}{
"docker": {},
"npm": {},
"go": {},
}
const supportedHubTypeList = "docker|npm|go"
// Validate 针对语义级别做进一步校验,防止非法配置启动服务。
func (c *Config) Validate() error {
if c == nil {
return errors.New("配置为空")
}
g := c.Global
if g.ListenPort <= 0 || g.ListenPort > 65535 {
return newFieldError("Global.ListenPort", "必须在 1-65535")
}
if g.StoragePath == "" {
return newFieldError("Global.StoragePath", "不能为空")
}
if g.CacheTTL.DurationValue() <= 0 {
return newFieldError("Global.CacheTTL", "必须大于 0")
}
if g.MaxMemoryCache <= 0 {
return newFieldError("Global.MaxMemoryCacheSize", "必须大于 0")
}
if g.MaxRetries < 0 {
return newFieldError("Global.MaxRetries", "不能为负数")
}
if g.InitialBackoff.DurationValue() <= 0 {
return newFieldError("Global.InitialBackoff", "必须大于 0")
}
if g.UpstreamTimeout.DurationValue() <= 0 {
return newFieldError("Global.UpstreamTimeout", "必须大于 0")
}
if len(c.Hubs) == 0 {
return errors.New("至少需要配置一个 Hub")
}
seenNames := map[string]struct{}{}
for i := range c.Hubs {
hub := &c.Hubs[i]
if hub.Name == "" {
return newFieldError("Hub[].Name", "不能为空")
}
if _, exists := seenNames[hub.Name]; exists {
return newFieldError(hubField(hub.Name, "Name"), "重复")
}
seenNames[hub.Name] = struct{}{}
if err := validateDomain(hub.Domain); err != nil {
return fmt.Errorf("%s: %w", hubField(hub.Name, "Domain"), err)
}
normalizedType := strings.ToLower(strings.TrimSpace(hub.Type))
if normalizedType == "" {
return newFieldError(hubField(hub.Name, "Type"), "不能为空")
}
if _, ok := supportedHubTypes[normalizedType]; !ok {
return newFieldError(hubField(hub.Name, "Type"), "仅支持 "+supportedHubTypeList)
}
hub.Type = normalizedType
if (hub.Username == "") != (hub.Password == "") {
return newFieldError(hubField(hub.Name, "Username/Password"), "必须同时提供或同时留空")
}
if err := validateUpstream(hub.Upstream); err != nil {
return fmt.Errorf("%s: %w", hubField(hub.Name, "Upstream"), err)
}
if hub.Proxy != "" {
if err := validateUpstream(hub.Proxy); err != nil {
return fmt.Errorf("%s: %w", hubField(hub.Name, "Proxy"), err)
}
}
}
return nil
}
func validateDomain(domain string) error {
if domain == "" {
return errors.New("Domain 不能为空")
}
if strings.Contains(domain, "/") {
return errors.New("Domain 不允许包含路径")
}
if strings.Contains(domain, " ") {
return errors.New("Domain 不允许包含空格")
}
if strings.HasPrefix(domain, "http") {
return errors.New("Domain 不应包含协议头")
}
return nil
}
func validateUpstream(raw string) error {
if raw == "" {
return errors.New("缺少上游地址")
}
parsed, err := url.Parse(raw)
if err != nil {
return err
}
if parsed.Scheme != "http" && parsed.Scheme != "https" {
return fmt.Errorf("仅支持 http/https上游: %s", raw)
}
if parsed.Host == "" {
return fmt.Errorf("上游缺少 Host: %s", raw)
}
return nil
}
// EffectiveCacheTTL 返回特定 Hub 生效的 TTL未覆盖时回退至全局值。
func (c *Config) EffectiveCacheTTL(h HubConfig) time.Duration {
if h.CacheTTL.DurationValue() > 0 {
return h.CacheTTL.DurationValue()
}
return c.Global.CacheTTL.DurationValue()
}