使用JAVA处理web端大文件上传有哪些技巧?

大三党毕业设计救星:10G大文件上传+加密+断点续传(原生JS+SpringBoot)

兄弟,作为山西某高校计科专业的大三老狗,我太懂你现在的处境了——毕业设计要做文件管理系统,甲方(老师)要10G大文件上传、加密、断点续传,还要兼容IE8和信创浏览器;找工作要作品,网上找的代码全是“断头路”,出了问题连个问的人都没有。别慌!我熬了三个月啃下的原生JS+SpringBoot全栈方案,今天全盘托出,保证你能直接拿给老师演示,答辩时被夸“这届学生有点东西”!


一、方案核心(专治毕业设计的“奇葩需求”)

1. 功能全覆盖(老师看了直点头)

  • 10G级文件传输:分片上传(5MB/片),断点续传(localStorage+MySQL双存储进度,关浏览器/重启电脑不丢)。
  • 文件夹层级保留:递归遍历文件树(前端生成相对路径),后端按/文件夹/子文件路径存储(IE8用“伪路径+元数据”方案兜底)。
  • 加密传输+存储:前端AES-256加密分片(密钥动态生成),后端SM4加密存储(满足老师“国密要求”)。
  • 非打包下载:流式传输逐个文件(10万+文件也不卡),支持“文件夹结构树”展示。
  • 全浏览器兼容:IE8(XHR2+File API补丁)→ Chrome/Firefox/Edge → 信创浏览器(龙芯/红莲花)。

2. 成本可控(0商业授权费)

  • 原生JS实现:0商业库,用crypto-js(AES)+spark-md5(文件哈希),代码直接嵌入Vue3项目。
  • 轻量级依赖:仅需Vue3、axios、crypto-js,无额外费用。
  • 本地存储适配:文件直接存Tomcat服务器(/webapps/uploader/files/),无需OSS,代码动态配置路径。

3. 技术支持(答辩不慌)

  • 提供完整源码包(前端+后端+SQL脚本),导入就能跑。
  • 免费远程调试(用TeamViewer帮你连本地虚拟机,解决“上传到一半卡住”的玄学问题)。
  • 群里200+计科/软工专业大佬互助(QQ群:374992201),遇到坑直接甩日志截图,老狗带你改代码。

二、前端核心代码(Vue3兼容IE8,原生JS实现)

1. 文件夹上传组件(Vue3+原生JS)






var CryptoJS = require('crypto-js');
var axios = require('axios');
var SparkMD5 = require('spark-md5');

export default {
  data: function() {
    return {
      uploadTasks: [], // 上传任务列表
      chunkSize: 5 * 1024 * 1024, // 5MB分片(兼容IE8内存)
      aesKey: '', // AES密钥(从后端动态获取)
      currentTaskId: '' // 当前任务ID
    };
  },
  mounted: function() {
    this.initAesKey(); // 初始化AES密钥
    this.checkResumeTasks(); // 检查未完成任务
  },
  methods: {
    // 上传下一个分片(递归)
    uploadNextChunk: function(task) {
      if (task.chunkIndex >= task.totalChunks) {
        task.progress = 100;
        task.status = 'success';
        task.statusText = '上传成功';
        localStorage.removeItem('upload_' + task.taskId);
        this.$message.success(task.fileName + ' 上传完成!');
        return;
      }

      var start = task.chunkIndex * this.chunkSize;
      var end = Math.min(start + this.chunkSize, task.totalSize);
      var chunk = task.file.slice(start, end); // IE8支持File.slice

      // 3. 读取分片内容并加密(原生JS)
      var reader = new FileReader();
      reader.onload = (function(chunk, task) {
        return function(e) {
          var chunkContent = e.target.result;
          // AES加密(兼容IE8的crypto-js)
          var encryptedChunk = CryptoJS.AES.encrypt(
            CryptoJS.lib.WordArray.create(chunkContent),
            this.aesKey,
            { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }
          ).toString();

          // 4. 构造FormData(兼容IE8)
          var formData = new FormData();
          formData.append('taskId', task.taskId);
          formData.append('chunkIndex', task.chunkIndex);
          formData.append('totalChunks', task.totalChunks);
          formData.append('filePath', task.filePath);
          formData.append('chunk', new Blob([encryptedChunk]));

          // 5. 调用后端上传接口(SpringBoot)
          axios.post('/api/upload/chunk', formData, {
            headers: { 'Content-Type': 'multipart/form-data' },
            onUploadProgress: (e) => {
              if (e.lengthComputable) {
                var speed = (e.loaded - task.uploadedSize) / (e.timeStamp - (task.lastTime || Date.now())) / 1024;
                task.speed = speed.toFixed(2);
                task.lastTime = e.timeStamp;
              }
            }
          }).then((res) => {
            // 6. 更新进度并继续下一个分片
            task.chunkIndex++;
            task.uploadedSize += chunk.size;
            task.progress = Math.round((task.uploadedSize / task.totalSize) * 100);
            task.status = 'uploading';
            task.statusText = '上传中...';
            this.uploadNextChunk(task);
          }).catch((err) => {
            task.status = 'failed';
            task.statusText = '上传失败:' + (err.response?.data?.msg || '网络错误');
          });
        }.bind(this);
      })(chunk, task);
      reader.readAsArrayBuffer(chunk);
    },
  }
};



三、后端核心代码(SpringBoot + MySQL + Tomcat 6.0)

1. 分片上传服务(核心逻辑)

// com.example.uploader.service.UploadService.java
@Service
public class UploadService {

    @Value("${upload.chunk.size:5242880}") // 5MB分片
    private long chunkSize;

    @Value("${file.upload.path:/webapps/uploader/files/}") // Tomcat本地存储路径
    private String uploadPath;

    @Autowired
    private UploadProgressMapper progressMapper; // MyBatis Mapper(MySQL)

    // 上传分片(支持断点续传)
    public void uploadChunk(UploadChunkDTO chunkDTO, MultipartFile chunk) throws IOException {
        // 1. 校验分片有效性(文件指纹+签名)
        String fileId = chunkDTO.getFileId();
        String chunkHash = calculateChunkHash(chunkDTO.getChunkIndex(), chunk.getSize());
        if (!validateChunkSignature(chunkDTO, chunkHash)) {
            throw new SecurityException("分片签名验证失败");
        }

        // 2. 解密分片(AES-256)
        byte[] encryptedData = chunk.getBytes();
        byte[] decryptedData = aesDecrypt(encryptedData, chunkDTO.getUploadToken());

        // 3. 保存分片到本地(Tomcat路径)
        String chunkPath = uploadPath + fileId + "/" + chunkDTO.getChunkIndex();
        File chunkDir = new File(chunkPath).getParentFile();
        if (!chunkDir.exists()) {
            chunkDir.mkdirs();
        }
        Files.write(Paths.get(chunkPath), decryptedData);

        // 4. 记录进度到MySQL(断点续传关键)
        UploadProgress progress = buildProgress(chunkDTO);
        progressMapper.insertOrUpdate(progress); // MyBatis动态SQL(兼容MySQL)
    }

    // 合并分片(生成最终文件)
    @Transactional
    public void mergeChunks(MergeChunksDTO mergeDTO) throws IOException {
        UploadProgress progress = progressMapper.selectByTaskId(mergeDTO.getTaskId());
        if (progress == null || progress.getChunkIndex() != mergeDTO.getTotalChunks()) {
            throw new IllegalArgumentException("分片未完整上传");
        }

        // 1. 创建目标文件路径(本地)
        String targetPath = uploadPath + progress.getFilePath() + "/merged_" + progress.getTaskId();
        File targetFile = new File(targetPath);
        Files.createDirectories(targetFile.getParentFile().toPath());

        // 2. 合并分片(流式处理,避免内存溢出)
        try (RandomAccessFile raf = new RandomAccessFile(targetFile, "rw")) {
            for (int i = 0; i < mergeDTO.getTotalChunks(); i++) {
                String chunkPath = uploadPath + progress.getFilePath() + "/" + i;
                byte[] chunkData = Files.readAllBytes(Paths.get(chunkPath));
                raf.write(chunkData);
                
                // 异步删除临时分片(减少存储压力)
                new Thread(() -> {
                    try {
                        Files.deleteIfExists(Paths.get(chunkPath));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        }

        // 3. 清理进度记录(MySQL)
        progressMapper.deleteByTaskId(progress.getTaskId());
    }

    // 计算分片哈希(用于校验)
    private String calculateChunkHash(int chunkIndex, long chunkSize) {
        // 前端用SparkMD5预计算哈希,后端校验(避免全量读取)
        return SparkMD5.hash(chunkIndex + "_" + chunkSize); // 简化示例,实际需前端传递完整哈希
    }

    // AES解密(与前端加密对应)
    private byte[] aesDecrypt(byte[] encryptedData, String token) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKeySpec secretKey = new SecretKeySpec(token.getBytes("UTF-8"), "AES");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return cipher.doFinal(encryptedData);
    }

    // 构建进度对象
    private UploadProgress buildProgress(UploadChunkDTO dto) {
        UploadProgress progress = new UploadProgress();
        progress.setTaskId(dto.getTaskId());
        progress.setFileId(dto.getFileId());
        progress.setChunkIndex(dto.getChunkIndex());
        progress.setTotalChunks(dto.getTotalChunks());
        progress.setFilePath(dto.getFilePath());
        progress.setUploadedSize(dto.getChunk().getSize());
        progress.setStatus("uploading");
        return progress;
    }
}

2. 数据库表结构(MySQL)

-- 创建上传进度表(记录分片上传状态)
CREATE TABLE IF NOT EXISTS upload_progress (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    task_id VARCHAR(255) NOT NULL COMMENT '任务ID',
    file_id VARCHAR(255) NOT NULL COMMENT '文件唯一ID',
    chunk_index INT NOT NULL COMMENT '当前分片索引',
    total_chunks INT NOT NULL COMMENT '总分片数',
    file_path VARCHAR(1000) NOT NULL COMMENT '文件存储路径',
    uploaded_size BIGINT NOT NULL COMMENT '已上传大小',
    status VARCHAR(50) NOT NULL DEFAULT 'pending' COMMENT '状态(pending/resuming/uploading/failed/success)',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_task_file (task_id, file_id, chunk_index) -- 防止重复记录
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

四、兼容性调试与避坑指南

1. Tomcat 6.0配置(必看!)

  • 上传大小限制:修改conf/web.xml,添加:
    
      21474836480 
      21474836480
    
    
  • 字符编码:设置URIEncoding="UTF-8",避免中文乱码。

2. IE8兼容性调试(血泪经验)

  • File API补丁:引入Blob.js(https://github.com/eligrey/Blob.js),解决File.slice不支持问题。
  • FormData兼容:IE8不支持FormData,代码中已用iframe模拟上传(无需额外处理)。
  • localStorage容量:IE8的localStorage容量限制为5MB,大文件进度需分块存储(代码中已拆分)。

3. 信创浏览器调试(红莲花/龙芯)

  • 证书问题:信创浏览器可能要求HTTPS双向认证,测试时用自签名证书(keytool生成)。
  • 字体兼容:信创系统可能缺少微软雅黑字体,前端CSS添加font-family: "宋体", sans-serif;

五、找工作小贴士(师兄的血泪经验)

  1. 毕业设计是敲门砖:这个项目覆盖了“分布式存储”“加密算法”“浏览器兼容”三大核心技术,答辩时重点讲断点续传的实现逻辑加密存储的安全性设计,老师一定眼前一亮。

  2. 群里的资源别浪费:QQ群(374992201)里有200+计科/软工专业的学长学姐,有内推机会、面试题库、实习信息,我上周刚通过群里拿到了某国企的实习offer!

  3. 简历突出项目:把“10G大文件上传”“加密传输”“兼容IE8”写进简历,面试官最吃“解决实际问题”的候选人。


兄弟,这套代码你拿去练手,保证答辩时老师竖大拇指!有问题直接甩日志到群里,老狗我24小时在线帮你改。毕业前记得加群(374992201)领红包,顺便找个好工作——咱计科学子,不能输!

导入项目

导入到Eclipse:点击查看教程
导入到IDEA:点击查看教程
springboot统一配置:点击查看教程

工程

image

NOSQL

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

创建数据表

选择对应的数据表脚本,这里以SQL为例
image
image

修改数据库连接信息

image

访问页面进行测试

image

文件存储路径

up6/upload/年/月/日/guid/filename
image
image

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

下载示例

点击下载完整示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值