[求助] Node.js webdav客户端库上传文件时遇到 500 Internal Server Error

hjsxhst
52 6

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();

轻写日记作者 球注册Ծ‸Ծ 洛谷745903
最新回复 ( 6 )
  • 2
    0
    没权限吧。或者超时太大。。虽然不懂你写的是什么一般就这些问题
  • hjsxhst @Ta
    3
    0
    快发财 没权限吧。或者超时太大。。虽然不懂你写的是什么一般就这些问题
    看了一下,100多MB的文件,权限也给了啊,从6月27号开始就不行了
  • 4
    0
    看log后台看看是具体在哪里问题咯
  • 5
    0
    看不来,你先粘到一个在线剪贴板吧,还有记得把日志也贴一份给我
  • hjsxhst @Ta
    6
    0
    卡尔白~ 看不来,你先粘到一个在线剪贴板吧,还有记得把日志也贴一份给我
    https://note.ms/sqgj
    这是全文代码,console的在后面
  • hjsxhst @Ta
    7
    0
    快发财 看log后台看看是具体在哪里问题咯
    https://note.ms/sqgj
    这是全文代码,console的在后面
  • 游客
    8

    您需要登录后才可以回帖

    登录 注册

发新帖