Files
NetworkAuth/config/config.go

290 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"NetworkAuth/utils"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
var currentConfigFilePath string
// ============================================================================
// 结构体定义
// ============================================================================
// ServerConfig 服务器配置结构体
// 包含服务器运行相关的配置信息
type ServerConfig struct {
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"` // 是否输出访问日志
}
// 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{
Host: "0.0.0.0",
Port: 8080,
Dist: "",
DevMode: false,
AccessLog: true,
},
Database: DatabaseConfig{
Type: "sqlite",
MySQL: MySQLConfig{
Host: "localhost",
Port: 3306,
Username: "",
Password: "",
Database: "",
Charset: "utf8mb4",
MaxIdleConns: 10,
MaxOpenConns: 100,
},
SQLite: SQLiteConfig{
Path: "database.db",
},
},
Redis: RedisConfig{
Host: "localhost",
Port: 6379,
Password: "",
DB: 0,
},
Log: LogConfig{
Level: "info",
File: "logs/app.log",
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)
}
}
currentConfigFilePath = cfgFilePath
viper.SetConfigFile(cfgFilePath)
viper.SetConfigType("json")
viper.AddConfigPath(".")
defaultConfig := GetDefaultAppConfig()
var needWrite bool
var configBytes []byte
// 检查配置文件是否存在
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("读取配置文件失败")
}
} 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
}
}
}
if needWrite {
configBytes, err = json.MarshalIndent(defaultConfig, "", " ")
if err != nil {
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("已成功生成并加载默认配置")
} else {
log.Info("已写出更新后的配置结构到文件")
}
} else {
configBytes = fileContent
}
// 将配置加载到viper中
if err := viper.ReadConfig(bytes.NewBuffer(configBytes)); err != nil {
log.WithField("err", err).Fatal("读取配置到viper失败")
}
cleanPath := filepath.Clean(cfgFilePath)
log.WithField("file", utils.DisplayPath(cleanPath)).Info("使用配置文件")
// 验证配置
if _, err := ValidateConfig(); err != nil {
log.WithFields(
log.Fields{
"err": err,
},
).Fatal("配置内容验证失败")
}
}
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)
}
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
}
// UpdateConfig 更新配置文件
// 接收一个回调函数,在回调函数中修改配置对象,然后保存到文件
func UpdateConfig(updateFn func(*AppConfig)) error {
// 1. 获取当前配置
var currentConfig AppConfig
if err := viper.Unmarshal(&currentConfig); err != nil {
return err
}
// 2. 执行更新回调
updateFn(&currentConfig)
return SaveConfig(&currentConfig)
}
func syncViperConfig(currentConfig *AppConfig) {
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)
}