7、AWS SDK for Go-文件分片上传

这篇具有很好参考价值的文章主要介绍了7、AWS SDK for Go-文件分片上传。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

对象介绍

分段即分片。
文档地址:传送门

源数据

ETag	表示对象的特定版本。对于未作为分段上传上传、未加密或使用 Amazon S3 托管密钥 (SSE-S3) 的服务器端加密进行加密的对象,
ETag 是数据的 MD5 摘要。

对象分片上传

对象的分片上传优势

使用分段上传可提供以下优势:

  • 提高吞吐量 – 您可以并行上传分段以提高吞吐量。
  • 从任何网络问题中快速恢复 – 较小的分段大小可以将由于网络错误而需重启失败的上传所产生的影响降至最低。
  • 暂停和恢复对象上传 – 您可以在一段时间内逐步上传对象分段。启动分段上传后,不存在过期期限;您必须显式地完成或停止分段上传。
  • 在您知道对象的最终大小前开始上传 – 您可以在创建对象时将其上传。

建议您按以下方式使用分段上传:

  • 如果您在稳定的高带宽网络上传大型对象,请通过并行分段上传对象实现多线程上传,使用分段上传以充分利用您的可用带宽。
  • 如果您在断点网络中上传对象,请使用分段上传以提高应对网络错误的复原能力,从而避免重新上传。在使用分段上传时,您只需重新尝试上传在上传期间中断的部分即可。而无需从头重新开始上传对象。

分段上传流程

分段上分为三个步骤:开始上传、上传对象分段,以及在上传所有分段后完成分段上传。

分段上传开始

当您发送请求以开始分段上传时,Amazon S3 将返回具有上传 ID 的响应,此 ID 是分段上传的唯一标识符。无论您何时上传分段、列出分段、完成上传或停止上传,您都必须包括此上传 ID。如果您想要提供描述已上传的对象的任何元数据,必须在请求中提供它以开始分段上传。

分段上传

上传分段时,除了指定上传 ID,还必须指定分段编号。
您可以选择 1 和 10000 之间的任意分段编号。
分段编号在您正在上传的对象中唯一地识别分段及其位置。
您选择的分段编号不必是连续序列(例如,它可以是 1、5 和 14)。
如果您使用之前上传的分段的同一分段编号上传新分段,则之前上传的分段将被覆盖。

无论您何时上传分段,Amazon S3 都将在其响应中返回实体标签 (ETag) 标头。
对于每个分段上传,您必须记录分段编号和 ETag 值。
您必须在随后的请求中包括这些值以完成分段上传。

分段上传完成

完成分段上传时,Amazon S3 通过按升序的分段编号规范化分段来创建对象。
如果在开始分段上传请求中提供了任何对象元数据,则 Amazon S3 会将该元数据与对象相关联。
成功完成请求后,分段将不再存在。

完成分段上传请求必须包括上传 ID 以及分段编号和相应的 ETag 值的列表。
Amazon S3 响应包括可唯一地识别组合对象数据的 ETag。
此 ETag 无需成为对象数据的 MD5 哈希。

分段上传调用示例

对于此示例,假设您正在为一个 100 GB 的文件生成分段上传。在这种情况下,您应在整个过程中进行以下 API 调用。总共将有 1002 个 API 调用。

  • 一个用于启动该过程的 CreateMultipartUpload 调用。

  • 1000 个单独的 UploadPart 调用,每次上传 100 MB 的一部分,总大小为 100 GB。

  • 一个用于完成该过程的 CompleteMultipartUpload 调用。

分段上传列表

您可以列出特定分段上传或所有正在进行的分段上传的分段。
列出分段操作将返回您已为特定分段上传而上传的分段信息。
对于每个列出分段请求,Amazon S3 将返回有关特定分段上传的分段信息,最多为 1000 个分段。
如果分段上传中的分段超过 1000 个,您必须发送一系列列出分段请求以检索所有分段。
请注意,返回的分段列表不包括未完成上传的分段。
使用列出分段上传 操作,您可以获得正在进行的分段上传的列表。

正在进行的分段上传是已开始但还未完成或停止的上传。
每个请求将返回最多 1000 个分段上传。
如果正在进行的分段上传超过 1000 个,您必须发送其他请求才能检索剩余的分段上传。
仅使用返回的列表进行验证。
发送完成分段上传 请求时,请勿使用此列表的结果。
相反,当上传分段和 Amazon S3 返回的相应 ETag 值时,请保留您自己的指定分段编号的列表。

使用分段上传操作的校验和

