From 22d04a554bceb13b34175e46b0599846be873e50 Mon Sep 17 00:00:00 2001 From: johnniang Date: Fri, 29 Mar 2019 21:39:35 +0800 Subject: [PATCH] Complete Smms file handler --- pom.xml | 12 +- .../ryanc/halo/config/HaloConfiguration.java | 24 +++ .../file/FileHandlerConfiguration.java | 35 ---- .../halo/handler/file/LocalFileHandler.java | 2 + .../halo/handler/file/QnYunFileHandler.java | 2 + .../halo/handler/file/SmmsFileHandler.java | 194 ++++++++++++++++++ .../halo/handler/file/UpYunFileHandler.java | 2 + .../halo/model/enums/AttachmentType.java | 7 +- .../service/impl/AttachmentServiceImpl.java | 4 +- .../java/cc/ryanc/halo/utils/HaloUtils.java | 20 +- .../cc/ryanc/halo/utils/HttpClientUtils.java | 91 ++++++++ src/main/resources/application.yaml | 8 +- 12 files changed, 336 insertions(+), 65 deletions(-) delete mode 100644 src/main/java/cc/ryanc/halo/handler/file/FileHandlerConfiguration.java create mode 100644 src/main/java/cc/ryanc/halo/handler/file/SmmsFileHandler.java create mode 100644 src/main/java/cc/ryanc/halo/utils/HttpClientUtils.java diff --git a/pom.xml b/pom.xml index edb5f237b..4217d0ed9 100755 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ 0.4.8 0.12.1 3.8.1 + 4.5.7 @@ -140,11 +141,6 @@ hutool-core ${hutool.version} - - cn.hutool - hutool-http - ${hutool.version} - cn.hutool hutool-crypto @@ -225,6 +221,12 @@ ${commons-lang3.version} + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + diff --git a/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java b/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java index 589c93c7c..20bd9c6cd 100644 --- a/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java +++ b/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java @@ -10,13 +10,22 @@ import cc.ryanc.halo.security.filter.ApiAuthenticationFilter; import cc.ryanc.halo.security.handler.AdminAuthenticationFailureHandler; import cc.ryanc.halo.security.handler.DefaultAuthenticationFailureHandler; import cc.ryanc.halo.service.UserService; +import cc.ryanc.halo.utils.HttpClientUtils; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.web.client.RestTemplate; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; /** * Halo configuration. @@ -27,6 +36,21 @@ import org.springframework.core.Ordered; @EnableConfigurationProperties(HaloProperties.class) public class HaloConfiguration { + private final static int TIMEOUT = 5000; + + @Bean + public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { + builder.failOnEmptyBeans(false); + return builder.build(); + } + + @Bean + public RestTemplate httpsRestTemplate(RestTemplateBuilder builder) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + RestTemplate httpsRestTemplate = builder.build(); + httpsRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(TIMEOUT))); + return httpsRestTemplate; + } + @Bean @ConditionalOnMissingBean public StringCacheStore stringCacheStore() { diff --git a/src/main/java/cc/ryanc/halo/handler/file/FileHandlerConfiguration.java b/src/main/java/cc/ryanc/halo/handler/file/FileHandlerConfiguration.java deleted file mode 100644 index af8527b0a..000000000 --- a/src/main/java/cc/ryanc/halo/handler/file/FileHandlerConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -package cc.ryanc.halo.handler.file; - -import cc.ryanc.halo.config.properties.HaloProperties; -import cc.ryanc.halo.service.OptionService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * File handler configuration. - * - * @author johnniang - * @date 3/27/19 - */ -@Configuration -public class FileHandlerConfiguration { - - @Bean - @ConditionalOnMissingBean - FileHandler localFileHandler(OptionService optionService, HaloProperties haloProperties) { - return new LocalFileHandler(optionService, haloProperties); - } - - @Bean - @ConditionalOnMissingBean - FileHandler qnYunFileHandler(OptionService optionService) { - return new QnYunFileHandler(optionService); - } - - @Bean - @ConditionalOnMissingBean - FileHandler upYunFileHandler(OptionService optionService) { - return new UpYunFileHandler(optionService); - } -} diff --git a/src/main/java/cc/ryanc/halo/handler/file/LocalFileHandler.java b/src/main/java/cc/ryanc/halo/handler/file/LocalFileHandler.java index 5b397596d..595dc9ee1 100644 --- a/src/main/java/cc/ryanc/halo/handler/file/LocalFileHandler.java +++ b/src/main/java/cc/ryanc/halo/handler/file/LocalFileHandler.java @@ -12,6 +12,7 @@ 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; @@ -32,6 +33,7 @@ import java.util.Objects; * @date 3/27/19 */ @Slf4j +@Component public class LocalFileHandler implements FileHandler { /** diff --git a/src/main/java/cc/ryanc/halo/handler/file/QnYunFileHandler.java b/src/main/java/cc/ryanc/halo/handler/file/QnYunFileHandler.java index 5dc62143d..523390edd 100644 --- a/src/main/java/cc/ryanc/halo/handler/file/QnYunFileHandler.java +++ b/src/main/java/cc/ryanc/halo/handler/file/QnYunFileHandler.java @@ -20,6 +20,7 @@ import com.qiniu.util.StringMap; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartFile; @@ -37,6 +38,7 @@ import static cc.ryanc.halo.handler.file.FileHandler.isImageType; * @date 3/27/19 */ @Slf4j +@Component public class QnYunFileHandler implements FileHandler { private final OptionService optionService; diff --git a/src/main/java/cc/ryanc/halo/handler/file/SmmsFileHandler.java b/src/main/java/cc/ryanc/halo/handler/file/SmmsFileHandler.java new file mode 100644 index 000000000..6fc4a3f5a --- /dev/null +++ b/src/main/java/cc/ryanc/halo/handler/file/SmmsFileHandler.java @@ -0,0 +1,194 @@ +package cc.ryanc.halo.handler.file; + +import cc.ryanc.halo.exception.FileOperationException; +import cc.ryanc.halo.model.enums.AttachmentType; +import cc.ryanc.halo.model.support.UploadResult; +import cc.ryanc.halo.utils.FilenameUtils; +import cc.ryanc.halo.utils.HttpClientUtils; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Objects; + +/** + * Sm.ms file handler. + * + * @author johnniang + * @date 3/29/19 + */ +@Slf4j +@Component +public class SmmsFileHandler implements FileHandler { + + private final static String UPLOAD_API = "https://sm.ms/api/upload"; + + private final static String DELETE_API = "https://sm.ms/api/delete/%s"; + + private final static String SUCCESS_CODE = "success"; + + private final static String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"; + + private final RestTemplate httpsRestTemplate; + + public SmmsFileHandler(RestTemplate httpsRestTemplate) { + this.httpsRestTemplate = httpsRestTemplate; + } + + @Override + public UploadResult upload(MultipartFile file) { + Assert.notNull(file, "Multipart file must not be null"); + + if (!FileHandler.isImageType(file.getContentType())) { + log.error("Invalid extension: [{}]", file.getContentType()); + throw new FileOperationException("Invalid extension for file " + file.getOriginalFilename() + ". Only \"jpeg, jpg, png, gif, bmp\" files are supported"); + } + + HttpHeaders headers = new HttpHeaders(); + // Set content type + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + // Set user agent manually + headers.set(HttpHeaders.USER_AGENT, DEFAULT_USER_AGENT); + + LinkedMultiValueMap body = new LinkedMultiValueMap<>(); + + try { + body.add("smfile", new HttpClientUtils.MultipartFileResource(file.getBytes(), file.getOriginalFilename())); + } catch (IOException e) { + log.error("Failed to get file input stream", e); + throw new FileOperationException("Failed to upload " + file.getOriginalFilename() + " file", e); + } + + body.add("ssl", false); + body.add("format", "json"); + + HttpEntity> httpEntity = new HttpEntity<>(body, headers); + + // Upload file + ResponseEntity mapResponseEntity = httpsRestTemplate.postForEntity(UPLOAD_API, httpEntity, SmmsResponse.class); + + // Check status + if (mapResponseEntity.getStatusCode().isError()) { + log.error("Server response detail: [{}]", mapResponseEntity.toString()); + throw new FileOperationException("Smms server response error. status: " + mapResponseEntity.getStatusCodeValue()); + } + + // Get smms response + SmmsResponse smmsResponse = mapResponseEntity.getBody(); + + // Check error + if (!isResponseSuccessfully(smmsResponse)) { + log.error("Smms response detail: [{}]", smmsResponse); + throw new FileOperationException(smmsResponse.getMsg()).setErrorData(smmsResponse); + } + + // Get response data + SmmsResponseData data = smmsResponse.getData(); + + // Build result + UploadResult result = new UploadResult(); + result.setFilename(FilenameUtils.getBasename(file.getOriginalFilename())); + result.setSuffix(FilenameUtils.getExtension(file.getOriginalFilename())); + result.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); + + result.setFilePath(data.getUrl()); + result.setThumbPath(data.getUrl()); + result.setKey(data.getHash()); + result.setWidth(data.getWidth()); + result.setHeight(data.getHeight()); + result.setSize(data.getSize().longValue()); + + log.info("File: [{}] uploaded successfully", file.getOriginalFilename()); + + return result; + } + + @Override + public void delete(String key) { + Assert.hasText(key, "Deleting key must not be blank"); + + // Build delete url + String url = String.format(DELETE_API, key); + + // Set user agent manually + HttpHeaders headers = new HttpHeaders(); + headers.set(HttpHeaders.USER_AGENT, DEFAULT_USER_AGENT); + + // Delete the file + ResponseEntity responseEntity = httpsRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, headers), String.class); + + if (responseEntity.getStatusCode().isError()) { + log.debug("Smms server response error: [{}]", responseEntity.toString()); + throw new FileOperationException("Smms server response error"); + } + + log.debug("Smms response detail: [{}]", responseEntity.getBody()); + + // Deleted successfully or have been deleted already + log.info("File was deleted successfully or had been deleted already"); + } + + @Override + public boolean supportType(AttachmentType type) { + return AttachmentType.SMMS.equals(type); + } + + /** + * Check if the response is response successfully or not. + * + * @param smmsResponse smms response must not be null + * @return true if response successfully; false otherwise + */ + private boolean isResponseSuccessfully(@NonNull SmmsResponse smmsResponse) { + Assert.notNull(smmsResponse, "Smms response must not be null"); + + return smmsResponse.getCode().equals(SUCCESS_CODE); + } + + @Data + @ToString + @NoArgsConstructor + static class SmmsResponse { + + private String code; + + private String msg; + + private SmmsResponseData data; + + } + + @Data + @ToString(callSuper = true) + @NoArgsConstructor + static class SmmsResponseData { + + private String filename; + + private String storename; + + private Integer size; + + private Integer width; + + private Integer height; + + private String hash; + + private String delete; + + private String url; + + private String path; + + } +} diff --git a/src/main/java/cc/ryanc/halo/handler/file/UpYunFileHandler.java b/src/main/java/cc/ryanc/halo/handler/file/UpYunFileHandler.java index a2d7acbf3..47b19446f 100644 --- a/src/main/java/cc/ryanc/halo/handler/file/UpYunFileHandler.java +++ b/src/main/java/cc/ryanc/halo/handler/file/UpYunFileHandler.java @@ -10,6 +10,7 @@ import com.UpYun; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.DigestUtils; import org.springframework.web.multipart.MultipartFile; @@ -25,6 +26,7 @@ import java.util.Objects; * @date 3/27/19 */ @Slf4j +@Component public class UpYunFileHandler implements FileHandler { private final OptionService optionService; diff --git a/src/main/java/cc/ryanc/halo/model/enums/AttachmentType.java b/src/main/java/cc/ryanc/halo/model/enums/AttachmentType.java index b889ac3bd..9659b52f1 100644 --- a/src/main/java/cc/ryanc/halo/model/enums/AttachmentType.java +++ b/src/main/java/cc/ryanc/halo/model/enums/AttachmentType.java @@ -21,7 +21,12 @@ public enum AttachmentType implements ValueEnum { /** * 七牛云 */ - QNYUN(2); + QNYUN(2), + + /** + * sm.ms + */ + SMMS(3); private Integer value; diff --git a/src/main/java/cc/ryanc/halo/service/impl/AttachmentServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/AttachmentServiceImpl.java index f3cb359aa..84ee5c492 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/AttachmentServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/AttachmentServiceImpl.java @@ -60,6 +60,8 @@ public class AttachmentServiceImpl extends AbstractCrudService true) + .build(); + + return HttpClients.custom() + .setSSLContext(sslContext) + .setSSLHostnameVerifier(new NoopHostnameVerifier()) + .setDefaultRequestConfig(getReqeustConfig(timeout)) + .build(); + } + + /** + * Gets request config. + * + * @param timeout connection timeout (ms) + * @return request config + */ + private static RequestConfig getReqeustConfig(int timeout) { + return RequestConfig.custom() + .setConnectTimeout(timeout) + .setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout) + .build(); + } + + + /** + * Multipart file resource. + * + * @author johnniang + */ + public static class MultipartFileResource extends ByteArrayResource { + + private final String filename; + + public MultipartFileResource(byte[] buf, String filename) { + super(buf); + this.filename = filename; + } + + @Override + public String getFilename() { + return this.filename; + } + + } + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c34abb3cc..02a6de456 100755 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -25,10 +25,10 @@ spring: password: 123456 #MySql配置 -# driver-class-name: com.mysql.cj.jdbc.Driver -# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai -# username: root -# password: 123456 + # driver-class-name: com.mysql.cj.jdbc.Driver + # url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai + # username: root + # password: 123456 h2: console: