256 lines
8.7 KiB
Go
256 lines
8.7 KiB
Go
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"github.com/spf13/viper"
|
||
)
|
||
|
||
// Config 应用配置
|
||
type Config struct {
|
||
Service ServiceConfig `mapstructure:"service"`
|
||
Redis RedisConfig `mapstructure:"redis"`
|
||
MySQL MySQLConfig `mapstructure:"mysql"`
|
||
MongoDB MongoDBConfig `mapstructure:"mongodb"`
|
||
OSS OSSConfig `mapstructure:"oss"`
|
||
Wechat WechatConfig `mapstructure:"wechat"`
|
||
Scheduler SchedulerConfig `mapstructure:"scheduler"`
|
||
Admin AdminConfig `mapstructure:"admin"`
|
||
}
|
||
|
||
// ServiceConfig 服务配置
|
||
type ServiceConfig struct {
|
||
Name string `mapstructure:"name"`
|
||
Version string `mapstructure:"version"`
|
||
Host string `mapstructure:"host"`
|
||
AdminPort int `mapstructure:"admin_port"`
|
||
APIPort int `mapstructure:"api_port"`
|
||
}
|
||
|
||
// RedisConfig Redis配置
|
||
type RedisConfig struct {
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
Password string `mapstructure:"password"`
|
||
DB int `mapstructure:"db"`
|
||
PoolSize int `mapstructure:"pool_size"`
|
||
MinIdleConns int `mapstructure:"min_idle_conns"`
|
||
}
|
||
|
||
// OSSConfig 阿里云OSS配置
|
||
type OSSConfig struct {
|
||
AccessKeyID string `mapstructure:"accessKeyId"`
|
||
AccessKeySecret string `mapstructure:"accessKeySecret"`
|
||
RoleARN string `mapstructure:"roleArn"`
|
||
RoleSessionName string `mapstructure:"roleSessionName"`
|
||
Region string `mapstructure:"region"`
|
||
BucketName string `mapstructure:"bucketName"`
|
||
}
|
||
|
||
// WechatConfig 微信支付配置
|
||
type WechatConfig struct {
|
||
AppID string `mapstructure:"app_id"` // 微信小程序或公众号 APPID
|
||
MchID string `mapstructure:"mch_id"` // 商户号
|
||
NotifyURL string `mapstructure:"notify_url"` // 支付结果通知地址
|
||
APIKeyV3 string `mapstructure:"api_key_v3"` // API v3密钥
|
||
SerialNo string `mapstructure:"serial_no"` // 证书序列号
|
||
PrivateKey string `mapstructure:"private_key"` // 私钥内容(PEM格式)
|
||
PrivateKeyPath string `mapstructure:"private_key_path"` // 私钥文件路径
|
||
CertPath string `mapstructure:"cert_path"` // 证书文件路径(可选)
|
||
}
|
||
|
||
// MySQLConfig MySQL配置
|
||
type MySQLConfig struct {
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
Username string `mapstructure:"username"`
|
||
Password string `mapstructure:"password"`
|
||
Database string `mapstructure:"database"`
|
||
Charset string `mapstructure:"charset"`
|
||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||
ConnMaxLifetime string `mapstructure:"conn_max_lifetime"` // 如 "3600s"
|
||
}
|
||
|
||
// MongoDBConfig MongoDB配置
|
||
type MongoDBConfig struct {
|
||
URI string `mapstructure:"uri"` // 连接字符串,如: mongodb://admin:password@localhost:27017
|
||
Database string `mapstructure:"database"` // 数据库名
|
||
Timeout string `mapstructure:"timeout"` // 连接超时,如 "10s"
|
||
}
|
||
|
||
// SchedulerConfig 调度器配置
|
||
type SchedulerConfig struct {
|
||
TickInterval string `mapstructure:"tick_interval"` // 时间轮刻度间隔,如 "100ms"
|
||
SlotNum int `mapstructure:"slot_num"` // 槽位数量
|
||
}
|
||
|
||
// AdminConfig 管理员配置
|
||
type AdminConfig struct {
|
||
JWTSecret string `mapstructure:"jwt_secret"` // JWT密钥
|
||
JWTExpiresIn string `mapstructure:"jwt_expires_in"` // JWT过期时间,如 "24h"
|
||
}
|
||
|
||
// LoadConfig 加载配置文件
|
||
// 支持从多个位置查找配置文件(优先级从高到低):
|
||
// 1. 命令行参数指定的路径
|
||
// 2. /app/data/config/config.yaml(容器内挂载路径)
|
||
// 3. ./data/config/config.yaml(开发环境)
|
||
// 4. ./config.yaml(项目根目录,兼容旧配置)
|
||
func LoadConfig(configFile string) (*Config, error) {
|
||
v := viper.New()
|
||
v.SetConfigType("yaml")
|
||
|
||
// 如果指定了配置文件且文件存在,直接使用
|
||
if configFile != "" && configFile != "config.yaml" {
|
||
if _, err := os.Stat(configFile); err == nil {
|
||
v.SetConfigFile(configFile)
|
||
}
|
||
}
|
||
|
||
// 如果还没有设置配置文件,尝试从多个位置查找
|
||
if v.ConfigFileUsed() == "" {
|
||
configPaths := []string{
|
||
"/app/data/config/config.yaml", // 容器内挂载路径
|
||
"./data/config/config.yaml", // 开发环境
|
||
"./config.yaml", // 项目根目录(兼容旧配置)
|
||
}
|
||
|
||
for _, path := range configPaths {
|
||
if _, err := os.Stat(path); err == nil {
|
||
v.SetConfigFile(path)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果仍然没有找到配置文件,使用默认路径
|
||
if v.ConfigFileUsed() == "" {
|
||
if configFile != "" {
|
||
v.SetConfigFile(configFile)
|
||
} else {
|
||
v.SetConfigFile("config.yaml")
|
||
}
|
||
}
|
||
|
||
// 设置默认值
|
||
setDefaults(v)
|
||
|
||
if err := v.ReadInConfig(); err != nil {
|
||
return nil, fmt.Errorf("读取配置文件失败: %w (尝试的路径: %s)", err, v.ConfigFileUsed())
|
||
}
|
||
|
||
var config Config
|
||
if err := v.Unmarshal(&config); err != nil {
|
||
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
||
}
|
||
|
||
// 处理证书路径:如果是绝对路径,直接验证;如果是相对路径,尝试从多个位置查找
|
||
if config.Wechat.PrivateKeyPath != "" {
|
||
if filepath.IsAbs(config.Wechat.PrivateKeyPath) {
|
||
// 绝对路径:直接验证文件是否存在
|
||
if _, err := os.Stat(config.Wechat.PrivateKeyPath); err != nil {
|
||
// 如果绝对路径不存在,尝试从其他位置查找
|
||
// 提取相对路径部分(去掉 /app/data/storage/ 或 storage/ 前缀)
|
||
relPath := config.Wechat.PrivateKeyPath
|
||
if strings.HasPrefix(relPath, "/app/data/storage/") {
|
||
relPath = strings.TrimPrefix(relPath, "/app/data/storage/")
|
||
} else if strings.HasPrefix(relPath, "storage/") {
|
||
relPath = strings.TrimPrefix(relPath, "storage/")
|
||
}
|
||
|
||
// 尝试从多个位置查找
|
||
certBasePaths := []string{
|
||
"/app/data/storage", // 容器内挂载路径
|
||
"./deploy/storage", // 部署目录
|
||
"./data/storage", // 开发环境
|
||
"./storage", // 项目根目录(兼容旧配置)
|
||
}
|
||
|
||
for _, basePath := range certBasePaths {
|
||
fullPath := filepath.Join(basePath, relPath)
|
||
if _, err := os.Stat(fullPath); err == nil {
|
||
config.Wechat.PrivateKeyPath = fullPath
|
||
if config.Wechat.CertPath != "" {
|
||
certRelPath := config.Wechat.CertPath
|
||
if strings.HasPrefix(certRelPath, "/app/data/storage/") {
|
||
certRelPath = strings.TrimPrefix(certRelPath, "/app/data/storage/")
|
||
} else if strings.HasPrefix(certRelPath, "storage/") {
|
||
certRelPath = strings.TrimPrefix(certRelPath, "storage/")
|
||
}
|
||
config.Wechat.CertPath = filepath.Join(basePath, certRelPath)
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 相对路径:尝试从多个位置查找
|
||
certBasePaths := []string{
|
||
"/app/data/storage", // 容器内挂载路径
|
||
"./deploy/storage", // 部署目录
|
||
"./data/storage", // 开发环境
|
||
"./storage", // 项目根目录(兼容旧配置)
|
||
}
|
||
|
||
// 提取相对路径部分(去掉 storage/ 前缀)
|
||
relPath := config.Wechat.PrivateKeyPath
|
||
if strings.HasPrefix(relPath, "storage/") {
|
||
relPath = strings.TrimPrefix(relPath, "storage/")
|
||
}
|
||
|
||
for _, basePath := range certBasePaths {
|
||
fullPath := filepath.Join(basePath, relPath)
|
||
if _, err := os.Stat(fullPath); err == nil {
|
||
config.Wechat.PrivateKeyPath = fullPath
|
||
if config.Wechat.CertPath != "" {
|
||
certRelPath := config.Wechat.CertPath
|
||
if strings.HasPrefix(certRelPath, "storage/") {
|
||
certRelPath = strings.TrimPrefix(certRelPath, "storage/")
|
||
}
|
||
config.Wechat.CertPath = filepath.Join(basePath, certRelPath)
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return &config, nil
|
||
}
|
||
|
||
// setDefaults 设置默认配置值
|
||
func setDefaults(v *viper.Viper) {
|
||
v.SetDefault("service.name", "dd_fiber_api")
|
||
v.SetDefault("service.version", "1.0.0")
|
||
v.SetDefault("service.host", "0.0.0.0")
|
||
v.SetDefault("service.admin_port", 8080)
|
||
v.SetDefault("service.api_port", 8081)
|
||
|
||
v.SetDefault("redis.host", "localhost")
|
||
v.SetDefault("redis.port", 6379)
|
||
v.SetDefault("redis.password", "")
|
||
v.SetDefault("redis.db", 0)
|
||
v.SetDefault("redis.pool_size", 10)
|
||
v.SetDefault("redis.min_idle_conns", 5)
|
||
|
||
v.SetDefault("mysql.host", "localhost")
|
||
v.SetDefault("mysql.port", 3306)
|
||
v.SetDefault("mysql.username", "root")
|
||
v.SetDefault("mysql.password", "")
|
||
v.SetDefault("mysql.database", "")
|
||
v.SetDefault("mysql.charset", "utf8mb4")
|
||
v.SetDefault("mysql.max_open_conns", 100)
|
||
v.SetDefault("mysql.max_idle_conns", 10)
|
||
v.SetDefault("mysql.conn_max_lifetime", "3600s")
|
||
|
||
v.SetDefault("scheduler.tick_interval", "100ms")
|
||
v.SetDefault("scheduler.slot_num", 3600)
|
||
|
||
v.SetDefault("admin.jwt_secret", "your-secret-key-change-in-production")
|
||
v.SetDefault("admin.jwt_expires_in", "24h")
|
||
}
|