在将对象上传到 Amazon S3 时,可指定校验和算法以供 Amazon S3 使用。默认情况下,Amazon S3 使用 MD5 来验证数据完整性;但是,您可以指定要使用的其他校验和算法。使用 MD5 时,Amazon S3 会在上传完成后计算整个分段对象的校验和。此校验和不是整个对象的校验和,而是每个分段的校验和的校验和。

当您指示 Amazon S3 使用其他校验和时,Amazon S3 会计算每个分段的校验和值并存储这些值。您可以使用 API 或 SDK 通过 GetObject 或 HeadObject 来检索单个分段的校验和值。

并发分段上传操作

在分布式开发环境中,您的应用程序可以同时在同一对象上开始多个更新。您的应用程序可能会使用同一对象键开始多个分段上传。然后,对于其中每个上传,您的应用程序可以上传分段并将完成上传请求发送到 Amazon S3,以创建数据元。当存储桶启用了 S3 版本控制时,完成分段上传将始终创建一个新版本。对于未启用版本控制的存储桶,在开始分段上传和完成分段上传期间接收的某些其他请求可能会优先开始。

注意

在开始分段上传和完成分段上传期间接收的某些其他请求可能会优先开始。例如,如果在使用键开始分段上传之后,但在完成分段上传之前其他操作删除了该键,则完成分段上传响应可能表示在未看到对象的情况下即成功创建了对象。

分段所需权限

方法列表:

开始分段上传
上传分段
上传分段(复制)
完成分段上传
中止分段上传
列出分段
列出分段上传

所需权限:

s3:PutObject
s3:AbortMultipartUpload
s3:ListMultipartUploadParts

预签名

默认情况下,所有对象和存储桶都是私有的。但是,您可以使用预签名 URL 选择性地共享对象,或者允许客户/用户将对象上传到存储桶,而无需 AWS 安全凭证或权限。

您可以使用预签名 URL 生成可用于访问 Amazon S3 存储桶的 URL。创建预签名 URL 时,将其与特定操作相关联。您可以共享 URL,任何有权访问该 URL 的人都可以像原始签名用户一样执行嵌入在 URL 中的操作。URL 在达到其过期时间后会过期且不再起作用。

预签名作用

可以共享对象、上传对象、删除对象

分片上传使用预签名 URL

预签名 URL 允许您访问在 URL 中识别的对象,条件是预签名 URL 的创建者拥有访问该对象的权限。即,如果您收到用于上传对象的预签名 URL,只要该预签名 URL 的创建者拥有上传该对象所需的权限,您即可上传对象。

默认情况下,所有的对象和存储桶都是私有的。如果您希望您的用户/客户能够将特定对象上载到您的存储桶,但您不要求他们拥有 AWS 安全凭证或权限,那么预签名 URL 将非常有用。

创建预签名 URL 时,您必须提供安全凭证,然后指定一个存储桶名称、一个对象键、一个 HTTP 方法(对上传对象执行 PUT 操作)和一个截止日期和时间。预签名 URL 仅在指定的持续时间内有效。也就是说,必须在到期日期和时间之前启动操作。

如果操作由多个步骤构成(例如分段上传),则所有步骤必须在到期前启动。否则,当 Amazon S3 尝试使用失效的 URL 启动步骤时,您将收到错误消息。

在到期日期和时间之前,可以多次使用预签名 URL。

对象分片上传示例

纯后端的分片上传示例

package main

import (
	"bytes"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"net/http"
	"os"
	"time"
)

var (
	accessKey = "xxx"
	secretKey = "xxx"
	region    = "oss-cn-beijing"
	endpoint  = "oss-cn-beijing.aliyuncs.com"
)

func main() {
	//只要不修改session,session就可以安全的并发使用。
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
		Endpoint:    aws.String(endpoint),
		Region:      aws.String(region),
		//minio:true,oss:false
		S3ForcePathStyle: aws.Bool(false),
		//SDK 支持使用客户端 TLS 证书配置的环境和会话选项,这些证书作为客户端 TLS 握手的一部分发送以进行客户端身份验证。
		//如果使用,则需要 Cert 和 Key 值。如果缺少一个,或者无法加载文件的内容,则会返回一个错误。
		//ClientTLSCert:              nil,
		//ClientTLSKey:               nil,
	})
	if err != nil {
		panic(err)
	}

	svc := s3.New(sess)
	//本地文件 5.jpg
	filename := "5.jpg"
	var chunkSize int64 = 26214400 //25M
	name, size := getFileInfo(filename)
	var bucket = "bkt-bj1"
	//	分片上传
	PartsUpload(svc, bucket, name, size, chunkSize)

}

//先获取文件名称和大小
func getFileInfo(filename string) (string, int64) {
	//文件操作
	file, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := file.Close(); err != nil {
			panic(err)
		}
	}()
	fileInfo, _ := file.Stat()

	return filename, fileInfo.Size()
}
func PartsUpload(svc *s3.S3, bucket, key string, size, chunkSize int64) {
	oneDay := 86400

	//文件操作
	file, err := os.Open(key)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := file.Close(); err != nil {
			panic(err)
		}
	}()
	buffer := make([]byte, size)
	fileType := http.DetectContentType(buffer)
	if _, err := file.Read(buffer); err != nil {
		panic(err)
	}

	//给分片上传1天的过期时间
	expires := time.Now().Add(time.Duration(oneDay) * time.Second)
	//创建分片
	res, err := svc.CreateMultipartUpload(&s3.CreateMultipartUploadInput{
		Bucket:  &bucket,
		Key:     &key,
		Expires: &expires,
		//
		ContentType: aws.String(fileType),
	})
	if err != nil {
		panic(err)
	}
	var remainSize int64
	//获取分片个数
	chunkNum := size / chunkSize
	if size%chunkSize > 0 {
		chunkNum++
		remainSize = size % chunkSize
	}
	var completedParts []*s3.CompletedPart
	var i int64
	for ; i < chunkNum; i++ {
		partNum := i + 1
		partSize := chunkSize
		if partNum == chunkNum {
			if remainSize > 0 {
				partSize = remainSize
			}
		}
		fileBytes := buffer[chunkSize*i : chunkSize*i+partSize]
		//UploadPart=	req, out := c.UploadPartRequest(input)+req.Send()
		uploadResult, err := svc.UploadPart(&s3.UploadPartInput{
			Bucket:     &bucket,
			Key:        &key,
			PartNumber: aws.Int64(partNum),
			UploadId:   res.UploadId,
			//
			Body:          bytes.NewReader(fileBytes),
			ContentLength: aws.Int64(int64(len(fileBytes))),
		})
		if err != nil {
			panic(err)
		}
		completedParts = append(completedParts, &s3.CompletedPart{
			ETag:       uploadResult.ETag,
			PartNumber: aws.Int64(partNum),
		})
	}

	compResp, err := svc.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
		Bucket:   &bucket,
		Key:      &key,
		UploadId: res.UploadId,
		//
		MultipartUpload: &s3.CompletedMultipartUpload{
			Parts: completedParts,
		},
	})
	if err != nil {
		panic(err)
	}
	fmt.Printf("compResp:%+v\n", compResp.String())
}

后端预签名,后端分片上传示例

package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/gofrs/uuid"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

var (
	accessKey = "xxx"
	secretKey = "xxx"
	region    = "oss-cn-beijing"
	endpoint  = "oss-cn-beijing.aliyuncs.com"
)

func main() {
	//只要不修改session,session就可以安全的并发使用。
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
		Endpoint:    aws.String(endpoint),
		Region:      aws.String(region),
		//minio:true,oss:false
		S3ForcePathStyle: aws.Bool(false),
		//SDK 支持使用客户端 TLS 证书配置的环境和会话选项,这些证书作为客户端 TLS 握手的一部分发送以进行客户端身份验证。
		//如果使用,则需要 Cert 和 Key 值。如果缺少一个,或者无法加载文件的内容,则会返回一个错误。
		//ClientTLSCert:              nil,
		//ClientTLSKey:               nil,
	})
	if err != nil {
		panic(err)
	}

	svc := s3.New(sess)
	//本地文件
	filename := "6.jpg"
	var chunkSize int64 = 26214400 //25M
	name, size := getFileInfo(filename)
	var bucket = "bkt-bj1"
	//	分片上传预签名URL
	uploadCredential := PartsUploadPre(svc, bucket, name, size, chunkSize)
	completedPart := UploadByURLs(uploadCredential, name)
	//使用预签名URL上传并完成上传
	Comp(uploadCredential.CompleteURL, completedPart)
}

//先获取文件名称和大小
func getFileInfo(filename string) (string, int64) {
	//文件操作
	file, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := file.Close(); err != nil {
			panic(err)
		}
	}()
	fileInfo, _ := file.Stat()

	return filename, fileInfo.Size()
}

// UploadCredential 返回给客户端的上传凭证
type UploadCredential struct {
	SessionID   string   `json:"sessionID"`
	ChunkSize   int64    `json:"chunkSize"` // 分块大小,0 为部分快
	Expires     int64    `json:"expires"`   // 上传凭证过期时间, Unix 时间戳
	UploadURLs  []string `json:"uploadURLs,omitempty"`
	UploadID    string   `json:"uploadID,omitempty"`
	CompleteURL string   `json:"completeURL,omitempty"`
}

