Files
jiu/backend/config/config.go
T
wangjia 53fa259284
Deploy Server / release-deploy-server (push) Successful in 40s
chore: release server-v1.0.56
会话/设备管理后端:user_sessions 会话表、JWT 加 sid 校验、按平台类限并发登录、
登录失败锁定、修复禁用账号仍可凭 refresh 续期漏洞、/auth/ping、/auth/logout、
GET /sessions、DELETE /sessions/:id(管理员强制下线)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 23:12:13 +08:00

108 lines
4.1 KiB
Go

package config
import (
"log"
"strings"
"github.com/spf13/viper"
)
type Config struct {
Server ServerConfig
Database DatabaseConfig
JWT JWTConfig
License LicenseConfig
Storage StorageConfig
Session SessionConfig
}
type ServerConfig struct {
Port string `mapstructure:"port"`
Mode string `mapstructure:"mode"` // debug | release
CORSOrigin string `mapstructure:"cors_origin"` // 允许的 CORS 来源,生产设为具体域名
}
type DatabaseConfig struct {
DSN string `mapstructure:"dsn"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
MaxOpenConns int `mapstructure:"max_open_conns"`
}
type JWTConfig struct {
Secret string `mapstructure:"secret"`
AccessExpireMin int `mapstructure:"access_expire_min"` // Access Token 有效分钟数
RefreshExpireH int `mapstructure:"refresh_expire_h"` // Refresh Token 有效小时数
}
type LicenseConfig struct {
HMACSecret string `mapstructure:"hmac_secret"` // legacy, kept for backward compat
Ed25519PublicKey string `mapstructure:"ed25519_public_key"` // base64 Ed25519 public key for token verification
Ed25519PrivateKey string `mapstructure:"ed25519_private_key"` // base64 Ed25519 private key for token signing (keep in Bitwarden)
}
// SessionConfig 登录会话与并发限制(全局默认,可被每店 session_policy 覆盖)。
type SessionConfig struct {
LimitDesktop int `mapstructure:"limit_desktop"` // 桌面端(win/mac/linux)最大并发会话,0=禁止
LimitMobile int `mapstructure:"limit_mobile"` // 移动端(android/ios)最大并发会话,0=禁止
LimitWeb int `mapstructure:"limit_web"` // web 端最大并发会话,0=禁止
MaxFailures int `mapstructure:"max_failures"` // 连续登录失败几次后锁定
LockMinutes int `mapstructure:"lock_minutes"` // 锁定时长(分钟)
}
type StorageConfig struct {
UploadDir string `mapstructure:"upload_dir"`
BaseURL string `mapstructure:"base_url"`
PublicURL string `mapstructure:"public_url"` // 商品公开页基础 URL,用于生成二维码
WebDir string `mapstructure:"web_dir"` // Flutter web 构建产物目录,用于 OG 标签注入
}
var C Config
func Load() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("./config")
// 环境变量覆盖(生产部署时使用)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// 显式绑定没有默认值的 key,确保 AutomaticEnv 能找到对应 env var
_ = viper.BindEnv("database.dsn", "DATABASE_DSN")
_ = viper.BindEnv("jwt.secret", "JWT_SECRET")
_ = viper.BindEnv("license.hmac_secret", "LICENSE_HMAC_SECRET")
_ = viper.BindEnv("license.ed25519_public_key", "LICENSE_ED25519_PUBLIC_KEY")
_ = viper.BindEnv("license.ed25519_private_key", "LICENSE_ED25519_PRIVATE_KEY")
_ = viper.BindEnv("storage.upload_dir", "STORAGE_UPLOAD_DIR")
_ = viper.BindEnv("storage.base_url", "STORAGE_BASE_URL")
_ = viper.BindEnv("storage.public_url", "STORAGE_PUBLIC_URL")
_ = viper.BindEnv("storage.web_dir", "STORAGE_WEB_DIR")
// 默认值
viper.SetDefault("server.port", "8080")
viper.SetDefault("server.mode", "debug")
viper.SetDefault("server.cors_origin", "*")
viper.SetDefault("jwt.access_expire_min", 60)
viper.SetDefault("jwt.refresh_expire_h", 168) // 7天
viper.SetDefault("session.limit_desktop", 2)
viper.SetDefault("session.limit_mobile", 2)
viper.SetDefault("session.limit_web", 2) // 默认不禁 web(官网挂着 Web 版 app);设 0 可禁
viper.SetDefault("session.max_failures", 5)
viper.SetDefault("session.lock_minutes", 15)
viper.SetDefault("database.max_idle_conns", 10)
viper.SetDefault("database.max_open_conns", 100)
viper.SetDefault("storage.upload_dir", "./uploads/images")
viper.SetDefault("storage.base_url", "http://localhost:8080/images")
viper.SetDefault("storage.public_url", "http://localhost:8081")
viper.SetDefault("storage.web_dir", "./web")
if err := viper.ReadInConfig(); err != nil {
log.Println("[config] no config file found, using defaults and env vars")
}
if err := viper.Unmarshal(&C); err != nil {
log.Fatalf("[config] failed to unmarshal config: %v", err)
}
}