diff --git a/src/main/java/cc/ryanc/halo/exception/FileUploadException.java b/src/main/java/cc/ryanc/halo/exception/FileUploadException.java new file mode 100644 index 000000000..67e3068d1 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/exception/FileUploadException.java @@ -0,0 +1,17 @@ +package cc.ryanc.halo.exception; + +/** + * File upload exception. + * + * @author johnniang + * @date 3/27/19 + */ +public class FileUploadException extends ServiceException { + public FileUploadException(String message) { + super(message); + } + + public FileUploadException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/cc/ryanc/halo/model/enums/BlogProperties.java b/src/main/java/cc/ryanc/halo/model/enums/BlogProperties.java index 388863ff8..ad755b4c8 100644 --- a/src/main/java/cc/ryanc/halo/model/enums/BlogProperties.java +++ b/src/main/java/cc/ryanc/halo/model/enums/BlogProperties.java @@ -4,7 +4,7 @@ package cc.ryanc.halo.model.enums; * @author : RYAN0UP * @date : 2019-03-17 */ -public enum BlogProperties implements PropertyEnum { +public enum BlogProperties implements PropertyEnum { /** * 博客语言 @@ -149,13 +149,7 @@ public enum BlogProperties implements PropertyEnum { /** * 附件存储位置 */ - ATTACH_LOC("attach_loc", String.class), - - /** - * 七牛云 Zone. - */ - QINIU_ZONE("qiniu_zone", String.class); - + ATTACHMENT_TYPE("attachment_type", String.class); private String value; diff --git a/src/main/java/cc/ryanc/halo/model/enums/PropertyEnum.java b/src/main/java/cc/ryanc/halo/model/enums/PropertyEnum.java index 60df17652..fa010ece5 100644 --- a/src/main/java/cc/ryanc/halo/model/enums/PropertyEnum.java +++ b/src/main/java/cc/ryanc/halo/model/enums/PropertyEnum.java @@ -9,7 +9,7 @@ import org.springframework.util.Assert; * @author johnniang * @date 3/26/19 */ -public interface PropertyEnum extends ValueEnum { +public interface PropertyEnum extends ValueEnum { /** * Get property type. @@ -67,7 +67,26 @@ public interface PropertyEnum extends ValueEnum { } // Should never happen - throw new UnsupportedOperationException("Unsupported blog property type:" + type.getName() + " provided"); + throw new UnsupportedOperationException("Unsupported convention for blog property type:" + type.getName() + " provided"); + } + + /** + * Converts to enum. + * + * @param value string value must not be null + * @param type propertye value enum type must not be null + * @param property value enum type + * @return property enum value or null + */ + static > T convertToEnum(@NonNull String value, @NonNull Class type) { + Assert.hasText(value, "Property value must not be blank"); + + try { + return Enum.valueOf(type, value); + } catch (Exception e) { + // Ignore this exception + return null; + } } /** @@ -87,6 +106,7 @@ public interface PropertyEnum extends ValueEnum { || type.isAssignableFrom(Byte.class) || type.isAssignableFrom(Double.class) || type.isAssignableFrom(Float.class) + || type.isAssignableFrom(Enum.class) ); } } diff --git a/src/main/java/cc/ryanc/halo/model/enums/QnYunProperties.java b/src/main/java/cc/ryanc/halo/model/enums/QnYunProperties.java new file mode 100644 index 000000000..07eda4139 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/enums/QnYunProperties.java @@ -0,0 +1,41 @@ +package cc.ryanc.halo.model.enums; + +/** + * Qi niu yun properties. + * + * @author johnniang + * @date 3/26/19 + */ +public enum QnYunProperties implements PropertyEnum { + + ZONE("qiniu_zone", String.class), + ACCESS_KEY("qiniu_access_key", String.class), + SECRET_KEY("qiniu_secret_key", String.class), + DOMAIN("qiniu_domain", String.class), + BUCKET("qiniu_bucket", String.class), + SMALL_URL("qiniu_small_url", String.class); + + private String value; + + private Class type; + + QnYunProperties(String value, Class type) { + if (!PropertyEnum.isSupportedType(type)) { + throw new IllegalArgumentException("Unsupported blog property type: " + type); + } + + this.value = value; + this.type = type; + } + + @Override + public String getValue() { + return value; + } + + @Override + public Class getType() { + return type; + } + +} diff --git a/src/main/java/cc/ryanc/halo/model/support/QiNiuPutSet.java b/src/main/java/cc/ryanc/halo/model/support/QiNiuPutSet.java index ba89b9a79..c49ebd635 100644 --- a/src/main/java/cc/ryanc/halo/model/support/QiNiuPutSet.java +++ b/src/main/java/cc/ryanc/halo/model/support/QiNiuPutSet.java @@ -13,6 +13,15 @@ import lombok.Data; @Data public class QiNiuPutSet { + /** + * 文件hash值 + */ + public String hash; + /** + * 文件名 + */ + public String key; + /** * 图片大小 */ @@ -21,10 +30,10 @@ public class QiNiuPutSet { /** * 长 */ - private Integer w; + private Integer width; /** * 宽 */ - private Integer h; + private Integer height; } diff --git a/src/main/java/cc/ryanc/halo/service/FileService.java b/src/main/java/cc/ryanc/halo/service/FileService.java index 18f786012..40d9bd37a 100644 --- a/src/main/java/cc/ryanc/halo/service/FileService.java +++ b/src/main/java/cc/ryanc/halo/service/FileService.java @@ -35,4 +35,22 @@ public interface FileService { */ @NonNull UploadResult uploadToLocal(@NonNull MultipartFile file); + + /** + * Uploads file to qi niu yun. + * + * @param file multipart file must not be null + * @return upload result + */ + @NonNull + UploadResult uploadToQnYun(@NonNull MultipartFile file); + + /** + * Uploads file to you pai yun. + * + * @param file multipart file must not be null + * @return upload result + */ + @NonNull + UploadResult uploadToYpYun(@NonNull MultipartFile file); } diff --git a/src/main/java/cc/ryanc/halo/service/OptionService.java b/src/main/java/cc/ryanc/halo/service/OptionService.java index d0280cfb8..a34184a12 100755 --- a/src/main/java/cc/ryanc/halo/service/OptionService.java +++ b/src/main/java/cc/ryanc/halo/service/OptionService.java @@ -3,7 +3,6 @@ package cc.ryanc.halo.service; import cc.ryanc.halo.exception.MissingPropertyValueException; import cc.ryanc.halo.model.dto.OptionOutputDTO; import cc.ryanc.halo.model.entity.Option; -import cc.ryanc.halo.model.enums.BlogProperties; import cc.ryanc.halo.model.enums.PropertyEnum; import cc.ryanc.halo.model.params.OptionParam; import cc.ryanc.halo.service.base.CrudService; @@ -60,7 +59,7 @@ public interface OptionService extends CrudService { * @param properties blog properties * @param source source */ - void saveProperties(@NonNull Map, String> properties, String source); + void saveProperties(@NonNull Map properties, String source); /** * Get all options @@ -110,7 +109,7 @@ public interface OptionService extends CrudService { * @return an option value */ @Nullable - String getByPropertyOfNullable(@NonNull PropertyEnum property); + String getByPropertyOfNullable(@NonNull PropertyEnum property); /** * Gets option value by blog property. @@ -120,7 +119,7 @@ public interface OptionService extends CrudService { * @throws MissingPropertyValueException throws when property value dismisses */ @NonNull - String getByPropertyOfNonNull(@NonNull PropertyEnum property); + String getByPropertyOfNonNull(@NonNull PropertyEnum property); /** * Gets option value by blog property. @@ -129,7 +128,7 @@ public interface OptionService extends CrudService { * @return an optional option value */ @NonNull - Optional getByProperty(@NonNull PropertyEnum property); + Optional getByProperty(@NonNull PropertyEnum property); /** * Gets property value by blog property. @@ -140,7 +139,7 @@ public interface OptionService extends CrudService { * @param property type * @return property value */ - T getByProperty(@NonNull PropertyEnum property, @NonNull Class propertyType, T defaultValue); + T getByProperty(@NonNull PropertyEnum property, @NonNull Class propertyType, T defaultValue); /** * Gets property value by blog property. @@ -150,7 +149,7 @@ public interface OptionService extends CrudService { * @param property type * @return property value */ - Optional getByProperty(@NonNull PropertyEnum property, @NonNull Class propertyType); + Optional getByProperty(@NonNull PropertyEnum property, @NonNull Class propertyType); /** * Gets value by key. @@ -195,12 +194,12 @@ public interface OptionService extends CrudService { int getRssPageSize(); /** - * Get quniu zone. + * Get qi niu yun zone. * * @return qiniu zone */ @NonNull - Zone getQiniuZone(); + Zone getQnYunZone(); /** * Gets locale. diff --git a/src/main/java/cc/ryanc/halo/service/impl/FileServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/FileServiceImpl.java index 050ee0d2d..51f72b92d 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/FileServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/FileServiceImpl.java @@ -1,12 +1,24 @@ package cc.ryanc.halo.service.impl; import cc.ryanc.halo.config.properties.HaloProperties; +import cc.ryanc.halo.exception.FileUploadException; import cc.ryanc.halo.exception.ServiceException; +import cc.ryanc.halo.model.enums.QnYunProperties; +import cc.ryanc.halo.model.support.QiNiuPutSet; import cc.ryanc.halo.model.support.UploadResult; import cc.ryanc.halo.service.FileService; import cc.ryanc.halo.service.OptionService; import cc.ryanc.halo.utils.FilenameUtils; import cc.ryanc.halo.utils.HaloUtils; +import cc.ryanc.halo.utils.JsonUtils; +import com.qiniu.common.QiniuException; +import com.qiniu.common.Zone; +import com.qiniu.http.Response; +import com.qiniu.storage.Configuration; +import com.qiniu.storage.UploadManager; +import com.qiniu.storage.persistent.FileRecorder; +import com.qiniu.util.Auth; +import com.qiniu.util.StringMap; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; import org.apache.commons.lang3.StringUtils; @@ -26,6 +38,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Calendar; +import java.util.Objects; /** * File service implementation. @@ -58,10 +71,8 @@ public class FileServiceImpl implements FileService { /** * Check work directory. - * - * @throws URISyntaxException throws when work directory is not a uri */ - private void checkWorkDir() throws URISyntaxException { + private void checkWorkDir() { // Get work path Path workPath = Paths.get(workDir); @@ -131,7 +142,7 @@ public class FileServiceImpl implements FileService { uploadResult.setFilename(basename); uploadResult.setFilePath(subFilePath); uploadResult.setSuffix(extension); - uploadResult.setMediaType(MediaType.valueOf(file.getContentType())); + uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); uploadResult.setSize(file.getSize()); // Check file type @@ -165,6 +176,82 @@ public class FileServiceImpl implements FileService { } } + @Override + public UploadResult uploadToQnYun(MultipartFile file) { + Assert.notNull(file, "Multipart file must not be null"); + + // Get all config + Zone zone = optionService.getQnYunZone(); + String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.ACCESS_KEY); + String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.SECRET_KEY); + String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.BUCKET); + String domain = optionService.getByPropertyOfNonNull(QnYunProperties.DOMAIN); + String smallUrl = optionService.getByPropertyOfNullable(QnYunProperties.SMALL_URL); + + // Create configuration + Configuration configuration = new Configuration(zone); + + // Create auth + Auth auth = Auth.create(accessKey, secretKey); + // Build put plicy + StringMap putPolicy = new StringMap(); + putPolicy.put("returnBody", "{\"size\":$(fsize), " + + "\"width\":$(imageInfo.width), " + + "\"height\":$(imageInfo.height)," + + " \"key\":\"$(key)\", " + + "\"hash\":\"$(etag)\"}"); + // Get upload token + String uploadToken = auth.uploadToken(bucket, null, 3600, putPolicy); + + // Create temp path + Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir"), bucket); + + try { + // Get file recorder for temp directory + FileRecorder fileRecorder = new FileRecorder(tmpPath.toFile()); + // Get upload manager + UploadManager uploadManager = new UploadManager(configuration, fileRecorder); + // Put the file + // TODO May need to set key manually + Response response = uploadManager.put(file.getInputStream(), null, uploadToken, null, null); + + log.debug("QnYun response: [{}]", response.toString()); + log.debug("QnYun response body: [{}]", response.bodyString()); + + response.jsonToObject(QiNiuPutSet.class); + + // Convert response + QiNiuPutSet putSet = JsonUtils.jsonToObject(response.bodyString(), QiNiuPutSet.class); + + // Get file full path + String filePath = StringUtils.appendIfMissing(domain, "/") + putSet.getHash(); + + // Build upload result + UploadResult result = new UploadResult(); + result.setFilename(putSet.getHash()); + result.setFilePath(filePath); + result.setThumbPath(StringUtils.isBlank(smallUrl) ? filePath : filePath + smallUrl); + result.setSuffix(FilenameUtils.getExtension(file.getOriginalFilename())); + result.setWidth(putSet.getWidth()); + result.setHeight(putSet.getHeight()); + result.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); + + return result; + } catch (IOException e) { + if (e instanceof QiniuException) { + log.error("QnYun error response: [{}]", ((QiniuException) e).response); + } + + throw new FileUploadException("Failed to upload file " + file.getOriginalFilename() + " to QnYun", e); + } + } + + @Override + public UploadResult uploadToYpYun(MultipartFile file) { + Assert.notNull(file, "Multipart file must not be null"); + return null; + } + /** * Generates thumbnail image. * diff --git a/src/main/java/cc/ryanc/halo/service/impl/OptionServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/OptionServiceImpl.java index 1bb2ac00e..230ecefc9 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/OptionServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/OptionServiceImpl.java @@ -5,6 +5,7 @@ import cc.ryanc.halo.model.dto.OptionOutputDTO; import cc.ryanc.halo.model.entity.Option; import cc.ryanc.halo.model.enums.BlogProperties; import cc.ryanc.halo.model.enums.PropertyEnum; +import cc.ryanc.halo.model.enums.QnYunProperties; import cc.ryanc.halo.model.params.OptionParam; import cc.ryanc.halo.repository.OptionRepository; import cc.ryanc.halo.service.OptionService; @@ -86,8 +87,6 @@ public class OptionServiceImpl extends AbstractCrudService impl return; } - // (Not recommended) Don't write "this::save" here - // Types of key and value are String options.forEach((key, value) -> save(key, value, source)); } @@ -102,7 +101,7 @@ public class OptionServiceImpl extends AbstractCrudService impl } @Override - public void saveProperties(Map, String> properties, String source) { + public void saveProperties(Map properties, String source) { if (CollectionUtils.isEmpty(properties)) { return; } @@ -151,33 +150,33 @@ public class OptionServiceImpl extends AbstractCrudService impl } @Override - public String getByPropertyOfNullable(PropertyEnum property) { + public String getByPropertyOfNullable(PropertyEnum property) { return getByProperty(property).orElse(null); } @Override - public String getByPropertyOfNonNull(PropertyEnum property) { + public String getByPropertyOfNonNull(PropertyEnum property) { Assert.notNull(property, "Blog property must not be null"); return getByKeyOfNonNull(property.getValue()); } @Override - public Optional getByProperty(PropertyEnum property) { + public Optional getByProperty(PropertyEnum property) { Assert.notNull(property, "Blog property must not be null"); return getByKey(property.getValue()); } @Override - public T getByProperty(PropertyEnum property, Class propertyType, T defaultValue) { + public T getByProperty(PropertyEnum property, Class propertyType, T defaultValue) { Assert.notNull(property, "Blog property must not be null"); return getByProperty(property, propertyType).orElse(defaultValue); } @Override - public Optional getByProperty(PropertyEnum property, Class propertyType) { + public Optional getByProperty(PropertyEnum property, Class propertyType) { return getByProperty(property).map(propertyValue -> PropertyEnum.convertTo(propertyValue, propertyType)); } @@ -222,8 +221,8 @@ public class OptionServiceImpl extends AbstractCrudService impl } @Override - public Zone getQiniuZone() { - return getByProperty(BlogProperties.QINIU_ZONE).map(qiniuZone -> { + public Zone getQnYunZone() { + return getByProperty(QnYunProperties.ZONE).map(qiniuZone -> { Zone zone; switch (qiniuZone) { @@ -262,5 +261,4 @@ public class OptionServiceImpl extends AbstractCrudService impl }).orElseGet(Locale::getDefault); } - } diff --git a/src/main/java/cc/ryanc/halo/web/controller/core/InstallController.java b/src/main/java/cc/ryanc/halo/web/controller/core/InstallController.java index 5e3d6ee62..112356db4 100644 --- a/src/main/java/cc/ryanc/halo/web/controller/core/InstallController.java +++ b/src/main/java/cc/ryanc/halo/web/controller/core/InstallController.java @@ -173,7 +173,7 @@ public class InstallController { properties.put(BlogProperties.NEW_COMMENT_NOTICE, Boolean.FALSE.toString()); properties.put(BlogProperties.COMMENT_PASS_NOTICE, Boolean.FALSE.toString()); properties.put(BlogProperties.COMMENT_REPLY_NOTICE, Boolean.FALSE.toString()); - properties.put(BlogProperties.ATTACH_LOC, AttachmentType.SERVER.getValue().toString()); + properties.put(BlogProperties.ATTACHMENT_TYPE, AttachmentType.SERVER.getValue().toString()); // Create properties optionService.saveProperties(properties, "system"); @@ -261,7 +261,7 @@ public class InstallController { //// options.put(BlogProperties.NEW_COMMENT_NOTICE, TrueFalseEnum.FALSE.getDesc()); //// options.put(BlogProperties.COMMENT_PASS_NOTICE, TrueFalseEnum.FALSE.getDesc()); //// options.put(BlogProperties.COMMENT_REPLY_NOTICE, TrueFalseEnum.FALSE.getDesc()); -//// options.put(BlogProperties.ATTACH_LOC, AttachLocationEnum.SERVER.getDesc()); +//// options.put(BlogProperties.ATTACHMENT_TYPE, AttachLocationEnum.SERVER.getDesc()); //// optionService.saveOptions(options); // // //更新日志 diff --git a/src/test/java/cc/ryanc/halo/service/impl/OptionServiceImplTest.java b/src/test/java/cc/ryanc/halo/service/impl/OptionServiceImplTest.java index 8dcad5ff9..d40cefb03 100644 --- a/src/test/java/cc/ryanc/halo/service/impl/OptionServiceImplTest.java +++ b/src/test/java/cc/ryanc/halo/service/impl/OptionServiceImplTest.java @@ -1,7 +1,7 @@ package cc.ryanc.halo.service.impl; import cc.ryanc.halo.model.entity.Option; -import cc.ryanc.halo.model.enums.BlogProperties; +import cc.ryanc.halo.model.enums.QnYunProperties; import cc.ryanc.halo.repository.OptionRepository; import com.qiniu.common.Zone; import org.junit.Test; @@ -73,13 +73,13 @@ public class OptionServiceImplTest { } private void getQiniuZoneTest(Zone actualZone, Option option) { - BlogProperties zoneProperty = BlogProperties.QINIU_ZONE; + QnYunProperties zoneProperty = QnYunProperties.ZONE; // Given given(optionRepository.findByOptionKey(zoneProperty.getValue())).willReturn(Optional.ofNullable(option)); // When - Zone zone = optionService.getQiniuZone(); + Zone zone = optionService.getQnYunZone(); // Then then(optionRepository).should().findByOptionKey(zoneProperty.getValue());