政府招投标项目大文件传输解决方案设计与实现

项目背景与需求分析

作为本次政府招投标项目的开发负责人,我负责解决100G级大文件传输的技术难题。项目要求支持包括IE8在内的所有主流浏览器,同时必须适配信创国产化环境(统信UOS、中标麒麟、银河麒麟等操作系统),后端采用SpringBoot框架,数据库需兼容达梦和人大金仓等国产数据库,前端使用Vue2-cli框架。

现有方案问题分析

原方案采用百度开源的WebUploader组件,存在以下问题:

  1. 不支持文件夹下载功能
  2. 无法稳定传输100G级超大文件
  3. 缺乏端到端加密传输能力
  4. 不支持国密SM4算法
  5. 缺乏官方技术支持,问题解决困难

新方案选型与技术选型

核心要求

  • 浏览器兼容性:IE8+及现代浏览器
  • 国产化支持:全面适配信创环境
  • 安全要求:支持SM4国密算法加密传输
  • 性能要求:稳定传输100G+文件
  • 功能要求:支持文件夹上传/下载、断点续传、进度显示

技术选型

  • 前端框架:Vue2 + Element UI(兼容IE8需额外配置)
  • 文件传输:基于WebSocket+分片传输的自定义实现
  • 加密算法:引入GMSSL库实现SM4加密
  • 后端框架:SpringBoot + Netty(处理大文件传输)

前端实现方案

1. 项目初始化与IE8兼容配置

// vue.config.js
module.exports = {
  transpileDependencies: ['element-ui'],
  configureWebpack: {
    resolve: {
      alias: {
        'vue$': 'vue/dist/vue.esm.js' // 确保使用完整版Vue
      }
    }
  }
}

2. 大文件上传组件实现




import { encryptSM4 } from '@/utils/sm4'; // SM4加密工具函数

export default {
  data() {
    return {
      fileInfo: null,
      uploading: false,
      uploadProgress: 0,
      chunkSize: 5 * 1024 * 1024, // 5MB分片
      ws: null
    };
  },
  methods: {
    handleFileChange(file) {
      this.fileInfo = file.raw;
    },
    
    formatFileSize(bytes) {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    },
    
    async startUpload() {
      if (!this.fileInfo) return;
      
      this.uploading = true;
      this.uploadProgress = 0;
      
      try {
        // 1. 初始化WebSocket连接
        this.ws = new WebSocket('ws://your-server-address/upload');
        
        // 2. 准备文件元数据
        const fileMeta = {
          fileName: this.fileInfo.name,
          fileSize: this.fileInfo.size,
          chunkSize: this.chunkSize,
          totalChunks: Math.ceil(this.fileInfo.size / this.chunkSize),
          fileType: this.fileInfo.type,
          lastModified: this.fileInfo.lastModified
        };
        
        // 3. 发送初始化请求(包含SM4加密的元数据)
        const encryptedMeta = encryptSM4(JSON.stringify(fileMeta), 'your-sm4-key');
        this.ws.send(JSON.stringify({
          type: 'INIT',
          data: encryptedMeta
        }));
        
        // 4. 分片上传
        await this.uploadChunks();
        
      } catch (error) {
        console.error('上传失败:', error);
        this.$message.error('文件上传失败');
      } finally {
        this.uploading = false;
        if (this.ws) this.ws.close();
      }
    },
    
    async uploadChunks() {
      return new Promise((resolve) => {
        let currentChunk = 0;
        const totalChunks = Math.ceil(this.fileInfo.size / this.chunkSize);
        
        this.ws.onmessage = (event) => {
          const response = JSON.parse(event.data);
          
          if (response.type === 'CHUNK_ACK') {
            currentChunk++;
            this.uploadProgress = Math.round((currentChunk / totalChunks) * 100);
            
            if (currentChunk >= totalChunks) {
              this.$message.success('文件上传完成');
              resolve();
              return;
            }
            
            // 读取下一个分片
            const start = currentChunk * this.chunkSize;
            const end = Math.min(start + this.chunkSize, this.fileInfo.size);
            const chunk = this.fileInfo.slice(start, end);
            
            // 创建FileReader读取分片内容
            const reader = new FileReader();
            reader.onload = (e) => {
              const encryptedChunk = encryptSM4(e.target.result, 'your-sm4-key');
              this.ws.send(JSON.stringify({
                type: 'CHUNK',
                chunkIndex: currentChunk,
                data: encryptedChunk
              }));
            };
            reader.readAsArrayBuffer(chunk);
          }
        };
        
        // 启动第一个分片上传
        const start = 0;
        const end = Math.min(start + this.chunkSize, this.fileInfo.size);
        const chunk = this.fileInfo.slice(start, end);
        
        const reader = new FileReader();
        reader.onload = (e) => {
          const encryptedChunk = encryptSM4(e.target.result, 'your-sm4-key');
          this.ws.send(JSON.stringify({
            type: 'CHUNK',
            chunkIndex: 0,
            data: encryptedChunk
          }));
        };
        reader.readAsArrayBuffer(chunk);
      });
    }
  },
  beforeDestroy() {
    if (this.ws) this.ws.close();
  }
};

3. SM4加密工具函数 (utils/sm4.js)

// 引入GMSSL库(需通过npm安装或直接引入编译后的js文件)
// 这里使用模拟实现,实际项目应使用标准GMSSL实现

