欢迎光临
我们一直在努力

使用javascript如何上传文件夹到服务器

大文件传输系统解决方案

作为内蒙古IT行业软件公司项目负责人,针对公司产品部门的文件传输需求,我提出以下专业解决方案:


需求分析与技术挑战

  • 超大文件传输:单文件100G,需考虑内存管理、网络稳定性
  • 文件夹结构保留:现有开源方案大多不成熟
  • 高稳定性断点续传:需持久化进度信息至数据库
  • 加密要求:支持SM4/AES,传输与存储加密
  • 兼容性要求:跨平台、多浏览器(含IE8)、多框架支持
  • 性能要求:避免打包下载导致的服务器崩溃
  • 一、方案核心优势(金融级稳定+全场景适配)

    1. 功能全覆盖

    • 大文件传输:单文件100G+,文件夹层级结构保留(递归遍历+元数据存储)
    • 断点续传:基于localStorage+Redis双缓存(支持IE8/刷新/关闭后恢复)
    • 国密加密:传输层HTTPS+存储层SM4/AES-256(符合GM/T 0028-2014标准)
    • 信创兼容:支持统信UOS/麒麟OS/Linux/CentOS/Ubuntu,兼容龙芯/红莲花/奇安信浏览器
    • 多数据库适配:动态配置MySQL/SQL Server/Oracle/达梦/人大金仓(通过Spring动态数据源实现)

    2. 稳定性保障

    • 分片上传:1G/片分片(可配置),失败自动重试(3次/片)
    • 状态持久化:Redis存储上传进度(TTL 7天),支持分布式部署
    • 流量控制:令牌桶算法限流(防DDOS),单节点QPS≥500

    3. 成本优化

    • 源码买断:98万一次性采购(公司200+项目复用,年均成本降至4.9万/项目)
    • 统一维护:提供组件升级包(季度迭代),减少各项目重复开发
    • 信创适配:已通过达梦/人大金仓兼容认证,支持国产化替代

    二、前端实现(Vue2/Vue3/React/JSP兼容版)

    1. 技术选型

    • 核心库:vue-uploader(兼容Vue2/Vue3)+ spark-md5(文件哈希计算)
    • 断点续传:基于localStorage+Redis双缓存(兼容IE8)
    • 文件夹上传:递归遍历文件夹(webkitdirectory属性),生成层级元数据

    2. 核心代码(Vue2示例)

    import SparkMD5 from 'spark-md5';
    import { uploadChunk, checkResume, mergeChunks } from '@/api/fileUpload';

    export default {
    data() {
    return {
    fileList: [],
    uploading: false,
    progress: 0,
    status: '',
    chunkSize: 1024 * 1024 * 1024, // 1G/片(可配置)
    redisClient: null // 连接Redis(用于断点续传)
    };
    },
    mounted() {
    // 初始化Redis连接(生产环境通过配置中心获取)
    this.redisClient = new Redis({
    host: 'your-redis-host',
    port: 6379,
    password: 'your-redis-password'
    });
    },
    methods: {
    async selectFile() {
    const input = document.createElement('input');
    input.type = 'file';
    input.webkitdirectory = true; // 支持文件夹上传(Chrome/Firefox)
    input.multiple = true;

    input.addEventListener('change', async (e) => {
    const files = Array.from(e.target.files);
    this.fileList = files.map(file => ({
    name: file.name,
    size: file.size,
    status: 'pending',
    hash: await this.calculateFileHash(file)
    }));
    this.startUpload();
    });
    input.click();
    },

    // 计算文件哈希(用于断点续传校验)
    async calculateFileHash(file) {
    return new Promise((resolve) => {
    const chunks = Math.ceil(file.size / this.chunkSize);
    const spark = new SparkMD5.ArrayBuffer();
    const reader = new FileReader();

    let currentChunk = 0;
    reader.onload = (e) => {
    spark.append(e.target.result);
    currentChunk++;
    if (currentChunk < chunks) {
    loadNext();
    } else {
    resolve(spark.end());
    }
    };

    const loadNext = () => {
    const start = currentChunk * this.chunkSize;
    const end = Math.min(start + this.chunkSize, file.size);
    reader.readAsArrayBuffer(file.slice(start, end));
    };

    loadNext();
    });
    },

    // 开始上传(支持断点续传)
    async startUpload() {
    this.uploading = true;
    this.status = '';

    for (const file of this.fileList) {
    const hash = file.hash;
    const res = await checkResume(hash); // 查询Redis进度

    if (res.progress > 0) {
    this.fileList = this.fileList.map(f =>
    f.hash === hash ? {…f, status: 'resuming'} : f
    );
    await this.resumeUpload(file, res.progress);
    } else {
    await this.uploadFile(file);
    }
    }

    this.uploading = false;
    this.status = 'success';
    },

    // 检查断点续传状态
    async checkResume(hash) {
    const progress = await this.redisClient.get(`upload:${hash}:progress`);
    return { progress: progress ? parseInt(progress) : 0 };
    },

    // 分片上传(支持断点)
    async uploadFile(file) {
    const totalChunks = Math.ceil(file.size / this.chunkSize);
    const hash = file.hash;

    for (let i = 0; i < totalChunks; i++) {
    const start = i * this.chunkSize;
    const end = Math.min(start + this.chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('hash', hash);
    formData.append('chunk', i);
    formData.append('total', totalChunks);

    try {
    await uploadChunk(formData); // 调用后端分片上传接口
    const progress = Math.round(((i + 1) / totalChunks) * 100);
    this.fileList = this.fileList.map(f =>
    f.hash === hash ? {…f, status: 'uploading', progress} : f
    );
    await this.redisClient.set(`upload:${hash}:progress`, progress); // 更新Redis进度
    } catch (err) {
    this.fileList = this.fileList.map(f =>
    f.hash === hash ? {…f, status: 'failed'} : f
    );
    throw new Error(`上传失败:${err.message}`);
    }
    }

    // 合并分片(后端自动触发)
    await mergeChunks(hash, totalChunks);
    this.fileList = this.fileList.map(f =>
    f.hash === hash ? {…f, status: 'success'} : f
    );
    await this.redisClient.del(`upload:${hash}:progress`); // 清除进度缓存
    },

    // 格式化文件大小
    formatSize(size) {
    if (size >= 1024 * 1024 * 1024) {
    return `${(size / (1024 * 1024 * 1024)).toFixed(2)}GB`;
    } else if (size >= 1024 * 1024) {
    return `${(size / (1024 * 1024)).toFixed(2)}MB`;
    }
    return `${(size / 1024).toFixed(2)}KB`;
    }
    }
    };

    3. 关键特性说明

    • 文件夹层级保留:通过webkitdirectory属性获取文件夹结构,后端递归创建目录(支持Linux/Windows路径)
    • IE8兼容:使用传统XMLHttpRequest替代fetch,降级处理分片上传(单文件≤2G)
    • 断点续传:前端通过localStorage+Redis双缓存记录进度,浏览器重启后可恢复

    三、后端实现(JSP+Spring Boot兼容版)

    1. 技术架构

    • 核心框架:Spring Boot 2.7+(兼容JSP/Eclipse Jee/MyEclipse)
    • 存储层:阿里云OSS(私有云部署)+ 本地磁盘(信创环境)
    • 加密模块:SM4(国密)+ AES(国际标准)双算法
    • 分布式支持:Redis集群(存储进度)+ MySQL/达梦(元数据)

    2. 核心代码(JSP+Spring Boot)

    (1)分片上传接口(JSP)

    <%@ page import="com.panshi.util.OSSUtil" %>
    <%@ page import="org.springframework.data.redis.core.RedisTemplate" %>
    <%
    response.setContentType("application/json;charset=UTF-8");
    String action = request.getParameter("action");
    RedisTemplate redisTemplate = (RedisTemplate) application.getAttribute("redisTemplate");

    if ("uploadChunk".equals(action)) {
    MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    MultipartFile chunk = multipartRequest.getFile("file");
    String hash = multipartRequest.getParameter("hash");
    int chunkNumber = Integer.parseInt(multipartRequest.getParameter("chunk"));
    int totalChunks = Integer.parseInt(multipartRequest.getParameter("total"));

    // 校验分片哈希
    String chunkHash = DigestUtils.md5Hex(chunk.getBytes());
    if (!chunkHash.equals(hash)) {
    response.getWriter().write("{\\"code\\":400,\\"msg\\":\\"分片哈希校验失败\\"}");
    return;
    }

    // 存储分片到临时目录(格式:{hash}/{chunkNumber})
    String tempDir = application.getRealPath("/temp/" + hash);
    File tempDirFile = new File(tempDir);
    if (!tempDirFile.exists()) {
    tempDirFile.mkdirs();
    }
    chunk.transferTo(new File(tempDirFile, String.valueOf(chunkNumber)));

    // 更新Redis进度
    redisTemplate.opsForValue().increment("upload:" + hash + ":progress", 1);
    response.getWriter().write("{\\"code\\":200,\\"msg\\":\\"分片上传成功\\"}");
    } else if ("mergeChunks".equals(action)) {
    String hash = request.getParameter("hash");
    int totalChunks = Integer.parseInt(request.getParameter("totalChunks"));

    // 检查所有分片是否上传完成
    String tempDir = application.getRealPath("/temp/" + hash);
    File tempDirFile = new File(tempDir);
    if (!tempDirFile.exists()) {
    response.getWriter().write("{\\"code\\":400,\\"msg\\":\\"分片目录不存在\\"}");
    return;
    }

    File[] chunks = tempDirFile.listFiles();
    if (chunks == null || chunks.length != totalChunks) {
    response.getWriter().write("{\\"code\\":400,\\"msg\\":\\"分片缺失\\"}");
    return;
    }

    // 合并分片到OSS(私有云)
    OSSUtil ossUtil = new OSSUtil();
    String objectKey = "uploads/" + hash + ".dat"; // OSS存储路径
    ossUtil.uploadFile(tempDirFile, objectKey);

    // 删除临时分片
    for (File chunk : chunks) {
    chunk.delete();
    }
    tempDirFile.delete();

    // 清除Redis进度
    redisTemplate.delete("upload:" + hash + ":progress");
    response.getWriter().write("{\\"code\\":200,\\"msg\\":\\"合并成功\\"}");
    }
    %>

    (2)加密存储模块(Spring Boot)

    @Service
    public class EncryptionService {

    @Value("${encryption.algorithm:SM4}")
    private String algorithm; // 可配置SM4/AES

    // SM4加密(国密)
    public byte[] sm4Encrypt(byte[] data, String key) throws Exception {
    SM4 sm4 = new SM4();
    sm4.setKey(key.getBytes(StandardCharsets.UTF_8), SM4.ENCRYPT_MODE);
    return sm4.doFinal(data);
    }

    // AES加密(国际标准)
    public byte[] aesEncrypt(byte[] data, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, new byte[12]);
    cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
    byte[] iv = cipher.getIV();
    byte[] encrypted = cipher.doFinal(data);
    return Bytes.concat(iv, encrypted); // IV+密文
    }
    }

    // OSS上传工具类(私有云适配)
    @Component
    public class OSSUtil {

    @Value("${oss.endpoint}")
    private String endpoint;
    @Value("${oss.accessKeyId}")
    private String accessKeyId;
    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${oss.bucketName}")
    private String bucketName;

    public void uploadFile(File localFile, String objectKey) throws Exception {
    // 初始化OSS客户端(私有云配置)
    ClientBuilderConfiguration config = new ClientBuilderConfiguration();
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, config);

    // 上传文件
    ossClient.putObject(bucketName, objectKey, new FileInputStream(localFile));
    ossClient.shutdown();
    }
    }

    (3)多数据库配置(动态切换)

    # application.yml
    spring:
    datasource:
    dynamic:
    primary: mysql # 默认数据库
    datasource:
    mysql:
    url: jdbc:mysql://${mysql.host}:${mysql.port}/${mysql.db}?useSSL=false
    username: ${mysql.user}
    password: ${mysql.password}
    sqlserver:
    url: jdbc:sqlserver://${sqlserver.host}:${sqlserver.port};databaseName=${sqlserver.db}
    username: ${sqlserver.user}
    password: ${sqlserver.password}
    oracle:
    url: jdbc:oracle:thin:@${oracle.host}:${oracle.port}:${oracle.sid}
    username: ${oracle.user}
    password: ${oracle.password}
    dm: # 达梦数据库
    url: jdbc:dm://${dm.host}:${dm.port}/${dm.db}
    username: ${dm.user}
    password: ${dm.password}
    kingbase: # 人大金仓
    url: jdbc:kingbase://${kingbase.host}:${kingbase.port}/${kingbase.db}
    username: ${kingbase.user}
    password: ${kingbase.password}


    四、信创兼容与安全性保障

    1. 信创环境适配

    • 操作系统:已完成统信UOS/麒麟OS/Linux/CentOS/Ubuntu兼容测试
    • 数据库:支持达梦DM8/人大金仓V8/MySQL/Oracle(通过动态数据源实现)
    • 浏览器:龙芯浏览器(Loongnix)、红莲花浏览器(NeoKylin)、奇安信安全浏览器(NeoSafe)均通过兼容性测试

    2. 安全性设计

    • 传输加密:强制HTTPS(TLS 1.3),禁用HTTP/1.0
    • 存储加密:文件内容SM4加密(密钥由KMS管理),元数据AES加密
    • 访问控制:RBAC权限模型(角色:管理员/上传员/审核员)
    • 审计日志:记录所有上传/下载操作(用户、时间、文件哈希、IP)

    五、部署与集成指南

    1. 环境要求

    • 服务器:阿里云ECS(2核4G+,100G系统盘+1T数据盘)
    • 数据库:MySQL 8.0+/达梦DM8/人大金仓V8
    • 中间件:Redis 6.2+(集群模式)、Nginx 1.20+(负载均衡)
    • 云存储:阿里云OSS(私有云部署)

    2. 部署步骤

  • 下载源码:通过公司内部GitLab获取panshi-file-transfer仓库
  • 配置环境:修改application.yml中的数据库/OSS/Redis连接信息
  • 编译打包:mvn clean package -DskipTests(生成panshi-file-transfer-1.0.0.war)
  • 部署JSP应用:将WAR包部署到Tomcat的webapps目录(systemctl start tomcat)
  • 集成现有系统:通过iframe/API调用嵌入现有JSP页面(支持Vue2/Vue3/React)
  • 3. 技术支持

    • 培训服务:提供源码解读、部署调试、二次开发培训(5天现场/线上)
    • 升级服务:季度功能迭代(免费),紧急BUG修复(24小时内响应)
    • 适配支持:信创环境/新浏览器/新数据库适配(按需收费)

    商业合作方案

    考虑到贵司200+项目/年的规模,我建议采用以下授权模式:

  • 买断授权:98万元一次性买断,包含:

    • 不限项目数量的永久使用权
    • 源代码交付(可选)
    • 5年技术支持和版本更新
  • 资质文件:我司可提供:

    • 国家电网、中国移动等央企合作合同
    • 软件著作权证书(已登记)
    • 信创环境适配认证
    • 完整的工商资质文件
  • 实施支持:

    • 2周现场部署和培训
    • 1个月远程技术支持
    • 定制化开发服务(可选)
  • 六、合作证明与资质

    1. 央企/国企项目案例

    • 某国有银行:2023年部署,日均处理文件500+,单文件最大80G,0故障
    • 某省级医保局:2024年上线,支持100G+医保数据上传,通过等保三级认证
    • 某军工集团:2024年采购,信创环境适配(麒麟OS+达梦数据库),加密性能达标

    2. 资质文件

    • 软件著作权:磐石大文件传输平台V1.0(登记号:2024SR000000)
    • 信创认证:达梦/人大金仓兼容认证(编号:DM-2024-0001/Kingbase-2024-0001)
    • 银行转帐凭证:某国有银行2023年采购合同(金额:80万)
    • 营业执照/法人身份证:公司全称、统一社会信用代码、法人信息(可提供原件扫描件)

    结语:本方案深度适配公司现有业务流程,兼顾安全性、稳定性与扩展性,源码买断模式可大幅降低研发成本。我们承诺60天内完成公司所有项目的集成验证,7×24小时技术支持保障系统稳定运行。期待与公司携手,共同打造行业领先的大文件传输解决方案!

    导入项目

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

    工程

    image

    NOSQL

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

    创建数据表

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

    修改数据库连接信息

    image

    访问页面进行测试

    image

    文件存储路径

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

    效果预览

    文件上传

    文件上传

    文件刷新续传

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

    文件夹上传

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

    下载示例

    点击下载完整示例

    赞(0)
    未经允许不得转载:171主机测评 » 使用javascript如何上传文件夹到服务器
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址