使用 Go 上傳檔案到 Amazon S3 儲存服務

前置作業

先到 AWS IAM 建立一個使用者,打開「Security credentials」頁籤,並選擇「Create access key」按鈕,最後將生成的「Access key ID」和「Secret access key」儲存起來。

實作

建立專案。

1
2
mkdir go-s3-example
cd go-s3-example

初始化 Go Modules。

1
go mod init github.com/memochou1993/go-s3-example

下載 aws-sdk-go 套件。

1
go get github.com/aws/aws-sdk-go/aws

新增 main.go 檔,填入必要變數。

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
var (
AccessKeyID = ""
SecretAccessKey = ""
Region = ""
Bucket = ""
)

func main() {
// 連線
sess := ConnectAws()

// 表單
http.Handle("/", http.FileServer(http.Dir("./public")))

// 上傳單一檔案
http.HandleFunc("/upload-file", func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(32 << 20) // 限制檔案上傳大小
for _, fileHeaders := range r.MultipartForm.File {
for _, header := range fileHeaders {
Upload(sess, Bucket, header)
}
}
})

// 上傳多個檔案
http.HandleFunc("/upload-files", func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(32 << 20) // 限制檔案上傳大小
for _, fileHeaders := range r.MultipartForm.File {
UploadMultiple(sess, Bucket, fileHeaders)
}
})

log.Fatal(http.ListenAndServe(":8080", nil))
}

新增 ConnectAws 方法,用來取得一個 Session:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

func ConnectAws() *session.Session {
sess, err := session.NewSession(&aws.Config{
Region: aws.String(Region),
Credentials: credentials.NewStaticCredentials(
AccessKeyID,
SecretAccessKey,
"", // 如果沒有使用 AWS STS 的話,填入空字串即可
),
})
if err != nil {
panic(err)
}
return sess
}

新增 Upload 方法,用來上傳單一檔案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func Upload(sess *session.Session, bucket string, header *multipart.FileHeader) error {
svc := s3manager.NewUploader(sess)
src, err := header.Open()
if err != nil {
return err
}
if _, err := svc.Upload(&s3manager.UploadInput{
Bucket: aws.String(bucket),
ACL: aws.String("public-read"),
Key: aws.String(header.Filename),
Body: src,
}); err != nil {
return err
}
return nil
}

新增 UploadMultiple 方法,用來上傳多個檔案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func UploadMultiple(sess *session.Session, bucket string, fileHeaders []*multipart.FileHeader) error {
var objects []s3manager.BatchUploadObject
for _, header := range fileHeaders {
src, err := header.Open()
if err != nil {
return err
}
objects = append(objects, s3manager.BatchUploadObject{
Object: &s3manager.UploadInput{
Bucket: aws.String(bucket),
ACL: aws.String("public-read"),
Key: aws.String(header.Filename),
Body: src,
},
})
}
svc := s3manager.NewUploader(sess)
iter := &s3manager.UploadObjectsIterator{Objects: objects}
return svc.UploadWithIterator(aws.BackgroundContext(), iter)
}

public 資料夾新增 index.html 檔,當作表單:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 上傳單一檔案 -->
<form method="POST" action="/upload-file" enctype="multipart/form-data">
<input type="file" name="files[]">
<input type="submit">
</form>
<!-- 上傳多個檔案 -->
<form method="POST" action="/upload-files" enctype="multipart/form-data">
<input type="file" name="files[]" multiple>
<input type="submit">
</form>
</body>
</html>

執行。

1
go run main.go

前端

使用 Axios 上傳檔案,範例如下。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body id="app">
<input id="file" type="file">
<button onclick="upload()">Upload</button>
<script>
const upload = async () => {
const file = document.getElementById('file').files[0];
const formData = new FormData();
formData.append('files[]', file);
const res = await axios('https://localhost:8080/upload-file', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
data: formData,
});
console.log(res);
const img = document.createElement('img');
img.setAttribute('src', `${res.data.data.avatar}?t=${+new Date()}`);
document.getElementById('app').appendChild(img);
};
</script>
</body>
</html>

瀏覽網頁

前往 http://127.0.0.1:8080 瀏覽。

參考資料