mirror of https://github.com/halo-dev/halo
Complete Smms file handler
parent
fd59393634
commit
22d04a554b
12
pom.xml
12
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -21,7 +21,12 @@ public enum AttachmentType implements ValueEnum<Integer> {
|
|||
/**
|
||||
* 七牛云
|
||||
*/
|
||||
QNYUN(2);
|
||||
QNYUN(2),
|
||||
|
||||
/**
|
||||
* sm.ms
|
||||
*/
|
||||
SMMS(3);
|
||||
|
||||
private Integer value;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 百度主动推送
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue