feat: provide static mapping feature (#710)

* Provide static mapping feature

* feat: filter inner mapping for static storage.
pull/711/head
Ryan Wang 2020-03-24 22:48:58 +08:00 committed by GitHub
parent 2792d11cd2
commit bc75275815
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 173 additions and 74 deletions

View File

@ -34,7 +34,7 @@ public class InMemoryCacheStore extends AbstractStringCacheStore {
/**
* Lock.
*/
private Lock lock = new ReentrantLock();
private final Lock lock = new ReentrantLock();
public InMemoryCacheStore() {
// Run a cache store cleaner

View File

@ -0,0 +1,96 @@
package run.halo.app.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.event.StaticStorageChangedEvent;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import static run.halo.app.utils.HaloUtils.URL_SEPARATOR;
import static run.halo.app.utils.HaloUtils.ensureBoth;
/**
* @author ryanwang
* @date 2020-03-24
*/
@Slf4j
public class HaloRequestMappingHandlerMapping extends RequestMappingHandlerMapping implements ApplicationListener<StaticStorageChangedEvent> {
private final Set<String> blackPatterns = new HashSet<>(16);
private final PathMatcher pathMatcher;
private final HaloProperties haloProperties;
public HaloRequestMappingHandlerMapping(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
this.initBlackPatterns();
pathMatcher = new AntPathMatcher();
}
@Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
log.debug("Looking path: [{}]", lookupPath);
for (String blackPattern : blackPatterns) {
if (this.pathMatcher.match(blackPattern, lookupPath)) {
log.debug("Skipped path [{}] with pattern: [{}]", lookupPath, blackPattern);
return null;
}
}
return super.lookupHandlerMethod(lookupPath, request);
}
private void initBlackPatterns() {
String uploadUrlPattern = ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
String adminPathPattern = ensureBoth(haloProperties.getAdminPath(), URL_SEPARATOR) + "?*/**";
blackPatterns.add("/themes/**");
blackPatterns.add("/js/**");
blackPatterns.add("/images/**");
blackPatterns.add("/fonts/**");
blackPatterns.add("/css/**");
blackPatterns.add("/assets/**");
blackPatterns.add("/color.less");
blackPatterns.add("/swagger-ui.html");
blackPatterns.add("/csrf");
blackPatterns.add("/webjars/**");
blackPatterns.add(uploadUrlPattern);
blackPatterns.add(adminPathPattern);
}
@Override
public void onApplicationEvent(StaticStorageChangedEvent event) {
Path staticPath = event.getStaticPath();
try (Stream<Path> rootPathStream = Files.list(staticPath)) {
synchronized (this) {
blackPatterns.clear();
initBlackPatterns();
rootPathStream.forEach(rootPath -> {
if (Files.isDirectory(rootPath)) {
String directoryPattern = "/" + rootPath.getFileName().toString() + "/**";
blackPatterns.add(directoryPattern);
log.debug("Exclude for folder path pattern: [{}]", directoryPattern);
} else {
String pathPattern = "/" + rootPath.getFileName().toString();
blackPatterns.add(pathPattern);
log.debug("Exclude for file path pattern: [{}]", pathPattern);
}
}
);
}
} catch (IOException e) {
log.error("Failed to refresh static directory mapping", e);
}
}
}

View File

@ -17,9 +17,6 @@ import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
@ -33,12 +30,9 @@ import run.halo.app.factory.StringToEnumConverterFactory;
import run.halo.app.model.support.HaloConst;
import run.halo.app.security.resolver.AuthenticationArgumentResolver;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
import static run.halo.app.utils.HaloUtils.*;
@ -188,48 +182,4 @@ public class WebMvcAutoConfiguration extends WebMvcConfigurationSupport {
return new HaloRequestMappingHandlerMapping(haloProperties);
}
private static class HaloRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final Set<String> blackPatterns = new HashSet<>(16);
private final PathMatcher pathMatcher;
private final HaloProperties haloProperties;
public HaloRequestMappingHandlerMapping(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
this.initBlackPatterns();
pathMatcher = new AntPathMatcher();
}
@Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
log.debug("Looking path: [{}]", lookupPath);
for (String blackPattern : blackPatterns) {
if (this.pathMatcher.match(blackPattern, lookupPath)) {
log.debug("Skipped path [{}] with pattern: [{}]", lookupPath, blackPattern);
return null;
}
}
return super.lookupHandlerMethod(lookupPath, request);
}
private void initBlackPatterns() {
String uploadUrlPattern = ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
String adminPathPattern = ensureBoth(haloProperties.getAdminPath(), URL_SEPARATOR) + "?*/**";
blackPatterns.add("/themes/**");
blackPatterns.add("/js/**");
blackPatterns.add("/images/**");
blackPatterns.add("/fonts/**");
blackPatterns.add("/css/**");
blackPatterns.add("/assets/**");
blackPatterns.add("/color.less");
blackPatterns.add("/swagger-ui.html");
blackPatterns.add("/csrf");
blackPatterns.add("/webjars/**");
blackPatterns.add(uploadUrlPattern);
blackPatterns.add(adminPathPattern);
}
}
}

