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