Files
billai/server/main.go
cheliangzhao eb76c3a8dc fix: 修复微信账单金额解析问题(半角¥符号支持)
- 修复 parse_amount 函数同时支持全角¥和半角¥
- 新增 MonthRangePicker 日期选择组件
- 新增 /api/monthly-stats 接口获取月度统计
- 分析页面月度趋势使用全量数据
- 新增健康检查路由
2026-01-10 19:21:24 +08:00

167 lines
5.2 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 main
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/gin-gonic/gin"
"billai-server/adapter"
adapterHttp "billai-server/adapter/http"
"billai-server/adapter/python"
"billai-server/config"
"billai-server/database"
"billai-server/repository"
repoMongo "billai-server/repository/mongo"
"billai-server/router"
)
func main() {
// 加载配置
config.Load()
// 解析路径
uploadDirAbs := config.ResolvePath(config.Global.UploadDir)
outputDirAbs := config.ResolvePath(config.Global.OutputDir)
pythonPathAbs := config.ResolvePath(config.Global.PythonPath)
// 确保目录存在
os.MkdirAll(uploadDirAbs, 0755)
os.MkdirAll(outputDirAbs, 0755)
// 打印配置信息
printBanner(pythonPathAbs, uploadDirAbs, outputDirAbs)
// 检查 Python 是否存在
if _, err := os.Stat(pythonPathAbs); os.IsNotExist(err) {
fmt.Printf("⚠️ 警告: Python 路径不存在: %s\n", pythonPathAbs)
fmt.Println(" 请在配置文件中指定正确的 Python 路径")
}
// 初始化适配器(外部服务交互层)
initAdapters()
// 初始化数据层
if err := initRepository(); err != nil {
fmt.Printf("⚠️ 警告: 数据层初始化失败: %v\n", err)
fmt.Println(" 账单数据将不会存储到数据库")
os.Exit(1)
}
// 连接 MongoDB保持兼容旧代码后续可移除
if err := database.Connect(); err != nil {
fmt.Printf("⚠️ 警告: MongoDB 连接失败: %v\n", err)
fmt.Println(" 账单数据将不会存储到数据库")
os.Exit(1)
} else {
// 优雅关闭时断开连接
defer database.Disconnect()
}
// 创建路由
r := gin.Default()
// 注册路由
router.Setup(r, router.Config{
OutputDir: outputDirAbs,
Version: config.Global.Version,
})
// 监听系统信号
go func() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("\n🛑 正在关闭服务...")
database.Disconnect()
os.Exit(0)
}()
// 启动服务
printAPIInfo()
r.Run(":" + config.Global.Port)
}
// printBanner 打印启动横幅
func printBanner(pythonPath, uploadDir, outputDir string) {
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Println("📦 BillAI 账单分析服务")
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf("📁 项目根目录: %s\n", config.Global.ProjectRoot)
fmt.Printf("<22> 适配器模式: %s\n", config.Global.AnalyzerMode)
if config.Global.AnalyzerMode == "http" {
fmt.Printf("🌐 分析服务: %s\n", config.Global.AnalyzerURL)
} else {
fmt.Printf("🐍 Python路径: %s\n", pythonPath)
}
fmt.Printf("📂 上传目录: %s\n", uploadDir)
fmt.Printf("📂 输出目录: %s\n", outputDir)
fmt.Printf("🍃 MongoDB: %s/%s\n", config.Global.MongoURI, config.Global.MongoDatabase)
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
}
// printAPIInfo 打印 API 信息
func printAPIInfo() {
fmt.Printf("\n🚀 服务已启动: http://localhost:%s\n", config.Global.Port)
fmt.Println("📝 API 接口:")
fmt.Println(" POST /api/upload - 上传并分析账单")
fmt.Println(" GET /api/bills - 获取账单列表(支持分页和时间筛选)")
fmt.Println(" GET /api/review - 获取需要复核的记录")
fmt.Println(" GET /download/* - 下载结果文件")
fmt.Println(" GET /health - 健康检查")
fmt.Println()
}
// initAdapters 初始化适配器(外部服务交互层)
// 在这里配置与外部系统的交互方式
// 支持两种模式: http (推荐) 和 subprocess
func initAdapters() {
var cleaner adapter.Cleaner
switch config.Global.AnalyzerMode {
case "http":
// 使用 HTTP API 调用 Python 服务(推荐)
httpCleaner := adapterHttp.NewCleaner(config.Global.AnalyzerURL)
// 检查服务健康状态
if err := httpCleaner.HealthCheck(); err != nil {
fmt.Printf("⚠️ 警告: Python 分析服务不可用 (%s): %v\n", config.Global.AnalyzerURL, err)
fmt.Println(" 请确保分析服务已启动: cd analyzer && python server.py")
} else {
fmt.Printf("🌐 已连接到分析服务: %s\n", config.Global.AnalyzerURL)
}
cleaner = httpCleaner
case "subprocess":
// 使用子进程调用 Python 脚本(传统模式)
pythonCleaner := python.NewCleaner()
fmt.Println("🐍 使用子进程模式调用 Python")
cleaner = pythonCleaner
default:
// 默认使用 HTTP 模式
cleaner = adapterHttp.NewCleaner(config.Global.AnalyzerURL)
fmt.Printf("🌐 使用 HTTP 模式 (默认): %s\n", config.Global.AnalyzerURL)
}
adapter.SetCleaner(cleaner)
fmt.Println("🔌 适配器初始化完成")
}
// initRepository 初始化数据存储层
// 在这里配置数据持久化方式
// 后续可以通过修改这里来切换不同的存储实现(如 PostgreSQL、MySQL 等)
func initRepository() error {
// 初始化 MongoDB 存储
mongoRepo := repoMongo.NewRepository()
if err := mongoRepo.Connect(); err != nil {
return err
}
repository.SetRepository(mongoRepo)
fmt.Println("💾 数据层初始化完成")
return nil
}