更新时间:2024-10-24 GMT+08:00

Go

分段上传Go语言的示例代码,如下所示:

package main

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
	vod "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/vod/v1"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/vod/v1/model"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/vod/v1/region"
	"io"
	"net/http"
	"os"
)

// 区域
const regionNorth4 = "cn-north-4"
const regionNorth1 = "cn-north-1"
const regionEast2 = "cn-east-2"

// ak/sk
const ak = ""
const sk = ""

// 设置缓冲区大小,每次读取文件分段的大小,根据情况自行设定
// 1M
const bufferSize = 1024 * 1024

/*
分段上传示例
*/
func main() {
	// 本地要上传的媒资路径
	filePath := ""

	// 上传媒资文件
	partUpload(filePath)
}

/*
分段上传
filePath 上传文件的本地路径
*/
func partUpload(filePath string) {
	// 校验文件路径和文件
	fileInfo := validFile(filePath)
	fileName := fileInfo.Name()
	fmt.Println(fileName)

	file, err := os.Open(filePath)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
			panic(err)
		}
	}(file)

	// 此处仅以MP4文件示例,其他格式可参考官网说明
	fileType := "MP4"
	fileContentType := "video/mp4"

	// 1.初始化鉴权并获取vodClient
	client := createVodClient()
	// 2.创建点播媒资
	fmt.Println("开始创建媒资:" + fileName)
	assetResponse := createAsset(client, fileName, fileName, fileType)

	// 3.获取初始化上传任务授权
	initAuthResponse := initPartUploadAuthority(client, assetResponse, fileContentType)

	// 4.初始化上传任务
	uploadId := initPartUpload(initAuthResponse.SignStr, fileContentType)

	// 文件分段计数
	partNumber := 1

	// 7.读取文件内容,循环5-6步上传所有分段
	for {
		buf := make([]byte, bufferSize)
		n, err := file.Read(buf)
		if n == 0 || err != nil || err == io.EOF {
			break
		}
		// 先md5,再base64 encode
		h := md5.New()
		h.Write(buf[:n])
		data := h.Sum(nil)
		contentMd5 := base64.StdEncoding.EncodeToString(data)
		fmt.Println("该文件第", partNumber, "段MD5为:", contentMd5)

		// 5.获取上传分段的授权
		partUploadAuthorityResponse := getPartUploadAuthority(client, fileContentType, assetResponse,
			contentMd5, uploadId, partNumber)

		// 6.上传分段
		uploadPartFile(partUploadAuthorityResponse.SignStr, buf[:n], contentMd5)

		// 段号自增
		partNumber++
	}

	// 8.获取已上传分段的授权
	listPartUploadAuthorityResponse := listUploadedPartAuthority(client, assetResponse, uploadId)

	// 9.获取已上传的分段
	partInfo := listUploadedPart(listPartUploadAuthorityResponse.SignStr)

	// 10.获取合并段授权
	mergePartUploadAuthorityResponse := mergeUploadedPartAuthority(client, assetResponse, uploadId)

	// 11.合并上传分段
	mergeUploadedPart(mergePartUploadAuthorityResponse.SignStr, partInfo)

	// 12.确认媒资上传
	confirmUploaded(client, assetResponse)

	fmt.Println("创建媒资结束 assetId:", *assetResponse.AssetId)
}

/*
校验文件路径和文件
filePath: 文件路径
*/
func validFile(filePath string) os.FileInfo {
	// 检查文件是否存在
	fileInfo, err := os.Stat(filePath)
	if os.IsNotExist(err) {
		fmt.Println("文件不存在")
		// 抛出错误
		panic(errors.New("文件不存在"))
	}
	if err != nil {
		fmt.Println("发生错误:", err)
		panic(err)
	}
	return fileInfo
}

