From 046c27d71c067683f6f74078b7818af4a8a25585 Mon Sep 17 00:00:00 2001 From: duandazhi Date: Fri, 16 Jul 2021 12:16:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=20file=20service=20=E5=A2=9E=E5=8A=A0=EF=BC=9Aftp=E3=80=81ceph?= =?UTF-8?q?=E3=80=81aliyun=20oss=20=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-modules/ruoyi-file/pom.xml | 55 +- .../ruoyi/file/config/AliyunOssConfig.java | 60 ++ .../java/com/ruoyi/file/config/FtpConfig.java | 85 +++ .../com/ruoyi/file/config/MinioConfig.java | 6 + .../ruoyi/file/config/ResourcesConfig.java | 4 + .../service/AliyunOssFileServiceImpl.java | 564 ++++++++++++++++++ .../file/service/CephSysFileServiceImpl.java | 167 ++++++ .../service/FastDfsSysFileServiceImpl.java | 38 +- .../file/service/FtpFileServiceImpl.java | 79 +++ .../ruoyi/file/service/ISysFileService.java | 81 ++- .../file/service/LocalSysFileServiceImpl.java | 21 +- .../file/service/MinioSysFileServiceImpl.java | 47 +- 12 files changed, 1201 insertions(+), 6 deletions(-) create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/AliyunOssConfig.java create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/FtpConfig.java create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/AliyunOssFileServiceImpl.java create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/CephSysFileServiceImpl.java create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FtpFileServiceImpl.java diff --git a/ruoyi-modules/ruoyi-file/pom.xml b/ruoyi-modules/ruoyi-file/pom.xml index 0c8015ef..364fb611 100644 --- a/ruoyi-modules/ruoyi-file/pom.xml +++ b/ruoyi-modules/ruoyi-file/pom.xml @@ -65,7 +65,60 @@ com.ruoyi ruoyi-common-swagger - + + + + + commons-net + commons-net + 3.6 + + + cn.hutool + hutool-all + 5.7.3 + + + + + com.aliyun.oss + aliyun-sdk-oss + 3.5.0 + + + + com.google.code.gson + gson + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + + + commons-logging + commons-logging + + + + + + + + + com.ceph + libcephfs + 0.80.5 + + + com.amazonaws + aws-java-sdk-s3 + 1.11.415 + + diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/AliyunOssConfig.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/AliyunOssConfig.java new file mode 100644 index 00000000..7e825a89 --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/AliyunOssConfig.java @@ -0,0 +1,60 @@ +package com.ruoyi.file.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * aliyun oss https://help.aliyun.com/learn/learningpath/oss.html ,需要购买 + * + * @author dazer + */ +@RefreshScope +@Configuration +@ConfigurationProperties(prefix = "aliyun.oss") +public class AliyunOssConfig { + /** + * aliyun oss相关配置 + * ACCESS_KEY_SECRET + * AccessKeyId eg:LTAI4GFov2QymkmPf9cXdH5z + * AccessKeySecret eg:ap8nmIvD1TctcCLsADS4JbkOoXOluW + * BucketName eg:yuebaoxiao + * Endpoint eg:oss-cn-shenzhen.aliyuncs.com + */ + private String accessKeyId; + private String accessKeySecret; + private String ossBucketName; + private String ossEndpoint; + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessKeySecret() { + return accessKeySecret; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + public String getOssBucketName() { + return ossBucketName; + } + + public void setOssBucketName(String ossBucketName) { + this.ossBucketName = ossBucketName; + } + + public String getOssEndpoint() { + return ossEndpoint; + } + + public void setOssEndpoint(String ossEndpoint) { + this.ossEndpoint = ossEndpoint; + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/FtpConfig.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/FtpConfig.java new file mode 100644 index 00000000..0414ea1a --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/FtpConfig.java @@ -0,0 +1,85 @@ +package com.ruoyi.file.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * ftp config + * vsftpd 服务器搭建、ftp客户端filezilla使、ceph分布式文件系统 + * https://blog.csdn.net/ab601026460/article/details/105928311 + * @author dazer + */ +@RefreshScope +@Configuration +@ConfigurationProperties( + prefix = "ftp" +) +public class FtpConfig { + /** + * ftp访问地址 + * eg1: www.ourslook.com + * eg2: 192.168.0.1 + */ + private String hostName; + /** + * ftp端口,默认21 + */ + private Integer port = 21; + /** + * ftp访问密码 + * 有可能是匿名,就不需要账号和密码 + * eg: ftpuser + */ + private String userName; + /** + * ftp账号密码 + * eg: ftpx123.pwd..1 + */ + private String password; + /** + * filezila server: 就是空 + * vsftpd:使用的 系统用户的目录,这里往往都是不是根目录,如:/home/ftpuser/ + */ + private String rootFtpPath = ""; + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRootFtpPath() { + return rootFtpPath; + } + + public void setRootFtpPath(String rootFtpPath) { + this.rootFtpPath = rootFtpPath; + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/MinioConfig.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/MinioConfig.java index ee3e14c2..6121aed7 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/MinioConfig.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/MinioConfig.java @@ -1,6 +1,7 @@ package com.ruoyi.file.config; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.minio.MinioClient; @@ -10,27 +11,32 @@ import io.minio.MinioClient; * * @author ruoyi */ +@RefreshScope @Configuration @ConfigurationProperties(prefix = "minio") public class MinioConfig { /** * 服务地址 + * eg: http://192.168.254.100:9900 */ private String url; /** * 用户名 + * eg: D998GE6ZTQXSATTJWX35 */ private String accessKey; /** * 密码 + * eg: QZVQGnhIQQE734UYSUFlGOZViE6+ZlDEfUG3NjXJ */ private String secretKey; /** * 存储桶名称 + * eg: mall */ private String bucketName; diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java index 7e443309..da97520c 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java @@ -2,6 +2,7 @@ package com.ruoyi.file.config; import java.io.File; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -11,17 +12,20 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * * @author ruoyi */ +@RefreshScope @Configuration public class ResourcesConfig implements WebMvcConfigurer { /** * 上传文件存储在本地的根路径 + * eg: D:/ruoyi/uploadPath */ @Value("${file.path}") private String localFilePath; /** * 资源映射路径 前缀 + * eg: /statics */ @Value("${file.prefix}") public String localFilePrefix; diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/AliyunOssFileServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/AliyunOssFileServiceImpl.java new file mode 100644 index 00000000..7d935b96 --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/AliyunOssFileServiceImpl.java @@ -0,0 +1,564 @@ +package com.ruoyi.file.service; + +import cn.hutool.extra.spring.SpringUtil; +import com.aliyun.oss.*; +import com.aliyun.oss.model.*; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.ruoyi.file.config.AliyunOssConfig; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * aliyun oss 文件存储 实现类 + * https://help.aliyun.com/learn/learningpath/oss.html + * 代码核心实现,来自aliyun oss 官方demo + * https://github.com/aliyun/aliyun-oss-java-sdk + *

