转golang第一次遇到图片上传的需求,所以记录一下。

因为架构设计需要后端上传到S3服务器,当然用前端JS上传会简单不少,不过前端上传的话,安全性也是一个个不得不要考虑的问题,并且S3的流量费也不便宜。

1、引入相应的s3开发sdk;

go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3

2、通过下面这段代码来读取form文档上传;

fileHeader, err := c.FormFile("file")
if err != nil {
	c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": err.Error()})
	return
}
fileExt := filepath.Ext(fileHeader.Filename)
log.Println("name:", fileHeader.Filename, fileHeader.Size, fileExt)

3、读取之后,就应该将它存储到本地服务器了,务必保证你要存储的目录已经创建;

//生成文件名
fileName := fmt.Sprintf("%d%d%s", time.Now().Unix(), rand.Intn(89999) + 10000, fileExt)

//生成本地存储路径
localFile := "uploads/"+fileName

//保存到本地目录
c.SaveUploadedFile(fileHeader, localFile)

到了这一步,如果图片已经成功存储在你的uploads文件夹了,那我们就已经完成了30%的工作,当然此处难度系数偏低。

如果不是用Gin框架来做的话,图片存储也可以用os库来实现;

4、S3对象存储链接,鉴权;

cfg, err := config.LoadDefaultConfig(context.TODO(),
	config.WithRegion(appConfig.S3.Region),
	config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
		appConfig.S3.Key,
		appConfig.S3.Secret,
		"",    //此参数为token,若自己的凭据无token则设定为空
	)),
)
if err != nil {
	log.Println("configuration error:" + err.Error())
}
client := s3.NewFromConfig(cfg)

5、如果鉴权成功了,那就可以进行推送了;

file, err := os.Open(localFile)
if err != nil {
	log.Println("Unable to open file : ", localFile)
	return
}
defer file.Close()

input := &s3.PutObjectInput{
	Bucket: aws.String(appConfig.S3.Bucket),
	Key:  	aws.String(fileName),
	Body: 	file,
}

_, err = PutFile(context.TODO(), client, input)
if err != nil {
	log.Println("Got error uploading file:", err)
	return
}

6、完成之后,执行拼接网址访问就可以了,是不是很简单。

7、此controller完整代码如下,可自行截取。

package controllers

import (
	"context"
	"fmt"
	"gin/utils"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/gin-gonic/gin"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

type S3PutObjectAPI interface {
	PutObject(
		ctx context.Context,
		params *s3.PutObjectInput,
		optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
}

func PutFile(c context.Context, api S3PutObjectAPI, input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
	return api.PutObject(c, input)
}

func Upload(c *gin.Context)  {
	fileHeader, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": err.Error()})
		return
	}
    fileExt := filepath.Ext(fileHeader.Filename)
	log.Println("name:", fileHeader.Filename, fileHeader.Size, fileExt)


    //此处可以添加图片尺寸和后缀限制
    //因公司内部项目,暂时未限定
    

	//生成文件名
	fileName := fmt.Sprintf("%d%d%s", time.Now().Unix(), rand.Intn(89999) + 10000, fileExt)

    //生成本地存储路径
	localFile := "uploads/"+fileName

	//保存到本地目录
	c.SaveUploadedFile(fileHeader, localFile)

	//加载S3的配置项,此处为个人编写了一个Config实现
    //如果不想要config这种单独文件形式,则可以将下面appConfig变量自行换为真实的参数
	appConfig, err := utils.LoadConfig("conf/app.json")
	if err != nil {
		log.Println("Load configure file fail.")
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg" : "配置文件加载失败!",
		})
        return 
	}

	//上传到S3服务器
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion(appConfig.S3.Region),
		config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
			appConfig.S3.Key,
			appConfig.S3.Secret,
			"",    //此参数为token,若自己的凭据无token则设定为空
		)),
	)
	if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg" : "configuration error:" + err.Error(),
		})
        return
	}
	client := s3.NewFromConfig(cfg)
	file, err := os.Open(localFile)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg" : "Unable to open file : " + localFile,
		})
        return 
	}
	defer file.Close()

	input := &s3.PutObjectInput{
		Bucket: aws.String(appConfig.S3.Bucket),
		Key:  	aws.String(fileName),
		Body: 	file,
	}

	_, err = PutFile(context.TODO(), client, input)
	if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg" : "Got error uploading file:" + err.error(),
		})
		return
	}

	//删除本地记录
	os.Remove(localFile)

	c.JSON(http.StatusOK, gin.H{
        "code": 0, 
        "msg": "上传成功!", 
        "data": gin.H{
		    "imgurl": "https://aws.cloudfront.net/" + fileName,
	    },
    })
}