实现百度网盘上传文件和分享链接功能
需要前往百度开放平台申请key和token
js
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const axios = require('axios');
const FormData = require('form-data');
// 配置信息
const config = {
accessToken: '121.6c4468c8f1c6f9c73bba9bd7e21e6f63.YHp2C6F7X5XfCRClRllUF-A9Q2HWK5idwgwkXTD.4n8CSQ',
appKey: 'cT9I7SLq7m8wUhQjKfOdMccp3p6ysB8L'
};
// 配置分片大小,保持4MB不变
const BLOCK_SIZE = 4 * 1024 * 1024; // 4MB
// 计算文件MD5
function calculateMD5(filePath, start = 0, end = Infinity) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('md5');
const stream = fs.createReadStream(filePath, { start, end });
stream.on('data', (data) => {
hash.update(data);
});
stream.on('end', () => {
resolve(hash.digest('hex'));
});
stream.on('error', (error) => {
reject(error);
});
});
}
// 计算文件所有分片的MD5
async function calculateBlockList(filePath) {
const stats = fs.statSync(filePath);
const fileSize = stats.size;
const totalBlocks = Math.ceil(fileSize / BLOCK_SIZE);
const blockList = [];
for (let i = 0; i < totalBlocks; i++) {
const start = i * BLOCK_SIZE;
const end = Math.min(start + BLOCK_SIZE, fileSize);
const md5 = await calculateMD5(filePath, start, end);
blockList.push(md5);
}
return blockList;
}
// 预上传函数
async function precreate(filePath, remotePath) {
try {
// 获取文件信息
const stats = fs.statSync(filePath);
const fileSize = stats.size;
// 计算所有分片的MD5
console.log('计算文件分片MD5...');
const blockList = await calculateBlockList(filePath);
console.log('分片MD5列表:', blockList);
// 预上传请求参数
const precreateParams = {
path: remotePath,
size: fileSize,
isdir: 0,
block_list: JSON.stringify(blockList), // 转换为JSON字符串
autoinit: 1,
rtype: 1 // 覆盖同名文件
};
console.log('\n发送预上传请求...');
console.log('请求参数:', precreateParams);
// 发送预上传请求
const response = await axios.post(
'https://pan.baidu.com/rest/2.0/xpan/file?method=precreate&access_token=' + config.accessToken,
precreateParams,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } // 设置正确的Content-Type
);
console.log('\n预上传请求结果:');
console.log('状态码:', response.status);
console.log('响应数据:', response.data);
return response.data;
} catch (error) {
console.error('预上传失败:', error);
if (error.response) {
console.error('响应数据:', error.response.data);
console.error('响应状态:', error.response.status);
}
throw error;
}
}
// 获取上传域名函数
async function getUploadDomain(path, uploadid) {
try {
const params = {
method: 'locateupload',
appid: 250528, // 文档中指定的固定值
access_token: config.accessToken,
path: path,
uploadid: uploadid,
upload_version: '2.0' // 文档中指定的固定值
};
console.log('\n发送获取上传域名请求...');
console.log('请求参数:', params);
const response = await axios.get('https://d.pcs.baidu.com/rest/2.0/pcs/file', { params });
console.log('\n获取上传域名请求结果:');
console.log('状态码:', response.status);
console.log('响应数据:', response.data);
// 优先使用https的服务器
const httpsServers = response.data.servers.filter(server => server.server.startsWith('https://'));
if (httpsServers.length > 0) {
return httpsServers[0].server;
}
// 如果没有https服务器,使用第一个服务器
if (response.data.servers.length > 0) {
return response.data.servers[0].server;
}
throw new Error('未获取到有效的上传域名');
} catch (error) {
console.error('获取上传域名失败:', error);
if (error.response) {
console.error('响应数据:', error.response.data);
console.error('响应状态:', error.response.status);
}
throw error;
}
}
// 分片上传函数
async function uploadChunk(filePath, uploadDomain, remotePath, uploadid, partseq) {
try {
const start = partseq * BLOCK_SIZE;
const end = Math.min(start + BLOCK_SIZE, fs.statSync(filePath).size);
// 使用与calculateMD5相同的方式读取分片内容
// 这样可以确保读取的内容与预上传时完全一致,MD5值也会匹配
const readChunk = () => {
return new Promise((resolve, reject) => {
const chunks = [];
const stream = fs.createReadStream(filePath, { start, end });
stream.on('data', (chunk) => {
chunks.push(chunk);
});
stream.on('end', () => {
resolve(Buffer.concat(chunks));
});
stream.on('error', (error) => {
reject(error);
});
});
};
const chunkBuffer = await readChunk();
// 打印实际分片大小,验证是否正确读取了分片
console.log(`实际读取分片大小: ${chunkBuffer.length}字节`);
// 验证当前分片的MD5是否与预上传时一致
const chunkMD5 = crypto.createHash('md5').update(chunkBuffer).digest('hex');
console.log(`当前分片计算的MD5: ${chunkMD5}`);
// 准备表单数据
const formData = new FormData();
formData.append('file', chunkBuffer, { filename: 'chunk' + partseq });
console.log(`
发送分片${partseq}上传请求...`);
console.log('上传域名:', uploadDomain);
console.log(`分片范围: ${start}-${end}字节`);
console.log(`分片大小: ${chunkBuffer.length}字节`);
// 发送分片上传请求,增加超时设置
const response = await axios.post(
`${uploadDomain}/rest/2.0/pcs/superfile2`,
formData,
{
params: {
access_token: config.accessToken,
method: 'upload',
type: 'tmpfile',
path: remotePath,
uploadid: uploadid,
partseq: partseq
},
headers: formData.getHeaders(),
timeout: 60000, // 增加到60秒超时
maxContentLength: Infinity,
maxBodyLength: Infinity
}
);
console.log(`
分片${partseq}上传结果:`);
console.log('状态码:', response.status);
console.log('响应数据:', response.data);
return response.data;
} catch (error) {
console.error(`分片${partseq}上传失败:`, error);
if (error.response) {
console.error('响应数据:', error.response.data);
console.error('响应状态:', error.response.status);
} else if (error.request) {
console.error('请求已发送但未收到响应:', error.request);
} else {
console.error('请求配置错误:', error.message);
}
throw error;
}
}
// 创建文件函数
async function createFile(remotePath, fileSize, blockList, uploadid) {
try {
// 创建文件请求参数
const createParams = {
path: remotePath,
size: fileSize,
isdir: 0,
block_list: JSON.stringify(blockList),
uploadid: uploadid,
rtype: 1 // 覆盖同名文件,与预上传保持一致
};
console.log('\n发送创建文件请求...');
console.log('请求参数:', createParams);
// 发送创建文件请求
const response = await axios.post(
'https://pan.baidu.com/rest/2.0/xpan/file?method=create&access_token=' + config.accessToken,
createParams,
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
timeout: 60000
}
);
console.log('\n创建文件结果:');
console.log('状态码:', response.status);
console.log('响应数据:', response.data);
return response.data;
} catch (error) {
console.error('创建文件失败:', error);
if (error.response) {
console.error('响应数据:', error.response.data);
console.error('响应状态:', error.response.status);
}
throw error;
}
}
// 主函数
async function main() {
try {
// 本地文件路径
const localFilePath = path.join(__dirname, 'files', '1768820155275.txt');
// 检查文件是否存在
if (!fs.existsSync(localFilePath)) {
console.error('文件不存在:', localFilePath);
return;
}
// 获取文件信息
const stats = fs.statSync(localFilePath);
console.log('文件信息:');
console.log(' 路径:', localFilePath);
console.log(' 大小:', stats.size, '字节');
console.log(' 修改时间:', stats.mtime);
// 远程文件路径 (apps/{appname}/filename)
// 动态拿文件名
const remoteFilePath = '/小说资源/' + path.basename(localFilePath);
// 执行预上传
const precreateResult = await precreate(localFilePath, remoteFilePath);
// 如果预上传成功,获取上传域名
if (precreateResult.errno === 0) {
const uploadDomain = await getUploadDomain(remoteFilePath, precreateResult.uploadid);
// 计算所有分片的MD5(用于创建文件时使用)
const blockList = await calculateBlockList(localFilePath);
// 执行分片上传
const totalBlocks = Math.ceil(stats.size / BLOCK_SIZE);
console.log(`\n开始分片上传,共${totalBlocks}个分片...`);
// 保存所有分片的上传结果
const uploadResults = [];
for (let i = 0; i < totalBlocks; i++) {
const result = await uploadChunk(localFilePath, uploadDomain, remoteFilePath, precreateResult.uploadid, i);
uploadResults.push(result);
}
console.log('\n所有分片上传完成!');
// 检查所有分片是否上传成功
const allChunksUploaded = uploadResults.every(result => result && result.request_id);
if (allChunksUploaded) {
console.log('\n开始创建文件...');
// 调用创建文件接口
const createResult = await createFile(remoteFilePath, stats.size, blockList, precreateResult.uploadid);
if (createResult.errno === 0) {
console.log('\n🎉 文件上传完成!');
console.log('文件信息:');
console.log(' 文件名:', createResult.server_filename);
console.log(' 文件大小:', createResult.size, '字节');
console.log(' 文件路径:', createResult.path);
console.log(' 文件ID:', createResult.fs_id);
console.log(' 文件MD5:', createResult.md5);
// 调用创建分享链接函数
console.log('\n开始创建分享链接...');
await createShareLink(createResult.fs_id);
} else {
console.error('\n❌ 创建文件失败!');
}
} else {
console.error('\n❌ 存在分片上传失败,无法创建文件!');
}
}
} catch (error) {
console.error('程序执行错误:', error);
}
}
// 执行主程序
main();
// 生成随机四位数分享口令(数字+小写字母)
function generateSharePassword() {
const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
let password = '';
for (let i = 0; i < 4; i++) {
password += chars.charAt(Math.floor(Math.random() * chars.length));
}
return password;
}
// 创建分享链接函数
async function createShareLink(fsid) {
try {
// 应用ID为121883393
const appId = '121883393';
// 生成随机四位数分享口令
const pwd = generateSharePassword();
// 分享有效期默认7天
const period = 7;
// 创建分享请求参数
const shareParams = {
fsid_list: JSON.stringify([fsid.toString()]), // 分享文件id列表,只包含当前上传的文件
period: period,
pwd: pwd,
remark: '上传文件分享' // 可选备注
};
console.log('\n发送创建分享链接请求...');
console.log('请求参数:', shareParams);
// 发送创建分享链接请求
const response = await axios.post(
`https://pan.baidu.com/apaas/1.0/share/set?product=netdisk&appid=${appId}&access_token=${config.accessToken}`,
shareParams,
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
timeout: 60000
}
);
console.log('\n创建分享链接结果:');
console.log('状态码:', response.status);
console.log('响应数据:', response.data);
if (response.data.errno === 0) {
console.log('\n🎉 分享链接创建成功!');
console.log('分享信息:');
console.log(' 分享长链接:', response.data.data.link);
console.log(' 分享短链接:', response.data.data.short_url);
console.log(' 分享提取码:', response.data.data.pwd);
console.log(' 分享有效期:', response.data.data.period, '天');
console.log(' 分享ID:', response.data.data.share_id);
return response.data.data;
} else {
console.error('\n❌ 创建分享链接失败!');
console.error('错误信息:', response.data.show_msg || response.data);
return null;
}
} catch (error) {
console.error('创建分享链接失败:', error);
if (error.response) {
console.error('响应数据:', error.response.data);
console.error('响应状态:', error.response.status);
}
return null;
}
}package.json
json
{
"name": "ceshi",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"axios": "^1.13.2",
"form-data": "^4.0.5"
}
}