Files
billai/server/config/config.go
clz 829b3445bc feat: 添加用户登录认证功能
- 新增登录页面(使用 shadcn-svelte 组件)
- 后端添加 JWT 认证 API (/api/auth/login, /api/auth/validate)
- 用户账号通过 server/config.yaml 配置
- 前端路由保护(未登录跳转登录页)
- 侧边栏显示当前用户信息
- 支持退出登录功能
2026-01-11 18:50:01 +08:00

272 lines
7.1 KiB
Go

package config
import (
"flag"
"fmt"
"os"
"path/filepath"
"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
}
}
// ResolvePath 解析路径(相对路径转为绝对路径)
func ResolvePath(path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(Global.ProjectRoot, path)
}