Go语言文件上传格式校验

1
2
Go语言处理文件上传格式检验,对上传的文件进行格式检查!
本文将使用一个常用的开源包进行文件格式的检验,主要介绍包的使用;适合一般的文件上传格式限制业务场景~

Go语言处理文件上传格式校验

安装第三方包
1
go get -v -u github.com/h2non/filetype
处理文件上传业务逻辑
1、gozero 框架的路由定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "v1"

@server(
group: v1/audio
middleware: UserAgentMiddleware
)
service app-api {
@doc "音频上传"
@handler audioUpload
post /v1/app/audioUpload (AudioUploadReq) returns (AudioUploadResp)
}

AudioUploadReq {
ID int `form:"id"` // 除了文件上传要选择文件,这个字段是关联业务 ID 字段
}

AudioUploadResp {
FileUrl string `json:"file_url"` // 文件上传成功之后 OSS 能访问到的文件地址
}
2、gozero 生成代码后上传业务处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
const (
maxFileSize = 1024 * 1024 * 50 // 最大的文件上传大小(50MB)
uploadPath = "./upload" // 本地上传文件临时目录
)

func (l *AudioUploadLogic) AudioUpload(req *types.AudioUploadReq, r *http.Request) (resp *types.AudioUploadResp, err error) {
// go-zero 框架的最大文件上传默认是 1M 超过 1M 记得在配置文件添加配置
// MaxBytes: 52428800 # 这里设置为 50MB
_ = r.ParseMultipartForm(maxFileSize)
resp = new(types.AudioUploadResp)

sUserId := l.ctx.Value("userid").(string)
iUserId, _ := strconv.Atoi(sUserId)

file, fileHeader, err := r.FormFile("file")
if err != nil {
return nil, errors.New("请选择要上传的文件!")
}
defer file.Close()

// 文件大小检查
if fileHeader.Size > maxFileSize {
return nil, errors.New("文件已超过50M!")
}

// 文件格式检查
buf := make([]byte, 261)
n, _ := io.ReadFull(file, buf)
buf = buf[:n]

// MP3 的 MIME 通常为 "audio/mpeg"
if !filetype.IsMIME(buf, "audio/mpeg") {
return nil, errors.New("文件格式不正确!")
}

// 判断路径是否存在
path := uploadPath + "/" + time.Now().Format(time.DateOnly)
_, err = os.ReadDir(path)
if err != nil {
_ = os.MkdirAll(path, fs.ModePerm)
}

// 生成随机的文件名包括 文件后缀
randomFileName := fmt.Sprintf("%s%s", uuid.New().String(), filepath.Ext(fileHeader.Filename))
// 生成本地上传的文件路径 目录 + 随机文件名称
localFileName := fmt.Sprintf("%s/%s", path, randomFileName)

logx.Infof("randomFileName: %s", randomFileName)
logx.Infof("localFileName: %s", localFileName)

// 创建本地文件
f, err := os.OpenFile(localFileName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return nil, errors.New("上传失败、请重新上传!")
}
defer f.Close()

// 将上传的文件内容拷贝到目标文件
_, err = io.Copy(f, file)
if err != nil {
return nil, errors.New("上传失败、请重新上传!")
}
// 上传至阿里云 OSS 的文件位置
uploadFileName := "manage/audio/" + randomFileName
uploadURL, err := pkg.UploadFileToOSS(l.svcCtx.Config, fileHeader, localFileName, uploadFileName)
if err != nil {
//return nil, err
return nil, errors.New("上传失败、请重新上传!")
}
logx.Infof("url: %s", uploadURL)

db := l.svcCtx.DB
ret := db.Model(&model.AudioOrderInfo{}).Where("id = ?", req.ID).Updates(map[string]interface{}{
"user_id": iUserId,
"audio_url": uploadURL,
"updated_at": time.Now(),
})
if ret.Error != nil || ret.RowsAffected == 0 {
return nil, errors.New("上传失败、请重新上传!")
}
resp.FileUrl = uploadURL

return resp, nil
}

3、文件格式校验说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 文件格式检查
buf := make([]byte, 261)
n, _ := io.ReadFull(file, buf)
buf = buf[:n]

// MP3 的 MIME 通常为 "audio/mpeg"
if !filetype.IsMIME(buf, "audio/mpeg") {
return nil, errors.New("文件格式不正确!")
}
上面对文件格式的校验主要是使用的`filetype.IsMIME`指定必须符合指定的文件才能进行文件上传操作~

或者获取上传文件的 `MIME` 信息,然后进行文件类型检查
mime, _ := filetype.Get(buf)
logx.Infof("mime:%s", mime) // mp3 音频文件的 mime 信息为:"audio/mpeg"

3、上传到阿里云 oss 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func UploadFileToOSS(c config.Config, fileHeader *multipart.FileHeader, localFileName, uploadFileName string) (url string, err error) {
client, err := oss.New("oss-cn-beijing.aliyuncs.com", c.ALiYun.AccessKeyId, c.ALiYun.AccessKeySecret)
if err != nil {
logx.Errorf("Creates a new client err: %v", err)
return "", err
}

bucketName := "you-bucket-name"
bucket, err := client.Bucket(bucketName)
if err != nil {
logx.Errorf("Get the bucket instance err: %v", err)
return "", err
}

// 上传文件选项设置
opt := []oss.Option{
oss.Meta("Content-Type", fileHeader.Header.Get("Content-Type")),
}

if err = bucket.PutObjectFromFile(uploadFileName, localFileName, opt...); err != nil {
logx.Errorf("PutObjectFromFile err: %v", err)
return "", err
}

// oss 访问地址要更为自己申请的访问地址
return fmt.Sprintf("https://oss-api-app.test.net/%s", uploadFileName), nil
}