- README/CHANGELOG: add v1.0.7 entry\n- Server: JWT expiry validated server-side (401 codes)\n- Web: logout/redirect on 401; proxy forwards Authorization\n- Server: bill service uses repository consistently
317 lines
8.4 KiB
Go
317 lines
8.4 KiB
Go
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 = "1.0.7"
|
|
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)
|
|
}
|