minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现(二)

简介: minio&前后端分离上传视频/上传大文件——前后端分离断点续传&minio分片上传实现

5.5 MinioClientUtils工具类

package com.zhulang.waveedu.common.util;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.UploadObjectArgs;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.http.MediaType;
import java.io.*;
/**
 * 操作minio的工具类
 *
 * @author 狐狸半面添
 * @create 2023-02-08 22:08
 */
public class MinioClientUtils {
    private final MinioClient minioClient;
    public MinioClientUtils(MinioClient minioClient) {
        this.minioClient = minioClient;
    }
    /**
     * 获取minio文件的输入流对象
     *
     * @param bucket   桶
     * @param filePath 文件路径
     * @return 输入流
     * @throws Exception 异常
     */
    public InputStream getObject(String bucket, String filePath) throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(filePath).build());
    }
    /**
     * 将分块文件上传到分布式文件系统
     *
     * @param bytes    文件的字节数组
     * @param bucket   桶
     * @param filePath 存储在桶中的文件路径
     */
    public void uploadChunkFile(byte[] bytes, String bucket, String filePath) throws Exception {
        // 1.指定资源的媒体类型为未知二进制流,以分片形式上传至minio
        try (
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)
        ) {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucket)
                            .object(filePath)
                            // InputStream stream, long objectSize 对象大小, long partSize 分片大小(-1表示5M,最大不要超过5T,最多10000)
                            .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                            .contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE)
                            .build()
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 将文件上传到分布式文件系统
     *
     * @param bytes    文件的字节数组
     * @param bucket   桶
     * @param filePath 存储在桶中的文件路径
     */
    public void uploadFile(byte[] bytes, String bucket, String filePath) throws Exception {
        // 1.指定资源的媒体类型,默认未知二进制流
        String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        // 2.判断是否有后缀,有后缀则根据后缀推算出文件类型,否则使用默认的未知二进制流
        if (filePath.contains(".")) {
            // 取objectName中的扩展名
            String extension = filePath.substring(filePath.lastIndexOf("."));
            ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
            if (extensionMatch != null) {
                contentType = extensionMatch.getMimeType();
            }
        }
        // 3.以分片形式上传至minio
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket(bucket)
                .object(filePath)
                // InputStream stream, long objectSize 对象大小, long partSize 分片大小(-1表示5M,最大不要超过5T,最多10000)
                .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                .contentType(contentType)
                .build();
        // 上传
        minioClient.putObject(putObjectArgs);
    }
    /**
     * 根据文件路径将文件上传到文件系统
     *
     * @param naiveFilePath 本地文件路径
     * @param bucket        桶
     * @param minioFilePath 保存到minio的文件路径位置
     * @throws Exception 异常
     */
    public void uploadChunkFile(String naiveFilePath, String bucket, String minioFilePath) throws Exception {
        UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                .bucket(bucket)
                .object(minioFilePath)
                .filename(naiveFilePath)
                .build();
        minioClient.uploadObject(uploadObjectArgs);
    }
    /**
     * 下载文件保存至本地临时文件中
     *
     * @param tempFilePrefix 临时文件的前缀
     * @param tempFileSuffix 临时文件的后缀
     * @param bucket         桶
     * @param filePath       文件路径
     * @return 携带数据的临时文件
     * @throws Exception 异常信息
     */
    public File downloadFile(String tempFilePrefix, String tempFileSuffix, String bucket, String filePath) throws Exception {
        // 1.创建空文件,临时保存下载下来的分块文件数据
        File tempFile = File.createTempFile(tempFilePrefix, tempFileSuffix);
        try (
                // 2.获取目标文件的输入流对象
                InputStream inputStream = getObject(bucket, filePath);
                // 3.获取临时空文件的输出流对象
                FileOutputStream outputStream = new FileOutputStream(tempFile);
        ) {
            // 4.进行数据拷贝
            IOUtils.copy(inputStream, outputStream);
            // 5.返回保存了数据的临时文件
            return tempFile;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
//    public File downloadFile(String tempFilePrefix, String tempFileSuffix, String bucket, String filePath) throws Exception {
//        // 1.创建空文件,临时保存下载下来的分块文件数据
//        File tempFile = File.createTempFile(tempFilePrefix, tempFileSuffix);
//        try {
//            Long start = System.currentTimeMillis();
//            minioClient.downloadObject(
//                    DownloadObjectArgs.builder()
//                            // 指定 bucket 存储桶
//                            .bucket(bucket)
//                            // 指定 哪个文件
//                            .object(filePath)
//                            // 指定存放位置与名称
//                            .filename(tempFile.getPath())
//                            .build());
//            Long end = System.currentTimeMillis();
//            System.out.println("下载分块时间:"+(end-start)+"ms");
//            // 5.返回保存了数据的临时文件
//            return tempFile;
//        } catch (Exception e) {
//            throw new RuntimeException(e.getMessage());
//        }
//    }
}

5.6 FileTypeUtils工具类

package com.zhulang.waveedu.common.util;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
/**
 * 文件类型工具类
 *
 * @author 狐狸半面添
 * @create 2023-02-09 0:13
 */
public class FileTypeUtils {
    private static final Map<String, String> contentType = new HashMap<>();
    /**
     * 获取文件的 mime 类型
     *
     * @param file 文件
     * @return mime类型
     */
    public static String getMimeType(File file) {
        AutoDetectParser parser = new AutoDetectParser();
        parser.setParsers(new HashMap<MediaType, Parser>());
        Metadata metadata = new Metadata();
        metadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, file.getName());
        try (InputStream stream = Files.newInputStream(file.toPath())) {
            parser.parse(stream, new DefaultHandler(), metadata, new ParseContext());
        } catch (Exception e) {
            throw new RuntimeException();
        }
        return metadata.get(HttpHeaders.CONTENT_TYPE);
    }
    /**
     * 根据 mimetype 获取文件的简单类型
     *
     * @param mimeType mime类型
     * @return 简单类型:文本,图片,音频,视频,其它
     */
    public static String getSimpleType(String mimeType) {
        String simpleType = mimeType.split("/")[0];
        switch (simpleType) {
            case "text":
                return "文本";
            case "image":
                return "图片";
            case "audio":
                return "音频";
            case "video":
                return "视频";
            case "application":
                return "其它";
            default:
                throw new RuntimeException("mimeType格式错误");
        }
    }
    // 测试
    public static void main(String[] args) {
        File file = new File("D:\\location语法规则.docx");
        String mimeType = getMimeType(file);
        System.out.println(mimeType);
        System.out.println(getSimpleType(mimeType));
    }

5.7 FileFormatUtils工具类

package com.zhulang.waveedu.common.util;
import java.text.DecimalFormat;
/**
 * 文件格式工具
 *
 * @author 狐狸半面添
 * @create 2023-02-09 19:43
 */
public class FileFormatUtils {
    /**
     * 将字节单位的文件大小转为格式化的文件大小表示
     *
     * @param fileLength 文件字节大小
     * @return 格式化文件大小表示
     */
    public static String formatFileSize(long fileLength) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize = "0B";
        if (fileLength == 0) {
            return wrongSize;
        }
        if (fileLength < 1024) {
            fileSizeString = df.format((double) fileLength) + " B";
        } else if (fileLength < 1048576) {
            fileSizeString = df.format((double) fileLength / 1024) + " KB";
        } else if (fileLength < 1073741824) {
            fileSizeString = df.format((double) fileLength / 1048576) + " MB";
        } else {
            fileSizeString = df.format((double) fileLength / 1073741824) + " GB";
        }
        return fileSizeString;
    }
}

