个人应用接入使用阿里云盘和百度网盘

个人应用接入使用阿里云盘和百度网盘

一、阿里云盘

官方文档接入流程 · 语雀流程概述服务端 API 调用流程如下图所示1. 创建账...https://www.yuque.com/aliyundrive/zpfszx/btw0tw

1. 接入授权

1.1. App Key、App Secret和用户授权验证

在通过网盘开发者认证之后,创建个人应用会生成APP ID(对应App Key)和App Secret,后续在组装生成用户授权验证地址时会使用到,同时也需要配置好Scopes(授权范围)以及回调URI(用于授权后跳转到个人应用等),参数细节详见官方文档。

// 生成授权二维码链接

// 此处只填写了必须的参数

func GenerateAuthUrl() string {

params := url.Values{}

params.Add("response_type", "code")

params.Add("client_id", appKey)

params.Add("redirect_uri", redirectUri)

// params.Add("state", state) // 可通过填入随机数来防止CSRF攻击

params.Add("scope", scope)

authUrlWithParams := authUrl + "?" + params.Encode()

return authUrlWithParams

}

1.2. 授权验证方式壹——无后端服务授权模式

阿里云盘通过OAuth2.0 + PKCE的方式,通过提供code_verifier(常通过个人应用进行运算得来)来进行一种无需App Secret的授权验证模式。流程图(Sequence Diagram)

通过code_verifier做参数请求阿里云盘,交换得到的Access Token,默认有效期为30天。

(此次并未作代码实践)

1.3. 授权验证方式贰——扫码授权验证

流程图如下:

实践中,最初通过"github.com/skip2/go-qrcode",把组装的授权验证链接转换为二维码,可以正常工作。

// 生成二维码图片并保存

func SaveQrCode(authUrl string) {

qrCode, _ := qrcode.New(authUrl, qrcode.Medium)

err := qrCode.WriteFile(256, "auth_qr_code.png")

if err != nil {

log.Fatal(err)

}

fmt.Println("QR code saved as auth_qr_code.png")

}

后续又使用API获取授权二维码,同样是可以工作的,有效时长默认3分钟。

// 获取授权二维码的链接和用于校验状态的sid

func GetQrCodeFromAli() (string, string, error) {

reqBody := map[string]interface{}{

"client_id": appKey,

"client_secret": appSecret,

"scopes": scopes,

}

jsonData, _ := json.Marshal(reqBody)

req, err := http.NewRequest("POST", apiBase+"/oauth/authorize/qrcode", bytes.NewBuffer(jsonData))

if err != nil {

return "", "", err

}

req.Header.Set("Content-Type", "application/json")

defer req.Body.Close()

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return "", "", err

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

var qrCodeResp QrCodeResponse

_ = json.Unmarshal(body, &qrCodeResp)

return qrCodeResp.QrCodeUrl, qrCodeResp.Sid, nil

}

// 获取二维码图片

func GetQrCodePic(sid string) {

// 构建请求 URL

url := fmt.Sprintf("%s/oauth/qrcode/%s", apiBase, sid)

// 创建请求

req, err := http.NewRequest("GET", url, nil)

if err != nil {

log.Fatalf("Failed to create request: %v", err)

}

// 发送请求

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

log.Fatalf("Failed to send request: %v", err)

}

defer resp.Body.Close()

// 创建本地文件

filePath := "qrcode.jpeg"

file, err := os.Create(filePath)

if err != nil {

log.Fatalf("Failed to create file: %v", err)

}

defer file.Close()

// 写入文件

_, err = io.Copy(file, resp.Body)

if err != nil {

log.Fatalf("Failed to write to file: %v", err)

}

log.Printf("QR code image saved to %s", filePath)

}

1.4. 用code_verifier或code(authCode)交换获取Access Token

上述两种不同的方式在交换获取Access Token的方法是有差异的,无后端模式扫码后会提供code_verifier,而扫码模式将会提供code(或authCode),从官方文档中可见,在传参的时候有一些差异(下方代码是传code)。

