2021年4月5日

通过nodejs接入百度网盘开放平台实现上传文件

作者 theluyuan

由于想将服务器的内容备份一下,但是使用oos是要钱的(没钱),况且现在百度网盘是有会员的,所以就直接上传到百度网盘作为备份就可以了,使用nodejs实现

需要先获取token ,这个需要接入百度开放平台,因为不属于这里的讨论范畴,就不着重讨论,只重点讲解上传。

根据官网说明:需要实现预上传、分片上传、创建文件。只有完成这三步,才能将文件上传到网盘。

预上传

只要请求接口就要使用到一个库 axios 先安装axios

npm install axios

接口地址https://pan.baidu.com/rest/2.0/xpan/file?method=precreate

参数需要几

path是上传文件路径 在网盘里面的 需要进行编码 在node里面就是使用encodeURI()函数处理一下

size 这个是文件大小,下面将详细讨论如何在node中获取文件大小。

isdir 是不是文件或目录 0 是文件 1是目录 上传时选择对应的格式

autoinit 固定值1

rtype 重命名逻辑 注意下文档描述

block_list 重点,分片的md5,下面也会讲解如何获取分片

获取文件大小

fs.stat()这个函数在node中是获取文件信息 文档地址

先在全局设置分片大小 const size = 4 * 1024 * 1024

4mb 这是网盘规定的,而且第一个分片不支持小于4m

获取文件大小就是

    return new Promise((res, e) => {
        
        fs.stat("./a.exe", {}, (err, stats) => {

            if (!err) {
                daxiao = stats.size
                let fenpian = Math.ceil(stats.size / size)
                res({
                    size: daxiao,
                    num: fenpian
                })
            } else {
                e("文件路径错误或文件不存在")
            }

        })
    })

我封装了一个promise 然后返回是size就是大小 num就是分片数量

另一个重点是如何获取分片md5。

function getfile({ i, url }) {

    return new Promise((res) => {
        let data = '';
        let start = i * size
        let end = (i + 1) * size - 1
        const stream = fs.createReadStream(url, {
            start,
            end
        })
        stream.on('data', (chunk) => {
            // console.log(`接收到 ${chunk.length} 个字节的数据`);
            if (!data) {
                data = chunk
            } else {
                data = Buffer.concat([data, chunk])
            }
        });
        stream.on('end', () => {
            res(data)
        });
    })

}

这个函数是获取分片内容,根据上面的函数可以拿到一共有多少分片,然后就可以循环这个函数,传入第几个和文件地址,会返回buffer

burrefr不能进行相加,会转变成字符串,然后大小就不是之前的那样了,必须使用Buffer.concat

然后就是获取md5。

这里使用到了crypto

文档地址是 http://nodejs.cn/api/crypto.html#crypto_crypto_createhash_algorithm_options

const crypto = require('crypto');
async function getmd5list(url, info) {
    promistlist = []
    // fs.createReadStream
    for (let i = 0; i < info.num; i++) {
        let data = await getfile({ url, i })
        const hash = crypto.createHash('md5'); // 创建一个md5加密的hash
        hash.update(data); // 更新内容
        const md5 = hash.digest('hex'); // 返回计算内容
        console.log(md5);
        promistlist.push(md5)
    }
    return promistlist
}

这就是计算md5的方法。

然后md5的列表就会被拿到了。

现在所有内容都拿完整了,下面就是预上传了。

开始预上传

拼接一下参数进行上传

async function precreate(info, url) {
    let block_list = await getmd5list(url, info)
    block_list = JSON.stringify(block_list)
    let data = {
        path: encodeURI('/app/服务器备份/baidu.exe'), //这是你的上传地址 需要进行url编码
        size: info.size, //上传大小
        isdir: 0, // 是不是文件夹 0 文件 1 文件夹
        autoinit: 1,
        block_list, // md5的list 注意按顺序
        rtype: 1, 
    }
    let str = "" // 这个需要拼接成formdata格式的,所以这面就手动拼接了
    for (let i in data) {
        str += `${i}=${data[i]}&`
    }

    axios.post('https://pan.baidu.com/rest/2.0/xpan/file?method=precreate&access_token=这是你的token', str).then((res) => {
        // 如果成功的话就是会返回 uploadid 和  block_list
        // uplpadid就是下面上传的id  block_list就是要上传的分片
        upload({
            url,
            info,
            uploadid: res.data.uploadid,
            pian: res.data.block_list
        })
    })
}

到此预上传就完成了

下面就是上传分片了。

分片上传

async function upload({ url, info, uploadid, pian }) {
    for (let i of pian) {
        let data = await upgetfile({ url, i })
        await uploadfile(i,data,uploadid) // 循环上传需要上传的分片
    }
    // https://pan.baidu.com/rest/2.0/xpan/file?method=create 

    // 下面的操作是上传完成合成文件
    let data = {
        path:encodeURI('/app/服务器备份/baidu.exe'), // 与上面那个一定要相同 而且还是需要url编码
        size: daxiao, // 这个就是上传的大小,而且是总大小,不是分片的大小
        isdir:0,
        rtype: 1,
        uploadid, // 这个就是上面返回的uploadid
        block_list:JSON.stringify(promistlist) // 这个就是上传的MD5列表,还是全部的

    }
    let s = '' // 需要formdata格式
    for(let i in data){
        s += i + "=" + data[i] + '&'
    }
    console.log(s)
    axios.post('https://pan.baidu.com/rest/2.0/xpan/file?method=create',s,{
        params:{
            access_token: '你的token',
        }
    }).then((res)=>{
        console.log(res)
    })
}

上面的uploadfile 是上传 下面的是拼接文件 不进行拼接文件是不能完成上传的。

function uploadfile(i,data,uploadid){

    return new Promise((r)=>{
        console.log(data.length)
        let urls = '/rest/2.0/pcs/superfile2?' // 请求地址
        let params = {
            access_token: 'token', // 你的token
            method: 'upload', // 默认
            type: "tmpfile", // 默认
            path: encodeURI('/app/服务器备份/baidu.exe'), //上传地址 与之前的相同
            uploadid, //还是返回的uploadid
            partseq: i // 这个是分片的编号
        }
        for (let j in params) {
            urls += j + '=' + params[j] + '&'
        }
    // 下面由于我用axios上传失败了 改用httpsrequest
    const options = {
        hostname: 'd.pcs.baidu.com', 
        port: 443,
        path: urls,
        method: 'PUT', // 这面官网说是post 但是我试了下必须post 不然一直错
        headers: {
            'content-type':'multipart/form-data', //这是格式
            'Content-Length': data.length // 这是主体长度
          }
      };
      
      const req = https.request(options, (res) => {
        console.log('状态码:', res.statusCode);
        console.log('请求头:', res.headers);
      
        res.on('data', (d) => {
          process.stdout.write(d);
        });
        r(1)
      });
      
      req.on('error', (e) => {
        console.error(e);
      });
      req.write(data); // 写入数据
      req.end(); //写入完成一定要执行end
    })
 
}

到现在为止就是上传成功了。

其实挺简单的,但是网上很少有nodejs的资料,如果你看到感觉还是有问题可以留言我继续完善,或者是加我qq