feat: 数据库配置将 MySQL DSN 拆分为独立字段(db_name/username/password/host/port)
This commit is contained in:
5 files changed
+76
-12
No files matched your search
@@ -60,7 +60,11 @@ go run .
|
||||
# config.yaml
|
||||
database:
|
||||
type: sqlite # sqlite(默认)或 mysql
|
||||
dsn: "" # MySQL 连接串,sqlite 模式下忽略
|
||||
db_name: "" # MySQL 数据库名,sqlite 模式下忽略
|
||||
username: "" # MySQL 用户名,sqlite 模式下忽略
|
||||
password: "" # MySQL 密码,sqlite 模式下忽略
|
||||
host: "" # MySQL IP 或主机名,sqlite 模式下忽略
|
||||
port: "" # MySQL 端口,sqlite 模式下忽略
|
||||
web:
|
||||
port: "8080" # Web 服务端口,"" 或 "0" 可只启用 socket
|
||||
socket: "" # unix socket 路径(Linux 部署推荐,见 install_linux.sh)
|
||||
@@ -78,7 +82,11 @@ secret: <自动生成> # Session 加密密钥;缺失时拒绝启动
|
||||
```yaml
|
||||
database:
|
||||
type: mysql
|
||||
dsn: user:password@tcp(127.0.0.1:3306)/blog_go?charset=utf8mb4&parseTime=True&loc=Local
|
||||
db_name: blog_go
|
||||
username: user
|
||||
password: password
|
||||
host: 127.0.0.1
|
||||
port: "3306"
|
||||
web:
|
||||
port: "8080"
|
||||
```
|
||||
|
||||
+23
-6
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -21,8 +22,20 @@ type Config struct {
|
||||
|
||||
// DatabaseConfig 保存数据库相关配置。
|
||||
type DatabaseConfig struct {
|
||||
Type string `yaml:"type"` // "sqlite"(默认)或 "mysql"
|
||||
DSN string `yaml:"dsn"` // MySQL 连接字符串(type 为 "mysql" 时必填)
|
||||
Type string `yaml:"type"` // "sqlite"(默认)或 "mysql"
|
||||
DBName string `yaml:"db_name"` // 数据库名(type 为 "mysql" 时必填)
|
||||
Username string `yaml:"username"` // 用户名(type 为 "mysql" 时必填)
|
||||
Password string `yaml:"password"` // 密码(type 为 "mysql" 时必填)
|
||||
Host string `yaml:"host"` // IP 或主机名(type 为 "mysql" 时必填)
|
||||
Port string `yaml:"port"` // 端口(type 为 "mysql" 时必填)
|
||||
}
|
||||
|
||||
// MySQLDSN 根据拆分字段构建 MySQL 连接字符串。
|
||||
func (d *DatabaseConfig) MySQLDSN() string {
|
||||
return fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
d.Username, d.Password, d.Host, d.Port, d.DBName,
|
||||
)
|
||||
}
|
||||
|
||||
// WebConfig 保存 Web 服务器监听配置。
|
||||
@@ -41,8 +54,8 @@ var defaultTrustedProxies = []string{"127.0.0.1", "::1"}
|
||||
|
||||
const defaultPort = "8080"
|
||||
|
||||
// mysqlExampleDSN 会写入新建的配置文件,作为参考示例。
|
||||
const mysqlExampleDSN = "user:password@tcp(127.0.0.1:3306)/blog_go?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
// mysqlExampleDBName 作为示例写入新建的配置文件。
|
||||
const mysqlExampleDBName = "blog_go"
|
||||
|
||||
// getConfigPath 返回按操作系统区分的配置目录和配置文件路径。
|
||||
func getConfigPath() (dir, file string) {
|
||||
@@ -107,8 +120,12 @@ func LoadConfig(customPath string) *Config {
|
||||
|
||||
cfg := &Config{
|
||||
Database: DatabaseConfig{
|
||||
Type: "sqlite",
|
||||
DSN: mysqlExampleDSN,
|
||||
Type: "sqlite",
|
||||
DBName: mysqlExampleDBName,
|
||||
Username: "user",
|
||||
Password: "password",
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
},
|
||||
Web: WebConfig{
|
||||
Port: defaultPort,
|
||||
|
||||
@@ -20,3 +20,17 @@ func TestConfigFileCreatedNotWorldReadable(t *testing.T) {
|
||||
t.Fatalf("config perms = %v, want 0640", perm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMySQLDSN(t *testing.T) {
|
||||
d := &DatabaseConfig{
|
||||
DBName: "blog_go",
|
||||
Username: "user",
|
||||
Password: "pass:word",
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
}
|
||||
want := "user:pass:word@tcp(127.0.0.1:3306)/blog_go?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
if got := d.MySQLDSN(); got != want {
|
||||
t.Fatalf("MySQLDSN() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -53,7 +53,11 @@ if [[ ! -f "${CONFIG_DIR}/config.yaml" ]]; then
|
||||
cat > "${CONFIG_DIR}/config.yaml" <<EOF
|
||||
database:
|
||||
type: sqlite
|
||||
dsn: ""
|
||||
db_name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
host: ""
|
||||
port: ""
|
||||
web:
|
||||
port: "8080"
|
||||
socket: ${SOCKET_PATH}
|
||||
|
||||
+24
-3
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -36,6 +37,26 @@ func randomAdminPassword() string {
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// validateMySQLConfig 校验 MySQL 连接所需字段均非空。
|
||||
func validateMySQLConfig(d *config.DatabaseConfig) error {
|
||||
if d.DBName == "" {
|
||||
return errors.New("'db_name' is required when type is 'mysql'")
|
||||
}
|
||||
if d.Username == "" {
|
||||
return errors.New("'username' is required when type is 'mysql'")
|
||||
}
|
||||
if d.Password == "" {
|
||||
return errors.New("'password' is required when type is 'mysql'")
|
||||
}
|
||||
if d.Host == "" {
|
||||
return errors.New("'host' is required when type is 'mysql'")
|
||||
}
|
||||
if d.Port == "" {
|
||||
return errors.New("'port' is required when type is 'mysql'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitDB 打开数据库连接、执行迁移并初始化管理员用户。
|
||||
func InitDB(cfg *config.Config) *gorm.DB {
|
||||
// 确保存储目录存在。
|
||||
@@ -47,10 +68,10 @@ func InitDB(cfg *config.Config) *gorm.DB {
|
||||
|
||||
switch cfg.Database.Type {
|
||||
case "mysql":
|
||||
if cfg.Database.DSN == "" {
|
||||
log.Fatalf("Database DSN is required when type is 'mysql'. Please set it in your config file.")
|
||||
if err := validateMySQLConfig(&cfg.Database); err != nil {
|
||||
log.Fatalf("Invalid MySQL config: %v", err)
|
||||
}
|
||||
dialector = mysql.Open(cfg.Database.DSN)
|
||||
dialector = mysql.Open(cfg.Database.MySQLDSN())
|
||||
default:
|
||||
dbPath := filepath.Join(cfg.Path, "blog.db")
|
||||
dialector = sqlite.Open(dbPath)
|
||||
|
||||
Reference in New Issue
Block a user