147
internal/config/config.go
Normal file
147
internal/config/config.go
Normal file
@@ -0,0 +1,147 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user