// 交换Access Token

func ExchangeToken(code string) (*TokenResponse, error) {

params := url.Values{}

params.Add("grant_type", "authorization_code")

params.Add("code", code)

params.Add("client_id", appKey)

params.Add("client_secret", appSecret)

params.Add("redirect_uri", redirectUri)

resp, err := http.PostForm(tokenUrl, params)

if err != nil {

return nil, err

}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)

if err != nil {

return nil, err

}

var tokenResp TokenResponse

err = json.Unmarshal(body, &tokenResp)

if err != nil {

return nil, err

}

if tokenResp.Code != "" {

return nil, fmt.Errorf("API error: %s", tokenResp.Message)

}

return &tokenResp, nil

}

拿到Access Token之后就可以个人应用就可以调用API来与阿里云盘交互了。

2. 上传文件

想要通过个人应用(通过授权校验后)上传文件到阿里云盘,主要有三个流程:

创建文件文件上传上传完毕

不过,一系列的文件操作都需要提供唯一标识用户云盘的drive_id,可以通过一个POST请求来获取。

2.1. 获取drive_id

在官方文档中,没有在这一部分再次说明需要设置Header,不过想要获得drive_id,是通过access_token获取用户信息,要把access_token放到head里。

// 获取用户信息

func GetUserDriveInfo(accessToken string) (*UserDriveInfoResponse, error) {

req, err := http.NewRequest("POST", apiBase+"/adrive/v1.0/user/getDriveInfo", nil)

if err != nil {

return nil, err

}

req.Header.Set("Authorization", "Bearer "+accessToken) // 设置Header

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return nil, err

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

var userDriveInfoResp UserDriveInfoResponse

json.Unmarshal(body, &userDriveInfoResp)

return &userDriveInfoResp, nil

}

有了access_token和drive_id之后,可以开始上传文件的流程了。

2.2. 创建文件

在正式上传文件之前,会在阿里云盘的服务器上创建一个占位符或元数据记录,不是实际的文件内容,而是文件的元数据,包括文件名、大小、类型等信息。最后返回一个唯一的文件标识符(file_id或upload_id)在后续的上传过程中使用。

发送请求时的必要参数有:

// 创建文件

func createFile(accessToken string, driveId string, parentFileID string, fileName string, fileSize int64, checkNameMode string) (*CreateFileResponse, error) {

reqBody := map[string]interface{}{

"drive_id": driveId,

"parent_file_id": parentFileID,

"name": fileName,

"type": "file",

"check_name_mode": checkNameMode,

"size": fileSize, // 如果后续是秒传,这个参数是必须的,此处不是秒传

"local_created_at": time.Now().Unix(), // 选填参数

}

jsonData, _ := json.Marshal(reqBody)

req, err := http.NewRequest("POST", apiBase+"/adrive/v1.0/openFile/create", bytes.NewBuffer(jsonData))

if err != nil {

return nil, err

}

req.Header.Set("Authorization", "Bearer "+accessToken)

req.Header.Set("Content-Type", "application/json")

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return nil, err

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

var createFileResp CreateFileResponse

json.Unmarshal(body, &createFileResp)

return &createFileResp, nil

}

返回参数上,创建好文件之后,会返回part_info_list[*].upload_url、file_id、upload_id,后续的上传会通过这个链接进行。

2.3. 刷新获取上传链接

除了创建文件之后会返回上传链接,也可以根据创建文件后返回的upload_id来获取上传链接。

// 获取上传链接

