|
|
package com.ruoyi.file.impl;
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.img.ImgUtil;
|
|
|
import cn.hutool.core.io.IoUtil;
|
|
|
import cn.hutool.core.lang.UUID;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import cn.hutool.core.util.URLUtil;
|
|
|
import cn.hutool.core.util.ZipUtil;
|
|
|
import com.ruoyi.common.utils.HttpDownloadUtil;
|
|
|
import com.ruoyi.common.utils.file.FileUtils;
|
|
|
import com.ruoyi.common.utils.redis.RedisUtils;
|
|
|
import com.ruoyi.file.FileService;
|
|
|
import com.ruoyi.file.config.SpringFileStorageProperties;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.SneakyThrows;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.tika.Tika;
|
|
|
import org.dromara.x.file.storage.core.Downloader;
|
|
|
import org.dromara.x.file.storage.core.FileInfo;
|
|
|
import org.dromara.x.file.storage.core.FileStorageService;
|
|
|
import org.dromara.x.file.storage.core.constant.Constant;
|
|
|
import org.dromara.x.file.storage.core.platform.FileStorage;
|
|
|
import org.dromara.x.file.storage.core.platform.LocalPlusFileStorage;
|
|
|
|
|
|
import org.dromara.x.file.storage.core.platform.UpyunUssFileStorage;
|
|
|
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
|
|
|
import org.dromara.x.file.storage.core.tika.TikaFactory;
|
|
|
import org.dromara.x.file.storage.core.upload.FilePartInfo;
|
|
|
import org.dromara.x.file.storage.core.upload.MultipartUploadSupportInfo;
|
|
|
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
import java.awt.*;
|
|
|
import java.io.*;
|
|
|
import java.time.Duration;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@Service
|
|
|
@Slf4j
|
|
|
@RequiredArgsConstructor
|
|
|
public class FileServiceImpl implements FileService {
|
|
|
|
|
|
private final FileStorageService service;
|
|
|
private final SpringFileStorageProperties properties;
|
|
|
private final TikaFactory tikaFactory;
|
|
|
public static final String CACHE_KEY_PREFIX = "file:multipart:";
|
|
|
|
|
|
|
|
|
@Override
|
|
|
public String multipartUploadInit() {
|
|
|
try {
|
|
|
|
|
|
UploadPretreatment p = new UploadPretreatment();
|
|
|
Param param = paramThreadLocal.get();
|
|
|
initUploadPretreatment(p, param);
|
|
|
FileStorage fileStorage = service.getFileStorage(p.getPlatform());
|
|
|
MultipartUploadSupportInfo supportInfo = fileStorage.isSupportMultipartUpload();
|
|
|
if (!(supportInfo.getIsSupport() && supportInfo.getIsSupportAbort() && supportInfo.getIsSupportListParts())) {
|
|
|
throw new RuntimeException("不支持分片上传");
|
|
|
}
|
|
|
boolean isUpyunUss = fileStorage instanceof UpyunUssFileStorage;
|
|
|
FileInfo fileInfo = service.initiateMultipartUpload()
|
|
|
.setPlatform(p.getPlatform())
|
|
|
.setPath(p.getPath())
|
|
|
.setSaveFilename(p.getSaveFilename())
|
|
|
.putMetadata(isUpyunUss, "X-Upyun-Multi-Part-Size", String.valueOf(5 * 1024 * 1024))// 设置 Metadata,不需要可以不写
|
|
|
.init();
|
|
|
|
|
|
String uploadId = UUID.fastUUID().toString(true);
|
|
|
RedisUtils.setCacheObject(CACHE_KEY_PREFIX + uploadId, fileInfo, Duration.ofMinutes(60));
|
|
|
return uploadId;
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void multipartUpload(String uploadId, Integer partNumber, InputStream inputStream) {
|
|
|
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
|
|
|
if (fileInfo == null) {
|
|
|
throw new RuntimeException("未初始化分片上传");
|
|
|
}
|
|
|
service.uploadPart(fileInfo, partNumber, inputStream).upload();
|
|
|
}
|
|
|
|
|
|
|
|
|
@Override
|
|
|
public String multipartUploadComplete(String uploadId) {
|
|
|
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
|
|
|
if (fileInfo == null) {
|
|
|
throw new RuntimeException("未初始化分片上传");
|
|
|
}
|
|
|
service.completeMultipartUpload(fileInfo).complete();
|
|
|
RedisUtils.deleteObject(CACHE_KEY_PREFIX + uploadId);
|
|
|
return fileInfo.getUrl();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void multipartUploadAbort(String uploadId) {
|
|
|
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
|
|
|
|
|
|
if (fileInfo == null) {
|
|
|
throw new RuntimeException("未初始化分片上传");
|
|
|
}
|
|
|
service.abortMultipartUpload(fileInfo).abort();
|
|
|
RedisUtils.deleteObject(CACHE_KEY_PREFIX + uploadId);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public List<Integer> multipartUploadListParts(String uploadId) {
|
|
|
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
|
|
|
return service.listParts(fileInfo).listParts().getList().stream().map(FilePartInfo::getPartNumber).collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void delete(String url) {
|
|
|
try {
|
|
|
FileInfo fileInfo = getFileInfoByURL(url);
|
|
|
service.delete(fileInfo);
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Downloader download(String url) {
|
|
|
try {
|
|
|
FileInfo fileInfo = getFileInfoByURL(url);
|
|
|
return service.download(fileInfo);
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@SneakyThrows
|
|
|
public void download(String url, Duration exp, HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
FileInfo fileInfo = getFileInfoByURL(url);
|
|
|
FileStorage fileStorage = service.getFileStorage(fileInfo.getPlatform());
|
|
|
|
|
|
if (fileStorage.isSupportPresignedUrl()) {
|
|
|
url = generatePresignedUrlInner(url, null, fileInfo, fileStorage);
|
|
|
log.debug("跳转:"+url);
|
|
|
response.reset();
|
|
|
response.setStatus(HttpServletResponse.SC_FOUND);
|
|
|
response.setHeader("Location", url);
|
|
|
response.getWriter().print("");
|
|
|
response.flushBuffer();
|
|
|
} else {
|
|
|
if (fileStorage instanceof LocalPlusFileStorage) {
|
|
|
LocalPlusFileStorage fs = (LocalPlusFileStorage) fileStorage;
|
|
|
File file = new File(new File(fs.getStoragePath(), fileInfo.getPath()), fileInfo.getFilename());
|
|
|
log.debug("下载本地文件:" + file.getAbsolutePath());
|
|
|
if (!file.isFile()) {
|
|
|
throw new FileNotFoundException("文件不存在");
|
|
|
}
|
|
|
HttpDownloadUtil.download(request, response, file);
|
|
|
|
|
|
} else {
|
|
|
response.reset();
|
|
|
FileUtils.setAttachmentResponseHeader(response, fileInfo.getFilename());
|
|
|
service.download(fileInfo).outputStream(response.getOutputStream());
|
|
|
response.flushBuffer();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
log.warn("下载文件失败", e);
|
|
|
response.sendError(404, "下载文件失败");
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@SneakyThrows
|
|
|
public String generatePresignedUrl(String url, Duration exp) {
|
|
|
try {
|
|
|
FileInfo fileInfo = getFileInfoByURL(url);
|
|
|
FileStorage fileStorage = service.getFileStorage(fileInfo.getPlatform());
|
|
|
return generatePresignedUrlInner(url, exp, fileInfo, fileStorage);
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@SneakyThrows
|
|
|
private String generatePresignedUrlInner(String url, Duration exp, FileInfo fileInfo, FileStorage fileStorage) {
|
|
|
if (!fileStorage.isSupportPresignedUrl()) {
|
|
|
throw new RuntimeException("不支持预签名URL");
|
|
|
}
|
|
|
GeneratePresignedUrlResult downloadResult = service.generatePresignedUrl()
|
|
|
.setPlatform(fileInfo.getPlatform()) // 存储平台,不传使用默认的
|
|
|
.setPath(fileInfo.getPath()) // 文件路径
|
|
|
.setFilename(fileInfo.getFilename()) // 文件名,也可以换成缩略图的文件名
|
|
|
.setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
|
|
|
.setExpiration(exp == null ? new Date(System.currentTimeMillis() + 600000) : new Date(System.currentTimeMillis() + exp.toMillis())) // 过期时间 10 分钟
|
|
|
.putResponseHeaders(
|
|
|
Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + (fileInfo.getFilename().endsWith("." + FileService.IMAGE_WEBP + FileService.SRC_EXT) ? "原图.zip" : fileInfo.getFilename()))
|
|
|
|
|
|
.generatePresignedUrl();
|
|
|
log.debug("生成访问预签名 URL 结果:{}", downloadResult);
|
|
|
String q = downloadResult.getUrl().substring(downloadResult.getUrl().indexOf("?"));
|
|
|
return URLUtil.encode(url) + q;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public FileService setSize() {
|
|
|
return setSize(properties.getMaxWidth(), properties.getMaxHeight());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public FileService setWatermark() {
|
|
|
return setWatermark(properties.getWatermarkImage());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public FileService setThumbnail(boolean thumbnail) {
|
|
|
if (thumbnail) {
|
|
|
return setThumbnail(properties.getThWidth(), properties.getThHeight());
|
|
|
} else {
|
|
|
return setThumbnail(0, 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@SneakyThrows
|
|
|
public String save(InputStream in) {
|
|
|
try {
|
|
|
UploadPretreatment p = service.of(in);
|
|
|
Param param = paramThreadLocal.get();
|
|
|
initUploadPretreatment(p, param);
|
|
|
return p.upload().getUrl();
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
@Override
|
|
|
@SneakyThrows
|
|
|
public String saveImage(InputStream in) {
|
|
|
try {
|
|
|
byte[] imageBytes = IoUtil.readBytes(in, true);
|
|
|
Image image = ImgUtil.read(new ByteArrayInputStream(imageBytes));
|
|
|
Param param = paramThreadLocal.get();
|
|
|
param.setKeepFilename(false);
|
|
|
String srcFilename = param.getFilename();
|
|
|
param.setFilename(FileService.DEFAULT_IMAGE_FILENAME);
|
|
|
UploadPretreatment p = service.of(formatImage(image, param.getMaxWidth(), param.getMaxHeight(), param.getWatermark()));
|
|
|
initUploadPretreatment(p, param);
|
|
|
String url = p.upload().getUrl();
|
|
|
if (param.getThHeight() > 0 && param.getThWidth() > 0) {
|
|
|
UploadPretreatment p1 = service.of(formatImage(image, param.getThWidth(), param.getThHeight(), null));
|
|
|
String platform = param.getPlatform();
|
|
|
if (StrUtil.isNotBlank(platform)) {
|
|
|
p1 = p1.setPlatform(platform);
|
|
|
}
|
|
|
p1.setPath(p.getPath());
|
|
|
p1.setSaveFilename(p.getSaveFilename() + FileService.THUMBNAIL_EXT);
|
|
|
p1.upload();
|
|
|
}
|
|
|
|
|
|
if (param.isSaveSrc() && StrUtil.isNotBlank(srcFilename)) {
|
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
ZipUtil.zip(out, new String[]{srcFilename}, new InputStream[]{new ByteArrayInputStream(imageBytes)});
|
|
|
UploadPretreatment p1 = service.of(new ByteArrayInputStream(out.toByteArray()));
|
|
|
String platform = param.getPlatform();
|
|
|
if (StrUtil.isNotBlank(platform)) {
|
|
|
p1 = p1.setPlatform(platform);
|
|
|
}
|
|
|
p1.setPath(p.getPath());
|
|
|
p1.setSaveFilename(p.getSaveFilename() + FileService.SRC_EXT);
|
|
|
p1.upload();
|
|
|
}
|
|
|
|
|
|
return url;
|
|
|
} finally {
|
|
|
paramThreadLocal.remove();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
@Override
|
|
|
public Tika getTika() {
|
|
|
return tikaFactory.getTika();
|
|
|
}
|
|
|
|
|
|
private FileInfo getFileInfoByURL(String url) {
|
|
|
if (StrUtil.isBlank(url)) {
|
|
|
throw new RuntimeException("URL不能为空");
|
|
|
}
|
|
|
FileInfo fileInfo = new FileInfo();
|
|
|
String platform = paramThreadLocal.get().getPlatform();
|
|
|
FileStorage fileStorage = null;
|
|
|
if (StrUtil.isBlank(platform)) {
|
|
|
fileStorage = service.getFileStorage();
|
|
|
} else {
|
|
|
fileStorage = service.getFileStorage(platform);
|
|
|
}
|
|
|
fileInfo.setPlatform(fileStorage.getPlatform());
|
|
|
String domain = null;
|
|
|
try {
|
|
|
domain = (String) BeanUtil.getProperty(fileStorage, "domain");
|
|
|
} catch (Exception e) {
|
|
|
}
|
|
|
if (StrUtil.isBlank(domain)) {
|
|
|
int index = url.indexOf("://");
|
|
|
if (index > -1) {
|
|
|
url = url.substring(url.indexOf("/", index + 4) + 1);
|
|
|
} else if (url.startsWith("//")) {
|
|
|
url = url.substring(url.indexOf("/", 3) + 1);
|
|
|
} else if (url.startsWith("/")) {
|
|
|
url = url.substring(1);
|
|
|
}
|
|
|
index = url.indexOf("/");
|
|
|
url = url.substring(index + 1);
|
|
|
index = url.lastIndexOf("/");
|
|
|
|
|
|
fileInfo.setPath(url.substring(0, index + 1));
|
|
|
fileInfo.setFilename(url.substring(index + 1));
|
|
|
} else {
|
|
|
url = url.substring(domain.length());
|
|
|
int index = url.lastIndexOf("/");
|
|
|
fileInfo.setPath(url.substring(0, index + 1));
|
|
|
fileInfo.setFilename(url.substring(index + 1));
|
|
|
}
|
|
|
|
|
|
|
|
|
return fileInfo;
|
|
|
}
|
|
|
|
|
|
private void initUploadPretreatment(UploadPretreatment p, Param param) {
|
|
|
|
|
|
String platform = param.getPlatform();
|
|
|
if (StrUtil.isNotBlank(platform)) {
|
|
|
p = p.setPlatform(platform);
|
|
|
} else {
|
|
|
p = p.setPlatform(service.getFileStorage().getPlatform());
|
|
|
}
|
|
|
String uri = param.getUri();
|
|
|
if (StrUtil.isBlank(uri)) {
|
|
|
String rule = param.getRule();
|
|
|
if (StrUtil.isBlank(rule)) {
|
|
|
if (param.isKeepFilename()) {
|
|
|
rule = RULE_KEEP_FILENAME;
|
|
|
} else {
|
|
|
rule = RULE_RANDOM_FILENAME;
|
|
|
}
|
|
|
}
|
|
|
uri = generateURI(param.getPrefix(), rule, param.getFilename());
|
|
|
} else {
|
|
|
uri = param.getPrefix() + uri;
|
|
|
}
|
|
|
|
|
|
int index = uri.lastIndexOf("/") + 1;
|
|
|
String path = uri.substring(0, index);
|
|
|
String filename = uri.substring(index);
|
|
|
log.debug("path={},filename={}", path, filename);
|
|
|
p.setPath(path).setSaveFilename(filename);
|
|
|
}
|
|
|
}
|