临时文件存储
在应用部署为多实例的场景下,不能将文件存储在应用系统内部,可以使用如下两种方式实现文件存储
- 使用共享存储,共享存储目录为 /storage/data,在其下自定义目录存储,需要注意的是文件多时有性能问题
- 使用对象存储,例如使用 minio 存储
下面给出具体示例,对于临时存储,可以按照日期创建目录或 bucket,再按照日期删除目录或 bucket。完整代码在文章的最后
使用共享存储
按照规则“临时文件夹名+日期”创建今天的操作目录(可按照需求添加用户 id 等完善命名规则),每次上传文件时都会检查昨天文件存储目录是否存在,如果存在则清空文件夹并删除,如果不存在直接存储文件到今天临时目录下。示例代码如下:
//定义临时目录路径前缀
private static final String BASE_DIR = "/storage/data/uploaded_files_";
/**
* 递归删除目录及其内容。
*
* @param dirPath 要删除的目录路径
* @throws IOException 如果发生 I/O 错误
*/
private void deleteDirectory(Path dirPath) throws IOException {
if (Files.exists(dirPath)) {
Files.walkFileTree(dirPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
System.out.println("已删除文件: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
System.out.println("已删除目录: " + dir);
return FileVisitResult.CONTINUE;
}
});
} else {
System.out.println("指定的目录不存在: " + dirPath);
}
}
/**
* 上传文件到共享存储
* @param file
*/
private void uploadTempFile(MultipartFile file) {
InputStream ins = null;
try {
ins = file.getInputStream();
// 昨天
LocalDate yesterday = LocalDate.now().minusDays(1);
Path yesterdayPath = Paths.get(BASE_DIR + yesterday.format(DateTimeFormatter.BASIC_ISO_DATE));
if (Files.exists(yesterdayPath)) {
//清空昨天文件存储目录
this.deleteDirectory(yesterdayPath);
}
// 今天
LocalDate today = LocalDate.now();
Path todayPath = Paths.get(BASE_DIR + today.format(DateTimeFormatter.BASIC_ISO_DATE));
if (Files.notExists(todayPath)) {
Files.createDirectories(todayPath);
}
//上传文件到指定目录
Path targetFilePath = todayPath.resolve(file.getOriginalFilename());
Files.copy(ins, targetFilePath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ins);
}
}
使用对象存储
实现逻辑与上面方法相似,由于需要操作 minio 的批量删除等操作,需要使用 minio 相关原生方法,通过下面代码获取 minio 客户端实例:
import storage.util.MinioClientFactory;
@Autowired
MinioClientFactory minioClientFactory;
//获取当前请求,通过请求获取到minio客户端
HttpServletRequest request = SpringWebUtil.getRequest();
MinioClient minioClient = minioClientFactory.getMinioClient(request);
通过日期规则定义 bucket 名称(可按照需求添加用户 id 等完善命名规则),实现示例如下:
//自定义存储桶bucket命名前缀
private static final String BUCKET_NAME = "tempbucket";
/**
* 上传文件至minio
* @param file 上传文件
*/
private void uploadMinioTempFile(MultipartFile file) {
InputStream ins = null;
try {
HttpServletRequest request = SpringWebUtil.getRequest();
// 今天
LocalDate today = LocalDate.now();
String todayBucket = BUCKET_NAME + today.format(DateTimeFormatter.BASIC_ISO_DATE);
// 昨天
LocalDate yesterday = today.minusDays(1);
String yesterdayBucket = BUCKET_NAME + yesterday.format(DateTimeFormatter.BASIC_ISO_DATE);
ins = file.getInputStream();
// 获取minio客户端
MinioClient minioClient = minioClientFactory.getMinioClient(request);
// 处理昨天数据
if (minioClient.bucketExists(yesterdayBucket)) {
System.out.println("存储桶存在,准备清理...");
// 删除存储桶中的所有对象
minioClient.listObjects(yesterdayBucket).forEach(item -> {
try {
minioClient.removeObject(yesterdayBucket, item.get().objectName());
} catch (Exception e) {
e.printStackTrace();
}
});
// 删除空的存储桶
minioClient.removeBucket(yesterdayBucket);
System.out.println("存储桶已成功删除.");
} else if (!minioClient.bucketExists(todayBucket)) {
minioClient.makeBucket(todayBucket);
}
// 上传
minioClient.putObject(todayBucket, file.getOriginalFilename(), ins, file.getSize(), file.getContentType());
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ins);
}
}
/**
* 获取上传文件
* @param storeFileName 上传对象名称
* @param operateType 操作类型,支持download,view
*/
public void getMinioTempFile(String storeFileName, String operateType) {
try {
LocalDate today = LocalDate.now();
String todayBucket = BUCKET_NAME + today.format(DateTimeFormatter.BASIC_ISO_DATE);
HttpServletRequest request = SpringWebUtil.getRequest();
HttpServletResponse response = SpringWebUtil.getResponse();
MinioClient minioClient = minioClientFactory.getMinioClient(request);
//获取对象属性
ObjectStat objectStat = minioClient.statObject(todayBucket, storeFileName);
response.setContentType(objectStat.contentType());
//获取对象流
InputStream inputStream = minioClient.getObject(todayBucket, storeFileName);
if ("download".equals(operateType)) {
String headerKey = "Content-Disposition";
String headerValue = "attachment; filename=\"" + URLEncoder.encode(objectStat.name(), "UTF-8") + "\"";
response.setHeader(headerKey, headerValue);
}
OutputStream os = response.getOutputStream();
// 将文件内容写入响应流
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
通过“企业门户-->资源-->对象存储管理”可进入到 minio 管理页面,查看 minio 上传相关内容
获取 minio 登录账号信息,使用管理员账号登录控制台,打开 kube 控制面板,切换到“全部命名空间”,在容器组中搜索 minio 关键词,找到 minio-deploy,点击名称进入到详情页,从环境变量中获取登录用户名和密码。
完整示例代码
包括两个公共方法
- 临时文件存储:tempSave
- 获取 minio 上传文件:getMinioTempFile
package main.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import com.justep.util.SpringWebUtil;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import main.service.*;
import storage.util.MinioClientFactory;
@Service("main-TestCustomUserService")
public class TestUserServiceCustomImpl implements TestServiceCustom {
private static final String BASE_DIR = "/storage/data/uploaded_files_";
//自定义存储桶bucket命名前缀
private static final String BUCKET_NAME = "tempbucket";
@Autowired
MinioClientFactory minioClientFactory;
public String tempSave(MultipartFile file, String type) throws Exception {
// 请添加你的业务代码
if ("file".equals(type)) {
this.uploadTempFile(file);
} else {
this.uploadMinioTempFile(file);
}
return "ok";
}
/**
* 递归删除目录及其内容。
*
* @param dirPath 要删除的目录路径
* @throws IOException 如果发生 I/O 错误
*/
private void deleteDirectory(Path dirPath) throws IOException {
if (Files.exists(dirPath)) {
Files.walkFileTree(dirPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
System.out.println("已删除文件: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
System.out.println("已删除目录: " + dir);
return FileVisitResult.CONTINUE;
}
});
} else {
System.out.println("指定的目录不存在: " + dirPath);
}
}
/**
* 上传文件到共享存储
* @param file
*/
private void uploadTempFile(MultipartFile file) {
InputStream ins = null;
try {
ins = file.getInputStream();
// 昨天
LocalDate yesterday = LocalDate.now().minusDays(1);
Path yesterdayPath = Paths.get(BASE_DIR + yesterday.format(DateTimeFormatter.BASIC_ISO_DATE));
if (Files.exists(yesterdayPath)) {
//清空昨天文件存储目录
this.deleteDirectory(yesterdayPath);
}
// 今天
LocalDate today = LocalDate.now();
Path todayPath = Paths.get(BASE_DIR + today.format(DateTimeFormatter.BASIC_ISO_DATE));
if (Files.notExists(todayPath)) {
Files.createDirectories(todayPath);
}
//上传文件到指定目录
Path targetFilePath = todayPath.resolve(file.getOriginalFilename());
Files.copy(ins, targetFilePath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ins);
}
}
/**
* 上传文件至minio
* @param file 上传文件
*/
private void uploadMinioTempFile(MultipartFile file) {
InputStream ins = null;
try {
HttpServletRequest request = SpringWebUtil.getRequest();
// 今天
LocalDate today = LocalDate.now();
String todayBucket = BUCKET_NAME + today.format(DateTimeFormatter.BASIC_ISO_DATE);
// 昨天
LocalDate yesterday = today.minusDays(1);
String yesterdayBucket = BUCKET_NAME + yesterday.format(DateTimeFormatter.BASIC_ISO_DATE);
ins = file.getInputStream();
// 获取minio客户端
MinioClient minioClient = minioClientFactory.getMinioClient(request);
// 处理昨天数据
if (minioClient.bucketExists(yesterdayBucket)) {
System.out.println("存储桶存在,准备清理...");
// 删除存储桶中的所有对象
minioClient.listObjects(yesterdayBucket).forEach(item -> {
try {
minioClient.removeObject(yesterdayBucket, item.get().objectName());
} catch (Exception e) {
e.printStackTrace();
}
});
// 删除空的存储桶
minioClient.removeBucket(yesterdayBucket);
System.out.println("存储桶已成功删除.");
} else if (!minioClient.bucketExists(todayBucket)) {
minioClient.makeBucket(todayBucket);
}
// 上传
minioClient.putObject(todayBucket, file.getOriginalFilename(), ins, file.getSize(), file.getContentType());
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ins);
}
}
/**
* 获取上传文件
* @param storeFileName 上传对象名称
* @param operateType 操作类型,支持download,view
*/
public void getMinioTempFile(String storeFileName, String operateType) {
try {
LocalDate today = LocalDate.now();
String todayBucket = BUCKET_NAME + today.format(DateTimeFormatter.BASIC_ISO_DATE);
HttpServletRequest request = SpringWebUtil.getRequest();
HttpServletResponse response = SpringWebUtil.getResponse();
MinioClient minioClient = minioClientFactory.getMinioClient(request);
ObjectStat objectStat = minioClient.statObject(todayBucket, storeFileName);
response.setContentType(objectStat.contentType());
InputStream inputStream = minioClient.getObject(todayBucket, storeFileName);
if ("download".equals(operateType)) {
String headerKey = "Content-Disposition";
String headerValue = "attachment; filename=\"" + URLEncoder.encode(objectStat.name(), "UTF-8") + "\"";
response.setHeader(headerKey, headerValue);
}
OutputStream os = response.getOutputStream();
// 将文件内容写入响应流
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}