//分片上传
func PartsUploadPre(svc *s3.S3, bucket, key string, size, chunkSize int64) *UploadCredential {
	var oneDay = 86400

	var uploadCredential = &UploadCredential{
		ChunkSize: chunkSize,
		//创建一个key回调用
		SessionID: uuid.Must(uuid.NewV4()).String(),
	}
	//回调有效期
	expires := time.Now().Add(time.Duration(oneDay) * time.Second)
	uploadCredential.Expires = expires.Unix()

	var urls []string
	//创建分片
	res, err := svc.CreateMultipartUpload(&s3.CreateMultipartUploadInput{
		Bucket:  &bucket,
		Key:     &key,
		Expires: &expires,
	})
	if err != nil {
		panic(err)
	}
	//获取分片个数
	chunkNum := size / chunkSize
	if size%chunkSize > 0 {
		chunkNum++
	}
	var i int64
	for ; i < chunkNum; i++ {
		partNum := i + 1
		signedReq, _ := svc.UploadPartRequest(&s3.UploadPartInput{
			Bucket:     &bucket,
			Key:        &key,
			PartNumber: aws.Int64(partNum),
			UploadId:   res.UploadId,
		})
		//预签名一个一天的地址
		signedURL, err := signedReq.Presign(time.Duration(oneDay) * time.Second)
		if err != nil {
			panic(err)
		}
		urls = append(urls, signedURL)

	}

	signedReq, _ := svc.CompleteMultipartUploadRequest(&s3.CompleteMultipartUploadInput{
		Bucket:   &bucket,
		Key:      &key,
		UploadId: res.UploadId,
	})
	signedURL, err := signedReq.Presign(time.Duration(oneDay) * time.Second)
	if err != nil {
		panic(err)
	}
	uploadCredential.UploadURLs = urls
	uploadCredential.CompleteURL = signedURL
	uploadCredential.UploadID = *res.UploadId
	//该结构入库,并返回前端。待前端上传完文件,使用sessionID回调。后端根据sessionID查询记录做相应的db更新。
	return uploadCredential
}
func UploadByURLs(uploadCredential *UploadCredential, uploadKey string) []CompletedPart {
	chunkSize := int(uploadCredential.ChunkSize)
	//文件操作
	file, err := os.Open(uploadKey)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := file.Close(); err != nil {
			panic(err)
		}
	}()
	fileInfo, _ := file.Stat()
	size := int(fileInfo.Size())
	buffer := make([]byte, size)
	//fileType := http.DetectContentType(buffer)
	if _, err := file.Read(buffer); err != nil {
		panic(err)
	}

	remainSize := 0 //当最后一片小于块大小时
	//获取分片个数
	chunkNum := size / chunkSize
	if size%chunkSize > 0 {
		//有余数,余数即最后一片的大小,非块大小
		chunkNum++
		remainSize = size % chunkSize
	}
	var completedPart []CompletedPart
	for i := 0; i < chunkNum; i++ {
		signedURL := uploadCredential.UploadURLs[i]
		partNum := i + 1
		partSize := chunkSize
		//最后一片的处理
		if partNum == chunkNum {
			if remainSize > 0 {
				partSize = remainSize
			}
		}
		fileBytes := buffer[chunkSize*i : chunkSize*i+partSize]
		//直接将文件内容传入请求体,不要form表单和任意的key
		req, err := http.NewRequest("PUT", signedURL, bytes.NewReader(fileBytes))
		if err != nil {
			panic(err)
		}
		res, _ := http.DefaultClient.Do(req)
		defer res.Body.Close()
		body, _ := ioutil.ReadAll(res.Body)

		fmt.Printf("-----i---------res:%+v\n", res.Header["Etag"])
		fmt.Printf("-----i---------body:%+v\n", string(body))
		completedPart = append(completedPart, CompletedPart{
			ETag:       res.Header["Etag"][0],
			PartNumber: int64(partNum),
		})
	}

	return completedPart
}

/**
 *  @Author: wangzhen
 *  @Date: 2022-07-19 08:53:36
 *  @Description:
 *  @param compURL
 *  @param completedPart
 */
