Java工程师实现视频文件上传minio文件系统存储及网页实现分批加载视频播放

Java工程师实现minio存储大型视频文件网页实现分批加载视频播放

一、需求说明

老板给我出个题目,让我把的电影文件上传到minio文件系统,再通过WEB端分配加载视频播放,类似于我们普通的电影网站。小编把Java代码共享出来。是真正的能拿过来直接用的。小编我亲自测过。

1.1minio版本

bash 复制代码
<dependency>
		    <groupId>io.minio</groupId>
		    <artifactId>minio</artifactId>
		    <version>7.1.4</version>
		</dependency>

1.2maven依赖

bash 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sun</groupId>
    <artifactId>springboot-sun</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--springboot工程需要继承的父工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>

    <dependencies>
    
    
    
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    
    
        <!--web开发的起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--<scope>runtime</scope>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
		<dependency>
		    <groupId>io.minio</groupId>
		    <artifactId>minio</artifactId>
		    <version>7.1.4</version>
		</dependency>
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-lang3</artifactId>
		    <version>3.12.0</version>
		</dependency>
	<dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	    <version>1.18.28</version>
	    <scope>provided</scope>
	</dependency>
	<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.12</version>
</dependency>
    </dependencies>

</project>

1.3分段视频播放Controller

bash 复制代码
package com.sun.controller;

import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.StatObjectArgs;
import io.minio.errors.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import com.sun.minio.util.MinIoUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

@RestController
public class VideoController2 {

 

    @GetMapping("/testDisplay2/{fileName}")
    public ResponseEntity<byte[]> getVideo(
            @PathVariable String fileName,
            @RequestHeader(value = "Range", required = false) String range) {
        try {
        	 String bucketName = "movie";
             String objectName = fileName + ".mp4";
             MinioClient minioClient= MinIoUtil.minioClient;
            // 根据Range请求头来设置分批加载的范围
            GetObjectArgs.Builder getObjectArgsBuilder = GetObjectArgs.builder()
                 .bucket(bucketName)
                 .object(objectName);
            long start=0l;
            if (range!= null && range.startsWith("bytes=")) {
                String[] values = range.split("=")[1].split("-");
                 start = Long.parseLong(values[0]);
                long end = values.length > 1? Long.parseLong(values[1]) : -1;
                if (end!= -1) {
                    long length = end - start + 1;
                    getObjectArgsBuilder.offset(start).length(length);
                } else {
                    getObjectArgsBuilder.offset(start);
                }
            }

            // 获取视频流
            try (InputStream inputStream = minioClient.getObject(getObjectArgsBuilder.build())) {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
                int length;
                while ((length = inputStream.read(buffer))!= -1) {
                    outputStream.write(buffer, 0, length);
                }
                byte[] videoBytes = outputStream.toByteArray();

                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.parseMediaType("video/mp4"));

                // 如果是分段请求,设置相应的响应头
                if (range!= null) {
                    ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
                    long fileSize = objectStat.length();
                    headers.set("Content-Range", "bytes " + start + "-" + (start + videoBytes.length - 1) + "/" + fileSize);
                    return new ResponseEntity<>(videoBytes, headers, HttpStatus.PARTIAL_CONTENT);
                } else {
                    headers.setContentLength(videoBytes.length);
                    return new ResponseEntity<>(videoBytes, headers, HttpStatus.OK);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

1.4 html5 播放

bash 复制代码
<html>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
    <style type="text/css">
        .upload {
            margin-top: 100px;
            margin-left: 100px;
            text-align: center;
        }

    </style>

</head>
<body>
<h1 style="text-align: center;margin-top: 20px">test</h1>
<div>
 <!--  <img src="/minio_demo/testClick/testDisplay?url=/data/temp/img/a6efce1b9d16fdfabf36882ab08f8c5495ee7b9f.jpg"> -->
</div>

<div>

  <!--   
   
 <video src="/minio_demo/testClick/testDisplay?md5=a172e15a869fd7224618840c0815dcb1" controls width="640" height="360">
    您的浏览器不支持视频标签。
</video>
   -->
   
    <video src="/minio_demo/testDisplay2/test" controls width="640" height="360">
    您的浏览器不支持视频标签。
</video>
   
   
</div>
</body>
</html>

1.5上传Controller

bash 复制代码
 package com.sun.controller;


import io.minio.errors.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import com.sun.minio.util.MinIoContentType;
import com.sun.minio.util.MinIoUtil;
import com.sun.minio.util.MinioBucketEnum;
import com.sun.service.MinioServiceImpl;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;

@RestController
@RequestMapping("testClick")
public class MinioDemoController {
	 @Autowired
	private MinioServiceImpl minioServiceImpl;

    /**
     * 直接页面展示
     * @param response
     */
    @RequestMapping("/testDisplay")
    public void testDisplay(HttpServletResponse response,String md5) throws Exception {
        MinIoUtil.displayFile(minioServiceImpl,MinioBucketEnum.VIDEO_FILES,response,md5);
    }
    @RequestMapping("/testDownload")
    public void testDownLoad(HttpServletResponse response,String md5) throws Exception {
        MinIoUtil.downloadFile(minioServiceImpl,MinioBucketEnum.VIDEO_FILES,response,md5,"");
    }
    @RequestMapping("/testLoad")
    public void testLoadFile(HttpServletResponse response,String md5,String targetPath) throws  Exception{
        MinIoUtil.loadObject(MinioBucketEnum.VIDEO_FILES,targetPath,md5);
    }
    @RequestMapping("/deleteFile")
    public String deleteFile(String md5) throws  Exception{
        MinIoUtil.deleteObject(minioServiceImpl,MinioBucketEnum.VIDEO_FILES, md5);
        return "deleteFileSucceed";
    }
    
    
    @RequestMapping("/testUpload")
    @ResponseBody
    public String testUpload(HttpServletRequest request, @RequestParam("multipartFile") MultipartFile[] multipartfiles,String url,String pucketName)throws  Exception{
        String filePath = "";
        for(MultipartFile multipartFile:multipartfiles){
            String contentType = multipartFile.getContentType();
            InputStream inputStream = multipartFile.getInputStream();
            if(!StringUtils.isBlank(url)&&!url.startsWith("/")){
                url = "/"+url;
            }
            if(!StringUtils.isBlank(url)&&!url.endsWith("/")){
                url += "/";
            }
            MinioBucketEnum minioBucketEnum = MinioBucketEnum.VIDEO_FILES;
			/*
			 * if(pucketName.equals("monthlytext")){ minioBucketEnum =
			 * MinioBucketEnum.MONTHLY_TEXT; } if(pucketName.equals("email")){
			 * minioBucketEnum = MinioBucketEnum.VIDEO_FILES; }
			 * if(pucketName.equals("excel")){ minioBucketEnum = MinioBucketEnum.EXCEL; }
			 */
            
            String md5 = MinIoUtil.upload(minioServiceImpl,minioBucketEnum,url + multipartFile.getOriginalFilename(), inputStream, MinIoContentType.getContentType(contentType));
            filePath+="<p>"+md5+"</p>";
            if(multipartFile.getOriginalFilename().contains(".mp4")) {
                filePath = "<video src=\"/minio_demo/testClick/testDisplay?md5="+md5+"\" controls width=\"640\" height=\"360\">\n" +
                        "    您的浏览器不支持视频标签。\n" +
                        "</video>";
            }
            // 构建包含两个a标签的HTML代码字符串
            StringBuilder htmlBuilder = new StringBuilder();
            htmlBuilder.append("<a href=\"/minio_demo/testClick/deleteFile?md5=").append(md5).append("\">删除文件</a>");
            htmlBuilder.append("&nbsp;&nbsp;&nbsp;&nbsp;"); // 增加一些空格间隔两个标签
            htmlBuilder.append("<a href=\"/minio_demo/testClick/testDownload?md5=").append(md5).append("\">下载文件</a>");
            // 得到最终的HTML字符串
            String htmlString = htmlBuilder.toString();
            filePath=filePath+htmlString;
           // filePath+="<p>"+md5+"</p>";
        }
        return filePath;
    }
    @RequestMapping("/testMv1")
    public ModelAndView testMv1(HttpServletRequest request){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/minio/demo");
        return modelAndView;
    }
    @RequestMapping("/testMv2")
    public ModelAndView testMv2(HttpServletRequest request){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/minio/demo2");
        return modelAndView;
    }
    @RequestMapping("/testMv3")
    public ModelAndView testMv3(){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("/minio/demo3");
        return mv;
    }
}

1.6 上传HTML5

bash 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>文件上传页面</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <style type="text/css">
        /* 整体页面样式 */
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f4f4f4;
        }

        /* 标题样式 */
        h1 {
            color: #333;
            margin-bottom: 30px;
        }

        /* 上传表单容器样式 */
      .upload {
            background-color: #fff;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            width: 400px;
        }

        /* 表单段落样式 */
      .upload p {
            margin-bottom: 20px;
        }

        /* 文件选择输入框样式 */
      .upload input[type="file"] {
            width: 100%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }

        /* 文本输入框样式 */
      .upload input[type="text"] {
            width: 100%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }

        /* 提交按钮样式 */
      .upload input[type="submit"] {
            background-color: #007BFF;
            color: #fff;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s ease;
        }

      .upload input[type="submit"]:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <h1>文件上传至MinIO</h1>
    <div class="upload">
        <form action="/minio_demo/testClick/testUpload" method="post" enctype="multipart/form-data">
            <p>
                选择文件: <input type="file" name="multipartFile" />
            </p>
            <p>
                桶名称: <input type="text" name="pucketName" value="movie" />
            </p>
            <p>
                文件目录: <input type="text" name="url" hidden />
            </p>
            <p>
                <input type="submit" value="上传并检测" />
            </p>
        </form>
    </div>
</body>
</html>

1.7 MinIoUtil工具类

bash 复制代码
package com.sun.minio.util;


import io.minio.*;
import io.minio.errors.*;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.web.multipart.MultipartFile;

import com.sun.doman.MinioRecord;
import com.sun.service.MinioServiceImpl;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.io.*;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

/**
 * minio工具类
 */

/**
 * minio工具类
 */
public class MinIoUtil {
    private static MinioServiceImpl minioService = null;
    private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(MinIoUtil.class);
    public static MinioClient minioClient = null;
    private static  String enpoint;
    private static  String username;
    private static  String password;
    static {
        try{
            Properties properties = PropertiesLoaderUtils.loadAllProperties("application.properties");
            enpoint = properties.getProperty("minioEnpoint");
            Logger.info("获取minio[enpoint]:"+enpoint);
            username = properties.getProperty("minioUserName");
            Logger.info("获取minio[minioUserName]:"+username);
            password = properties.getProperty("minioPassword");
            Logger.info("获取minio[minioPassword]"+password);
            minioClient = MinioClient.builder().endpoint(enpoint).credentials(username,password).build();
            for(MinioBucketEnum minioBucketEnum: MinioBucketEnum.values()){
                if(!bucketExist(minioBucketEnum.getBucketName())){
                    createBucket(minioBucketEnum.getBucketName());
                }
            }
        }catch (Exception e){
            Logger.error("获取minio连接失败",e);
            e.printStackTrace();
        }

    }
    
    
    
   
  
    
    
    
    //检查桶是否存在
    public static boolean bucketExist(String buckeyName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(buckeyName).build());
    }
    //创建桶
    public static  void createBucket(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, RegionConflictException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
    }



    /**
     *
     * @return
     */
    public Map<String,String> getAccess(){
        Map<String,String> map = new HashMap<>();
        map.put("username",username);
        map.put("password",password);
        return map;
    }
    /**
     *
     * @param name 文件名,可以是单纯的文件名test.pdf,也可以是类似aaa/bbb/ccc/test.pdf,如果没有重复文件名,则会为在minio中的文件路径
     * @param inputStream 文件流
     * @param contentType
     * @return
     */
    public static String upload(MinioServiceImpl minioService ,MinioBucketEnum minioBucketEnum, String name, InputStream inputStream, MinIoContentType contentType)throws Exception{
        if(StringUtils.isBlank(name)||inputStream==null){
            return null;
        }
        try{
            BufferedInputStream bis = new BufferedInputStream(inputStream);
            String md5Val = MD5Util.md5HashCode32(bis);
            MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
            if(byMd5Val==null){
                if(objectExist(minioBucketEnum.getBucketName(),name)){//minio存在同名文件,需要改一下文件名
                    name = getOtherName(name,md5Val);
                }
                boolean flag = false;
                int count = 3;
                for(int i = 0;i<count;i++){//失败尝试3次.最后一次还失败抛异常
                    if(flag){
                        break;
                    }
                    try{
                        minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketEnum.getBucketName()).object(name).contentType(contentType == null ? MinIoContentType.STREAM.contentType : contentType.contentType)
                                .stream(bis, bis.available(), partSize).build());
                        flag = true;
                    }catch (Exception e){
                        if (i==count-1){
                            throw e;
                        }
                        TimeUnit.MILLISECONDS.sleep(200);
                    }
                }
                minioService.insertOne(MinioRecord.builder().bucketName(minioBucketEnum.getBucketName()).md5Val(md5Val).minioFilePath(name).remainNum(1)
                        .createTime(DateTime.now().toString("yyyy-MM-dd HH:mm:ss")).updateTime(DateTime.now().toString("yyyy-MM-dd HH:mm:ss")).build());
            }else{
                byMd5Val.setRemainNum((byMd5Val.getRemainNum()==null?0:byMd5Val.getRemainNum())+1);
                minioService.updateRemainNum(byMd5Val);
            }
            return md5Val;
        }catch (Exception e){
            Logger.error("上传文件失败,name:"+name,e);
            throw e;
        }finally {
            try{
                inputStream.close();
            }catch (Exception e){

            }
        }
    }
    
    
    
    
    
    
    public static String upload(MinioBucketEnum minioBucketEnum, String name, InputStream inputStream, MinIoContentType contentType)throws Exception{
        if(StringUtils.isBlank(name)||inputStream==null){
            return null;
        }
        try{
            BufferedInputStream bis = new BufferedInputStream(inputStream);
            String md5Val = MD5Util.md5HashCode32(bis);
            MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
            if(byMd5Val==null){
                if(objectExist(minioBucketEnum.getBucketName(),name)){//minio存在同名文件,需要改一下文件名
                    name = getOtherName(name,md5Val);
                }
                boolean flag = false;
                int count = 3;
                for(int i = 0;i<count;i++){//失败尝试3次.最后一次还失败抛异常
                    if(flag){
                        break;
                    }
                    try{
                        minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketEnum.getBucketName()).object(name).contentType(contentType == null ? MinIoContentType.STREAM.contentType : contentType.contentType)
                                .stream(bis, bis.available(), partSize).build());
                        flag = true;
                    }catch (Exception e){
                        if (i==count-1){
                            throw e;
                        }
                        TimeUnit.MILLISECONDS.sleep(200);
                    }
                }
                minioService.insertOne(MinioRecord.builder().bucketName(minioBucketEnum.getBucketName()).md5Val(md5Val).minioFilePath(name).remainNum(1)
                        .createTime(DateTime.now().toString("yyyy-MM-dd HH:mm:ss")).updateTime(DateTime.now().toString("yyyy-MM-dd HH:mm:ss")).build());
            }else{
                byMd5Val.setRemainNum((byMd5Val.getRemainNum()==null?0:byMd5Val.getRemainNum())+1);
                minioService.updateRemainNum(byMd5Val);
            }
            return md5Val;
        }catch (Exception e){
            Logger.error("上传文件失败,name:"+name,e);
            throw e;
        }finally {
            try{
                inputStream.close();
            }catch (Exception e){

            }
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    /**
     *
     * @param name 文件名,可以是单纯的文件名test.pdf,也可以是类似aaa/bbb/ccc/test.pdf,如果没有重复文件名,则会为在minio中的文件路径
     * @param filePath 需要上传的文件路径
     * @param contentType
     * @return
     */
    public static String upload(MinioBucketEnum minioBucketEnum, String name, String filePath, MinIoContentType contentType) throws Exception {
        File file = new File(filePath);
        if(!file.exists()||minioBucketEnum==null){
            return null;
        }
        return upload(minioBucketEnum,name, new FileInputStream(file), contentType);
    }

    /**
     * 基于MultipartFile文件上传
     * @param minioBucketEnum 桶信息
     * @param file 文件
     * @param contentType 内容类型
     * @return 返回结果
     * @throws Exception 异常
     */
    public static String upload(@NotNull MinioBucketEnum minioBucketEnum, @NotNull MultipartFile file, MinIoContentType contentType) throws Exception {
        String filename = file.getOriginalFilename();
        byte[] bytes = file.getBytes();
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        String md5Str = upload(minioBucketEnum, filename, bais, contentType);
        bais.close();
        return md5Str;
    }


    /**
     *

     * @param response
     * @param md5ValOrOldPath 新文件上传则是md5值,旧文件则依然使用旧文件的路径
     * @param exportFileName 最终给到前端的文件名
     */
    public static void downloadFile(MinioServiceImpl minioService ,MinioBucketEnum minioBucketEnum, HttpServletResponse response, String md5ValOrOldPath,String exportFileName){
        downloadFileByMd5(minioService,minioBucketEnum,response,checkOutMd5Val(md5ValOrOldPath),exportFileName);
    }
    /**
     * 根据文件md5值获取
     * @param md5Val
     * @return
     */
    private static void downloadFileByMd5(MinioServiceImpl minioService ,MinioBucketEnum minioBucketEnum, HttpServletResponse response, String md5Val,String exportFileName){
        if(StringUtils.isBlank(md5Val)||response==null){
            return ;
        }
        try {
            MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
            if(byMd5Val!=null){
                ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(minioBucketEnum.getBucketName()).object(byMd5Val.getMinioFilePath()).build());
                DownloadUtils.downloadFile(response, minioClient.getObject(GetObjectArgs.builder().bucket(minioBucketEnum.getBucketName()).object(byMd5Val.getMinioFilePath()).build())
                        , objectStat.name(), objectStat.length(), objectStat.contentType(), true, -1,exportFileName);
            }
        } catch (Exception e) {
            Logger.error("下载文件失败",e);
            e.printStackTrace();
        }
    }

    /**
     *
     * @param response
     * @param md5ValOrOldPath 新文件上传则是md5值,旧文件则依然使用旧文件的路径,最终都是通过文件md5找文件
     * @throws Exception
     */
    public static void displayFile(MinioServiceImpl minioService ,MinioBucketEnum minioBucketEnum, HttpServletResponse response, String md5ValOrOldPath) throws Exception {

        displayFileByMd5( minioService ,minioBucketEnum,response,checkOutMd5Val(md5ValOrOldPath));
    }

    /**
     * 根据MD5值展示文件
     * @param response
     * @param md5Val
     */
    private static void displayFileByMd5(MinioServiceImpl minioService ,MinioBucketEnum minioBucketEnum, HttpServletResponse response, String md5Val) throws Exception {
        MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
        ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(minioBucketEnum.getBucketName()).object(byMd5Val.getMinioFilePath()).build());
        String objectName = objectStat.name();
        String fileName = objectName.substring(objectName.lastIndexOf("/")+1);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type",objectStat.contentType());
        response.addHeader("Content-Disposition","inline;filename=" +  URLEncoder.encode(fileName, "UTF-8"));
        response.addHeader("Content-Length","" + objectStat.length());
        try(
                ServletOutputStream outputStream = response.getOutputStream();
                BufferedInputStream bufferedInputStream = new BufferedInputStream(getObjectInputStream(minioBucketEnum.getBucketName(),objectName));
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
        ){
            int lenth;
            byte[] bytes = new byte[1024];
            while ((lenth=bufferedInputStream.read(bytes,0,bytes.length))!=-1){
                bufferedOutputStream.write(bytes,0,lenth);
            }
            bufferedOutputStream.flush();

        }catch (Exception e){
            Logger.error("下载失败objectname:"+objectName,e);
        }

    }


    /**
     *
     * @param name
     * @param md5Val
     * @return
     */
    private static String getOtherName(String name,String md5Val){
        try{
            String preFix = name.substring(0,name.lastIndexOf("/")+1);
            if(!name.contains(".")){
                return preFix+md5Val;
            }
            String suffix = name.substring(name.lastIndexOf("."));
            return preFix+md5Val+suffix;
        }catch (Exception e){
            //最起码把md5的值返回作为文件名
            return md5Val;
        }
    }

    /**
     * 文件下载,可以下载到本地某个地址
     * @param targetFilaPath 目标文件
     * @param md5ValOrOldPath 要下载到的目标文件地址,文件如果已经存在就无法下载
     * @throws Exception
     */
    public static void loadObject(MinioBucketEnum minioBucketEnum, String targetFilaPath, String md5ValOrOldPath) throws Exception{

        loadOBbjectByMD5(minioBucketEnum,targetFilaPath,checkOutMd5Val(md5ValOrOldPath));
    }
    /**
     * 文件下载,可以下载到本地某个地址
     * @param targetFilePath 要下载到的目标文件地址,文件如果已经存在就无法下载
     * @throws Exception
     */
    private static void loadOBbjectByMD5(MinioBucketEnum minioBucketEnum, String targetFilePath, String md5Val) throws Exception {
        if(StringUtils.isBlank(targetFilePath)){
            return ;
        }
        MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
        if(byMd5Val==null){
            return ;
        }
        File file = new File(targetFilePath);
        if(file.exists()){
            return ;
        }
        file.createNewFile();
        loadOBbjectByMD5(minioBucketEnum,new FileOutputStream(file),md5Val);
    }


    /**
     *
     * @param os
     * @param md5ValOrOldPath 新文件上传则是md5值,旧文件则依然使用旧文件的路径,最终都是通过文件md5找文件
     * @throws Exception
     */
    public static void loadObject(MinioBucketEnum minioBucketEnum, OutputStream os, String md5ValOrOldPath) throws Exception{
        //先判断是不是32位的md5值

        loadOBbjectByMD5(minioBucketEnum,os,checkOutMd5Val(md5ValOrOldPath));
    }

    /**
     * 看下传入参数是否是md5值,为md5值则直接返回..如果是旧文件系统保留的文件路径,需要看下迁移的文件中是否有过该文件,有则返回该文件的实际md5值
     * 虽然可能会查两次数据库,不过问题不大,这表数据并不大
     * @param md5ValOrOldPath
     * @return
     */
    private static String checkOutMd5Val(String md5ValOrOldPath){
        //先判断是不是32位的md5值
        if(StringUtils.isBlank(md5ValOrOldPath)){
            return "";
        }
        if(md5ValOrOldPath.length()!=32||md5ValOrOldPath.contains(".")||md5ValOrOldPath.contains("/")||md5ValOrOldPath.contains("\\")){
            String code = MD5Util.md5HashCode32Str(md5ValOrOldPath);
            MinioRecord byOldPath = minioService.getByOldPathMD5(code);
            if(byOldPath==null){
                return "";
            }
            md5ValOrOldPath = byOldPath.getMd5Val();
        }
        return md5ValOrOldPath;
    }

    /**
     * 文件下载
     * @param os 目标输出流
     * @throws Exception
     */
    private static boolean loadOBbjectByMD5(MinioBucketEnum minioBucketEnum, OutputStream os, String md5Val)throws Exception{
        if(StringUtils.isBlank(md5Val)||os==null){
            return false;
        }
        MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
        if(byMd5Val==null){
            return false;
        }
        try(
                BufferedInputStream bufferedInputStream = new BufferedInputStream(getObjectInputStream(minioBucketEnum.getBucketName(),byMd5Val.getMinioFilePath()));
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(os);
        ){
            int lenth;
            byte[] bytes = new byte[1024];
            while ((lenth=bufferedInputStream.read(bytes,0,bytes.length))!=-1){
                bufferedOutputStream.write(bytes,0,lenth);
            }
            bufferedOutputStream.flush();
            return true;
        }catch (Exception e){
            Logger.error("下载失败objname:"+byMd5Val.getMinioFilePath(),e);
            throw e;
        }
    }
    /**
     * true存在
     * @param bucketName
     * @param name
     * @return
     */
    private static boolean objectExist(String bucketName,String name)throws Exception{
        try{
            ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(name).build());
            if(!StringUtils.isBlank(objectStat.name())){
                return true;
            }
            return false;
        }catch (Exception e){
            return false;

        }
    }


    private static final long partSize = 1024*1024*5l;
    /**
     * 覆盖并上传文件
     * @param bucketName
     * @param name
     * @param inputStream
     * @throws Exception
     */


    /**
     * 获取对象流
     * @param bucketName
     * @param objectName
     * @return
     */
    private static InputStream getObjectInputStream(String bucketName,String objectName) throws Exception{
        int count = 3;
        for(int i = 0;i<count;i++){
            try{
                InputStream object = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
                return object;
            }catch (Exception e2){
                if(i==count-1){
                    throw e2;
                }
                TimeUnit.MILLISECONDS.sleep(200);
            }
        }
        return null;
    }
    /**
     * 获取对象流
     * @param bucketName
     * @param objectName
     * @return
     */
    public static InputStream getObjectInputStreamByMd5Val(String bucketName,String objectName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        MinioRecord byMd5Val = minioService.getByMd5Val(bucketName, objectName);
        InputStream object = null;
        try {
            object = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(byMd5Val.getMinioFilePath()).build());
        } catch (Exception e) {
            e.printStackTrace();
            object = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(byMd5Val.getMinioFilePath()).build());
        }
        return object;
    }

    /**
     * 根据md5值删除
     * 新文件上传则是md5值,旧文件则依然使用旧文件的路径,最终都是通过文件md5找文件
     * @return
     */
    public static void deleteObject(MinioServiceImpl minioService,MinioBucketEnum minioBucketEnum, String md5ValOrOldPath) throws Exception{
        String md5Val = checkOutMd5Val(md5ValOrOldPath);
        MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),md5Val);
        if(byMd5Val==null){
            return;
        }
        if(byMd5Val.getRemainNum()!=null&&byMd5Val.getRemainNum()<=1){
            if(objectExist(minioBucketEnum.getBucketName(),byMd5Val.getMinioFilePath())){
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioBucketEnum.getBucketName()).object(byMd5Val.getMinioFilePath()).build());
                minioService.deleteRecord(minioBucketEnum.getBucketName(),md5Val);
            }
        }else{
            Integer remainNum = byMd5Val.getRemainNum();
            if(remainNum==null){
                byMd5Val.setRemainNum(1);
            }else{
                byMd5Val.setRemainNum(remainNum-1);
            }
            minioService.updateRemainNum(byMd5Val);
        }
    }
    /**
     * 仅供判断该文件是否存在.
     * @param minioMd5Value
     * @return
     */
    public static boolean isExist(MinioBucketEnum minioBucketEnum, String minioMd5Value) {
        MinioRecord byMd5Val = minioService.getByMd5Val(minioBucketEnum.getBucketName(),minioMd5Value);
        if(byMd5Val==null){
            return false;
        }else{
            return true;
        }
    }

    /**
     * 返回minio数据库表的记录
     * @return
     */
    public static MinioRecord getMinioRecordByMd5Value(MinioBucketEnum minioBucketEnum, String minioMd5Value){
        return minioService.getByMd5Val(minioBucketEnum.getBucketName(),minioMd5Value);
    }
	
}
bash 复制代码
package com.sun.minio.util;

import org.apache.commons.lang3.StringUtils;


public enum MinIoContentType {
    PDF("application/pdf"),
    STREAM("application/octet-stream"),
    IMAGE("image/jpeg"),
    VEDIO("video/mp4"),
    TEXT("text/html")
    ;
    public String contentType;
    private MinIoContentType(String contentType){
        this.contentType = contentType;
    }
    public static MinIoContentType getContentType(String contentType){
        for(MinIoContentType minIoContentType : MinIoContentType.values()){
            if(minIoContentType.contentType.equals(contentType)){
                return minIoContentType;
            }
        }
        return STREAM;
    }
    public static MinIoContentType getContentTypeByFileName(String fileName){
        if(StringUtils.isBlank(fileName)){
            return STREAM;
        }
        String substring = fileName.substring(fileName.lastIndexOf("."), fileName.length());
        if(StringUtils.isBlank(substring)){
            return STREAM;
        }
        substring = substring.toLowerCase();
        if("pdf".equals(substring)){
            return PDF;
        }
        if("png".equals(substring)||"jpg".equals(substring)||"jpng".equals(substring)||"gif".equals(substring)){
            return IMAGE;
        }
        if("mp4".equals(substring)||"avi".equals(substring)||"mkv".equals(substring)||"mov".equals(substring)||"rmvb".equals(substring)
                ||"FLV".equals(substring)||"rmvb".equals(substring)||"rm".equals(substring)){
            return VEDIO;
        }
        if("txt".equals(substring)){
            return TEXT;
        }
        return STREAM;
    }
}
bash 复制代码
package com.sun.minio.util;

public enum MinioBucketEnum {
    //EMAIL("email"),
    //EXCEL("excel"),
    //TYPICAL("typical"),
    //FIRE_CONTROL("firecontrol"),
    //MONTHLY_TEXT("monthlytext");
	
	VIDEO_FILES("movie");
	
	
    private String bucketName;
    private MinioBucketEnum(String bucketName){
        this.bucketName = bucketName;
    }
    public String getBucketName(){
        return bucketName;
    }
}
bash 复制代码
package com.sun.minio.util;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.MessageDigest;
/**
 * 网上copy的代码,获取文件的32位MD5值
 */
public class MD5Util {
    /**
     * 保证文件的MD5值为32位
     * @param filePath	文件路径
     * @return
     * @throws FileNotFoundException
     */
    public static String md5HashCode32(String filePath) throws Exception{

        BufferedInputStream bfis = new BufferedInputStream(new FileInputStream(filePath));
        return md5HashCode32(bfis);
    }

    /**
     * java计算文件32位md5值
     * @param fis 输入流
     * @return
     */
    public static String md5HashCode32(InputStream fis) {
        try {

            fis.mark(Integer.MAX_VALUE);
            //拿到一个MD5转换器,如果想使用SHA-1或SHA-256,则传入SHA-1,SHA-256
            MessageDigest md = MessageDigest.getInstance("MD5");

            //分多次将一个文件读入,对于大型文件而言,比较推荐这种方式,占用内存比较少。
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = fis.read(buffer, 0, 1024)) != -1) {
                md.update(buffer, 0, length);
            }
            fis.reset();
            //转换并返回包含16个元素字节数组,返回数值范围为-128到127
            byte[] md5Bytes  = md.digest();
            StringBuffer hexValue = new StringBuffer();
            for (int i = 0; i < md5Bytes.length; i++) {
                int val = ((int) md5Bytes[i]) & 0xff;//解释参见最下方
                if (val < 16) {
                    /**
                     * 如果小于16,那么val值的16进制形式必然为一位,
                     * 因为十进制0,1...9,10,11,12,13,14,15 对应的 16进制为 0,1...9,a,b,c,d,e,f;
                     * 此处高位补0。
                     */
                    hexValue.append("0");
                }
                //这里借助了Integer类的方法实现16进制的转换
                hexValue.append(Integer.toHexString(val));
            }
            return hexValue.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     *字符串经过md5加密成32位
     */
    public static String md5HashCode32Str(String plainText) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte[] md5Bytes  = md.digest();
            StringBuffer hexValue = new StringBuffer();
            for (int i = 0; i < md5Bytes.length; i++) {
                int val = ((int) md5Bytes[i]) & 0xff;//解释参见最下方
                if (val < 16) {
                    /**
                     * 如果小于16,那么val值的16进制形式必然为一位,
                     * 因为十进制0,1...9,10,11,12,13,14,15 对应的 16进制为 0,1...9,a,b,c,d,e,f;
                     * 此处高位补0。
                     */
                    hexValue.append("0");
                }
                //这里借助了Integer类的方法实现16进制的转换
                hexValue.append(Integer.toHexString(val));
            }
            return hexValue.toString();

        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

    }
}
bash 复制代码
package com.sun.minio.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;


public class DownloadUtils {

    private static final String JSON_APPLICATION = "application/json";

    private static final Logger log = LoggerFactory.getLogger(DownloadUtils.class.getName());

    public static boolean downloadFile( HttpServletResponse response, InputStream inputStream
            , String objectName, long fileLen, String contentType
            , boolean closeInputStream, long maxAge,String exportFileName) throws Exception {
        String fileName = StringUtils.isEmpty(exportFileName)?objectName.substring(objectName.lastIndexOf("/")+1):exportFileName;
        if (!StringUtils.isEmpty(contentType)) {
            response.setContentType(contentType);
        } else {
            response.setContentType("application/octet-stream");
        }
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        response.setHeader("X-Actual-Content-Length", String.valueOf(fileLen));
        cors(response, maxAge);
        OutputStream out = response.getOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        } catch (Exception e) {
            log.info("download file error.e=" + e);
            return false;
        } finally {
            if (closeInputStream) {
                inputStream.close();
            }
            out.flush();
            out.close();
        }
        return true;
    }
    public static void cors(HttpServletResponse response, long maxAge) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
        if (maxAge > 0) {
            response.setHeader("Access-Control-Max-Age", String.valueOf(maxAge));
            response.setHeader("Cache-Control", "max-age=" + maxAge);
        }
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Expose-Headers", "Cache-Control, Accept-Ranges, Content-Encoding, Content-Length, Content-Range, X-Actual-Content-Length, Content-Disposition");
    }
}
相关推荐
考虑考虑14 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613515 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊16 小时前
Java学习第22天 - 云原生与容器化
java
渣哥18 小时前
原来 Java 里线程安全集合有这么多种
java
间彧18 小时前
Spring Boot集成Spring Security完整指南
java
间彧18 小时前
Spring Secutiy基本原理及工作流程
java
Java水解19 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆21 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学1 天前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole1 天前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端