+ * 对象存储可以对图片进行处理 + * 图片处理 https://help.aliyun.com/document_detail/47505.html + * 图片样式 https://help.aliyun.com/document_detail/48884.html + *

+ * 对象存储鉴权:阿里云临时安全令牌(Security Token Service,STS) https://help.aliyun.com/document_detail/28756.htm?spm=a2c4g.11186623.2.4.3fba6d13JdXEzJ#reference-ong-5nv-xdb + * 对象存储oss docs 授权访问 使用STS进行临时授权 使用签名URL进行临时授权 https://help.aliyun.com/document_detail/32016.html?spm=a2c4g.11186623.6.992.7a943b4aPjkyTA#title-pu8-5o8-x7j + * 搜索 【sts】 + * + * @author yabo dazer + * @date 2019/8/6 19:02 + * //@see AliyunMsgUtil + */ +@Service +public class AliyunOssFileServiceImpl implements ISysFileService { + private static final Logger log = LoggerFactory.getLogger(AliyunOssFileServiceImpl.class); + @Autowired + private AliyunOssConfig aliyunOssConfig; + /** + * 这些阿里云访问id + */ + private String ACCESS_KEY_ID; + private String ACCESS_KEY_SECRET; + private String BUCKET_NAME; + /** + * https://oss.console.aliyun.com/ + * 这些阿里云 oss 参数都需要替换 + *

+ * 如果是内网的话,访问速度肯定更快,内网不限制速度。 + */ + private String ENDPOINT = "oss-cn-shenzhen.aliyuncs.com"; + private String ENDPOINT_INTERNAL = ENDPOINT.replace(".aliyuncs.com", "-internal.aliyuncs.com"); + + /** + * 域名绑定 + * USER_DOMAIN_NAME: 域名名称, oss 访问路径绑定的用户自定义域名; 如果没有,就设置为null + * hostHttps: 是否开启了https, 需要在控制台配置 + * https://oss.console.aliyun.com/bucket/oss-cn-shanghai/hiber2019/domain + *

+ * private static final String USER_DOMAIN_NAME = "image.jl-media.cn"; + */ + private static final String USER_DOMAIN_NAME = null; + private static final boolean HOST_HTTPS = true; + + @PostConstruct + public void init() { + ACCESS_KEY_ID = aliyunOssConfig.getAccessKeyId(); + ACCESS_KEY_SECRET = aliyunOssConfig.getAccessKeySecret(); + BUCKET_NAME = aliyunOssConfig.getOssBucketName(); + ENDPOINT = aliyunOssConfig.getOssEndpoint(); + ENDPOINT_INTERNAL = ENDPOINT.replace(".aliyuncs.com", "-internal.aliyuncs.com"); + } + + /** + * demo 地址 https://help.aliyun.com/learn/learningpath/oss.html + *

+ * 简单demo https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/samples/UploadSample.java + * 分片上传demo(大文件) https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/samples/MultipartUploadSample.java 分片上传,在oss上面能看到碎片记录 + * + * @return eg: https://hiber2019.oss-cn-shanghai.aliyuncs.com/upload/default/20190806202208849_jvs5g.png + */ + @Override + public String uploadFile(MultipartFile file) throws Exception { + return this.uploadFile(file, null); + } + + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + //key: 这里不能以/开头 + String newName = validateModule(file, null); + //key: 这里不能以/开头 + String requestKey = "upload/" + "/" + newName; + //这里增加一个前缀区分一下是测试环境还是正式环境 + boolean isProd = "prod".equalsIgnoreCase(SpringUtil.getActiveProfile()); + if (!isProd) { + requestKey = SpringUtil.getActiveProfile() + "/" + requestKey; + } + + long mb5 = 5 * 1024 * 1024L; + if (file.getSize() > mb5) { + //大于5mb,我们就分片上传 + this.ossUploadFileBigMultiable(isProd ? ENDPOINT_INTERNAL : ENDPOINT, requestKey, file); + } else { + //否则,我们常规上传 + this.ossUploadFileSmall(isProd ? ENDPOINT_INTERNAL : ENDPOINT, requestKey, file); + } + + // 解析结果 + // 注意,这里可能 需要 replace + String accessPath; + if (StringUtils.isNotBlank(USER_DOMAIN_NAME)) { + if (HOST_HTTPS) { + accessPath = "https://" + USER_DOMAIN_NAME + "/" + requestKey; + } else { + accessPath = "http://" + USER_DOMAIN_NAME + "/" + requestKey; + } + } else { + accessPath = "https://" + BUCKET_NAME + "." + ENDPOINT + "/" + requestKey; + } + return accessPath; + } + + /** + * demo 地址 https://help.aliyun.com/learn/learningpath/oss.html + * https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/samples/DeleteObjectsSample.java + */ + @Override + public boolean deleteFile(String fileUrl) { + /* + * Constructs a client instance with your account for accessing OSS + */ + OSS client = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET); + String storePath = getStorePath(fileUrl); + List keys = new ArrayList<>(); + keys.add(storePath); + + try { + /* + * Delete all objects uploaded recently under the bucket + */ + log.info("\nDeleting all objects:"); + DeleteObjectsResult deleteObjectsResult = client.deleteObjects( + new DeleteObjectsRequest(BUCKET_NAME).withKeys(keys)); + List deletedObjects = deleteObjectsResult.getDeletedObjects(); + for (String object : deletedObjects) { + log.info("\t" + object); + } + return true; + } catch (OSSException oe) { + log.error("Caught an OSSException, which means your request made it to OSS, " + + "but was rejected with an error response for some reason."); + log.error("Error Message: " + oe.getErrorCode()); + log.error("Error Code: " + oe.getErrorCode()); + log.error("Request ID: " + oe.getRequestId()); + log.error("Host ID: " + oe.getHostId()); + } catch (ClientException ce) { + log.error("Caught an ClientException, which means the client encountered " + + "a serious internal problem while trying to communicate with OSS, " + + "such as not being able to access the network."); + log.error("Error Message: " + ce.getMessage()); + } finally { + /* + * Do not forget to shut down the client finally to release all allocated resources. + */ + client.shutdown(); + } + return false; + } + + /** + * 转换url + * + * @param filePath https://hiber2019.oss-cn-shanghai.aliyuncs.com/upload/default/20190806202208849_jvs5g.png + * @return upload/default/20190806202208849_jvs5g.png + */ + private String getStorePath(String filePath) { + String publicPath1 = "https://" + BUCKET_NAME + "." + ENDPOINT + "/"; + String publicPath2 = "http://" + BUCKET_NAME + "." + ENDPOINT + "/"; + String publicPath3 = "https://" + USER_DOMAIN_NAME + "/"; + String publicPath4 = "http://" + USER_DOMAIN_NAME + "/"; + //String publicPath5 = ServletCacheUtils.getInstance().getHttpRootPath(); + + + filePath = filePath.replace(publicPath1, ""); + filePath = filePath.replace(publicPath2, ""); + filePath = filePath.replace(publicPath3, ""); + filePath = filePath.replace(publicPath4, ""); + //filePath = filePath.replace(publicPath5, ""); + return filePath; + } + + /** + * oss上传方式1:小文件 + * + * @param picturePath 文件的访问路径,access url + * @param file 等待上传的文件 + * @return picturePath 上传文件的相对路径 + * demo https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/samples/UploadSample.java + */ + private String ossUploadFileSmall(String endpoint, String picturePath, MultipartFile file) throws IOException { + OSS ossClient = new OSSClientBuilder().build(endpoint, ACCESS_KEY_ID, ACCESS_KEY_SECRET); + + try { + // 上传文件 (上传文件流的形式) + PutObjectResult putResult = ossClient.putObject(BUCKET_NAME, picturePath, file.getInputStream()); + } catch (OSSException oe) { + log.error("Caught an OSSException, which means your request made it to OSS, " + + "but was rejected with an error response for some reason."); + log.error("Error Message: {}", oe.getErrorMessage()); + log.error("Error Code {}: ", oe.getErrorCode()); + log.error("Request ID {}: ", oe.getRequestId()); + log.error("Host ID {}: ", oe.getHostId()); + log.error(""); + + log.error("图片上传失败(OSS)", oe); + oe.printStackTrace(); + throw new IOException("图片上传失败(OSS)"); + } catch (ClientException ce) { + log.error("Caught an ClientException, which means the client encountered " + + "a serious internal problem while trying to communicate with OSS, " + + "such as not being able to access the network."); + log.error("Error Message: " + ce.getMessage()); + log.error(""); + + log.error("图片上传失败(Client)", ce); + ce.printStackTrace(); + throw new IOException("图片上传失败(Client)"); + } catch (Throwable e) { + log.error("图片上传失败(Throwable)", e); + e.printStackTrace(); + + /* try { + //保存错误日志 + SysLogService sysLogService = SpringContextUtils.getBean(SysLogService.class); + sysLogService.saveExceptionLog(e, "图片上传失败", SysLogEntity.OPERATION_WARN_LEVEL_1); + } catch (Exception ex) { + ex.printStackTrace(); + }*/ + + throw new IOException("图片上传失败(Throwable)"); + } finally { + ossClient.shutdown(); + } + return picturePath; + } + + // ================================= 一下全是大文件上传的代码 =============================================== + + /** + * oss上传方式2:大文件(分片上传) + * + * @param picturePath 文件的访问路径,access url, 也是 oss中的唯一存放地址 + * @param file 等待上传的文件 + * @return picturePath 上传文件的相对路径 + * demo https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/samples/MultipartUploadSample.java + *

+ * 除了通过PUT Object接口上传文件到OSS以外,OSS还提供了另外一种上传模式——Multipart Upload。 https://help.aliyun.com/document_detail/31991.html + */ + private String ossUploadFileBigMultiable(String endpoint, String picturePath, MultipartFile file) throws IOException { + String requestKey = String.valueOf(picturePath); + List partETags = Collections.synchronizedList(new ArrayList<>()); + /** + * 线程池 + * Executors.newFixedThreadPool(10); + * 这里不限制大小,线程会按照最大能力,开启,限制了大小,比如:10, 就一共只开启这么多线程 + *

+ * private static ExecutorService executorService = Executors.newCachedThreadPool(); + */ + ExecutorService executorService = new ThreadPoolExecutor(50, 1000, 60L, TimeUnit.SECONDS, + new SynchronousQueue<>(), new ThreadFactoryBuilder().setNameFormat("aliyun-oss-upload(" + requestKey + ")-thread-pool-%d").build()); + + /* + * Constructs a client instance with your account for accessing OSS + */ + ClientBuilderConfiguration conf = new ClientBuilderConfiguration(); + conf.setIdleConnectionTime(1000); + OSS client = new OSSClientBuilder().build(endpoint, ACCESS_KEY_ID, ACCESS_KEY_SECRET, conf); + + try { + /* + * Claim a upload id firstly + */ + InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(BUCKET_NAME, requestKey); + InitiateMultipartUploadResult result = client.initiateMultipartUpload(request); + String uploadId = result.getUploadId(); + + /* + * Calculate how many parts to be divided + */ + final long partSize = 5 * 1024 * 1024L; // 5MB + long fileLength = file.getSize(); + int partCount = (int) (fileLength / partSize); + if (fileLength % partSize != 0) { + partCount++; + } + if (partCount > 10000) { + throw new RuntimeException("Total parts count should not exceed 10000"); + } + /* + * Upload multiparts to your bucket + */ + if (log.isInfoEnabled()) { + log.info("Begin to upload multiparts to OSS from a file\n"); + } + for (int i = 0; i < partCount; i++) { + long startPos = i * partSize; + long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize; + executorService.execute(new PartUploader(client, requestKey, partETags, file, startPos, curPartSize, i + 1, uploadId)); + } + + /* + * Waiting for all parts finished + */ + executorService.shutdown(); + while (!executorService.isTerminated()) { + try { + executorService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + /* try { + //等待所有任务执行完毕, 这里 不限定20分钟必须执行完毕 + countDownLatch.await(60 * 20, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + log.error("所有线程都已经执行完毕...., 一共开启线程数量: {}", partCount); + }*/ + + /* + * Verify whether all parts are finished + */ + if (partETags.size() != partCount) { + throw new RuntimeException("Upload multiparts fail due to some parts are not finished yet"); + } else { + if (log.isInfoEnabled()) { + log.info("Succeed to complete multiparts into an object named {} \n", requestKey); + } + } + + /* + * View all parts uploaded recently + */ + listAllParts(client, requestKey, uploadId); + + /* + * Complete to upload multiparts + */ + completeMultipartUpload(client, partETags, requestKey, uploadId); + + /* + * Fetch the object that newly created at the step below. + */ + if (log.isInfoEnabled()) { + log.info("Fetching an object"); + } + OSSObject ossObject = client.getObject(new GetObjectRequest(BUCKET_NAME, requestKey)); + if (log.isInfoEnabled()) { + log.info(ossObject.getKey()); + } + } catch (OSSException oe) { + log.error("Caught an OSSException, which means your request made it to OSS, " + + "but was rejected with an error response for some reason."); + log.error("Error Message: {}", oe.getErrorMessage()); + log.error("Error Code {}: ", oe.getErrorCode()); + log.error("Request ID {}: ", oe.getRequestId()); + log.error("Host ID {}: ", oe.getHostId()); + log.error(""); + + log.error("图片上传失败(OSS)", oe); + oe.printStackTrace(); + throw new IOException("图片上传失败(OSS Multipart)"); + } catch (ClientException ce) { + log.error("Caught an ClientException, which means the client encountered " + + "a serious internal problem while trying to communicate with OSS, " + + "such as not being able to access the network."); + log.error("Error Message: " + ce.getMessage()); + log.error(""); + + log.error("图片上传失败(Client)", ce); + ce.printStackTrace(); + throw new IOException("图片上传失败(Client Multipart)"); + } finally { + /* + * Do not forget to shut down the client finally to release all allocated resources. + */ + if (client != null) { + client.shutdown(); + } + } + + return picturePath; + } + + private class PartUploader implements Runnable { + private MultipartFile file; + private long startPos; + + private long partSize; + private int partNumber; + private String uploadId; + + private OSS client; + private String requestKey; + private final List partETags; + + private PartUploader(OSS client, String requestKey, List partETags, MultipartFile file, long startPos, long partSize, int partNumber, String uploadId) { + this.client = client; + this.requestKey = requestKey; + this.partETags = partETags; + + this.file = file; + this.startPos = startPos; + this.partSize = partSize; + this.partNumber = partNumber; + this.uploadId = uploadId; + } + + @Override + public void run() { + InputStream instream = null; + try { + instream = file.getInputStream(); + instream.skip(this.startPos); + + UploadPartRequest uploadPartRequest = new UploadPartRequest(); + uploadPartRequest.setBucketName(BUCKET_NAME); + uploadPartRequest.setKey(this.requestKey); + uploadPartRequest.setUploadId(this.uploadId); + uploadPartRequest.setInputStream(instream); + uploadPartRequest.setPartSize(this.partSize); + uploadPartRequest.setPartNumber(this.partNumber); + + UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest); + if (log.isInfoEnabled()) { + log.info("Part {} done\n", this.partNumber); + } + + synchronized (partETags) { + partETags.add(uploadPartResult.getPartETag()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (instream != null) { + try { + instream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + + private void completeMultipartUpload(OSS client, List partETags, String requestKey, String uploadId) { + // Make part numbers in ascending order + partETags.sort(Comparator.comparingInt(PartETag::getPartNumber)); + + if (log.isInfoEnabled()) { + log.info("Completing to upload multiparts\n"); + } + + CompleteMultipartUploadRequest completeMultipartUploadRequest = + new CompleteMultipartUploadRequest(BUCKET_NAME, requestKey, uploadId, partETags); + client.completeMultipartUpload(completeMultipartUploadRequest); + } + + private void listAllParts(OSS client, String requestKey, String uploadId) { + if (log.isInfoEnabled()) { + log.info("Listing all parts......"); + } + ListPartsRequest listPartsRequest = new ListPartsRequest(BUCKET_NAME, requestKey, uploadId); + PartListing partListing = client.listParts(listPartsRequest); + + int partCount = partListing.getParts().size(); + for (int i = 0; i < partCount; i++) { + PartSummary partSummary = partListing.getParts().get(i); + if (log.isInfoEnabled()) { + log.info("\tPart# {} , ETag={}", partSummary.getPartNumber(), partSummary.getETag()); + } + } + if (log.isInfoEnabled()) { + log.info("\n"); + } + } + + @Override + public String listObject() { + OSS client = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET); + final int maxKeys = 200; + String nextMarker = null; + ObjectListing objectListing; + + long size = 0L; + String result = ""; + + do + { + objectListing = client.listObjects(new ListObjectsRequest(BUCKET_NAME).withMarker(nextMarker).withMaxKeys(maxKeys)); + + List sums = objectListing.getObjectSummaries(); + for (OSSObjectSummary s : sums) { + size += s.getSize() / 1024; + } + nextMarker = objectListing.getNextMarker(); + + } while (objectListing.isTruncated()); + client.shutdown(); + + if (size > (1024 * 1024)) { + result = (new BigDecimal((double) size / 1024 / 1024)).setScale(2, BigDecimal.ROUND_HALF_UP) + "GB"; + } else if (size > 1024) { + result = (new BigDecimal((double) size / 1024).setScale(2, BigDecimal.ROUND_HALF_UP)) + "MB"; + } else { + result = size + "KB"; + } + return result; + } + + /** + * 阿里云 对象存储 oss 使用签名URL进行临时授权 + * https://help.aliyun.com/document_detail/32016.html?spm=a2c4g.11186623.6.992.7a943b4aPjkyTA#title-pu8-5o8-x7j + * + * @param objectName 完成的url, filePath + * @return 返回url签名之后的url + */ + public String getStsURL(String objectName) { + if (StringUtils.isBlank(objectName)) { + return objectName; + } + try { + objectName = new URL(objectName).getPath(); + if (StringUtils.isBlank(objectName)) { + return objectName; + } + if (objectName.startsWith("/")) { + objectName = objectName.replaceFirst("/", ""); // 不能以/ 开头。例如 /dev/upload/123.jpg,需要转为 dev/upload/123.jpg + } + } catch (MalformedURLException e) { + // 忽略 + } + OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET); + // 设置URL过期时间为12小时,最大值就是43200 + Date expiration = new Date(System.currentTimeMillis() + (43200 * 1000)); + // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。 + URL url = ossClient.generatePresignedUrl(BUCKET_NAME, objectName, expiration); + // 关闭OSSClient。 + ossClient.shutdown(); + return url.toString(); + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/CephSysFileServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/CephSysFileServiceImpl.java new file mode 100644 index 00000000..f83d4bcf --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/CephSysFileServiceImpl.java @@ -0,0 +1,167 @@ +package com.ruoyi.file.service; + +import cn.hutool.extra.spring.SpringUtil; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.*; +import com.ruoyi.common.core.utils.SpringUtils; +import com.ruoyi.file.utils.FileUploadUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * @author dazer + * @date 2020-4-4 + * ceph实现s3文件上传 + * 上传文件相关 + * 参考网址:https://blog.csdn.net/qq_32524177/article/details/76226257 + * ceph储存的S3接口实现 或者支持文件挂载的方式(NFS NAS挂载) + * https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/RetrievingObjectUsingJava.html + * 参考:Amazonaws S3 java SDK连接初探 https://www.cnblogs.com/zmdd/p/9342510.html + * ========================================================================================================================= + * 部署: + * 1:https://ceph.com/planet/%E5%9F%BA%E4%BA%8Edocker%E9%83%A8%E7%BD%B2ceph%E4%BB%A5%E5%8F%8A%E4%BF%AE%E6%94%B9docker-image/ + * 基于docker部署ceph以及修改docker image + * 2:ceph存储,使用docker部署 https://www.cnblogs.com/bladeyul/p/10649049.html + * 3:使用docker 搭建 ceph 开发环境,使用aws sdk 存储数据 https://blog.csdn.net/freewebsys/article/details/79553386 + */ +public class CephSysFileServiceImpl implements ISysFileService { + private static final Logger log = LoggerFactory.getLogger(AliyunOssFileServiceImpl.class); + + protected static AmazonS3 amazonS3 = null; + /** + * s3 提供的 accessKey secretKey + * BUCKET_NAME: 概念和阿里云 oss 一模一样 + */ + private static String ACCESS_KEY = "XPVF8TESA1X4SFU*****"; + private static String SECRET_KEY = "hBBEFpV3qsyI7HAdCBzA2ZdAhuANJFRIUz****"; + private static String HOST = "127.0.0.1"; + private static String BUCKET_NAME = "dfwwbook"; + /** + * 域名绑定 + * USER_DOMAIN_NAME: 域名名称, oss 访问路径绑定的用户自定义域名; 如果没有,就设置为null + * hostHttps: 是否开启了https, 需要在控制台配置 + * https://oss.console.aliyun.com/bucket/oss-cn-shanghai/hiber2019/domain + *

+ * private static final String USER_DOMAIN_NAME = "image.jl-media.cn"; + */ + private static final String USER_DOMAIN_NAME = null; + private static final boolean HOST_HTTPS = true; + + /** + * ceph配置初始化 + */ + static { + log.info("开始初始化ceph配置"); + AWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY); + ClientConfiguration clientConfiguration = new ClientConfiguration(); + clientConfiguration.setProtocol(Protocol.HTTP); + amazonS3 = new AmazonS3Client(credentials, clientConfiguration); + amazonS3.setEndpoint(HOST); + log.info("ceph配置初始化成功"); + } + + /** + * 使用s3 api 创建 ceph bucket + * + * @param bucketname + * @return + */ + public Bucket createBucket(String bucketname) { + Bucket bucket = amazonS3.createBucket(bucketname); + log.info("bucket name is {}", bucket); + return bucket; + } + + /** + * 根据 bucketName 和 filename 获取指定文件返回输入流 + */ + public S3ObjectInputStream getObject(String bucketName, String filename) { + S3Object s3Object = amazonS3.getObject(new GetObjectRequest(bucketName, filename)); + S3ObjectInputStream s3ObjectInputStream = s3Object.getObjectContent(); + return s3ObjectInputStream; + } + + @Override + public String uploadFile(MultipartFile file) throws Exception { + return this.uploadFile(file, null); + } + + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + //key: 这里不能以/开头 + String newName = validateModule(file, null); + //key: 这里不能以/开头 + String requestKey = "upload/" + newName; + //这里增加一个前缀区分一下是测试环境还是正式环境 + boolean isProd = "prod".equalsIgnoreCase(SpringUtil.getActiveProfile()); + if (!isProd) { + requestKey = SpringUtil.getActiveProfile() + "/" + requestKey; + } + + // long mb5 = 5 * 1024 * 1024L; + //大于5mb,我们就分片上传 + PutObjectResult result = amazonS3.putObject(BUCKET_NAME, requestKey, file.getInputStream(), new ObjectMetadata()); + // 上传成功 + if (result.isRequesterCharged()) { + // 解析结果 + // 注意,这里可能 需要 replace + String accessPath; + if (StringUtils.isNotBlank(USER_DOMAIN_NAME)) { + if (HOST_HTTPS) { + accessPath = "https://" + USER_DOMAIN_NAME + "/" + requestKey; + } else { + accessPath = "http://" + USER_DOMAIN_NAME + "/" + requestKey; + } + } else { + accessPath = "https://" + BUCKET_NAME + "/" + requestKey; + } + return accessPath; + } + return null; + } + + @Override + public boolean deleteFile(String fileUrl) { + if (StringUtils.isEmpty(fileUrl)) { + return false; + } + String storePath = getStorePath(fileUrl); + amazonS3.deleteObject(BUCKET_NAME, storePath); + return true; + } + + @Override + public String listObject() { + return null; + } + + /** + * 转换url + * + * @param filePath https://hiber2019.oss-cn-shanghai.aliyuncs.com/upload/default/20190806202208849_jvs5g.png + * @return upload/default/20190806202208849_jvs5g.png + */ + private String getStorePath(String filePath) { + String publicPath1 = "https://" + BUCKET_NAME + "/"; + String publicPath2 = "http://" + BUCKET_NAME + "/"; + String publicPath3 = "https://" + USER_DOMAIN_NAME + "/"; + String publicPath4 = "http://" + USER_DOMAIN_NAME + "/"; + //String publicPath5 = ServletCacheUtils.getInstance().getHttpRootPath(); + + filePath = filePath.replace(publicPath1, ""); + filePath = filePath.replace(publicPath2, ""); + filePath = filePath.replace(publicPath3, ""); + filePath = filePath.replace(publicPath4, ""); + //filePath = filePath.replace(publicPath5, ""); + return filePath; + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsSysFileServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsSysFileServiceImpl.java index deee22ee..76dbbe86 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsSysFileServiceImpl.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsSysFileServiceImpl.java @@ -1,6 +1,11 @@ package com.ruoyi.file.service; +import com.github.tobato.fastdfs.domain.conn.PooledConnectionFactory; +import com.github.tobato.fastdfs.exception.FdfsUnsupportStorePathException; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -10,14 +15,15 @@ import com.github.tobato.fastdfs.service.FastFileStorageClient; /** * FastDFS 文件存储 - * * @author ruoyi */ @Service public class FastDfsSysFileServiceImpl implements ISysFileService { + private final Logger logger = LoggerFactory.getLogger(FastDfsSysFileServiceImpl.class); /** * 域名或本机访问地址 + * FastDFS配置 其他参数见:{@link PooledConnectionFactory} */ @Value("${fdfs.domain}") public String domain; @@ -35,8 +41,38 @@ public class FastDfsSysFileServiceImpl implements ISysFileService @Override public String uploadFile(MultipartFile file) throws Exception { + return this.uploadFile(file); + } + + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + // fastdsf 这里的 modules 没用 + validateModule(file, modules); + StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null); + + /// fileUrl = "http://127.0.0.1:22122/" + storePath.getFullPath(); return domain + "/" + storePath.getFullPath(); } + + @Override + public boolean deleteFile(String fileUrl) { + if (StringUtils.isEmpty(fileUrl)) { + return false; + } + try { + StorePath storePath = StorePath.parseFromUrl(fileUrl); + storageClient.deleteFile(storePath.getGroup(), storePath.getPath()); + return true; + } catch (FdfsUnsupportStorePathException e) { + logger.warn(e.getMessage()); + } + return false; + } + + @Override + public String listObject() { + return null; + } } diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FtpFileServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FtpFileServiceImpl.java new file mode 100644 index 00000000..67123a69 --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FtpFileServiceImpl.java @@ -0,0 +1,79 @@ +package com.ruoyi.file.service; + +import cn.hutool.extra.ftp.Ftp; +import cn.hutool.extra.ftp.FtpMode; +import com.ruoyi.file.config.FtpConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * ftp目录用来上传文件; + * ftp, 如:iis、linux ftp、vsftpd、FileZilla Server,需要自己搭建服务 + * @author dazer + */ +@Service +public class FtpFileServiceImpl implements ISysFileService { + @Autowired + private FtpConfig ftpConfig; + public static final String ACCESS_PREFIX = ""; + + @Override + public String uploadFile(MultipartFile file) throws Exception { + return this.uploadFile(file, null); + } + + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + String fileName = "upload/" + validateModule(file, modules); + + Ftp ftp = null; + try { + ftp = new Ftp(ftpConfig.getHostName(), ftpConfig.getPort(), ftpConfig.getUserName(), ftpConfig.getPassword()); + ftp.cd(""); + ftp.setMode(FtpMode.Passive); + ftp.upload("", fileName, file.getInputStream()); + } finally { + if (ftp != null) { + ftp.close(); + } + } + return ftpConfig.getHostName() + "/" + fileName; + } + + @Override + public boolean deleteFile(String fileUrl) { + Ftp ftp = null; + try { + ftp = new Ftp(ftpConfig.getHostName(), ftpConfig.getPort(), ftpConfig.getUserName(), ftpConfig.getPassword()); + String storePath = getStorePath(fileUrl); + return ftp.delFile(storePath); + } finally { + if (ftp != null) { + try { + ftp.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public String listObject() { + return null; + } + + private String getStorePath(String filePath) { + int groupStartPos = -1; + if ((groupStartPos = filePath.indexOf(ACCESS_PREFIX) + ACCESS_PREFIX.length()) + 1 == 0) { + groupStartPos = 0; + //throw new RrException("解析文件路径错误,被解析路径url没有" + Constant.SERVIER_NAME_SUFFIX + ",当前解析路径为".concat(filePath)); + } + // 获取group起始位置 + String groupAndPath = filePath.substring(groupStartPos); + return groupAndPath + ""; + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/ISysFileService.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/ISysFileService.java index 5a353489..26330a6e 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/ISysFileService.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/ISysFileService.java @@ -1,14 +1,44 @@ package com.ruoyi.file.service; +import com.ruoyi.common.core.exception.file.FileNameLengthLimitExceededException; +import com.ruoyi.common.core.exception.file.InvalidExtensionException; +import com.ruoyi.common.core.utils.file.MimeTypeUtils; +import com.ruoyi.file.utils.FileUploadUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.ruoyi.file.utils.FileUploadUtils.assertAllowed; +import static com.ruoyi.file.utils.FileUploadUtils.extractFilename; + /** * 文件上传接口 - * + * 1: default: 最原始的java文件上传 + * 2: ftp 使用ftp模拟文件服务器; 如:iis、linux ftp、vsftpd、FileZilla Server,需要自己搭建服务 + * 3: FastDfs 是淘宝开源的分布式文件系统; 淘宝 开发的分布式 dfs, 需要自己搭建服务 (FastDFS) + * 4: minio 轻量级分布式文件系统; 类似一个阿里云oss、腾讯COS的一个开源、轻量级别的对象存储付; + * 5: aliyun oss; aliyun oss https://help.aliyun.com/learn/learningpath/oss.html ,需要购买 + * 6: CEPH 分布式大数据文件存储系统 http://docs.ceph.org.cn/ * @author ruoyi */ public interface ISysFileService { + /** + * 允许上传文件存放的目录 + * 不同项目,这里可能做不同的修改;不过不想区分,就default; + * 项目稍微大一些,如果不区分目录,后期要做删除 or 迁移就很麻烦; + */ + String[] DEFAULT_MODULES_NAME = { + // 图片 + "default", "banner", "product", "images", "music", + // pdf + "pdf" }; + /** * 文件上传接口 * @@ -16,5 +46,52 @@ public interface ISysFileService * @return 访问地址 * @throws Exception */ - public String uploadFile(MultipartFile file) throws Exception; + String uploadFile(MultipartFile file) throws Exception; + + String uploadFile(MultipartFile file, String modules) throws Exception; + + /** + * 删除文件 + * + * @param fileUrl 文件访问地址,全路径或者不是全路径都可以 + * @return + */ + boolean deleteFile(String fileUrl); + + /** + * 获取文件占用空间 + * 别名:objectsCapacity + * @return 文件大小字符串,eg: 100MB、2G + */ + String listObject(); + + /** + * 校验文件名称长度 & 校验文件大小 & 校验上传的目录是否是项目中注册了的 & 返回新的文件名称 + * + * @param file 文件 + * @param modules 模块,这里作为上传的文件夹使用;eg: 项目中有banner、video、music、txt、product、default 多个模块,不同模块存放到不同文件夹中; + * @return 新的系统生成的文件名称 + * @throws InvalidExtensionException + */ + default String validateModule(MultipartFile file, String modules) throws InvalidExtensionException { + Objects.requireNonNull(file, "文件不能为空!"); + modules = StringUtils.defaultString(modules, "default"); + + //1、这里校验上传文件的模块,如果没有注册,直接报错 + if (!Arrays.stream(DEFAULT_MODULES_NAME).collect(Collectors.toList()).contains(modules)) { + throw new RuntimeException("上传模块" + modules + "不存在,请现在 'FolderPath.UploadModules'中注册. 枚举值,请参见Home接口:api/getFolderPath"); + } + + // 2、校验文件名称长度 + /// String ext = (String.valueOf(file.getOriginalFilename()).substring(String.valueOf(file.getOriginalFilename()).lastIndexOf("."))).toLowerCase(); + int fileNamelength = file.getOriginalFilename().length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + // 3、文件大小校验 + assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + return modules + "/" + extractFilename(file); + } } diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/LocalSysFileServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/LocalSysFileServiceImpl.java index c0e20681..4414b14a 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/LocalSysFileServiceImpl.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/LocalSysFileServiceImpl.java @@ -1,5 +1,6 @@ package com.ruoyi.file.service; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; @@ -17,18 +18,21 @@ public class LocalSysFileServiceImpl implements ISysFileService { /** * 资源映射路径 前缀 + * eg: eg: /statics */ @Value("${file.prefix}") public String localFilePrefix; /** * 域名或本机访问地址 + * eg: http://127.0.0.1:9300 */ @Value("${file.domain}") public String domain; /** * 上传文件存储在本地的根路径 + * eg: D:/ruoyi/uploadPath */ @Value("${file.path}") private String localFilePath; @@ -43,8 +47,23 @@ public class LocalSysFileServiceImpl implements ISysFileService @Override public String uploadFile(MultipartFile file) throws Exception { - String name = FileUploadUtils.upload(localFilePath, file); + return this.uploadFile(file, null); + } + + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + String name = FileUploadUtils.upload(localFilePath + "/" + StringUtils.defaultString(modules, ""), file); String url = domain + localFilePrefix + name; return url; } + + @Override + public boolean deleteFile(String fileUrl) { + return false; + } + + @Override + public String listObject() { + return null; + } } diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/MinioSysFileServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/MinioSysFileServiceImpl.java index 3dd2fc6f..e1bd8c62 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/MinioSysFileServiceImpl.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/MinioSysFileServiceImpl.java @@ -1,5 +1,8 @@ package com.ruoyi.file.service; +import cn.hutool.extra.spring.SpringUtil; +import io.minio.RemoveObjectArgs; +import io.minio.errors.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -8,6 +11,10 @@ import com.ruoyi.file.utils.FileUploadUtils; import io.minio.MinioClient; import io.minio.PutObjectArgs; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + /** * Minio 文件存储 * @@ -32,7 +39,17 @@ public class MinioSysFileServiceImpl implements ISysFileService @Override public String uploadFile(MultipartFile file) throws Exception { - String fileName = FileUploadUtils.extractFilename(file); + return this.uploadFile(file, null); + } + + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + String fileName = validateModule(file ,modules); + boolean isProd = "prod".equalsIgnoreCase(SpringUtil.getActiveProfile()); + if (!isProd) { + fileName = SpringUtil.getActiveProfile() + "/" + fileName; + } + PutObjectArgs args = PutObjectArgs.builder() .bucket(minioConfig.getBucketName()) .object(fileName) @@ -42,4 +59,32 @@ public class MinioSysFileServiceImpl implements ISysFileService client.putObject(args); return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName; } + + @Override + public boolean deleteFile(String fileUrl) { + RemoveObjectArgs args = RemoveObjectArgs.builder(). + bucket(minioConfig.getBucketName()). + object(fileUrl). + build(); + try { + client.removeObject(args); + return true; + } catch (ErrorResponseException | + InsufficientDataException | + InternalException | + InvalidKeyException | + InvalidResponseException | + IOException | + NoSuchAlgorithmException | + ServerException | + XmlParserException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public String listObject() { + return null; + } }