java大文件分片上传

1.效果图

2.前端html

<!DOCTYPE html>
<html>
<head></head>
<body>
<form>
    <input type="file" id="fileInput" multiple>
    <button type="button" onclick="upload()" >大文件分片上传</button>
</form>
<script>
    function upload() {
        var fileInput = document.getElementById('fileInput');
        var fileName = document.getElementById("fileInput").files[0].name;
        var files = fileInput.files;
        var chunkSize = 1024 * 1024 * 10; // 每个块的大小为10MB
        var totalChunks = Math.ceil(files[0].size / chunkSize); // 文件总块数
        var currentChunk = 0; // 当前块数
        console.log("当前文件:"+fileName+",大小:"+files[0].size+",分片大小:"+chunkSize+",分片数:"+totalChunks);

        // 分片上传文件
        function uploadChunk() {
            var xhr = new XMLHttpRequest();
            var formData = new FormData();

            // 将当前块数和总块数添加到formData中
            formData.append('current_slice_index', currentChunk);
            formData.append('file_name', fileName);
            formData.append('user_name', "15910761260");

            // 计算当前块在文件中的偏移量和长度
            var start = currentChunk * chunkSize;
            var end = Math.min(files[0].size, start + chunkSize);
            var chunk = files[0].slice(start, end);

            // 添加当前块到formData中
            formData.append('file', chunk);

            // 发送分片到后端
            xhr.open('POST', 'http://192.x.x.x:8060/file/fileInfo/uploadSlice');
            xhr.send(formData);

            xhr.onload = function(data) {
                console.log('上传第'+ currentChunk +'个分片结果:'+xhr.responseText);
                // 需要判断反馈结果,如果成功,才继续,否则再次上传失败部分
                // 更新当前块数
                currentChunk++;

                // 如果还有未上传的块,则继续上传
                if (currentChunk < totalChunks) {
                    uploadChunk();
                } else {
                    // 所有块都上传完毕,进行文件合并
                    console.log("开始合并");
                    mergeChunks(fileName);
                }
            }
        }

        // 合并所有分片
        function mergeChunks() {
            var xhr = new XMLHttpRequest();
            var formData = new FormData();
            formData.append('user_name', "15910761260");
            formData.append('total_slice_num', totalChunks);
            formData.append('file_name', fileName);
            formData.append('file_owner', 'MediaResource');
            formData.append('order_code', 'MediaResource');
            formData.append('folder', 'MediaResource');

            xhr.open("POST", "http://192.x.x.x:8060/file/fileInfo/mergeSlice");
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        console.log("文件合并完成:", xhr.responseText);
                    } else {
                        console.error(xhr.responseText);
                    }
                }
            };
            xhr.send(formData);
        }

        // 开始上传
        uploadChunk();
    }
</script>
</body>
</html>