func getUploadUrl(accessToken string, driveId string, fileId, uploadId string) ([]struct {

PartNumber int `json:"part_number"`

UploadUrl string `json:"upload_url"`

}, error) {

reqBody := map[string]interface{}{ // 都是必填参数

"drive_id": driveId,

"file_id": fileId,

"upload_id": uploadId,

"part_info_list": []map[string]int{

{"part_number": 1},

},

}

jsonData, _ := json.Marshal(reqBody)

req, err := http.NewRequest("POST", apiBase+"/adrive/v1.0/openFile/getUploadUrl", bytes.NewBuffer(jsonData))

if err != nil {

return nil, err

}

req.Header.Set("Authorization", "Bearer "+accessToken)

req.Header.Set("Content-Type", "application/json")

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return nil, err

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

var createFileResp struct {

PartInfoList []struct {

PartNumber int `json:"part_number"`

UploadUrl string `json:"upload_url"`

} `json:"part_info_list"`

}

json.Unmarshal(body, &createFileResp)

return createFileResp.PartInfoList, nil

}

2.4. 上传文件

主流的上传方式是分片上传,也支持单步上传和文件秒传。

2.4.1. 单片上传

在阿里云盘中,单文件上传最大支持5GB,通过一个向上传链接发送PUT请求。

// 上传文件

func upload(accessToken string, driveId string, dstDir string, filePath string) error {

// 1. create

fileSize, _ := GetFileSize(filePath)

// 创建文件请求的响应结果

createFileResp, _ := createFile(accessToken, driveId, dstDir, filePath, fileSize, "auto_rename")

// 2. upload

file, err := os.Open(filePath)

if err != nil {

return err

}

defer file.Close()

uploadUrl, _ := getUploadUrl(accessToken, driveId, createFileResp.FileId, createFileResp.UploadId)

req, err := http.NewRequest("PUT", uploadUrl[0].UploadUrl, file)

req.Header.Set("Authorization", "Bearer "+accessToken)

if err != nil {

return err

}

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return err

}

defer resp.Body.Close()

// 3. complete

return completeUpload(accessToken, driveId, createFileResp.FileId, createFileResp.UploadId)

}

2.4.2. 分片上传

与单步上传相比,分片上传基本就是把一个大文件分为了多个部分,进行了多次单片上传。不过,在参数上有不同。分片上传需要在创建文件后刷新获取上传链接,而在刷新获取上传链接时,part_info_list不再只分为一片。此外,如何分片,也是必不可少的。

// 刷新获取上传链接时,提供的参数有所不同

reqBody := map[string]interface{}{ // 都是必填参数

"drive_id": driveId,

"file_id": fileId,

"upload_id": uploadId,

"part_info_list": []map[string]int{

{"part_number": 1},

{"part_number": 2},

{"part_number": 3},

},

}

在返回响应时,会返回多个用于上传的链接(分为3片的文件会分别通过三个链接上传),官方的示例如下:

2.4.3. 文件秒传

文件秒传,通过把用户的上传的进行HASH匹配,如果存在文件HASH值,就不需要上传而是直接从服务器复制过去。上文提到,如果需要使用秒传能力,在创建时带上秒传所需参数。

如果文件比较大,计算content_hash比较耗时。 可以使用只计算文件前1k的sha1,放入pre_hash字段。如果前1k没有匹配,说明文件无法做秒传。如果匹配到再计算完整的sha1,进行秒传。

(未实践...)

2.5. 上传完毕

这个步骤的目的是:通知阿里云盘服务器所有文件块已上传完成,服务器可以将这些块合并成一个完整的文件。

// completeUpload 完成上传

func completeUpload(accessToken string, driveId string, fileId, uploadId string) error {

var newFile File

reqBody := map[string]interface{}{

"drive_id": driveId,

"file_id": fileId,

"upload_id": uploadId,

}

jsonData, _ := json.Marshal(reqBody)

req, err := http.NewRequest("POST", apiBase+"/adrive/v1.0/openFile/complete", bytes.NewBuffer(jsonData))

if err != nil {

return err

}

req.Header.Set("Authorization", "Bearer "+accessToken)

req.Header.Set("Content-Type", "application/json")

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return err

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

json.Unmarshal(body, &newFile)

return nil

}

二、百度网盘

官方文档

获取用户信息https://pan.baidu.com/union/doc/pksg0s9ns

1. 预准备

