如果框架默认存储使用的本地磁盘(这里举例若依脚手架),对于一些文件较大较多且有数据备份、数据安全、分布式等等就满足不了我们的要求,对于这种情况我们可以集成OSS对象存储服务。minio是目前github上star最多的数据存储框架。minio可以用来搭建分布式存储服务,可以很好的和机器学习相结合。
1.ruoyi-common/pom.xml文件添加minio依赖。
<!-- Minio 文件存储 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.1</version>
</dependency>
2.在CommonController.java自定义Minio服务器上传请求,这里我们只需把uploadUtils后调用的方法改成我们自己的方法就行。url是为了配合本地存储的路径,这里我们只需返回两个一样就可以。
另外一种方式也可以在通知公告新增和修改页面将文件上传的路径common/upload修改为common/uploadMinio,然后上传图片测试验证结果。这个就是重写一个接口,原理一样,因为本人对前端不是太熟悉,这里不做介绍
/**
* 通用上传请求
*/
@PostMapping("/common/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception
{
try
{
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = uploadUtils.uploadMinio(file,"wsp");
String url = serverConfig.getUrl() + fileName;
AjaxResult ajax = AjaxResult.success();
ajax.put("fileName", fileName);
ajax.put("url", fileName);
return ajax;
}
catch (Exception e)
{
return AjaxResult.error(e.getMessage());
}
}
3.编写uploadMinio方法
/**
* 自定义bucketName配置上传到Minio服务器
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public final String uploadMinio(MultipartFile file, String bucketName) throws IOException
{
try
{
return uploadMinino(bucketName, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 真正的上传方法
*/
private final String uploadMinino(String bucketName, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException, ServerException, InsufficientDataException, NoSuchAlgorithmException, InternalException, InvalidResponseException, XmlParserException, InvalidKeyException, ErrorResponseException {
//判断是否有这个桶
if (!minioUtils.bucketExists(bucketName)) {
minioUtils.createBucket(bucketName);
}
//桶下是否有这个目录
if (!minioUtils.doesFolderExist(bucketName, getExtension(file))) {
minioUtils.putDirObject(bucketName, getExtension(file) + "/");
}
//判断文件长度
int fileNamelength = file.getOriginalFilename().length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
//判断文件大小
assertAllowed(file, allowedExtension);
try
{
String fileName = extractFilename(file);
if (getExtension(file).equals("jpg") || getExtension(file).equals("png") || getExtension(file).equals("jpg")){
minioUtils.putObject(bucketName, file,getExtension(file) + "/" + fileName,MimeTypeUtils.IMAGE_JPG);
} else {
minioUtils.putObject(bucketName, getExtension(file) + "/" + fileName,file.getInputStream());
}
return ACCESS_URL + "/" + bucketName + "/" + getExtension(file) + "/" + fileName;
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
还需要一个Minio操作工具类,这里为了以后DML操作,功能比较齐,如果文件服务器里的东西不需要删除,只写一个增加即可。
@Component
public class MinioUtils {
private static final String ENDPOINT = "http://localhost:9000";
private static final String ACCESS_KEY = "minioadmin";
private static final String SECRET_KEY = "minioadmin";
private MinioClient minioClient;
@PostConstruct
public void init() {
this.minioClient = MinioClient.builder().endpoint(ENDPOINT)
.credentials(ACCESS_KEY, SECRET_KEY).build();
}
/**
* 验证bucketName是否存在
*
* @return boolean true:存在
*/
public boolean bucketExists(String bucketName)
throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 创建bucket
*
* @param bucketName bucket名称
*/
public void createBucket(String bucketName)
throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
setBucketPolicy(bucketName);
}
}
/**
* 设置 bucket 的访问策略
*
* @param bucketName 桶名称
*/
public void setBucketPolicy(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
String config = " {\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Action\": [\n" +
" \"s3:GetBucketLocation\",\n" +
" \"s3:ListBucket\"\n" +
" ],\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": \"*\",\n" +
" \"Resource\": \"arn:aws:s3:::" + bucketName + "\"\n" +
" },\n" +
" {\n" +
" \"Action\": \"s3:GetObject\",\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": \"*\",\n" +
" \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"\n" +
" }\n" +
" ],\n" +
" \"Version\": \"2012-10-17\"\n" +
"}";
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(config)
.build());
}
/**
* 获取存储桶策略
*
* @param bucketName 存储桶名称
* @return json
*/
private JSONObject getBucketPolicy(String bucketName)
throws
IOException, InvalidKeyException, InvalidResponseException, BucketPolicyTooLargeException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InsufficientDataException, ErrorResponseException {
String bucketPolicy = minioClient
.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
return JSONObject.parseObject(bucketPolicy);
}
/**
* 获取全部bucket
* <p>
* https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
*/
public List<Bucket> getAllBuckets()
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.listBuckets();
}
/**
* 根据bucketName获取信息
*
* @param bucketName bucket名称
*/
public Optional<Bucket> getBucket(String bucketName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}
/**
* 根据bucketName删除信息
*
* @param bucketName bucket名称
*/
public void removeBucket(String bucketName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 判断文件是否存在
*
* @param bucketName 存储桶
* @param objectName 对象
* @return true:存在
*/
public boolean doesObjectExist(String bucketName, String objectName) {
boolean exist = true;
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
exist = false;
}
return exist;
}
/**
* 判断文件夹是否存在
*
* @param bucketName 存储桶
* @param objectName 文件夹名称(去掉/)
* @return true:存在
*/
public boolean doesFolderExist(String bucketName, String objectName) {
boolean exist = false;
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir() && objectName.equals(item.objectName())) {
exist = true;
}
}
} catch (Exception e) {
exist = false;
}
return exist;
}
/**
* 根据文件前置查询文件
*
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return MinioItem 列表
*/
public List<Item> getAllObjectsByPrefix(String bucketName, String prefix,
boolean recursive)
throws
ErrorResponseException, InsufficientDataException, InternalException, InvalidKeyException, InvalidResponseException,
IOException, NoSuchAlgorithmException, ServerException, XmlParserException {
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> o : objectsIterator) {
Item item = o.get();
list.add(item);
}
}
return list;
}
/**
* 获取文件流
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @return 二进制流
*/
public InputStream getObject(String bucketName, String objectName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 断点下载
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param offset 起始字节的位置
* @param length 要读取的长度
* @return 流
*/
public InputStream getObject(String bucketName, String objectName, long offset, long length)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length)
.build());
}
/**
* 获取路径下文件列表
*
* @param bucketName bucket名称
* @param prefix 文件名称
* @param recursive 是否递归查找,如果是false,就模拟文件夹结构查找
* @return 二进制流
*/
public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) {
return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
}
/**
* 通过MultipartFile,上传文件
*
* @param bucketName 存储桶
* @param file 文件
* @param objectName 对象名
*/
public ObjectWriteResponse putObject(String bucketName, MultipartFile file,
String objectName, String contentType)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
InputStream inputStream = file.getInputStream();
return minioClient.putObject(
PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType)
.stream(inputStream, inputStream.available(), -1)
.build());
}
/**
* 上传本地文件
*
* @param bucketName 存储桶
* @param objectName 对象名称
* @param fileName 本地文件路径
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, String fileName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(bucketName).object(objectName).filename(fileName).build());
}
/**
* 通过流上传文件
*
* @param bucketName 存储桶
* @param objectName 文件对象
* @param inputStream 文件流
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream inputStream)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.putObject(
PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
inputStream, inputStream.available(), -1)
.build());
}
/**
* 创建文件夹或目录
*
* @param bucketName 存储桶
* @param objectName 目录路径
*/
public ObjectWriteResponse putDirObject(String bucketName, String objectName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.putObject(
PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
new ByteArrayInputStream(new byte[]{}), 0, -1)
.build());
}
/**
* 获取文件信息, 如果抛出异常则说明文件不存在
*
* @param bucketName bucket名称
* @param objectName 文件名称
*/
public StatObjectResponse statObject(String bucketName, String objectName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient
.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 拷贝文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param srcBucketName 目标bucket名称
* @param srcObjectName 目标文件名称
*/
public ObjectWriteResponse copyObject(String bucketName, String objectName,
String srcBucketName, String srcObjectName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder().bucket(bucketName).object(objectName).build())
.bucket(srcBucketName)
.object(srcObjectName)
.build());
}
/**
* 删除文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
*/
public void removeObject(String bucketName, String objectName)
throws
IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 批量删除文件
*
* @param bucketName bucket
* @param keys 需要删除的文件列表
*/
public boolean removeObjects(String bucketName, List<String> keys) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
List<DeleteObject> objects = new LinkedList<>();
for (String objectName:keys){
objects.add(new DeleteObject(objectName));
}
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
}
return true;
}
}
这里我是本地搭建的minio服务,具体怎么搭建,请看Minio服务搭建
以上就是后端需要改动的地方,后面是前端需要改动的地方
src/components/Editor/index.vue
src/components/ImageUpload/index.vue
总之,什么地方用到文件上传把地址换成现在的地址,之前是拼的本地路径。
遇到的问题
-
前端图片不显示问题
先看返回值的路径格式对不对,要是对的话,把路径复制出来在一个新网页上查看,看是否是查看状态,而不是直接下载。如果出现下载状态,是因为在上传的时候没有定义类型,就是响应头不符合,上面可以看到我在上传的时候(uploadMinino方法的try块)判断了文件是什么类型,然后去调用不同的方法。如果是图片是直接查看,文件的话是下载。
-
文件访问问题
原本框架用的static修饰来做到直接打点去调,而本人习惯把对象交给Spring IOC容器管理,所以在调用的时候可能会报错,这个时候查看这个方法是静态的还是在IOC容器中即可。 -
Minio删除文件无效问题
minio中的DML操作没有返回值,所以删没删掉我们需要去服务器查看,具体原因请见minio删除无效解决办法