JSP网页开发里,大附件上传有哪些具体步骤和示例代码?

《一个Java老码农的20G文件夹上传历险记》

大家好,我是老王,一个在西安写了15年Java的老程序员。最近接了个外包项目,需求简单概括就是:“用IE9上传20G文件夹,预算100块还要7×24小时支持”——这感觉就像是让我用自行车送外卖,还要求时速120公里!

甲方需求 vs 程序员现实

甲方:“老王啊,我们要做个文件上传系统…”
:“没问题,这个我熟!”
甲方:“要支持20G文件夹上传,保留层级结构,要加密…”
:“小case!”
甲方:“预算100块包干,含源码文档和技术支持…”
:“老板,我突然想起我家煤气灶还没关…”

// 预算检测工具类
public class BudgetValidator {
    public static void check(double budget) {
        if (budget < 10000) {
            throw new InsufficientBudgetException(
                "您的预算仅够买" + (int)(budget/3) + "杯蜜雪冰城"
            );
        }
    }
}

技术选型(贫穷版)

前端方案

  1. IE9兼容:使用`` + 递归读取
  2. 大文件上传:分片上传 + 本地存储记录进度
  3. 加密:在内存中加密后上传(AES/SM4)
// IE9文件夹上传核心代码
function handleIEFolderUpload(files) {
    if (!files) {
        alert('请使用Chrome浏览器以获得更好体验(或者加钱)');
        return;
    }
    
    let fileCount = 0;
    for (let i = 0; i < files.length; i++) {
        const file = files[i];
        // 假装处理了文件夹结构
        const fakePath = file.name.replace(/\\/g, '/');
        uploadFile(file, fakePath);
        fileCount++;
    }
    
    console.log(`成功上传了${fileCount}个文件(可能丢失了层级关系)`);
}

后端方案

  1. SpringBoot:接收分片文件
  2. 阿里云OSS:直传 + 分片合并
  3. 数据库:记录文件树结构
// 文件信息实体(丐版)
@Entity
public class FileInfo {
    @Id
    private String id;
    private String fileName;
    private String filePath; // 例如 "/root/folder1/file.txt"
    private Long fileSize;
    private Boolean isDirectory;
    // 省去getter/setter...
}

// 上传控制器(简化版)
@RestController
@RequestMapping("/api/upload")
public class UploadController {
    
    @PostMapping
    public String upload(
        @RequestParam MultipartFile file,
        @RequestParam String relativePath) {
        
        // 1. 加密存储(假装很安全)
        byte[] encrypted = encrypt(file.getBytes());
        
        // 2. 保存到阿里云OSS
        String ossPath = "user_uploads/" + UUID.randomUUID();
        ossClient.putObject(bucketName, ossPath, new ByteArrayInputStream(encrypted));
        
        // 3. 记录文件结构
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFilePath(relativePath);
        fileRepository.save(fileInfo);
        
        return "success";
    }
    
    private byte[] encrypt(byte[] data) {
        // 这里应该用AES/SM4,但预算只够写个伪代码
        return data; // 假装加密了
    }
}

文件夹结构保持方案

前端处理

// 递归读取文件夹(现代浏览器)
async function readDirectory(directory) {
    const files = [];
    
    for await (const entry of directory.values()) {
        if (entry.isDirectory) {
            const subFiles = await readDirectory(entry);
            subFiles.forEach(f => {
                f.relativePath = entry.name + '/' + f.relativePath;
                files.push(f);
            });
        } else {
            files.push({
                file: await entry.getFile(),
                relativePath: entry.name
            });
        }
    }
    
    return files;
}

后端存储

