This commit is contained in:
2026-05-28 14:43:13 +08:00
parent c16a8dfbc4
commit 957a594a0f
7 changed files with 353 additions and 64 deletions
+216
View File
@@ -0,0 +1,216 @@
package config
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"github.com/pelletier/go-toml/v2"
)
// Cfg 是全局配置实例,在 Load() 后可用
var Cfg *Config
// Config 是完整的配置结构
type Config struct {
Data DataConfig `toml:"data"`
Database DatabaseConfig `toml:"database"`
Server ServerConfig `toml:"server"`
}
// DataConfig 数据存储配置
type DataConfig struct {
// 数据存储根目录,Windows 默认 "data"Linux 默认 "/srv/portal_page"
Dir string `toml:"dir"`
}
// DatabaseConfig 数据库配置
type DatabaseConfig struct {
// 数据库类型: "sqlite" 或 "mysql"
Type string `toml:"type"`
// SQLite 数据库文件名(相对于 data.dir)
Path string `toml:"path"`
// MySQL 配置
MySQL MySQLConfig `toml:"mysql"`
}
// MySQLConfig MySQL 连接配置
type MySQLConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
User string `toml:"user"`
Password string `toml:"password"`
DBName string `toml:"dbname"`
}
// ServerConfig Web 服务器配置
type ServerConfig struct {
// 监听地址,格式 ":8080"
Addr string `toml:"addr"`
// Unix socket 路径(设置后优先使用 unix socketaddr 被忽略)
Unix string `toml:"unix"`
}
// defaultConfig 返回当前平台的默认配置
func defaultConfig() *Config {
cfg := &Config{
Data: DataConfig{
Dir: "data",
},
Database: DatabaseConfig{
Type: "sqlite",
Path: "portal.db",
MySQL: MySQLConfig{
Host: "127.0.0.1",
Port: 3306,
User: "root",
Password: "",
DBName: "portal_page",
},
},
Server: ServerConfig{
Addr: ":8080",
Unix: "",
},
}
// Linux 下遵循 FHS 标准
if runtime.GOOS == "linux" {
cfg.Data.Dir = "/srv/portal_page"
}
return cfg
}
// configPath 返回当前平台的配置文件路径
func configPath() string {
if runtime.GOOS == "windows" {
// Windows: 相对于可执行文件的 conf/config.toml
exePath, err := os.Executable()
if err != nil {
return "conf/config.toml"
}
return filepath.Join(filepath.Dir(exePath), "conf", "config.toml")
}
// Linux: FHS 标准 /etc/portal_page/config.toml
return "/etc/portal_page/config.toml"
}
// Load 加载配置文件,不存在则自动生成,缺失项自动补全
func Load() error {
path := configPath()
def := defaultConfig()
// 如果配置文件不存在,使用默认配置直接生成
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Printf("配置文件不存在,自动生成: %s", path)
Cfg = def
return saveConfig(path, Cfg)
}
// 读取并解析配置文件
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("读取配置文件失败: %w", err)
}
Cfg = &Config{}
if err := toml.Unmarshal(data, Cfg); err != nil {
return fmt.Errorf("解析配置文件失败: %w", err)
}
// 补全缺失项
changed := fillDefaults(Cfg, def)
if changed {
log.Printf("配置文件有缺失项,已自动补全: %s", path)
if err := saveConfig(path, Cfg); err != nil {
return fmt.Errorf("保存补全后的配置文件失败: %w", err)
}
}
return nil
}
// fillDefaults 用默认值补全零值字段,返回是否有变更
func fillDefaults(cfg, def *Config) bool {
changed := false
// Data
if cfg.Data.Dir == "" {
cfg.Data.Dir = def.Data.Dir
changed = true
}
// Database
if cfg.Database.Type == "" {
cfg.Database.Type = def.Database.Type
changed = true
}
if cfg.Database.Path == "" {
cfg.Database.Path = def.Database.Path
changed = true
}
if cfg.Database.MySQL.Host == "" {
cfg.Database.MySQL.Host = def.Database.MySQL.Host
changed = true
}
if cfg.Database.MySQL.Port == 0 {
cfg.Database.MySQL.Port = def.Database.MySQL.Port
changed = true
}
if cfg.Database.MySQL.User == "" {
cfg.Database.MySQL.User = def.Database.MySQL.User
changed = true
}
// Password 允许为空字符串,不补全
if cfg.Database.MySQL.DBName == "" {
cfg.Database.MySQL.DBName = def.Database.MySQL.DBName
changed = true
}
// Server
if cfg.Server.Addr == "" && cfg.Server.Unix == "" {
cfg.Server.Addr = def.Server.Addr
changed = true
}
return changed
}
// saveConfig 将配置写入文件
func saveConfig(path string, cfg *Config) error {
// 确保目录存在
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建配置目录失败: %w", err)
}
data, err := toml.Marshal(cfg)
if err != nil {
return fmt.Errorf("序列化配置失败: %w", err)
}
if err := os.WriteFile(path, data, 0644); err != nil {
return fmt.Errorf("写入配置文件失败: %w", err)
}
return nil
}
// GetUploadDir 返回上传文件目录的完整路径
func GetUploadDir() string {
return filepath.Join(Cfg.Data.Dir, "uploads")
}
// GetDBPath 返回 SQLite 数据库文件的完整路径
func GetDBPath() string {
return filepath.Join(Cfg.Data.Dir, Cfg.Database.Path)
}
// GetDSN 返回 MySQL 的 DSN 连接字符串
func (m *MySQLConfig) GetDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=Local",
m.User, m.Password, m.Host, m.Port, m.DBName)
}