RAG文件分片上传

文件上传流程

controller层

  • 前端会对文件先进行MD5计算出唯一标识,再进行分片向后端发出分片上传的请求
  • 在每个文件第一个分片的时候要进行验证,截取文件的扩展名,验证改文件类型是否支持
  • 如果文件没有所属的组织标签,那么就获取该用户的主组织标签,并设置这个文件所属组织
    Service层
  • 先查询mysql数据库中 file_update 表是否存在这个文件 MD5 标识,查询是 file_MD5 + usrId 一起查询,如果不存在就创建
  • 双层校验:第一层:检查Redis(或其他缓存)中是否标记该分片已上传,第二层:检查数据库(chunk_info表)中是否有该分片的记录
  • 处理 “分片已上传但数据库无记录” 的异常场景
if (chunkUploaded) {
    if (!chunkInfoExists) {
        // 计算分片MD5、构建存储路径
        // 检查MinIO中是否真的存在该分片
        // 如果MinIO中不存在,把chunkUploaded设为false,触发重新上传
    } else {
        return; // 分片已上传且数据库有记录,直接结束
    }
}
  • 分片实际上传到 MinIO(核心操作),并且在 Redis 中标记分片已上传
  • 补充分片信息到数据库(chunk_info 表)

前端对文件进行分片

  • 前端使用 MD5 算法把文件的二进制数据转换成32位字符串,作为这个文件的唯一标识。
  • 还有 SHA-256 算法成长64位字符串,安全性远高于 MD5 算法。但是相比于 MD5 计算速度有所下降,并且 64 位存储空间也需要更大
  • 在对文件进行分块的时候是按照 5MB 进行分块的, 这个 5MB 是最佳实践数值。主流浏览器和后端服务器对于单次 Post 请求表单数据大小有默认限制, 5MB 低于大多数中间件的安全阈值,同时避免分块过小导致 TCP 握手次数增多, 5MB 也能正好利用 TCP 拥塞窗口。前段浏览器在读取分块的时候会加载到内存, 5MB 可以实现多路(3 ~ 5 路)并行上传。 5MB 不是固定值,可以根据实际网速,文件大小进行相关的调整
  • 后端接受的请求参数
    @PostMapping("/chunk")
    public ResponseEntity<Map<String, Object>> uploadChunk(
            @RequestParam("fileMd5") String fileMd5,        //fileMd5 文件的MD5值,用于唯一标识文件
            @RequestParam("chunkIndex") int chunkIndex,     //chunkIndex 分片索引,表示当前分片的位置
            @RequestParam("totalSize") long totalSize,      //totalSize 文件总大小
            @RequestParam("fileName") String fileName,      //fileName 文件名
            @RequestParam(value = "totalChunks", required = false) Integer totalChunks,       //totalChunks 总分片数量
            @RequestParam(value = "orgTag", required = false) String orgTag,                  //orgTag 组织标签,如果未指定则使用用户的主组织标签
            @RequestParam(value = "isPublic", required = false, defaultValue = "false") boolean isPublic,      //isPublic 是否公开,默认为false
            @RequestParam("file") MultipartFile file,                                          //接收分块的文件
            @RequestAttribute("userId") String userId) throws IOException {

分块上传在 Redis 中缓存

  • Redis BitMap 是一个由二进制数组,每一位都是 0 和 1,分块的数对应偏移量下标, 1表示已上传, 0表示未上传
// 核心:查询 redisKey 对应位图中,下标为 chunkIndex 的位值(0=未上传,1=已上传)
boolean isUploaded = redisTemplate.opsForValue().getBit(redisKey, chunkIndex);

// 分片上传成功后,标记该分片为已上传(位值设为1)
redisTemplate.opsForValue().setBit(redisKey, chunkIndex, true);

//全部分片上传完成后,需要合并文件,此时用 bitCount 统计位图中 1 的数量,对比总分片数
long uploadedCount = redisTemplate.execute(new RedisCallback<Long>() {
    @Override
    public Long doInRedis(RedisConnection connection) throws DataAccessException {
        // 统计 redisKey 对应位图中 1 的数量(已上传分片数)
        return connection.bitCount(redisKey.getBytes());
    }
});

MinIO 存储分片文件

  • 极致的高性能,适配大文件分片的高并发读写,MinIO 采用单机多线程 + 分布式横向扩展架构,底层基于 Go 语言开发(无 GC 瓶颈)
  • 原生支持 S3 协议,完美适配分片上传的标准流程,分片上传的核心流程(初始化分片→并行上传分片→校验分片→合并分片→断点续传)是对象存储的标准能力,而 MinIO100% 兼容 S3 协议,原生提供分片上传的全量 API,原生能力可以直接覆盖业务需求。
  • 其他存储公有云对象存储 阿里云 OSS / 腾讯云 COS
  • 查询 MinIO 中分片是否存在,这个查询部分最后返回来一个 StatObiectResponse 对象,如果不存在就返回报错。
    // 用于与 MinIO 服务器交互
    @Autowired
    private MinioClient minioClient;

    storagePath = "chunks/" + fileMd5 + "/" + chunkIndex;
    //查询「uploads 存储桶」中「指定路径(storagePath)」的对象是否存在,并获取该对象的全量元数据(大小、修改时间、类型等)
    StatObjectResponse stat = minioClient.statObject(     //statObject:MinIO 客户端提供的核心方法,方法名直译是「统计对象」功能就是查询对象的状态 / 元数据
                                                          //是 MinIO SDK 中用于对象存在性检查、元数据获取的常用方法
                            StatObjectArgs.builder()      //这是 MinIO SDK 采用的建造者模式(Builder Pattern) 实现
                                .bucket("uploads")        //设置操作的存储桶名称为uploads,这是statObject的必选参数
                                .object(storagePath)      //设置要查询的对象路径 / 名称为变量storagePath的值,这也是statObject的必选参数;
                                .build()                  //建造者模式的收尾方法
                          );
  • MinIO 文件上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                            .bucket("uploads")
                            .object(storagePath)
                            .stream(file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build();
                    
     minioClient.putObject(putObjectArgs);

MinIO 文件合并

合并流程

  • 根据 userId 和 fileMd5 检查改合并请求是否有权限,也就是在 file_upload 表中是否有该记录,还有一层显示检查 userId ,双层检查
  • 检查分片是否上传完整:要在 controller 先判断 是否分块 上传数量 < 实际数量,然后在 service 层检测是否 上传数量 == 预期数量。双层检测
  • 合并文件
  • 发送任务到 Kafka
  • 流程:检查分片数量是否与预期一致 ,检查每个分片是否存在,合并分片,检查合并后的文件,清理分片文件,删除 Redis 中的分片状态记录,更新文件状态,生成预签名 URL(有效期为 1 小时)
  • 检查分片数量:首先在主表 file_upload 中找到文件的大小,然后每个分块 5MB 计算应该有多少块,再从数据库 chunk_info 中根据 userId 和 file_MD5 查询成功上传了多少个分块
posted @ 2026-01-26 17:57  Huangyien  阅读(5)  评论(0)    收藏  举报