SpringBoot集成minio分片上传合并(前端直传minio)

 数据库效果图:

 

代码部分:自己搭建minio测试  Result和配置文件可以自己编写 不需要和我一样

my.storage.minio.protocol                   = http
my.storage.minio.endPoint                   = 192.168.3.115:9000
my.storage.minio.stsEndPoint                = 192.168.3.115:9000
my.storage.minio.accessKey                  = ZCEqAP5ECpdyt4pg9VIx
my.storage.minio.secretKey                  = dAlXmXdHWjYOZb8IWBowJGnGZzeKL9OC3TmYR8uO
my.storage.minio.internetProtocol           = http
my.storage.minio.internetEndPoint           = 192.168.3.115:9000
my.storage.minio.intranetProtocol           = http
my.storage.minio.intranetEndPoint           = 192.168.3.115:9000



    public Result checkFileByMd5(FileCheckAndMergeForm fileCheckAndMergeForm) {
        String md5 = fileCheckAndMergeForm.getMd5();
        String bucket = fileCheckAndMergeForm.getBucket();
        FileUploadInfo fileUploadInfo = (FileUploadInfo)redisUtil.get(md5);
        if (fileUploadInfo != null) {
            List<Integer> listParts = minioUtil.getListParts(fileUploadInfo.getObject(), fileUploadInfo.getUploadId(),bucket);
            fileUploadInfo.setListParts(listParts);
            return Result.ok("上传中")
                    .build("fileUploadInfo", fileUploadInfo);
        }
        ShardFilesUpload shardFilesUpload = shardFilesUploadMapper.selectShardFilesUploadByMd5(md5,bucket);
        if (shardFilesUpload != null) {
            FileUploadInfo dbFileInfo = BeanUtil.copyFrom(shardFilesUpload, FileUploadInfo.class);
            return Result.ok("上传成功")
                    .build("fileUploadInfo", dbFileInfo);
        }
        return Result.ok("未上传");
    }

    @SneakyThrows
    public Result initMultipartUpload(FileUploadInfo fileUploadInfo) {
        FileUploadInfo redisFileUploadInfo = (FileUploadInfo)redisUtil.get(fileUploadInfo.getMd5());
        String bucket = fileUploadInfo.getBucket();
        // 若 redis 中有该 md5 的记录,以 redis 中为主
        String object;
        if (redisFileUploadInfo != null) {
            fileUploadInfo = redisFileUploadInfo;
            object = redisFileUploadInfo.getObject();
        } else {
            String originFileName = fileUploadInfo.getOriginFileName();
            String suffix = FileUtil.extName(originFileName);
            String fileName = FileUtil.mainName(originFileName);
            // 对文件重命名,并以年月日文件夹格式存储
            String nestFile = DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd");
            object = nestFile + "/" + fileName + "_" + fileUploadInfo.getMd5() + "." + suffix;
            fileUploadInfo.setObject(object).setType(suffix);
        }
        UploadUrlsVO urlsVO;
        // 单文件上传
        if (fileUploadInfo.getChunkCount() == 1) {
            urlsVO = minioUtil.getUploadObjectUrl(fileUploadInfo.getContentType(), object,bucket);
        } else {
            // 分片上传
            urlsVO = minioUtil.initMultiPartUpload(fileUploadInfo, object,bucket);
        }
        fileUploadInfo.setUploadId(urlsVO.getUploadId());
        // 存入 redis
        redisUtil.set(fileUploadInfo.getMd5(), fileUploadInfo, 1, TimeUnit.DAYS);
        return Result.ok()
                .build("urlsVO", urlsVO);
    }


    public Result mergeMultipartUpload(FileCheckAndMergeForm fileCheckAndMergeForm){
        String md5 = fileCheckAndMergeForm.getMd5();
        String bucket = fileCheckAndMergeForm.getBucket();
        FileUploadInfo redisFileUploadInfo = (FileUploadInfo)redisUtil.get(md5);
        if (redisFileUploadInfo==null){
            return Result.error("文件处理失败");
        }
        MyProperties.Minio minio = myProperties.getStorage().getMinio();
        String url = String.format("%s://%s/%s/%s", minio.getProtocol(), minio.getEndPoint(), bucket, redisFileUploadInfo.getObject());
          //获取真实的请求IP  如果是互联网地址,则将minio 地址替换
        if (getRequest() != null) {
            String ipAddr = IpUtil.getIpAddr(getRequest());
            if (IpUtil.isExternalIP(ipAddr)) {
                url = url.replaceFirst(minio.getProtocol(), minio.getInternetProtocol());
                url = url.replaceFirst(minio.getEndPoint(), minio.getInternetEndPoint());
            } else {
                url = url.replaceFirst(minio.getProtocol(), minio.getIntranetProtocol());
                url = url.replaceFirst(minio.getEndPoint(), minio.getIntranetEndPoint());
            }
        }
        // 更新数据库
        ShardFilesUpload shardFilesUpload = BeanUtil.copyFrom(redisFileUploadInfo, ShardFilesUpload.class);
        shardFilesUpload.setId(UUID.randomUUID().toString());
        shardFilesUpload.setUrl(url);
        shardFilesUpload.setIsDelete(0);
        shardFilesUpload.setBucket(bucket);
        shardFilesUpload.setCreateTime(System.currentTimeMillis());
        Integer chunkCount = redisFileUploadInfo.getChunkCount();
        // 分片为 1 ,不需要合并,否则合并后看返回的是 true 还是 false
        boolean isSuccess = chunkCount == 1 || minioUtil.mergeMultipartUpload(redisFileUploadInfo.getObject(), redisFileUploadInfo.getUploadId(),bucket);
        if (isSuccess) {
            shardFilesUploadMapper.insert(shardFilesUpload);
            redisUtil.del(md5);
            return Result.ok()
                    .build("storage", "minio")
                    .build("url", url);
        }
        return Result.error("文件处理失败");
    }

    /**
     * 检查文件是否存在
     */
    @RequestMapping (value = "/multipart/check", method = RequestMethod.POST)
    public Result checkFileByMd5(@RequestBody @Valid FileCheckAndMergeForm fileCheckAndMergeForm) {
        return sdkService.checkFileByMd5(fileCheckAndMergeForm);
    }

    /**
     * 初始化文件分片地址及相关数据
     */
    @RequestMapping (value = "/multipart/init", method = RequestMethod.POST)
    public Result initMultiPartUpload(@RequestBody @Valid FileUploadInfo fileUploadInfo) {
        return sdkService.initMultipartUpload(fileUploadInfo);
    }

    /**
     * 文件合并(单文件不会合并,仅信息入库)
     */
    @RequestMapping (value = "/multipart/merge", method = RequestMethod.POST)
    public Result mergeMultipartUpload(@RequestBody @Valid FileCheckAndMergeForm fileCheckAndMergeForm) {
        return sdkService.mergeMultipartUpload(fileCheckAndMergeForm);
    }
  Result checkFileByMd5(FileCheckAndMergeForm fileCheckAndMergeForm) ;

    Result initMultipartUpload(FileUploadInfo fileUploadInfo) ;

    Result mergeMultipartUpload(FileCheckAndMergeForm fileCheckAndMergeForm);

 数据库是postgresSQL

DROP TABLE IF EXISTS religious.shard_files_upload;

CREATE TABLE religious.shard_files_upload
(
    id               VARCHAR(255) NOT NULL,
    upload_id        VARCHAR(255),
    md5              VARCHAR(255),
    url              VARCHAR(255),
    bucket           VARCHAR(64),
    object           VARCHAR(255),
    origin_file_name VARCHAR(255),
    size             BIGINT,
    type             VARCHAR(64),
    chunk_size       BIGINT,
    chunk_count      INTEGER,
    is_delete        INTEGER,
    create_time      BIGINT,
    PRIMARY KEY (id)
);

COMMENT ON TABLE religious.shard_files_upload IS '分片上传记录明细表';
COMMENT ON COLUMN religious.shard_files_upload.upload_id IS '文件上传id';
COMMENT ON COLUMN religious.shard_files_upload.md5 IS '文件计算md5';
COMMENT ON COLUMN religious.shard_files_upload.url IS '文件访问地址';
COMMENT ON COLUMN religious.shard_files_upload.bucket IS '存储桶';
COMMENT ON COLUMN religious.shard_files_upload.object IS 'minio中文件名';
COMMENT ON COLUMN religious.shard_files_upload.origin_file_name IS '原始文件名';
COMMENT ON COLUMN religious.shard_files_upload.size IS '文件大小';
COMMENT ON COLUMN religious.shard_files_upload.type IS '文件类型';
COMMENT ON COLUMN religious.shard_files_upload.chunk_size IS '分片大小';
COMMENT ON COLUMN religious.shard_files_upload.chunk_count IS '分片数量';
COMMENT ON COLUMN religious.shard_files_upload.is_delete IS '是否删除';
COMMENT ON COLUMN religious.shard_files_upload.create_time IS '创建时间';
package net.junnan.religious.base.api.config;