export function encryptSM4(data, key) {
  // 实际项目中应使用GMSSL的SM4加密实现
  // 以下是简化示例,实际需要处理二进制数据
  
  // 简单模拟加密 - 实际项目中必须替换为标准SM4实现
  const encrypted = [];
  for (let i = 0; i < data.length; i++) {
    encrypted.push(data.charCodeAt(i) ^ key.charCodeAt(i % key.length));
  }
  
  // 实际项目中应返回标准的加密结果格式
  return new Uint8Array(encrypted).buffer;
}

export function decryptSM4(encryptedData, key) {
  // 解密实现(与加密对称)
  // 实际项目中应使用GMSSL的SM4解密实现
}

后端实现方案要点

1. SpringBoot + Netty实现

// 文件上传控制器示例
@RestController
@RequestMapping("/upload")
public class FileUploadController {
    
    @Autowired
    private FileUploadService fileUploadService;
    
    @PostMapping("/init")
    public ResponseEntity initUpload(@RequestBody String encryptedMeta) {
        // 1. 解密SM4元数据
        String fileMetaStr = SM4Util.decrypt(encryptedMeta, "your-sm4-key");
        FileMeta fileMeta = JSON.parseObject(fileMetaStr, FileMeta.class);
        
        // 2. 创建临时文件和分片目录
        String tempDir = "/tmp/uploads/" + UUID.randomUUID();
        new File(tempDir).mkdirs();
        
        // 3. 返回初始化响应
        Map response = new HashMap<>();
        response.put("status", "READY");
        response.put("tempDir", tempDir);
        return ResponseEntity.ok(response);
    }
    
    @PostMapping("/chunk")
    public ResponseEntity uploadChunk(
            @RequestParam("chunkIndex") int chunkIndex,
            @RequestParam("fileData") MultipartFile fileData) {
        
        try {
            // 1. 解密SM4分片数据
            byte[] decryptedData = SM4Util.decrypt(fileData.getBytes(), "your-sm4-key");
            
            // 2. 保存分片到临时目录
            String tempDir = "从会话或请求头获取临时目录";
            String chunkPath = tempDir + "/chunk_" + chunkIndex;
            Files.write(Paths.get(chunkPath), decryptedData);
            
            return ResponseEntity.ok().build();
        } catch (Exception e) {
            return ResponseEntity.status(500).build();
        }
    }
    
    @PostMapping("/complete")
    public ResponseEntity completeUpload(
            @RequestParam("tempDir") String tempDir,
            @RequestParam("fileName") String fileName) {
        
        try {
            // 1. 合并所有分片
            fileUploadService.mergeChunks(tempDir, fileName);
            
            // 2. 清理临时文件
            fileUploadService.cleanupTempFiles(tempDir);
            
            return ResponseEntity.ok().build();
        } catch (Exception e) {
            return ResponseEntity.status(500).build();
        }
    }
}

国产化环境适配方案

1. 浏览器兼容性处理

  • 使用Babel转译ES6+语法为ES5
  • 引入polyfill.io服务或手动引入所需polyfill
  • 对Element UI进行IE8兼容性配置

2. 操作系统适配

  • 交叉编译前端资源确保在国产Linux上正常运行
  • 使用统信UOS、中标麒麟等系统的默认浏览器进行测试
  • 处理不同系统的文件路径分隔符问题

3. 数据库适配

  • 使用Spring Data JPA的多数据源配置
  • 为达梦和人大金仓数据库编写特定的SQL方言处理
  • 测试数据库连接池在国产环境下的性能

安全方案

1. 传输安全

  • 全程使用SM4加密传输
  • 实现WebSocket握手阶段的双向认证
  • 添加传输完整性校验(MD5/SHA256)

2. 存储安全

  • 文件存储前再次加密
  • 实现安全的密钥管理方案
  • 记录完整的操作日志

性能优化方案

1. 分片策略优化

  • 动态调整分片大小(根据网络状况)
  • 实现并行分片上传
  • 添加智能重试机制

2. 内存管理

  • 使用流式处理避免大文件内存驻留
  • 实现垃圾回收机制清理临时资源
  • 监控内存使用情况

实施计划

  1. 第一阶段(2周):完成核心上传功能开发,实现基本分片上传和SM4加密
  2. 第二阶段(1周):完善下载功能,实现文件夹下载支持
  3. 第三阶段(1周):全面适配国产化环境,进行信创系统测试
  4. 第四阶段(1周):性能优化和安全加固
  5. 第五阶段(1周):完整测试和文档编写

风险评估与应对

  1. IE8兼容性问题

    • 风险:部分现代API不支持
    • 应对:使用全面polyfill,限制功能集
  2. 大文件稳定性

    • 风险:网络中断导致传输失败
    • 应对:实现完善的断点续传和校验机制
  3. 国产化环境差异

    • 风险:不同系统行为不一致
    • 应对:建立全面的测试矩阵,覆盖所有目标环境

总结

本方案通过自定义WebSocket+分片传输机制,结合SM4国密算法,解决了WebUploader在政府项目中的局限性。方案充分考虑了国产化环境要求、浏览器兼容性和大文件传输稳定性,同时提供了完整的前后端实现示例。下一步将进入详细设计和开发阶段,确保按时交付符合要求的解决方案。

SQL示例

创建数据库

image

配置数据库连接

image

自动下载maven依赖

image

启动项目

image

启动成功

image

访问及测试

默认页面接口定义

image

在浏览器中访问

image

数据表中的数据

image

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

批量下载

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

下载续传

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

文件夹下载

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

示例下载

下载完整示例

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