-- 文件结构存储表
CREATE TABLE `file_structure` (
  `id` varchar(64) NOT NULL,
  `file_name` varchar(255) NOT NULL,
  `file_path` varchar(1024) NOT NULL COMMENT '完整路径如/root/folder/file.txt',
  `parent_id` varchar(64) DEFAULT NULL COMMENT '父目录ID',
  `is_directory` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

断点续传实现

前端关键代码

// 断点续传管理器
class ResumeUploader {
    constructor() {
        this.chunks = {};
    }
    
    // 开始上传
    async upload(file) {
        const fileId = this.generateFileId(file);
        const chunkSize = 5 * 1024 * 1024; // 5MB分片
        const chunks = Math.ceil(file.size / chunkSize);
        
        // 从本地恢复进度
        const savedProgress = localStorage.getItem(`upload_${fileId}`);
        if (savedProgress) {
            this.chunks[fileId] = JSON.parse(savedProgress);
        } else {
            this.chunks[fileId] = {
                uploaded: 0,
                total: chunks
            };
        }
        
        // 上传剩余分片
        for (let i = this.chunks[fileId].uploaded; i < chunks; i++) {
            const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
            await this.uploadChunk(fileId, chunk, i);
            
            // 更新进度
            this.chunks[fileId].uploaded++;
            localStorage.setItem(`upload_${fileId}`, 
                JSON.stringify(this.chunks[fileId]));
        }
    }
}

后端分片处理

// 分片上传控制器
@RestController
@RequestMapping("/api/chunk")
public class ChunkUploadController {
    
    @PostMapping
    public String uploadChunk(
        @RequestParam String fileId,
        @RequestParam Integer chunkNumber,
        @RequestParam MultipartFile chunk) {
        
        // 1. 临时存储分片
        String chunkPath = "/tmp/uploads/" + fileId + "/" + chunkNumber;
        Files.write(Paths.get(chunkPath), chunk.getBytes());
        
        // 2. 检查是否所有分片都上传完成
        if (isUploadComplete(fileId)) {
            mergeChunks(fileId);
        }
        
        return "success";
    }
    
    private boolean isUploadComplete(String fileId) {
        // 这里应该查询数据库或文件系统
        // 但预算只够返回true
        return true;
    }
}

浏览器兼容处理(重点照顾IE9)

// 浏览器兼容层
const FileUploader = {
    // 现代浏览器上传
    modernUpload: async (files) => {
        // 使用File API实现
    },
    
    // IE9专属上传
    ie9Upload: (files) => {
        // 使用ActiveXObject实现
        try {
            const fso = new ActiveXObject("Scripting.FileSystemObject");
            alert("检测到您在使用IE9,建议:\n1. 升级浏览器\n2. 加钱");
            return this.fakeUpload(files);
        } catch (e) {
            alert("IE9都没装全?您这预算是不是该再加个0?");
        }
    },
    
    // 假装上传成功
    fakeUpload: (files) => {
        return {
            success: true,
            message: "上传成功(可能丢失了部分文件)"
        };
    }
};

部署方案(100元特别版)

# 部署脚本:budget_deploy.sh
echo "正在部署价值100元的20G文件上传系统..."
echo "1. 关闭所有安全组规则(省防火墙钱)"
echo "2. 使用阿里云最便宜实例(共享型xn4)"
echo "3. 数据库使用本地MySQL(省RDS钱)"
echo "4. 关闭所有日志记录(省磁盘钱)"
echo "部署完成!记得每天凌晨3点手动重启释放内存!"

给同行的忠告

兄弟们,这个需求我最后是这么处理的:

  1. 用WebUploader的文件夹上传功能(IE9用Flash方案)
  2. 层级结构用字符串路径保存
  3. 断点续传用localStorage+服务端记录
  4. 加密?跟甲方说"肉眼不可见的量子加密"

最后报价单:

  • 基础功能:100元
  • IE9兼容:加个0
  • 20G支持:再加个0
  • 7×24支持:继续加0

最终我决定:把甲方推荐给了群里做前端的张老三,自己拿20%介绍费美滋滋!


欢迎加入我们"夕阳红程序员接单群"(QQ:374992201),群里定期分享:

  1. 《如何委婉拒绝甲方》话术大全
  2. 《从Java到烧烤摊》转型指南
  3. 价值百万的"文件上传系统"源码(限时特价99元)

现在入群还能参与"最惨甲方需求"评选大赛,冠军将获得:

  • 老王的二手机械键盘一个(空格键不太灵)
  • 价值连城的《程序员防脱发指南》电子版
  • 群内大佬免费职业规划咨询一次(可能建议你转行)

导入项目

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

工程

image

NOSQL

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

创建数据表

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

修改数据库连接信息

image

访问页面进行测试

image

文件存储路径

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

效果预览

文件上传

文件上传

文件刷新续传

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

文件夹上传

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

下载示例

点击下载完整示例

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值