func Comp(compURL string, completedPart []CompletedPart) {
	var byteXML CompleteMultipartUpload
	byteXML.Parts = completedPart
	marshal, err := xml.Marshal(byteXML)
	if err != nil {
		panic(err)
	}
	//<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>"30d7bfaaf99d52090061d7d7ef999dd8"</ETag></Part></CompleteMultipartUpload>
	//req, err := http.NewRequest("POST", compURL, bytes.NewReader([]byte(xml.Header+string(marshal))))
	req, err := http.NewRequest("POST", compURL, bytes.NewReader(marshal))
	if err != nil {
		panic(err)
	}
	compRes, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := compRes.Body.Close(); err != nil {
			panic(err)
		}
	}()
	body, err := ioutil.ReadAll(compRes.Body)
	if err != nil {
		panic(err)
	}
	fmt.Printf("comp Res:%+v\n", compRes)
	fmt.Printf("comp body:%+v\n", string(body))
	///成功返回格式:
	//<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>http://127.0.0.1:9000/wzzz/02-qt%E7%AE%80%E4%BB%8B_-.mp4</Location><Bucket>wzzz</Bucket><Key>02-qt简介_-.mp4</Key><ETag>&#34;04273bd4d505a743dfa4250aff17b0e0-2&#34;</ETag></CompleteMultipartUploadResult>
	//错误返回格式:
	/*
		<Error><Code>MalformedXML</Code><Message>The XML you provided was not well-formed or did not validate against our published schema.</Message><Key>02-qt简介_-.mp4</Key><BucketName>wzzz</BucketName><Resource>/wzzz/02-qt简介_-.mp4</Resource><RequestId>1702DFDBB74A1B54</RequestId><HostId>2234a90c-d10d-4d52-a2bc-95a6b2e18b9e</HostId></Error>
	*/
}

type CompletedPart struct {
	ETag       string `type:"ETag"`
	PartNumber int64  `type:"PartNumber"`
}
type CompleteMultipartUpload struct {
	Parts []CompletedPart `xml:"Part"`
}

后端预签名,前端分片上传示例

后端分片预签名,
前端使用预签名URL上传文件分片,完成后通知后端完成分片上传。

运行后端代码,然后浏览器打开页面:
选择文件=>上传(我选择的文件时big1.mp4)
然后测试文件下载:
http://127.0.0.1:8111/download/bkt-bj1/big1.mp4文章来源地址https://www.toymoban.com/news/detail-790728.html

后端代码

package main

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/gin-gonic/gin"
	"github.com/gofrs/uuid"
	"net/http"
	"time"
)

const (
	oneDay = 86400
)

var (
	accessKey = "xxx"
	secretKey = "xxx"
	region    = "oss-cn-beijing"
	endpoint  = "oss-cn-beijing.aliyuncs.com"
)

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		origin := c.Request.Header.Get("Origin")
		if origin != "" {
			c.Header("Access-Control-Allow-Origin", origin)
			//主要设置Access-Control-Allow-Origin
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "false")
			c.Set("content-type", "application/json")
		}
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}

func main() {
	//只要不修改session,session就可以安全的并发使用。
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
		Endpoint:    aws.String(endpoint),
		Region:      aws.String(region),
		//minio:true,oss:false
		S3ForcePathStyle: aws.Bool(false),
		//SDK 支持使用客户端 TLS 证书配置的环境和会话选项,这些证书作为客户端 TLS 握手的一部分发送以进行客户端身份验证。
		//如果使用,则需要 Cert 和 Key 值。如果缺少一个,或者无法加载文件的内容,则会返回一个错误。
		//ClientTLSCert:              nil,
		//ClientTLSKey:               nil,
	})
	if err != nil {
		panic(err)
	}
	bucket := "bkt-bj1"
	svc := s3.New(sess)
	var chunkSize int64 = 26214400 //25M
	r := gin.Default()
	r.Use(Cors())
	type UploadReq struct {
		Name string `json:"name"`
		Size int64  `json:"size"`
	}
	//上传请求
	r.POST("/uploadRequest", func(c *gin.Context) {
		var param UploadReq
		err := c.ShouldBindJSON(&param)
		if err != nil {
			panic(err)
		}
		fmt.Println("param:", param)
		name, size := param.Name, param.Size
		//name, size := AnalogFrontEnd() //模拟生成参数
		uploadCredential := PartsUpload(svc, bucket, name, size, chunkSize)
		c.JSON(200, uploadCredential)
	})
	type CompReq struct {
		Name      string          `json:"name"`
		SessionID string          `json:"sessionID"`
		UploadID  string          `json:"uploadID"`
		Parts     []CompletedPart `json:"part"`
	}
	//上传完成
	r.POST("/uploadComplete", func(c *gin.Context) {
		var param CompReq
		err := c.ShouldBindJSON(&param)
		fmt.Printf("uploadComplete param:%+v\n", param)
		if err != nil {
			panic(err)
		}
		//	模拟前端上传文件,分片+合并
		CompleteMulti(svc, bucket, param.Name, param.UploadID, param.Parts)
		c.JSON(200, param.Name)
	})

	type DownloadReq struct {
		Bucket string `uri:"bucket" binding:"required"`
		Key    string `uri:"key" binding:"required"`
	}
	//下载
	r.GET("/download/:bucket/:key", func(c *gin.Context) {
		var param DownloadReq
		err := c.ShouldBindUri(&param)
		if err != nil {
			panic(err)
		}
		fmt.Println("param:", param)
		c.Redirect(http.StatusFound, Download(svc, param.Bucket, param.Key))
	})
	r.Run(":8111")

}
func Download(svc *s3.S3, bucket, key string) string {
	//name := "02-qt简介_-.mp4"
	signedReq, _ := svc.GetObjectRequest(&s3.GetObjectInput{
		Bucket: &bucket,
		Key:    &key,
	})
	signedURL, err := signedReq.Presign(time.Duration(oneDay) * time.Second)
	if err != nil {
		panic(err)
	}
	fmt.Println("signedURL:", signedURL)

	return signedURL
}

