Files

290 lines
9.9 KiB
Go
Raw Permalink Normal View History

2025-10-27 23:12:15 +08:00
package config
import (
"bytes"
"encoding/json"
2026-03-28 23:30:02 +08:00
"os"
"path/filepath"
2025-10-27 23:12:15 +08:00
"NetworkAuth/utils"
2025-10-27 23:12:15 +08:00
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
2026-03-28 23:30:02 +08:00
var currentConfigFilePath string
2025-10-27 23:12:15 +08:00
// ============================================================================
// 结构体定义
// ============================================================================
// ServerConfig 服务器配置结构体
// 包含服务器运行相关的配置信息
type ServerConfig struct {
2026-03-18 21:51:17 +08:00
Host string `json:"host" mapstructure:"host"` // 服务器监听地址
Port int `json:"port" mapstructure:"port"` // 服务器监听端口
Dist string `json:"dist" mapstructure:"dist"` // 静态文件目录
DevMode bool `json:"dev_mode" mapstructure:"dev_mode"` // 开发模式(跳过验证码等)
AccessLog bool `json:"access_log" mapstructure:"access_log"` // 是否输出访问日志
2025-10-27 23:12:15 +08:00
}
// DatabaseConfig 数据库配置结构体
// 包含数据库连接相关的配置信息
type DatabaseConfig struct {
Type string `json:"type" mapstructure:"type"` // 数据库类型mysql/sqlite
MySQL MySQLConfig `json:"mysql" mapstructure:"mysql"` // MySQL配置
SQLite SQLiteConfig `json:"sqlite" mapstructure:"sqlite"` // SQLite配置
}
// MySQLConfig MySQL数据库配置结构体
// 包含MySQL数据库连接的详细配置信息
type MySQLConfig struct {
Host string `json:"host" mapstructure:"host"` // 数据库主机地址
Port int `json:"port" mapstructure:"port"` // 数据库端口
Username string `json:"username" mapstructure:"username"` // 数据库用户名
Password string `json:"password" mapstructure:"password"` // 数据库密码
Database string `json:"database" mapstructure:"database"` // 数据库名称
Charset string `json:"charset" mapstructure:"charset"` // 字符集
MaxIdleConns int `json:"max_idle_conns" mapstructure:"max_idle_conns"` // 最大空闲连接数
MaxOpenConns int `json:"max_open_conns" mapstructure:"max_open_conns"` // 最大打开连接数
}
// SQLiteConfig SQLite数据库配置结构体
// 包含SQLite数据库文件路径配置
type SQLiteConfig struct {
Path string `json:"path" mapstructure:"path"` // 数据库文件路径
}
// RedisConfig Redis配置结构体
// 包含Redis连接相关的配置信息
type RedisConfig struct {
Host string `json:"host" mapstructure:"host"` // Redis服务器地址
Port int `json:"port" mapstructure:"port"` // Redis服务器端口
Password string `json:"password" mapstructure:"password"` // Redis密码
DB int `json:"db" mapstructure:"db"` // Redis数据库编号
}
// LogConfig 日志配置结构体
// 包含日志记录相关的配置信息
type LogConfig struct {
Level string `json:"level" mapstructure:"level"` // 日志级别
File string `json:"file" mapstructure:"file"` // 日志文件路径
MaxSize int `json:"max_size" mapstructure:"max_size"` // 单个日志文件最大大小(MB)
MaxBackups int `json:"max_backups" mapstructure:"max_backups"` // 保留的旧日志文件数量
MaxAge int `json:"max_age" mapstructure:"max_age"` // 日志文件保留天数
}
// AppConfig 应用配置结构体
type AppConfig struct {
Server ServerConfig `json:"server" mapstructure:"server"`
Database DatabaseConfig `json:"database" mapstructure:"database"`
Redis RedisConfig `json:"redis" mapstructure:"redis"`
Log LogConfig `json:"log" mapstructure:"log"`
}
// ============================================================================
// 公共函数
// ============================================================================
// GetDefaultAppConfig 获取默认应用配置
func GetDefaultAppConfig() *AppConfig {
return &AppConfig{
Server: ServerConfig{
2026-03-18 21:51:17 +08:00
Host: "0.0.0.0",
Port: 8080,
Dist: "",
DevMode: false,
AccessLog: true,
2025-10-27 23:12:15 +08:00
},
Database: DatabaseConfig{
Type: "sqlite",
MySQL: MySQLConfig{
Host: "localhost",
Port: 3306,
Username: "",
Password: "",
Database: "",
2025-10-27 23:12:15 +08:00
Charset: "utf8mb4",
MaxIdleConns: 10,
MaxOpenConns: 100,
},
SQLite: SQLiteConfig{
Path: "database.db",
2025-10-27 23:12:15 +08:00
},
},
Redis: RedisConfig{
Host: "localhost",
Port: 6379,
Password: "",
DB: 0,
},
Log: LogConfig{
Level: "info",
File: "logs/app.log",
2025-10-27 23:12:15 +08:00
MaxSize: 100,
MaxBackups: 5,
MaxAge: 30,
},
}
}
// Init 初始化配置文件
func Init(cfgFilePath string) {
if !filepath.IsAbs(cfgFilePath) {
if wd, err := os.Getwd(); err == nil {
candidate := filepath.Clean(filepath.Join(wd, cfgFilePath))
if _, statErr := os.Stat(candidate); statErr == nil {
cfgFilePath = candidate
} else {
cfgFilePath = filepath.Join(utils.GetRootDir(), cfgFilePath)
}
} else {
cfgFilePath = filepath.Join(utils.GetRootDir(), cfgFilePath)
}
}
2026-04-27 23:09:33 +08:00
2026-03-28 23:30:02 +08:00
currentConfigFilePath = cfgFilePath
2025-10-31 10:28:25 +08:00
viper.SetConfigFile(cfgFilePath)
2025-10-27 23:12:15 +08:00
viper.SetConfigType("json")
viper.AddConfigPath(".")
2026-04-27 23:09:33 +08:00
defaultConfig := GetDefaultAppConfig()
var needWrite bool
var configBytes []byte
2026-03-28 23:30:02 +08:00
2026-04-27 23:09:33 +08:00
// 检查配置文件是否存在
fileContent, err := os.ReadFile(cfgFilePath)
if err != nil {
if os.IsNotExist(err) {
log.WithField("file", utils.DisplayPath(filepath.Clean(cfgFilePath))).Info("配置文件不存在,将在本地生成默认配置")
needWrite = true
} else {
log.WithField("err", err).Fatal("读取配置文件失败")
2025-10-27 23:12:15 +08:00
}
2026-04-27 23:09:33 +08:00
} else {
// 尝试解析现有的配置,与默认配置合并,结构不一致则重写
if err := json.Unmarshal(fileContent, defaultConfig); err != nil {
log.WithField("err", err).Warn("配置文件解析失败,将使用默认值重写")
needWrite = true
} else {
// 将合并后的配置重新序列化,比对是否需要更新(例如结构体增加了新字段)
newBytes, _ := json.MarshalIndent(defaultConfig, "", " ")
// 简单比较去除空白后的长度或内容
if !bytes.Equal(bytes.TrimSpace(fileContent), bytes.TrimSpace(newBytes)) {
needWrite = true
}
2026-03-19 05:11:44 +08:00
}
2026-04-27 23:09:33 +08:00
}
2026-03-28 23:30:02 +08:00
2026-04-27 23:09:33 +08:00
if needWrite {
configBytes, err = json.MarshalIndent(defaultConfig, "", " ")
2026-03-28 23:30:02 +08:00
if err != nil {
2026-04-27 23:09:33 +08:00
log.WithField("err", err).Fatal("配置序列化错误")
}
if err := os.MkdirAll(filepath.Dir(cfgFilePath), 0755); err != nil {
log.WithField("err", err).Fatal("创建配置目录失败")
}
if err := os.WriteFile(cfgFilePath, configBytes, 0644); err != nil {
log.WithField("err", err).Fatal("写入配置文件失败")
}
if len(fileContent) == 0 {
log.Info("已成功生成并加载默认配置")
2026-03-28 23:30:02 +08:00
} else {
2026-04-27 23:09:33 +08:00
log.Info("已写出更新后的配置结构到文件")
2026-03-28 23:30:02 +08:00
}
2026-04-27 23:09:33 +08:00
} else {
configBytes = fileContent
}
2025-10-27 23:12:15 +08:00
2026-04-27 23:09:33 +08:00
// 将配置加载到viper中
if err := viper.ReadConfig(bytes.NewBuffer(configBytes)); err != nil {
log.WithField("err", err).Fatal("读取配置到viper失败")
2026-03-28 23:30:02 +08:00
}
cleanPath := filepath.Clean(cfgFilePath)
2026-04-27 23:09:33 +08:00
log.WithField("file", utils.DisplayPath(cleanPath)).Info("使用配置文件")
2026-03-28 23:30:02 +08:00
2025-10-27 23:12:15 +08:00
// 验证配置
if _, err := ValidateConfig(); err != nil {
log.WithFields(
log.Fields{
"err": err,
},
).Fatal("配置内容验证失败")
2025-10-27 23:12:15 +08:00
}
}
2026-03-28 23:30:02 +08:00
func SaveConfig(appConfig *AppConfig) error {
if err := ValidateConfigValue(appConfig); err != nil {
return err
}
if currentConfigFilePath == "" {
currentConfigFilePath = "config.json"
}
if !filepath.IsAbs(currentConfigFilePath) {
currentConfigFilePath = filepath.Join(utils.GetRootDir(), currentConfigFilePath)
2026-03-28 23:30:02 +08:00
}
if err := os.MkdirAll(filepath.Dir(currentConfigFilePath), 0755); err != nil {
return err
}
configBytes, err := json.MarshalIndent(appConfig, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(currentConfigFilePath, configBytes, 0644); err != nil {
return err
}
viper.SetConfigFile(currentConfigFilePath)
viper.SetConfigType("json")
if err := viper.ReadInConfig(); err != nil {
return err
}
syncViperConfig(appConfig)
return nil
}
2026-03-18 21:51:17 +08:00
// UpdateConfig 更新配置文件
// 接收一个回调函数,在回调函数中修改配置对象,然后保存到文件
func UpdateConfig(updateFn func(*AppConfig)) error {
// 1. 获取当前配置
var currentConfig AppConfig
if err := viper.Unmarshal(&currentConfig); err != nil {
return err
2025-10-27 23:12:15 +08:00
}
2026-03-18 21:51:17 +08:00
// 2. 执行更新回调
updateFn(&currentConfig)
2026-03-28 23:30:02 +08:00
return SaveConfig(&currentConfig)
}
2026-03-18 21:51:17 +08:00
2026-03-28 23:30:02 +08:00
func syncViperConfig(currentConfig *AppConfig) {
2026-03-18 21:51:17 +08:00
viper.Set("server.host", currentConfig.Server.Host)
viper.Set("server.port", currentConfig.Server.Port)
viper.Set("server.dist", currentConfig.Server.Dist)
viper.Set("server.dev_mode", currentConfig.Server.DevMode)
viper.Set("server.access_log", currentConfig.Server.AccessLog)
viper.Set("database.type", currentConfig.Database.Type)
viper.Set("database.mysql.host", currentConfig.Database.MySQL.Host)
viper.Set("database.mysql.port", currentConfig.Database.MySQL.Port)
viper.Set("database.mysql.username", currentConfig.Database.MySQL.Username)
viper.Set("database.mysql.password", currentConfig.Database.MySQL.Password)
viper.Set("database.mysql.database", currentConfig.Database.MySQL.Database)
viper.Set("database.mysql.charset", currentConfig.Database.MySQL.Charset)
viper.Set("database.mysql.max_idle_conns", currentConfig.Database.MySQL.MaxIdleConns)
viper.Set("database.mysql.max_open_conns", currentConfig.Database.MySQL.MaxOpenConns)
viper.Set("database.sqlite.path", currentConfig.Database.SQLite.Path)
viper.Set("redis.host", currentConfig.Redis.Host)
viper.Set("redis.port", currentConfig.Redis.Port)
viper.Set("redis.password", currentConfig.Redis.Password)
viper.Set("redis.db", currentConfig.Redis.DB)
viper.Set("log.level", currentConfig.Log.Level)
viper.Set("log.file", currentConfig.Log.File)
viper.Set("log.max_size", currentConfig.Log.MaxSize)
viper.Set("log.max_backups", currentConfig.Log.MaxBackups)
viper.Set("log.max_age", currentConfig.Log.MaxAge)
2025-10-27 23:12:15 +08:00
}