文件服务器 file service 增加:ftp、ceph、aliyun oss 初版

pull/84/head
duandazhi 4 years ago
parent 7e72849d05
commit 046c27d71c

@ -66,6 +66,59 @@
<artifactId>ruoyi-common-swagger</artifactId>
</dependency>
<!-- apache commons net ftp工具类; hutool 里面 scope 是 compile -->
<!--用来ftp上传-->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.3</version>
</dependency>
<!--阿里云OSS库-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.5.0</version>
<exclusions>
<!--排除 sdk-core 存在的包-->
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--ceph upload start -->
<!--文件上传 ceph 形式,一般项目都用不上,注释掉; 不要删-->
<!-- ceph实现s3 文件上传-->
<dependency>
<groupId>com.ceph</groupId>
<artifactId>libcephfs</artifactId>
<version>0.80.5</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.415</version>
</dependency>
<!--ceph upload end -->
</dependencies>
<build>

@ -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;
}
}

@ -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 ftpfilezilla使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;
/**
* ftp21
*/
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;
}
}

@ -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;

@ -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;

@ -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
* <p>
*
* https://help.aliyun.com/document_detail/47505.html
* https://help.aliyun.com/document_detail/48884.html
* </p>
* Security Token ServiceSTS 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
* <p>
* 访
*/
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
* <p>
* 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
* <p>
* 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<String> 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<String> 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;
}
/**
* oss1
*
* @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;
}
// ================================= 一下全是大文件上传的代码 ===============================================
/**
* oss2
*
* @param picturePath 访access url, oss
* @param file
* @return picturePath
* demo https://github.com/aliyun/aliyun-oss-java-sdk/blob/master/src/samples/MultipartUploadSample.java
* <p>
* PUT ObjectOSSOSSMultipart 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<PartETag> partETags = Collections.synchronizedList(new ArrayList<>());
/**
* 线
* Executors.newFixedThreadPool(10);
* 线10 线
* <p>
* 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<PartETag> partETags;
private PartUploader(OSS client, String requestKey, List<PartETag> 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<PartETag> 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<OSSObjectSummary> 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 urlurl
*/
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();
}
}

@ -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
* cephs3
*
* https://blog.csdn.net/qq_32524177/article/details/76226257
* cephS3 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/
* dockercephdocker 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
* <p>
* 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;
}
}

@ -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;
}
}

@ -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, iislinux ftpvsftpdFileZilla 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 + "";
}
}

@ -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 iislinux ftpvsftpdFileZilla Server
* 3: FastDfs dfs, (FastDFS)
* 4: minio ossCOS;
* 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: 100MB2G
*/
String listObject();
/**
* & & &
*
* @param file
* @param modules 使;eg: bannervideomusictxtproductdefault
* @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);
}
}

@ -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;
}
}

@ -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;
}
}

Loading…
Cancel
Save