package config import ( "log" "net" "os" "strings" "time" ftsService "bsm/full/module/base/fts/service" mgtService "bsm/full/module/base/mgt/service" passportService "bsm/full/module/base/passport/service" senderService "bsm/full/module/base/sender/service" walletService "bsm/full/module/finance/wallet/service" "git.apinb.com/bsm-sdk/core/conf" "git.apinb.com/bsm-sdk/core/env" "git.apinb.com/bsm-sdk/core/printer" coreVars "git.apinb.com/bsm-sdk/core/vars" ) // SrvConfig is the ecmall aggregate configuration. It only carries the // services registered by pkgs/ecmall: the e-commerce domain (address, mall, // market, order), the payment domain (wallet), the platform domain (ads, cms, // feedback, fts, logs, mgt) and the base support services they rely on // (initial, passport, sender). Modules that are not part of ecmall keep their // own configuration in their own process. type SrvConfig struct { conf.Base `yaml:",inline"` Server ServerConfig `yaml:"Server"` Authorization AuthorizationConfig `yaml:"Authorization"` Databases *conf.DBConf `yaml:"Databases"` MicroService *conf.MicroServiceConf `yaml:"MicroService"` Rpc map[string]conf.RpcConf `yaml:"Rpc"` Apm *conf.ApmConf `yaml:"APM"` Etcd *conf.EtcdConf `yaml:"Etcd"` Services []string `yaml:"Services"` Fts *ftsService.Config `yaml:"Fts"` Mgt *mgtService.Config `yaml:"Mgt"` Passport *passportService.Config `yaml:"Passport"` Sender *senderService.Config `yaml:"Sender"` Wallet *walletService.Config `yaml:"Wallet"` } type ListenerConfig struct { BindIP string `yaml:"BindIP"` Port string `yaml:"Port"` Addr string `yaml:"-"` } type ServerConfig struct { GRPC ListenerConfig `yaml:"GRPC"` HTTP ListenerConfig `yaml:"HTTP"` } type AuthorizationConfig struct { Key string `yaml:"Key"` Expire int64 `yaml:"Expire"` Anonymous []string `yaml:"Anonymous"` } var Spec SrvConfig // publicJwtSecretKeys 是仓库中出现过的公开 JWT 密钥字面量,绝不允许被当作有效签名密钥使用: // - CHANGE_ME_32_BYTE_JWT_SECRET_KEY:历史样例值(恰好 32 字节,能通过长度校验); // - Cblocksmesh2022C:bsm-sdk env.NewEnv() 的内置默认值(SDK 不可改,只能在此拒绝)。 var publicJwtSecretKeys = map[string]struct{}{ "CHANGE_ME_32_BYTE_JWT_SECRET_KEY": {}, "Cblocksmesh2022C": {}, } // publicSessionSecret 是 session cookie HMAC 密钥的公开占位值。 const publicSessionSecret = "CHANGE_ME" func New(serviceKey string) { conf.New(serviceKey, &Spec) normalizeListener(&Spec.Server.GRPC) normalizeListener(&Spec.Server.HTTP) if Spec.Server.GRPC.Addr == Spec.Server.HTTP.Addr { panic("gRPC and HTTP listeners must use different addresses") } // JWT 是全系统身份凭据,签名密钥必须由部署显式提供:优先取 BSM_JwtSecretKey 环境变量, // 空值与公开样例值一律拒绝启动,避免默认配置直接上线后被用于伪造任意身份。 if envKey := strings.TrimSpace(os.Getenv("BSM_JwtSecretKey")); envKey != "" { Spec.Authorization.Key = envKey } Spec.Authorization.Key = strings.TrimSpace(Spec.Authorization.Key) if Spec.Authorization.Key == "" { log.Fatalln("ERROR: JWT secret is not configured; provide it through the BSM_JwtSecretKey environment variable") } if _, isPublic := publicJwtSecretKeys[Spec.Authorization.Key]; isPublic { log.Fatalln("ERROR: JWT secret is a public sample value; provide a private key through the BSM_JwtSecretKey environment variable") } keyLength := len(Spec.Authorization.Key) if keyLength != 16 && keyLength != 24 && keyLength != 32 { log.Fatalln("ERROR: JWT secret must contain 16, 24, or 32 bytes") } if Spec.Authorization.Expire <= 0 { panic("Authorization.Expire must be greater than zero") } // session cookie 的 HMAC 密钥同样不得为空或公开占位值。 sessionSecret := strings.TrimSpace(Spec.SecretKey) if sessionSecret == "" || sessionSecret == publicSessionSecret { log.Fatalln("ERROR: SecretKey must not be empty or a public placeholder; provide a private secret through the BSM_SECRET_KEY environment variable") } env.NewEnv().JwtSecretKey = Spec.Authorization.Key coreVars.JwtExpire = time.Duration(Spec.Authorization.Expire) * time.Second // Keep the embedded base address meaningful for module configurations that // still consume it, while ecmall itself uses the two explicit listeners. Spec.BindIP, Spec.Port, Spec.Addr = Spec.Server.HTTP.BindIP, Spec.Server.HTTP.Port, Spec.Server.HTTP.Addr conf.NotNil(Spec.Service, Spec.Cache) assignSharedConfig() conf.PrintInfo(Spec.Server.GRPC.Addr) printer.Info("gRPC Address: %s", Spec.Server.GRPC.Addr) printer.Info("HTTP Address: %s", Spec.Server.HTTP.Addr) } func normalizeListener(listener *ListenerConfig) { listener.Port = conf.CheckPort(listener.Port) listener.BindIP = conf.CheckIP(listener.BindIP) listener.Addr = net.JoinHostPort(listener.BindIP, listener.Port) } func assignSharedConfig() { if Spec.Fts != nil { Spec.Fts.Base, Spec.Fts.Databases, Spec.Fts.Rpc, Spec.Fts.Apm, Spec.Fts.Etcd = Spec.Base, Spec.Databases, Spec.Rpc, Spec.Apm, Spec.Etcd } if Spec.Mgt != nil { Spec.Mgt.Base, Spec.Mgt.Databases, Spec.Mgt.Rpc, Spec.Mgt.Apm, Spec.Mgt.Etcd = Spec.Base, Spec.Databases, Spec.Rpc, Spec.Apm, Spec.Etcd } if Spec.Passport != nil { Spec.Passport.Base, Spec.Passport.Databases, Spec.Passport.MicroService, Spec.Passport.Rpc, Spec.Passport.Apm, Spec.Passport.Etcd = Spec.Base, Spec.Databases, Spec.MicroService, Spec.Rpc, Spec.Apm, Spec.Etcd } if Spec.Sender != nil { Spec.Sender.Base, Spec.Sender.Databases, Spec.Sender.MicroService, Spec.Sender.Rpc, Spec.Sender.Apm, Spec.Sender.Etcd = Spec.Base, Spec.Databases, Spec.MicroService, Spec.Rpc, Spec.Apm, Spec.Etcd } if Spec.Wallet != nil { Spec.Wallet.Base, Spec.Wallet.Databases, Spec.Wallet.MicroService, Spec.Wallet.Rpc, Spec.Wallet.Apm, Spec.Wallet.Etcd = Spec.Base, Spec.Databases, Spec.MicroService, Spec.Rpc, Spec.Apm, Spec.Etcd } } func Enabled(name string) bool { if raw := strings.TrimSpace(os.Getenv("BSM_SERVICES")); raw != "" { for _, selected := range strings.Split(raw, ",") { if strings.EqualFold(strings.TrimSpace(selected), name) || strings.EqualFold(strings.TrimSpace(selected), "all") { return true } } return false } for _, service := range Spec.Services { if strings.EqualFold(strings.TrimSpace(service), name) || strings.EqualFold(strings.TrimSpace(service), "all") { return true } } return false }