feat: 支持ZIP压缩包上传(含密码保护)

This commit is contained in:
CHE LIANG ZHAO
2026-01-23 13:46:45 +08:00
parent 49e3176e6b
commit a97a8d6a20
22 changed files with 973 additions and 72 deletions

View File

@@ -17,6 +17,12 @@ type CleanResult struct {
Output string // 脚本输出信息
}
// ConvertResult 格式转换结果
type ConvertResult struct {
OutputPath string // 转换后的文件路径
BillType string // 检测到的账单类型: alipay/wechat
}
// Cleaner 账单清洗器接口
// 负责将原始账单数据清洗为标准格式
type Cleaner interface {
@@ -25,4 +31,9 @@ type Cleaner interface {
// outputPath: 输出文件路径
// opts: 清洗选项
Clean(inputPath, outputPath string, opts *CleanOptions) (*CleanResult, error)
// Convert 转换账单文件格式xlsx -> csv处理 GBK 编码等)
// inputPath: 输入文件路径
// 返回: 转换后的文件路径, 检测到的账单类型, 错误
Convert(inputPath string) (outputPath string, billType string, err error)
}

View File

@@ -185,6 +185,88 @@ func (c *Cleaner) downloadFile(remotePath, localPath string) error {
return nil
}
// ConvertResponse 转换响应
type ConvertResponse struct {
Success bool `json:"success"`
BillType string `json:"bill_type"`
Message string `json:"message"`
OutputPath string `json:"output_path,omitempty"`
}
// Convert 转换账单文件格式xlsx -> csv处理 GBK 编码等)
func (c *Cleaner) Convert(inputPath string) (outputPath string, billType string, err error) {
// 打开输入文件
file, err := os.Open(inputPath)
if err != nil {
return "", "", fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
// 创建 multipart form
var body bytes.Buffer
writer := multipart.NewWriter(&body)
// 添加文件
part, err := writer.CreateFormFile("file", filepath.Base(inputPath))
if err != nil {
return "", "", fmt.Errorf("创建表单文件失败: %w", err)
}
if _, err := io.Copy(part, file); err != nil {
return "", "", fmt.Errorf("复制文件内容失败: %w", err)
}
writer.Close()
// 发送转换请求
fmt.Printf("🌐 调用转换服务: %s/convert\n", c.baseURL)
req, err := http.NewRequest("POST", c.baseURL+"/convert", &body)
if err != nil {
return "", "", fmt.Errorf("创建请求失败: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := c.httpClient.Do(req)
if err != nil {
return "", "", fmt.Errorf("HTTP 请求失败: %w", err)
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", "", fmt.Errorf("读取响应失败: %w", err)
}
// 处理错误响应
if resp.StatusCode != http.StatusOK {
var errResp ErrorResponse
if err := json.Unmarshal(respBody, &errResp); err == nil {
return "", "", fmt.Errorf("转换失败: %s", errResp.Detail)
}
return "", "", fmt.Errorf("转换失败: HTTP %d - %s", resp.StatusCode, string(respBody))
}
// 解析成功响应
var convertResp ConvertResponse
if err := json.Unmarshal(respBody, &convertResp); err != nil {
return "", "", fmt.Errorf("解析响应失败: %w", err)
}
// 下载转换后的文件到本地(与输入文件同目录,但扩展名改为 .csv
localOutputPath := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))] + ".csv"
fmt.Printf(" 下载转换后文件: %s -> %s\n", convertResp.OutputPath, localOutputPath)
if err := c.downloadFile(convertResp.OutputPath, localOutputPath); err != nil {
return "", "", fmt.Errorf("下载转换结果失败: %w", err)
}
// 验证文件是否存在
if _, err := os.Stat(localOutputPath); err != nil {
return "", "", fmt.Errorf("下载后文件不存在: %s", localOutputPath)
}
fmt.Printf(" 文件下载成功,已保存到: %s\n", localOutputPath)
return localOutputPath, convertResp.BillType, nil
}
// HealthCheck 检查 Python 服务健康状态
func (c *Cleaner) HealthCheck() error {
resp, err := c.httpClient.Get(c.baseURL + "/health")

View File

@@ -90,5 +90,11 @@ func detectBillTypeFromOutput(output string) string {
return ""
}
// Convert 转换账单文件格式xlsx -> csv处理 GBK 编码等)
// 子进程模式不支持此功能,请使用 HTTP 模式
func (c *Cleaner) Convert(inputPath string) (outputPath string, billType string, err error) {
return "", "", fmt.Errorf("子进程模式不支持文件格式转换,请使用 HTTP 模式 (analyzer_mode: http)")
}
// 确保 Cleaner 实现了 adapter.Cleaner 接口
var _ adapter.Cleaner = (*Cleaner)(nil)