5.8 CipherUtils加密解密工具类

package com.zhulang.waveedu.common.util;
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
 * AES加密解密工具
 *
 * @author 狐狸半面添
 * @create 2023-01-18 20:34
 */
public class CipherUtils {
    private static final String SECRET_KEY = "tangyulang5201314";
    private static final String AES = "AES";
    private static final String CHARSET_NAME = "UTF-8";
    /**
     * 生成密钥 key
     *
     * @param password 加密密码
     * @return
     * @throws Exception
     */
    private static SecretKeySpec generateKey(String password) throws Exception {
        // 1.构造密钥生成器,指定为AES算法,不区分大小写
        KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
        // 2. 因为AES要求密钥的长度为128,我们需要固定的密码,因此随机源的种子需要设置为我们的密码数组
        // 生成一个128位的随机源, 根据传入的字节数组
        /*
         * 这种方式 windows 下正常, Linux 环境下会解密失败
         * keyGenerator.init(128, new SecureRandom(password.getBytes()));
         */
        // 兼容 Linux
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(password.getBytes());
        keyGenerator.init(128, random);
        // 3.产生原始对称密钥
        SecretKey original_key = keyGenerator.generateKey();
        // 4. 根据字节数组生成AES密钥
        return new SecretKeySpec(original_key.getEncoded(), AES);
    }
    /**
     * 加密
     *
     * @param content  加密的内容
     * @param password 加密密码
     * @return
     */
    private static String aESEncode(String content, String password) {
        try {
            // 根据指定算法AES自成密码器
            Cipher cipher = Cipher.getInstance(AES);
            // 基于加密模式和密钥初始化Cipher
            cipher.init(Cipher.ENCRYPT_MODE, generateKey(password));
            // 单部分加密结束, 重置Cipher, 获取加密内容的字节数组(这里要设置为UTF-8)防止解密为乱码
            byte[] bytes = cipher.doFinal(content.getBytes(CHARSET_NAME));
            // 将加密后的字节数组转为字符串返回
            return Base64.getUrlEncoder().encodeToString(bytes);
        } catch (Exception e) {
            // 如果有错就返回 null
            return null;
        }
    }
    /**
     * 解密
     *
     * @param content  解密内容
     * @param password 解密密码
     * @return
     */
    private static String AESDecode(String content, String password) {
        try {
            // 将加密并编码后的内容解码成字节数组
            byte[] bytes = Base64.getUrlDecoder().decode(content);
            // 这里指定了算法为AES
            Cipher cipher = Cipher.getInstance(AES);
            // 基于解密模式和密钥初始化Cipher
            cipher.init(Cipher.DECRYPT_MODE, generateKey(password));
            // 单部分加密结束,重置Cipher
            byte[] result = cipher.doFinal(bytes);
            // 将解密后的字节数组转成 UTF-8 编码的字符串返回
            return new String(result, CHARSET_NAME);
        } catch (Exception e) {
            // 如果有错就返回 null
            return null;
        }
    }
    /**
     * 加密
     *
     * @param content 加密内容
     * @return 加密结果
     */
    public static String encrypt(String content) {
        return aESEncode(content, SECRET_KEY);
    }
    /**
     * 解密
     *
     * @param content 解密内容
     * @return 解密结果
     */
    public static String decrypt(String content) {
        try {
            return AESDecode(content, SECRET_KEY);
        } catch (Exception e) {
            return null;
        }
    }
}

5.9 MediaFile.java

package com.zhulang.waveedu.service.po;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * 第三方服务-媒资文件表
 * </p>
 *
 * @author 狐狸半面添
 * @since 2023-02-08
 */
@TableName("service_media_file")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MediaFile implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 主键id(雪花算法)
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /**
     * 文件名称
     */
    private String fileName;
    /**
     * 文件类型:文本,图片,音频,视频,其它
     */
    private String fileType;
    /**
     * 文件格式
     */
    private String fileFormat;
    /**
     * 标签
     */
    private String tag;
    /**
     * 存储桶
     */
    private String bucket;
    /**
     * 文件存储路径
     */
    private String filePath;
    /**
     * 文件的md5值
     */
    private String fileMd5;
    /**
     * 文件字节大小
     */
    private Long fileByteSize;
    /**
     * 文件格式化大小
     */
    private String fileFormatSize;
    /**
     * 上传人id
     */
    private Long userId;
    /**
     * 创建时间(上传时间)
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * 修改时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

相关文章
|
JavaScript 前端开发 Java
springboot整合minio+vue实现大文件分片上传,断点续传(复制可用,包含minio工具类)
springboot整合minio+vue实现大文件分片上传,断点续传(复制可用,包含minio工具类)
4378 2
|
存储 前端开发 Java
spring boot 实现Minio分片上传
spring boot 实现Minio分片上传
1765 1
|
2月前
|
存储 监控 前端开发
大文件上传下载处理方案-断点续传,秒传,分片,合并
本文介绍了大文件上传下载的断点续传技术方案。上传方面,通过前端将大文件分块(如5MB/块),后端使用MinIO存储分块并合并,实现断点续传和秒传功能。下载方面,采用Range请求分片下载,前端合并分片触发下载。技术要点包括:1)前端分块计算MD5;2)后端MinIO存储管理;3)分片校验与合并;4)进度监控和异常处理。该方案解决了大文件传输中断问题,提升用户体验,适用于视频等大文件传输场景,完整代码示例包含前后端实现。
|
存储 Java 文件存储
|
云安全 安全
阿里云服务器25端口解封教程完美解决25端口开通问题
阿里云服务器25端口如何申请解封?默认阿里云25端口是封禁的,一般建议使用465端口代替,但是也可以申请解封25端口,阿小云来详细说下阿里云服务器25端口解封教程
12081 0
 阿里云服务器25端口解封教程完美解决25端口开通问题
|
2月前
|
前端开发 Java Maven
MinIO的预签名直传机制
我们传统使用MinIo做OSS对象存储的应用方式往往都是在后端配置与MinIO的连接和文件上传下载的相关接口,然后我们在前端调用这些接口完成文件的上传下载机制,但是,当并发量过大,频繁访问会对后端的并发往往会对服务器造成极大的压力,大文件传输场景下,服务器被迫承担数据中转的角色,既消耗大量带宽资源,又形成单点性能瓶颈。这时,我们引入了MinIO的一种预签名机制。
MinIO的预签名直传机制
|
存储 前端开发 Java
学成在线笔记+踩坑(5)——【媒资模块】上传视频,断点续传
上传视频,MinIO断点续传、检查文件/分块、上传分块、合并分块
学成在线笔记+踩坑(5)——【媒资模块】上传视频,断点续传
|
存储 前端开发 JavaScript
SpringBoot结合Minio实现文件切片极速上传
【10月更文挑战第21天】
1016 6
|
存储 前端开发 Java
如何使用 Spring 上传文件:全面指南
如何使用 Spring 上传文件:全面指南
1584 1