2.java代码

        上传分片代码

    @ResponseBody
    @PostMapping(value = "/uploadSlice")
    @Operation(summary = "上传文件分片", description = "返回是否上传成功")
    public MisResult<String> UploadSlice(UploadFileSliceInput uploadFileSliceInput,
                                         @RequestParam(value = "file") MultipartFile multipartFile) throws Exception {
        return fileInfoService.UploadSlice(uploadFileSliceInput, multipartFile.getBytes());
    }

    @Override
    public MisResult<String> UploadSlice(UploadFileSliceInput uploadFileSliceInput, byte[] content) {
        MisResult<String> result = new MisResult<>();

        try {
            //参数校验
            ValidateUtil.ValidateThrowException(uploadFileSliceInput);
            if (content == null || content.length == 0) {
                result.Error(MisResultCode.PARAM_ERROR);
                return result;
            }

            //判断分片是否已上传
            String nameMd5 = DigestUtils.md5Hex(uploadFileSliceInput.getFile_name());
            String redisKey = nameMd5 + "_" + uploadFileSliceInput.getUser_name();
            String chunkStatus = (String) RedisUtil.HashGet(redisKey, uploadFileSliceInput.getCurrent_slice_index() + "");
            if (StringUtils.hasLength(chunkStatus) && MisStatus.Finished.equals(chunkStatus)) {
                result.Success("文件分片上传成功!");
                return result;
            }

            //保存文件分片到临时文件夹
            String absolutePath = FileConfig.Path + File.separator + "temp" + File.separator + "slice_" + nameMd5
                    + "_" + uploadFileSliceInput.getCurrent_slice_index();
            File saveFile = new File(absolutePath);
            FileCopyUtils.copy(content, saveFile);

            //记录上传状态
            long timeout = RedisUtil.GetExpire(redisKey);
            RedisUtil.HashSet(redisKey, uploadFileSliceInput.getCurrent_slice_index() + "", MisStatus.Finished);
            result.Success("文件分片上传成功!");
            if (timeout < 0) {
                RedisUtil.SetExpire(redisKey, 60 * 60);//1个小时超时删除
            }
        } catch (Exception e) {
            result.Error(MisResultCode.UNKNOWN_ERROR, "分片上传异常," + e.getMessage());
            log.error("分片上传异常", e);
        }
        return result;
    }

        上传参数

package mis.dto.file.fileinfo;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * description 分片上传文件dto
 */
@Schema(description = "分片上传文件dto")
@Data
@Accessors(chain = true)
public class UploadFileSliceInput {

    @NotNull
    @Schema(description = "上传手机号")
    private String user_name;

    @NotNull
    @Schema(description = "文件名")
    private String file_name;

