转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,
},
})
}