/*
1.构建鉴权
*/
func createVodClient() *vod.VodClient {
	auth, _ := basic.NewCredentialsBuilder().
		WithAk(ak).
		WithSk(sk).
		SafeBuild()
	reg, _ := region.SafeValueOf(regionNorth4)

	build, _ := vod.VodClientBuilder().
		WithRegion(reg).
		WithCredential(auth).
		SafeBuild()

	client := vod.NewVodClient(build)
	return client
}

/*
2.创建媒资
videoName: 名称
title: 标题
videoType: 文件类型
*/
func createAsset(client *vod.VodClient, videoName string, title string,
	videoType string) *model.CreateAssetByFileUploadResponse {
	request := &model.CreateAssetByFileUploadRequest{}
	request.Body = &model.CreateAssetByFileUploadReq{
		VideoName: videoName,
		Title:     title,
		VideoType: videoType,
	}
	response, err := client.CreateAssetByFileUpload(request)
	if err == nil {
		fmt.Println(response)
	} else {
		fmt.Println(err)
		panic(err)
	}
	return response
}

/*
3.获取初始化上传任务授权
client
assetResponse
*/
func initPartUploadAuthority(client *vod.VodClient, assetResponse *model.CreateAssetByFileUploadResponse,
	fileContentType string) *model.ShowAssetTempAuthorityResponse {
	fmt.Println("获取初始化上传任务授权 initPartUploadAuthority start")
	// 设置参数
	request := &model.ShowAssetTempAuthorityRequest{}
	request.HttpVerb = "POST"
	request.Bucket = assetResponse.Target.Bucket
	request.ObjectKey = assetResponse.Target.Object
	request.ContentType = &fileContentType
	// 发送初始化请求
	response, err := client.ShowAssetTempAuthority(request)
	if err == nil {
		fmt.Print(response)
	} else {
		fmt.Println(err)
		panic(err)
	}
	fmt.Println("获取初始化上传任务授权 initPartUploadAuthority end\n")
	return response
}

/*
4.初始化分段上传
signStr 第3步返回的带签名的初始化url
contentType 媒体文件格式
return uploadId
*/
func initPartUpload(signStr *string, contentType string) string {
	fmt.Println("初始化分段上传 initPartUpload start")
	// 创建一个新的HTTP客户端
	client := &http.Client{}
	// 创建一个新的请求
	req, err := http.NewRequest("POST", *signStr, nil)

	req.Header.Set("Content-Type", contentType)
	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error sending request:", err)
		panic(err)
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			panic(err)
		}
	}(resp.Body)

	// 读取响应体
	body, _ := io.ReadAll(resp.Body)
	// 打印响应体
	fmt.Println(string(body))

	// 解析响应体,返回结果为xml文本,按格式解析
	var initiateMultipartUploadResult InitiateMultipartUploadResult
	err = xml.Unmarshal(body, &initiateMultipartUploadResult)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Bucket:%s, Key:%s, UploadId:%s\n", initiateMultipartUploadResult.Bucket,
		initiateMultipartUploadResult.Key, initiateMultipartUploadResult.UploadId)
	fmt.Println("初始化分段上传 initPartUpload end\n")
	return initiateMultipartUploadResult.UploadId
}

/*
5.获取分段上传授权
client
fileContentType 媒体文件格式
assetResponse 创建媒资返回的结果
contentMd5 当前分段计算出的contentMd5
uploadId
partNumber 段号
return 授权结果
*/
func getPartUploadAuthority(client *vod.VodClient, fileContentType string, assetResponse *model.CreateAssetByFileUploadResponse,
	contentMd5 string, uploadId string, partNumber int) *model.ShowAssetTempAuthorityResponse {
	fmt.Println("获取分段上传授权 getPartUploadAuthority start; partNumber:", partNumber)
	// 设置参数
	request := &model.ShowAssetTempAuthorityRequest{}
	request.HttpVerb = "PUT"
	request.Bucket = assetResponse.Target.Bucket
	request.ObjectKey = assetResponse.Target.Object
	request.ContentType = &fileContentType
	request.ContentMd5 = &contentMd5
	request.UploadId = &uploadId
	partNumberRequest := int32(partNumber)
	request.PartNumber = &partNumberRequest
	// 发送请求
	response, err := client.ShowAssetTempAuthority(request)
	if err == nil {
		fmt.Print(response)
	} else {
		fmt.Println(err)
		panic(err)
	}
	fmt.Println("获取分段上传授权 getPartUploadAuthority end; partNumber:", partNumber, "\n")
	return response
}

