Files
full/pkgs/ecmall/internal/config/config.go
2026-09-22 21:15:34 +08:00

157 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 字节,能通过长度校验);
// - Cblocksmesh2022Cbsm-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
}