package config import ( "flag" "fmt" "os" "path/filepath" "strconv" "gopkg.in/yaml.v3" ) // Config 服务配置 type Config struct { Version string // 应用版本 Port string // 服务端口 ProjectRoot string // 项目根目录 PythonPath string // Python 解释器路径 CleanScript string // 清理脚本路径 UploadDir string // 上传文件目录 OutputDir string // 输出文件目录 // Analyzer 服务配置 (HTTP 模式) AnalyzerURL string // Python 分析服务 URL AnalyzerMode string // 适配器模式: http 或 subprocess // MongoDB 配置 MongoURI string // MongoDB 连接 URI MongoDatabase string // 数据库名称 MongoRawCollection string // 原始数据集合名称 MongoCleanedCollection string // 清洗后数据集合名称 // 认证配置 JWTSecret string // JWT 密钥 TokenExpiry int // Token 过期时间(小时) Users []UserInfo // 用户列表 } // UserInfo 用户信息 type UserInfo struct { Username string `json:"username"` Password string `json:"-"` // 不序列化密码 Name string `json:"name"` Email string `json:"email"` Role string `json:"role"` } // configFile YAML 配置文件结构 type configFile struct { Version string `yaml:"version"` Server struct { Port int `yaml:"port"` } `yaml:"server"` Python struct { Path string `yaml:"path"` Script string `yaml:"script"` } `yaml:"python"` Analyzer struct { URL string `yaml:"url"` Mode string `yaml:"mode"` // http 或 subprocess } `yaml:"analyzer"` Directories struct { Upload string `yaml:"upload"` Output string `yaml:"output"` } `yaml:"directories"` MongoDB struct { URI string `yaml:"uri"` Database string `yaml:"database"` Collections struct { Raw string `yaml:"raw"` Cleaned string `yaml:"cleaned"` } `yaml:"collections"` } `yaml:"mongodb"` Auth struct { JWTSecret string `yaml:"jwt_secret"` TokenExpiry int `yaml:"token_expiry"` // 小时 Users []struct { Username string `yaml:"username"` Password string `yaml:"password"` Name string `yaml:"name"` Email string `yaml:"email"` Role string `yaml:"role"` } `yaml:"users"` } `yaml:"auth"` } // Global 全局配置实例 var Global Config // getEnvOrDefault 获取环境变量,如果不存在则返回默认值 func getEnvOrDefault(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // getDefaultProjectRoot 获取默认项目根目录 func getDefaultProjectRoot() string { if root := os.Getenv("BILLAI_ROOT"); root != "" { return root } exe, err := os.Executable() if err == nil { exeDir := filepath.Dir(exe) if filepath.Base(exeDir) == "server" { return filepath.Dir(exeDir) } } cwd, _ := os.Getwd() if filepath.Base(cwd) == "server" { return filepath.Dir(cwd) } return cwd } // getDefaultPythonPath 获取默认 Python 路径 func getDefaultPythonPath() string { if python := os.Getenv("BILLAI_PYTHON"); python != "" { return python } return "analyzer/venv/bin/python" } // loadConfigFile 加载 YAML 配置文件 func loadConfigFile(configPath string) *configFile { data, err := os.ReadFile(configPath) if err != nil { return nil } var cfg configFile if err := yaml.Unmarshal(data, &cfg); err != nil { fmt.Printf("⚠️ 配置文件解析失败: %v\n", err) return nil } return &cfg } // Load 加载配置 func Load() { var configFilePath string flag.StringVar(&configFilePath, "config", "config.yaml", "配置文件路径") flag.Parse() // 设置默认值 Global.Version = "0.0.1" Global.Port = getEnvOrDefault("PORT", "8080") Global.ProjectRoot = getDefaultProjectRoot() Global.PythonPath = getDefaultPythonPath() Global.CleanScript = "analyzer/clean_bill.py" Global.UploadDir = "server/uploads" Global.OutputDir = "server/outputs" // Analyzer 默认值 Global.AnalyzerURL = getEnvOrDefault("ANALYZER_URL", "http://localhost:8001") Global.AnalyzerMode = getEnvOrDefault("ANALYZER_MODE", "http") // MongoDB 默认值 Global.MongoURI = getEnvOrDefault("MONGO_URI", "mongodb://localhost:27017") Global.MongoDatabase = getEnvOrDefault("MONGO_DATABASE", "billai") Global.MongoRawCollection = getEnvOrDefault("MONGO_RAW_COLLECTION", "bills_raw") Global.MongoCleanedCollection = getEnvOrDefault("MONGO_CLEANED_COLLECTION", "bills_cleaned") // 查找配置文件 configPath := configFilePath if !filepath.IsAbs(configPath) { if _, err := os.Stat(configPath); os.IsNotExist(err) { configPath = filepath.Join("server", configFilePath) } } // 加载配置文件 if cfg := loadConfigFile(configPath); cfg != nil { fmt.Printf("📄 加载配置文件: %s\n", configPath) if cfg.Version != "" { Global.Version = cfg.Version } if cfg.Server.Port > 0 { Global.Port = fmt.Sprintf("%d", cfg.Server.Port) } if cfg.Python.Path != "" { Global.PythonPath = cfg.Python.Path } if cfg.Python.Script != "" { Global.CleanScript = cfg.Python.Script } if cfg.Directories.Upload != "" { Global.UploadDir = cfg.Directories.Upload } if cfg.Directories.Output != "" { Global.OutputDir = cfg.Directories.Output } // Analyzer 配置 if cfg.Analyzer.URL != "" { Global.AnalyzerURL = cfg.Analyzer.URL } if cfg.Analyzer.Mode != "" { Global.AnalyzerMode = cfg.Analyzer.Mode } // MongoDB 配置 if cfg.MongoDB.URI != "" { Global.MongoURI = cfg.MongoDB.URI } if cfg.MongoDB.Database != "" { Global.MongoDatabase = cfg.MongoDB.Database } if cfg.MongoDB.Collections.Raw != "" { Global.MongoRawCollection = cfg.MongoDB.Collections.Raw } if cfg.MongoDB.Collections.Cleaned != "" { Global.MongoCleanedCollection = cfg.MongoDB.Collections.Cleaned } // 认证配置 if cfg.Auth.JWTSecret != "" { Global.JWTSecret = cfg.Auth.JWTSecret } if cfg.Auth.TokenExpiry > 0 { Global.TokenExpiry = cfg.Auth.TokenExpiry } // 用户列表 for _, u := range cfg.Auth.Users { Global.Users = append(Global.Users, UserInfo{ Username: u.Username, Password: u.Password, Name: u.Name, Email: u.Email, Role: u.Role, }) } } // 环境变量覆盖 if port := os.Getenv("PORT"); port != "" { Global.Port = port } if python := os.Getenv("BILLAI_PYTHON"); python != "" { Global.PythonPath = python } if root := os.Getenv("BILLAI_ROOT"); root != "" { Global.ProjectRoot = root } // Analyzer 环境变量覆盖 if url := os.Getenv("ANALYZER_URL"); url != "" { Global.AnalyzerURL = url } if mode := os.Getenv("ANALYZER_MODE"); mode != "" { Global.AnalyzerMode = mode } // MongoDB 环境变量覆盖 if uri := os.Getenv("MONGO_URI"); uri != "" { Global.MongoURI = uri } if db := os.Getenv("MONGO_DATABASE"); db != "" { Global.MongoDatabase = db } if rawColl := os.Getenv("MONGO_RAW_COLLECTION"); rawColl != "" { Global.MongoRawCollection = rawColl } if cleanedColl := os.Getenv("MONGO_CLEANED_COLLECTION"); cleanedColl != "" { Global.MongoCleanedCollection = cleanedColl } // 认证相关环境变量覆盖 if jwtSecret := os.Getenv("JWT_SECRET"); jwtSecret != "" { Global.JWTSecret = jwtSecret } if tokenExpiry := os.Getenv("TOKEN_EXPIRY"); tokenExpiry != "" { if expiry, err := strconv.Atoi(tokenExpiry); err == nil && expiry > 0 { Global.TokenExpiry = expiry } } // 管理员账号环境变量覆盖(覆盖配置文件中的第一个用户) adminUser := os.Getenv("ADMIN_USERNAME") adminPass := os.Getenv("ADMIN_PASSWORD") if adminUser != "" && adminPass != "" { adminName := os.Getenv("ADMIN_NAME") if adminName == "" { adminName = "管理员" } adminEmail := os.Getenv("ADMIN_EMAIL") if adminEmail == "" { adminEmail = adminUser + "@billai.com" } // 查找并更新 admin 用户,或添加新用户 found := false for i := range Global.Users { if Global.Users[i].Username == adminUser || Global.Users[i].Role == "admin" { Global.Users[i].Username = adminUser Global.Users[i].Password = adminPass Global.Users[i].Name = adminName Global.Users[i].Email = adminEmail Global.Users[i].Role = "admin" found = true break } } if !found { Global.Users = append(Global.Users, UserInfo{ Username: adminUser, Password: adminPass, Name: adminName, Email: adminEmail, Role: "admin", }) } } } // ResolvePath 解析路径(相对路径转为绝对路径) func ResolvePath(path string) string { if filepath.IsAbs(path) { return path } return filepath.Join(Global.ProjectRoot, path) }