开发者日记:毕业设计大文件管理系统开发实录
日期:2023年11月20日
天气:多云转晴

作为一名即将毕业的软件工程学生,我的毕业设计选题是基于WebUploader的大文件管理系统,要求支持20GB文件传输、文件夹层级结构保留,并兼容IE8及国产信创浏览器(如龙芯浏览器、红莲花浏览器)。经过两周的技术攻关,现将关键实现细节和代码片段整理如下,供同样面临毕业设计挑战的同学参考。


一、技术选型与兼容性方案

  1. 前端框架

    • Vue3 CLI + WebUploader(主方案) + HTML5 File API(备选)
    • 浏览器兼容性
      • IE8:使用webuploader.flashonly.js + @babel/polyfill
      • 国产浏览器:测试发现红莲花浏览器需禁用directory属性,改用递归上传
  2. 后端架构

    • PHP 8.0 + 阿里云OSS SDK
    • 分片上传:固定5MB分片,支持断点续传
    • MD5秒传:通过文件哈希去重
  3. 存储方案

    • 阿里云OSS存储文件本体
    • MySQL记录元数据(文件路径、分片状态、用户权限)

二、核心代码实现

1. 前端兼容性处理(Vue3组件)
// src/components/FileUploader.vue
import WebUploader from 'webuploader';
import { isIE8, isLoongBrowser } from '@/utils/browserDetect';

export default {
  mounted() {
    const uploaderConfig = {
      swf: '/static/Uploader.swf', // IE8必需
      server: '/api/upload',
      pick: '#picker',
      accept: { title: 'All', extensions: '*' },
      compress: false,
      chunked: true,
      chunkSize: 5 * 1024 * 1024, // 5MB分片
      threads: 3,
      formData: { token: localStorage.getItem('upload_token') }
    };

    // 国产浏览器降级方案
    if (isLoongBrowser() || isIE8()) {
      uploaderConfig.directory = false; // 禁用文件夹拖拽
      uploaderConfig.dnd = false; // 禁用HTML5拖放
    }

    const uploader = WebUploader.create(uploaderConfig);

    // 文件夹递归上传(非IE8环境)
    if (!isIE8()) {
      uploader.on('beforeFileQueued', (file) => {
        if (file.relativePath) {
          this.uploadFolder(file);
          return false; // 阻止默认队列行为
        }
      });
    }

    // 分片上传进度
    uploader.on('uploadProgress', (file, percentage) => {
      this.$emit('progress', { name: file.name, progress: percentage });
    });
  },
  methods: {
    async uploadFolder(rootFile) {
      // 递归解析文件夹结构(需配合HTML5 File API)
      const entries = await this.readDirectory(rootFile);
      entries.forEach(entry => {
        if (entry.isFile) {
          this.uploadSingleFile(entry, rootFile.relativePath);
        }
      });
    }
  }
};
2. 后端分片处理(PHP)
// api/upload.php
 'your-aliyun-key',
    'accessKeySecret' => 'your-aliyun-secret',
    'endpoint' => 'oss-cn-hangzhou.aliyuncs.com',
    'bucket' => 'your-bucket-name'
];

// 处理分片上传
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $chunk = $_POST['chunk'] ?? 0;
    $chunks = $_POST['chunks'] ?? 1;
    $md5 = $_POST['md5'] ?? '';
    $relativePath = $_POST['relativePath'] ?? '';
    $userId = $_SESSION['user_id'] ?? 0;

    // 存储临时分片
    $chunkPath = "/tmp/chunks/{$md5}_{$chunk}";
    move_uploaded_file($_FILES['file']['tmp_name'], $chunkPath);

    // 记录分片状态到MySQL
    $pdo = new PDO('mysql:host=localhost;dbname=file_manager', 'user', 'password');
    $stmt = $pdo->prepare("REPLACE INTO file_chunks (md5, chunk_index, user_id) VALUES (?, ?, ?)");
    $stmt->execute([$md5, $chunk, $userId]);

    // 合并分片(最后一个分片)
    if ($chunk == $chunks - 1) {
        $ossPath = "users/{$userId}/{$relativePath}" . basename($_POST['name']);
        $this->mergeChunks($md5, $chunks, $ossPath, $config);
        
        // 记录完整文件信息
        $fileStmt = $pdo->prepare("INSERT INTO files (name, path, md5, size, user_id) VALUES (?, ?, ?, ?, ?)");
        $fileStmt->execute([
            $_POST['name'],
            $ossPath,
            $md5,
            $_POST['size'],
            $userId
        ]);

        echo json_encode(['status' => 'completed', 'path' => $ossPath]);
    } else {
        echo json_encode(['status' => 'pending']);
    }
}

function mergeChunks($md5, $chunks, $ossPath, $config) {
    $ossClient = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']);
    $tempFile = "/tmp/{$md5}_merged";

    // 合并本地分片
    for ($i = 0; $i < $chunks; $i++) {
        file_put_contents($tempFile, file_get_contents("/tmp/chunks/{$md5}_{$i}"), FILE_APPEND);
    }

    // 上传至阿里云OSS
    $ossClient->uploadFile($config['bucket'], $ossPath, $tempFile);

    // 清理临时文件
    unlink($tempFile);
    for ($i = 0; $i < $chunks; $i++) unlink("/tmp/chunks/{$md5}_{$i}");
}
3. MySQL表设计(关键表)
-- 文件分片状态表
CREATE TABLE `file_chunks` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `md5` CHAR(32) NOT NULL COMMENT '文件MD5',
  `chunk_index` INT NOT NULL COMMENT '分片序号',
  `user_id` INT NOT NULL COMMENT '所属用户',
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY `md5_chunk` (`md5`, `chunk_index`)
);

-- 文件元数据表
CREATE TABLE `files` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(255) NOT NULL COMMENT '文件名',
  `path` VARCHAR(512) NOT NULL COMMENT 'OSS存储路径',
  `md5` CHAR(32) NOT NULL COMMENT '文件哈希',
  `size` BIGINT NOT NULL COMMENT '文件大小',
  `user_id` INT NOT NULL COMMENT '上传用户',
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

三、关键问题与解决方案

  1. IE8文件夹上传

    • 方案:禁用directory属性,改用Flash版WebUploader的普通文件选择
    • 代码:通过``实现多选文件
  2. 红莲花浏览器兼容性

    • 问题:拖拽上传会触发浏览器崩溃
    • 解决:检测到红莲花浏览器时隐藏拖拽区域
  3. 20GB文件内存优化

    • 前端:始终使用FileReader.readAsArrayBuffer分片读取
    • 后端:PHP配置upload_max_filesize = 0(由分片逻辑接管)

四、毕业设计特色功能

  1. 信创浏览器适配

    • 通过User-Agent自动切换兼容模式
    // utils/browserDetect.js
    export function isLoongBrowser() {
      return navigator.userAgent.includes('LoongBrowser');
    }
    
  2. 管理员监控面板

    • 基于Vue3 + ECharts实现上传速度实时图表
  3. 安全增强

    • 文件下载需携带JWT令牌
    • 阿里云OSS配置Bucket Policy限制访问

寻求帮助:目前国产浏览器(如奇安信)在文件夹上传时存在路径解析异常,欢迎加入QQ群 374992201 指导调试!

今日进度:完成基础分片上传,明日将攻克信创浏览器兼容性问题。


(签名)
浙江某高校 · 软件工程1901班 · 张明
2023年11月20日 夜


附:项目地址
GitHub(待开源):https://github.com/graduation-project/file-manager
演示视频:项目根目录/demo/文件夹内

致谢:感谢群里王工提供的国产浏览器测试环境,以及李学长分享的阿里云OSS断点续传方案!

安装环境

PHP:7.2.14
Alt

调整块大小

Alt

NOSQL

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

SQL

创建数据库

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

配置数据库连接

Alt

安装依赖

Alt

访问页面进行测试

Alt

数据表中的数据

Alt

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

免费下载示例

点击下载完整示例

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