<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.4</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.8.1</version>
</dependency>
min:
io:
endpoint: http://192.168.247.130:9000
accessKey: admin
secretKey: password
bucket: images
imageType: .jpeg
spring:
servlet:
multipart:
max-file-size: 5MB
max-request-size: 15MB
package com.jd.emergencydepartment.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Data
@Component
public class MinIoConfig {
@Value("${min.io.endpoint}")
private String endpoint;
@Value("${min.io.accessKey}")
private String accessKey;
@Value("${min.io.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient(){
return MinioClient.builder().endpoint(endpoint).credentials(accessKey,secretKey).build();
}
}
package com.jd.minio;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@Component
public class MinIoUtils {
@Resource
private MinioClient client;
private static final String SEPARATOR_DOT = ".";
private static final String SEPARATOR_ACROSS = "-";
private static final String SEPARATOR_STR = "";
// 存储桶名称
private static final String chunkBucKet = "img";
/**
* 不排序
*/
public final static boolean NOT_SORT = false;
/**
* 排序
*/
public final static boolean SORT = true;
/**
* 默认过期时间(分钟)
*/
private final static Integer DEFAULT_EXPIRY = 60;
/**
* 删除分片
*/
public final static boolean DELETE_CHUNK_OBJECT = true;
/**
* 不删除分片
*/
public final static boolean NOT_DELETE_CHUNK_OBJECT = false;
/**
* @param bucketName
* @return boolean
* @Description 判断 bucket是否存在
* @author exe.wangtaotao
* @date 2020/10/21 16:33
*/
public boolean bucketExists(String bucketName) {
try {
return client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 创建存储桶
* 创建 bucket
*
* @param bucketName
*/
public void makeBucket(String bucketName) {
try {
boolean isExist = bucketExists(bucketName);
if (!isExist) {
client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param
* @return java.util.List<java.lang.String>
* @Description 获取文件存储服务的所有存储桶名称
* @author exe.wangtaotao
* @date 2020/10/21 16:35
*/
public List<String> listBucketNames() {
List<Bucket> bucketList = listBuckets();
List<String> bucketListName = new ArrayList<>();
for (Bucket bucket : bucketList) {
bucketListName.add(bucket.name());
}
return bucketListName;
}
/**
* @return java.util.List<io.minio.messages.Bucket>
* @Description 列出所有存储桶
*/
@SneakyThrows
private List<Bucket> listBuckets() {
return client.listBuckets();
}
/**
* 获取对象文件名称列表
*
* @param bucketName 存储桶名称
* @param prefix 对象名称前缀(文件夹 /xx/xx/xxx.jpg 中的 /xx/xx/)
* @return objectNames
*/
public List<String> listObjectNames(String bucketName, String prefix) {
return listObjectNames(bucketName, prefix, NOT_SORT);
}
/**
* 获取对象文件名称列表
*
* @param bucketName 存储桶名称
* @param prefix 对象名称前缀(文件夹 /xx/xx/xxx.jpg 中的 /xx/xx/)
* @param sort 是否排序(升序)
* @return objectNames
*/
@SneakyThrows
public List<String> listObjectNames(String bucketName, String prefix, Boolean sort) {
boolean flag = bucketExists(bucketName);
if (flag) {
ListObjectsArgs listObjectsArgs;
if (null == prefix) {
listObjectsArgs = ListObjectsArgs.builder()
.bucket(bucketName)
.recursive(true)
.build();
} else {
listObjectsArgs = ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(prefix)
.recursive(true)
.build();
}
Iterable<io.minio.Result<Item>> chunks = client.listObjects(listObjectsArgs);
List<String> chunkPaths = new ArrayList<>();
for (io.minio.Result<Item> item : chunks) {
chunkPaths.add(item.get().objectName());
}
if (sort) {
chunkPaths.sort(new Str2IntComparator(false));
}
return chunkPaths;
}
return new ArrayList<>();
}
/**
* 在桶下创建文件夹,文件夹层级结构根据参数决定
*
* @param bucket 桶名称
* @param WotDir 格式为 xxx/xxx/xxx/
*/
@SneakyThrows
public String createDirectory(String bucket, String WotDir) {
if (!this.bucketExists(bucket)) {
return null;
}
client.putObject(PutObjectArgs.builder().bucket(bucket).object(WotDir).stream(
new ByteArrayInputStream(new byte[]{}), 0, -1)
.build());
return WotDir;
}
/**
* 删除一个文件
*
* @param bucketName
* @param objectName
*/
@SneakyThrows
public boolean removeObject(String bucketName, String objectName) {
if (!bucketExists(bucketName)) {
return false;
}
client.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
return true;
}
/**
* @param bucketName
* @param objectNames
* @return java.util.List<java.lang.String>
* @Description 删除指定桶的多个文件对象, 返回删除错误的对象列表,全部删除成功,返回空列表
* @author exe.wangtaotao
* @date 2020/10/21 16:43
*/
@SneakyThrows
public List<String> removeObjects(String bucketName, List<String> objectNames) {
if (!bucketExists(bucketName)) {
return new ArrayList<>();
}
List<DeleteObject> deleteObjects = new ArrayList<>(objectNames.size());
for (String objectName : objectNames) {
deleteObjects.add(new DeleteObject(objectName));
}
List<String> deleteErrorNames = new ArrayList<>();
Iterable<io.minio.Result<DeleteError>> results = client.removeObjects(
RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(deleteObjects)
.build());
for (io.minio.Result<DeleteError> result : results) {
DeleteError error = result.get();
deleteErrorNames.add(error.objectName());
}
return deleteErrorNames;
}
/**
* 获取访问对象的外链地址
* 获取文件的下载url
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param expiry 过期时间(分钟) 最大为7天 超过7天则默认最大值
* @return viewUrl
*/
@SneakyThrows
public String getObjectUrl(String bucketName, String objectName, Integer expiry) {
expiry = expiryHandle(expiry);
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(expiry)
.build()
);
}
/**
* 创建上传文件对象的外链
*
* @param bucketName 存储桶名称
* @param objectName 欲上传文件对象的名称
* @return uploadUrl
*/
public String createUploadUrl(String bucketName, String objectName) {
return createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY);
}
/**
* 创建上传文件对象的外链
*
* @param bucketName 存储桶名称
* @param objectName 欲上传文件对象的名称
* @param expiry 过期时间(分钟) 最大为7天 超过7天则默认最大值
* @return uploadUrl
*/
@SneakyThrows
public String createUploadUrl(String bucketName, String objectName, Integer expiry) {
expiry = expiryHandle(expiry);
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(bucketName)
.object(objectName)
.expiry(expiry)
.build()
);
}
/**
* 文件下载
*
* @param fileName 文件名
* @return InputStream
*/
public void downLoadFile(HttpServletResponse response,String fileName) {
/* InputStream inputStream = null;
try {
StatObjectResponse statObjectResponse = client.statObject(StatObjectArgs.builder().bucket(chunkBucKet).object(fileName).build());
if (statObjectResponse.size() > 0) {
inputStream = client.getObject(GetObjectArgs.builder().bucket(chunkBucKet).object(fileName).build());
}
} catch (Exception e) {
e.printStackTrace();
}
return inputStream;*/
try(InputStream ism = new BufferedInputStream(client.getObject(GetObjectArgs.builder()
.bucket(chunkBucKet)
.object(fileName).build()))) {
// 调用statObject()来判断对象是否存在。
// 如果不存在, statObject()抛出异常,
// 否则则代表对象存在
client.statObject(StatObjectArgs.builder()
.bucket(chunkBucKet)
.object(fileName).build());
byte buf[] = new byte[1024];
int length = 0;
response.reset();
//Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。
// Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,
// 文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType("application/x-msdownload");
response.setCharacterEncoding("utf-8");
OutputStream osm = new BufferedOutputStream(response.getOutputStream());
while ((length = ism.read(buf))>0) {
osm.write(buf,0, length);
}
osm.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 批量下载
*
* @param directory
* @return
*/
@SneakyThrows
public List<String> downLoadMore(String bucket, String directory) {
Iterable<io.minio.Result<Item>> objs = client.listObjects(ListObjectsArgs.builder().bucket(bucket).prefix(directory).useUrlEncodingType(false).build());
List<String> list = new ArrayList<>();
for (io.minio.Result<Item> result : objs) {
String objectName = null;
objectName = result.get().objectName();
StatObjectResponse statObject = client.statObject(StatObjectArgs.builder().bucket(bucket).object(objectName).build());
if (statObject != null && statObject.size() > 0) {
String fileurl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(statObject.object()).method(Method.GET).build());
list.add(fileurl);
}
}
return list;
}
/**
* @param multipartFile
* @param bucketName
* @param directory image/
* @return java.lang.String
* @Description 文件上传
* @author exe.wangtaotao
* @date 2020/10/21 13:45
*/
public MinIoUploadResDTO upload(MultipartFile multipartFile, String bucketName, String directory) throws Exception {
if (!this.bucketExists(bucketName)) {
this.makeBucket(bucketName);
}
InputStream inputStream = multipartFile.getInputStream();
directory = Optional.ofNullable(directory).orElse("");
String minFileName = directory + minFileName(multipartFile.getOriginalFilename());
System.out.println(minFileName);
System.out.println(multipartFile.getContentType());
//上传文件到指定目录
client.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(minFileName)
.contentType(multipartFile.getContentType())
.stream(inputStream, inputStream.available(), -1)
.build());
inputStream.close();
// 返回生成文件名、访问路径
return new MinIoUploadResDTO(minFileName, getObjectUrl(bucketName, minFileName, DEFAULT_EXPIRY));
}
public MinIoUploadResDTO uploadByBytes(byte[] bytes,String fileName, String bucketName, String directory) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
fileName =directory+"/"+fileName;
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectWriteResponse objectWriteResponse = client.putObject(PutObjectArgs.builder().bucket(bucketName)
.object(fileName)
.contentType("image/jpeg")
.stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
.build());
return new MinIoUploadResDTO(fileName,getObjectUrl(bucketName,fileName,DEFAULT_EXPIRY));
}
/**
* @param response
* @return java.lang.String
* @Description 下载文件
* @author exe.wangtaotao
* @date 2020/10/21 15:18
*/
public void download(HttpServletResponse response, String bucketName, String minFileName) {
InputStream fileInputStream = null;
try {
fileInputStream = client.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(minFileName).build());
// response.setHeader("Content-Disposition", "attachment;filename=" + minFileName);
// response.setContentType("application/force-download");
response.setContentType("image/jpeg");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(fileInputStream, response.getOutputStream());
} catch (ErrorResponseException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException | InsufficientDataException e) {
e.printStackTrace();
} finally {
//关闭流
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 批量创建分片上传外链
*
* @param bucketName 存储桶名称
* @param objectMD5 欲上传分片文件主文件的MD5
* @param chunkCount 分片数量
* @return uploadChunkUrls
*/
public List<String> createUploadChunkUrlList(String bucketName, String objectMD5, Integer chunkCount) {
if (null == bucketName) {
bucketName = chunkBucKet;
}
if (null == objectMD5) {
return null;
}
objectMD5 += "/";
if (null == chunkCount || 0 == chunkCount) {
return null;
}
List<String> urlList = new ArrayList<>(chunkCount);
for (int i = 1; i <= chunkCount; i++) {
String objectName = objectMD5 + i + ".chunk";
urlList.add(createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY));
}
return urlList;
}
/**
* 创建指定序号的分片文件上传外链
*
* @param bucketName 存储桶名称
* @param objectMD5 欲上传分片文件主文件的MD5
* @param partNumber 分片序号
* @return uploadChunkUrl
*/
public String createUploadChunkUrl(String bucketName, String objectMD5, Integer partNumber) {
if (null == bucketName) {
bucketName = chunkBucKet;
}
if (null == objectMD5) {
return null;
}
objectMD5 += "/" + partNumber + ".chunk";
return createUploadUrl(bucketName, objectMD5, DEFAULT_EXPIRY);
}
/**
* 获取分片文件名称列表
*
* @param bucketName 存储桶名称
* @param ObjectMd5 对象Md5
* @return objectChunkNames
*/
public List<String> listChunkObjectNames(String bucketName, String ObjectMd5) {
if (null == bucketName) {
bucketName = chunkBucKet;
}
if (null == ObjectMd5) {
return null;
}
return listObjectNames(bucketName, ObjectMd5, SORT);
}
/**
* 获取分片名称地址HashMap key=分片序号 value=分片文件地址
*
* @param bucketName 存储桶名称
* @param ObjectMd5 对象Md5
* @return objectChunkNameMap
*/
public Map<Integer, String> mapChunkObjectNames(String bucketName, String ObjectMd5) {
if (null == bucketName) {
bucketName = chunkBucKet;
}
if (null == ObjectMd5) {
return null;
}
List<String> chunkPaths = listObjectNames(bucketName, ObjectMd5);
if (null == chunkPaths || chunkPaths.size() == 0) {
return null;
}
Map<Integer, String> chunkMap = new HashMap<>(chunkPaths.size());
for (String chunkName : chunkPaths) {
Integer partNumber = Integer.parseInt(chunkName.substring(chunkName.indexOf("/") + 1, chunkName.lastIndexOf(".")));
chunkMap.put(partNumber, chunkName);
}
return chunkMap;
}
/**
* 合并分片文件成对象文件
*
* @param chunkBucKetName 分片文件所在存储桶名称
* @param composeBucketName 合并后的对象文件存储的存储桶名称
* @param chunkNames 分片文件名称集合
* @param objectName 合并后的对象文件名称
* @return true/false
*/
@SneakyThrows
public boolean composeObject(String chunkBucKetName, String composeBucketName, List<String> chunkNames, String objectName, boolean isDeleteChunkObject) {
if (null == chunkBucKetName) {
chunkBucKetName = chunkBucKet;
}
List<ComposeSource> sourceObjectList = new ArrayList<>(chunkNames.size());
for (String chunk : chunkNames) {
sourceObjectList.add(
ComposeSource.builder()
.bucket(chunkBucKetName)
.object(chunk)
.build()
);
}
client.composeObject(
ComposeObjectArgs.builder()
.bucket(composeBucketName)
.object(objectName)
.sources(sourceObjectList)
.build()
);
if (isDeleteChunkObject) {
removeObjects(chunkBucKetName, chunkNames);
}
return true;
}
/**
* 合并分片文件成对象文件
*
* @param bucketName 存储桶名称
* @param chunkNames 分片文件名称集合
* @param objectName 合并后的对象文件名称
* @return true/false
*/
public boolean composeObject(String bucketName, List<String> chunkNames, String objectName) {
return composeObject(chunkBucKet, bucketName, chunkNames, objectName, NOT_DELETE_CHUNK_OBJECT);
}
/**
* 合并分片文件成对象文件
*
* @param bucketName 存储桶名称
* @param chunkNames 分片文件名称集合
* @param objectName 合并后的对象文件名称
* @return true/false
*/
public boolean composeObject(String bucketName, List<String> chunkNames, String objectName, boolean isDeleteChunkObject) {
return composeObject(chunkBucKet, bucketName, chunkNames, objectName, isDeleteChunkObject);
}
/**
* 合并分片文件,合并成功后删除分片文件
*
* @param bucketName 存储桶名称
* @param chunkNames 分片文件名称集合
* @param objectName 合并后的对象文件名称
* @return true/false
*/
public boolean composeObjectAndRemoveChunk(String bucketName, List<String> chunkNames, String objectName) {
return composeObject(chunkBucKet, bucketName, chunkNames, objectName, DELETE_CHUNK_OBJECT);
}
/**
* @param originalFileName
* @return java.lang.String
* @Description 生成上传文件名
* @author exe.wangtaotao
* @date 2020/10/21 15:07
*/
private String minFileName(String originalFileName) {
String suffix = originalFileName;
if (originalFileName.contains(SEPARATOR_DOT)) {
suffix = originalFileName.substring(originalFileName.lastIndexOf(SEPARATOR_DOT));
}
return UUID.randomUUID().toString().replace(SEPARATOR_ACROSS, SEPARATOR_STR).toUpperCase() + suffix;
}
/**
* 将分钟数转换为秒数
*
* @param expiry 过期时间(分钟数)
* @return expiry
*/
private static int expiryHandle(Integer expiry) {
expiry = expiry * 60;
if (expiry > 604800) {
return 604800;
}
return expiry;
}
static class Str2IntComparator implements Comparator<String> {
private final boolean reverseOrder; // 是否倒序
public Str2IntComparator(boolean reverseOrder) {
this.reverseOrder = reverseOrder;
}
@Override
public int compare(String arg0, String arg1) {
Integer intArg0 = Integer.parseInt(arg0.substring(arg0.indexOf("/") + 1, arg0.lastIndexOf(".")));
Integer intArg1 = Integer.parseInt(arg1.substring(arg1.indexOf("/") + 1, arg1.lastIndexOf(".")));
if (reverseOrder) {
return intArg1 - intArg0;
} else {
return intArg0 - intArg1;
}
}
}
}
package com.jd.minio;
import lombok.Data;
import java.io.Serializable;
@Data
public class MinIoUploadResDTO implements Serializable {
private static final long serialVersionUID = 475040120689218785L;
private String minFileName;
private String minFileUrl;
public MinIoUploadResDTO(String minFileName, String minFileUrl) {
this.minFileName = minFileName;
this.minFileUrl = minFileUrl;
}
}
package com.jd.minio;
import java.io.Serializable;
public class Result<T> implements Serializable {
private static final long serialVersionUID = 6273326371984994386L;
private Integer code;
private String msg;
private T data;
private Result() {
this.code = 200;
this.msg = "OK";
}
private Result(T data) {
this.code = 200;
this.msg = "OK";
this.setData(data);
}
private Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result<T> setError(Integer code, String msg) {
this.setCode(code);
this.setMsg(msg);
return this;
}
public boolean isSuccess() {
return this.getCode().equals(200);
}
public static Result ok() {
return new Result();
}
public static <T> Result ok(T data) {
return new Result(data);
}
public static <T> Result ok(Integer code, String msg) {
return new Result(code, msg);
}
public static <T> Result ok(Integer code, String msg, T data) {
return new Result(code, msg, data);
}
public static <T> Result error() {
return new Result(500, "failed");
}
public static <T> Result error(String msg) {
return new Result(500, msg);
}
public static <T> Result error(Integer code, String msg) {
return new Result(code, msg);
}
public static <T> Result error(Integer code, String msg, T data) {
return new Result(code, msg, data);
}
public Integer getCode() {
return this.code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return this.msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return this.data;
}
public void setData(T data) {
this.data = data;
}
}
package com.jd.minio;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/minio")
public class MinIoController {
@Resource
private MinIoUtils minIoUtils;
// 存储桶名称
private static final String MINIO_BUCKET = "image";
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* private static final String base64 = "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";
*/
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public Result upload(@RequestParam(value = "files") MultipartFile files){
try {
return Result.ok(minIoUtils.upload(files,MINIO_BUCKET,null));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@RequestMapping(value = "/file", method = RequestMethod.GET)
public Result files(){
try {
BASE64Decoder base64Decoder = new BASE64Decoder();
return Result.ok(minIoUtils.uploadByBytes(base64Decoder.decodeBuffer(base64),"20220804161251.jpeg",MINIO_BUCKET,"20220804"));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@GetMapping("/download")
public void download(@RequestParam("minFileName")String minFileName,HttpServletResponse response){
minIoUtils.download(response,"image",minFileName);
}
}
有了这些, 大家就可以自己去玩了, 然后根据自己的业务改造
建议不要用MinIo自带的图片分享URL, 因为最大只支持7天, 我在网上找了一些方案, 基本都是直接通过文件夹路径访问, 但是只有单击可以, 我在集群上发现是不行的, 所以我是自己做了个接口直接返回图片流
public void download(HttpServletResponse response, String fileName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
try (
InputStream inputStream = client.getObject(GetObjectArgs
.builder()
.bucket(bucket)
.object(fileName)
.build())) {
response.setContentType("image/jpeg");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(inputStream, response.getOutputStream());
}
}
通过传入文件名直接返回图片
okk