前置的创建应用、获取App Key、获取App Secret、以及交换获取Access Token不再赘述,大致步骤与阿里云盘相同,只是参数和用来请求的链接不同。下面主要关注不同之处:

1.1. 简化模式授权

此种模式与阿里云盘中无后端服务授权模式类似,不过不像阿里云盘需要通过个人应用运算生成code_verifier。

1.2. 设备码模式授权

流程图如下,通过向硬件设备请求设备码和用户码,作为参数来请求Access Token,使得一些不支持浏览器或输入受限的设备也可以接入。

2. 上传文件

百度网盘上传文件,同样也分为三个步骤:

预上传文件上传(有不需要预上传的单步上传)创建文件

与阿里云盘不同的是:百度网盘提供的Access Token已经可以唯一标识用户,所以也没有像阿里云盘那样去获取drive_id,上传文件的时候,常把Access Token用来作为参数。

此外:

2.1. 预上传

在预上传时,如果文件有进行分片,则需要把各个分片的MD5,填入一个字符串数组作为参数;与阿里云盘不同的是,第一个步骤必须填入size这个参数(阿里云盘只有在秒传的时候需要填写size)。

同样的,响应时会返回uploadid用来唯一标识本次传输任务。

2.2. 获取上传域名

在获取上传链接时,百度网盘会返回多个上传链接(为了并行执行多个分片的上传,也可能是负载均衡),使用其中任意一个作为上传链接即可。

// 获取上传 URL

func getUploadUrl(accessToken string, dstDir string, uploadId string) string {

req, err := http.NewRequest("GET",

"https://d.pcs.baidu.com/rest/2.0/pcs/file?"+

"method=locateupload&appid=250528&access_token="+accessToken+

"&path="+dstDir+"&uploadid="+uploadId+"&upload_version=2.0", nil)

if err != nil {

return ""

}

client := &http.Client{}

resp, err := client.Do(req)

if err != nil {

return "Exec req err"

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

var uploadDomainResponse UploadDomainResponse

json.Unmarshal(body, &uploadDomainResponse)

return uploadDomainResponse.Servers[0].Server

}

下面是官方文档中的响应示例:

2.3. 文件上传

2.3.1. 单步上传

针对小于2GB的文件,百度网盘提供了无需预上传和创建文件流程的单步上传,请求参数如下:

// 单步上传

func uploadFile(accessToken, localPath, remotePath string) error {

url := "https://c.pcs.baidu.com/rest/2.0/pcs/file"

params := map[string]string{

"method": "upload",

"access_token": accessToken,

"path": remotePath,

"ondup": "newcopy",

}

file, err := os.Open(localPath)

if err != nil {

return err

}

defer file.Close()

resp, err := grequests.Post(url, &grequests.RequestOptions{

Params: params,

Files: []grequests.FileUpload{

{

FileContents: file,

},

},

})

if err != nil {

return err

}

if !resp.Ok {

return fmt.Errorf("upload failed with status code: %d", resp.StatusCode)

}

return nil

}

2.3.2. 分片上传

在请求参数上,百度网盘把参数大多都放在URL的位置,与阿里云盘大多放在Body请求体中不同。此处的分片上传,通过partseq来区分、上传分好的多个分片(也是并行的)。

2.4. 创建文件

创建文件和阿里云盘中完成上传所实现的功能类似,把多个分片,组成一个完整的文件。

相关推荐

[原创]游戏类型大百科(Game Genres)-科普游戏类型与英文缩写
beat365中国在线体育官网

[原创]游戏类型大百科(Game Genres)-科普游戏类型与英文缩写

📅 09-19 👁️ 4658
为什么二月二要剪头发?原来寓意这么好!
beat365中国在线体育官网

为什么二月二要剪头发?原来寓意这么好!

📅 08-11 👁️ 6758
日本的洗衣机品牌有哪些
365彩票下载1.0.0老版本

日本的洗衣机品牌有哪些

📅 06-30 👁️ 7314