/*
6.上传分段
signStr 第5步返回的带签名的上传url
fileByte 当前分段数据
contentMd5 当前分段contentMd5
*/
func uploadPartFile(signStr *string, fileByte []byte, contentMd5 string) {
	fmt.Print("上传分段 uploadPartFile start")

	// 创建一个新的HTTP客户端
	client := &http.Client{}
	// 创建一个新的请求
	req, err := http.NewRequest("PUT", *signStr, bytes.NewBuffer(fileByte))
	req.Header.Set("Content-MD5", contentMd5)
	req.Header.Set("Content-Type", "application/octet-stream")

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error sending request:", err)
		panic(err)
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			panic(err)
		}
	}(resp.Body)
	// 读取响应体
	body, _ := io.ReadAll(resp.Body)
	// 打印响应体
	fmt.Println(string(body))
	if resp.StatusCode != 200 {
		panic("上传文件失败!")
	}
	fmt.Println("上传分段 uploadPartFile end \n")
}

/*
8.获取列举已上传段的授权
client
assetResponse 创建媒资响应结果
uploadId
return
*/
func listUploadedPartAuthority(client *vod.VodClient, assetResponse *model.CreateAssetByFileUploadResponse,
	uploadId string) *model.ShowAssetTempAuthorityResponse {
	fmt.Println("获取列举已上传段的授权 listUploadedPartAuthority start")
	// 设置参数
	request := &model.ShowAssetTempAuthorityRequest{}
	request.HttpVerb = "GET"
	request.Bucket = assetResponse.Target.Bucket
	request.ObjectKey = assetResponse.Target.Object
	request.UploadId = &uploadId
	// 发送请求
	response, err := client.ShowAssetTempAuthority(request)
	if err == nil {
		fmt.Print(response)
	} else {
		fmt.Println(err)
		panic(err)
	}
	fmt.Println("获取列举已上传段的授权 listUploadedPartAuthority end\n")
	return response
}

/*
9.查询已上传的分段
signStr 第8步返回的带签名的查询url
return
*/
func listUploadedPart(signStr *string) string {
	fmt.Println("查询已上传的分段 listUploadedPart start")
	// 查询分段的起始段号
	partNumberMarker := 0
	// 组装合并段参数,xml格式
	result := "<CompleteMultipartUpload>"
	// 创建一个新的HTTP客户端
	client := &http.Client{}
	for {
		// 创建一个新的请求
		url := *signStr + "&part-number-marker=" + fmt.Sprintf("%d", partNumberMarker)
		req, _ := http.NewRequest("GET", url, nil)
		// 发送请求
		resp, _ := client.Do(req)
		// 读取响应体
		body, _ := io.ReadAll(resp.Body)
		// 打印响应体
		fmt.Println(string(body))
		// 解析响应结果,转换xml格式文本
		var listPartsResult ListPartsResult
		_ = xml.Unmarshal(body, &listPartsResult)
		// 响应结果中没有parts跳出
		if len(listPartsResult.Parts) < 1 {
			break
		}
		// 循环parts组装Part数据
		for _, part := range listPartsResult.Parts {
			num := part.PartNumber
			tag := part.Etag
			result += "<Part>" +
				"<PartNumber>" +
				fmt.Sprintf("%d", num) +
				"</PartNumber>" +
				"<ETag>" +
				tag +
				"</ETag>" +
				"</Part>"
		}
		// 响应中下个段号值设为起始段号并再次请求
		partNumberMarker = listPartsResult.NextPartNumberMarker
		if partNumberMarker%1000 != 0 {
			break
		}
	}
	result += "</CompleteMultipartUpload>"
	fmt.Println(result)
	fmt.Println("查询已上传的分段 listUploadedPart end\n")
	return result
}

