奉行长期主义的开发者都有一个共识:对于服务器来说,数据备份非常重要,因为服务器上的数据通常是无价的,如果丢失了这些数据,可能会导致严重的后果,伴随云时代的发展,备份技术也让千行百业看到了其“云基因”的成长与进化,即基于云存储的云备份。
本次我们使用Golang1.18完成百度网盘(百度云盘)接口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写入文件或者存入数据库,本文只讨论授权和上传逻辑,故不加入数据库的相关操作。
至此,百度网盘的授权操作就完成了。
说白了,有点像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"`
}
至此,完成了文件上传的三个阶段:预上传、分片上传、创建文件。
我们知道在 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
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功
我想知道是否可以通过自动创建数组来插入数组,如果数组不存在的话,就像在PHP中一样:$toto[]='titi';如果尚未定义$toto,它将创建数组并将“titi”压入。如果已经存在,它只会推送。在Ruby中我必须这样做:toto||=[]toto.push('titi')可以一行完成吗?因为如果我有一个循环,它会测试“||=”,除了第一次:Person.all.eachdo|person|toto||=[]#with1billionofperson,thislineisuseless999999999times...toto.push(person.name)你有更好的解决方案吗?
文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3
我不确定如何为我的搜索功能添加自动完成表单。"get"do%>nil%>我有一个具有自定义操作的Controllerdefquery@users=Search.user(params[:query])@article=Search.article(params[:query])end模型如下:defself.user(search)ifsearchUser.find(:all,:conditions=>['first_nameLIKE?',"%#{search}%"])elseUser.find(:all)endenddefself.article(search)ifsearchArt
我对自动测试的工作方式的印象(基于cucumbergithubwiki和其他在线内容)是它应该重新运行红色示例,直到它们通过。我的问题是它会重新运行规范文件中找到失败示例的所有示例,包括通过的示例。我不想浪费时间在修复失败示例的同时重新运行通过的示例。是否可以配置自动测试以便仅运行失败的示例? 最佳答案 您需要rspec-retrygem。以下是文档中有关如何实现它的一些示例:将它应用到覆盖整个测试套件的configureblock中...RSpec.configuredo|config|config.verbose_retry=t
代码:threads=[]Thread.abort_on_exception=truebegin#throwexceptionsinthreadssowecanseethemthreadseputs"EXCEPTION:#{e.inspect}"puts"MESSAGE:#{e.message}"end崩溃:.rvm/gems/ruby-2.1.3@req/gems/activesupport-4.1.5/lib/active_support/dependencies.rb:478:inload_missing_constant':自动加载常量MyClass时检测到循环依赖稍加研究后,