基于ConcurrentMap锁机制的NFS文件合并方案

我们在前面已经介绍 《基于ConcurrentMap锁机制的NFS分片上传方案》,今天把上传后的分片文件进行合并。先给大家发一个设计流程图

需要关键的vo类:Chunk(分片文件) 和 FileInfo(合并文件)

import org.springframework.web.multipart.MultipartFile;

import java.io.Serializable;

public class Chunk implements Serializable {

    private MultipartFile file;

    private Long id;
    /**
     * 当前文件块,从1开始
     */
    private Integer chunkNumber;
    /**
     * 分块大小
     */
    private Long chunkSize;
    /**
     * 当前分块大小
     */
    private Long currentChunkSize;
    /**
     * 总大小
     */
    private Long totalSize;
    /**
     * 文件标识
     */
    private String identifier;
    /**
     * 文件名
     */
    private String filename;
    /**
     * 相对路径
     */
    private String relativePath;
    /**
     * 总块数
     */
    private Integer totalChunks;
    /**
     * 文件类型
     */
    private String type;
}
import java.io.Serializable;

public class FileInfo implements Serializable {
    private Long id;

    private String filename;

    private String identifier;

    private Long totalSize;

    private String type;

    private String location;
}

代码设计关键步骤

  1. 初始化NFS客户端
  2. 给文件路径上锁
  3. 简单用UUID方式生成合并后的文件名
  4. 获取上传后的分片文件,并进行分片排序【重要】
  5. try-with-resouces进行文件合并操作
  6. 清理分片目录
public Map<String,String> mergeFiles(FileInfo fileInfo) throws IOException {
        Nfs3 nfs = null;
        String identifier = fileInfo.getIdentifier();
        Map<String,String> result = new HashMap<>();
        try {
            // 初始化NFS客户端并构建路径
            nfs = getNfsClient();
            Object mergeLock = MERGE_LOCK_MAP.computeIfAbsent(identifier, k -> new Object());

            synchronized (mergeLock) {
                // 准备目录路径
                String[] chunkDirPath = {CHUNK_DIR, identifier};
                Nfs3File chunkDir = new Nfs3File(nfs, "/"); // 无论NFS_DIR末尾带不带/,这里开头必须/,否者堆栈溢出
                // NFS路径规范
                // NFS客户端要求每次只操作单级路径
                // 当使用new Nfs3File(parent, child)时,child应该是单级目录名(如"chunks"),而不是多级路径(如"chunks/identifier")
                for (String dir : chunkDirPath) {
                    chunkDir = new Nfs3File(chunkDir, dir);
                }

                Nfs3File outputDir = new Nfs3File(nfs, "/"); // 无论NFS_DIR末尾带不带/,这里开头必须/,否者堆栈溢出
                //outputDir = new Nfs3File(outputDir, MERGED_DIR); // 创建合并子目录
                // ensureDirExists(outputDir);

                // 创建合并目标文件
                // 生成文件UUID名称用于存储
                String uuid = UUID.randomUUID().toString().replace("-", "");
                String fileName = fileInfo.getFilename();
                String ext = fileName.substring(fileName.lastIndexOf("."));
                String nfileName = uuid + ext;
                Nfs3File mergedFile = new Nfs3File(outputDir, nfileName);

                // 获取排序后的分片列表
                List<Nfs3File> sortedChunks = getSortedChunks(chunkDir);

                // 合并分片内容
                try (OutputStream output = new NfsFileOutputStream(mergedFile)) {
                    for (Nfs3File chunk : sortedChunks) {
                        // 4.1 验证分片有效性
                        if (!chunk.exists()) {
                            throw new IOException("分片文件不存在: " + chunk.getPath());
                        }

                        // 4.2 流式复制分片内容
                        try (InputStream input = new NfsFileInputStream(chunk)) {
                            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区提升性能
                            int bytesRead;
                            while ((bytesRead = input.read(buffer)) != -1) {
                                output.write(buffer, 0, bytesRead);
                            }
                        }
                    }
                }
                // 清理分片目录
                deleteRecursively(chunkDir); // 调用自定义递归删除

                // 6. 验证合并结果
                if (!mergedFile.exists()) {
                    throw new IOException("合并文件创建失败: " + mergedFile.getPath());
                }
                // return MERGED_DIR + "/" + nfileName;

                result.put("videoPath", BASE_PATH + "/" + nfileName);
                result.put("videoUploadName", nfileName);
                result.put("fileName", fileInfo.getFilename());
                return result;
            }
        } finally {
            if (nfs != null) nfs = null; // 清理资源
            // 移除锁(允许后续重试)
            MERGE_LOCK_MAP.remove(identifier);
        }
    }

 

posted @ 2025-07-24 11:23  子墨老师  阅读(18)  评论(0)    收藏  举报