Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] add GenPresignedUrl #70

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions example/demo_presignedurl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package main

import (
"bytes"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"

ufsdk "github.com/ufilesdk-dev/ufile-gosdk"
"github.com/ufilesdk-dev/ufile-gosdk/example/helper"
)

func main() {
log.SetFlags(log.Lshortfile)
if _, err := os.Stat(helper.FakeBigFilePath); os.IsNotExist(err) {
helper.GenerateFakefile(helper.FakeBigFilePath, helper.FakeBigFileSize)
}
config, err := ufsdk.LoadConfig(helper.ConfigFile)
if err != nil {
panic(err.Error())
}
u, err := ufsdk.NewFileRequest(config, nil)
if err != nil {
panic(err.Error())
}
fileKey := helper.GenerateUniqKey()
err = putfile(helper.FakeBigFilePath, fileKey, u)
if err != nil {
log.Println("上传文件失败,具体错误详情:", err.Error())
return
}
log.Println("上传文件成功")

fileKey = helper.GenerateUniqKey()
err = mputfile(helper.FakeBigFilePath, fileKey, u)
if err != nil {
log.Println("分片上传文件失败,具体错误详情:", err.Error())
return
}
log.Println("分片上传文件成功")
}

func putfile(filePath string, keyName string, u *ufsdk.UFileRequest) error {
// 请确保在服务端生成该签名URL时设置的请求头与在使用URL时设置的请求头一致
// u.RequestHeader = http.Header{}
// u.RequestHeader.Set("key", "value")
// 根据请求添加query
// u.RequestHeader = url.Values{}
// u.query.Set("key", "value")

signedUrl := u.GenPresignedURL(keyName, 0, "PUT")
log.Println("上传文件的url 为:", signedUrl)
//使用url上传文件
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()

req, err := http.NewRequest("PUT", signedUrl, file)
if err != nil {
return err
}

// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// 读取响应
_, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
log.Println("返回上传状态码: ", resp.StatusCode)
if resp.StatusCode != 200 {
return fmt.Errorf("Remote response code is %d - %s not 2xx call DumpResponse(true) show details",
resp.StatusCode, http.StatusText(resp.StatusCode))
}

return nil
}

// 分片上传
func mputfile(filePath string, keyName string, u *ufsdk.UFileRequest) error {
// 创建一个新的HTTP客户端
client := &http.Client{}

//初始化分片上传
state, err := u.InitiateMultipartUpload(keyName, "")
if err != nil {
return err
}

file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()

chunk := make([]byte, state.BlkSize)
var partNumber int
for {
bytesRead, fileErr := file.Read(chunk)
if fileErr == io.EOF || bytesRead == 0 { //后面直接读到了结尾
break
}

// 获取分片上传的url
query := url.Values{}
query.Set("uploadId", state.UploadID)
query.Set("partNumber", strconv.Itoa(partNumber))
u.RequestQuery = query
signedUrl := u.GenPresignedURL(keyName, 0, "PUT")
log.Println("上传分片的url 为:", signedUrl)

// 使用url上传分片
buf := bytes.NewBuffer(chunk[:bytesRead])
req, err := http.NewRequest("PUT", signedUrl, buf)
if err != nil {
return err
}

// 发送请求
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// 读取响应
_, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
log.Println("返回上传状态码: ", resp.StatusCode)
if resp.StatusCode != 200 {
return fmt.Errorf("Remote response code is %d - %s not 2xx call DumpResponse(true) show details",
resp.StatusCode, http.StatusText(resp.StatusCode))
}
if err != nil {
u.AbortMultipartUpload(state)
return err
}
// 解析出响应中的etag
etag := strings.Trim(resp.Header.Get("Etag"), "\"") //为保证线程安全,这里就不保留 lastResponse
if etag == "" {
etag = strings.Trim(resp.Header.Get("ETag"), "\"") //为保证线程安全,这里就不保留 lastResponse
}
state.Etags[partNumber] = etag
partNumber++
}

return u.FinishMultipartUpload(state)
}
23 changes: 23 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,29 @@ func (u *UFileRequest) GetPrivateURL(keyName string, expiresDuation time.Duratio
return reqURL + "?" + query.Encode()
}

//GenPresignedURL 生成文件请求的签名URL。
//keyName 表示请求 ufile 的文件名。
//expiresDuation 表示链接的过期时间,从现在算起,24 * time.Hour 表示过期时间为一天。
//method 表示http请求的method。
//请求的query和header加入到UFileRequest中
func (u *UFileRequest) GenPresignedURL(keyName string, expiresDuation time.Duration, method string) string {
t := time.Now()
t = t.Add(expiresDuation)
expires := strconv.FormatInt(t.Unix(), 10)
signature, publicKey := u.Auth.AuthorizationPrivateURL(method, u.BucketName, keyName, expires, u.RequestHeader)
query := url.Values{}
for k, v := range u.RequestQuery {
for i := 0; i < len(v); i++ {
query.Add(k, v[i])
}
}
query.Set("UCloudPublicKey", publicKey)
query.Set("Signature", signature)
query.Set("Expires", expires)
reqURL := u.genFileURL(keyName)
return reqURL + "?" + query.Encode()
}