View File

@ -47,6 +47,6 @@ public class StaticStorageController {
@ApiOperation("Uploads static file")
public void upload(String basePath,
@RequestPart("file") MultipartFile file) {
staticStorageService.update(basePath, file);
staticStorageService.upload(basePath, file);
}
}

View File

@ -0,0 +1,27 @@
package run.halo.app.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.nio.file.Path;
/**
* @author ryanwang
* @date 2020-03-24
*/
public class StaticStorageChangedEvent extends ApplicationEvent {
@Getter
private final Path staticPath;
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public StaticStorageChangedEvent(Object source, Path staticPath) {
super(source);
this.staticPath = staticPath;
}
}

View File

@ -20,7 +20,7 @@ public class StringToEnumConverterFactory implements ConverterFactory<String, En
private static class StringToEnumConverter<T extends Enum>
implements Converter<String, T> {
private Class<T> enumType;
private final Class<T> enumType;
private StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;

View File

@ -54,7 +54,7 @@ public class SmmsFileHandler implements FileHandler {
private final OptionService optionService;
private HttpHeaders headers = new HttpHeaders();
private final HttpHeaders headers = new HttpHeaders();
public SmmsFileHandler(RestTemplate httpsRestTemplate,
OptionService optionService) {

View File

@ -43,7 +43,7 @@ public enum AttachmentType implements ValueEnum<Integer> {
*/
TENCENTCOS(6);
private Integer value;
private final Integer value;
AttachmentType(Integer value) {
this.value = value;

View File

@ -15,7 +15,7 @@ public enum BanStatusEnum {
*/
NORMAL(0);
private int status;
private final int status;
BanStatusEnum(int status) {
this.status = status;

View File

@ -19,7 +19,7 @@ public enum CommentViolationTypeEnum {
*/
FREQUENTLY(1);
private int type;
private final int type;
CommentViolationTypeEnum(int type) {
this.type = type;

View File

@ -23,7 +23,7 @@ public enum DataType implements ValueEnum<Integer> {
BOOL(3);
private Integer value;
private final Integer value;
DataType(Integer value) {
this.value = value;

View File

@ -18,7 +18,7 @@ public enum GlobalPathType implements ValueEnum<Integer> {
*/
ABSOLUTE(1);
private Integer value;
private final Integer value;
GlobalPathType(Integer value) {
this.value = value;

View File

@ -23,7 +23,7 @@ public enum MigrateType implements ValueEnum<Integer> {
*/
CNBLOGS(2);
private Integer value;
private final Integer value;
MigrateType(Integer value) {
this.value = value;

View File

@ -18,7 +18,7 @@ public enum OptionType implements ValueEnum<Integer> {
*/
CUSTOM(1);
private Integer value;
private final Integer value;
OptionType(Integer value) {
this.value = value;

View File

@ -16,7 +16,7 @@ public enum PostEditorType implements ValueEnum<Integer> {
*/
RICHTEXT(1);
private Integer value;
private final Integer value;
PostEditorType(Integer value) {
this.value = value;

View File

@ -28,7 +28,7 @@ public enum PostPermalinkType implements ValueEnum<Integer> {
*/
ID(3);
private Integer value;
private final Integer value;
PostPermalinkType(Integer value) {
this.value = value;

View File

@ -18,7 +18,7 @@ public enum StaticDeployType implements ValueEnum<Integer> {
*/
NETLIFY(1);
private Integer value;
private final Integer value;
StaticDeployType(Integer value) {
this.value = value;

View File

@ -52,8 +52,8 @@ public enum UpOssProperties implements PropertyEnum {
OSS_THUMBNAIL_STYLE_RULE("oss_upyun_thumbnail_style_rule", String.class, "");
private final String defaultValue;
private String value;
private Class<?> type;
private final String value;
private final Class<?> type;
UpOssProperties(String value, Class<?> type, String defaultValue) {
this.defaultValue = defaultValue;

View File

@ -46,7 +46,7 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
protected final OptionService optionService;
protected final AbstractStringCacheStore cacheStore;
private final UrlPathHelper urlPathHelper = new UrlPathHelper();
private OneTimeTokenService oneTimeTokenService;
private final OneTimeTokenService oneTimeTokenService;
private volatile AuthenticationFailureHandler failureHandler;
/**

View File

@ -14,6 +14,8 @@ import java.util.List;
*/
public interface StaticStorageService {
String API_FOLDER_NAME = "api";
/**
* Static folder location.
*/
@ -47,5 +49,5 @@ public interface StaticStorageService {
* @param basePath base path
* @param file file must not be null.
*/
void update(String basePath, @NonNull MultipartFile file);
void upload(String basePath, @NonNull MultipartFile file);
}

View File

@ -54,7 +54,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
private final AbstractStringCacheStore cacheStore;
private final Map<String, PropertyEnum> propertyEnumMap;
private final ApplicationEventPublisher eventPublisher;
private HaloProperties haloProperties;
private final HaloProperties haloProperties;
public OptionServiceImpl(HaloProperties haloProperties,
OptionRepository optionRepository,

View File

@ -3,12 +3,16 @@ package run.halo.app.service.impl;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.event.StaticStorageChangedEvent;
import run.halo.app.exception.FileOperationException;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.support.StaticFile;
@ -32,16 +36,17 @@ import java.util.stream.Stream;
*/
@Service
@Slf4j
public class StaticStorageServiceImpl implements StaticStorageService {
public class StaticStorageServiceImpl implements StaticStorageService, ApplicationListener<ApplicationStartedEvent> {
private final Path staticDir;
private final HaloProperties haloProperties;
private final ApplicationEventPublisher eventPublisher;
public StaticStorageServiceImpl(HaloProperties haloProperties) throws IOException {
public StaticStorageServiceImpl(HaloProperties haloProperties,
ApplicationEventPublisher eventPublisher) throws IOException {
staticDir = Paths.get(haloProperties.getWorkDir(), STATIC_FOLDER);
this.eventPublisher = eventPublisher;
FileUtils.createIfAbsent(staticDir);
this.haloProperties = haloProperties;
}
@Override
@ -100,6 +105,7 @@ public class StaticStorageServiceImpl implements StaticStorageService {
} else {
Files.deleteIfExists(path);
}
onChange();
} catch (IOException e) {
throw new FileOperationException("文件 " + relativePath + " 删除失败", e);
}
@ -111,6 +117,10 @@ public class StaticStorageServiceImpl implements StaticStorageService {
Path path;
if (StringUtils.startsWith(folderName, API_FOLDER_NAME)) {
throw new FileOperationException("目录名称 " + folderName + " 不合法");
}
if (StringUtils.isEmpty(basePath)) {
path = Paths.get(staticDir.toString(), folderName);
} else {
@ -129,11 +139,15 @@ public class StaticStorageServiceImpl implements StaticStorageService {
}
@Override
public void update(String basePath, MultipartFile file) {
public void upload(String basePath, MultipartFile file) {
Assert.notNull(file, "Multipart file must not be null");
Path uploadPath;
if (StringUtils.startsWith(file.getOriginalFilename(), API_FOLDER_NAME)) {
throw new FileOperationException("文件名称 " + file.getOriginalFilename() + " 不合法");
}
if (StringUtils.isEmpty(basePath)) {
uploadPath = Paths.get(staticDir.toString(), file.getOriginalFilename());
} else {
@ -147,8 +161,18 @@ public class StaticStorageServiceImpl implements StaticStorageService {
try {
Files.createFile(uploadPath);
file.transferTo(uploadPath);
onChange();
} catch (IOException e) {
throw new ServiceException("上传文件失败").setErrorData(uploadPath);
}
}
private void onChange() {
eventPublisher.publishEvent(new StaticStorageChangedEvent(this, staticDir));
}
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
onChange();
}
}