rt,我在使用webdav库上传文件到星月盘时遇到了500错误,空间足够,求问怎么解决?
这是全文代码:
const { createClient } = require('webdav');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const archiver = require('archiver');
const stream = require('stream');
class EncryptedWebDAVUploader {
constructor(serverURL, username, password, encryptionKey, options = {}) {
this.client = createClient(serverURL, {
username: username,
password: password,
...options
});
this.encryptionKey = crypto.createHash('sha256').update(encryptionKey).digest();
this.maxConcurrent = options.maxConcurrent || 8;
this.retryCount = options.retryCount || 50;
this.timeFormat = options.timeFormat || 'YYYY-MM-DD_HH-mm-ss';
this.retryDelay = options.retryDelay || 1000;
this.zipCompressionLevel = options.zipCompressionLevel || 9;
}
// 生成时间戳
getTimeStamp() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return this.timeFormat
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
}
// 创建ZIP压缩包
createZipArchive(localFolderPath, zipFilePath) {
return new Promise((resolve, reject) => {
console.log(`🗜️ 创建ZIP压缩包: ${zipFilePath}`);
const output = fs.createWriteStream(zipFilePath);
const archive = archiver('zip', {
zlib: { level: this.zipCompressionLevel }
});
output.on('close', () => {
console.log(`✅ ZIP压缩包创建完成: ${archive.pointer()} 总字节`);
resolve(archive.pointer());
});
archive.on('error', (err) => {
console.error('❌ 创建ZIP压缩包失败:', err);
reject(err);
});
archive.on('warning', (err) => {
if (err.code === 'ENOENT') {
console.warn('⚠️ 压缩警告:', err.message);
} else {
console.error('❌ 压缩错误:', err);
reject(err);
}
});
archive.on('progress', (progress) => {
const percent = progress.fs.totalBytes > 0
? ((progress.fs.processedBytes / progress.fs.totalBytes) * 100).toFixed(1)
: 0;
process.stdout.write(`\r📦 压缩进度: ${percent}% (${progress.entries.processed} 文件已处理)`);
});
archive.pipe(output);
// 使用 glob 模式排除不需要的目录和文件
const archiveOptions = {
globOptions: {
ignore: [
'**/node_modules/**',
'**/.git/**',
'**/.DS_Store',
'**/Thumbs.db',
'**/desktop.ini',
'**/*.msi', // 排除 msi 文件
'**/*.exe',
'**/*.gz', // 排除 exe 文件
'**/live2d-widget-models-master/**'
]
}
};
archive.directory(localFolderPath, false, archiveOptions);
archive.finalize();
});
}
// 替代方法:手动扫描并添加文件
createZipArchiveManual(localFolderPath, zipFilePath) {
return new Promise((resolve, reject) => {
console.log(`🗜️ 手动创建ZIP压缩包: ${zipFilePath}`);
const output = fs.createWriteStream(zipFilePath);
const archive = archiver('zip', {
zlib: { level: this.zipCompressionLevel }
});
output.on('close', () => {
console.log(`✅ ZIP压缩包创建完成: ${archive.pointer()} 总字节`);
resolve(archive.pointer());
});
archive.on('error', (err) => {
console.error('❌ 创建ZIP压缩包失败:', err);
reject(err);
});
archive.pipe(output);
// 手动扫描并添加文件
this.scanAndAddFilesToArchive(localFolderPath, archive)
.then(() => {
archive.finalize();
})
.catch(reject);
});
}
// 扫描目录并添加文件到压缩包
async scanAndAddFilesToArchive(dirPath, archive, basePath = '') {
const items = fs.readdirSync(dirPath);
for (const item of items) {
const fullPath = path.join(dirPath, item);
const relativePath = path.join(basePath, item);
const stat = fs.statSync(fullPath);
// 跳过不需要的目录和文件
if (this.shouldIgnore(fullPath, item)) {
console.log(`⏭️ 跳过: ${relativePath}`);
continue;
}
if (stat.isDirectory()) {
// 递归处理子目录
await this.scanAndAddFilesToArchive(fullPath, archive, relativePath);
} else {
// 添加文件到压缩包
archive.file(fullPath, { name: relativePath });
}
}
}
// 判断是否应该忽略该路径 - 更新版本,跳过 msi 和 exe 文件
shouldIgnore(filePath, fileName) {
const ignorePatterns = [
// 目录
'node_modules',
'.git',
// 系统文件
'.DS_Store',
'Thumbs.db',
'desktop.ini',
// 临时和日志文件
'*.tmp',
'*.log',
'*.temp',
// 可执行文件 - 新增
'*.exe',
'*.msi',
'*.gz',
// 其他可能的可执行文件扩展名
'*.bat',
'*.cmd',
'*.sh',
'*.dmg',
'*.pkg'
];
// 检查文件扩展名
const ext = path.extname(fileName).toLowerCase();
if (ext === '.exe' || ext === '.msi') {
return true;
}
// 检查完整文件名
return ignorePatterns.some(pattern => {
if (pattern.includes('*')) {
// 简单的通配符匹配
const regex = new RegExp('^' + pattern.replace('.', '\\.').replace('*', '.*') + '$', 'i');
return regex.test(fileName);
}
return fileName.toLowerCase() === pattern.toLowerCase() ||
filePath.toLowerCase().includes(pattern.toLowerCase());
});
}
// AES-256加密文件
encryptFile(filePath) {
return new Promise((resolve, reject) => {
try {
console.log(`🔒 加密文件: ${path.basename(filePath)}`);
const fileData = fs.readFileSync(filePath);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, iv);
let encrypted = cipher.update(fileData);
encrypted = Buffer.concat([encrypted, cipher.final()]);
// 将IV和加密数据合并
const result = Buffer.concat([iv, encrypted]);
resolve(result);
} catch (error) {
reject(new Error(`加密文件失败 ${filePath}: ${error.message}`));
}
});
}
// 流式加密和上传
async encryptAndUploadStream(inputStream, remoteFilePath, fileSize) {
return new Promise((resolve, reject) => {
console.log(`🔒 开始流式加密上传: ${path.basename(remoteFilePath)}`);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, iv);
// 创建转换流:先写入IV,然后加密数据
let isIVWritten = false;
const transformStream = new stream.Transform({
transform(chunk, encoding, callback) {
if (!isIVWritten) {
this.push(iv);
isIVWritten = true;
}
this.push(cipher.update(chunk));
callback();
},
flush(callback) {
this.push(cipher.final());
callback();
}
});
// 创建上传流
const uploadStream = this.client.createWriteStream(remoteFilePath);
let uploadedBytes = 0;
transformStream.on('data', (chunk) => {
uploadedBytes += chunk.length;
const progress = fileSize ? ((uploadedBytes / (fileSize + 16)) * 100).toFixed(1) : '未知';
process.stdout.write(`\r📤 上传进度: ${progress}% (${uploadedBytes} 字节)`);
});
inputStream
.pipe(transformStream)
.pipe(uploadStream)
.on('finish', () => {
console.log(`\n✅ 流式加密上传完成: ${path.basename(remoteFilePath)}`);
resolve();
})
.on('error', (error) => {
console.error(`\n❌ 流式加密上传失败: ${error.message}`);
reject(error);
});
});
}
// 上传加密的ZIP文件
async uploadEncryptedZip(localFolderPath, remoteFolderPath, options = {}) {
console.time('总上传时间');
const uploadOptions = {
addTimestamp: true,
timestampFormat: 'YYYY-MM-DD_HH-mm-ss',
useStreaming: true,
useManualZip: false, // 是否使用手动模式创建ZIP
skipExtensions: ['.msi', '.exe'], // 新增:要跳过的扩展名列表
...options
};
this.timeFormat = uploadOptions.timestampFormat;
let tempZipPath = '';
try {
// 检查目录是否存在
if (!fs.existsSync(localFolderPath)) {
throw new Error(`本地目录不存在: ${localFolderPath}`);
}
// 生成时间戳
const timestamp = this.getTimeStamp();
const folderName = path.basename(localFolderPath);
const finalRemotePath = uploadOptions.addTimestamp
? `${remoteFolderPath.replace(/\/$/, '')}_${timestamp}.encrypted.zip`
: `${remoteFolderPath.replace(/\/$/, '')}.encrypted.zip`;
console.log(`📁 本地目录: ${localFolderPath}`);
console.log(`📍 远程路径: ${finalRemotePath}`);
console.log(`⏭️ 跳过文件扩展名: ${uploadOptions.skipExtensions.join(', ')}`);
// 创建临时ZIP文件
tempZipPath = path.join(__dirname, `temp_${timestamp}_${folderName}.zip`);
// 步骤1: 创建ZIP压缩包
console.log('\n=== 步骤1: 创建ZIP压缩包 ===');
let zipSize;
if (uploadOptions.useManualZip) {
// 使用手动模式
zipSize = await this.createZipArchiveManual(localFolderPath, tempZipPath);
} else {
// 使用自动模式
zipSize = await this.createZipArchive(localFolderPath, tempZipPath);
}
console.log(`✅ ZIP文件大小: ${(zipSize / 1024 / 1024).toFixed(2)} MB`);
// 步骤2: 加密并上传
console.log('\n=== 步骤2: 加密并上传 ===');
if (uploadOptions.useStreaming) {
// 使用流式处理(推荐大文件)
const readStream = fs.createReadStream(tempZipPath);
const stats = fs.statSync(tempZipPath);
await this.encryptAndUploadStream(readStream, finalRemotePath, stats.size);
} else {
// 使用缓冲区处理(小文件)
await this.encryptAndUploadFile(tempZipPath, finalRemotePath);
}
console.timeEnd('总上传时间');
return {
success: true,
remotePath: finalRemotePath,
localFolder: localFolderPath,
zipSize: zipSize,
encryptedSize: fs.statSync(tempZipPath).size + 16, // +16 for IV
timestamp: timestamp,
skippedExtensions: uploadOptions.skipExtensions
};
} catch (error) {
console.error('❌ 上传失败:', error.message);
return {
success: false,
error: error.message
};
} finally {
// 清理临时文件
if (tempZipPath && fs.existsSync(tempZipPath)) {
try {
fs.unlinkSync(tempZipPath);
console.log(`🧹 已清理临时文件: ${path.basename(tempZipPath)}`);
} catch (cleanupError) {
console.warn('⚠️ 清理临时文件失败:', cleanupError.message);
}
}
}
}
// 传统的加密上传方法(使用缓冲区)
async encryptAndUploadFile(localFilePath, remoteFilePath) {
const maxRetries = this.retryCount;
let lastError = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`🔒 加密 ${path.basename(localFilePath)}... (尝试 ${attempt}/${maxRetries})`);
const encryptedData = await this.encryptFile(localFilePath);
console.log(`🔄 上传 ${path.basename(remoteFilePath)}...`);
await this.client.putFileContents(remoteFilePath, encryptedData);
console.log(`✅ 成功上传(加密) ${path.basename(remoteFilePath)}`);
return { success: true };
} catch (error) {
lastError = error;
if (attempt === maxRetries) {
console.error(`❌ 上传失败 ${path.basename(remoteFilePath)} (${attempt}次尝试后):`, error.message);
return {
success: false,
error: error.message,
attempts: attempt
};
}
const delayMs = this.retryDelay * Math.pow(2, attempt - 1);
console.log(`⏳ ${path.basename(remoteFilePath)} 上传失败,${delayMs/1000}秒后重试... (${attempt}/${maxRetries})`);
await this.delay(delayMs + Math.random() * 1000);
}
}
return { success: false, error: lastError?.message || 'Unknown error' };
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例 - 加密打包上传
async function mainEncryptedZipUpload() {
const uploader = new EncryptedWebDAVUploader(
'https://pan.xxbyq.net/dav',
'email',
'WebdavKey',
'encryptKey', // 加密密钥
{
maxConcurrent: 5,
retryCount: 10,
retryDelay: 1000,
zipCompressionLevel: 9,
useStreaming: true,
useManualZip: true // 使用手动模式可以更精确地控制跳过文件
}
);
try {
const localPath = 'C:/users/administrator/desktop/html网页搭建/';
const remotePath = './html网页搭建_加密备份/';
console.log('🚀 开始加密打包上传...');
console.log('⚠️ 注意: 将自动跳过 .msi 和 .exe 文件');
const result = await uploader.uploadEncryptedZip(localPath, remotePath, {
useManualZip: true,
skipExtensions: ['.msi', '.exe', '.bat', '.cmd'] // 可以在这里添加更多要跳过的扩展名
});
if (result.success) {
console.log('\n🎉 加密打包上传任务完成!');
console.log(`📍 加密ZIP文件位置: ${result.remotePath}`);
console.log(`📊 原始目录: ${result.localFolder}`);
console.log(`🗜️ ZIP文件大小: ${(result.zipSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`⏭️ 跳过的文件类型: ${result.skippedExtensions.join(', ')}`);
console.log('🔑 请妥善保管加密密钥: encryptkey');
} else {
console.log('❌ 上传任务失败');
console.log(`错误信息: ${result.error}`);
}
} catch (error) {
console.error('💥 上传失败:', error);
}
}
// 执行上传
mainEncryptedZipUpload();