    @NotNull
    @Schema(description = "当前分片序号")
    private int current_slice_index;

}

          合并分片代码

    @ResponseBody
    @PostMapping(value = "/mergeSlice")
    @Operation(summary = "合并文件分片", description = "返回是否上传成功")
    public MisResult<FileInfoOutput> MergeSlice(UploadFileSliceMerge uploadFileSliceMerge) throws Exception {
        return fileInfoService.MergeSlice(uploadFileSliceMerge);
    }

    @Override
    public MisResult<FileInfoOutput> MergeSlice(UploadFileSliceMerge uploadFileSliceMerge) throws Exception {
        MisResult<FileInfoOutput> result = new MisResult<>();

        //参数校验,非空项及上传分片数量是否正确
        ValidateUtil.ValidateThrowException(uploadFileSliceMerge);
        String nameMd5 = DigestUtils.md5Hex(uploadFileSliceMerge.getFile_name());
        String redisKey = nameMd5 + "_" + uploadFileSliceMerge.getUser_name();
        Map<Object, Object> sliceMap = RedisUtil.HashGetMap(redisKey);
        if (sliceMap.size() != uploadFileSliceMerge.getTotal_slice_num()) {
            result.Error(MisResultCode.MIS_ERR_NOTEXITS, "文件分片上传不完整," + uploadFileSliceMerge.getUser_name());
            log.error(result.getMessage());
            return result;
        }

        //根据分片文件序号排列
        File tmpDir = new File(FileConfig.Path + File.separator + "temp");
        File[] sliceFiles = tmpDir.listFiles((dir, name) -> name.contains("slice") &&
                name.contains(nameMd5));
        if (sliceFiles == null) {
            result.Error(MisResultCode.MIS_ERR_NOTEXITS, "文件分片不存在!");
            return result;
        }
        Arrays.sort(sliceFiles, (o1, o2) -> {
            String o1Index = o1.getName().replace("_", "")
                    .replace("slice", "")
                    .replace(nameMd5, "");
            String o2Index = o2.getName().replace("_", "")
                    .replace("slice", "")
                    .replace(nameMd5, "");
            return Integer.parseInt(o1Index) - Integer.parseInt(o2Index);
        });

        //设置最终存储路径
        String fileOwner = StringUtils.hasLength(uploadFileSliceMerge.getFile_owner()) ?
                uploadFileSliceMerge.getFile_owner() : "unknown";//文件拥有者
        String newFileName = UuidGenerator.generate() + "." + FileUtil.GetSuffix(uploadFileSliceMerge.getFile_name());//新文件名
        String storageDir = FileConfig.Path + File.separator;
        if (StringUtils.hasLength(uploadFileSliceMerge.getFolder())) {
            storageDir += uploadFileSliceMerge.getFolder();//存储目录
        } else {
            storageDir += DateUnitl.ToString(null, "yyyyMM") + File.separator + fileOwner;//存储目录
        }
        if (!new File(storageDir).exists()) {
            new File(storageDir).mkdirs();
        }
        String absolutePath = storageDir + File.separator + newFileName;//存储绝对路径
        String filePath = absolutePath.replace(FileConfig.Path, "");//存储相对路径

        //合并分片文件
        FileChannel outChannel = null;
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(absolutePath);
            outChannel = outputStream.getChannel();
            for (int i = 0; i < sliceFiles.length; i++) {
                FileInputStream inputStream = new FileInputStream(sliceFiles[i]);
                try (FileChannel inChannel = inputStream.getChannel()) {
                    inChannel.transferTo(0, inChannel.size(), outChannel);
                    FileUtil.Close(inChannel);
                }
                FileUtil.Close(inputStream);
            }
        } catch (Exception e) {
            log.error("合并分片异常", e);
            result.Error(MisResultCode.ACTION_ERROR, "合并分片异常," + uploadFileSliceMerge.getFile_name());
            return result;
        } finally {
            FileUtil.Close(outChannel);
            FileUtil.Close(outputStream);
        }

        //保存最终文件信息
        File bigFile = new File(absolutePath);
        String uuid = UuidGenerator.generate();
        String downloadUrl = FileConfig.Access + "?fileInfoId=" + uuid;
        FileInfo newFileInfo = new FileInfo(uuid)
                .setFile_owner(fileOwner)
                .setOrder_code(uploadFileSliceMerge.getOrder_code())
                .setFile_name(uploadFileSliceMerge.getFile_name())
                .setFile_path(filePath)
                .setFile_absolute_path(absolutePath)
                .setFile_size(bigFile.length())
                .setFile_type(FileUtil.JudgeFileTypeByName(uploadFileSliceMerge.getFile_name()))
                .setFile_status(MisStatus.Enable)
                .setDownload_path(downloadUrl)
                .setNote(MisServerConfig.App);
        var savedFile = this.fileInfoRepository.save(newFileInfo);

        //清理分片和缓存
        for (File sliceFile : sliceFiles) {
            sliceFile.delete();
        }
        RedisUtil.Delete(nameMd5 + "_" + uploadFileSliceMerge.getUser_name());

        //返回文件对象
        var modelMapper = new ModelMapper();
        var fileInfoOutput = modelMapper.map(savedFile, FileInfoOutput.class);
        fileInfoOutput.setPreview_path("/file/fileInfo/preview?fileInfoId=" + uuid);
        result.Success(fileInfoOutput);
        return result;
    }

        合并分片参数

package mis.dto.file.fileinfo;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * description 合并分片参数dto
 */
@Schema(description = "分片上传文件dto")
@Data
@Accessors(chain = true)
public class UploadFileSliceMerge {

    //合并参数
    @NotNull
    @Schema(description = "上传手机号")
    private String user_name;

    @NotNull
    @Schema(description = "总分片数")
    private double total_slice_num;

    //文件参数
    @NotNull
    @Schema(description = "文件名")
    private String file_name;//文件名

    @Schema(description = "文件持有人")
    private String file_owner;//文件持有人

    @Schema(description = "单据编码")
    private String order_code;//单据编码

    @Schema(description = "文件夹,如果文件夹不为空,则使用传入的文件夹保存文件")
    private String folder;//文件夹,如果文件夹不为空,则使用传入的文件夹保存文件
    
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kenick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值