奉行長(zhǎng)期主義的開發(fā)者都有一個(gè)共識(shí):對(duì)于服務(wù)器來說程拭,數(shù)據(jù)備份非常重要,因?yàn)榉?wù)器上的數(shù)據(jù)通常是無價(jià)的棍潘,如果丟失了這些數(shù)據(jù)恃鞋,可能會(huì)導(dǎo)致嚴(yán)重的后果崖媚,伴隨云時(shí)代的發(fā)展,備份技術(shù)也讓千行百業(yè)看到了其“云基因”的成長(zhǎng)與進(jìn)化恤浪,即基于云存儲(chǔ)的云備份畅哑。
本次我們使用Golang1.18完成百度網(wǎng)盤(百度云盤)接口API自動(dòng)化備份上傳功能,以及演示如何將該模塊進(jìn)行開源發(fā)布水由。
百度網(wǎng)盤API接入授權(quán)
如果希望golang服務(wù)可以訪問并且上傳用戶的百度網(wǎng)盤拐邪,則需要經(jīng)過用戶同意厘唾,這個(gè)流程被稱為“授權(quán)”。百度網(wǎng)盤開放平臺(tái)基于 OAuth2.0 接入授權(quán)。OAuth2.0 是一種授權(quán)協(xié)議宰掉,通過該協(xié)議用戶可以授權(quán)開發(fā)者應(yīng)用訪問個(gè)人網(wǎng)盤信息與文件补君。
用戶同意授權(quán)后矿辽,開發(fā)者應(yīng)用會(huì)獲取到一個(gè) Access Token竞膳,該 Access Token 是用戶同意授權(quán)的憑證。開發(fā)者應(yīng)用需要依賴 Access Token 憑證調(diào)用百度網(wǎng)盤公開API彤恶,實(shí)現(xiàn)訪問用戶網(wǎng)盤信息與授權(quán)資源钞钙。
基本流程和三方登錄差不多,需要跳轉(zhuǎn)百度網(wǎng)盤授權(quán)頁進(jìn)行授權(quán)動(dòng)作声离,隨后授權(quán)碼(code)會(huì)發(fā)送到回調(diào)網(wǎng)址芒炼,再用授權(quán)碼換取Access Token。但不一樣的是抵恋,百度官網(wǎng)提供一種相對(duì)簡(jiǎn)單的獲取code方式焕议,即oob,所謂oob就是直接在線請(qǐng)求后在表單中復(fù)制授權(quán)碼即可弧关,不需要回調(diào)網(wǎng)址的參與。
首先根據(jù)官網(wǎng)文檔:https://pan.baidu.com/union/doc/ol0rsap9s 創(chuàng)建應(yīng)用唤锉,創(chuàng)建好之后世囊,將應(yīng)用id拼接位oob授權(quán)網(wǎng)址:
https://openapi.baidu.com/oauth/2.0/authorize?client_id=你的應(yīng)用id&response_type=code&redirect_uri=oob&scope=basic+netdisk
在線訪問復(fù)制授權(quán)碼:
注意授權(quán)碼一次性有效并且會(huì)在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
}
這里分別創(chuàng)建網(wǎng)盤結(jié)構(gòu)體和秘鑰結(jié)構(gòu)體窿祥,通過官方接口將oob方式獲取的code交換token株憾,分別為accessToken和refreshToken,refreshToken用于刷新 Access Token, 有效期為10年晒衩。
這里最好將token寫入文件或者存入數(shù)據(jù)庫嗤瞎,本文只討論授權(quán)和上傳邏輯,故不加入數(shù)據(jù)庫的相關(guān)操作听系。
至此贝奇,百度網(wǎng)盤的授權(quán)操作就完成了。
服務(wù)器本地文件上傳至百度網(wǎng)盤
根據(jù)官網(wǎng)文檔描述:https://pan.baidu.com/union/doc/3ksg0s9ye靠胜,上傳流程是指掉瞳,用戶將本地文件上傳到百度網(wǎng)盤云端服務(wù)器的過程毕源。文件上傳分為三個(gè)階段:預(yù)上傳、分片上傳陕习、創(chuàng)建文件霎褐。第二個(gè)階段分片上傳依賴第一個(gè)階段預(yù)上傳的結(jié)果,第三個(gè)階段創(chuàng)建文件依賴第一個(gè)階段預(yù)上傳和第二階段分片上傳的結(jié)果该镣,串行完成這三個(gè)階段任務(wù)后冻璃,本地文件成功上傳到網(wǎng)盤服務(wù)器。
說白了损合,有點(diǎn)像HTTP連接的三次握手省艳,目的就是為了保證上傳數(shù)據(jù)的完整性,強(qiáng)制串行的原子操作也有利于保證上傳任務(wù)的可靠性塌忽。
首先構(gòu)建預(yù)上傳函數(shù):
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
}
這里參數(shù)為預(yù)上傳參數(shù)的結(jié)構(gòu)體:
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"` // 需要與上一個(gè)階段預(yù)上傳precreate接口中的path保持一致
UploadID string `query:"uploadid"` // 上一個(gè)階段預(yù)上傳precreate接口下發(fā)的uploadid
PartSeq int64 `query:"partseq"` // 文件分片的位置序號(hào)拍埠,從0開始,參考上一個(gè)階段預(yù)上傳precreate接口返回的block_list
File io.Reader `file:"file"` // 是 RequestBody參數(shù) 上傳的文件內(nèi)容
}
對(duì)于總體積大于4mb的文件土居,通過切片的方式進(jìn)行上傳枣购。
總后是合并文件寫入文件邏輯:
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"`
}
至此,完成了文件上傳的三個(gè)階段:預(yù)上傳擦耀、分片上傳棉圈、創(chuàng)建文件。
開源發(fā)布Publish
我們知道在 Golang的項(xiàng)目中眷蜓,可以 import 一個(gè)托管在遠(yuǎn)程倉(cāng)庫的模塊分瘾,這個(gè)模塊在我們使用 go get 的時(shí)候,會(huì)下載到本地吁系。既然是放在遠(yuǎn)程倉(cāng)庫上德召,意味著所有人都可以發(fā)布,并且所以人也都可以使用汽纤,所以為了讓鄉(xiāng)親們更方便地上傳數(shù)據(jù)到百度網(wǎng)盤上岗,讓我們把這個(gè)項(xiàng)目開源。
先在你的 Github 上新建一個(gè)倉(cāng)庫蕴坪,記得選 Public(公開項(xiàng)目)肴掷,隨后將項(xiàng)目代碼推送到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
在項(xiàng)目根目錄使用go mod init 命令進(jìn)行初始化,注意這里的模塊名背传,填寫我們的git倉(cāng)庫名稱呆瞻,但是不要帶著.git:
go mod init github.com/zcxey2911/bdyp_upload_golang
再次推送項(xiàng)目模塊代碼:
git add -A
git commit -m "Add a go mod file"
git push -u origin main
全部完成以后,刷新我們的倉(cāng)庫径玖,就可以看到我們的剛剛上傳的項(xiàng)目代碼了痴脾,點(diǎn)擊 release 發(fā)布一個(gè)版本即可。
最后挺狰,通過go get命令安裝發(fā)布之后的模塊:
go get github.com/zcxey2911/bdyp_upload_golang
完整的調(diào)用流程:
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", "應(yīng)用appkey", "應(yīng)用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,
}))
}
查看上傳的數(shù)據(jù):
簡(jiǎn)單快速明郭,一氣呵成买窟。
結(jié)語
當(dāng)然了百度云盤備份也不是沒有缺陷,將數(shù)據(jù)存儲(chǔ)在云端可能會(huì)存在安全性和隱私性問題薯定,與此同時(shí)始绍,數(shù)據(jù)量很大或者數(shù)據(jù)分布在不同地點(diǎn)的情況下,恢復(fù)數(shù)據(jù)所需的時(shí)間會(huì)比較長(zhǎng)话侄。不差錢的同學(xué)也可以選擇磁盤快照服務(wù)亏推,最后奉上項(xiàng)目地址,與君共勉:https://github.com/zcxey2911/bdyp_upload_golang