Complete Smms file handler

pull/137/head
johnniang 2019-03-29 21:39:35 +08:00
parent fd59393634
commit 22d04a554b
12 changed files with 336 additions and 65 deletions

12
pom.xml
View File

@ -48,6 +48,7 @@
<thumbnailator.version>0.4.8</thumbnailator.version>
<commonmark.version>0.12.1</commonmark.version>
<commons-lang3.version>3.8.1</commons-lang3.version>
<httpclient.version>4.5.7</httpclient.version>
</properties>
<dependencies>
@ -140,11 +141,6 @@
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
@ -225,6 +221,12 @@
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
</dependencies>
<build>

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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 {
/**

View File

@ -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;

View File

@ -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<String, Object> 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<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(body, headers);
// Upload file
ResponseEntity<SmmsResponse> 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<String> 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;
}
}

View File

@ -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;

View File

@ -21,7 +21,12 @@ public enum AttachmentType implements ValueEnum<Integer> {
/**
*
*/
QNYUN(2);
QNYUN(2),
/**
* sm.ms
*/
SMMS(3);
private Integer value;

View File

@ -60,6 +60,8 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
AttachmentType attachmentType = getAttachmentType();
log.debug("Starting uploading... type: [{}], file: [{}]", attachmentType, file.getOriginalFilename());
// Upload file
UploadResult uploadResult = fileHandlers.upload(file, attachmentType);
@ -105,6 +107,6 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
*/
@NonNull
private AttachmentType getAttachmentType() {
return optionService.getEnumByPropertyOrDefault(BlogProperties.ATTACHMENT_TYPE, AttachmentType.class, AttachmentType.LOCAL);
return optionService.getValueEnumByPropertyOrDefault(BlogProperties.ATTACHMENT_TYPE, Integer.class, AttachmentType.class, AttachmentType.LOCAL);
}
}

View File

@ -84,7 +84,7 @@ public class HaloUtils {
Assert.hasText(pluralLabel, "Plural label must not be blank");
if (times <= 0) {
return "no " + label;
return "no " + pluralLabel;
}
if (times == 1) {
@ -219,7 +219,6 @@ public class HaloUtils {
public static Date getCreateTime(String srcPath) {
try {
BasicFileAttributes basicFileAttributes = Files.readAttributes(Paths.get(srcPath), BasicFileAttributes.class);
basicFileAttributes.creationTime().toMillis();
return new Date(basicFileAttributes.creationTime().toMillis());
} catch (IOException e) {
throw new RuntimeException("Failed to open the " + srcPath + " file", e);
@ -271,23 +270,6 @@ public class HaloUtils {
}
}
/**
*
*
* @param smtpHost smtpHost
* @param userName
* @param password password
*/
public static void configMail(String smtpHost, String userName, String password) {
Assert.hasText(smtpHost, "SMTP host config must not be blank");
Assert.hasText(userName, "Email username must not be blank");
Assert.hasText(password, "Email password must not be blank");
final Properties properties = OhMyEmail.defaultConfig(false);
properties.setProperty("mail.smtp.host", smtpHost);
OhMyEmail.config(properties, userName, password);
}
/**
*
*

View File

@ -0,0 +1,91 @@
package cc.ryanc.halo.utils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.lang.NonNull;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
/**
* Http client utilities.
*
* @author johnniang
* @date 3/29/19
*/
public class HttpClientUtils {
/**
* Timeout (Default is 5s).
*/
private final static int TIMEOUT = 5000;
private HttpClientUtils() {
}
/**
* Creates https client.
*
* @param timeout connection timeout (ms)
* @return https client
* @throws KeyStoreException key store exception
* @throws NoSuchAlgorithmException no such algorithm exception
* @throws KeyManagementException key management exception
*/
@NonNull
public static CloseableHttpClient createHttpsClient(int timeout) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
// TODO Set key store in production environment
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, (certificate, authType) -> 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;
}
}
}

View File

@ -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: