Bootstrap

通过接口上传文件到百度网盘

背景

之前做的一个项目,由于需要频繁的上传大文件资源,导致存储空间的需求直线上升,在解决这个问题的过程中,偶然发现了百度网盘有一个开放平台,只需要简单的几步操作,就可以实现本地资源实时上传到百度网盘。当然了,最终我们的项目是没有采用这种方案,因为还是有一些暂时没有解决的问题,但这个方案作为个人使用,我觉得还是可以的,考验动手能力~。

前置工作

百度网盘开放平台的地址:根据平台的介绍,百度网盘开放了很多能力,包括图片处理,音视频,上传文件,下载文件,附件能力,nas能力,文件提取,设备管理等等。我们在解决文件存储问题的过程,主要是用到了上传文件和音视频的接口。

来看下整个的接入流程。

1.创建应用

首先,百度网盘开放平台,并不是像百度云或者其他云服务一样,针对所有用户都开放的,需要提交使用申请,通过之后方可接入相关能力。申请地址如下:,这个没什么好说的,技术是人家的,想拿来用就得按人家的规则来。一般提交了申请,1,2天结果就下来了。申请下来之后,到开放平台的控制台创建一个工程(和百度云控制台的工程是互通的)

2.设定回调参数

创建完成后,会得到一个应用的相关信息,同时根据平台的建议,需要设定一个可以访问到的回调页

当然不设定也是可以的,下面我会提到,但如果是生产环境使用的话,最好是设定这样一个回调页。这一部分平台的相关帮助内容在这个页面设定完成后,我们可以访问一个特定的链接,用到工程项目中的apikey和secretkey以及回调链接当作参数,具体如下

http://openapi.baidu.com/oauth/2.0/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REGISTERED_REDIRECT_URI&
scope=email&
display=popup

这一步完成后,会跳转到我们设定的那个回调页,同时会多一个code参数,拿到这个code就可以取换取token了(一个标准的Oauth2.0操作)。

3.获取token

前面提到,如果不设定回调页也是可以的,在对应的url参数上填写“obb”固定值,访问拼接好的连接后,会跳转到一个百度提供的回调页,也可以得到code,但这不是建议的方式。这一步,主要涉及到跳转,传参,取值等操作,取到code之后,基本这一步以后就不会再用到了,因为换取到access_token的同时,还会得到一个refresh_token,其中access_token的有效期是一个月,而refresh_token的有效期确实10年!也就是说,成功拿到access_token之后,有10年的使用期~~至此,前戏结束。

上传文件

拿到令牌以后,后续就容易多了。接下来,要把大象装冰箱,总共分三步。我们要把我们的大文件传到百度云盘,也分三步,接口文档为

1.预上传

这里我就想说一下那个block_list这个参数,这个值是文件的md5值,在第一步预上传的时候,可以通过程序来获取该值。实现的接口代码就非常简单了,也简单贴一下

/// 
/// 预上传,参数模型
/// 
public class BdNasPrecreate
{
    /// 
    /// 上传后使用的文件绝对路径
    /// 
    public string path { get; set; }
    /// 
    /// 文件或目录的大小,单位是字节(B)
    /// 
    public string size { get; set; }
    /// 
    /// 是否目录,0 文件、1 目录
    /// 
    public string isdir { get; set; } = "0";
    /// 
    /// 固定值1
    /// 
    public int autoinit { get; set; } = 1;
    /// 
    /// 文件命名策略,默认0
    ///0 为不重命名,返回冲突
    ///1 为只要path冲突即重命名
    ///2 为path冲突且block_list不同才重命名
    ///3 为覆盖
    /// 
    public int rtype { get; set; } = 3;
    /// 
    /// 文件各分片MD5的json串(调用的时候,给一个md5格式的默认值,可以把文件名的md5值传入)
    /// 
    public List block_list { get; set; }
}
/// 
/// 预上传返回参数
/// 
public class BdNasPrecreateReponse
{
    /// 
    /// 文件的绝对路径
    /// 
    public string path { get; set; }
    /// 
    /// 上传id
    /// 
    public string uploadid { get; set; }
    /// 
    /// 返回类型,1 文件在云端不存在、2 文件在云端已存在
    /// 
    public int return_type { get; set; }
    /// 
    /// 需要上传的分片序号,索引从0开始
    /// 
    public List block_list { get; set; }
    /// 
    /// 错误码,0-正常
    /// 
    public int errno { get; set; }
    /// 
    /// 请求id
    /// 
    public long request_id { get; set; }
}
/// 
/// 第一步,预请求
/// 
/// 
/// 
[HttpPost]
public Response UploadFile1([FromBody]BdNasPrecreate precreate)
{
     var token = GetAccessToken();
     string url = $"http://pan.baidu.com/rest/2.0/xpan/file?method=precreate&access_token={token.access_token}";        
     string ret = Common.RequestHelper.HttpPost(url, JsonHelper.JsonSerialize(precreate), null, "application/x-www-form-urlencoded");
     if (ret.Contains("uploadid"))
     		return _resp.success(JsonHelper.JsonDeserialize(ret));
     return _resp.error('与请求失败');        
}

第一步里的返回参数里,最重要的就是uploadid了,这是第二步里要用到的一个参数。

2.分片上传

第二步需要注意的就是,除了文件需要放到请求body里,其他参数都需要放到url里,看下postman的实例

对应的接口代码

/// 
/// 分片上传,只有file需要置于form表单中,其余参数需要置于querystring中
///1、普通用户单个分片大小固定为4MB
///2、普通会员用户单个分片大小上限为16MB
///3、超级会员用户单个分片大小上限为32MB
/// 
public class BdNasMultipart
{
    /// 
    /// 固定值 upload
    /// 
    public string method { get; set; } = "upload";
    /// 
    /// 固定值 tmpfile
    /// 
    public string type { get; set; } = "tmpfile";
    /// 
    /// 上传后使用的文件绝对路径
    /// 
    public string path { get; set; }
    /// 
    /// precreate接口下发的uploadid
    /// 
    public string uploadid { get; set; }
    /// 
    /// 文件分片的位置序号,参考precreate接口返回的block_list
    /// 
    public int partseq { get; set; }

    /// 
    /// 文件是否在服务器本地存一份
    /// 
    public int save_local { get; set; } = 1;
}
/// 
/// 分片上传返回参数
/// 
public class BdNasMultipartResponse
{
    /// 
    /// 文件md5值
    /// 
    public string md5 { get; set; }
    /// 
    /// 请求值
    /// 
    public string request_id { get; set; }
}
/// 
/// 第二步,分片上传
/// 
/// 
/// 
[HttpPost]
public async Task UploadFile2([FromQuery]BdNasMultipart mulitepart)
{    
    
  	var files = Request.Form.Files;
  	if (files.Count() == 0)
  	{
  	  return _resp.error("没有检测到上传文件");
  	}
 		string path = await UploadFile(files[0]);
  	List filelist = new List();
    filelist.Add(path);
	  var token = GetAccessToken();
	  string url = $"https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?method={mulitepart.method}&type={mulitepart.type}&path={mulitepart.path}&uploadid={mulitepart.uploadid}&partseq={mulitepart.partseq}&access_token={token.access_token}";
 	  string ret =  RequestHelper.HttpPostAsync(url, null, filelist);
	  if (ret.Contains("md5"))  	
    	return _resp.success(JsonHelper.JsonDeserialize(ret));  	
  	return _resp.error("分片上传失败");    
}

3.创建文件

对应的接口代码

public class BdNasCreate
{
    /// 
    /// 上传后使用的文件绝对路径
    /// 
    public string path { get; set; }
    /// 
    /// 文件或目录的大小,必须要和文件真实大小保持一致
    /// 
    public string size { get; set; }
    /// 
    /// 是否目录,0 文件、1 目录
    /// 
    public int isdir { get; set; } = 0;
    /// 
    /// 文件命名策略,默认1
    /// 0 为不重命名,返回冲突
    /// 1 为只要path冲突即重命名
    /// 2 为path冲突且block_list不同才重命名
    /// 3 为覆盖
    /// 
    public int rtype { get; set; } = 3;
    /// 
    /// 文件各分片MD5的json串
    /// MD5对应superfile2返回的md5,且要按照序号顺序排列
    /// 
    public List block_list { get; set; }
}

public class BdNasCreateResponse
{
    /// 
    /// 文件在云端的唯一标识ID
    /// 
    public long fs_id { get; set; }
    /// 
    /// 文件的MD5,只有提交文件时才返回,提交目录时没有该值
    /// 
    public string md5 { get; set; }
    /// 
    /// 文件名
    /// 
    public string server_filename { get; set; }
    /// 
    /// 分类类型, 1 视频 2 音频 3 图片 4 文档 5 应用 6 其他 7 种子
    /// 
    public int category { get; set; }
    /// 
    /// 上传后使用的文件绝对路径
    /// 
    public string path { get; set; }
    /// 
    /// 文件大小,单位B
    /// 
    public long size { get; set; }
    /// 
    /// 文件创建时间
    /// 
    public long ctime { get; set; }
    /// 
    /// 文件修改时间
    /// 
    public long mtime { get; set; }
    /// 
    /// 是否目录,0 文件、1 目录
    /// 
    public int isdir { get; set; }
}
/// 
/// 第三步,创建文件
/// 
/// 
/// 
[HttpPost]
public Response UploadFile3([FromBody] BdNasCreate create)
{    
     var token = GetAccessToken();
     string url = $"https://pan.baidu.com/rest/2.0/xpan/file?method=create&access_token={token.access_token}";
     string ret = Common.RequestHelper.HttpPost(url, JsonHelper.JsonSerialize(create), null, "application/x-www-form-urlencoded");
     if (ret.Contains("fs_id"))
     {
       	return _resp.success(JsonHelper.JsonDeserialize(ret));
     }
     return _resp.error("创建文件失败");    
}

三步操作完成后,打开百度网盘,我们就看到我们刚上传的文件啦

这里我分享的是上传文件每个步骤拆开后的单独操作和对应开发的接口,实际应用的时候,我们可能需要把三步操作合并成一步,其中可能会涉及到一些网络延时等问题,需要按实际的业务需求酌情处理。

一般此类操作都是在后台或者单独的服务中另起线程来运行。至此,上传的整个流程就完成了,剩下的就是怎么更好的融入到业务系统了。

一点关于视频文件的总结

再简单介绍下获取视频流的接口其实百度虽然开放了视频能力,而接口文档写的实在有限。但是话又说回来了,人家都把能力放开给你用了,而且这个能力比多大多数个人和团队自己处理的要好的多,还要什么自行车!

我个人觉得,要彻底理解平台开放视频能力,需要有一些预备知识,比如hls协议,hls和rtmp协议的区别,m3u8是什么类型的文件,文件中的各个字段都代表什么意思等等。这个我就不多说了,我是觉得视频处理这块,每个环节单独拎出来都是很大的一块,我也在学习中,就直接看一个例子吧

这个其实在接口里看是没有实际意义的,但这样可以直接看到百度返回来的m3u8格式的索引文件和分块文件,实际使用的时候,该接口需要直接反馈到终端播放器即可完成视频输出。比如我这里用VLC播放器来演示一下

至此,就完成了视频播放的集成。如果要下载这个视频,从开放平台提供的接口里也是可以获取到的,叫做dlink.如下图

最后,再总结下,我个人觉得百度其实挺厚道的,虽然最后没有继承它的视频能力,但上传,下载等功能还是非常实用的,而且账号如果开通了vip,接口使用起来也会更顺畅,用非常低的成本,获得了原本需要几倍成本才有的存储能力,还是非常厚道的。不过还是要提醒下,百度的文档里说的也很清楚,如果要继承其接口能力,在产品发布的时候,最好是给人家发个邮件告知一下,别薅high了,薅过头了,回头人家反手就把你告上公堂,你也无话可说啊。

好了,先这些,下次再来说说怎么通过接口从网盘下载文件。