在Golang的Web开发中,处理文件上传是很常见的场景,其中获取上传的Multipart文件的大小和MIME类型是后续进行文件校验、存储处理的基础操作。Golang的标准库net/http已经提供了完善的Multipart文件解析能力,结合mime/multipart和io包就能完成相关信息的获取。

Multipart文件上传的基本解析流程
首先我们需要先了解Golang中解析Multipart文件的基本步骤,只有正确解析出文件句柄,才能进一步获取文件的相关信息。处理文件上传的核心是先调用ParseMultipartForm方法解析请求中的表单数据,然后从解析结果中获取文件头信息。
下面是一个基础的Multipart文件解析示例:
package main
import (
"fmt"
"net/http"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 解析Multipart表单,最大内存设置为10MB
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, "解析表单失败: "+err.Error(), http.StatusBadRequest)
return
}
// 获取上传的文件,假设前端表单中文件字段名为file
file, fileHeader, err := r.FormFile("file")
if err != nil {
http.Error(w, "获取文件失败: "+err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
// 后续可以基于file和fileHeader获取文件信息
}
获取Multipart文件的大小
获取文件大小有两种常用方式,一种是直接通过文件头multipart.FileHeader的Size字段获取,另一种是通过读取文件内容计算大小,两种方式各有适用场景。
通过FileHeader的Size字段获取
multipart.FileHeader结构体中已经内置了Size字段,该字段记录了上传文件的大小,单位是字节,这种方式获取效率最高,不需要额外读取文件内容。
// 接上面的uploadHandler函数中的代码
// 通过FileHeader的Size字段获取文件大小,单位字节
fileSize := fileHeader.Size
fmt.Printf("文件大小为: %d 字节,约 %.2f MBn", fileSize, float64(fileSize)/(1024*1024))
通过读取文件内容计算大小
如果需要同时读取文件内容做其他处理,也可以通过读取文件的所有字节来计算大小,这种方式会额外消耗内存,适合小文件场景。
import (
"io"
"fmt"
)
// 接上面的uploadHandler函数中的代码
// 读取文件所有内容计算大小
fileBytes, err := io.ReadAll(file)
if err != nil {
http.Error(w, "读取文件失败: "+err.Error(), http.StatusInternalServerError)
return
}
fileSizeByRead := len(fileBytes)
fmt.Printf("通过读取内容获取的文件大小为: %d 字节n", fileSizeByRead)
获取Multipart文件的MIME类型
获取MIME类型也有两种方式,一种是通过文件头的Header字段获取前端传递的类型,另一种是通过文件内容检测真实的MIME类型,后者更准确,因为前端传递的类型可以被篡改。
获取前端传递的MIME类型
前端在上传文件时会在请求头中携带文件的MIME类型,我们可以通过multipart.FileHeader的Header字段获取这个值,但是需要注意这个值不可信。
// 接上面的uploadHandler函数中的代码
// 获取前端传递的MIME类型
contentType := fileHeader.Header.Get("Content-Type")
fmt.Printf("前端传递的MIME类型为: %sn", contentType)
通过文件内容检测真实MIME类型
Golang的net/http包提供了DetectContentType函数,可以通过读取文件的前512个字节来检测真实的MIME类型,这种方式更加可靠。
import (
"net/http"
"fmt"
)
// 接上面的uploadHandler函数中的代码
// 检测真实的MIME类型,需要读取文件前512字节
// 注意:如果之前已经读取过文件内容,需要重新定位文件指针,或者直接使用之前读取的字节
buffer := make([]byte, 512)
_, err = file.Read(buffer)
if err != nil && err != io.EOF {
http.Error(w, "读取文件内容失败: "+err.Error(), http.StatusInternalServerError)
return
}
realContentType := http.DetectContentType(buffer)
fmt.Printf("检测到的真实MIME类型为: %sn", realContentType)
// 如果之前读取过文件,需要重新定位指针到开头,方便后续处理
_, err = file.Seek(0, 0)
if err != nil {
http.Error(w, "重置文件指针失败: "+err.Error(), http.StatusInternalServerError)
return
}
完整示例代码
下面是一个完整的文件上传处理示例,包含了获取文件大小和MIME类型的完整逻辑:
package main
import (
"fmt"
"io"
"net/http"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 只处理POST请求
if r.Method != http.MethodPost {
http.Error(w, "只支持POST请求", http.StatusMethodNotAllowed)
return
}
// 解析Multipart表单,最大内存10MB
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, "解析表单失败: "+err.Error(), http.StatusBadRequest)
return
}
// 获取上传的文件
file, fileHeader, err := r.FormFile("file")
if err != nil {
http.Error(w, "获取文件失败: "+err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
// 1. 获取文件大小
fileSize := fileHeader.Size
fmt.Printf("文件大小: %d 字节n", fileSize)
// 2. 获取前端传递的MIME类型
clientContentType := fileHeader.Header.Get("Content-Type")
fmt.Printf("前端传递的MIME类型: %sn", clientContentType)
// 3. 检测真实MIME类型
buffer := make([]byte, 512)
_, err = file.Read(buffer)
if err != nil && err != io.EOF {
http.Error(w, "读取文件失败: "+err.Error(), http.StatusInternalServerError)
return
}
realContentType := http.DetectContentType(buffer)
fmt.Printf("真实MIME类型: %sn", realContentType)
// 重置文件指针,方便后续保存文件等操作
_, err = file.Seek(0, 0)
if err != nil {
http.Error(w, "重置文件指针失败: "+err.Error(), http.StatusInternalServerError)
return
}
// 返回结果给前端
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "文件大小: %d字节, 前端MIME类型: %s, 真实MIME类型: %s", fileSize, clientContentType, realContentType)
}
func main() {
http.HandleFunc("/upload", uploadHandler)
fmt.Println("服务启动在 :8080 端口")
http.ListenAndServe(":8080", nil)
}
注意事项
- 解析Multipart表单时设置的最大内存值,超过该值的文件部分会被存储到临时文件中,不影响文件大小的获取。
- 前端传递的MIME类型可以被伪造,如果需要严格校验文件类型,一定要使用内容检测的方式,不要只依赖前端传递的值。
- 检测MIME类型时只需要读取文件前512字节即可,不需要读取整个文件,避免大文件场景下的内存浪费。
- 如果后续需要对文件做保存等处理,一定要在检测MIME类型后重置文件指针到开头,否则会出现文件内容读取不完整的问题。