You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言如何从Multipart.File格式CSV提取数据并写入模板CSV?

解决Go中处理多表头CSV并映射结构体的问题

你的CSV文件属于非标准格式:包含前置无关行、多个独立的表头-数据块,且数值带千分位逗号。下面是分步解决方案,包含提取指定列、映射结构体、写入模板CSV的完整实现。

一、核心思路

  1. 过滤无效行:跳过前置的公司信息行(列数不足4的行);
  2. 识别表头:通过检测是否包含Msrp/Dealer(支持大小写变体)来定位表头行,记录列名到索引的映射;
  3. 解析数据行:针对每个表头块,提取对应列的数值,处理千分位逗号后转换为整数;
  4. 映射结构体:将解析后的数据赋值给结构体字段;
  5. 写入模板:将结构体数据按模板格式写入新CSV。

二、完整代码实现

1. 定义结构体

调整结构体字段名更清晰,明确对应CSV列:

type Part struct {
    PartName string // 对应CSV第一列(如VGA-1、CASE-1)
    Price    int    // 对应CSV的Dealer列
    Msrp     int    // 对应CSV的Msrp列
}

2. 处理上传的CSV文件

import (
    "encoding/csv"
    "fmt"
    "io"
    "strconv"
    "strings"
)

// ProcessUploadedCSV 从Multipart.File中解析CSV并映射到Part切片
func ProcessUploadedCSV(f io.Reader) ([]Part, error) {
    reader := csv.NewReader(f)
    records, err := reader.ReadAll()
    if err != nil {
        return nil, fmt.Errorf("读取CSV失败: %w", err)
    }

    var parts []Part
    var currentColIndex map[string]int // 存储当前表头的列名与索引映射
    // 定义目标列的大小写变体,兼容不同写法
    targetCols := map[string]map[string]bool{
        "Dealer": {"Dealer": true, "DEALER": true, "dealer": true},
        "Msrp":   {"Msrp": true, "MSRP": true, "msrp": true},
    }

    for _, row := range records {
        // 跳过空行或列数不足的无效行
        if len(row) < 4 {
            continue
        }

        // 检查当前行是否是表头
        isHeader := false
        colIndex := make(map[string]int)
        for idx, col := range row {
            trimmedCol := strings.TrimSpace(col)
            // 只要包含Dealer或Msrp的任意变体,就判定为表头
            if targetCols["Dealer"][trimmedCol] || targetCols["Msrp"][trimmedCol] {
                isHeader = true
            }
            colIndex[trimmedCol] = idx
        }

        if isHeader {
            // 更新当前表头的列索引映射
            currentColIndex = colIndex
            continue
        }

        // 未找到有效表头前,跳过数据行
        if currentColIndex == nil {
            continue
        }

        // 提取并处理各列数据
        partName := strings.TrimSpace(row[0])

        // 处理Dealer列(转换为int,先去掉千分位逗号)
        dealerVal := strings.TrimSpace(row[currentColIndex["Dealer"]])
        dealerVal = strings.ReplaceAll(dealerVal, ",", "")
        price, err := strconv.Atoi(dealerVal)
        if err != nil {
            return nil, fmt.Errorf("解析Dealer价格%s失败: %w", dealerVal, err)
        }

        // 处理Msrp列
        msrpVal := strings.TrimSpace(row[currentColIndex["Msrp"]])
        msrpVal = strings.ReplaceAll(msrpVal, ",", "")
        msrp, err := strconv.Atoi(msrpVal)
        if err != nil {
            return nil, fmt.Errorf("解析Msrp价格%s失败: %w", msrpVal, err)
        }

        // 添加到结构体切片
        parts = append(parts, Part{
            PartName: partName,
            Price:    price,
            Msrp:     msrp,
        })
    }

    return parts, nil
}

3. 将数据写入CSV模板

// WriteToTemplateCSV 将Part切片写入指定的CSV模板(输出流)
func WriteToTemplateCSV(parts []Part, w io.Writer) error {
    writer := csv.NewWriter(w)
    defer writer.Flush()

    // 写入模板表头(可根据你的模板需求修改)
    if err := writer.Write([]string{"部件名称", "经销商价格", "建议零售价"}); err != nil {
        return fmt.Errorf("写入表头失败: %w", err)
    }

    // 写入数据行,还原千分位格式
    for _, part := range parts {
        priceStr := formatWithCommas(part.Price)
        msrpStr := formatWithCommas(part.Msrp)
        if err := writer.Write([]string{part.PartName, priceStr, msrpStr}); err != nil {
            return fmt.Errorf("写入行%s失败: %w", part.PartName, err)
        }
    }

    return nil
}

// formatWithCommas 给整数添加千分位分隔符
func formatWithCommas(n int) string {
    s := strconv.Itoa(n)
    if len(s) <= 3 {
        return s
    }
    var sb strings.Builder
    remainder := len(s) % 3
    sb.WriteString(s[:remainder])
    for i := remainder; i < len(s); i += 3 {
        if remainder != 0 {
            sb.WriteString(",")
        }
        sb.WriteString(s[i:i+3])
        remainder = 1
    }
    return sb.String()
}

三、使用示例

在处理上传文件的Handler中调用:

func UploadHandler(w http.ResponseWriter, r *http.Request) {
    // 解析上传的文件
    r.ParseMultipartForm(10 << 20) // 10MB
    file, _, err := r.FormFile("csv-file")
    if err != nil {
        http.Error(w, "获取上传文件失败", http.StatusBadRequest)
        return
    }
    defer file.Close()

    // 处理CSV
    parts, err := ProcessUploadedCSV(file)
    if err != nil {
        http.Error(w, fmt.Sprintf("处理CSV失败: %v", err), http.StatusInternalServerError)
        return
    }

    // 写入模板CSV(这里直接输出到响应,也可以写入文件)
    w.Header().Set("Content-Type", "text/csv")
    w.Header().Set("Content-Disposition", "attachment; filename=output.csv")
    if err := WriteToTemplateCSV(parts, w); err != nil {
        http.Error(w, fmt.Sprintf("生成CSV失败: %v", err), http.StatusInternalServerError)
        return
    }
}

四、关键优化点

  • 兼容大小写:通过map存储列名的大小写变体,避免因列名大小写不一致导致的解析失败;
  • 灵活列索引:用map记录列名到索引的映射,无需硬编码列位置,适配表头顺序变化;
  • 错误处理:每个步骤都添加明确的错误信息,便于调试;
  • 千分位处理:解析时去除逗号转整数,写入时还原千分位格式,保证数据格式一致性。

内容的提问来源于stack exchange,提问作者carlcaroline

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 10:17:12