diff --git a/configs/config.example.ini b/configs/config.example.ini index 947b029..1ce445b 100644 --- a/configs/config.example.ini +++ b/configs/config.example.ini @@ -28,6 +28,8 @@ path = data/baihu.db dsn = # 表前缀 table_prefix = baihu_ +# SSL 模式 (仅 mysql/postgres): postgres 支持 disable/require/verify-ca/verify-full; mysql 支持 true/skip-verify +# ssl_mode = disable [security] # JWT 密钥。留空则在首次启动时自动生成并保存到数据库设置中。 diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index fb42917..f6e58b0 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -22,6 +22,7 @@ | `BH_DB_PATH` | database.path | SQLite 物理文件存储路径 | ./data/baihu.db | | `BH_DB_DSN` | database.dsn | 数据库 DSN (仅 mysql/postgres, 优先级高。**需对应设置 type**) | - | | `BH_DB_TABLE_PREFIX` | database.table_prefix | 数据库表前缀 | baihu_ | +| `BH_DB_SSL_MODE` | database.ssl_mode | SSL 模式: postgres 支持 disable/require/verify-ca/verify-full; mysql 支持 true/skip-verify | - | | `BAIHU_SECRET_KEY` | - | 系统加密秘钥,用于机密变量功能(**注:仅支持环境变量设置,不支持配置文件**) | - | --- diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 1d593de..e6724ef 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -107,6 +107,7 @@ func (a *App) initDatabase() { DBName: a.Config.Database.DBName, Path: a.Config.Database.Path, DSN: a.Config.Database.DSN, + SSLMode: a.Config.Database.SSLMode, } if err := database.Init(dbCfg); err != nil { diff --git a/internal/constant/constant.go b/internal/constant/constant.go index bafdc49..34da6ff 100644 --- a/internal/constant/constant.go +++ b/internal/constant/constant.go @@ -193,6 +193,7 @@ var ( RuntimeDBPath string RuntimeDBDSN string RuntimeDBTablePrefix string + RuntimeDBSSLMode string ) // Secret JWT和密码salt密钥,运行中自动从数据库加载 diff --git a/internal/database/database.go b/internal/database/database.go index 5eeea33..a012e6c 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -28,6 +28,7 @@ type Config struct { DBName string Path string // for sqlite DSN string // for mysql/mariadb unix socket or custom dsn + SSLMode string // postgres: disable/require/verify-ca/verify-full; mysql: true/skip-verify } func Init(cfg *Config) error { @@ -37,24 +38,19 @@ func Init(cfg *Config) error { loc := systime.CST time.Local = loc + dsn, err := buildDSN(cfg) + if err != nil { + return err + } + var dialector gorm.Dialector switch cfg.Type { case "sqlite": - dialector = sqlite.Open(cfg.Path + "?_busy_timeout=5000") + dialector = sqlite.Open(dsn) case "mysql": - dsn := cfg.DSN - if dsn == "" { - dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Asia%%2FShanghai", - cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName) - } dialector = mysql.Open(dsn) case "postgres": - dsn := cfg.DSN - if dsn == "" { - dsn = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai", - cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName) - } dialector = postgres.Open(dsn) default: return fmt.Errorf("unsupported database type: %s", cfg.Type) @@ -102,3 +98,33 @@ func AutoMigrate(models ...interface{}) error { func GetDB() *gorm.DB { return DB } + +func buildDSN(cfg *Config) (string, error) { + switch cfg.Type { + case "sqlite": + return cfg.Path + "?_busy_timeout=5000", nil + case "mysql": + if cfg.DSN != "" { + return cfg.DSN, nil + } + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Asia%%2FShanghai", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName) + if cfg.SSLMode != "" { + dsn += "&tls=" + cfg.SSLMode + } + return dsn, nil + case "postgres": + if cfg.DSN != "" { + return cfg.DSN, nil + } + sslMode := cfg.SSLMode + if sslMode == "" { + sslMode = "disable" + } + dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s TimeZone=Asia/Shanghai", + cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, sslMode) + return dsn, nil + default: + return "", fmt.Errorf("unsupported database type: %s", cfg.Type) + } +} diff --git a/internal/services/config_service.go b/internal/services/config_service.go index 1c062b1..f9d49b3 100644 --- a/internal/services/config_service.go +++ b/internal/services/config_service.go @@ -28,6 +28,7 @@ type DatabaseConfig struct { Path string `ini:"path"` DSN string `ini:"dsn"` TablePrefix string `ini:"table_prefix"` + SSLMode string `ini:"ssl_mode"` } type SecurityConfig struct { @@ -135,6 +136,7 @@ func LoadConfig(path string) (*AppConfig, error) { constant.RuntimeDBPath = Config.Database.Path constant.RuntimeDBDSN = Config.Database.DSN constant.RuntimeDBTablePrefix = Config.Database.TablePrefix + constant.RuntimeDBSSLMode = Config.Database.SSLMode // 暂存旧的 Secret,不再直接给 constant 赋值(改为到 settings 初始化时判断) // constant.Secret = Config.Security.Secret @@ -178,6 +180,7 @@ func applyEnvOverrides() { getEnvStr("BH_DB_PATH", &Config.Database.Path) getEnvStr("BH_DB_DSN", &Config.Database.DSN) getEnvStr("BH_DB_TABLE_PREFIX", &Config.Database.TablePrefix) + getEnvStr("BH_DB_SSL_MODE", &Config.Database.SSLMode) // Security getEnvStr("BH_SECRET", &Config.Security.Secret) diff --git a/internal/utils/runtime_env.go b/internal/utils/runtime_env.go index 82ceb0e..1cd7198 100644 --- a/internal/utils/runtime_env.go +++ b/internal/utils/runtime_env.go @@ -35,6 +35,7 @@ func BuildRuntimeProcessEnv() []string { appendEnvIfSet(&envs, "BH_DB_PATH", constant.RuntimeDBPath) appendEnvIfSet(&envs, "BH_DB_DSN", constant.RuntimeDBDSN) appendEnvIfSet(&envs, "BH_DB_TABLE_PREFIX", constant.RuntimeDBTablePrefix) + appendEnvIfSet(&envs, "BH_DB_SSL_MODE", constant.RuntimeDBSSLMode) return envs }