奉行長期主義的開發者都有一個共識:對于伺服器來說,資料備份非常重要,因為伺服器上的資料通常是無價的,如果丢失了這些資料,可能會導緻嚴重的後果,伴随雲時代的發展,備份技術也讓千行百業看到了其“雲基因”的成長與進化,即基于雲存儲的雲備份。
本次我們使用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