大文件上传功能开发学习笔记

任务背景与目标

作为刚入职三个月的新人,近期接到核心任务:开发支持2G级文件批量上传功能。需实现以下核心目标:

  1. 兼容Chrome/Firefox/Edge/IE11主流浏览器
  2. 前端基于Vue2.6+Axios框架
  3. 后端PHP7.4+MySQL5.7环境
  4. 实现分片上传、断点续传、进度可视化
  5. 提供完整技术文档与源码

技术调研与方案制定

关键技术选型

前端框架
Vue2.6.14
Axios 0.21.1
后端技术
PHP7.4.3
Workerman 4.0
存储方案
本地存储
七牛云对象存储
数据库
MySQL5.7
Redis 5.0

核心功能模块

  1. 前端模块

    • 文件分片算法(5MB/片)
    • MD5校验机制
    • 进度条动态更新
    • 暂停/继续控制
  2. 后端模块

    • 分片接收接口
    • 临时文件存储
    • 合并校验服务
    • 上传记录表设计

开发环境搭建

前端环境配置

# 创建Vue项目
vue create file-uploader
cd file-uploader
npm install axios spark-md5 --save

# 配置Webpack
module.exports = {
    performance: {
        maxAssetSize: 1024 * 1024 * 10, // 10MB
        maxEntrypointSize: 1024 * 1024 * 10
    }
}

后端环境配置

-- 创建上传记录表
CREATE TABLE upload_tasks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    file_md5 VARCHAR(32) NOT NULL,
    total_chunks INT NOT NULL,
    finished_chunks INT DEFAULT 0,
    status ENUM('uploading','completed','failed') DEFAULT 'uploading',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

核心功能实现

前端分片上传组件




import SparkMD5 from 'spark-md5'

export default {
  data() {
    return {
      progress: 0,
      isPaused: false,
      currentUploads: new Map()
    }
  },
  methods: {
    async handleFileSelect(e) {
      const files = Array.from(e.target.files)
      files.forEach(file => this.processFile(file))
    },
    async processFile(file) {
      // 计算文件MD5
      const fileMD5 = await this.calculateMD5(file)
      
      // 初始化上传任务
      this.currentUploads.set(fileMD5, {
        chunks: this.splitFile(file),
        finished: 0
      })
      
      // 开始上传
      this.uploadChunks(fileMD5)
    },
    splitFile(file) {
      const chunks = []
      const chunkSize = 5 * 1024 * 1024 // 5MB
      const totalChunks = Math.ceil(file.size / chunkSize)
      
      for(let i=0; i<totalChunks; i++) {
        chunks.push({
          index: i,
          blob: file.slice(i*chunkSize, (i+1)*chunkSize)
        })
      }
      return chunks
    },
    async uploadChunks(fileMD5) {
      const task = this.currentUploads.get(fileMD5)
      
      for(let chunk of task.chunks) {
        if(this.isPaused) break
        
        const formData = new FormData()
        formData.append('chunk', chunk.blob)
        formData.append('index', chunk.index)
        formData.append('total', task.chunks.length)
        formData.append('md5', fileMD5)
        
        try {
          await axios.post('/api/upload', formData, {
            onUploadProgress: (e) => {
              chunk.progress = e.loaded
              this.updateProgress(fileMD5)
            }
          })
          task.finished++
        } catch (error) {
          // 上传失败重试机制
          chunk.retries = chunk.retries || 0
          if(chunk.retries < 3) {
            chunk.retries++
            await this.sleep(1000)
            this.uploadChunks(fileMD5)
          }
        }
      }
      
      if(!this.isPaused && task.finished === task.chunks.length) {
        this.mergeFile(fileMD5)
      }
    },
    calculateMD5(file) {
      return new Promise((resolve) => {
        const spark = new SparkMD5.ArrayBuffer()
        const fileReader = new FileReader()
        
        fileReader.onload = (e) => {
          spark.append(e.target.result)
          resolve(spark.end())
        }
        
        fileReader.readAsArrayBuffer(file)
      })
    }
  }
}

PHP后端处理逻辑

prepare("UPDATE upload_tasks SET finished_chunks = ? WHERE file_md5 = ?");
$stmt->bind_param("is", $index, $md5);
$stmt->execute();

// 合并文件(当最后一片上传时)
if($index == $total - 1) {
    $finalPath = "uploads/$md5";
    $file = fopen($finalPath, 'w');
    
    for($i=0; $i<$total; $i++) {
        $chunkPath = "temp/$md5-$i";
        fwrite($file, file_get_contents($chunkPath));
        unlink($chunkPath); // 删除临时分片
    }
    
    fclose($file);
    
    // 更新任务状态
    $stmt = $mysql->prepare("UPDATE upload_tasks SET status = 'completed' WHERE file_md5 = ?");
    $stmt->bind_param("s", $md5);
    $stmt->execute();
}

测试验证与优化

测试用例设计

测试场景预期结果测试方法
2G文件上传5分钟内完成模拟2G测试文件上传
断点续传中断后能继续上传手动终止后重新上传
多文件同时上传互不影响完成上传同时选择5个2G文件上传
IE11兼容性进度条正常显示在IE11虚拟机中测试

性能优化方案

  1. 内存优化
// PHP内存限制设置
ini_set('memory_limit', '512M');
  1. 并发控制
// 前端并发上传限制
class UploadQueue {
    constructor(maxConcurrent = 3) {
        this.queue = []
        this.maxConcurrent = maxConcurrent
        this.running = 0
    }
    
    async add(task) {
        this.queue.push(task)
        await this.process()
    }
    
    async process() {
        while(this.queue.length > 0 && this.running < this.maxConcurrent) {
            const task = this.queue.shift()
            this.running++
            await task()
            this.running--
        }
    }
}

学习资源与支持

  1. 推荐学习资料

  2. 技术支持渠道

    • QQ群:374992201(每日19:00-22:00在线答疑)
    • GitHub仓库:file-uploader(持续更新)
    • 邮件支持:support@yourdomain.com(24小时内响应)

通过本次开发实践,系统掌握了以下关键技能:

  • 大文件分片上传算法设计
  • 前后端交互优化策略
  • 浏览器兼容性处理技巧
  • 分布式上传系统架构思维

欢迎各位同行加入技术交流,共同解决开发中的疑难问题!

安装环境

PHP:7.2.14
Alt

调整块大小

Alt

NOSQL

NOSQL不需要任何配置,可以直接访问测试
Alt

SQL

创建数据库

您可以直接复制脚本进行创建
Alt
Alt

配置数据库连接

Alt

安装依赖

Alt

访问页面进行测试

Alt

数据表中的数据

Alt

效果预览

文件上传

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件续传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
文件夹上传

批量下载

支持文件批量下载
批量下载

下载续传

文件下载支持离线保存进度信息,刷新页面,关闭页面,重启系统均不会丢失进度信息。
下载续传

文件夹下载

支持下载文件夹,并保留层级结构,不打包,不占用服务器资源。
文件夹下载

免费下载示例

点击下载完整示例

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