前言
简介
- 官网地址:https://docs.min.io/cn/
- MinIO 是在 Apache License v2.0 下发布的对象存储服务器。 它与 Amazon S3 云存储服务兼容。 它最适合存储非结构化数据,如照片,视频,日志文件,备份和容器/ VM 映像。 对象的大小可以从几 KB 到最大 5TB。
- MinIO 服务器足够轻,可以与应用程序堆栈捆绑在一起,类似于 NodeJS,Redis 和 MySQL。
- 一种高性能的分布式对象存储服务器,用于大型数据基础设施。它是机器学习和其他大数
据工作负载下 Hadoop HDFS 的理想 s3 兼容替代品
优点/选择的原因
- Minio 有良好的存储机制
- Minio 有很好纠删码的算法与擦除编码算法
- 拥有RS code 编码数据恢复原理
- 公司做强做大时,数据的拥有重要性,对数据治理与大数据分析做准备。
- 搭建自己的一套文件系统服务,对文件数据进行安全保护。
- 拥有自己的平台,不限于其他方限制。
一、安装/配置及启动
安装及启动
启动后会打印出 AccessKey 和 SecretKey 等信息
- Windows:https://min.io/download#/windows
- Linux:https://min.io/download#/linux
- Docker:https://min.io/download#/docker
默认配置
- 默认 AccessKey 和 SecretKey
- AccessKey:minioadmin
- SecretKey:minioadmin
- 自定义 AccessKey 和 SecretKey
export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=miniostorage
- 默认端口
- 9000
- 自定义端口
--address 0.0.0.0:9001
- 自定义文件夹地址
export MINIO_VOLUMES="/home/minio/data"
访问
- 本机访问:默认端口(http://127.0.0.1:9000)
二、使用
- 访问
http://127.0.0.1:9000
进行登录 - 创建Bucket :选择Administrator-Buckets菜单,单击右侧Create Buckets按钮进入Bucket创建页面进行创建
- 选择:User-Object Browser菜单,并单击右侧 Bucket 进入管理文件页面
4.上传:选择右侧Upload上传按钮即可上传
三、MINIO 基础概念
-
Object:存储到 Minio 的基本对象,如文件、字节流,Anything…
-
Bucket:用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
-
Drive:即存储数据的磁盘,在 MinIO 启动时,以参数的方式传入。Minio 中所有的对象数据都会存储在 Drive 里。
-
Set:即一组 Drive 的集合
- 每个 Set 中的 Drive 分布在不同位置。
- 一个对象存储在一个Set上
- 一个集群划分为多个Set
- 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
- 一个SET中的Drive尽可能分布在不同的节点上
四、Java中上传文件MinIO服务器
1、引入 maven 依赖及配置
引入maven
<!-- minio 相关依赖 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
<!-- alibaba的fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.23</version>
</dependency>
<!-- thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
application.yml配置
server:
port: 2546
minio:
endpoint: http://127.0.0.1:9000 #MinIO服务所在地址
port: 9000
bucketName: filestorebucket #存储桶名称
accessKey: admin #访问的key
secretKey: adminadmin #访问的秘钥
secure: false
将配置文件中 minio 的配置信息通过注解的方式注入到 MinioProp
,方便后续使用
/**
* minio 属性值
*/
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProp {
/**
* 连接url
*/
private String endpoint;
/**
* 端口
*/
private String port;
/**
* 存储桶名称
*/
private String bucketName;
/**
* 用户名
*/
private String accesskey;
/**
* 密码
*/
private String secretKey;
private String secure;
}
核心配置类
/**
* minio 核心配置类
*/
@Configuration
@EnableConfigurationProperties(MinioProp.class)
public class MinioConfig {
@Autowired
private MinioProp minioProp;
/**
* 获取 MinioClient
*
*/
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(minioProp.getEndpoint())
.credentials(minioProp.getAccesskey(),minioProp.getSecretKey())
.build();
}
}
2、示例代码
工具类
/**
* Minio工具类
*
* @author wh
* @version 1.0.0
*/
@Slf4j
@Component
public class MinioUtils {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
@Value("${minio.bucketName}")
private String BUCKET_NAME;
@Value("${minio.endpoint}")
private String ENDPOINT;
@Resource
public MinioClient minioClient;
/**
* 判断桶是否存在
*
* @param bucketName bucket名称
* @return true存在,false不存在
*/
public Boolean bucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("检查桶是否存在失败!", e);
}
}
/**
* 创建bucket
*
* @param bucketName bucket名称
*/
public void createBucket(String bucketName) {
if (this.bucketExists(bucketName)) {
log.info("存储桶已经存在!");
} else {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("创建桶失败!", e);
}
}
}
/**
* 创建只读bucket
*
* @param bucketName bucket名称
*/
public void createReadOnlyBucket(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
if (this.bucketExists(bucketName)) {
log.info("存储桶已经存在!");
} else {
//创建存储桶并设置只读权限
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
BucketPolicyConfig bucketPolicyConfig = createBucketPolicyConfig(bucketName);
SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(JSONObject.toJSONString(bucketPolicyConfig))
.build();
minioClient.setBucketPolicy(setBucketPolicyArgs);
}
}
private BucketPolicyConfig createBucketPolicyConfig(String bucketName) {
BucketPolicyConfig.Statement statement = BucketPolicyConfig.Statement.builder()
.Effect("Allow")
.Principal("*")
.Action("s3:GetObject")
.Resource("arn:aws:s3:::" + bucketName + "/*.**").build();
return BucketPolicyConfig.builder()
.Version("2012-10-17")
.Statement(Collections.singletonList(statement))
.build();
}
/**
* 上传MultipartFile文件到全局默认文件桶中
*
* @param file 文件
* @return 文件对应的URL
*/
public String putObject(MultipartFile file) {
// 给文件名添加时间戳防止重复
String fileName = FileUtil.renFileName(Objects.requireNonNull(file.getOriginalFilename()));
String objectName = sdf.format(new Date()) + "/" + fileName;
// 开始上传
this.putMultipartFile(BUCKET_NAME, objectName, file);
log.info("文件上传成功!");
return objectName;
}
/**
* 上传文件
*
* @param fileName 文件名
* @param stream 文件流
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
* @return 文件url
*/
public String putObject(@NotNull String fileName, @NotNull InputStream stream, @NotNull String contentType) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
this.createReadOnlyBucket(BUCKET_NAME);
// 给文件名添加时间戳防止重复
String objectName = sdf.format(new Date()) + "/" + FileUtil.renFileName(fileName);
// 开始上传
this.putInputStream(BUCKET_NAME, objectName, stream, contentType);
return ENDPOINT + "/" + BUCKET_NAME + "/" + objectName;
}
/**
* 上传bytes文件
*
* @param fileName 文件名
* @param bytes 字节
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
* @return 文件url
*/
public String putObject(String fileName, byte[] bytes, String contentType) {
// 给文件名添加时间戳防止重复
String objectName = sdf.format(new Date()) + "/" + FileUtil.renFileName(fileName);
// 开始上传
this.putBytes(BUCKET_NAME, objectName, bytes, contentType);
return ENDPOINT + "/" + BUCKET_NAME + "/" + objectName;
}
/**
* 上传MultipartFile文件到指定的文件桶下
*
* @param bucketName 桶名称
* @param file 文件
* @return 文件对应的URL
*/
public String putObject(String bucketName, String fileName, MultipartFile file) {
// 先创建桶
this.createBucket(bucketName);
// 给文件名添加时间戳防止重复
String objectName = sdf.format(new Date()) + "/" + FileUtil.renFileName(fileName);
// 开始上传
this.putMultipartFile(BUCKET_NAME, objectName, file);
log.info("文件上传成功!");
return ENDPOINT + "/" + BUCKET_NAME + "/" + objectName;
}
/**
* 上传流到指定的文件桶下
*
* @param bucketName 桶名称
* @param fileName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @param stream 文件流
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
* @return 文件对应的URL
*/
public String putObject(String bucketName, String fileName, InputStream stream, String contentType) {
// 先创建桶
this.createBucket(bucketName);
// 给文件名添加时间戳防止重复
String objectName = sdf.format(new Date()) + "/" + FileUtil.renFileName(fileName);
// 开始上传
this.putInputStream(bucketName, objectName, stream, contentType);
return ENDPOINT + "/" + bucketName + "/" + objectName;
}
/**
* 上传流到指定的文件桶下
*
* @param bucketName 桶名称
* @param fileName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @param bytes 字节
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
* @return 文件对应的URL
*/
public String putObject(String bucketName, String fileName, byte[] bytes, String contentType) {
// 先创建桶
this.createBucket(bucketName);
// 给文件名添加时间戳防止重复
String objectName = sdf.format(new Date()) + "/" + FileUtil.renFileName(fileName);
// 开始上传
this.putBytes(bucketName, objectName, bytes, contentType);
return ENDPOINT + "/" + bucketName + "/" + objectName;
}
/**
* 上传File文件到默认桶下
*
* @param fileName 文件名
* @param file 文件
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
* @return 文件对应的URL
*/
public String putObject(String fileName, File file, String contentType) {
// 给文件名添加时间戳防止重复
String objectName = sdf.format(new Date()) + "/" + FileUtil.renFileName(fileName);
// 开始上传
this.putFile(BUCKET_NAME, objectName, file, contentType);
return ENDPOINT + "/" + BUCKET_NAME + "/" + objectName;
}
/**
* 上传File文件
*
* @param bucketName 文件桶
* @param objectName 文件名
* @param file 文件
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
* @return 文件对应的URL
*/
public String putObject(String bucketName, String objectName, File file, String contentType) {
// 先创建桶
this.createBucket(bucketName);
// 给文件名添加时间戳防止重复
String fileName = FileUtil.renFileName(objectName);
// 开始上传
this.putFile(bucketName, fileName, file, contentType);
return ENDPOINT + "/" + bucketName + "/" + fileName;
}
/**
* 判断文件是否存在
*
* @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @return true存在, 反之
*/
public Boolean checkFileIsExist(String objectName) {
return this.checkFileIsExist(BUCKET_NAME, objectName);
}
/**
* 判断文件夹是否存在
*
* @param folderName 文件夹名称
* @return true存在, 反之
*/
public Boolean checkFolderIsExist(String folderName) {
return this.checkFolderIsExist(BUCKET_NAME, folderName);
}
/**
* 判断文件是否存在
*
* @param bucketName 桶名称
* @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @return true存在, 反之
*/
public Boolean checkFileIsExist(String bucketName, String objectName) {
try {
minioClient.statObject(
StatObjectArgs.builder().bucket(bucketName).object(objectName).build()
);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 判断文件夹是否存在
*
* @param bucketName 桶名称
* @param folderName 文件夹名称
* @return true存在, 反之
*/
public Boolean checkFolderIsExist(String bucketName, String folderName) {
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs
.builder()
.bucket(bucketName)
.prefix(folderName)
.recursive(false)
.build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir() && folderName.equals(item.objectName())) {
return true;
}
}
} catch (Exception e) {
return false;
}
return true;
}
/**
* 根据文件全路径获取文件流
*
* @param objectName 文件名称
* @return 文件流
*/
public InputStream getObject(String objectName) {
return this.getObject(BUCKET_NAME, objectName);
}
/**
* 根据文件桶和文件全路径获取文件流
*
* @param bucketName 桶名称
* @param objectName 文件名
* @return 文件流
*/
public InputStream getObject(String bucketName, String objectName) {
try {
return minioClient
.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
throw new RuntimeException("根据文件名获取流失败!", e);
}
}
/**
* 根据url获取文件流
*
* @param url 文件URL
* @return 文件流
*/
public InputStream getObjectByUrl(String url) {
try {
return new URL(url).openStream();
} catch (IOException e) {
throw new RuntimeException("根据URL获取流失败!", e);
}
}
/**
* 获取全部bucket
*
* @return 所有桶信息
*/
public List<Bucket> getAllBuckets() {
try {
return minioClient.listBuckets();
} catch (Exception e) {
throw new RuntimeException("获取全部存储桶失败!", e);
}
}
/**
* 根据bucketName获取信息
*
* @param bucketName bucket名称
* @return 单个桶信息
*/
public Optional<Bucket> getBucket(String bucketName) {
try {
return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
} catch (Exception e) {
throw new RuntimeException("根据存储桶名称获取信息失败!", e);
}
}
/**
* 根据bucketName删除信息
*
* @param bucketName bucket名称
*/
public void removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("根据存储桶名称删除桶失败!", e);
}
}
/**
* 删除文件
*
* @param objectName 文件名称
*/
public boolean removeObject(String objectName) {
try {
this.removeObject(BUCKET_NAME, objectName);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 删除文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
*/
public boolean removeObject(String bucketName, String objectName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
return false;
}
return true;
}
/**
* 上传MultipartFile通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param file 文件
*/
private void putMultipartFile(String bucketName, String objectName, MultipartFile file) {
InputStream stream = null;
try {
stream = file.getInputStream();
} catch (IOException e) {
throw new RuntimeException("文件流获取错误", e);
}
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(file.getContentType())
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("文件流上传错误", e);
}
}
/**
* 上传InputStream通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param stream 文件流
*/
private void putInputStream(String bucketName, String objectName, InputStream stream, String contentType) {
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件流上传错误", e);
}
}
/**
* 上传 bytes 通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param bytes 字节编码
*/
private void putBytes(String bucketName, String objectName, byte[] bytes, String contentType) {
// 字节转文件流
InputStream stream = new ByteArrayInputStream(bytes);
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件流上传错误", e);
}
}
/**
* 上传 file 通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param file 文件
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 点击查看: <a href="https://www.runoob.com/http/http-content-type.html">图片格式</a>
*/
private void putFile(String bucketName, String objectName, File file, String contentType) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(fileInputStream, fileInputStream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件上传错误", e);
}
}
/**
* 获取文件信息, 如果抛出异常则说明文件不存在
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @throws Exception <a href="https://docs.minio.io/cn/java-client-api-reference.html#statObject">官方异常信息</a>
*/
public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 获取文件外链
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param expires 过期时间 <=7
* @return url
*/
@SneakyThrows
public String getObjectURL(String bucketName, String objectName, Integer expires) {
return getObjectURL(bucketName, objectName, expires, Method.GET);
}
/**
* 获取文件外链
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param expires 过期时间 <=7
* @return url
*/
@SneakyThrows
public String getObjectURL(String bucketName, String objectName, Integer expires, Method method) {
Boolean aBoolean = checkFileIsExist(bucketName, objectName);
if (!aBoolean) {
throw new RuntimeException("文件不存在!");
}
IntFunction<Integer> integerIntFunction = (int i) -> Math.min(i, 7);
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(method)
.bucket(bucketName)
.object(objectName)
.expiry(integerIntFunction.apply(expires), TimeUnit.HOURS)
.build());
}
/**
* 获取文件外链
*
* @param objectName 文件名称
* @param expires 过期时间 <=7
* @return url
*/
@SneakyThrows
public String getObjectURL(String objectName, Integer expires) {
return getObjectURL(BUCKET_NAME, objectName, expires);
}
/**
* 获取文件外链
*
* @param objectName 文件名称
* @return url
*/
@SneakyThrows
public String getObjectURL(String objectName) {
return getObjectURL(BUCKET_NAME, objectName, 1);
}
/**
* 根据文件前置查询文件
*
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return MinioItem 列表
*/
@SneakyThrows
public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
List<Item> list = new ArrayList<>();
Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(prefix)
.recursive(recursive).build()
);
if (objectsIterator != null) {
for (Result<Item> result : objectsIterator) {
Item item = result.get();
list.add(item);
}
}
return list;
}
}
public class FileUtil {
/**
* 生成唯一ID
*
* @param objectName 文件名
* @return 唯一ID
*/
public static String renFileName(String objectName) {
//判断文件最后一个点所在的位置
int lastIndexOf = objectName.lastIndexOf(".");
if (lastIndexOf == -1) {
return String.format("%s_%s", objectName, System.currentTimeMillis());
} else {
// 获取文件前缀,已最后一个点进行分割
String filePrefix = objectName.substring(0, objectName.lastIndexOf("."));
// 获取文件后缀,已最后一个点进行分割
String fileSuffix = objectName.substring(objectName.lastIndexOf(".") + 1);
// 组成唯一文件名
return String.format("%s_%s.%s", filePrefix, System.currentTimeMillis(), fileSuffix);
}
}
}
/**
* Minio Bucket访问策略配置
* Created by macro on 2020/8/11.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class BucketPolicyConfig {
private String Version;
private List<Statement> Statement;
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public static class Statement {
private String Effect;
private String Principal;
private String Action;
private String Resource;
}
}
MinioController(控制层)
/**
* MinIO对象存储管理
*/
@Controller
@Slf4j
@RequestMapping("/minio")
public class MinioController{
@Resource
private MinioUtils minioUtils;
/**
* 文件上传
*
* @param file
* @return
*/
@PostMapping(value = "/upload")
@ResponseBody
public Map<String, String> upload(@RequestPart("file") MultipartFile file) {
try {
String url = minioUtils.putObject(file);
Map<String, String> map = new HashMap<>();
String fileName = file.getOriginalFilename();
map.put("fileName", fileName);
map.put("objName", url);
return map;
} catch (Exception e) {
e.printStackTrace();
log.info("上传发生错误: {}!", e.getMessage());
throw e;
}
}
/**
* 文件删除
*
* @param objectName
* @return
*/
@PostMapping(value = "/delete")
@ResponseBody
public boolean delete(@RequestParam("objectName") String objectName) {
try {
minioUtils.removeObject(objectName);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 文件预览
*
* @param objectName 对象名称
* @return 文件访问连接
*/
@GetMapping(value = "/preview")
@ResponseBody
public String preview(@RequestParam("objectName") String objectName) {
Boolean aBoolean = minioUtils.checkFileIsExist(objectName);
if (!aBoolean) {
return "文件不存在!";
}
return minioUtils.getObjectURL(objectName);
}
/**
* 文件下载
*
* @param objectName 对象名称
* @return 文件流
*/
@GetMapping(value = "/download")
@ResponseBody
public void download(@RequestParam("objectName") String objectName, HttpServletResponse response) throws IOException {
try {
Boolean aBoolean = minioUtils.checkFileIsExist(objectName);
if (!aBoolean) {
response.setHeader("Content-type", "text/html;charset=UTF-8");
String data = "文件下载失败";
OutputStream ps = response.getOutputStream();
ps.write(data.getBytes(StandardCharsets.UTF_8));
return;
}
InputStream object = minioUtils.getObject(objectName);
byte[] buf = new byte[1024];
int length;
response.reset();
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(objectName.substring(objectName.lastIndexOf("/") + 1), "UTF-8"));
response.setContentType("application/octet-stream");
response.setCharacterEncoding("UTF-8");
OutputStream outputStream = response.getOutputStream();
// 输出文件
while ((length = object.read(buf)) > 0) {
outputStream.write(buf, 0, length);
}
// 关闭输出流
outputStream.close();
} catch (Exception e) {
response.setHeader("Content-type", "text/html;charset=UTF-8");
String data = "文件下载失败";
OutputStream ps = response.getOutputStream();
ps.write(data.getBytes(StandardCharsets.UTF_8));
}
}
}
上传测试
删除测试
获取文件/预览地址