防微杜漸,未雨綢繆,百度網盤(百度雲盤)介面API自動化備份上傳以及開源釋出,基於Golang1.18

劉悅的技術部落格發表於2023-01-11

奉行長期主義的開發者都有一個共識:對於伺服器來說,資料備份非常重要,因為伺服器上的資料通常是無價的,如果丟失了這些資料,可能會導致嚴重的後果,伴隨雲時代的發展,備份技術也讓千行百業看到了其“雲基因”的成長與進化,即基於雲端儲存的雲備份。

本次我們使用Golang1.18完成百度網盤(百度雲盤)介面API自動化備份上傳功能,以及演示如何將該模組進行開源釋出。

百度網盤API接入授權

如果希望golang服務可以訪問並且上傳使用者的百度網盤,則需要經過使用者同意,這個流程被稱為“授權”。百度網盤開放平臺基於 OAuth2.0 接入授權。OAuth2.0 是一種授權協議,透過該協議使用者可以授權開發者應用訪問個人網盤資訊與檔案。

使用者同意授權後,開發者應用會獲取到一個 Access Token,該 Access Token 是使用者同意授權的憑證。開發者應用需要依賴 Access Token 憑證呼叫百度網盤公開API,實現訪問使用者網盤資訊與授權資源。

基本流程和三方登入差不多,需要跳轉百度網盤授權頁進行授權動作,隨後授權碼(code)會傳送到回撥網址,再用授權碼換取Access Token。但不一樣的是,百度官網提供一種相對簡單的獲取code方式,即oob,所謂oob就是直接線上請求後在表單中複製授權碼即可,不需要回撥網址的參與。

首先根據官網文件:https://pan.baidu.com/union/doc/ol0rsap9s 建立應用,建立好之後,將應用id拼接位oob授權網址:

https://openapi.baidu.com/oauth/2.0/authorize?client_id=你的應用id&response_type=code&redirect_uri=oob&scope=basic+netdisk

線上訪問複製授權碼:

注意授權碼一次性有效並且會在10分鐘後過期,隨後編寫程式碼獲取token:

package bdyp  
  
import (  
	"fmt"  
	"net/http"  
	"net/url"  
)  
  
type Bcloud struct {  
	app_key      string  
	app_secret   string  
	accessToken  string  
	refreshToken string  
	logger       Logger  
}  
  
type tokenResp struct {  
	*Token  
	ErrorDescription string `json:"error_description"`  
}  
  
type Token struct {  
	AccessToken  string `json:"access_token"`  
	RefreshToken string `json:"refresh_token"`  
	ExpiresIn    int    `json:"expires_in"`  
}  
  
func (r *Bcloud) GetToken(code, redirectURI, app_key, app_secret string) (*Token, error) {  
	uri := fmt.Sprintf("https://openapi.baidu.com/oauth/2.0/token?"+  
		"grant_type=authorization_code&"+  
		"code=%s&"+  
		"client_id=%s&"+  
		"client_secret=%s&"+  
		"redirect_uri=%s",  
		url.QueryEscape(code),  
		url.QueryEscape(app_key),  
		url.QueryEscape(app_secret),  
		redirectURI)  
	resp := new(tokenResp)  
  
	err := r.requestJSON(http.MethodGet, uri, nil, resp)  
	if err != nil {  
		return nil, err  
	} else if resp.ErrorDescription != "" {  
		return nil, fmt.Errorf(resp.ErrorDescription)  
	}  
  
	r.app_key = app_key  
	r.app_secret = app_secret  
	r.accessToken = resp.AccessToken  
	r.refreshToken = resp.RefreshToken  
  
	return resp.Token, nil  
}

這裡分別建立網盤結構體和秘鑰結構體,透過官方介面將oob方式獲取的code交換token,分別為accessToken和refreshToken,refreshToken用於重新整理 Access Token, 有效期為10年。

這裡最好將token寫入檔案或者存入資料庫,本文只討論授權和上傳邏輯,故不加入資料庫的相關操作。

至此,百度網盤的授權操作就完成了。

伺服器本地檔案上傳至百度網盤

根據官網文件描述:https://pan.baidu.com/union/doc/3ksg0s9ye,上傳流程是指,使用者將本地檔案上傳到百度網盤雲端伺服器的過程。檔案上傳分為三個階段:預上傳、分片上傳、建立檔案。第二個階段分片上傳依賴第一個階段預上傳的結果,第三個階段建立檔案依賴第一個階段預上傳和第二階段分片上傳的結果,序列完成這三個階段任務後,本地檔案成功上傳到網盤伺服器。

說白了,有點像HTTP連線的三次握手,目的就是為了保證上傳資料的完整性,強制序列的原子操作也有利於保證上傳任務的可靠性。

首先構建預上傳函式:

func (r *Bcloud) FileUploadSessionStart(req *FileUploadSessionStartReq) (*FileUploadSessionStartResp, error) {  
	token, err := r.getAuthToken()  
	if err != nil {  
		return nil, err  
	}  
  
	req.Method = "precreate"  
	req.AccessToken = token  
  
	req_, err := req.to()  
	if err != nil {  
		return nil, err  
	}  
  
	resp := new(FileUploadSessionStartResp)  
  
	err = r.requestURLEncode(http.MethodPost, "https://pan.baidu.com/rest/2.0/xpan/file", req_, resp)  
	if err != nil {  
		return nil, err  
	} else if err := resp.Err(); err != nil {  
		return nil, err  
	}  
  
	if len(resp.BlockList) == 0 {  
		resp.BlockList = []int64{0}  
	}  
  
	return resp, nil  
}

這裡引數為預上傳引數的結構體:

type FileUploadSessionStartReq struct {  
	Method      string `query:"method"`  
	AccessToken string `query:"access_token"`  
	Path        string `json:"path"`  
	File        io.Reader  
	RType       *int64 `json:"rtype"`  
}

隨後是分片上傳邏輯:

func (r *Bcloud) FileUploadSessionAppend(req *FileUploadSessionAppendReq) error {  
	token, err := r.getAuthToken()  
	if err != nil {  
		return err  
	}  
  
	req.Method = "upload"  
	req.AccessToken = token  
	req.Type = "tmpfile"  
  
	resp := new(fileUploadSessionAppendResp)  
  
	err = r.requestForm(http.MethodPost, "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2", req, resp)  
	if err != nil {  
		return err  
	} else if err := resp.Err(); err != nil {  
		return err  
	} else if resp.ErrorMsg != "" {  
		return fmt.Errorf(resp.ErrorMsg)  
	}  
  
	return nil  
}  
  
type FileUploadSessionAppendReq struct {  
	Method      string    `query:"method"` // 本介面固定為precreate  
	AccessToken string    `query:"access_token"`  
	Type        string    `query:"type"`     // 固定值 tmpfile  
	Path        string    `query:"path"`     // 需要與上一個階段預上傳precreate介面中的path保持一致  
	UploadID    string    `query:"uploadid"` // 上一個階段預上傳precreate介面下發的uploadid  
	PartSeq     int64     `query:"partseq"`  // 檔案分片的位置序號,從0開始,參考上一個階段預上傳precreate介面返回的block_list  
	File        io.Reader `file:"file"`      // 是		RequestBody引數	上傳的檔案內容  
}

對於總體積大於4mb的檔案,透過切片的方式進行上傳。

總後是合併檔案寫入檔案邏輯:

func (r *Bcloud) FileUploadSessionFinish(req *FileUploadSessionFinishReq) error {  
	token, err := r.getAuthToken()  
	if err != nil {  
		return err  
	}  
  
	req.Method = "create"  
	req.AccessToken = token  
  
	req_, err := req.to()  
	if err != nil {  
		return err  
	}  
  
	resp := new(fileUploadSessionFinishResp)  
  
	err = r.requestURLEncode(http.MethodPost, "https://pan.baidu.com/rest/2.0/xpan/file", req_, resp)  
	if err != nil {  
		return err  
	} else if err := resp.Err(); err != nil {  
		return err  
	}  
  
	return nil  
}  
  
type FileUploadSessionFinishReq struct {  
	Method      string    `query:"method"`  
	AccessToken string    `query:"access_token"`  
	Path        string    `json:"path"`  
	File        io.Reader `json:"-"`  
	UploadID    string    `json:"uploadid"`  
	RType       *int64    `json:"rtype"`  
}

至此,完成了檔案上傳的三個階段:預上傳、分片上傳、建立檔案。

開源釋出Publish

我們知道在 Golang的專案中,可以 import 一個託管在遠端倉庫的模組,這個模組在我們使用 go get 的時候,會下載到本地。既然是放在遠端倉庫上,意味著所有人都可以釋出,並且所以人也都可以使用,所以為了讓鄉親們更方便地上傳資料到百度網盤,讓我們把這個專案開源。

先在你的 Github 上新建一個倉庫,記得選 Public(公開專案),隨後將專案程式碼推送到Github上面:

echo "# bdyp_upload_golang" >> README.md  
git init  
git add README.md  
git commit -m "first commit"  
git branch -M main  
git remote add origin https://github.com/zcxey2911/bdyp_upload_golang.git  
git push -u origin main

在專案根目錄使用go mod init 命令進行初始化,注意這裡的模組名,填寫我們的git倉庫名稱,但是不要帶著.git:

go mod init github.com/zcxey2911/bdyp_upload_golang

再次推送專案模組程式碼:

git add -A  
git commit -m "Add a go mod file"
git push -u origin main

全部完成以後,重新整理我們的倉庫,就可以看到我們的剛剛上傳的專案程式碼了,點選 release 釋出一個版本即可。

最後,透過go get命令安裝釋出之後的模組:

go get github.com/zcxey2911/bdyp_upload_golang

完整的呼叫流程:

package main  
  
import (  
	"fmt"  
	bdyp "github.com/zcxey2911/bdyp_upload_golang"  
	"os"  
)  
  
func main() {  
  
	var bcloud = bdyp.Bcloud{}  
  
	// 獲取token  
	res, err := bcloud.GetToken("oob獲取的code", "oob", "應用appkey", "應用appsecret")  
  
	fmt.Println(res)  
  
	if err != nil {  
		fmt.Println("err", err)  
	} else {  
		fmt.Printf("介面的token是: %#v\n", res.AccessToken)  
	}  
	// 讀取檔案  
	f, err := os.Open("/Users/liuyue/Downloads/ju1.webp")  
	if err != nil {  
		fmt.Println("err", err)  
		return  
	}  
	defer f.Close()  
  
	// 上傳檔案  
	print(bcloud.Upload(&bdyp.FileUploadReq{  
		Name:  "/apps/雲盤備份/ju2.webp",  
		File:  f,  
		RType: nil,  
	}))  
  
}

檢視上傳的資料:

簡單快速,一氣呵成。

結語

當然了百度雲盤備份也不是沒有缺陷,將資料儲存在雲端可能會存在安全性和隱私性問題,與此同時,資料量很大或者資料分佈在不同地點的情況下,恢復資料所需的時間會比較長。不差錢的同學也可以選擇磁碟快照服務,最後奉上專案地址,與君共勉:https://github.com/zcxey2911/bdyp_upload_golang

相關文章