惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Secure Thoughts
P
Privacy International News Feed
T
Tenable Blog
L
Lohrmann on Cybersecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
S
Securelist
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
月光博客
月光博客
T
The Blog of Author Tim Ferriss
Scott Helme
Scott Helme
爱范儿
爱范儿
Stack Overflow Blog
Stack Overflow Blog
C
Cisco Blogs
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
A
Arctic Wolf
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Security Latest
Security Latest
Blog — PlanetScale
Blog — PlanetScale
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
博客园 - 三生石上(FineUI控件)
I
InfoQ
Spread Privacy
Spread Privacy
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog
C
CERT Recently Published Vulnerability Notes
A
About on SuperTechFans
博客园_首页
Engineering at Meta
Engineering at Meta
Project Zero
Project Zero
Latest news
Latest news

博客园 - 子墨老师

基于Hutool的poi导出excel表格【升级更新】 如何设置wps单元格下拉选项设置 Caused by: org.jasypt.exceptions.EncryptionOperationNotPossibleException: Encryption raised an exception ZoomIt的使用与快捷键 springboot项目中使用Java 8的日期时间API 基于springboot系统,如何跟踪会话过期,浏览器会话标识是否收到正常响应,存储,并在后续请求保持携带 SpringBoot+MyBatis实现数据库字段加密 Vue2中能否实现输入中文自动转化为拼音, 且不带音调 Excel导出问题:accessExternalStylesheet 解构赋值+扩展运算符在数组和对象上的应用例子 收藏一下JDK下载地址 介绍几个axios接口请求顺序的问题 vue cli的介绍 Failed to start nginx.service: Unit nginx.service not found. 如何实现文件批量重命名后再进行批量打包下载 如何能成功在centos7下安装nodejs18+以上版本 centos7下卸载nodejs源码包 基于ConcurrentMap锁机制的NFS分片上传方案 如何将已经存在的本地项目源码关联到远程git仓库中 gitee如何使用 centos7安装php+wordpress
基于ConcurrentMap锁机制的NFS文件合并方案
子墨老师 · 2025-07-24 · via 博客园 - 子墨老师

我们在前面已经介绍 《基于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);
        }
    }