Java工程师实现minio存储大型视频文件网页实现分批加载视频播放
一、需求说明
老板给我出个题目,让我把的电影文件上传到minio文件系统,再通过WEB端分配加载视频播放,类似于我们普通的电影网站。小编把Java代码共享出来。是真正的能拿过来直接用的。小编我亲自测过。
1.1minio版本
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.4</version>
</dependency>
1.2maven依赖
<?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
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 播放
<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
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(" "); // 增加一些空格间隔两个标签
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
<!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工具类
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);
}
}
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;
}
}
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;
}
}
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 "";
}
}
}
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");
}
}