up
This commit is contained in:
@@ -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 socket,addr 被忽略)
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user