// UploadCredential 返回给客户端的上传凭证
type UploadCredential struct {
	Name       string   `json:"name"`
	SessionID  string   `json:"sessionID"`
	ChunkSize  int64    `json:"chunkSize"` // 分块大小,0 为部分快
	Expires    int64    `json:"expires"`   // 上传凭证过期时间, Unix 时间戳
	UploadURLs []string `json:"uploadURLs"`
	UploadID   string   `json:"uploadID"`
}

func PartsUpload(svc *s3.S3, bucket, name string, size, chunkSize int64) *UploadCredential {
	var uploadCredential = &UploadCredential{
		Name:      name,
		ChunkSize: chunkSize,
		//创建一个key回调用
		SessionID: uuid.Must(uuid.NewV4()).String(),
	}
	//回调有效期
	expires := time.Now().Add(time.Duration(oneDay) * time.Second)
	uploadCredential.Expires = expires.Unix()

	var urls []string
	//创建分片
	res, err := svc.CreateMultipartUpload(&s3.CreateMultipartUploadInput{
		Bucket:  &bucket,
		Key:     &name,
		Expires: &expires,
	})
	if err != nil {
		panic(err)
	}
	//获取分片个数
	chunkNum := size / chunkSize
	if size%chunkSize > 0 {
		chunkNum++
	}
	var i int64
	for ; i < chunkNum; i++ {
		partNum := i + 1
		signedReq, _ := svc.UploadPartRequest(&s3.UploadPartInput{
			Bucket:     &bucket,
			Key:        &name,
			PartNumber: aws.Int64(partNum),
			UploadId:   res.UploadId,
		})
		signedURL, err := signedReq.Presign(time.Duration(oneDay) * time.Second)
		if err != nil {
			panic(err)
		}
		urls = append(urls, signedURL)
	}

	uploadCredential.UploadURLs = urls
	uploadCredential.UploadID = *res.UploadId
	//该结构入库,并返回前端。待前端上传完文件,使用sessionID回调。后端根据sessionID查询记录做相应的db更新。
	return uploadCredential
}

type CompletedPart struct {
	ETag       string `json:"eTag"`
	PartNumber int64  `json:"partNumber"`
}

func CompleteMulti(svc *s3.S3, bucket, name, uploadID string, completedPart []CompletedPart) {
	input := &s3.CompleteMultipartUploadInput{
		Bucket:   &bucket,
		Key:      &name,
		UploadId: &uploadID,
		//
		MultipartUpload: &s3.CompletedMultipartUpload{
			Parts: make([]*s3.CompletedPart, 0, len(completedPart)),
		},
	}
	for _, part := range completedPart {
		input.MultipartUpload.Parts = append(input.MultipartUpload.Parts, &s3.CompletedPart{
			ETag:       &part.ETag,
			PartNumber: &part.PartNumber,
		})
	}

	if _, err := svc.CompleteMultipartUpload(input); err != nil {
		fmt.Println("CompleteMultipartUpload err:", err)
		panic(1)
	}
	///成功返回格式:
	//<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>http://127.0.0.1:9000/wzzz/02-qt%E7%AE%80%E4%BB%8B_-.mp4</Location><Bucket>wzzz</Bucket><Key>02-qt简介_-.mp4</Key><ETag>&#34;04273bd4d505a743dfa4250aff17b0e0-2&#34;</ETag></CompleteMultipartUploadResult>
	//错误返回格式:
	/*
		<Error><Code>MalformedXML</Code><Message>The XML you provided was not well-formed or did not validate against our published schema.</Message><Key>02-qt简介_-.mp4</Key><BucketName>wzzz</BucketName><Resource>/wzzz/02-qt简介_-.mp4</Resource><RequestId>1702DFDBB74A1B54</RequestId><HostId>2234a90c-d10d-4d52-a2bc-95a6b2e18b9e</HostId></Error>
	*/
}