/*
10.获取合并段授权
client
assetResponse
uploadId
return
*/
func mergeUploadedPartAuthority(client *vod.VodClient, assetResponse *model.CreateAssetByFileUploadResponse,
	uploadId string) *model.ShowAssetTempAuthorityResponse {
	fmt.Println("获取合并段授权 mergeUploadedPartAuthority start")
	// 设置参数
	request := &model.ShowAssetTempAuthorityRequest{}
	request.HttpVerb = "POST"
	request.Bucket = assetResponse.Target.Bucket
	request.ObjectKey = assetResponse.Target.Object
	request.UploadId = &uploadId
	// 发送请求
	response, err := client.ShowAssetTempAuthority(request)
	if err == nil {
		fmt.Print(response)
	} else {
		fmt.Println(err)
		panic(err)
	}
	fmt.Println("获取合并段授权 mergeUploadedPartAuthority end\n")
	return response
}

/*
11.合并分段
signStr 第10步返回的带签名的合并url
partInfo 需要合并段的数据
*/
func mergeUploadedPart(signStr *string, partInfo string) {
	fmt.Println("合并分段 mergeUploadedPart start")
	// 创建一个新的HTTP客户端
	client := &http.Client{}
	// 创建一个新的请求
	//req, err := http.NewRequest("POST", *signStr, bytes.NewBuffer([]byte(partInfo)))
	req, err := http.NewRequest("POST", *signStr, bytes.NewBufferString(partInfo))
	// 请求消息头中增加 "Content-Type":"application/xml"
	req.Header.Set("Content-Type", "application/xml")
	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error sending request:", err)
		panic(err)
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			panic(err)
		}
	}(resp.Body)
	// 读取响应体
	body, _ := io.ReadAll(resp.Body)
	// 打印响应体
	fmt.Println(string(body))
	fmt.Println("合并分段 mergeUploadedPart end\n")
}

/*
12.确认上传完成
*/
func confirmUploaded(client *vod.VodClient, assetResponse *model.CreateAssetByFileUploadResponse) {
	fmt.Println("确认上传完成 confirmUploaded start")
	// 设置请求参数
	request := &model.ConfirmAssetUploadRequest{}
	request.Body = &model.ConfirmAssetUploadReq{
		Status:  model.GetConfirmAssetUploadReqStatusEnum().CREATED,
		AssetId: *assetResponse.AssetId,
	}
	// 发送请求
	response, err := client.ConfirmAssetUpload(request)
	if err == nil {
		fmt.Print(response)
	} else {
		fmt.Println(err)
		panic(err)
	}
	fmt.Println("上传完成, assetId:", *response.AssetId)
}

// InitiateMultipartUploadResult 初始化分段上传返回的xml结构体
type InitiateMultipartUploadResult struct {
	Bucket   string `xml:"Bucket"`
	Key      string `xml:"Key"`
	UploadId string `xml:"UploadId"`
}

// ListPartsResult 查询已上传段返回的xml结构体
type ListPartsResult struct {
	Bucket               string `xml:"Bucket"`
	Key                  string `xml:"Key"`
	UploadId             string `xml:"UploadId"`
	NextPartNumberMarker int    `xml:"NextPartNumberMarker"`
	Parts                []Part `xml:"Part"`
}

// Part 查询已上传段返回的xml listPartsResult中part结构体
type Part struct {
	Etag       string `xml:"ETag"`
	PartNumber int    `xml:"PartNumber"`
}