package config import ( "log" "net" "regexp" "strconv" "strings" "git.apinb.com/bsm-sdk/core/conf" ) var ( Spec SrvConfig configNameRegexp = regexp.MustCompile(`^[a-z0-9-]+$`) ) type SrvConfig struct { conf.Base `yaml:",inline"` Databases *conf.DBConf `yaml:"Databases"` MicroService *conf.MicroServiceConf `yaml:"MicroService"` Etcd *conf.EtcdConf `yaml:"Etcd"` ObjectStorage ObjectStorageConf `yaml:"ObjectStorage"` Namespaces map[string]NamespaceConf `yaml:"Namespaces"` ServiceClients map[string]string `yaml:"ServiceClients"` Cleanup CleanupConf `yaml:"Cleanup"` } type ObjectStorageConf struct { Provider string `yaml:"Provider"` Endpoint string `yaml:"Endpoint"` Region string `yaml:"Region"` Bucket string `yaml:"Bucket"` PublicBaseURL string `yaml:"PublicBaseURL"` AccessKeyID string `yaml:"AccessKeyID"` AccessKeySecret string `yaml:"AccessKeySecret"` PresignTTLSeconds int64 `yaml:"PresignTTLSeconds"` } type NamespaceConf struct { Prefix string `yaml:"Prefix"` MaxSizeMB int64 `yaml:"MaxSizeMB"` AllowedExtensions []string `yaml:"AllowedExtensions"` } type CleanupConf struct { IntervalSeconds int64 `yaml:"IntervalSeconds"` } func New(srvKey string) { conf.New(srvKey, &Spec) Spec.Port = conf.CheckPort(Spec.Port) Spec.BindIP = conf.CheckIP(Spec.BindIP) Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port) validate() conf.PrintInfo(Spec.Addr) } func validate() { validateRequired("Service", Spec.Service) validateRequired("Cache", Spec.Cache) if Spec.ObjectStorage.Provider != "aliyun" { configError("ObjectStorage.Provider", "必须为 aliyun") } validateRequired("ObjectStorage.Endpoint", Spec.ObjectStorage.Endpoint) validateRequired("ObjectStorage.Region", Spec.ObjectStorage.Region) validateRequired("ObjectStorage.Bucket", Spec.ObjectStorage.Bucket) validateRequired("ObjectStorage.PublicBaseURL", Spec.ObjectStorage.PublicBaseURL) validateRequired("ObjectStorage.AccessKeyID", Spec.ObjectStorage.AccessKeyID) validateRequired("ObjectStorage.AccessKeySecret", Spec.ObjectStorage.AccessKeySecret) if Spec.ObjectStorage.PresignTTLSeconds < 60 || Spec.ObjectStorage.PresignTTLSeconds > 3600 { configError("ObjectStorage.PresignTTLSeconds", "必须在 60 到 3600 秒之间") } if len(Spec.Namespaces) == 0 { configError("Namespaces", "不能为空") } for name, namespace := range Spec.Namespaces { path := "Namespaces." + name if !configNameRegexp.MatchString(name) { configError(path, "名称仅允许小写字母、数字和连字符") } if !isSafeRelativeObjectPath(namespace.Prefix) { configError(path+".Prefix", "必须是安全的相对对象路径") } if namespace.MaxSizeMB <= 0 { configError(path+".MaxSizeMB", "必须大于 0") } if len(namespace.AllowedExtensions) == 0 { configError(path+".AllowedExtensions", "不能为空") } for index, extension := range namespace.AllowedExtensions { extensionPath := path + ".AllowedExtensions[" + stringIndex(index) + "]" if !strings.HasPrefix(extension, ".") { configError(extensionPath, "必须以 . 开头") } namespace.AllowedExtensions[index] = strings.ToLower(extension) } Spec.Namespaces[name] = namespace } if len(Spec.ServiceClients) == 0 { configError("ServiceClients", "不能为空") } for name, secret := range Spec.ServiceClients { path := "ServiceClients." + name if !configNameRegexp.MatchString(name) { configError(path, "服务名仅允许小写字母、数字和连字符") } validateRequired(path, secret) } if Spec.Cleanup.IntervalSeconds <= 0 { configError("Cleanup.IntervalSeconds", "必须大于 0") } } func validateRequired(path, value string) { if strings.TrimSpace(value) == "" { configError(path, "不能为空") } } func isSafeRelativeObjectPath(path string) bool { if path == "" || strings.HasPrefix(path, "/") || strings.Contains(path, "\\") { return false } for _, segment := range strings.Split(path, "/") { if segment == "." || segment == ".." { return false } } return true } func configError(path, message string) { log.Fatalf("配置项 %s 无效:%s", path, message) } func stringIndex(index int) string { return strconv.Itoa(index) }