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: