You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

371 lines
13 KiB
Java

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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