import com.google.common.collect.Multimap;
import io.minio.CreateMultipartUploadResponse;
import io.minio.ListPartsResponse;
import io.minio.MinioAsyncClient;
import io.minio.ObjectWriteResponse;
import io.minio.messages.Part;

public class CustomMinioClient extends MinioAsyncClient {

    /**
     * 继承父类
     * @param client
     */
    public CustomMinioClient(MinioAsyncClient client) {
        super(client);
    }


    /**
     * 初始化分片上传、获取 uploadId
     * @param bucket String  存储桶名称
     * @param region String
     * @param object String   文件名称
     * @param headers Multimap<String, String> 请求头
     * @param extraQueryParams Multimap<String, String>
     * @return String
     */
    public String initMultiPartUpload(String bucket, String region, String object, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws Exception {
        CreateMultipartUploadResponse response = super.createMultipartUploadAsync(bucket, region, object, headers, extraQueryParams).get();
        return response.result().uploadId();
    }

    /**
     * 合并分片
     * @param bucketName String   桶名称
     * @param region String
     * @param objectName String   文件名称
     * @param uploadId String   上传的 uploadId
     * @param parts Part[]   分片集合
     * @param extraHeaders Multimap<String, String>
     * @param extraQueryParams Multimap<String, String>
     * @return ObjectWriteResponse
     */
    public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws Exception {
        return super.completeMultipartUploadAsync(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams).get();
    }

    /**
     * 查询当前上传后的分片信息
     * @param bucketName String   桶名称
     * @param region String
     * @param objectName String   文件名称
     * @param maxParts Integer  分片数量
     * @param partNumberMarker Integer  分片起始值
     * @param uploadId String   上传的 uploadId
     * @param extraHeaders Multimap<String, String>
     * @param extraQueryParams Multimap<String, String>
     * @return ListPartsResponse
     */
    public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws Exception {
        return super.listPartsAsync(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams).get();
    }


}
package net.junnan.religious.base.api.form.common.Sdk;

import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
 * @title: FileCheckAndMergeForm
 * @Author CaoJun
 * @Date: 2025/5/7 上午11:27
 * @Version 1.0
 */
@Data
public class FileCheckAndMergeForm {

    @NotBlank(message = "bucket 不可为空")
    private String bucket;

    @NotBlank(message = "文件的MD5值不可为空")
    private String md5;
}
package net.junnan.religious.base.api.form.religious.ShardFilesUpload;


import lombok.Data;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * 文件上传信息,查询 redis 后的返回信息
 */
@Data
@Accessors(chain = true)
public class FileUploadInfo {
    @NotBlank(message = "md5 不能为空")
    private String md5;

    private String uploadId;

    @NotBlank(message = "文件名不能为空")
    private String originFileName;

    // 仅秒传会有值
    private String url;
    // 后端使用
    private String object;

    private String type;

    @NotNull(message = "文件大小不能为空")
    private Long size;

    @NotNull(message = "分片数量不能为空")
    private Integer chunkCount;

    @NotNull(message = "分片大小不能为空")
    private Long chunkSize;

    private String contentType;

    // listParts 从 1 开始,前端需要上传的分片索引+1
    private List<Integer> listParts;

    @NotBlank(message = "bucket 不可为空")
    private String bucket;

}
package net.junnan.religious.base.api.util;

import cn.hutool.core.util.IdUtil;
import com.google.common.collect.HashMultimap;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Part;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.junnan.religious.base.api.config.CustomMinioClient;
import net.junnan.religious.base.api.form.religious.ShardFilesUpload.FileUploadInfo;
import net.junnan.religious.base.api.form.religious.ShardFilesUpload.UploadUrlsVO;
import net.junnan.religious.base.api.properties.MyProperties;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
public class MinioUtil {

    private CustomMinioClient customMinioClient;

    @Resource
    MyProperties myProperties;

    // spring自动注入会失败
    @PostConstruct
    public void init() {
        MyProperties.Minio minio = myProperties.getStorage().getMinio();
        MinioAsyncClient minioClient = MinioAsyncClient.builder()
                .endpoint(String.format("%s://%s", minio.getProtocol(), minio.getEndPoint()))
                .credentials(minio.getAccessKey(), minio.getSecretKey())
                .build();
        customMinioClient = new CustomMinioClient(minioClient);
    }

    /**
     * 获取 Minio 中已经上传的分片文件
     * @param object 文件名称
     * @param uploadId 上传的文件id(由 minio 生成)
     * @return List<Integer>
     */
    @SneakyThrows
    public List<Integer> getListParts(String object, String uploadId,String bucketName) {
        List<Part> parts = getParts(object, uploadId,bucketName);
        return parts.stream()
                .map(Part::partNumber)
                .collect(Collectors.toList());
    }

    /**
     * 单文件签名上传
     * @param object 文件名称(uuid 格式)
     * @return UploadUrlsVO
     */
    public UploadUrlsVO getUploadObjectUrl(String contentType, String object,String bucketName) throws Exception {
        try {
            UploadUrlsVO urlsVO = new UploadUrlsVO();
            List<String> urlList = new ArrayList<>();
            // 主要是针对图片,若需要通过浏览器直接查看,而不是下载,需要指定对应的 content-type
            HashMultimap<String, String> headers = HashMultimap.create();
            if (contentType == null || contentType.equals("")) {
                contentType = "application/octet-stream";
            }
            headers.put("Content-Type", contentType);
            String uploadId = IdUtil.simpleUUID();
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            String url = customMinioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.PUT)
                    .bucket(bucketName)
                    .object(object)
                    .extraHeaders(headers)
                    .extraQueryParams(reqParams)
                    .expiry(1, TimeUnit.DAYS)
                    .build());
            urlList.add(url);
            urlsVO.setUploadId(uploadId).setUrls(urlList);
            return urlsVO;
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    /**
     * 初始化分片上传
     * @param fileUploadInfo 前端传入的文件信息
     * @param object object
     * @return UploadUrlsVO
     */
    public UploadUrlsVO initMultiPartUpload(FileUploadInfo fileUploadInfo, String object,String bucketName) throws Exception {
        Integer chunkCount = fileUploadInfo.getChunkCount();
        String contentType = fileUploadInfo.getContentType();
        String uploadId = fileUploadInfo.getUploadId();
        UploadUrlsVO urlsVO = new UploadUrlsVO();
        try {
            HashMultimap<String, String> headers = HashMultimap.create();
            if (contentType == null || contentType.equals("")) {
                contentType = "application/octet-stream";
            }
            headers.put("Content-Type", contentType);
            // 如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
            if (fileUploadInfo.getUploadId() == null || fileUploadInfo.getUploadId().equals("")) {
                uploadId = customMinioClient.initMultiPartUpload(bucketName, null, object, headers, null);
            }
            urlsVO.setUploadId(uploadId);
            List<String> partList = new ArrayList<>();
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            for (int i = 1; i <= chunkCount; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String uploadUrl = customMinioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(object)
                        .expiry(1, TimeUnit.DAYS)
                        .extraQueryParams(reqParams)
                        .build());
                partList.add(uploadUrl);
            }
            urlsVO.setUrls(partList);
            return urlsVO;
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    /**
     * 合并文件
     * @param object object
     * @param uploadId uploadUd
     */
    @SneakyThrows
    public boolean mergeMultipartUpload(String object, String uploadId,String bucketName) {
        // 获取所有分片
        List<Part> partsList = getParts(object, uploadId,bucketName);
        Part[] parts = new Part[partsList.size()];
        int partNumber = 1;
        for (Part part : partsList) {
            parts[partNumber - 1] = new Part(partNumber, part.etag());
            partNumber++;
        }
        // 合并分片
        customMinioClient.mergeMultipartUpload(bucketName, null, object, uploadId, parts, null, null);
        return true;
    }

    @NotNull
    private  List<Part> getParts(String object, String uploadId,String bucketName) throws Exception {
        int partNumberMarker = 0;
        boolean isTruncated = true;
        List<Part> parts = new ArrayList<>();
        while(isTruncated){
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, object, 1000, partNumberMarker, uploadId, null, null);
            parts.addAll(partResult.result().partList());
            // 检查是否还有更多分片
            isTruncated = partResult.result().isTruncated();
            if (isTruncated) {
                // 更新partNumberMarker以获取下一页的分片数据
                partNumberMarker = partResult.result().nextPartNumberMarker();
            }
        }
        return parts;
    }

}
package net.junnan.religious.base.api.util;


import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间和单位
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @param unit 单位
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time, TimeUnit unit) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, unit);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================

    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    //============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    //================有序集合 sort set===================

    /**
     * 有序set添加元素
     * @param key
     * @param value
     * @param score
     * @return
     */
    public boolean zSet(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public long batchZSet(String key, Set<ZSetOperations.TypedTuple> typles) {
        return redisTemplate.opsForZSet().add(key, typles);
    }

    public void zIncrementScore(String key, Object value, long delta) {
        redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
        redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 获取zset数量
     * @param key
     * @param value
     * @return
     */
    public long getZsetScore(String key, Object value) {
        Double score = redisTemplate.opsForZSet().score(key, value);
        if (score == null) {
            return 0;
        } else {
            return score.longValue();
        }
    }

    /**
     * 获取有序集 key 中成员 member 的排名 。
     * 其中有序集成员按 score 值递减 (从大到小) 排序。
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple> getZSetRank(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
    }

}
package net.junnan.religious.base.api.entity.common;

import lombok.Data;

import java.io.Serializable;

/**
 * @title: ShardFilesUpload
 * @Author CaoJun
 * @Date: 2025/5/6 上午11:14
 * @Version 1.0
 */
@Data
public class ShardFilesUpload implements Serializable {
    private static final long serialVersionUID = 1L;

    private String id ;
    /** 文件上传id */

    private String uploadId ;
    /** 文件计算md5 */

    private String md5 ;
    /** 文件访问地址 */

    private String url ;
    /** 存储桶 */

    private String bucket ;
    /** minio中文件名 */

    private String object ;
    /** 原始文件名 */

    private String originFileName ;
    /** 文件大小 */

    private Long size ;
    /** 文件类型 */

    private String type ;
    /** 分片大小 */

    private Long chunkSize ;
    /** 分片数量 */

    private Integer chunkCount ;
    /** 是否删除 */

    private Integer isDelete ;
    /** 创建时间 */

    private Long createTime ;

}

package net.junnan.religious.base.api.mapper.common;

import net.junnan.religious.base.api.entity.common.ShardFilesUpload;
import org.apache.ibatis.annotations.Mapper;

/**
 * @title: ShardFilesUploadMapper
 * @Author CaoJun
 * @Date: 2025/5/6 上午11:13
 * @Version 1.0
 */
@Mapper
public interface ShardFilesUploadMapper{

    /**
     * 新增数据
     *
     * @param shardFilesUpload 实例对象
     * @return 影响行数
     */
    int insert(ShardFilesUpload shardFilesUpload);

    ShardFilesUpload selectShardFilesUploadByMd5(String md5,String bucket);

}

@SneakyThrows
@Override
public Result checkFileByMd5(FileCheckAndMergeForm fileCheckAndMergeForm)  {
    String selector = myProperties.getStorage().getSelector();
    switch (selector) {
        case "minio":
            return minioStorage.checkFileByMd5(fileCheckAndMergeForm);
        case "aliyun":
            throw new Exception("aliyunStorage的方法缺失");
        default:
            throw new Exception("不支持的 my.storage.selector");
    }
}

@SneakyThrows
@Override
public Result initMultipartUpload(FileUploadInfo fileUploadInfo)  {
    String selector = myProperties.getStorage().getSelector();
    switch (selector) {
        case "minio":
            return minioStorage.initMultipartUpload(fileUploadInfo);
        case "aliyun":
            throw new Exception("aliyunStorage的方法缺失");
        default:
            throw new Exception("不支持的 my.storage.selector");
    }
}

@SneakyThrows
@Override
public Result mergeMultipartUpload(FileCheckAndMergeForm fileCheckAndMergeForm) {
    String selector = myProperties.getStorage().getSelector();
    switch (selector) {
        case "minio":
            return minioStorage.mergeMultipartUpload(fileCheckAndMergeForm);
        case "aliyun":
            throw new Exception("aliyunStorage的方法缺失");
        default:
            throw new Exception("不支持的 my.storage.selector");
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.junnan.religious.base.api.mapper.common.ShardFilesUploadMapper">
    <resultMap type="net.junnan.religious.base.api.entity.common.ShardFilesUpload" id="BaseResultMap">
        <result property="id" column="id" jdbcType="VARCHAR"/>
        <result property="uploadId" column="upload_id" jdbcType="VARCHAR"/>
        <result property="md5" column="md5" jdbcType="VARCHAR"/>
        <result property="url" column="url" jdbcType="VARCHAR"/>
        <result property="bucket" column="bucket" jdbcType="VARCHAR"/>
        <result property="object" column="object" jdbcType="VARCHAR"/>
        <result property="originFileName" column="origin_file_name" jdbcType="VARCHAR"/>
        <result property="size" column="size" jdbcType="BIGINT"/>
        <result property="type" column="type" jdbcType="VARCHAR"/>
        <result property="chunkSize" column="chunk_size" jdbcType="BIGINT"/>
        <result property="chunkCount" column="chunk_count" jdbcType="INTEGER"/>
        <result property="isDelete" column="is_delete" jdbcType="INTEGER"/>
        <result property="createTime" column="create_time" jdbcType="BIGINT"/>
    </resultMap>

    <select id="selectShardFilesUploadByMd5" parameterType="String" resultMap="BaseResultMap">
        select id,
               upload_id,
               md5,
               url,
               bucket,
               object,
               origin_file_name, size, type, chunk_size, chunk_count, is_delete, create_time
        from RELIGIOUS.SHARD_FILES_UPLOAD
        WHERE md5 = #{md5}
          and bucket = #{bucket}
          and is_delete =0
    </select>


    <!--新增数据-->
    <insert id="insert" keyProperty="id" useGeneratedKeys="true">
        insert into RELIGIOUS.SHARD_FILES_UPLOAD(id, upload_id, md5, url, bucket, object, origin_file_name, size, type,
                                                 chunk_size, chunk_count, is_delete, create_time)
        values (#{id}, #{uploadId}, #{md5}, #{url}, #{bucket}, #{object}, #{originFileName}, #{size}, #{type},
                #{chunkSize}, #{chunkCount}, #{isDelete}, #{createTime})
    </insert>


</mapper>

package net.junnan.religious.base.api.form.religious.ShardFilesUpload;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.List;

/**
 * 返回文件生成的分片上传地址
 */
@Data
@Accessors(chain = true)
public class UploadUrlsVO {

    private String uploadId;

    private List<String> urls;
}
package net.junnan.religious.base.api.util;

import com.alibaba.fastjson.JSON;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;

public class BeanUtil {

  


    public static <T> T copyFrom(Object obj, Class<T> clazz) {
        try {
            if (obj == null) {
                return null;
            }
            T      bean = clazz.getDeclaredConstructor().newInstance();
            String json = JSON.toJSONString(obj);
            return JSON.parseObject(json, (Type) bean.getClass());
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
            return null;
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

想买CT5的小曹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值