// Download 把文件下载到 HTTP Body 里面,这里只能用来下载小文件,建议使用 DownloadFile 来下载大文件。
func (u *UFileRequest) Download(reqURL string) error {
req, err := http.NewRequest("GET", reqURL, nil)
Expand Down
72 changes: 36 additions & 36 deletions file_mutipart_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ import (
"sync"
)

//MultipartState 用于保存分片上传的中间状态
// MultipartState 用于保存分片上传的中间状态
type MultipartState struct {
BlkSize int //服务器返回的分片大小
uploadID string
UploadID string
mimeType string
keyName string
etags map[int]string
Etags map[int]string
mux sync.Mutex
}

//UnmarshalJSON custom unmarshal json
// UnmarshalJSON custom unmarshal json
func (m *MultipartState) UnmarshalJSON(bytes []byte) error {
tmp := struct {
BlkSize int `json:"BlkSize"`
Expand All @@ -35,7 +35,7 @@ func (m *MultipartState) UnmarshalJSON(bytes []byte) error {
return err
}
m.BlkSize = tmp.BlkSize
m.uploadID = tmp.UploadID
m.UploadID = tmp.UploadID
return nil
}

Expand Down Expand Up @@ -63,10 +63,10 @@ type uploadChan struct {
err error
}

//MPut 分片上传一个文件,filePath 是本地文件所在的路径,内部会自动对文件进行分片上传,上传的方式是同步一片一片的上传。
//mimeType 如果为空的话,会调用 net/http 里面的 DetectContentType 进行检测。
//keyName 表示传到 ufile 的文件名。
//大于 100M 的文件推荐使用本接口上传。
// MPut 分片上传一个文件,filePath 是本地文件所在的路径,内部会自动对文件进行分片上传,上传的方式是同步一片一片的上传。
// mimeType 如果为空的话,会调用 net/http 里面的 DetectContentType 进行检测。
// keyName 表示传到 ufile 的文件名。
// 大于 100M 的文件推荐使用本接口上传。
func (u *UFileRequest) MPut(filePath, keyName, mimeType string) error {
file, err := openFile(filePath)
if err != nil {
Expand Down Expand Up @@ -101,16 +101,16 @@ func (u *UFileRequest) MPut(filePath, keyName, mimeType string) error {
return u.FinishMultipartUpload(state)
}

//AsyncMPut 异步分片上传一个文件,filePath 是本地文件所在的路径,内部会自动对文件进行分片上传,上传的方式是使用异步的方式同时传多个分片的块。
//mimeType 如果为空的话,会调用 net/http 里面的 DetectContentType 进行检测。
//keyName 表示传到 ufile 的文件名。
//大于 100M 的文件推荐使用本接口上传。
//同时并发上传的分片数量为10
// AsyncMPut 异步分片上传一个文件,filePath 是本地文件所在的路径,内部会自动对文件进行分片上传,上传的方式是使用异步的方式同时传多个分片的块。
// mimeType 如果为空的话,会调用 net/http 里面的 DetectContentType 进行检测。
// keyName 表示传到 ufile 的文件名。
// 大于 100M 的文件推荐使用本接口上传。
// 同时并发上传的分片数量为10
func (u *UFileRequest) AsyncMPut(filePath, keyName, mimeType string) error {
return u.AsyncUpload(filePath, keyName, mimeType, 10)
}

//AsyncUpload AsyncMPut 的升级版, jobs 表示同时并发的数量。
// AsyncUpload AsyncMPut 的升级版, jobs 表示同时并发的数量。
func (u *UFileRequest) AsyncUpload(filePath, keyName, mimeType string, jobs int) error {
if jobs <= 0 {
jobs = 1
Expand Down Expand Up @@ -181,11 +181,11 @@ func (u *UFileRequest) AsyncUpload(filePath, keyName, mimeType string, jobs int)
return u.FinishMultipartUpload(state)
}

//AbortMultipartUpload 取消分片上传,如果掉用 UploadPart 出现错误,可以调用本函数取消分片上传。
//state 参数是 InitiateMultipartUpload 返回的
// AbortMultipartUpload 取消分片上传,如果掉用 UploadPart 出现错误,可以调用本函数取消分片上传。
// state 参数是 InitiateMultipartUpload 返回的
func (u *UFileRequest) AbortMultipartUpload(state *MultipartState) error {
query := &url.Values{}
query.Add("uploadId", state.uploadID)
query.Add("uploadId", state.UploadID)
reqURL := u.genFileURL(state.keyName) + "?" + query.Encode()

req, err := http.NewRequest("DELETE", reqURL, nil)
Expand All @@ -197,11 +197,11 @@ func (u *UFileRequest) AbortMultipartUpload(state *MultipartState) error {
return u.request(req)
}

//InitiateMultipartUpload 初始化分片上传,返回一个 state 用于后续的 UploadPart, FinishMultipartUpload, AbortMultipartUpload 的接口。
// InitiateMultipartUpload 初始化分片上传,返回一个 state 用于后续的 UploadPart, FinishMultipartUpload, AbortMultipartUpload 的接口。
//
//keyName 表示传到 ufile 的文件名。
// keyName 表示传到 ufile 的文件名。
//
//mimeType 表示文件的 mimeType, 传空会报错,你可以使用 GetFileMimeType 方法检测文件的 mimeType。如果您上传的不是文件,您可以使用 http.DetectContentType https://golang.org/src/net/http/sniff.go?s=646:688#L11进行检测。
// mimeType 表示文件的 mimeType, 传空会报错,你可以使用 GetFileMimeType 方法检测文件的 mimeType。如果您上传的不是文件,您可以使用 http.DetectContentType https://golang.org/src/net/http/sniff.go?s=646:688#L11进行检测。
func (u *UFileRequest) InitiateMultipartUpload(keyName, mimeType string) (*MultipartState, error) {
reqURL := u.genFileURL(keyName) + "?uploads"
req, err := http.NewRequest("POST", reqURL, nil)
Expand Down Expand Up @@ -231,18 +231,18 @@ func (u *UFileRequest) InitiateMultipartUpload(keyName, mimeType string) (*Multi
return nil, err
}
response.keyName = keyName
response.etags = make(map[int]string)
response.Etags = make(map[int]string)
response.mimeType = mimeType

return response, err
}

//UploadPart 上传一个分片,buf 就是分片数据,buf 的数据块大小必须为 state.BlkSize,否则会报错。
//pardNumber 表示第几个分片,从 0 开始。例如一个文件按 state.BlkSize 分为 5 块,那么分片分别是 0,1,2,3,4。
//state 参数是 InitiateMultipartUpload 返回的
// UploadPart 上传一个分片,buf 就是分片数据,buf 的数据块大小必须为 state.BlkSize,否则会报错。
// pardNumber 表示第几个分片,从 0 开始。例如一个文件按 state.BlkSize 分为 5 块,那么分片分别是 0,1,2,3,4。
// state 参数是 InitiateMultipartUpload 返回的
func (u *UFileRequest) UploadPart(buf *bytes.Buffer, state *MultipartState, partNumber int) error {
query := &url.Values{}
query.Add("uploadId", state.uploadID)
query.Add("uploadId", state.UploadID)
query.Add("partNumber", strconv.Itoa(partNumber))

reqURL := u.genFileURL(state.keyName) + "?" + query.Encode()
Expand Down Expand Up @@ -280,15 +280,15 @@ func (u *UFileRequest) UploadPart(buf *bytes.Buffer, state *MultipartState, part
etag = strings.Trim(resp.Header.Get("ETag"), "\"") //为保证线程安全,这里就不保留 lastResponse
}
state.mux.Lock()
state.etags[partNumber] = etag
state.Etags[partNumber] = etag
state.mux.Unlock()
return nil
}

//UploadPartCopy 实现从一个已存在的Object中拷贝数据来上传一个Part。
// UploadPartCopy 实现从一个已存在的Object中拷贝数据来上传一个Part。
func (u *UFileRequest) UploadPartCopy(state *MultipartState, partNumber int, sourceBucketName, sourceObject string, offset, size int64) error {
query := &url.Values{}
query.Add("uploadId", state.uploadID)
query.Add("uploadId", state.UploadID)
query.Add("partNumber", strconv.Itoa(partNumber))

reqURL := u.genFileURL(state.keyName) + "?" + query.Encode()
Expand Down Expand Up @@ -322,21 +322,21 @@ func (u *UFileRequest) UploadPartCopy(state *MultipartState, partNumber int, sou
etag = strings.Trim(resp.Header.Get("ETag"), "\"") //为保证线程安全,这里就不保留 lastResponse
}
state.mux.Lock()
state.etags[partNumber] = etag
state.Etags[partNumber] = etag
state.mux.Unlock()
return nil
}

//FinishMultipartUpload 完成分片上传。分片上传必须要调用的接口。
//state 参数是 InitiateMultipartUpload 返回的
// FinishMultipartUpload 完成分片上传。分片上传必须要调用的接口。
// state 参数是 InitiateMultipartUpload 返回的
func (u *UFileRequest) FinishMultipartUpload(state *MultipartState) error {
query := &url.Values{}
query.Add("uploadId", state.uploadID)
query.Add("uploadId", state.UploadID)
reqURL := u.genFileURL(state.keyName) + "?" + query.Encode()
var etagsStr string
etagLen := len(state.etags)
etagLen := len(state.Etags)
for i := 0; i != etagLen; i++ {
etagsStr += state.etags[i]
etagsStr += state.Etags[i]
if i != etagLen-1 {
etagsStr += ","
}
Expand All @@ -360,7 +360,7 @@ func divideCeil(a, b int64) int {
return int(c)
}

//ListParts 获取已上传成功的分片列表
// ListParts 获取已上传成功的分片列表
func (u *UFileRequest) ListParts(uploadId string, maxParts, partNumberMarker int) (list ListPartsResponse, err error) {
if maxParts == 0 {
maxParts = 100
Expand Down
Loading