From 1d9145050542bd186207bbdb09d7374eab7c8942 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Fri, 29 Oct 2021 19:36:37 +0800 Subject: [PATCH] refactor: the naming method of uploaded attachments (#1500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Refactor the naming method of uploaded attachments * refactor: attachment upload parameter * refactor: 重构文件名称是否重复的判断方式 * refactor: the usage of file path descriptor * fix: remove blank line * feat: add more test case * fix: file base name in FilePathDescriptor * Revert "refactor: the usage of file path descriptor" This reverts commit b46ff3b4 --- .../app/handler/file/AliOssFileHandler.java | 54 +++-- .../app/handler/file/BaiduBosFileHandler.java | 44 ++-- .../app/handler/file/FilePathDescriptor.java | 189 ++++++++++++++++++ .../handler/file/HuaweiObsFileHandler.java | 55 +++-- .../app/handler/file/LocalFileHandler.java | 116 +++++------ .../app/handler/file/MinioFileHandler.java | 39 ++-- .../app/handler/file/QiniuOssFileHandler.java | 53 ++--- .../app/handler/file/SmmsFileHandler.java | 2 +- .../handler/file/TencentCosFileHandler.java | 57 +++--- .../app/handler/file/UpOssFileHandler.java | 1 + .../app/repository/AttachmentRepository.java | 9 + .../handler/file/FilePathDescriptorTest.java | 90 +++++++++ .../run/halo/app/utils/FilenameUtilsTest.java | 1 + 13 files changed, 498 insertions(+), 212 deletions(-) create mode 100644 src/main/java/run/halo/app/handler/file/FilePathDescriptor.java create mode 100644 src/test/java/run/halo/app/handler/file/FilePathDescriptorTest.java diff --git a/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java b/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java index 3445ad4da..2cdac5bc8 100644 --- a/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java @@ -18,8 +18,8 @@ import run.halo.app.exception.FileOperationException; import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.properties.AliOssProperties; import run.halo.app.model.support.UploadResult; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.service.OptionService; -import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.ImageUtils; /** @@ -27,6 +27,7 @@ import run.halo.app.utils.ImageUtils; * * @author MyFaith * @author ryanwang + * @author guqing * @date 2019-04-04 */ @Slf4j @@ -34,9 +35,12 @@ import run.halo.app.utils.ImageUtils; public class AliOssFileHandler implements FileHandler { private final OptionService optionService; + private final AttachmentRepository attachmentRepository; - public AliOssFileHandler(OptionService optionService) { + public AliOssFileHandler(OptionService optionService, + AttachmentRepository attachmentRepository) { this.optionService = optionService; + this.attachmentRepository = attachmentRepository; } @Override @@ -79,30 +83,21 @@ public class AliOssFileHandler implements FileHandler { } try { - final String basename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - final String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - final String timestamp = String.valueOf(System.currentTimeMillis()); - final StringBuilder upFilePath = new StringBuilder(); - - if (StringUtils.isNotEmpty(source)) { - upFilePath.append(source) - .append(URL_SEPARATOR); - } - - upFilePath.append(basename) - .append("_") - .append(timestamp) - .append(".") - .append(extension); - - String filePath = StringUtils.join(basePath.toString(), upFilePath.toString()); + FilePathDescriptor uploadFilePath = new FilePathDescriptor.Builder() + .setBasePath(basePath.toString()) + .setSubPath(source) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.ALIOSS) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); log.info(basePath.toString()); // Upload final PutObjectResult putObjectResult = ossClient.putObject(bucketName, - upFilePath.toString(), + uploadFilePath.getRelativePath(), file.getInputStream()); if (putObjectResult == null) { @@ -111,21 +106,22 @@ public class AliOssFileHandler implements FileHandler { // Response result final UploadResult uploadResult = new UploadResult(); - uploadResult.setFilename(basename); + uploadResult.setFilename(uploadFilePath.getName()); + String fullPath = uploadFilePath.getFullPath(); uploadResult - .setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); - uploadResult.setKey(upFilePath.toString()); + .setFilePath(StringUtils.isBlank(styleRule) ? fullPath : fullPath + styleRule); + uploadResult.setKey(uploadFilePath.getRelativePath()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); - uploadResult.setSuffix(extension); + uploadResult.setSuffix(uploadFilePath.getExtension()); uploadResult.setSize(file.getSize()); handleImageMetadata(file, uploadResult, () -> { - if (ImageUtils.EXTENSION_ICO.equals(extension)) { - return filePath; + if (ImageUtils.EXTENSION_ICO.equals(uploadFilePath.getExtension())) { + return fullPath; } else { - return StringUtils.isBlank(thumbnailStyleRule) ? filePath : - filePath + thumbnailStyleRule; + return StringUtils.isBlank(thumbnailStyleRule) ? fullPath : + fullPath + thumbnailStyleRule; } }); diff --git a/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java b/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java index 0f1d23966..85719fc8f 100644 --- a/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java @@ -1,6 +1,5 @@ package run.halo.app.handler.file; - import com.baidubce.auth.DefaultBceCredentials; import com.baidubce.services.bos.BosClient; import com.baidubce.services.bos.BosClientConfiguration; @@ -16,8 +15,8 @@ import run.halo.app.exception.FileOperationException; import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.properties.BaiduBosProperties; import run.halo.app.model.support.UploadResult; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.service.OptionService; -import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.ImageUtils; /** @@ -32,9 +31,12 @@ import run.halo.app.utils.ImageUtils; public class BaiduBosFileHandler implements FileHandler { private final OptionService optionService; + private final AttachmentRepository attachmentRepository; - public BaiduBosFileHandler(OptionService optionService) { + public BaiduBosFileHandler(OptionService optionService, + AttachmentRepository attachmentRepository) { this.optionService = optionService; + this.attachmentRepository = attachmentRepository; } @Override @@ -69,40 +71,42 @@ public class BaiduBosFileHandler implements FileHandler { domain = protocol + domain; try { - String basename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - String timestamp = String.valueOf(System.currentTimeMillis()); - String upFilePath = StringUtils.join(basename, "_", timestamp, ".", extension); - String filePath = StringUtils.join( - StringUtils.appendIfMissing(StringUtils.isNotBlank(domain) ? domain : source, "/"), - upFilePath); + FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() + .setBasePath(domain) + .setSubPath(source) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.BAIDUBOS) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); // Upload PutObjectResponse putObjectResponseFromInputStream = - client.putObject(bucketName, upFilePath, file.getInputStream()); + client.putObject(bucketName, pathDescriptor.getFullName(), file.getInputStream()); if (putObjectResponseFromInputStream == null) { throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到百度云失败 "); } // Response result UploadResult uploadResult = new UploadResult(); - uploadResult.setFilename(basename); + uploadResult.setFilename(pathDescriptor.getFullName()); + String fullPath = pathDescriptor.getFullPath(); uploadResult - .setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); - uploadResult.setKey(upFilePath); + .setFilePath(StringUtils.isBlank(styleRule) ? fullPath : fullPath + styleRule); + uploadResult.setKey(pathDescriptor.getRelativePath()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); - uploadResult.setSuffix(extension); + uploadResult.setSuffix(pathDescriptor.getExtension()); uploadResult.setSize(file.getSize()); // Handle thumbnail handleImageMetadata(file, uploadResult, () -> { - if (ImageUtils.EXTENSION_ICO.equals(extension)) { - return filePath; + if (ImageUtils.EXTENSION_ICO.equals(pathDescriptor.getExtension())) { + return fullPath; } else { - return StringUtils.isBlank(thumbnailStyleRule) ? filePath : - filePath + thumbnailStyleRule; + return StringUtils.isBlank(thumbnailStyleRule) ? fullPath : + fullPath + thumbnailStyleRule; } }); diff --git a/src/main/java/run/halo/app/handler/file/FilePathDescriptor.java b/src/main/java/run/halo/app/handler/file/FilePathDescriptor.java new file mode 100644 index 000000000..74baf7324 --- /dev/null +++ b/src/main/java/run/halo/app/handler/file/FilePathDescriptor.java @@ -0,0 +1,189 @@ +package run.halo.app.handler.file; + +import java.util.function.Predicate; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.Assert; +import run.halo.app.utils.FilenameUtils; + +/** + * File path descriptor. + * + * @author guqing + * @since 2021-10-21 + */ +@Slf4j +@Data +@Accessors(chain = true) +public final class FilePathDescriptor { + + private String name; + private String extension; + private String relativePath; + private String basePath; + private String fullName; + private String fullPath; + private String subPath; + + public static final class Builder { + + private String name; + private String extension; + private String subPath; + private String basePath; + private String nameSuffix = StringUtils.EMPTY; + private String separator = "/"; + private boolean automaticRename; + private Predicate renamePredicate; + private String relativePath; + + public Builder setSubPath(String subPath) { + this.subPath = StringUtils.removeEnd(subPath, separator); + return this; + } + + public Builder setAutomaticRename(Boolean automaticRename) { + this.automaticRename = automaticRename != null && automaticRename; + return this; + } + + public Builder setRenamePredicate(Predicate predicate) { + this.renamePredicate = predicate; + return this; + } + + /** + * Set path separator, NULL value is not allowed. + * + * @param separator path separator + * @return builder + */ + public Builder setSeparator(String separator) { + if (separator == null) { + throw new IllegalArgumentException("The separator must not be null."); + } + this.separator = separator; + return this; + } + + /** + * Set original file name. + * + * @param originalFileName original file name + * @return file path builder + */ + public Builder setOriginalName(String originalFileName) { + Assert.notNull(originalFileName, "The originalFileName must not be null."); + this.name = FilenameUtils.getBasename(originalFileName); + this.extension = FilenameUtils.getExtension(originalFileName); + return this; + } + + public Builder setBasePath(String basePath) { + this.basePath = StringUtils.removeEnd(basePath, separator); + return this; + } + + /** + * Set file base name suffix. + * + * @param nameSuffix file base name suffix + * @return builder + */ + public Builder setNameSuffix(String nameSuffix) { + if (nameSuffix == null) { + nameSuffix = StringUtils.EMPTY; + } + this.nameSuffix = nameSuffix; + return this; + } + + String buildName() { + StringBuilder sb = new StringBuilder() + .append(this.name); + if (shouldRename()) { + String timestamp = String.valueOf(System.currentTimeMillis()); + sb.append('-').append(timestamp); + } + sb.append(this.nameSuffix); + return sb.toString(); + } + + String getFullName() { + // eg. hello.jpg -> hello-uuid-thumbnail.jpg + if (StringUtils.isNotBlank(this.extension)) { + return this.name + '.' + this.extension; + } + return this.name; + } + + String getFullPath() { + if (StringUtils.isNotBlank(this.basePath)) { + return getPath(this.basePath, this.subPath, this.getFullName()); + } + return getPath(this.subPath, this.getFullName()); + } + + String getRelativePath() { + return getPath(this.subPath, getFullName()); + } + + private boolean shouldRename() { + if (!automaticRename) { + return false; + } + // automaticRename is true + if (renamePredicate == null) { + return true; + } + // renamePredicate not null + return renamePredicate.test(this.relativePath); + } + + private String getPath(String first, String... more) { + String path; + if (more.length == 0) { + path = first; + } else { + StringBuilder sb = new StringBuilder(); + sb.append(first); + for (String segment : more) { + if (StringUtils.isNotBlank(segment)) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(segment); + } + } + path = sb.toString(); + } + return path; + } + + /** + * build file path object. + * + * @return file path + */ + public FilePathDescriptor build() { + // build relative path first, used to determine if it needs to be renamed + this.relativePath = getRelativePath(); + // then build name, returns a new name if the relative path exists + this.name = buildName(); + + FilePathDescriptor descriptor = new FilePathDescriptor() + .setBasePath(this.basePath) + .setSubPath(this.subPath) + // regenerate relative path + .setRelativePath(getRelativePath()) + .setName(this.name) + .setExtension(extension) + .setFullPath(getFullPath()) + .setFullName(getFullName()); + log.info("FilePathDescriptor: [{}]", descriptor); + return descriptor; + } + } +} diff --git a/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java b/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java index 28df79ae4..e8bb5fe7b 100644 --- a/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java @@ -17,14 +17,15 @@ import run.halo.app.exception.FileOperationException; import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.properties.HuaweiObsProperties; import run.halo.app.model.support.UploadResult; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.service.OptionService; -import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.ImageUtils; /** * Huawei obs file handler. * * @author qilin + * @author guqing * @date 2020-04-03 */ @Slf4j @@ -32,9 +33,12 @@ import run.halo.app.utils.ImageUtils; public class HuaweiObsFileHandler implements FileHandler { private final OptionService optionService; + private final AttachmentRepository attachmentRepository; - public HuaweiObsFileHandler(OptionService optionService) { + public HuaweiObsFileHandler(OptionService optionService, + AttachmentRepository attachmentRepository) { this.optionService = optionService; + this.attachmentRepository = attachmentRepository; } @Override @@ -77,51 +81,44 @@ public class HuaweiObsFileHandler implements FileHandler { } try { - String basename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - String timestamp = String.valueOf(System.currentTimeMillis()); - StringBuilder upFilePath = new StringBuilder(); - - if (StringUtils.isNotEmpty(source)) { - upFilePath.append(source) - .append(URL_SEPARATOR); - } - - upFilePath.append(basename) - .append("_") - .append(timestamp) - .append(".") - .append(extension); - - String filePath = StringUtils.join(basePath.toString(), upFilePath.toString()); + FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() + .setBasePath(domain) + .setSubPath(source) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.HUAWEIOBS) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); log.info(basePath.toString()); // Upload PutObjectResult putObjectResult = - obsClient.putObject(bucketName, upFilePath.toString(), file.getInputStream()); + obsClient.putObject(bucketName, pathDescriptor.getRelativePath(), + file.getInputStream()); if (putObjectResult == null) { throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到华为云失败 "); } // Response result UploadResult uploadResult = new UploadResult(); - uploadResult.setFilename(basename); + uploadResult.setFilename(pathDescriptor.getName()); + String fullPath = pathDescriptor.getFullPath(); uploadResult - .setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); - uploadResult.setKey(upFilePath.toString()); + .setFilePath(StringUtils.isBlank(styleRule) ? fullPath : fullPath + styleRule); + uploadResult.setKey(pathDescriptor.getRelativePath()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); - uploadResult.setSuffix(extension); + uploadResult.setSuffix(pathDescriptor.getExtension()); uploadResult.setSize(file.getSize()); handleImageMetadata(file, uploadResult, () -> { - if (ImageUtils.EXTENSION_ICO.equals(extension)) { - return filePath; + if (ImageUtils.EXTENSION_ICO.equals(pathDescriptor.getExtension())) { + return fullPath; } else { - return StringUtils.isBlank(thumbnailStyleRule) ? filePath : - filePath + thumbnailStyleRule; + return StringUtils.isBlank(thumbnailStyleRule) ? fullPath : + fullPath + thumbnailStyleRule; } }); diff --git a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java index c081b21d2..64360fa0b 100644 --- a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java @@ -13,6 +13,7 @@ import java.util.Objects; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; import org.springframework.http.MediaType; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartFile; @@ -20,9 +21,8 @@ import run.halo.app.config.properties.HaloProperties; import run.halo.app.exception.FileOperationException; import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.support.UploadResult; -import run.halo.app.service.OptionService; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.utils.FilenameUtils; -import run.halo.app.utils.HaloUtils; import run.halo.app.utils.ImageUtils; /** @@ -30,6 +30,7 @@ import run.halo.app.utils.ImageUtils; * * @author johnniang * @author ryanwang + * @author guqing * @date 2019-03-27 */ @Slf4j @@ -53,13 +54,13 @@ public class LocalFileHandler implements FileHandler { */ private static final int THUMB_HEIGHT = 256; - private final OptionService optionService; + private final AttachmentRepository attachmentRepository; private final String workDir; - public LocalFileHandler(OptionService optionService, + public LocalFileHandler(AttachmentRepository attachmentRepository, HaloProperties haloProperties) { - this.optionService = optionService; + this.attachmentRepository = attachmentRepository; // Get work dir workDir = FileHandler.normalizeDirectory(haloProperties.getWorkDir()); @@ -83,57 +84,39 @@ public class LocalFileHandler implements FileHandler { } } + @NonNull @Override - public UploadResult upload(MultipartFile file) { + public UploadResult upload(@NonNull MultipartFile file) { Assert.notNull(file, "Multipart file must not be null"); - // Get current time - Calendar current = Calendar.getInstance(optionService.getLocale()); - // Get month and day of month - int year = current.get(Calendar.YEAR); - int month = current.get(Calendar.MONTH) + 1; - - String monthString = month < 10 ? "0" + month : String.valueOf(month); - - // Build directory - String subDir = UPLOAD_SUB_DIR + year + FILE_SEPARATOR + monthString + FILE_SEPARATOR; - - String originalBasename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - - // Get basename - String basename = originalBasename + '-' + HaloUtils.randomUUIDWithoutDash(); - - // Get extension - String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - - log.debug("Base name: [{}], extension: [{}] of original filename: [{}]", basename, - extension, file.getOriginalFilename()); - - // Build sub file path - String subFilePath = subDir + basename + '.' + extension; - - // Get upload path - Path uploadPath = Paths.get(workDir, subFilePath); - - log.info("Uploading file: [{}]to directory: [{}]", file.getOriginalFilename(), - uploadPath.toString()); - + FilePathDescriptor uploadFilePath = new FilePathDescriptor.Builder() + .setBasePath(workDir) + .setSubPath(generatePath()) + .setSeparator(FILE_SEPARATOR) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.LOCAL) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); + log.info("Uploading file: [{}] to directory: [{}]", file.getOriginalFilename(), + uploadFilePath.getRelativePath()); + Path localFileFullPath = Paths.get(uploadFilePath.getFullPath()); try { // TODO Synchronize here // Create directory - Files.createDirectories(uploadPath.getParent()); - Files.createFile(uploadPath); + Files.createDirectories(localFileFullPath.getParent()); + Files.createFile(localFileFullPath); // Upload this file - file.transferTo(uploadPath); + file.transferTo(localFileFullPath); // Build upload result UploadResult uploadResult = new UploadResult(); - uploadResult.setFilename(originalBasename); - uploadResult.setFilePath(subFilePath); - uploadResult.setKey(subFilePath); - uploadResult.setSuffix(extension); + uploadResult.setFilename(uploadFilePath.getName()); + uploadResult.setFilePath(uploadFilePath.getRelativePath()); + uploadResult.setKey(uploadFilePath.getRelativePath()); + uploadResult.setSuffix(uploadFilePath.getExtension()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); uploadResult.setSize(file.getSize()); @@ -141,28 +124,35 @@ public class LocalFileHandler implements FileHandler { // TODO refactor this: if image is svg ext. extension handleImageMetadata(file, uploadResult, () -> { // Upload a thumbnail - final String thumbnailBasename = basename + THUMBNAIL_SUFFIX; - final String thumbnailSubFilePath = subDir + thumbnailBasename + '.' + extension; - final Path thumbnailPath = Paths.get(workDir + thumbnailSubFilePath); + FilePathDescriptor thumbnailFilePath = new FilePathDescriptor.Builder() + .setBasePath(workDir) + .setSubPath(uploadFilePath.getSubPath()) + .setSeparator(FILE_SEPARATOR) + .setOriginalName(uploadFilePath.getFullName()) + .setNameSuffix(THUMBNAIL_SUFFIX) + .build(); + final Path thumbnailPath = Paths.get(thumbnailFilePath.getFullPath()); try (InputStream is = file.getInputStream()) { // Generate thumbnail - BufferedImage originalImage = ImageUtils.getImageFromFile(is, extension); - boolean result = generateThumbnail(originalImage, thumbnailPath, extension); + BufferedImage originalImage = + ImageUtils.getImageFromFile(is, uploadFilePath.getExtension()); + boolean result = generateThumbnail(originalImage, thumbnailPath, + uploadFilePath.getExtension()); if (result) { // Set thumb path - return thumbnailSubFilePath; + return thumbnailFilePath.getRelativePath(); } } catch (Throwable e) { log.warn("Failed to open image file.", e); } - return subFilePath; + return uploadFilePath.getRelativePath(); }); log.info("Uploaded file: [{}] to directory: [{}] successfully", - file.getOriginalFilename(), uploadPath.toString()); + file.getOriginalFilename(), uploadFilePath.getFullPath()); return uploadResult; } catch (IOException e) { - throw new FileOperationException("上传附件失败").setErrorData(uploadPath); + throw new FileOperationException("上传附件失败").setErrorData(uploadFilePath.getFullPath()); } } @@ -205,6 +195,19 @@ public class LocalFileHandler implements FileHandler { return AttachmentType.LOCAL; } + private String generatePath() { + // Get current time + Calendar current = Calendar.getInstance(); + // Get month and day of month + int year = current.get(Calendar.YEAR); + int month = current.get(Calendar.MONTH) + 1; + + String monthString = month < 10 ? "0" + month : String.valueOf(month); + + // Build directory + return UPLOAD_SUB_DIR + year + FILE_SEPARATOR + monthString + FILE_SEPARATOR; + } + private boolean generateThumbnail(BufferedImage originalImage, Path thumbPath, String extension) { Assert.notNull(originalImage, "Image must not be null"); @@ -215,11 +218,10 @@ public class LocalFileHandler implements FileHandler { try { Files.createFile(thumbPath); // Convert to thumbnail and copy the thumbnail - log.debug("Trying to generate thumbnail: [{}]", thumbPath.toString()); + log.debug("Trying to generate thumbnail: [{}]", thumbPath); Thumbnails.of(originalImage).size(THUMB_WIDTH, THUMB_HEIGHT).keepAspectRatio(true) .toFile(thumbPath.toFile()); - log.info("Generated thumbnail image, and wrote the thumbnail to [{}]", - thumbPath.toString()); + log.info("Generated thumbnail image, and wrote the thumbnail to [{}]", thumbPath); result = true; } catch (Throwable t) { // Ignore the error diff --git a/src/main/java/run/halo/app/handler/file/MinioFileHandler.java b/src/main/java/run/halo/app/handler/file/MinioFileHandler.java index 389f9b92a..28d27c008 100644 --- a/src/main/java/run/halo/app/handler/file/MinioFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/MinioFileHandler.java @@ -16,14 +16,14 @@ import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.properties.MinioProperties; import run.halo.app.model.support.HaloConst; import run.halo.app.model.support.UploadResult; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.service.OptionService; -import run.halo.app.utils.FilenameUtils; - /** * MinIO file handler. * * @author Wh1te + * @author guqing * @date 2020-10-03 */ @Slf4j @@ -31,9 +31,12 @@ import run.halo.app.utils.FilenameUtils; public class MinioFileHandler implements FileHandler { private final OptionService optionService; + private final AttachmentRepository attachmentRepository; - public MinioFileHandler(OptionService optionService) { + public MinioFileHandler(OptionService optionService, + AttachmentRepository attachmentRepository) { this.optionService = optionService; + this.attachmentRepository = attachmentRepository; } @NonNull @@ -62,36 +65,36 @@ public class MinioFileHandler implements FileHandler { .build(); try { - String basename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - String timestamp = String.valueOf(System.currentTimeMillis()); - String upFilePath = StringUtils - .join(StringUtils.isNotBlank(source) ? source + HaloConst.URL_SEPARATOR : "", - basename, "_", timestamp, ".", extension); - String filePath = - StringUtils.join(endpoint, bucketName, HaloConst.URL_SEPARATOR, upFilePath); + FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() + .setBasePath(endpoint + bucketName) + .setSubPath(source) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.MINIO) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); PutObjectArgs putObjectArgs = PutObjectArgs.builder() .contentType(file.getContentType()) .bucket(bucketName) .stream(file.getInputStream(), file.getSize(), -1) - .object(upFilePath) + .object(pathDescriptor.getRelativePath()) .build(); minioClient.ignoreCertCheck(); minioClient.putObject(putObjectArgs); UploadResult uploadResult = new UploadResult(); - uploadResult.setFilename(basename); - uploadResult.setFilePath(filePath); - uploadResult.setKey(upFilePath); + uploadResult.setFilename(pathDescriptor.getName()); + uploadResult.setFilePath(pathDescriptor.getFullPath()); + uploadResult.setKey(pathDescriptor.getRelativePath()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); - uploadResult.setSuffix(extension); + uploadResult.setSuffix(pathDescriptor.getExtension()); uploadResult.setSize(file.getSize()); // Handle thumbnail - handleImageMetadata(file, uploadResult, () -> filePath); + handleImageMetadata(file, uploadResult, pathDescriptor::getFullPath); return uploadResult; } catch (Exception e) { diff --git a/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java b/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java index 0e92c4e4d..6e890b9c5 100644 --- a/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java @@ -30,8 +30,8 @@ import run.halo.app.exception.FileOperationException; import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.properties.QiniuOssProperties; import run.halo.app.model.support.UploadResult; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.service.OptionService; -import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.ImageUtils; import run.halo.app.utils.JsonUtils; @@ -40,6 +40,7 @@ import run.halo.app.utils.JsonUtils; * * @author johnniang * @author ryanwang + * @author guqing * @date 2019-03-27 */ @Slf4j @@ -47,9 +48,12 @@ import run.halo.app.utils.JsonUtils; public class QiniuOssFileHandler implements FileHandler { private final OptionService optionService; + private final AttachmentRepository attachmentRepository; - public QiniuOssFileHandler(OptionService optionService) { + public QiniuOssFileHandler(OptionService optionService, + AttachmentRepository attachmentRepository) { this.optionService = optionService; + this.attachmentRepository = attachmentRepository; } @Override @@ -96,20 +100,15 @@ public class QiniuOssFileHandler implements FileHandler { .append(URL_SEPARATOR); try { - String basename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - String timestamp = String.valueOf(System.currentTimeMillis()); - StringBuilder upFilePath = new StringBuilder(); - if (StringUtils.isNotEmpty(source)) { - upFilePath.append(source) - .append(URL_SEPARATOR); - } - upFilePath.append(basename) - .append("_") - .append(timestamp) - .append(".") - .append(extension); + FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() + .setBasePath(basePath.toString()) + .setSubPath(source) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.QINIUOSS) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); // Get file recorder for temp directory FileRecorder fileRecorder = new FileRecorder(tmpPath.toFile()); @@ -117,7 +116,8 @@ public class QiniuOssFileHandler implements FileHandler { UploadManager uploadManager = new UploadManager(configuration, fileRecorder); // Put the file Response response = uploadManager - .put(file.getInputStream(), upFilePath.toString(), uploadToken, null, null); + .put(file.getInputStream(), pathDescriptor.getRelativePath(), uploadToken, null, + null); if (log.isDebugEnabled()) { log.debug("Qiniu oss response: [{}]", response.toString()); @@ -128,25 +128,26 @@ public class QiniuOssFileHandler implements FileHandler { PutSet putSet = JsonUtils.jsonToObject(response.bodyString(), PutSet.class); // Get file full path - String filePath = StringUtils.join(basePath.toString(), upFilePath.toString()); + String fullPath = pathDescriptor.getFullPath(); // Build upload result UploadResult result = new UploadResult(); - result.setFilename(basename); - result.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); - result.setKey(upFilePath.toString()); - result.setSuffix(extension); + result.setFilename(pathDescriptor.getName()); + + result.setFilePath(StringUtils.isBlank(styleRule) ? fullPath : fullPath + styleRule); + result.setKey(pathDescriptor.getRelativePath()); + result.setSuffix(pathDescriptor.getExtension()); result.setWidth(putSet.getWidth()); result.setHeight(putSet.getHeight()); result.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); result.setSize(file.getSize()); if (isImageType(file)) { - if (ImageUtils.EXTENSION_ICO.equals(extension)) { - result.setThumbPath(filePath); + if (ImageUtils.EXTENSION_ICO.equals(pathDescriptor.getExtension())) { + result.setThumbPath(fullPath); } else { - result.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : - filePath + thumbnailStyleRule); + result.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? fullPath : + fullPath + thumbnailStyleRule); } } diff --git a/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java b/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java index 66b709efa..f7e04d72d 100644 --- a/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java @@ -113,7 +113,7 @@ public class SmmsFileHandler implements FileHandler { // Check status if (mapResponseEntity.getStatusCode().isError()) { - log.error("Server response detail: [{}]", mapResponseEntity.toString()); + log.error("Server response detail: [{}]", mapResponseEntity); throw new FileOperationException( "SM.MS 服务状态异常,状态码: " + mapResponseEntity.getStatusCodeValue()); } diff --git a/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java b/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java index c7305c7c6..5c7a3f9b4 100644 --- a/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java @@ -1,6 +1,5 @@ package run.halo.app.handler.file; - import static run.halo.app.model.support.HaloConst.URL_SEPARATOR; import com.qcloud.cos.COSClient; @@ -21,8 +20,8 @@ import run.halo.app.exception.FileOperationException; import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.properties.TencentCosProperties; import run.halo.app.model.support.UploadResult; +import run.halo.app.repository.AttachmentRepository; import run.halo.app.service.OptionService; -import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.ImageUtils; /** @@ -37,9 +36,12 @@ import run.halo.app.utils.ImageUtils; public class TencentCosFileHandler implements FileHandler { private final OptionService optionService; + private final AttachmentRepository attachmentRepository; - public TencentCosFileHandler(OptionService optionService) { + public TencentCosFileHandler(OptionService optionService, + AttachmentRepository attachmentRepository) { this.optionService = optionService; + this.attachmentRepository = attachmentRepository; } @Override @@ -88,24 +90,15 @@ public class TencentCosFileHandler implements FileHandler { } try { - String basename = - FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename())); - String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - String timestamp = String.valueOf(System.currentTimeMillis()); - StringBuilder upFilePath = new StringBuilder(); - - if (StringUtils.isNotEmpty(source)) { - upFilePath.append(source) - .append(URL_SEPARATOR); - } - - upFilePath.append(basename) - .append("_") - .append(timestamp) - .append(".") - .append(extension); - - String filePath = StringUtils.join(basePath.toString(), upFilePath.toString()); + FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() + .setBasePath(basePath.toString()) + .setSubPath(source) + .setAutomaticRename(true) + .setRenamePredicate(relativePath -> + attachmentRepository + .countByFileKeyAndType(relativePath, AttachmentType.TENCENTCOS) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); // Upload ObjectMetadata objectMetadata = new ObjectMetadata(); @@ -114,31 +107,31 @@ public class TencentCosFileHandler implements FileHandler { // 设置 Content type, 默认是 application/octet-stream objectMetadata.setContentType(file.getContentType()); PutObjectResult putObjectResponseFromInputStream = cosClient - .putObject(bucketName, upFilePath.toString(), file.getInputStream(), + .putObject(bucketName, pathDescriptor.getRelativePath(), file.getInputStream(), objectMetadata); if (putObjectResponseFromInputStream == null) { throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到腾讯云失败 "); } - + String fullPath = pathDescriptor.getFullPath(); // Response result UploadResult uploadResult = new UploadResult(); - uploadResult.setFilename(basename); + uploadResult.setFilename(pathDescriptor.getName()); uploadResult - .setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); - uploadResult.setKey(upFilePath.toString()); + .setFilePath(StringUtils.isBlank(styleRule) ? fullPath : fullPath + styleRule); + uploadResult.setKey(pathDescriptor.getRelativePath()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); - uploadResult.setSuffix(extension); + uploadResult.setSuffix(pathDescriptor.getExtension()); uploadResult.setSize(file.getSize()); // Handle thumbnail handleImageMetadata(file, uploadResult, () -> { - if (ImageUtils.EXTENSION_ICO.equals(extension)) { - uploadResult.setThumbPath(filePath); - return filePath; + if (ImageUtils.EXTENSION_ICO.equals(pathDescriptor.getExtension())) { + uploadResult.setThumbPath(fullPath); + return fullPath; } else { - return StringUtils.isBlank(thumbnailStyleRule) ? filePath : - filePath + thumbnailStyleRule; + return StringUtils.isBlank(thumbnailStyleRule) ? fullPath : + fullPath + thumbnailStyleRule; } }); return uploadResult; diff --git a/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java b/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java index 9bad98899..3881b7803 100644 --- a/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java @@ -107,6 +107,7 @@ public class UpOssFileHandler implements FileHandler { filePath + thumbnailStyleRule; } }); + result.close(); return uploadResult; } catch (Exception e) { throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到又拍云失败", e); diff --git a/src/main/java/run/halo/app/repository/AttachmentRepository.java b/src/main/java/run/halo/app/repository/AttachmentRepository.java index dd68ba3c1..200aac43b 100644 --- a/src/main/java/run/halo/app/repository/AttachmentRepository.java +++ b/src/main/java/run/halo/app/repository/AttachmentRepository.java @@ -41,4 +41,13 @@ public interface AttachmentRepository * @return count of the given path */ long countByPath(@NonNull String path); + + /** + * Counts by attachment file key and type. + * + * @param fileKey attachment file key must not be blank + * @param type attachment type must not be null + * @return count of the given path and type + */ + long countByFileKeyAndType(@NonNull String fileKey, @NonNull AttachmentType type); } diff --git a/src/test/java/run/halo/app/handler/file/FilePathDescriptorTest.java b/src/test/java/run/halo/app/handler/file/FilePathDescriptorTest.java new file mode 100644 index 000000000..7777b58dd --- /dev/null +++ b/src/test/java/run/halo/app/handler/file/FilePathDescriptorTest.java @@ -0,0 +1,90 @@ +package run.halo.app.handler.file; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * File path descriptor test case. + * + * @author guqing + * @since 2021-10-21 + */ +public class FilePathDescriptorTest { + + private FilePathDescriptor.Builder descriptorBuilder; + + @BeforeEach + void setUp() { + descriptorBuilder = new FilePathDescriptor.Builder() + .setBasePath("/home/halo") + .setSubPath("2021/10/") + .setSeparator(FILE_SEPARATOR) + .setAutomaticRename(false) + .setRenamePredicate(builder -> true) + .setOriginalName("hello.jpg"); + } + + @Test + public void build() { + FilePathDescriptor descriptor = descriptorBuilder.build(); + assertEquals("/home/halo/2021/10/hello.jpg", descriptor.getFullPath()); + assertEquals("2021/10/hello.jpg", descriptor.getRelativePath()); + } + + @Test + public void autoRename() { + FilePathDescriptor descriptor = descriptorBuilder.setAutomaticRename(true).build(); + assertNotEquals("/home/halo/2021/10/hello.jpg", descriptor.getFullPath()); + assertNotEquals("2021/10/hello.jpg", descriptor.getRelativePath()); + } + + @Test + public void autoRenameWithPredicate() { + FilePathDescriptor descriptor1 = descriptorBuilder.setAutomaticRename(true) + .setRenamePredicate(path -> false).build(); + assertEquals("/home/halo/2021/10/hello.jpg", descriptor1.getFullPath()); + assertEquals("2021/10/hello.jpg", descriptor1.getRelativePath()); + + FilePathDescriptor descriptor2 = descriptorBuilder.setAutomaticRename(true) + .setRenamePredicate(path -> true).build(); + assertNotEquals("/home/halo/2021/10/hello.jpg", descriptor2.getFullPath()); + assertNotEquals("2021/10/hello.jpg", descriptor2.getRelativePath()); + } + + @Test + public void separator() { + FilePathDescriptor descriptor = descriptorBuilder.setSeparator("->").build(); + assertEquals("/home/halo->2021/10->hello.jpg", descriptor.getFullPath()); + assertEquals("2021/10->hello.jpg", descriptor.getRelativePath()); + } + + @Test + public void nameSuffix() { + FilePathDescriptor descriptor = descriptorBuilder.setNameSuffix("_thumbnail").build(); + assertEquals("/home/halo/2021/10/hello_thumbnail.jpg", descriptor.getFullPath()); + assertEquals("hello_thumbnail", descriptor.getName()); + assertEquals("hello_thumbnail.jpg", descriptor.getFullName()); + } + + @Test + public void withoutExtension() { + FilePathDescriptor descriptor = descriptorBuilder.setOriginalName("hello").build(); + assertEquals("hello", descriptor.getName()); + assertEquals("", descriptor.getExtension()); + assertEquals("hello", descriptor.getFullName()); + assertEquals("2021/10/hello", descriptor.getRelativePath()); + } + + @Test + public void otherName() { + FilePathDescriptor descriptor = descriptorBuilder.setOriginalName("1.4.9.png").build(); + assertEquals("1.4.9", descriptor.getName()); + assertEquals("1.4.9.png", descriptor.getFullName()); + assertEquals("/home/halo/2021/10/1.4.9.png", descriptor.getFullPath()); + assertEquals("2021/10/1.4.9.png", descriptor.getRelativePath()); + } +} diff --git a/src/test/java/run/halo/app/utils/FilenameUtilsTest.java b/src/test/java/run/halo/app/utils/FilenameUtilsTest.java index abde6008c..be60b1655 100644 --- a/src/test/java/run/halo/app/utils/FilenameUtilsTest.java +++ b/src/test/java/run/halo/app/utils/FilenameUtilsTest.java @@ -26,6 +26,7 @@ class FilenameUtilsTest { assertEquals("", FilenameUtils.getBasename("a/b/c/")); assertEquals("o", FilenameUtils.getBasename("he/ll/o.tar.gz")); assertEquals("i", FilenameUtils.getBasename("h/i.tar.bz2")); + assertEquals("1.4.9", FilenameUtils.getBasename("1.4.9.png")); } // foo.txt --> "txt"