refactor: 重构项目结构
- 将 Python 代码移至 analyzer/ 目录(含 venv) - 拆分 Go 服务器代码为模块化结构: - config/: 配置加载 - model/: 请求/响应模型 - service/: 业务逻辑 - handler/: API处理器 - 添加 .gitignore 文件 - 删除旧的独立脚本文件
This commit is contained in:
72
server/handler/review.go
Normal file
72
server/handler/review.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"billai-server/config"
|
||||
"billai-server/model"
|
||||
"billai-server/service"
|
||||
)
|
||||
|
||||
// Review 获取需要复核的记录
|
||||
func Review(c *gin.Context) {
|
||||
// 获取文件名参数
|
||||
fileName := c.Query("file")
|
||||
if fileName == "" {
|
||||
c.JSON(http.StatusBadRequest, model.ReviewResponse{
|
||||
Result: false,
|
||||
Message: "请提供文件名参数 (file)",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 构建文件路径
|
||||
outputDirAbs := config.ResolvePath(config.Global.OutputDir)
|
||||
filePath := filepath.Join(outputDirAbs, fileName)
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, model.ReviewResponse{
|
||||
Result: false,
|
||||
Message: "文件不存在: " + fileName,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 判断文件格式
|
||||
format := "csv"
|
||||
if strings.HasSuffix(fileName, ".json") {
|
||||
format = "json"
|
||||
}
|
||||
|
||||
// 提取需要复核的记录
|
||||
records := service.ExtractNeedsReview(filePath, format)
|
||||
|
||||
// 统计高低优先级数量
|
||||
highCount := 0
|
||||
lowCount := 0
|
||||
for _, r := range records {
|
||||
if r.ReviewLevel == "HIGH" {
|
||||
highCount++
|
||||
} else if r.ReviewLevel == "LOW" {
|
||||
lowCount++
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, model.ReviewResponse{
|
||||
Result: true,
|
||||
Message: "获取成功",
|
||||
Data: &model.ReviewData{
|
||||
Total: len(records),
|
||||
High: highCount,
|
||||
Low: lowCount,
|
||||
Records: records,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
119
server/handler/upload.go
Normal file
119
server/handler/upload.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"billai-server/config"
|
||||
"billai-server/model"
|
||||
)
|
||||
|
||||
// Upload 处理账单上传和清理请求
|
||||
func Upload(c *gin.Context) {
|
||||
// 1. 获取上传的文件
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.UploadResponse{
|
||||
Result: false,
|
||||
Message: "请上传账单文件 (参数名: file)",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 2. 解析请求参数
|
||||
var req model.UploadRequest
|
||||
c.ShouldBind(&req)
|
||||
if req.Format == "" {
|
||||
req.Format = "csv"
|
||||
}
|
||||
|
||||
// 3. 保存上传的文件
|
||||
timestamp := time.Now().Format("20060102_150405")
|
||||
inputFileName := fmt.Sprintf("%s_%s", timestamp, header.Filename)
|
||||
uploadDirAbs := config.ResolvePath(config.Global.UploadDir)
|
||||
inputPath := filepath.Join(uploadDirAbs, inputFileName)
|
||||
|
||||
dst, err := os.Create(inputPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.UploadResponse{
|
||||
Result: false,
|
||||
Message: "保存文件失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
io.Copy(dst, file)
|
||||
|
||||
// 4. 构建输出文件路径
|
||||
baseName := strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename))
|
||||
outputExt := ".csv"
|
||||
if req.Format == "json" {
|
||||
outputExt = ".json"
|
||||
}
|
||||
outputFileName := fmt.Sprintf("%s_%s_cleaned%s", timestamp, baseName, outputExt)
|
||||
outputDirAbs := config.ResolvePath(config.Global.OutputDir)
|
||||
outputPath := filepath.Join(outputDirAbs, outputFileName)
|
||||
|
||||
// 5. 构建命令参数
|
||||
cleanScriptAbs := config.ResolvePath(config.Global.CleanScript)
|
||||
args := []string{cleanScriptAbs, inputPath, outputPath}
|
||||
if req.Year != "" {
|
||||
args = append(args, "--year", req.Year)
|
||||
}
|
||||
if req.Month != "" {
|
||||
args = append(args, "--month", req.Month)
|
||||
}
|
||||
if req.Start != "" {
|
||||
args = append(args, "--start", req.Start)
|
||||
}
|
||||
if req.End != "" {
|
||||
args = append(args, "--end", req.End)
|
||||
}
|
||||
if req.Format != "" {
|
||||
args = append(args, "--format", req.Format)
|
||||
}
|
||||
|
||||
// 6. 执行 Python 脚本
|
||||
pythonPathAbs := config.ResolvePath(config.Global.PythonPath)
|
||||
cmd := exec.Command(pythonPathAbs, args...)
|
||||
cmd.Dir = config.Global.ProjectRoot
|
||||
output, err := cmd.CombinedOutput()
|
||||
outputStr := string(output)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.UploadResponse{
|
||||
Result: false,
|
||||
Message: "处理失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 7. 检测账单类型
|
||||
billType := ""
|
||||
if strings.Contains(outputStr, "支付宝") {
|
||||
billType = "alipay"
|
||||
} else if strings.Contains(outputStr, "微信") {
|
||||
billType = "wechat"
|
||||
}
|
||||
|
||||
// 8. 返回成功响应
|
||||
c.JSON(http.StatusOK, model.UploadResponse{
|
||||
Result: true,
|
||||
Message: "处理成功",
|
||||
Data: &model.UploadData{
|
||||
BillType: billType,
|
||||
FileURL: fmt.Sprintf("/download/%s", outputFileName),
|
||||
FileName: outputFileName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user