前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>分片文件上传</title>
    <h3>分片文件上传</h3>
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<input type="file" name="file" id="file">
<button id="upload" onClick="upload()">上传</button>
<script type="text/javascript">
    // 设置上传成功数量记录
    successTotal = 0
    function upload() {
        var file = document.getElementById("file").files[0];
        console.log(file.name+"|"+file.size)
        var filename=file.name
        var filesize=file.size
        $.ajax({
            url: 'http://127.0.0.1:8111/uploadRequest',
            type: 'POST',
            async:false,  //使用同步的方式,true为异步方式

            cache: false,
            dataType : "json",
            data: JSON.stringify({
                "name":filename,
                "size":filesize,
            }),
            processData: false,
            contentType : "application/json",

            success:function (res){
                console.log("pre resp:",res)
                var chunkSize= res.chunkSize
               var sessionID= res.sessionID
                // res.expires
                // res.uploadURLs
               var uploadID= res.uploadID

               var remainSize = 0 //当最后一片小于块大小时
                //获取分片个数
               var  chunkNum  = filesize / chunkSize
                if (filesize%chunkSize > 0 ){
                    //有余数,余数即最后一片的大小,非块大小
                    chunkNum++
                    remainSize = filesize % chunkSize
                }
                var compData = new Object();
                compData["name"]=filename
                compData["sessionID"]=sessionID
                compData["uploadID"]=uploadID
                compData["part"]=[]


                for (var i=0;i<res.uploadURLs.length;i++){
                   var partNum=i+1;
                   var signedURL = res.uploadURLs[i]
                    var partSize = chunkSize
                    //最后一片的处理
                    if( partNum == chunkNum) {
                        if (remainSize > 0 ){
                            partSize = remainSize
                        }
                    }
                    var chunk = file.slice(chunkSize*i,chunkSize*i+partSize);//切割文件
                    // 使用ajax提交
                    $.ajax({
                        url: signedURL,
                        async:false,  //使用同步的方式,true为异步方式
                        type: 'put',
                        cache: false,
                        data: chunk,
                        processData: false,
                        contentType: false,
                        success:function (data, textStatus, request){
                            console.log("s3 resp:",data)
                            console.log("s3 getAllResponseHeaders:",request.getAllResponseHeaders())
                            console.log("s3 xhr.getResponseHeader(\"ETag\"):",request.getResponseHeader("ETag"))
                            compData["part"].push({
                                "eTag":request.getResponseHeader("ETag"),
                                "partNumber":partNum
                            })
                        }
                    }).done(function(data, textStatus, request){
                        console.log("s3d resp:",data)
                        console.log("s3d getAllResponseHeaders:",request.getAllResponseHeaders())
                        console.log("s3d xhr.getResponseHeader(\"ETag\"):",request.getResponseHeader("ETag"))

                    }).fail(function(data, textStatus, request) {
                        console.log("s3f resp:",data)
                        console.log("s3f getAllResponseHeaders:",request.getAllResponseHeaders())
                        console.log("s3f xhr.getResponseHeader(\"ETag\"):",request.getResponseHeader("ETag"))


                    });
                }
                //    合并
                console.log("compData req:",compData)
                $.ajax({
                    url: "http://127.0.0.1:8111/uploadComplete",
                    type: 'post',
                    contentType : "application/json",
                    async:false,  //使用同步的方式,true为异步方式
                    dataType : "json",
                    cache: false,
                    data: JSON.stringify(compData),
                    processData: false,
                    success:function (res){
                        console.log("comp resp:",res)
                        alert("上传成功")
                    }
                }).done(function(res){
                    console.log("comp done:",res)

                }).fail(function(res) {
                    console.log("comp file:",res)

                });


            }
        }).done(function(res){
            console.log("pre down :",res)

        }).fail(function(res) {
            console.log("pre fail :",res)

        });





        // if (chunktotal == successTotal) {
        //     alert("上传成功")
        // } else {
        //     alert("上传失败")
        // }
    }


</script>
</body>
</html>

