From 68b4614cf6e42872a3091ef6568ad1690dac31a6 Mon Sep 17 00:00:00 2001 From: guqing <1484563614@qq.com> Date: Thu, 21 Oct 2021 15:17:04 +0800 Subject: [PATCH] refactor: attachment upload parameter --- .../app/handler/file/AliOssFileHandler.java | 53 +++--- .../app/handler/file/BaiduBosFileHandler.java | 43 +++-- .../app/handler/file/FilePathDescriptor.java | 175 ++++++++++++++++++ .../handler/file/HuaweiObsFileHandler.java | 54 +++--- .../app/handler/file/LocalFileHandler.java | 165 +++++------------ .../app/handler/file/MinioFileHandler.java | 38 ++-- .../app/handler/file/QiniuOssFileHandler.java | 51 ++--- .../app/handler/file/SmmsFileHandler.java | 2 +- .../handler/file/TencentCosFileHandler.java | 56 +++--- .../handler/file/FilePathDescriptorTest.java | 81 ++++++++ 10 files changed, 445 insertions(+), 273 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..9b4d756d4 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,20 @@ 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(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 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 +105,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..0dbf7589d 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,41 @@ 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(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 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..9939ba08e --- /dev/null +++ b/src/main/java/run/halo/app/handler/file/FilePathDescriptor.java @@ -0,0 +1,175 @@ +package run.halo.app.handler.file; + +import java.util.function.Predicate; +import lombok.Data; +import lombok.experimental.Accessors; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.Assert; +import run.halo.app.utils.FilenameUtils; +import run.halo.app.utils.HaloUtils; + +/** + * File path descriptor. + * + * @author guqing + * @since 2021-10-21 + */ +@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 path; + private String basePath; + private String nameSuffix = StringUtils.EMPTY; + private String separator = "/"; + private boolean automaticRename; + private Predicate renamePredicate; + + public Builder setSubPath(String subPath) { + this.path = 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 = basePath; + 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 getName() { + 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 getName() + '.' + this.extension; + } + return getName(); + } + + String getFullPath() { + if (StringUtils.isNotBlank(this.basePath)) { + return getPath(this.basePath, this.path, this.getFullName()); + } + return getPath(this.path, this.getFullName()); + } + + private boolean shouldRename() { + if (!automaticRename) { + return false; + } + // automaticRename is true + if (renamePredicate == null) { + return true; + } + // renamePredicate not null + return renamePredicate.test(this); + } + + 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() { + return new FilePathDescriptor() + .setBasePath(this.basePath) + .setSubPath(this.path) + .setRelativePath(getPath(this.path, getFullName())) + .setName(FilenameUtils.getBasename(getName())) + .setExtension(extension) + .setFullPath(getFullPath()) + .setFullName(getFullName()); + } + } +} 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..db83577e4 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,43 @@ 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(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 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 3ff86213d..84c1d6e7c 100644 --- a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java @@ -10,23 +10,19 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Calendar; import java.util.Objects; -import lombok.Data; -import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; -import org.apache.commons.lang3.StringUtils; 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; import run.halo.app.config.properties.HaloProperties; import run.halo.app.exception.FileOperationException; -import run.halo.app.handler.file.LocalFileHandler.FilePath.Builder; 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; /** @@ -34,6 +30,7 @@ import run.halo.app.utils.ImageUtils; * * @author johnniang * @author ryanwang + * @author guqing * @date 2019-03-27 */ @Slf4j @@ -57,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()); @@ -87,31 +84,37 @@ 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"); - FilePath uploadFilePath = new Builder() + FilePathDescriptor uploadFilePath = new FilePathDescriptor.Builder() .setBasePath(workDir) + .setSubPath(generatePath()) + .setSeparator(FILE_SEPARATOR) + .setAutomaticRename(true) + .setRenamePredicate(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 0) .setOriginalName(file.getOriginalFilename()) .build(); log.info("Uploading file: [{}] to directory: [{}]", file.getOriginalFilename(), - uploadFilePath.getSubPath()); - + uploadFilePath.getRelativePath()); + Path localFileFullPath = Paths.get(uploadFilePath.getFullPath()); try { // TODO Synchronize here // Create directory - Files.createDirectories(uploadFilePath.getFullPath().getParent()); - Files.createFile(uploadFilePath.getFullPath()); + Files.createDirectories(localFileFullPath.getParent()); + Files.createFile(localFileFullPath); // Upload this file - file.transferTo(uploadFilePath.getFullPath()); + file.transferTo(localFileFullPath); // Build upload result UploadResult uploadResult = new UploadResult(); uploadResult.setFilename(uploadFilePath.getName()); - uploadResult.setFilePath(uploadFilePath.getSubPath()); - uploadResult.setKey(uploadFilePath.getSubPath()); + uploadResult.setFilePath(uploadFilePath.getRelativePath()); + uploadResult.setKey(uploadFilePath.getRelativePath()); uploadResult.setSuffix(uploadFilePath.getExtension()); uploadResult .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); @@ -120,11 +123,14 @@ public class LocalFileHandler implements FileHandler { // TODO refactor this: if image is svg ext. extension handleImageMetadata(file, uploadResult, () -> { // Upload a thumbnail - FilePath thumbnailFilePath = new Builder() + FilePathDescriptor thumbnailFilePath = new FilePathDescriptor.Builder() .setBasePath(workDir) - .setOriginalName(THUMBNAIL_SUFFIX + uploadFilePath.getFullName()) + .setSubPath(uploadFilePath.getSubPath()) + .setSeparator(FILE_SEPARATOR) + .setOriginalName(uploadFilePath.getFullName()) + .setNameSuffix(THUMBNAIL_SUFFIX) .build(); - final Path thumbnailPath = thumbnailFilePath.getFullPath(); + final Path thumbnailPath = Paths.get(thumbnailFilePath.getFullPath()); try (InputStream is = file.getInputStream()) { // Generate thumbnail BufferedImage originalImage = @@ -133,110 +139,19 @@ public class LocalFileHandler implements FileHandler { uploadFilePath.getExtension()); if (result) { // Set thumb path - return thumbnailFilePath.getSubFilePath(); + return thumbnailFilePath.getRelativePath(); } } catch (Throwable e) { log.warn("Failed to open image file.", e); } - return uploadFilePath.getSubFilePath(); + return uploadFilePath.getRelativePath(); }); log.info("Uploaded file: [{}] to directory: [{}] successfully", - file.getOriginalFilename(), uploadFilePath.getFullPath().toString()); + file.getOriginalFilename(), uploadFilePath.getFullPath()); return uploadResult; } catch (IOException e) { - throw new FileOperationException("上传附件失败").setErrorData(uploadFilePath); - } - } - - @Data - @Accessors(chain = true) - public static final class FilePath { - String name; - String extension; - String subPath; - String subFilePath; - String basePath; - String fullName; - Path fullPath; - - public static final class Builder { - String name; - String extension; - String path; - String basePath; - - public Builder() { - this.path = generatePath(); - } - - 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; - } - - /** - * 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 = basePath; - return this; - } - - String getFullName() { - if (!hasExtension()) { - return this.name; - } - return this.name + '.' + this.extension; - } - - private Path getFullPath() { - if (StringUtils.isNotBlank(this.basePath)) { - return Paths.get(this.basePath, this.path, this.getFullName()); - } - return Paths.get(this.path, this.getFullName()); - } - - /** - * build file path object. - * - * @return file path - */ - public FilePath build() { - if (Files.exists(getFullPath())) { - this.name = this.name + '-' + HaloUtils.simpleUUID(); - } - return new FilePath() - .setBasePath(this.basePath) - .setSubPath(this.path) - .setSubFilePath(this.path + getFullName()) - .setName(this.name) - .setExtension(extension) - .setFullPath(getFullPath()) - .setFullName(getFullName()); - } - - private boolean hasExtension() { - return StringUtils.isNotBlank(this.extension); - } + throw new FileOperationException("上传附件失败").setErrorData(uploadFilePath.getFullPath()); } } @@ -279,6 +194,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"); @@ -289,11 +217,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..8d303c467 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,35 @@ 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(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 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..ba343ed7f 100644 --- a/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java @@ -30,6 +30,7 @@ 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; @@ -40,6 +41,7 @@ import run.halo.app.utils.JsonUtils; * * @author johnniang * @author ryanwang + * @author guqing * @date 2019-03-27 */ @Slf4j @@ -47,9 +49,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 +101,14 @@ 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(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 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..44e12e49e 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,14 @@ 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(builder -> + attachmentRepository.countByPath(builder.getFullPath()) > 0) + .setOriginalName(file.getOriginalFilename()) + .build(); // Upload ObjectMetadata objectMetadata = new ObjectMetadata(); @@ -114,31 +106,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/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..71dd2de7e --- /dev/null +++ b/src/test/java/run/halo/app/handler/file/FilePathDescriptorTest.java @@ -0,0 +1,81 @@ +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(builder -> false).build(); + assertEquals("/home/halo/2021/10/hello.jpg", descriptor1.getFullPath()); + assertEquals("2021/10/hello.jpg", descriptor1.getRelativePath()); + + FilePathDescriptor descriptor2 = descriptorBuilder.setAutomaticRename(true) + .setRenamePredicate(builder -> 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()); + } +}