添加依赖
代码语言:javascript复制<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>
新增yml配置
代码语言:javascript复制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
新建配置类
代码语言:javascript复制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();
}
}
新建工具类
代码语言:javascript复制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;
}
}
}
}
新建返回结果
代码语言:javascript复制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;
}
}
新建统一返回
代码语言:javascript复制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;
}
}
新建Controller
代码语言:javascript复制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 = "iVBORw0KGgoAAAANSUhEUgAAAp8AAAByCAYAAAAVrbp9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAACj0SURBVHhe7d0JcFPV4j9wxmHoc2TgDw78QEcY0WFxxCejoLxBRJFB1CcqoAgM6hNZBK0oroAClUItO4WWspVCWStgG7rRQoEQlrK0rKVAWSxCsVCEFiiFfv/n3Nw0N8lNmnS5UPr9zNyBnJ6cnNxs35x7zk0tEBEREREZhOGTiIiIiAzD8ElEREREhmH4JCIiIiLDMHwSERERkWEYPomIiIjIMAyfRERERGQYhk8iIiIiMgzDJxEREREZhuGTiIiIiAzD8ElEREREhmH4JCIiIiLDMHwSERERkWEYPomIiIjIMAyfRERERGQYhk8iIiIiMgzDJxEREREZhuGTiIiIiAzD8ElEREREhmH4JCIiIiLDMHwSERERkWEYPomIiIjIMAyfRERERGQYhk8iIiIiMgzDJxEREREZhuGTiIiIiAzD8ElEREREhmH4JCIiIiLDMHwSERERkWEYPomIiIjIMAyfRERERGQYhk8iIiIiMgzDJxEREREZhuGTiIiIiAzD8ElEREREhmH4JCIiIiLDMHwSERERkWHuYvgsxJ/pFlh2HML5IrXII1/rExEREdG95q6Fz KcFMwLDsas9UdwXS3zxNf6FZZnQUSECVl56mWf5cFiyVL/ryPLBJP4u7fNZ5kiYPKlM3m dTzLZIKl3PfVLi8vC6YI2Ve1QMizRCBCFFRC80RERFTN3aXweQVpUdMQPDUSu/JK1DJPfK1fUSI4RgQj2ENgysvKgodoqQSuYBFe3QW6LFOwD2FSBLpgbRDOs96 u81iQnBwhPdhUgThKVOmlDMgyr5YYBHhOEKEThnYZajOsoVfGeLFlwafgjMRERHdt 5K Lwhwk6ICCShSadxSy3zxNf63nIb4ESQUsKb3t/EJoOWDGsynOpSApcIi8p/RRhzqaaGSfWSDHAWk4dRVnH/3d6Wkzw5omqy DBiK/siw6F60YUIl r/HOWp 0P8XQRNiwiermHXWs7gSURERDbGh887udi6SAS3GWtw4Jpa5omv9StKExzLx3rYuTSIyeBoC6J5aoBVR0VNEcGIsIgwK//1EBito6TqBWfq9ACLCIBZFl9CpyT7qt62JlzbN4vy9zKDr9IHi0tItY7 6rXtZbtERER03zE8fF7NWIPpwVOw2HwBd9QyT3ytXyHqfMWKzH20zs1UL0gyaDklQnuYtB7ej3C5Qe1hdRmGRZsWk3r4Wq2ioYQ8nwOzdVTS9bZt1L LGyxrdyhzOpV2NKOkInTL6 qxTUnggCgREVHNY2z4LDqFhNBgBM8x4cRNtcwTX tXiBo8HUbonDfriJ2bTKWGwLLCqzzMLYOXd4eklTYdblD20 TQT2W pWb00iLCrRxxdN y7bbVi86UEK4XilW2EVxlU/eJRTuaKa4v mO7tgzSpS3liXqedxARERHdxwwMnyX4e0ckpgRPw4q9 WqZJ77WLz 5QKfMEU9bIFMOj tUlCN9wfqHkh3mlqqjftZwZy2T4VE3LCpTAJzCrsv8TzXMqpfElZRg6fa KPdDhl/5rxxRdawo54zKkVaLl8OS1lFMbd9l8NSOlsr 2YKsnCfqXbtERER0fzIufBYewdpZIpyFb8K5YrXME1/rl5sMR2UFT4vnRTzK360jjnIk0xo01RDnfJ3S8Kde9kAeng8WbchV5La86TL/0zmMupl/KclV6Q6nd5LXlXMy5f9Fv2wjqF50zUoNx/L2rdfRCZce kNEREQ1j0Hhsxg5KeEiSM3CH0cL1TJPfK1fTjIIuhmtVMjgJM9/6TEpitAmAp38VzsCaZ8HaaeEv9KgVgYRDG0r6u2ji9agbNKMpFoXK2kuywDpmng9LEaSfxNBWTmU71XPVHKE1RqOLXJeqvPopzr6atsPHPEkIiIiyZjwmb8HUdOCMTVyJy55c5pOX uXhzIiZz387T57ujkcXkoTqhxGIF0PfcsRS dV5W4PtyvhTdM30bbtX8eg7Bh4rZfLGMXVUA6xK4fevQzEGnJhlQzm1tBpDaK20KuM2Ip WsttIdT9OU JiIio5jAgfN7EMVMIgoNDkXTam9/F9LW 75RzbyqByzm8aenPnZTntNTjcDhce6hZPZwd7HI7 u1L1l8bcu1bmYfc5WWPYdlKhmplVFUZ7bSHYYdNt88qGVrF7cr5q7a5nKX3RdsH7X5w7isRERHVSFUePu9cMGPhlGDMWJOBArXME1/r 0aEJO3hZ1sgkufIdA5fyqmNHMNZ6WFm2/VLOQZF66Fm2YZFOZQtRxhdgpebuZClI4Qu17GOaroccpe3Y7ssR1f10qzK9tOXcp6mp3qiptK2blYU/ZZTB6x1nPaFOppsu5oy2murIP/mw6gsERER3Z qOHxeQ8aaGQiesgjmXG/O0ulrfV 4zjt0GUnUsM1V9IpDUHQNZUoIE3 3hURrUNRpP0uEVaVIJ/zphlHtyKTrSKmNda6pPHQvD4S7r1dKCYo6dUqDp/X/rvM8xXVKC TtaPeDvOx fxMREVHNUKXhsyg7EaEicMwRicOr03r6WL9C3AUshc6onge6h9zl4XZ5aF/n8Ll yIY2i7r9M05KCvzUbUFLuFUxEw58qr0QS2S9EZhnbi0LSn3x96QYzgX98f550Hl7TiHU4ZPIiKiGq/qwmdJHnZETkXwtCjsvaKWeeJr/QrSDVg2LqN6njiGS6XdYE0w0wt7ZbTv2jfnAOs6MuppFFer7Ho6o64utOHZGnKd74vr7Yj7oDkkT0RERDVTlYXPwiPrMTM4GOGbcnBbLfPE1/oV4zlgOY7qlcEhXNpG9 zXlSFMOy/T7SH3Ujp9cw6wani1lziHU3e8qKcz6upCM6opF2C53hNv 0NEREQ1TdWEz IcpIQHI3jWOnh3Wk8f61eUTsCynXYoQgQ//UVFenSCovYYt26Qk8HMQ/su13G9jdJwrCwgCi49H2hZlBFVTyvc5SZXuZfRljVQO98BOQKq2Yde9IeIiIhqnioJn/l7lmNa8BRE7vwb3p3W07f6FSWDpv5vqsswJ4Kfu2DoTM6DFCHLbY7Uux0RLuVpntxxHXWVodBDh2xzS9WL7okQK4JlWZlQBlDPbYnb87CPlP572CdERERUs1XpgiMiIiIiIi2GTyIiIiIyDMMnERERERmG4ZOIiIiIDMPwSURERESGYfgkIiIiIsMwfBIRERGRYRg iYiIiMgwDJ9EREREZBiGTyIiIiIyDMMnERERERmG4ZOIiIiIDMPwSURERESGYfgkIiIiIsMwfBIRERGRYe6P8Jmfi8CRW9H166MwX1HL6P5R3R9fPj/vrktbEfhBV3TtOxnmy2oZEVUPBdsxKbInui2dBnOBWkbV3n0RPgvid6HWc7HK1j hWC2l 0V1f3z5/Ly7Ctb0Q61atZStfzQ/vYiqk4L9n6HO2P9TtgHphWopVXf3x8hnzhn0f3sDGr69G6vPqWV3TSHmfhKDWu3SEKeWVC9V3f9ytH9PPb5SEfb/no63eiWiYXtxX9RgWWvUWdxUazi4y/2/HGOx99G2tUvGpEy1QqW5idNJMzGiZwc80bge6jVuifZd OXVRnIv6NWcZGDua/KYDio6l4vp1ai/1MN0fCpgVh9Ri2rDv7ch0X1ZyNQbNExt9RCqRDpA0Ix6f FILDLLvytlkp/BUUq5XpbYP35SNqlVlTkYUun2da/Dzjs Nw9tgth4nbnBJ5XC6x8a9/qZlYWtnwZjbCnwxDUeC6mtluBNb9m4u/raoVyu4Zdb6n9t/WhoWj/31FY WMGcvLVaqWs9zewWTKyS9Qim7RtmCHv76QLaoFd0YkT2DZyHeY9Mw9BjeYiuK1o/6cDOH9VrVDqH1heF 03ScAx5 YBTlIfFH0r/la7Dtru/ESHPO3Po7utsA GeLRtvK0722bu8fg3rARn41pjNoTx2C7cnknRk0Ul8d8gXjlshuXojFgyhNoNGUo1tzXRy7KuX qKc75rHQMn55V9/0jPkcs /BYuxjUf9cC/ykHMHq6usXk414c1yw8kG3vo9g /tBUBeGzBNmRvdC0Vi3UfqQjPhg2Et98 Qleb1NXBMvaeMp/I1yygMKA8FlNlaQm47fHIxHaejZCp SqpcCdQxaE1g/DnOfDETT8OG6r5dKF6G1IGrdVbClY2lYG1wgs/0leltt nNZO yg6juiHreEt8MlNOK0WS8Ub4jFZhLHV0Y5fp3xqXyjavwMLHxH1mkYgcshGbBgVj8h2c5XbnC76XrH8eQamx0Xbz/yOOOX2tyLhW3v7v70nvvSoNRXq/Q18a5 IiY4KV6xXrrM6ukgtsSo0b0V4U3GdJouxZNBGxH2XgKiO1sAYLIKhw929cwqxj4m6r6bhklpkdRMnv4sQ 2oeVq3RJtYr2N5N1H90Odaq/XfeUmLsLdn3vdw2Ydm/xXXF/l8xRnsd18fg3pGMwUq4GguLcnkXvlXC1ZdIUC7XdDVr/zB8VjqGT8 qf/jcG7JR9D8BYzLUgmrGHJxQ eHz1hb4NxUhssUQJORphpVuHsW0Vx8S4bIDpmepZQ4YPt3Jn78Ggc8lw9RXBMTPMtUvNjdxeEgYJr2cgHVdZ2P rDyl1NUFJD8rwkmLTTillrhQRzfntl8iQsxK7NSMCufOWCbKFiMlXS1w4UX7t3KQIANqq1ikn9J8Lbt2CjFtRHnDWBysyCyIv9IRIfr/28hs8dVHo AsNijBLBpp9swOZO4SoX02pnynjdlWZ35ZqAS5TdrX9HURbmU/W8Zgf7am/zdzsell2b7YZ2fVMumsdaQ66MsT0A70FyQmYqYon/5pJq6pZYo72YiRwbxnOlwGUcsk tBBXLdVKrRduLdtg/94EaaCJmKvcjkdPweJy O/Q6pyuaarWfun6sLn2Sx0bBeDuhPOIy/jBIZ moxH/mPCw2 k4uN55/GnyxDRdcwbJEJJ z1IzMtF0KhUPPGSCXW7bMSrozORfN75uJ0aYpwOJ/aKcfzmalWEyC/kocbNmOn6vgPkZaPHczGo438ajqP6xTi 9RhGfL4ZbbqaUL9LEtp/uhu/bvwH/zi8213C6Ndd K8ufbtNk5ty8SwoSl4vLO4r50T0X7YPszcXiB6XAlu34B5ZTp69k1Gs06iD8 b8Njbqeg74zQOOLzblbf/Vbl/fHl8bW7jRGomhgxJQfNOJtQT/ekg 5N0BVcc lMx3oU3H/uveb1cSMvERwOS0LhTHFq b8aI5X/jonZ4q5S3 99RlYTPc/PxWq1aeOB/f7iM/l6ID8CgQZ9h/l7bazgNo1tY52B62not074az2Ned1FeZygSc7cgqH8nPPFwXdRt0gavDvoNyX9qD0tLtlDrqU2Nk7PQUfy97ohEXNg6FR91boXGDRqhZYc3MCJ0h/7 v5ML86yh6PpUE9St1wStX/kUs7dfRNIIOdrbGXP13mt8cOLbMAR234MDAYsR2NEi4gZQciIN8xuEYe2SPYhqMAfr43WfGCLgHcVKEXgC30l3DDwa1tHNOYgNSMX0 iFYa7Ltw9s4MnSOCFd/IMPdlb1o//bWFEwTdcJnOgfkQuzpLcPbOqTrD4d7pcS8CcGi/UWhzo0UIWOgbP937NPcdLEpDpNE/chFzlHvOva L vH4IAmDBeL0Bgk 68T8E/ uADBzVbAfEwtEEo2J M3UX9BiOY5lpuJ6Jai7TZxyHRu5sxeLBT1Z4zNUQt8UJiJ1Q1Eu70zUH1mMe/DGBmmZs6EdbcdR/BMGbYCxDuCs3MIDW1cOtfTtvVO03vCFGP9CtnOBKw/ AM6BT6Bx0MDkFpwHqZ1b6BZQFu8uCICh1zeegtx/EgovljUDU9PaoGGE9vjhXnDMDHjmP77Z8lFmLd hW5TWqP NZ4KnQEQrL/xsbo5qg95g2EOg53Czdx6mgIPl/QBU8ENEf9gGfxwsJRmJWZ4 bz3Zf9Y3dp88/o0rw 6jd/BeNSq8 8hKoPn/5p PC1ZLw5ej9Gjt J/3QVH8KivF3IJacHwBY t6OvfxxaD0rDl5P2of9Hiagj6j/w5n5sdnjPKBLByn4o8ZsRGz1 uOfFWJR2Oi1x/Y6Zu3Y7HmgXi96x2kNMd3B4uRmNxXUe6rEVAwIy8M2vaXjtDXnI0oQXxTPNXrsA62buh3 g3NLwSldxP8SH zvKZfsWtl/7sVyCE9Hb0Ui0X6d7KvqNS8fIcbvQubvcP3Hovb7A4XCa70pwaFEqHhTtN3x3O4ZOzsDoqRkY kUyGoiyRkOyka3WLF//q3r/ Pb4yvubudKMh0V/anVOwbtj9 PrgN3o2kP2Jxbtpl50 yFZNse WA9bx GVn 1lyuZw2N3H/pe Xnajz6vJeEu8Xr4atwMdXpH7KhYvzsuHY7zyZf87qpLwWWTCJ3VEwGv3Kw6W cQ9hXU/ 8PfX26D8MpjMhg jXeUy/YtbIf2Y9UWPt9B396N0br7J/hy5Aj079ICdURofKCVPzY7HG7MhzlsNEaPtm7fvNPau/DZeyD6PPo03hrkj68 fx8dmj4grlcbL4rXj P L0FWyCvwE9ep1aQ9eg/9Cv6fvomnHu2Dgb0qI3xexc43Rbjodwj5a2IQ2NCEIzdv4fjX8xHYeQdyTAkiGC2D bha3Vm6GbNFsJn2o/txMevoZiS2iC87qxrORshE2/zOS9jaWdz2s2YRAdzwov3zv0WK9sORuEMtKPU3UjuK9lukuB819cKVRdGi/bmITXFKCgXZiHlStO80HzZ3ury/YYgzqwWlziGxtaj/vFk8y zc919f/rzVov4c/JGkfskquYr9/eeJsoWITXadYFCSslEJtytWlmPywSEL5pY3uOo5vh4Txo7FWLebCHbunmtey0LQDBGmwhaKr4bSeYTNE5dnTMNR5bLWFZi3B2DMBus2anGHssNn4OvotUK8bqO6o8mYxnhx8VD0jvoBg0OfEeGwKd7aoX02F GwuQ ainr1gt7GwN/HYNTaz9H9t2ai7mP4z8b9Tu f4vW 7b94SDks/ir6rPkR/ivfR9vAj/HREr3wWYwTO/sp/Xgw6A30j/4JX0cPRpegR0Td1uiTdlrn892X/WNTgvjh9fCvf/1L2eoPjxcl1UOVh8/aL4k3qeP23Xzn8l8Y3F18oLbfgdUOOVANn KDtpN4FEs/dkqKkDwxSZTH4PVV7le62RZVuP1wzz Nd XikP7HNKFLUkdF21uwQvu8LsrFdz0T0Ozjw0jT3uz1S/jpPdn/XVir 5XTy8PKhefw6UsiVL XAYfP2IK/8YNs/ U9SLqhlpXLVfzWR7TTKQ0bHPpZjJ1RezFo9CHEa9 ZS3nZ/6reP07KfHwLcjCwo2j3tTSszdW8/K5fxvgPZKDfgtByL/bRH8V02dwtOBLK7L/m9TLnpP31UnzhDPp2Ebf9QhriHbJ5efd/FYVPsY82j3xShC4/PPO/UKSe/MfLL0/eHnZXw6dov9OvezTvD/lI9m8lyh/A6 HuP4gvL sl6pQdPms//AbmHLW/8IpzVqJvE3G7D32GeG36vJWCEY1EecNeiNSMuhYemIquDWqL26pg Cw5A1Pz2QjyPwlkmBFSP0p8GB/EssahiI65bj1M3CgemW6eTjeiY5R5iZER7r5y3cGRYbbRzcvY1kWEL/FepOzXouP4Xc6NHOi0CEmj7PZv4cAnog2n0UfFjWOIFmHX06ipN7J/kMFuBXao 7nkVhGuHj6BzX0WILDpMiTv0O6c2zg8RC7IWYM0xzVUQN4BLBVBbtLHR8WngY2H/rtx/JtQUT8K28VDJkPBpcW/KyOhId dgt5b eWwVaL HMz/TDtn07al46zrOEmponUmZRR32VI3L3JfbfoaDf38SkOM8 bn1xBfb1LrlttfCA0TYWrxSnUu7jUsXyIuh4aXOXXgctrAssPnmI wWtnROZgTqmn3ejTeEyGwfnSyPZgVb8f3U9vi8dAgpGmfJkV7MXq6uO7Pn2Gd9sl/ewu CBDlEwZiab7m9X4uBN0mNHUNn0VJGCTq15k Dju1D/6NNPw4vQlq//oVNrq8dsu3f/LNgXijbTM0a/sGAs16 feVOXhs5b/aZf5LNunyA /JIw/pBYobOFzGxZo5 lIRw6jlRzhGe/8rmFX5oe7eFtZ5i8CQrtUhPypFkn/nEEvEUr9RD 9fdhSJ8eJdlIQrLzJOPMyXB04hBbiPj0/33V2 OH5Kcr mXBYLSiXK5gkQ0iXvUh29wmiq3zhUKtS9o TMh/f9APKIqBmU1wTdc7mQxj6y14sqaSwVZ7w5m34lK8Xx8UQJTCNlaOZmzGrrHdolef9X1XhUyg8isWftEVdORooQuKjHd7BF0ErsOsvT09AX8NnNyz4Sy2y2R AVuI2HxqeqBa48jZ81uq1zGn/F8M0 EFx3Y6Ypd2f2SHoJOo/ONjkFLIvY0lP2c8Khs9z1vmMymrzAnmIdS4WdluGwE4WXLidD3NXEYycRva0ciZaF7jEu4zy2VzCNjlv8Zlt4hEowYlRYQh8NAnH5RccNyvdtcpuPxcpz4v2W vMSTy8Qxm18zRqWrYC7O7puNLdtgV13oiMTOfnnLrSXW 0decWTBf9mRukXenuof 6/sGOHq79CXxW3J6bhH38a/cr3QObJiLLw7e30lHZnWrBfc6r8PmzCHTK5UKsihSXI6PVL6kpGPazuLzCJGqWbcv6ViJMvowp2qdDXjg6iwBbd3Wi0 s9H5GLRNvO4fPMJDwp6ndIdn2TPZz8iqjfHgHaHFIDGTLn09mxxZvFh58J/g6HM2zhMw2xzi 6/AsI mUPhq665DCRW6vs8CnebmN3KIfeu0Tavy3mx 2EX7tYvG/Sud6dIhwxn0bYosP4WXN41fNqYS/D1c501BN9aTF0f2m7ts1/aKKyf76q0BvLHaSHbUJtcRuN uzAyNAsRCScx67sm7jpcVzeh3BYlfvHSZmP7479qCvua8fFHoYLKklVhk 914vb2yvX/q/C8KkoRt6hOIRPGI53OjxqPSz90PMYGZfjZiTU1/D5MWKdP0EupSBo6FAMDU9z//7gZfisOyJJLbAzf/ YuO7TmHRALZD2jMMTon6rX1xnYyUOlwusKhY S7aKLw8iEC1V3qsuYvMLIgjVD0X0uuviJZSJNQ1nI hzx5XudkVIHyDrRyPN9cxBVkUnsLaRqNP/kDIqVxC1TtSPxDbxnLCtdF/ltNLdzov2RWBeJeckfnBAvLM7KlpvHbWz3rfyOosNLUT7baOxQR0tTPw Acs7y9HQEIQMzcQV7ZPBttJdZ7S1YOlaJfCtWat5bXrov64SdaV7G9GfX81I/jUeC RiolYpyHacr6FST8vUdqt4BfjqFg78T47iRmOvu28f95kqCZ8l/ BI5hrM2xyMX9TD 3L7JORxEQ47YrL2SNnZyWgpwmSb H1qgV1StDxU7xQ s35EA1H/yQXfl7Zr2/wXtBP1H8dI3QWYNUf1CJ9e8CZ84op1lLPWwCxYF3beQvQo8UHdfgdWOZ97oygfs4fE4wFxH2S7LltFw5UalnTblpvL/imH4uswr0hHzz4JytxPW9v13rZg4vZCNx/UXva/qvePE4ZPp9sr9/6v6vCpdQsX08LRVy4uqv8OonQH0iohfHqh0sNn2i9o4SZ8VsaCoysL5XzGUJj0lrnuN2OWCG9lr3QXwUctcXF8N aJNmYHqMPIB7Zjjho4L85eLm7bm5XuHtpXpgrMxsxxrtHKNmqXYD2fTPlcyECkaF9OS3B4H7t1FXv6ycPfC5C0S/MtW13prjfaemr0fFE/ApsPqgWS2v9Z472cq6Oek3XykGNqwLmFzBFhot0wxKboPGFtYfX9A Id0Ve51i8jLTern2P3v0oPn8VHEDL/KdQRAVG267y5hE91JFMvfOouODr2A q7aVtutcc0x1eaxWo1Uc0Kn Ibe5Ry6F2d/1eYg4Ev6h9yP7vKjNrtYvHcr3/iaP5thzc4zx/eXoYrdeSz 0rf33rK40Z AQ5nnMfyBWl47j ifx3NiNQdtfCu/1W f5wwfDreXvn3f/n6XxGXl/dBbRHU3lqk94SrpuEzYyLaiPrNfrCeDtruNmIH1alw Dz5veN8Rq3rK9eLv4VgXZybN8prR6wr0T2cwud2fIIyurnSOklOvDWewNomszH9p7PI lqGN08r3ctu/2Z0rDKa6LqYppJG7cybMVX0YeFc1zBSsHydy0imbaW762hrAdLeE/elgeNpn2z9X7lKb7amK9tK93nT7XeqeFOy0sfgz49r5pKq1LA6fXQ5jr1ez8Sayl7pbsiCo/Kr7PB51tILfmOaov3aGBwtuOHw/rk9tq1r MyZgqdFmHzc5HwG/9swrZKLiPRHPnts9/LLSw10V d8Os5pNCJ8inqmHfAT/eq2sgCFm/eIABiLD3QOuSeMk4cuUzHH5Yt7CZImVMJhZXXO579dThNS9TIXyvAfi0EpDmMGKu/6X X7x0mZj6 HOZ9/Jh/Axz kYYH7JYM uRfCZ/n3f9WEz1MrPsebb76LmWmuz6k7Kf5oIILa80F6k5irafjMi8R/Rf1ar4fDIVLfOYhfn5X9rEj4vIbdb4tw8UgiNGs1S/05YZEIbx5Wuqsjo1N/cD8uZh3dXIhk6wkFBXV1fXczUj4Q/3pa6e5F 7nTlor25yA2RS2wuXUWG Sphzy174WrS6wr3WM2uj7fcgLleUsdR1YvzV2phMl1cU5zjgqO43d5ePzNfQ4njHfbf1yGuUcogltvxHHNSzk/XK50l6er0jw55X1tJdpuasIhpw9B5QcExD6MXFyOJVfqSvfpYypx0qAhC47Kr3LDZwkSldHKHpirDYyK29gY3cI1fBasRE8RJmuHRzi 3kuOYKJcoORmzme7jQ4LW0ijysPng53NCNGs3tWudnf4sQeDwqdcYNS7fQwe CIbKwPilH6sdj7kLuyZLU8kHod G66Lp6rd9exs9H3d04f7LUT5y/uxHVE67Za6/hc e0nU65mBbdr9UFyAeV/Eo2HnHYhyeWH44NpfGPVRKrqMO4NzDu/Pt0X4sN63b3arRQ6863 V7x8nZT6 Bepq96678bvTavdf3peHorcgzHmhSjndC Gz/PsfOLpgk/j7BgzfVo4XmhvXE4fjYRHGHuu/HGe1c9yKzyH64 YijD2MYfF64zRXEdVbhrV3EOXxe9g9Fj5xGnO7 onyZnhvRiqy8wtRcPEoYkf3wrNPVvSwuzqf8bU0p/MOSzex/0Pxt0Zx7le6r1FXorsNNndwdLhc6b4eGZrEdeZnuYI EqHy5OXOP7epUXb7wN8hK5Q686fkaualFuHcjDXWc2dOv jwvPVV9o/hov/LYXE67n9j324saS76/9xW5Gieh/kL1ij9CZ143n7oteQmTk IUs51unSJ45tR3hxr/ cF5drri3tyeZVJWZw068cz4p3MzrrSfSm2ORxKLRH7VH5RmIOVqx2PcFnDaig2bFELfGCbM1tpK92rgcoe dyT0EEExpbovy/X8f0zdyk DJJzOJ3Cp3hNhoY9KsqfQa9UM7ILr6Pg2jGYNgzEc0E6h92LkjFYro6fOgbm65pbuH0G4YvboFHAR1heju8dum5kYMXEiZgotkWWioQGY1V5 Gz67R706ZqM/47x8jyf3obPUzkYp1lkYTuPYusRGaVlo6dnY7fLUe0irPhKfDi/kIRWr4lwPPKMwzdem1vZx9FVhpnn4/CC/x6MDErH0JGpaCbC9JDv5amf3H 4n1y VRldfaTfLgwvPY/lYWxw/MqEk79blPM01umWin7j0zEqMA0935UrlU14YcZF3X5576b4IJQLl2LRbMAufBks9seUdAz5IgUN5W32Pog0N0eUvOl/le8fnx9fx/N8vjc2XTnvZel5PqdV5DyfjrwKn77238fwWZH9fyszEx3EF7DaXbdgUJC9j6Onn8ERtY7P7pxC5HuPKgGvXpvu Gj41/jG/1P8998NlbJH3o1AtpvX9cnQrsrCpEf 0w/DS8/zGYANDqO6PobPrHUYp57jU3uez9bvfFNaNnr0Auy2fX77HD7Fe376dHRpKPtk3xp2C8H0wRVccHQ HUtEuHCZz6j4C0lPi3D1suNK9/yknaWn6fnjzXBr0Ppoi1q2E8ccBskuw/yKaMNpscvNtdZQI69rP enlW/tCyf3YXETcRv1wxDWLxEbxHNyTfeFyqH ae/uwQXvjma7UYC0d2XbS7ByrLVPSWOSse7DZZgqD0c/thKpDufPEU6nI8KpP6u7LlDu77QBB5Hv/NzM3o8I bOaIiCGfpCADT9sRHTPCCU4/9bVjDMOWfUqdr4h6j4ch6NON1uy3zp3dFKvDIf3H XsAvXnIeJLtf/O2zJNSHbi6/lHq6VcE8ZrFunYzvP51OIxmsU7kdit7G/fw et3AXoJn9NaGxrdFzyFb6O RHDInvg8YA GLpcBlPn8Cle7zlz8eoEx3mcjeaFY8ZqnQVH4ovKyV39lfOIPji5B/pHj8a3677Eu9NbirrN0HGDpYKf73YlO0ajhZ8f/PxewPRqNI 0ysPnw4G5 HvfcQz 30Y07WjCwz1S8VGYh1848jZ8lrVgR27tzFii80Ugf4Nc4S5vKxYfxrkZPhBh5tKRU/jePxVPdzWhXudEPP/5foRn3ETaLM8f7rhVgLiQnejYI86 0Ee3vv0Xjpq/ZP1Fnuc TcPEuHxcrsiwgM2Na9iwYA 690pC4xdEP9qb0Oztzeg58Ti2XdA75K7yqv9VvH/K9fjexvHNmRg8OAXNOslfx1J/8SfxLvzCka/99zF8Vmj/i0hzxnwUAweI16R8XpT2x7epEC6KcrF76Vj07/YC2jxaHw/Vb4rWnXpj5NxNyHH3MpOKTiNu/Ifo KR4oy4Ncs5hz8fwmeKvnvLJ0/YmllxU65cjfEpFf6UhOjQIEwKCMHfVdpy7CcQPkyOiFQif5k2YIgLLwjk6o7RXDiFK/G3ysCzNiGIxDg7SP2WP3OT8yj22 ykVn8S6xiIsfXjQ8fyTJ9KURUjyOqvWaMc9fWxfVZCWDlOfpZjx2BxMbjIPIa/ gbjw07ji6bnglRzEP l4WqPAhnMx9ZkoLP8mDcdO6S4vR8GufYjtZe/P7FfWwTQ3G/lu lO4NwMb kZhZrO5on4YZr28FrGzT CS8zTWktPWxUMv7YTrbriIzS KvzVYhV2lAd06xUHbf fN/VxQ8Vh86n6f3zfKWLAjt9pj3kekkujLseBIvAdeylmFH5b0wDOTWqBBQDt0WPQ95p/OQ1q8fviUiq7sRbR5BgISZyB03y6cE0 1hGg5IuocPiX7Lxy1CGiOBhOfR/t5IxC4/2DlfL6rsma8pEyNeOiN RWaymK0Kg f9QJcP0yJiO5XcUPlgqMuCK3IaSxrjOvIDtcZ XPeFvwpahLde LXyAVHbyHMzYyeqpWPZb0fhJ9fAwyI1hlpu4dVefjUG8khIro/ncasl2uhlt n2FDhEb6aIAcJLT2PAsptsttzmhLdTWcxW/6a0s8j4O7kE1WqKAmfN/KD36OfI8mYE dUGoZPIqJy Qfbw 3zSkf/9C2GvvUUHqpVCy39k13O8kFE1dlVWCz2eahjTGMxbOGLqDemMVqvTb07r/e9E9DGzw tf7JophRUDwyfRETlYpuHqm4P1MUjT3XCh PX43jNWYhMVEPkIjzcPg 19tjmeGxKD/RLMDmcdstIp0Nfg5/fc/jtcCVOIjVI1YVPIiIiIiInDJ9EREREZBiGTyIiIiIyDMMnERERERmG4ZOIiIiIDMPwSURERESGYfgkIiIiIsMwfBIRERGRYRg iYiIiMgwDJ9EREREZBiGTyIiIiIyDMMnERERERmG4ZOIiIiIDMPwSURERESGYfgkIiIiIsMwfBIRERGRYRg iYiIiMgwDJ9EREREZBiGTyIiIiIyDMMnERERERmG4ZOIiIiIDMPwSURERESGYfgkIiIiIoMA/x8gBXOjv4F2kgAAAABJRU5ErkJggg==";
*/
@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天, 我在网上找了一些方案, 基本都是直接通过文件夹路径访问, 但是只有单击可以, 我在集群上发现是不行的, 所以我是自己做了个接口直接返回图片流
扩展 返回图片流接口
代码语言:javascript复制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