到了这里,关于7、AWS SDK for Go-文件分片上传的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Windows下Qt使用AWS SDK for C++连接MinIO服务器

    MinIO——分布式对象存储服务器。 它是一个是一个高性能的对象存储服务器,用于构建云存储解决方案,出于工作需求用到了这个MinIO来管理文件。 但,我用的是Qt,c++语言,并且使用环境是windows,可MinIO的C++ SDK只能Linux使用,不支持Windows,如果非要自己编译Windows版本的话估

    2024年02月02日
    浏览(65)
  • 使用Linux SDK客户端向AWS Iot发送数据

    参考链接: https://ap-southeast-1.console.aws.amazon.com/iot/home?region=ap-southeast-1#/test 此篇文章用于测试,使用Linux SDK客户端向AWS Iot发送数据,准备环境如下: 1.1 客户端操作系统 虚拟机一台: Red Hat Enterprise Linux release 8.7 (Ootpa)   1.2 域名解析 确保客户端可以解析AWS iot 的终端节点,可

    2024年01月25日
    浏览(43)
  • 使用cmake配置aws-cpp-sdk以及在cmake项目中使用

    clion msvc 15(2017) cmake 这里不多赘述 注意命令里一定要加–recurse-submodules参数,否则编译的时候会提示项目找不到子项目 先设置build directory为build,然后设置cmake options BUILD_ONLY:设置要编译的模块,多个模块之间用\\\";\\\"分隔 CMAKE_INSTALL_PREFIX:cmake的下载路径,完成安装之后使用f

    2024年02月02日
    浏览(38)
  • 国区AWS上传本地文件创建私有AMI镜像(无需aws cli)

    一、制作本地镜像文件-VMDK 1、使用VMware或者ESXI,用自己的镜像创建虚拟机 该虚拟机的CPU和内存随意设定,后续在AWS上可以修改,硬盘只需要挂载一块系统盘,创建硬盘时的容量是AWS上拉起实例时的最小硬盘容量 2、保证虚拟机是DHCP获取IP 查看网卡信息,保证虚拟机能正常获

    2024年02月06日
    浏览(34)
  • Java AWS S3 文件上传实现

    Amazon S3(Simple Storage Service)是亚马逊云计算平台提供的一种对象存储服务,可以用于存储和检索任意类型的数据。在Java开发中,我们可以通过AWS SDK for Java来实现与Amazon S3的集成。 官方文档 https://docs.aws.amazon.com/zh_cn/sdk-for-java/v1/developer-guide/examples-s3.html 1. 配置maven依赖 2. 配置

    2024年02月21日
    浏览(32)
  • 基于java的 aws s3文件上传

    aws s3 文件上传代码 首先,确保您已经在AWS上创建了一个S3存储桶,并拥有相应的访问密钥和密钥ID。这些凭据将用于在Java代码中进行身份验证。 接下来,需要在Java项目中添加AWS SDK的依赖。可以使用Maven或Gradle进行依赖管理。以下是一个Maven的示例依赖项: 示例代码: 在上述

    2024年01月17日
    浏览(32)
  • Python文件上传 S3(AWS) 简单实现

    建立aws账户,进入到S3界面  点击 \\\"Create bucket\\\" 一系列操作之后——这里给bucket命名为csfyp python需要先: 这两个包含一些连接python和s3 连接的api 然后直接上代码

    2024年02月03日
    浏览(39)
  • FISCO BCOS Go-sdk 配置文件

    GitHub - FISCO-BCOS/go-sdk: golang SDK of FISCO BCOS 操作系统:CentOS7 Golang版本:1.17.2 WeBASE版本:1.5.2(已开启)可参见:WeBASE部署 - 搭建FISCO节点_ling1998的博客-CSDN博客 Git版本:1.8.3.1 编译时间有些长,耐心等待  配置文件内容 按Esc退出编辑,输入:wq保存即出。 配置文件中如下三个文件

    2024年01月19日
    浏览(43)
  • minio-docker单节点部署SDK测试文件上传下载

    目录 一,docker部署minio单节点单磁盘 二,SDK测试上传下载 1.拉取镜像 2.查看镜像 3.启动minio(新版本) 创建本机上的挂载目录,这个可以自己指定。 -d --restart=always是容器自启动,建议不要带。 增加这行参数,执行docker stop的时候不管用,他会立马又自己启动起来,只能强制删

    2024年04月28日
    浏览(23)
  • Vue3.0跨端Web SDK访问微信小程序云储存,文件上传路径不存在/文件受损无法显示问题(已解决)

    需要vue3.0作为pc端的后台管理来连接微信小程序客户端 需要Web SDK的引入,实现vue3.0接入云开发环境 需要以云环境作为线上服务器,将vue3.0上传的本地文件通过云环境进入云储存,并将文件在云端生成云端快捷访问路径及http/https路径(公网路径) 修改云端储存的权限代码:

    2024年02月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包