前端文件"全家桶":上传、下载、预览与解析——构建企业级文件管理系统

37 阅读11分钟

引言:文件操作的性能与体验之争

想象一下这个场景:用户需要上传一个5GB的设计文件,在上传到90%时网络突然中断,一切需要重新开始;或者财务人员需要下载包含10万行数据的报表,浏览器却因为内存不足而崩溃...

文件操作是企业级应用中最高频、最复杂的场景之一。从简单的图片上传,到TB级大文件传输,再到各种格式的在线预览,每一个环节都面临着性能、体验和稳定性的挑战。本文将带你构建一个完整的前端文件处理体系,解决从上传到解析的全链路问题。

一、文件操作的技术架构全景图

1.1 文件处理全链路分析

// 文件操作的技术挑战矩阵
const fileOperationChallenges = {
  // 上传环节
  upload: {
    largeFiles: "大文件上传超时、网络不稳定",
    networkInterruption: "网络中断导致重传",
    progressTracking: "准确的上传进度监控",
    concurrentUploads: "多文件并发上传控制"
  },
  
  // 下载环节  
  download: {
    largeFiles: "大文件下载内存溢出",
    downloadResume: "断点续传实现",
    progressTracking: "下载进度实时显示",
    fileSecurity: "防止未授权下载"
  },
  
  // 预览环节
  preview: {
    formatSupport: "多种文件格式兼容",
    performance: "大文件预览性能",
    mobileAdaptation: "移动端预览体验",
    security: "文件内容安全"
  },
  
  // 解析环节
  parse: {
    dataExtraction: "从文件中提取结构化数据",
    performance: "大文件解析性能",
    errorHandling: "格式错误的容错处理",
    memoryManagement: "解析过程的内存控制"
  }
};

1.2 文件类型与处理策略

// 文件类型分类与处理策略
class FileTypeManager {
  constructor() {
    this.fileCategories = {
      // 图像文件
      image: {
        extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'],
        preview: 'browser_native',
        maxPreviewSize: '10MB',
        parsing: 'metadata_only'
      },
      
      // 文档文件
      document: {
        extensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'],
        preview: 'third_party_lib',
        maxPreviewSize: '50MB',
        parsing: 'content_extraction'
      },
      
      // 文本文件
      text: {
        extensions: ['txt', 'csv', 'json', 'xml', 'log'],
        preview: 'browser_native',
        maxPreviewSize: '5MB',
        parsing: 'full_content'
      },
      
      // 压缩文件
      archive: {
        extensions: ['zip', 'rar', '7z', 'tar', 'gz'],
        preview: 'list_only',
        maxPreviewSize: '100MB',
        parsing: 'metadata_only'
      },
      
      // 媒体文件
      media: {
        extensions: ['mp4', 'avi', 'mov', 'mp3', 'wav'],
        preview: 'browser_native',
        maxPreviewSize: '100MB',
        parsing: 'metadata_only'
      },
      
      // 代码文件
      code: {
        extensions: ['js', 'ts', 'html', 'css', 'py', 'java'],
        preview: 'code_editor',
        maxPreviewSize: '2MB',
        parsing: 'syntax_highlighting'
      }
    };
  }
  
  // 获取文件类型分类
  getFileCategory(filename) {
    const extension = this.getFileExtension(filename).toLowerCase();
    
    for (const [category, config] of Object.entries(this.fileCategories)) {
      if (config.extensions.includes(extension)) {
        return category;
      }
    }
    
    return 'unknown';
  }
  
  // 获取文件扩展名
  getFileExtension(filename) {
    return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2);
  }
  
  // 获取文件处理策略
  getFileStrategy(filename) {
    const category = this.getFileCategory(filename);
    return this.fileCategories[category] || this.fileCategories.unknown;
  }
  
  // 检查文件是否支持预览
  isPreviewSupported(filename) {
    const strategy = this.getFileStrategy(filename);
    return strategy.preview !== 'unsupported';
  }
  
  // 检查文件是否支持解析
  isParsingSupported(filename) {
    const strategy = this.getFileStrategy(filename);
    return strategy.parsing !== 'unsupported';
  }
  
  // 获取推荐的文件大小限制
  getRecommendedSizeLimit(filename) {
    const strategy = this.getFileStrategy(filename);
    return this.parseSizeLimit(strategy.maxPreviewSize);
  }
  
  // 解析大小限制字符串
  parseSizeLimit(sizeStr) {
    const units = {
      'B': 1,
      'KB': 1024,
      'MB': 1024 * 1024,
      'GB': 1024 * 1024 * 1024
    };
    
    const match = sizeStr.match(/^(\d+)([KMGB]+)$/i);
    if (match) {
      const size = parseInt(match[1]);
      const unit = match[2].toUpperCase();
      return size * (units[unit] || 1);
    }
    
    return 10 * 1024 * 1024; // 默认10MB
  }
}

二、大文件上传:从基础到高级优化

2.1 基础文件上传组件

// 基础文件上传组件
class BasicFileUploader {
  constructor(options = {}) {
    this.options = {
      maxFileSize: 100 * 1024 * 1024, // 100MB
      allowedTypes: ['*/*'],
      multiple: true,
      maxFiles: 10,
      ...options
    };
    
    this.files = new Map();
    this.uploadQueue = [];
    this.isUploading = false;
    
    this.initialize();
  }
  
  initialize() {
    this.createFileInput();
    this.setupEventListeners();
  }
  
  // 创建文件输入元素
  createFileInput() {
    this.fileInput = document.createElement('input');
    this.fileInput.type = 'file';
    this.fileInput.multiple = this.options.multiple;
    this.fileInput.accept = this.options.allowedTypes.join(',');
    this.fileInput.style.display = 'none';
    
    document.body.appendChild(this.fileInput);
  }
  
  // 设置事件监听
  setupEventListeners() {
    this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
    
    // 拖放支持
    document.addEventListener('dragover', this.handleDragOver.bind(this));
    document.addEventListener('drop', this.handleDrop.bind(this));
  }
  
  // 处理文件选择
  handleFileSelect(event) {
    const selectedFiles = Array.from(event.target.files);
    this.processFiles(selectedFiles);
    this.fileInput.value = ''; // 重置input
  }
  
  // 处理拖放
  handleDragOver(event) {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'copy';
  }
  
  handleDrop(event) {
    event.preventDefault();
    const droppedFiles = Array.from(event.dataTransfer.files);
    this.processFiles(droppedFiles);
  }
  
  // 处理文件列表
  processFiles(files) {
    const validFiles = files.filter(file => this.validateFile(file));
    
    if (validFiles.length === 0) {
      this.emit('error', { type: 'no_valid_files', files });
      return;
    }
    
    // 添加到文件列表
    validFiles.forEach(file => {
      const fileId = this.generateFileId(file);
      this.files.set(fileId, {
        id: fileId,
        file,
        status: 'pending',
        progress: 0,
        uploadedSize: 0,
        startTime: null,
        speed: 0
      });
    });
    
    this.emit('filesAdded', { files: validFiles });
    this.addToUploadQueue(validFiles);
  }
  
  // 验证文件
  validateFile(file) {
    // 文件大小验证
    if (file.size > this.options.maxFileSize) {
      this.emit('error', {
        type: 'file_too_large',
        file,
        maxSize: this.options.maxFileSize
      });
      return false;
    }
    
    // 文件类型验证
    if (!this.isFileTypeAllowed(file.type, file.name)) {
      this.emit('error', {
        type: 'file_type_not_allowed', 
        file,
        allowedTypes: this.options.allowedTypes
      });
      return false;
    }
    
    // 文件数量验证
    if (this.files.size >= this.options.maxFiles) {
      this.emit('error', {
        type: 'max_files_exceeded',
        file,
        maxFiles: this.options.maxFiles
      });
      return false;
    }
    
    return true;
  }
  
  // 检查文件类型是否允许
  isFileTypeAllowed(fileType, fileName) {
    if (this.options.allowedTypes.includes('*/*')) {
      return true;
    }
    
    return this.options.allowedTypes.some(allowedType => {
      if (allowedType.includes('*')) {
        // 通配符匹配,如 image/*
        const [category, subtype] = allowedType.split('/');
        const [fileCategory, fileSubtype] = fileType.split('/');
        
        if (subtype === '*') {
          return category === fileCategory;
        }
      }
      
      return allowedType === fileType;
    });
  }
  
  // 添加到上传队列
  addToUploadQueue(files) {
    files.forEach(file => {
      const fileId = this.files.get(this.generateFileId(file))?.id;
      if (fileId && !this.uploadQueue.includes(fileId)) {
        this.uploadQueue.push(fileId);
      }
    });
    
    this.processUploadQueue();
  }
  
  // 处理上传队列
  async processUploadQueue() {
    if (this.isUploading || this.uploadQueue.length === 0) {
      return;
    }
    
    this.isUploading = true;
    
    // 并发上传控制
    const concurrency = this.options.concurrency || 3;
    const chunks = [];
    
    for (let i = 0; i < this.uploadQueue.length; i += concurrency) {
      const chunk = this.uploadQueue.slice(i, i + concurrency);
      chunks.push(chunk);
    }
    
    for (const chunk of chunks) {
      await Promise.all(
        chunk.map(fileId => this.uploadFile(fileId))
      );
    }
    
    this.isUploading = false;
  }
  
  // 上传单个文件
  async uploadFile(fileId) {
    const fileData = this.files.get(fileId);
    if (!fileData || fileData.status === 'uploading') {
      return;
    }
    
    fileData.status = 'uploading';
    fileData.startTime = Date.now();
    
    const formData = new FormData();
    formData.append('file', fileData.file);
    formData.append('fileName', fileData.file.name);
    formData.append('fileSize', fileData.file.size);
    
    try {
      const xhr = new XMLHttpRequest();
      
      // 进度监控
      xhr.upload.addEventListener('progress', (event) => {
        if (event.lengthComputable) {
          const progress = (event.loaded / event.total) * 100;
          this.updateFileProgress(fileId, progress, event.loaded);
        }
      });
      
      // 完成处理
      xhr.addEventListener('load', () => {
        if (xhr.status === 200) {
          fileData.status = 'completed';
          this.removeFromQueue(fileId);
          this.emit('uploadComplete', { fileId, response: JSON.parse(xhr.responseText) });
        } else {
          this.handleUploadError(fileId, `HTTP ${xhr.status}: ${xhr.statusText}`);
        }
      });
      
      // 错误处理
      xhr.addEventListener('error', () => {
        this.handleUploadError(fileId, 'Network error');
      });
      
      xhr.open('POST', this.options.uploadUrl);
      
      // 设置认证头
      if (this.options.headers) {
        Object.entries(this.options.headers).forEach(([key, value]) => {
          xhr.setRequestHeader(key, value);
        });
      }
      
      xhr.send(formData);
      
    } catch (error) {
      this.handleUploadError(fileId, error.message);
    }
  }
  
  // 更新文件进度
  updateFileProgress(fileId, progress, uploadedSize) {
    const fileData = this.files.get(fileId);
    if (!fileData) return;
    
    fileData.progress = progress;
    fileData.uploadedSize = uploadedSize;
    
    // 计算上传速度
    const now = Date.now();
    const timeElapsed = (now - fileData.startTime) / 1000; // 秒
    if (timeElapsed > 0) {
      fileData.speed = uploadedSize / timeElapsed; // bytes per second
    }
    
    this.emit('progress', { fileId, progress, uploadedSize, speed: fileData.speed });
  }
  
  // 处理上传错误
  handleUploadError(fileId, error) {
    const fileData = this.files.get(fileId);
    if (fileData) {
      fileData.status = 'error';
      fileData.error = error;
    }
    
    this.emit('error', { fileId, error, type: 'upload_failed' });
    this.removeFromQueue(fileId);
  }
  
  // 从队列移除
  removeFromQueue(fileId) {
    const index = this.uploadQueue.indexOf(fileId);
    if (index > -1) {
      this.uploadQueue.splice(index, 1);
    }
  }
  
  // 生成文件ID
  generateFileId(file) {
    return `${file.name}-${file.size}-${file.lastModified}-${Math.random().toString(36).substr(2, 9)}`;
  }
  
  // 事件发射器
  on(event, callback) {
    if (!this.eventListeners) {
      this.eventListeners = {};
    }
    if (!this.eventListeners[event]) {
      this.eventListeners[event] = [];
    }
    this.eventListeners[event].push(callback);
  }
  
  emit(event, data) {
    if (this.eventListeners && this.eventListeners[event]) {
      this.eventListeners[event].forEach(callback => {
        callback(data);
      });
    }
  }
  
  // 公开方法
  selectFiles() {
    this.fileInput.click();
  }
  
  getFileList() {
    return Array.from(this.files.values());
  }
  
  cancelUpload(fileId) {
    // 实现取消逻辑
    const fileData = this.files.get(fileId);
    if (fileData && fileData.status === 'uploading') {
      fileData.status = 'cancelled';
      this.removeFromQueue(fileId);
      this.emit('uploadCancelled', { fileId });
    }
  }
  
  removeFile(fileId) {
    this.files.delete(fileId);
    this.removeFromQueue(fileId);
  }
  
  destroy() {
    if (this.fileInput && this.fileInput.parentNode) {
      this.fileInput.parentNode.removeChild(this.fileInput);
    }
  }
}

2.2 高级大文件分片上传

// 高级分片上传管理器
class ChunkedFileUploader {
  constructor(options = {}) {
    this.options = {
      chunkSize: 5 * 1024 * 1024, // 5MB
      retryCount: 3,
      concurrentUploads: 3,
      checksum: true,
      ...options
    };
    
    this.files = new Map();
    this.uploadQueue = [];
    this.activeUploads = new Set();
    this.chunkQueue = [];
    
    this.initialize();
  }
  
  initialize() {
    this.setupEventListeners();
  }
  
  // 添加文件到上传队列
  async addFile(file) {
    const fileId = this.generateFileId(file);
    
    const fileData = {
      id: fileId,
      file,
      name: file.name,
      size: file.size,
      type: file.type,
      totalChunks: Math.ceil(file.size / this.options.chunkSize),
      uploadedChunks: 0,
      chunks: {},
      status: 'pending',
      progress: 0,
      checksum: null
    };
    
    // 计算文件校验和(可选)
    if (this.options.checksum) {
      fileData.checksum = await this.calculateFileChecksum(file);
    }
    
    // 初始化分片信息
    this.initializeChunks(fileData);
    
    this.files.set(fileId, fileData);
    this.uploadQueue.push(fileId);
    
    this.emit('fileAdded', { fileId, fileData });
    this.processUploads();
    
    return fileId;
  }
  
  // 初始化分片数据
  initializeChunks(fileData) {
    const { file, totalChunks, chunks } = fileData;
    
    for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
      const start = chunkIndex * this.options.chunkSize;
      const end = Math.min(start + this.options.chunkSize, file.size);
      const chunkSize = end - start;
      
      chunks[chunkIndex] = {
        index: chunkIndex,
        start,
        end,
        size: chunkSize,
        status: 'pending',
        retryCount: 0,
        checksum: null,
        uploadedSize: 0
      };
    }
  }
  
  // 计算文件校验和
  async calculateFileChecksum(file) {
    return new Promise((resolve) => {
      // 简单的校验和计算,实际项目中可以使用更复杂的算法
      const reader = new FileReader();
      
      reader.onload = (e) => {
        const buffer = e.target.result;
        let checksum = 0;
        
        for (let i = 0; i < buffer.byteLength; i++) {
          checksum = ((checksum << 5) - checksum) + buffer[i];
          checksum = checksum & checksum; // 转换为32位整数
        }
        
        resolve(checksum.toString(16));
      };
      
      reader.readAsArrayBuffer(file.slice(0, Math.min(file.size, 65536))); // 只计算前64KB
    });
  }
  
  // 处理上传队列
  async processUploads() {
    while (this.uploadQueue.length > 0 && this.activeUploads.size < this.options.concurrentUploads) {
      const fileId = this.uploadQueue.shift();
      await this.processFileUpload(fileId);
    }
  }
  
  // 处理单个文件上传
  async processFileUpload(fileId) {
    const fileData = this.files.get(fileId);
    if (!fileData || fileData.status === 'uploading') {
      return;
    }
    
    fileData.status = 'uploading';
    this.activeUploads.add(fileId);
    
    // 检查是否已经存在部分上传
    await this.checkExistingUpload(fileData);
    
    // 开始上传分片
    await this.uploadChunks(fileData);
    
    this.activeUploads.delete(fileId);
    
    // 如果所有分片都上传完成,完成文件上传
    if (fileData.uploadedChunks === fileData.totalChunks) {
      await this.completeFileUpload(fileData);
    }
    
    this.processUploads();
  }
  
  // 检查已存在的上传
  async checkExistingUpload(fileData) {
    try {
      const response = await fetch(`${this.options.uploadUrl}/status`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...this.options.headers
        },
        body: JSON.stringify({
          fileName: fileData.name,
          fileSize: fileData.size,
          fileChecksum: fileData.checksum,
          totalChunks: fileData.totalChunks
        })
      });
      
      if (response.ok) {
        const status = await response.json();
        
        // 更新已上传的分片
        status.uploadedChunks.forEach(chunkIndex => {
          if (fileData.chunks[chunkIndex]) {
            fileData.chunks[chunkIndex].status = 'completed';
            fileData.uploadedChunks++;
          }
        });
        
        this.updateFileProgress(fileData);
      }
    } catch (error) {
      console.warn('检查上传状态失败:', error);
    }
  }
  
  // 上传分片
  async uploadChunks(fileData) {
    const pendingChunks = this.getPendingChunks(fileData);
    
    while (pendingChunks.length > 0) {
      const chunkConcurrency = Math.min(
        this.options.concurrentUploads - this.activeUploads.size + 1,
        pendingChunks.length
      );
      
      const chunkBatch = pendingChunks.splice(0, chunkConcurrency);
      
      await Promise.all(
        chunkBatch.map(chunk => this.uploadChunk(fileData, chunk))
      );
    }
  }
  
  // 获取待上传的分片
  getPendingChunks(fileData) {
    return Object.values(fileData.chunks)
      .filter(chunk => chunk.status === 'pending')
      .sort((a, b) => a.index - b.index);
  }
  
  // 上传单个分片
  async uploadChunk(fileData, chunk) {
    if (chunk.status === 'completed') {
      return;
    }
    
    chunk.status = 'uploading';
    
    const formData = new FormData();
    const chunkBlob = fileData.file.slice(chunk.start, chunk.end);
    
    formData.append('chunk', chunkBlob);
    formData.append('chunkIndex', chunk.index);
    formData.append('totalChunks', fileData.totalChunks);
    formData.append('fileName', fileData.name);
    formData.append('fileSize', fileData.size);
    formData.append('fileChecksum', fileData.checksum);
    
    try {
      const xhr = new XMLHttpRequest();
      
      // 进度监控
      xhr.upload.addEventListener('progress', (event) => {
        if (event.lengthComputable) {
          chunk.uploadedSize = event.loaded;
          this.updateFileProgress(fileData);
        }
      });
      
      const uploadPromise = new Promise((resolve, reject) => {
        xhr.addEventListener('load', () => {
          if (xhr.status === 200) {
            chunk.status = 'completed';
            fileData.uploadedChunks++;
            this.updateFileProgress(fileData);
            resolve();
          } else {
            reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
          }
        });
        
        xhr.addEventListener('error', () => reject(new Error('Network error')));
        xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));
      });
      
      xhr.open('POST', `${this.options.uploadUrl}/chunk`);
      
      if (this.options.headers) {
        Object.entries(this.options.headers).forEach(([key, value]) => {
          xhr.setRequestHeader(key, value);
        });
      }
      
      xhr.send(formData);
      
      await uploadPromise;
      
    } catch (error) {
      await this.handleChunkUploadError(fileData, chunk, error);
    }
  }
  
  // 处理分片上传错误
  async handleChunkUploadError(fileData, chunk, error) {
    chunk.retryCount++;
    
    if (chunk.retryCount <= this.options.retryCount) {
      console.warn(`分片 ${chunk.index} 上传失败,正在重试 (${chunk.retryCount}/${this.options.retryCount}):`, error);
      
      // 延迟重试
      await new Promise(resolve => setTimeout(resolve, 1000 * chunk.retryCount));
      chunk.status = 'pending';
      return this.uploadChunk(fileData, chunk);
    } else {
      chunk.status = 'error';
      chunk.error = error.message;
      fileData.status = 'error';
      
      this.emit('error', {
        fileId: fileData.id,
        chunkIndex: chunk.index,
        error: error.message,
        type: 'chunk_upload_failed'
      });
    }
  }
  
  // 完成文件上传
  async completeFileUpload(fileData) {
    try {
      const response = await fetch(`${this.options.uploadUrl}/complete`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...this.options.headers
        },
        body: JSON.stringify({
          fileName: fileData.name,
          fileSize: fileData.size,
          fileChecksum: fileData.checksum,
          totalChunks: fileData.totalChunks
        })
      });
      
      if (response.ok) {
        fileData.status = 'completed';
        const result = await response.json();
        
        this.emit('uploadComplete', {
          fileId: fileData.id,
          fileData,
          result
        });
      } else {
        throw new Error(`Complete failed: ${response.statusText}`);
      }
    } catch (error) {
      fileData.status = 'error';
      fileData.error = error.message;
      
      this.emit('error', {
        fileId: fileData.id,
        error: error.message,
        type: 'complete_failed'
      });
    }
  }
  
  // 更新文件进度
  updateFileProgress(fileData) {
    let totalUploaded = 0;
    let totalSize = 0;
    
    Object.values(fileData.chunks).forEach(chunk => {
      totalSize += chunk.size;
      if (chunk.status === 'completed') {
        totalUploaded += chunk.size;
      } else if (chunk.status === 'uploading') {
        totalUploaded += chunk.uploadedSize;
      }
    });
    
    const progress = totalSize > 0 ? (totalUploaded / totalSize) * 100 : 0;
    fileData.progress = progress;
    
    this.emit('progress', {
      fileId: fileData.id,
      progress,
      uploadedSize: totalUploaded,
      totalSize: fileData.size,
      uploadedChunks: fileData.uploadedChunks,
      totalChunks: fileData.totalChunks
    });
  }
  
  // 暂停上传
  pauseUpload(fileId) {
    const fileData = this.files.get(fileId);
    if (fileData && fileData.status === 'uploading') {
      fileData.status = 'paused';
      
      // 从活跃上传中移除
      this.activeUploads.delete(fileId);
      
      // 将文件重新加入队列
      if (!this.uploadQueue.includes(fileId)) {
        this.uploadQueue.unshift(fileId);
      }
      
      this.emit('uploadPaused', { fileId });
    }
  }
  
  // 恢复上传
  resumeUpload(fileId) {
    const fileData = this.files.get(fileId);
    if (fileData && fileData.status === 'paused') {
      fileData.status = 'pending';
      this.processUploads();
      this.emit('uploadResumed', { fileId });
    }
  }
  
  // 生成文件ID
  generateFileId(file) {
    return `${file.name}-${file.size}-${file.lastModified}-${Math.random().toString(36).substr(2, 9)}`;
  }
  
  // 事件处理
  on(event, callback) {
    if (!this.eventListeners) this.eventListeners = {};
    if (!this.eventListeners[event]) this.eventListeners[event] = [];
    this.eventListeners[event].push(callback);
  }
  
  emit(event, data) {
    if (this.eventListeners && this.eventListeners[event]) {
      this.eventListeners[event].forEach(callback => callback(data));
    }
  }
}

三、文件下载:从简单到高级

3.1 智能文件下载管理器

// 智能文件下载管理器
class FileDownloadManager {
  constructor(options = {}) {
    this.options = {
      concurrentDownloads: 3,
      chunkSize: 2 * 1024 * 1024, // 2MB
      retryCount: 3,
      enableResume: true,
      ...options
    };
    
    this.downloads = new Map();
    this.activeDownloads = new Set();
    this.downloadQueue = [];
    
    this.setupStorage();
  }
  
  // 设置本地存储
  setupStorage() {
    // 检查浏览器支持
    this.supportsStorage = typeof Storage !== 'undefined';
    
    if (this.supportsStorage) {
      this.loadDownloadState();
    }
  }
  
  // 加载下载状态
  loadDownloadState() {
    try {
      const savedState = localStorage.getItem('fileDownloadState');
      if (savedState) {
        const state = JSON.parse(savedState);
        
        Object.entries(state).forEach(([downloadId, downloadData]) => {
          // 恢复未完成的下载
          if (downloadData.status === 'downloading' || downloadData.status === 'paused') {
            this.downloads.set(downloadId, downloadData);
            this.downloadQueue.push(downloadId);
          }
        });
        
        if (this.downloadQueue.length > 0) {
          this.processDownloadQueue();
        }
      }
    } catch (error) {
      console.warn('加载下载状态失败:', error);
    }
  }
  
  // 保存下载状态
  saveDownloadState() {
    if (!this.supportsStorage) return;
    
    try {
      const state = {};
      this.downloads.forEach((downloadData, downloadId) => {
        state[downloadId] = {
          id: downloadData.id,
          fileName: downloadData.fileName,
          fileSize: downloadData.fileSize,
          downloadedSize: downloadData.downloadedSize,
          status: downloadData.status,
          chunks: downloadData.chunks,
          url: downloadData.url
        };
      });
      
      localStorage.setItem('fileDownloadState', JSON.stringify(state));
    } catch (error) {
      console.warn('保存下载状态失败:', error);
    }
  }
  
  // 添加下载任务
  async addDownload(url, fileName, options = {}) {
    const downloadId = this.generateDownloadId(url, fileName);
    
    // 检查是否已存在
    if (this.downloads.has(downloadId)) {
      const existing = this.downloads.get(downloadId);
      if (existing.status === 'completed') {
        this.emit('error', {
          downloadId,
          error: '文件已下载完成',
          type: 'already_downloaded'
        });
        return downloadId;
      }
    }
    
    const downloadData = {
      id: downloadId,
      url,
      fileName: fileName || this.extractFileName(url),
      fileSize: 0,
      downloadedSize: 0,
      status: 'pending',
      chunks: {},
      progress: 0,
      speed: 0,
      startTime: null,
      options
    };
    
    this.downloads.set(downloadId, downloadData);
    this.downloadQueue.push(downloadId);
    
    // 获取文件信息
    await this.fetchFileInfo(downloadData);
    
    this.emit('downloadAdded', { downloadId, downloadData });
    this.processDownloadQueue();
    
    return downloadId;
  }
  
  // 获取文件信息
  async fetchFileInfo(downloadData) {
    try {
      const response = await fetch(downloadData.url, {
        method: 'HEAD',
        headers: this.options.headers
      });
      
      if (response.ok) {
        downloadData.fileSize = parseInt(response.headers.get('content-length') || '0');
        downloadData.acceptsRanges = response.headers.get('accept-ranges') === 'bytes';
        
        // 初始化分片信息
        if (this.options.enableResume && downloadData.acceptsRanges) {
          this.initializeChunks(downloadData);
        }
        
        this.emit('fileInfoFetched', { downloadId: downloadData.id, downloadData });
      } else {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
    } catch (error) {
      downloadData.status = 'error';
      downloadData.error = error.message;
      
      this.emit('error', {
        downloadId: downloadData.id,
        error: error.message,
        type: 'fetch_file_info_failed'
      });
    }
  }
  
  // 初始化分片
  initializeChunks(downloadData) {
    const totalChunks = Math.ceil(downloadData.fileSize / this.options.chunkSize);
    
    for (let i = 0; i < totalChunks; i++) {
      const start = i * this.options.chunkSize;
      const end = Math.min(start + this.options.chunkSize - 1, downloadData.fileSize - 1);
      
      downloadData.chunks[i] = {
        index: i,
        start,
        end,
        size: end - start + 1,
        status: 'pending',
        downloadedSize: 0,
        retryCount: 0
      };
    }
  }
  
  // 处理下载队列
  async processDownloadQueue() {
    while (this.downloadQueue.length > 0 && this.activeDownloads.size < this.options.concurrentDownloads) {
      const downloadId = this.downloadQueue.shift();
      await this.processDownload(downloadId);
    }
  }
  
  // 处理单个下载
  async processDownload(downloadId) {
    const downloadData = this.downloads.get(downloadId);
    if (!downloadData || downloadData.status === 'downloading') {
      return;
    }
    
    downloadData.status = 'downloading';
    downloadData.startTime = Date.now();
    this.activeDownloads.add(downloadId);
    
    try {
      if (this.options.enableResume && downloadData.acceptsRanges) {
        await this.downloadWithChunks(downloadData);
      } else {
        await this.downloadSimple(downloadData);
      }
      
      this.activeDownloads.delete(downloadId);
      
      if (downloadData.status === 'completed') {
        this.emit('downloadComplete', { downloadId, downloadData });
      }
      
    } catch (error) {
      this.activeDownloads.delete(downloadId);
      downloadData.status = 'error';
      downloadData.error = error.message;
      
      this.emit('error', {
        downloadId,
        error: error.message,
        type: 'download_failed'
      });
    }
    
    this.saveDownloadState();
    this.processDownloadQueue();
  }
  
  // 简单下载(不支持断点续传)
  async downloadSimple(downloadData) {
    const response = await fetch(downloadData.url, {
      headers: this.options.headers
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    const reader = response.body.getReader();
    const chunks = [];
    let receivedLength = 0;
    
    while (true) {
      const { done, value } = await reader.read();
      
      if (done) {
        break;
      }
      
      chunks.push(value);
      receivedLength += value.length;
      
      downloadData.downloadedSize = receivedLength;
      downloadData.progress = (receivedLength / downloadData.fileSize) * 100;
      
      this.updateDownloadSpeed(downloadData);
      this.emit('progress', { downloadId: downloadData.id, downloadData });
    }
    
    // 合并chunks并创建Blob
    const blob = new Blob(chunks);
    this.saveFile(blob, downloadData.fileName);
    
    downloadData.status = 'completed';
  }
  
  // 分片下载(支持断点续传)
  async downloadWithChunks(downloadData) {
    const pendingChunks = this.getPendingChunks(downloadData);
    
    while (pendingChunks.length > 0) {
      const chunkConcurrency = Math.min(
        this.options.concurrentDownloads - this.activeDownloads.size + 1,
        pendingChunks.length
      );
      
      const chunkBatch = pendingChunks.splice(0, chunkConcurrency);
      
      await Promise.all(
        chunkBatch.map(chunk => this.downloadChunk(downloadData, chunk))
      );
    }
    
    // 所有分片下载完成,合并文件
    if (downloadData.downloadedSize === downloadData.fileSize) {
      await this.assembleFile(downloadData);
      downloadData.status = 'completed';
    }
  }
  
  // 获取待下载的分片
  getPendingChunks(downloadData) {
    return Object.values(downloadData.chunks)
      .filter(chunk => chunk.status === 'pending')
      .sort((a, b) => a.index - b.index);
  }
  
  // 下载单个分片
  async downloadChunk(downloadData, chunk) {
    if (chunk.status === 'completed') {
      return;
    }
    
    chunk.status = 'downloading';
    
    try {
      const response = await fetch(downloadData.url, {
        headers: {
          ...this.options.headers,
          'Range': `bytes=${chunk.start}-${chunk.end}`
        }
      });
      
      if (!response.ok && response.status !== 206) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const reader = response.body.getReader();
      const chunkChunks = [];
      let receivedLength = 0;
      
      while (true) {
        const { done, value } = await reader.read();
        
        if (done) {
          break;
        }
        
        chunkChunks.push(value);
        receivedLength += value.length;
        
        chunk.downloadedSize = receivedLength;
        downloadData.downloadedSize += value.length;
        
        this.updateDownloadProgress(downloadData);
      }
      
      // 保存分片数据
      chunk.data = new Blob(chunkChunks);
      chunk.status = 'completed';
      
      // 保存到临时存储
      await this.saveChunkToStorage(downloadData.id, chunk);
      
    } catch (error) {
      await this.handleChunkDownloadError(downloadData, chunk, error);
    }
  }
  
  // 处理分片下载错误
  async handleChunkDownloadError(downloadData, chunk, error) {
    chunk.retryCount++;
    
    if (chunk.retryCount <= this.options.retryCount) {
      console.warn(`分片 ${chunk.index} 下载失败,正在重试 (${chunk.retryCount}/${this.options.retryCount}):`, error);
      
      await new Promise(resolve => setTimeout(resolve, 1000 * chunk.retryCount));
      chunk.status = 'pending';
      return this.downloadChunk(downloadData, chunk);
    } else {
      chunk.status = 'error';
      chunk.error = error.message;
      downloadData.status = 'error';
      
      this.emit('error', {
        downloadId: downloadData.id,
        chunkIndex: chunk.index,
        error: error.message,
        type: 'chunk_download_failed'
      });
    }
  }
  
  // 更新下载进度
  updateDownloadProgress(downloadData) {
    const progress = downloadData.fileSize > 0 ? 
      (downloadData.downloadedSize / downloadData.fileSize) * 100 : 0;
    
    downloadData.progress = progress;
    this.updateDownloadSpeed(downloadData);
    
    this.emit('progress', { downloadId: downloadData.id, downloadData });
    this.saveDownloadState();
  }
  
  // 更新下载速度
  updateDownloadSpeed(downloadData) {
    const now = Date.now();
    const timeElapsed = (now - downloadData.startTime) / 1000;
    
    if (timeElapsed > 0) {
      downloadData.speed = downloadData.downloadedSize / timeElapsed;
    }
  }
  
  // 保存分片到存储
  async saveChunkToStorage(downloadId, chunk) {
    // 使用IndexedDB或临时存储保存分片数据
    // 这里简化为内存存储,实际项目需要使用持久化存储
    if (!this.chunkStorage) {
      this.chunkStorage = new Map();
    }
    
    const chunkKey = `${downloadId}_chunk_${chunk.index}`;
    this.chunkStorage.set(chunkKey, chunk.data);
  }
  
  // 从存储加载分片
  async loadChunkFromStorage(downloadId, chunkIndex) {
    if (!this.chunkStorage) return null;
    
    const chunkKey = `${downloadId}_chunk_${chunkIndex}`;
    return this.chunkStorage.get(chunkKey) || null;
  }
  
  // 组装文件
  async assembleFile(downloadData) {
    const chunks = [];
    
    for (let i = 0; i < Object.keys(downloadData.chunks).length; i++) {
      const chunkData = await this.loadChunkFromStorage(downloadData.id, i);
      if (chunkData) {
        chunks.push(chunkData);
      }
    }
    
    const fullBlob = new Blob(chunks);
    this.saveFile(fullBlob, downloadData.fileName);
    
    // 清理临时存储
    this.cleanupChunkStorage(downloadData.id);
  }
  
  // 保存文件
  saveFile(blob, fileName) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = fileName;
    a.click();
    
    // 清理URL
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }
  
  // 清理分片存储
  cleanupChunkStorage(downloadId) {
    if (!this.chunkStorage) return;
    
    for (const key of this.chunkStorage.keys()) {
      if (key.startsWith(`${downloadId}_chunk_`)) {
        this.chunkStorage.delete(key);
      }
    }
  }
  
  // 暂停下载
  pauseDownload(downloadId) {
    const downloadData = this.downloads.get(downloadId);
    if (downloadData && downloadData.status === 'downloading') {
      downloadData.status = 'paused';
      this.activeDownloads.delete(downloadId);
      
      // 将下载重新加入队列
      if (!this.downloadQueue.includes(downloadId)) {
        this.downloadQueue.unshift(downloadId);
      }
      
      this.emit('downloadPaused', { downloadId });
      this.saveDownloadState();
    }
  }
  
  // 恢复下载
  resumeDownload(downloadId) {
    const downloadData = this.downloads.get(downloadId);
    if (downloadData && downloadData.status === 'paused') {
      downloadData.status = 'pending';
      this.processDownloadQueue();
      this.emit('downloadResumed', { downloadId });
    }
  }
  
  // 取消下载
  cancelDownload(downloadId) {
    const downloadData = this.downloads.get(downloadId);
    if (downloadData) {
      downloadData.status = 'cancelled';
      this.activeDownloads.delete(downloadId);
      
      // 从队列中移除
      const queueIndex = this.downloadQueue.indexOf(downloadId);
      if (queueIndex > -1) {
        this.downloadQueue.splice(queueIndex, 1);
      }
      
      // 清理存储
      this.cleanupChunkStorage(downloadId);
      this.downloads.delete(downloadId);
      
      this.emit('downloadCancelled', { downloadId });
      this.saveDownloadState();
    }
  }
  
  // 生成下载ID
  generateDownloadId(url, fileName) {
    return `${url}-${fileName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
  
  // 提取文件名
  extractFileName(url) {
    const path = url.split('/').pop();
    return path.split('?')[0];
  }
  
  // 事件处理
  on(event, callback) {
    if (!this.eventListeners) this.eventListeners = {};
    if (!this.eventListeners[event]) this.eventListeners[event] = [];
    this.eventListeners[event].push(callback);
  }
  
  emit(event, data) {
    if (this.eventListeners && this.eventListeners[event]) {
      this.eventListeners[event].forEach(callback => callback(data));
    }
  }
}

四、文件预览:多格式支持与性能优化

4.1 统一文件预览管理器

// 统一文件预览管理器
class FilePreviewManager {
  constructor(options = {}) {
    this.options = {
      maxPreviewSize: 50 * 1024 * 1024, // 50MB
      imageQuality: 0.8,
      pdfScale: 1.5,
      ...options
    };
    
    this.previewers = new Map();
    this.activePreviews = new Map();
    this.cache = new Map();
    
    this.initializePreviewers();
  }
  
  // 初始化预览器
  initializePreviewers() {
    // 图片预览器
    this.previewers.set('image', new ImagePreviewer(this.options));
    
    // PDF预览器
    this.previewers.set('pdf', new PDFPreviewer(this.options));
    
    // 文本预览器
    this.previewers.set('text', new TextPreviewer(this.options));
    
    // 文档预览器
    this.previewers.set('document', new DocumentPreviewer(this.options));
    
    // 视频预览器
    this.previewers.set('video', new VideoPreviewer(this.options));
    
    // 音频预览器
    this.previewers.set('audio', new AudioPreviewer(this.options));
  }
  
  // 预览文件
  async previewFile(file, container, options = {}) {
    const fileType = this.getFileType(file);
    const previewer = this.previewers.get(fileType);
    
    if (!previewer) {
      throw new Error(`不支持的文件类型: ${fileType}`);
    }
    
    // 检查文件大小
    if (file.size > this.options.maxPreviewSize) {
      throw new Error(`文件过大,最大支持 ${this.formatFileSize(this.options.maxPreviewSize)}`);
    }
    
    const previewId = this.generatePreviewId(file);
    this.activePreviews.set(previewId, { file, container, previewer });
    
    try {
      // 检查缓存
      if (this.cache.has(previewId)) {
        const cachedPreview = this.cache.get(previewId);
        await previewer.renderFromCache(cachedPreview, container, options);
        return previewId;
      }
      
      // 创建预览
      const previewData = await previewer.preview(file, container, options);
      
      // 缓存预览结果
      this.cache.set(previewId, previewData);
      
      return previewId;
      
    } catch (error) {
      this.activePreviews.delete(previewId);
      throw error;
    }
  }
  
  // 获取文件类型
  getFileType(file) {
    const typeManager = new FileTypeManager();
    return typeManager.getFileCategory(file.name);
  }
  
  // 生成预览ID
  generatePreviewId(file) {
    return `${file.name}-${file.size}-${file.lastModified}-${Math.random().toString(36).substr(2, 9)}`;
  }
  
  // 格式化文件大小
  formatFileSize(bytes) {
    const units = ['B', 'KB', 'MB', 'GB'];
    let size = bytes;
    let unitIndex = 0;
    
    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }
    
    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }
  
  // 销毁预览
  destroyPreview(previewId) {
    const preview = this.activePreviews.get(previewId);
    if (preview) {
      preview.previewer.destroy(previewId);
      this.activePreviews.delete(previewId);
    }
  }
  
  // 清理缓存
  clearCache() {
    this.cache.clear();
  }
  
  // 获取支持的预览类型
  getSupportedTypes() {
    return Array.from(this.previewers.keys());
  }
}

// 图片预览器
class ImagePreviewer {
  constructor(options) {
    this.options = options;
  }
  
  async preview(file, container, options = {}) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      const url = URL.createObjectURL(file);
      
      img.onload = () => {
        // 调整图片大小以适应容器
        this.adjustImageSize(img, container, options);
        container.appendChild(img);
        
        URL.revokeObjectURL(url);
        
        resolve({
          type: 'image',
          element: img,
          width: img.width,
          height: img.height
        });
      };
      
      img.onerror = () => {
        URL.revokeObjectURL(url);
        reject(new Error('图片加载失败'));
      };
      
      img.src = url;
    });
  }
  
  adjustImageSize(img, container, options) {
    const containerWidth = container.clientWidth;
    const containerHeight = container.clientHeight;
    
    const aspectRatio = img.width / img.height;
    
    let displayWidth = img.width;
    let displayHeight = img.height;
    
    if (displayWidth > containerWidth) {
      displayWidth = containerWidth;
      displayHeight = displayWidth / aspectRatio;
    }
    
    if (displayHeight > containerHeight) {
      displayHeight = containerHeight;
      displayWidth = displayHeight * aspectRatio;
    }
    
    img.style.width = `${displayWidth}px`;
    img.style.height = `${displayHeight}px`;
    img.style.objectFit = 'contain';
  }
  
  async renderFromCache(cachedData, container, options) {
    container.appendChild(cachedData.element);
  }
  
  destroy(previewId) {
    // 清理资源
  }
}

// PDF预览器
class PDFPreviewer {
  constructor(options) {
    this.options = options;
    
    // 动态加载PDF.js
    this.pdfjsLib = null;
    this.loadPDFJS();
  }
  
  async loadPDFJS() {
    if (typeof pdfjsLib !== 'undefined') {
      this.pdfjsLib = pdfjsLib;
      return;
    }
    
    // 动态加载PDF.js
    const script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js';
    script.onload = () => {
      this.pdfjsLib = window['pdfjsLib'];
      this.pdfjsLib.GlobalWorkerOptions.workerSrc = 
        'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
    };
    document.head.appendChild(script);
  }
  
  async preview(file, container, options = {}) {
    if (!this.pdfjsLib) {
      await new Promise(resolve => setTimeout(resolve, 100));
      if (!this.pdfjsLib) {
        throw new Error('PDF.js 加载失败');
      }
    }
    
    const arrayBuffer = await file.arrayBuffer();
    const pdf = await this.pdfjsLib.getDocument(arrayBuffer).promise;
    
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    container.appendChild(canvas);
    
    // 渲染第一页
    const page = await pdf.getPage(1);
    const viewport = page.getViewport({ scale: this.options.pdfScale });
    
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    
    await page.render({
      canvasContext: ctx,
      viewport: viewport
    }).promise;
    
    return {
      type: 'pdf',
      element: canvas,
      pdf: pdf,
      totalPages: pdf.numPages,
      currentPage: 1
    };
  }
  
  async renderFromCache(cachedData, container, options) {
    container.appendChild(cachedData.element);
  }
  
  destroy(previewId) {
    // 清理PDF资源
  }
}

// 文本预览器
class TextPreviewer {
  constructor(options) {
    this.options = options;
  }
  
  async preview(file, container, options = {}) {
    const text = await this.readFileAsText(file);
    
    const pre = document.createElement('pre');
    pre.style.cssText = `
      margin: 0;
      padding: 16px;
      background: #f5f5f5;
      border-radius: 4px;
      overflow: auto;
      max-height: 400px;
      font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
      font-size: 12px;
      line-height: 1.4;
    `;
    pre.textContent = text;
    
    container.appendChild(pre);
    
    return {
      type: 'text',
      element: pre,
      content: text,
      length: text.length
    };
  }
  
  async readFileAsText(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      
      reader.onload = (e) => resolve(e.target.result);
      reader.onerror = () => reject(new Error('文件读取失败'));
      
      reader.readAsText(file);
    });
  }
  
  async renderFromCache(cachedData, container, options) {
    container.appendChild(cachedData.element);
  }
  
  destroy(previewId) {
    // 文本预览无需特殊清理
  }
}

五、文件解析:从数据提取到结构化处理

5.1 通用文件解析器

// 通用文件解析器
class FileParser {
  constructor(options = {}) {
    this.options = {
      maxParseSize: 10 * 1024 * 1024, // 10MB
      chunkSize: 64 * 1024, // 64KB
      ...options
    };
    
    this.parsers = new Map();
    this.initializeParsers();
  }
  
  // 初始化解析器
  initializeParsers() {
    this.parsers.set('csv', new CSVParser(this.options));
    this.parsers.set('excel', new ExcelParser(this.options));
    this.parsers.set('json', new JSONParser(this.options));
    this.parsers.set('text', new TextParser(this.options));
    this.parsers.set('image', new ImageParser(this.options));
  }
  
  // 解析文件
  async parseFile(file, options = {}) {
    const fileType = this.getFileType(file);
    const parser = this.parsers.get(fileType);
    
    if (!parser) {
      throw new Error(`不支持的文件类型: ${fileType}`);
    }
    
    // 检查文件大小
    if (file.size > this.options.maxParseSize) {
      throw new Error(`文件过大,最大支持 ${this.formatFileSize(this.options.maxParseSize)}`);
    }
    
    return await parser.parse(file, options);
  }
  
  // 获取文件类型
  getFileType(file) {
    const extension = file.name.split('.').pop().toLowerCase();
    
    const typeMap = {
      'csv': 'csv',
      'xls': 'excel',
      'xlsx': 'excel',
      'json': 'json',
      'txt': 'text',
      'jpg': 'image',
      'jpeg': 'image',
      'png': 'image',
      'gif': 'image'
    };
    
    return typeMap[extension] || 'text';
  }
  
  // 格式化文件大小
  formatFileSize(bytes) {
    const units = ['B', 'KB', 'MB', 'GB'];
    let size = bytes;
    let unitIndex = 0;
    
    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }
    
    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }
  
  // 注册自定义解析器
  registerParser(fileType, parser) {
    this.parsers.set(fileType, parser);
  }
}

// CSV解析器
class CSVParser {
  constructor(options) {
    this.options = options;
  }
  
  async parse(file, options = {}) {
    const text = await this.readFileAsText(file);
    
    const {
      delimiter = ',',
      hasHeader = true,
      skipEmptyLines = true
    } = options;
    
    const lines = text.split('\n')
      .filter(line => !skipEmptyLines || line.trim() !== '');
    
    if (lines.length === 0) {
      return { headers: [], data: [] };
    }
    
    const headers = hasHeader ? 
      this.parseCSVLine(lines[0], delimiter) : 
      this.generateHeaders(lines[0].split(delimiter).length);
    
    const data = [];
    const startIndex = hasHeader ? 1 : 0;
    
    for (let i = startIndex; i < lines.length; i++) {
      const values = this.parseCSVLine(lines[i], delimiter);
      const row = {};
      
      headers.forEach((header, index) => {
        row[header] = values[index] || '';
      });
      
      data.push(row);
    }
    
    return {
      type: 'csv',
      headers,
      data,
      totalRows: data.length,
      fileSize: file.size
    };
  }
  
  parseCSVLine(line, delimiter) {
    const result = [];
    let current = '';
    let inQuotes = false;
    
    for (let i = 0; i < line.length; i++) {
      const char = line[i];
      
      if (char === '"') {
        inQuotes = !inQuotes;
      } else if (char === delimiter && !inQuotes) {
        result.push(current.trim());
        current = '';
      } else {
        current += char;
      }
    }
    
    result.push(current.trim());
    return result;
  }
  
  generateHeaders(count) {
    return Array.from({ length: count }, (_, i) => `Column${i + 1}`);
  }
  
  async readFileAsText(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      
      reader.onload = (e) => resolve(e.target.result);
      reader.onerror = () => reject(new Error('文件读取失败'));
      
      reader.readAsText(file);
    });
  }
}

// Excel解析器
class ExcelParser {
  constructor(options) {
    this.options = options;
    this.xlsx = null;
    this.loadXLSX();
  }
  
  async loadXLSX() {
    if (typeof XLSX !== 'undefined') {
      this.xlsx = XLSX;
      return;
    }
    
    // 动态加载SheetJS
    const script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js';
    script.onload = () => {
      this.xlsx = window['XLSX'];
    };
    document.head.appendChild(script);
  }
  
  async parse(file, options = {}) {
    if (!this.xlsx) {
      await new Promise(resolve => setTimeout(resolve, 100));
      if (!this.xlsx) {
        throw new Error('SheetJS 加载失败');
      }
    }
    
    const arrayBuffer = await file.arrayBuffer();
    const workbook = this.xlsx.read(arrayBuffer, { type: 'array' });
    
    const result = {
      type: 'excel',
      sheets: [],
      fileSize: file.size,
      totalSheets: workbook.SheetNames.length
    };
    
    workbook.SheetNames.forEach(sheetName => {
      const worksheet = workbook.Sheets[sheetName];
      const jsonData = this.xlsx.utils.sheet_to_json(worksheet, { header: 1 });
      
      const headers = jsonData.length > 0 ? jsonData[0] : [];
      const data = jsonData.length > 1 ? jsonData.slice(1) : [];
      
      result.sheets.push({
        name: sheetName,
        headers,
        data: data.map(row => {
          const obj = {};
          headers.forEach((header, index) => {
            obj[header] = row[index] || '';
          });
          return obj;
        }),
        totalRows: data.length
      });
    });
    
    return result;
  }
}

// JSON解析器
class JSONParser {
  constructor(options) {
    this.options = options;
  }
  
  async parse(file, options = {}) {
    const text = await this.readFileAsText(file);
    
    try {
      const data = JSON.parse(text);
      
      return {
        type: 'json',
        data,
        size: file.size,
        isValid: true,
        structure: this.analyzeJSONStructure(data)
      };
    } catch (error) {
      return {
        type: 'json',
        data: null,
        size: file.size,
        isValid: false,
        error: error.message
      };
    }
  }
  
  analyzeJSONStructure(data) {
    if (Array.isArray(data)) {
      return {
        type: 'array',
        length: data.length,
        itemStructure: data.length > 0 ? this.analyzeJSONStructure(data[0]) : null
      };
    } else if (typeof data === 'object' && data !== null) {
      return {
        type: 'object',
        keys: Object.keys(data),
        properties: Object.entries(data).reduce((acc, [key, value]) => {
          acc[key] = this.analyzeJSONStructure(value);
          return acc;
        }, {})
      };
    } else {
      return {
        type: typeof data,
        value: data
      };
    }
  }
  
  async readFileAsText(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      
      reader.onload = (e) => resolve(e.target.result);
      reader.onerror = () => reject(new Error('文件读取失败'));
      
      reader.readAsText(file);
    });
  }
}

// 图像解析器
class ImageParser {
  constructor(options) {
    this.options = options;
  }
  
  async parse(file, options = {}) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      const url = URL.createObjectURL(file);
      
      img.onload = () => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
        
        const imageData = ctx.getImageData(0, 0, img.width, img.height);
        const dominantColor = this.getDominantColor(imageData);
        
        URL.revokeObjectURL(url);
        
        resolve({
          type: 'image',
          width: img.width,
          height: img.height,
          format: file.type,
          fileSize: file.size,
          dominantColor,
          metadata: this.extractImageMetadata(file)
        });
      };
      
      img.onerror = () => {
        URL.revokeObjectURL(url);
        reject(new Error('图片加载失败'));
      };
      
      img.src = url;
    });
  }
  
  getDominantColor(imageData) {
    const data = imageData.data;
    const colorCount = {};
    let maxCount = 0;
    let dominantColor = '';
    
    for (let i = 0; i < data.length; i += 4) {
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];
      
      // 简化颜色空间(减少精度)
      const simplifiedR = Math.floor(r / 32) * 32;
      const simplifiedG = Math.floor(g / 32) * 32;
      const simplifiedB = Math.floor(b / 32) * 32;
      
      const colorKey = `${simplifiedR},${simplifiedG},${simplifiedB}`;
      colorCount[colorKey] = (colorCount[colorKey] || 0) + 1;
      
      if (colorCount[colorKey] > maxCount) {
        maxCount = colorCount[colorKey];
        dominantColor = colorKey;
      }
    }
    
    return dominantColor.split(',').map(Number);
  }
  
  extractImageMetadata(file) {
    // 这里可以扩展提取更多元数据
    return {
      name: file.name,
      type: file.type,
      size: file.size,
      lastModified: file.lastModified
    };
  }
}

六、面试官常见提问

技术深度类问题

  1. 大文件上传有哪些优化方案?断点续传如何实现?

    • 考察对大文件上传技术的全面理解
  2. 前端如何实现文件下载的进度监控和断点续传?

    • 考察文件下载的高级特性实现
  3. 不同格式文件的预览方案有哪些?各有什么优缺点?

    • 考察多格式文件预览的技术选型能力
  4. 前端解析大文件时如何避免内存溢出?

    • 考察内存管理和性能优化意识
  5. 如何保证文件操作的安全性?

    • 考察安全意识和防护措施

实战场景类问题

  1. 设计一个支持TB级文件上传的系统架构

    • 考察大规模文件处理的设计能力
  2. 如何实现一个企业网盘的核心功能?

    • 考察完整文件管理系统的设计能力
  3. 前端如何处理Excel文件的数据导入和导出?

    • 考察具体文件格式的处理能力

七、面试技巧与回答策略

7.1 展现系统设计能力

  • 分层架构:从基础功能→高级特性→性能优化层层展开
  • 技术选型:对比不同方案的优势和适用场景
  • 边界处理:强调错误处理、兼容性、降级方案

7.2 问题排查类问题回答模板

1. 现象分析:明确问题的具体表现和影响范围
2. 链路追踪:从上传→存储→预览→解析全链路分析
3. 性能优化:针对瓶颈提出具体的优化方案
4. 监控体系:建立完善的监控和报警机制

7.3 展现工程化思维

  • 模块化设计:展示可复用的组件和工具类
  • 错误处理:完善的异常处理和用户提示
  • 性能监控:实时监控和性能分析能力

结语

前端文件操作是一个涉及网络传输、内存管理、格式解析、性能优化的复杂领域。通过本文介绍的完整解决方案,你可以构建出稳定、高效、用户友好的文件管理系统,无论是简单的图片上传还是复杂的TB级文件处理都能游刃有余。

记住:优秀的文件操作体验是产品专业性的重要体现。从进度显示到错误恢复,从格式兼容到性能优化,每一个细节都影响着用户的最终体验。


思考题:在你的项目中,如何处理用户上传恶意文件的安全风险?欢迎在评论区分享你的安全防护方案!