feat: 完善项目架构并增强分析页面功能

- 新增项目文档和 Docker 配置
  - 添加 README.md 和 TODO.md 项目文档
  - 为各服务添加 Dockerfile 和 docker-compose 配置

- 重构后端架构
  - 新增 adapter 层(HTTP/Python 适配器)
  - 新增 repository 层(数据访问抽象)
  - 新增 router 模块统一管理路由
  - 新增账单处理 handler

- 扩展前端 UI 组件库
  - 新增 Calendar、DateRangePicker、Drawer、Popover 等组件
  - 集成 shadcn-svelte 组件库

- 增强分析页面功能
  - 添加时间范围筛选器(支持本月默认值)
  - 修复 DateRangePicker 默认值显示问题
  - 优化数据获取和展示逻辑

- 完善分析器服务
  - 新增 FastAPI 服务接口
  - 改进账单清理器实现
This commit is contained in:
2026-01-10 01:15:52 +08:00
parent 94f8ea12e6
commit 087ae027cc
96 changed files with 4301 additions and 482 deletions

View File

@@ -0,0 +1,94 @@
// Package python 实现通过子进程调用 Python 脚本的清洗器
package python
import (
"fmt"
"os/exec"
"strings"
"billai-server/adapter"
"billai-server/config"
)
// Cleaner 通过子进程调用 Python 脚本的清洗器实现
type Cleaner struct {
pythonPath string // Python 解释器路径
scriptPath string // 清洗脚本路径
workDir string // 工作目录
}
// NewCleaner 创建 Python 清洗器
func NewCleaner() *Cleaner {
return &Cleaner{
pythonPath: config.ResolvePath(config.Global.PythonPath),
scriptPath: config.ResolvePath(config.Global.CleanScript),
workDir: config.Global.ProjectRoot,
}
}
// NewCleanerWithConfig 使用自定义配置创建 Python 清洗器
func NewCleanerWithConfig(pythonPath, scriptPath, workDir string) *Cleaner {
return &Cleaner{
pythonPath: pythonPath,
scriptPath: scriptPath,
workDir: workDir,
}
}
// Clean 执行 Python 清洗脚本
func (c *Cleaner) Clean(inputPath, outputPath string, opts *adapter.CleanOptions) (*adapter.CleanResult, error) {
// 构建命令参数
args := []string{c.scriptPath, inputPath, outputPath}
if opts != nil {
if opts.Year != "" {
args = append(args, "--year", opts.Year)
}
if opts.Month != "" {
args = append(args, "--month", opts.Month)
}
if opts.Start != "" {
args = append(args, "--start", opts.Start)
}
if opts.End != "" {
args = append(args, "--end", opts.End)
}
if opts.Format != "" {
args = append(args, "--format", opts.Format)
}
}
// 执行 Python 脚本
fmt.Printf("🐍 执行清洗脚本...\n")
cmd := exec.Command(c.pythonPath, args...)
cmd.Dir = c.workDir
output, err := cmd.CombinedOutput()
outputStr := string(output)
if err != nil {
return nil, fmt.Errorf("清洗脚本执行失败: %w\n输出: %s", err, outputStr)
}
// 从输出中检测账单类型
billType := detectBillTypeFromOutput(outputStr)
return &adapter.CleanResult{
BillType: billType,
Output: outputStr,
}, nil
}
// detectBillTypeFromOutput 从 Python 脚本输出中检测账单类型
func detectBillTypeFromOutput(output string) string {
if strings.Contains(output, "支付宝") {
return "alipay"
}
if strings.Contains(output, "微信") {
return "wechat"
}
return ""
}
// 确保 Cleaner 实现了 adapter.Cleaner 接口
var _ adapter.Cleaner = (*Cleaner)(nil)