mirror of https://github.com/halo-dev/halo
feat: provide static mapping feature (#710)
* Provide static mapping feature * feat: filter inner mapping for static storage.pull/711/head
parent
2792d11cd2
commit
bc75275815
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -15,7 +15,7 @@ public enum BanStatusEnum {
|
|||
*/
|
||||
NORMAL(0);
|
||||
|
||||
private int status;
|
||||
private final int status;
|
||||
|
||||
BanStatusEnum(int status) {
|
||||
this.status = status;
|
||||
|
|
|
@ -19,7 +19,7 @@ public enum CommentViolationTypeEnum {
|
|||
*/
|
||||
FREQUENTLY(1);
|
||||
|
||||
private int type;
|
||||
private final int type;
|
||||
|
||||
CommentViolationTypeEnum(int type) {
|
||||
this.type = type;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue