JSP如何解决金融行业的大文件分块上传问题?

程序猿の毕业设计渡劫指南(附代码+求生攻略)

一、项目背景(哭唧唧版)

作为一只即将被学校"扫地出门"的计科狗,最近被毕业设计折磨得夜不能寐——导师甩下一句:“做个文件管理系统,要支持10G大文件上传+文件夹+断点续传+IE8兼容,否则别想毕业!”(说完就骑着共享单车溜了)

现在的情况是:

  • 前端:Vue3 + 原生JS(要兼容到IE8,比让恐龙玩Switch还离谱)
  • 后端:SpringBoot(跑在Tomcat 6.0上,比我家老古董电视还卡)
  • 存储:直接塞服务器硬盘(导师说"云存储太贵,咱们穷苦大学生要艰苦奋斗")
  • 浏览器:从IE8到Chrome120全兼容(包括龙芯浏览器这种"国产神器")
二、技术选型(妥协版)
  1. 上传组件

    • 现代浏览器:H5 File API + Web Workers(防卡死)
    • IE8:WebUploader + Flash(没错,2023年还要写Flash代码)
    • 最终方案:双引擎热切换(像混动车一样智能)
  2. 加密方案

    • 传输加密:AES-128(IE8用CryptoJS降级)
    • 存储加密:SM4(信创浏览器强制要求)
    • 密钥管理:用户密码+时间戳盐值(防止导师偷看我学习资料)
  3. 断点续传

    • 进度存储:localStorage(IE8用userData模拟)
    • 服务端记录:MySQL+文件系统双保险(防止浏览器清空数据)
三、前端实现(Vue3 + 原生JS)
// FileUploader.vue(精简求生版)



export default {
  data() {
    return {
      progress: 0,
      isIE8: !!window.ActiveXObject || "ActiveXObject" in window,
      chunkSize: 5 * 1024 * 1024 // 5MB分片
    }
  },
  methods: {
    triggerUpload() {
      if (this.isIE8) {
        // 触发IE8上传(通过iframe通信)
        document.getElementById('ie8Uploader').contentWindow.startUpload();
      } else {
        // 现代浏览器直接触发
        document.getElementById('modernFileInput').click();
      }
    },

    // 现代浏览器文件处理
    handleModernFileChange(e) {
      const files = e.target.files;
      Array.from(files).forEach(file => {
        if (file.webkitRelativePath) {
          this.uploadFolder(file); // 文件夹上传
        } else {
          this.uploadFile(file);
        }
      });
    },

    // 文件分片上传(现代浏览器)
    async uploadFile(file) {
      const fileId = this.generateFileId(file);
      const totalChunks = Math.ceil(file.size / this.chunkSize);
      let uploadedChunks = this.getSavedProgress(fileId) || 0;

      for (let i = uploadedChunks; i < totalChunks; i++) {
        const start = i * this.chunkSize;
        const end = Math.min(start + this.chunkSize, file.size);
        const chunk = file.slice(start, end);
        
        // 加密分片(简化版)
        const encrypted = this.encryptData(chunk);
        
        const formData = new FormData();
        formData.append('file', encrypted);
        formData.append('fileId', fileId);
        formData.append('chunkIndex', i);
        formData.append('totalChunks', totalChunks);

        try {
          await fetch('/api/upload/chunk', {
            method: 'POST',
            body: formData
          });
          
          this.progress = Math.floor(((i + 1) / totalChunks) * 100);
          this.saveProgress(fileId, i + 1);
        } catch (e) {
          console.error('上传失败:', e);
          break;
        }
      }
    },

    // IE8兼容方案(伪代码)
    uploadFileIE8(file) {
      // 这里需要写100行ActiveXObject代码...
      // 核心思路:通过XMLHttpRequest分片上传
      // 省略IE8兼容代码(手酸了)...
    },

    // 生成唯一文件ID(兼容IE8)
    generateFileId(file) {
      if (this.isIE8) {
        return file.name + file.size + file.lastModified; // 简单拼接
      }
      return file.name + '-' + CryptoJS.MD5(file.name + file.size).toString();
    }
  }
}

四、后端实现(SpringBoot + Tomcat 6.0)
// FileUploadController.java(求生版)
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {

    @Value("${file.upload-dir}")
    private String uploadDir;

    // 初始化上传任务
    @PostMapping("/init")
    public ResponseEntity> initUpload(
            @RequestParam String fileName,
            @RequestParam long fileSize) {

        String fileId = UUID.randomUUID().toString();
        String tempDir = uploadDir + "/temp/" + fileId;
        
        // 创建临时目录(Tomcat 6.0需要手动处理路径)
        new File(tempDir).mkdirs();

        Map response = new HashMap<>();
        response.put("fileId", fileId);
        response.put("chunkSize", 5242880); // 5MB
        response.put("uploadDir", tempDir);

        return ResponseEntity.ok(response);
    }

    // 处理分片上传
    @PostMapping("/chunk")
    public ResponseEntity uploadChunk(
            @RequestParam("file") MultipartFile file,
            @RequestParam String fileId,
            @RequestParam int chunkIndex,
            @RequestParam int totalChunks) {

        try {
            String tempDir = uploadDir + "/temp/" + fileId;
            String chunkPath = tempDir + "/" + chunkIndex;

            // 解密文件(简化版)
            byte[] decrypted = decrypt(file.getBytes());

            // 保存分片(Tomcat 6.0文件操作)
            Files.write(Paths.get(chunkPath), decrypted);

            // 如果是最后一个分片,合并文件
            if (chunkIndex == totalChunks - 1) {
                mergeFile(fileId, tempDir);
            }

            return ResponseEntity.ok().build();
        } catch (Exception e) {
            return ResponseEntity.status(500).build();
        }
    }

    // 合并文件(Tomcat 6.0兼容版)
    private void mergeFile(String fileId, String tempDir) throws IOException {
        File[] chunks = new File(tempDir).listFiles();
        if (chunks == null || chunks.length == 0) return;

        // 按分片序号排序
        Arrays.sort(chunks, Comparator.comparingInt(f -> {
            String name = f.getName();
            return Integer.parseInt(name);
        }));

        // 读取原始文件名(从第一个分片中获取)
        String fileName = "unknown"; // 实际应从数据库或第一个分片获取
        String outputPath = uploadDir + "/" + fileName;

        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            for (File chunk : chunks) {
                Files.copy(chunk.toPath(), fos);
                chunk.delete(); // 删除分片
            }
        }

        // 删除临时目录
        new File(tempDir).delete();
    }
}
五、IE8兼容血泪史(附解决方案)
  1. 文件夹上传

    • 现代浏览器:``
    • IE8:需要用ActiveXObject遍历文件系统(需要降低安全级别)
    // IE8文件夹遍历(伪代码)
    function browseFolderIE8(path) {
      try {
        const fso = new ActiveXObject("Scripting.FileSystemObject");
        const folder = fso.GetFolder(path);
        const files = new Enumerator(folder.files);
        
        for (; !files.atEnd(); files.moveNext()) {
          const file = files.item();
          // 递归处理子文件夹...
        }
      } catch (e) {
        alert("请降低IE安全级别!");
      }
    }
    
  2. localStorage替代方案

    // IE8的userData存储(古老的黑科技)
    function saveToUserData(key, value) {
      const storage = document.createElement('div');
      storage.id = 'ie8Storage';
      storage.style.behavior = 'url(#default#userData)';
      document.body.appendChild(storage);
      
      storage.setAttribute(key, value);
      storage.save('fileUploadProgress');
      document.body.removeChild(storage);
    }
    
  3. XMLHttpRequest降级

    // IE8的XHR实现
    function createIE8XHR() {
      try {
        return new ActiveXObject("Msxml2.XMLHTTP");
      } catch (e) {
        try {
          return new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {
          alert("您的浏览器太古老了,请升级!");
          return null;
        }
      }
    }
    
六、项目亮点(吹牛版)
  1. 全浏览器兼容:从IE8到Chrome120无缝支持(连龙芯浏览器都能跑)
  2. 军工级加密:AES+SM4双算法支持(导师看了都直呼专业)
  3. 企业级稳定性:MySQL+文件系统双进度存储(就算服务器爆炸也不丢数据)
  4. 信创环境适配:已通过龙芯浏览器测试(学校机房的国产CPU终于有用武之地了)
七、求职彩蛋

现在加入我的"毕业设计互助群"(QQ:374992201),即可获得:

  1. 完整项目源码(含IE8兼容补丁)
  2. 简历优化指导(群主亲授"如何把毕业设计吹成国家级项目")
  3. 内推机会(群内有多家IT公司HR潜伏)

特别提示:现在加群还能参与"找bug换红包"活动,每发现一个兼容性问题奖励5-20元(导师说这叫"众测")!

(最后小声说:这个项目还没完全跑通,但吹牛的功夫已经练得炉火纯青了…)

导入项目

导入到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、付费专栏及课程。

余额充值