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

JS向Go服务上传文件时multipart表单文件名路径前缀丢失如何解决

前缀丢失的原因

Go标准库net/http解析multipart表单的filename字段时,内部调用了filepath.Base()方法对文件名做清洗,会自动去掉所有路径前缀,只保留最后一级的纯文件名。这个是标准库的默认安全设计,目的是兼容不同客户端上传时携带的操作系统绝对路径,同时避免恶意用户构造带路径遍历的文件名引发安全问题。

你在原始请求体里能看到完整前缀,说明前端传值是正常的,就是标准库解析阶段给做了截断。

保留前缀的两种方案

方案1:前端新增表单字段传路径(推荐)

不需要改现有上传逻辑,只需在FormData中额外新增对应字段存储完整路径,后端直接读取普通表单值即可,兼容性最高,也符合规范:
前端示例:

const formData = new FormData()
const targetPath = `img/${hashKey}.png`
const file = new File([fileBlob], targetPath, { type: 'image/png' })
formData.append('files', file)
// 新增字段传完整路径
formData.append('file_paths', targetPath)

后端读取:

if err := r.ParseMultipartForm(32 << 20); err != nil {
    // 错误处理
    return
}
// 路径列表和文件列表顺序一一对应
pathList := r.MultipartForm.Value["file_paths"]
for idx, fileHeader := range r.MultipartForm.File["files"] {
    fullFilePath := pathList[idx]
    // 后续处理逻辑
}

方案2:后端手动解析multipart流

如果不想改前端代码,可以绕过标准库的自动解析逻辑,直接用MultipartReader读取原始part信息,拿到未清洗的原始文件名:

import (
    "io"
    "mime/multipart"
)

// 替换原来的ParseMultipartForm逻辑
reader, err := r.MultipartReader()
if err != nil {
    // 错误处理
    return
}

// 限制最大上传大小和之前的32M保持一致
maxSize := int64(32 << 20)
r.Body = http.MaxBytesReader(w, r.Body, maxSize)

for {
    part, err := reader.NextPart()
    if err == io.EOF {
        break
    }
    if err != nil {
        // 错误处理
        return
    }
    defer part.Close()

    if part.FormName() == "files" {
        // 这里拿到的FileName就是带img/前缀的原始值
        rawFileName := part.FileName()
        // 读取part的内容就是文件二进制流,自行处理存储即可
        fileContent, err := io.ReadAll(part)
        if err != nil {
            // 错误处理
            return
        }
        // 后续处理逻辑
    }
}

注意:使用带路径的文件名做存储时,一定要做好路径校验,限制最终生成的存储路径只能在你指定的业务目录范围内,避免出现路径遍历安全漏洞。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 14:24:02