mirror of https://github.com/halo-dev/halo
Upgrade spring boot version (#1289)
* Update gradle wrapper version * Update spring boot version to 2.5.0-M2 * Fix wrong const of temp_dir * Refactor error controller * Fix startup error due to theme not found * Refine error controller handler * Refine multipart resolver config * Fix ThemeRepositoryImplTest error * Fix freemarker not found error * chore: change jetty to undertow. * Remove useless throws Co-authored-by: Ryan Wang <i@ryanc.cc>pull/1298/head
parent
696a9ad2ee
commit
c22348e03a
11
build.gradle
11
build.gradle
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
id "org.springframework.boot" version "2.4.2"
|
||||
id "org.springframework.boot" version "2.5.0-M2"
|
||||
id "io.spring.dependency-management" version "1.0.11.RELEASE"
|
||||
id "checkstyle"
|
||||
id "java"
|
||||
|
@ -8,11 +8,7 @@ plugins {
|
|||
group = "run.halo.app"
|
||||
version = "1.4.5"
|
||||
description = "Halo, An excellent open source blog publishing application."
|
||||
|
||||
java {
|
||||
archivesBaseName = "halo"
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "8.39"
|
||||
|
@ -26,6 +22,7 @@ repositories {
|
|||
// url "https://maven.aliyun.com/nexus/content/groups/public"
|
||||
// }
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
jcenter()
|
||||
}
|
||||
|
||||
|
@ -111,7 +108,7 @@ dependencies {
|
|||
implementation "org.springframework.boot:spring-boot-starter-actuator"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
implementation "org.springframework.boot:spring-boot-starter-jetty"
|
||||
implementation "org.springframework.boot:spring-boot-starter-undertow"
|
||||
implementation "org.springframework.boot:spring-boot-starter-freemarker"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'halo'
|
||||
|
|
|
@ -6,15 +6,11 @@ import static run.halo.app.utils.HaloUtils.ensureBoth;
|
|||
import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import freemarker.core.TemplateClassResolver;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateExceptionHandler;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.MultipartConfigElement;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -25,6 +21,7 @@ import org.apache.commons.fileupload.FileUploadBase;
|
|||
import org.apache.commons.fileupload.servlet.ServletRequestContext;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
|
||||
|
@ -32,6 +29,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||
import org.springframework.boot.jackson.JsonComponentModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.FileUrlResource;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
|
||||
|
@ -41,6 +39,7 @@ import org.springframework.http.converter.HttpMessageConverter;
|
|||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||
|
@ -49,7 +48,6 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
|||
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.core.PageJacksonSerializer;
|
||||
|
@ -85,7 +83,7 @@ public class HaloMvcConfiguration implements WebMvcConfigurer {
|
|||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
// @Bean
|
||||
public Map<String, TemplateModel> freemarkerLayoutDirectives() {
|
||||
Map<String, TemplateModel> freemarkerLayoutDirectives = new HashMap<>();
|
||||
freemarkerLayoutDirectives.put("extends", new ThemeExtendsDirective());
|
||||
|
@ -95,53 +93,16 @@ public class HaloMvcConfiguration implements WebMvcConfigurer {
|
|||
return freemarkerLayoutDirectives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuring freemarker template file path.
|
||||
*
|
||||
* @return new FreeMarkerConfigurer
|
||||
*/
|
||||
@Bean
|
||||
FreeMarkerConfigurer freemarkerConfig(HaloProperties haloProperties)
|
||||
throws IOException, TemplateException {
|
||||
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
|
||||
configurer
|
||||
.setTemplateLoaderPaths(FILE_PROTOCOL + haloProperties.getWorkDir() + "templates/",
|
||||
"classpath:/templates/");
|
||||
configurer.setDefaultEncoding("UTF-8");
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("auto_import",
|
||||
"/common/macro/common_macro.ftl as common,/common/macro/global_macro.ftl as global");
|
||||
|
||||
configurer.setFreemarkerSettings(properties);
|
||||
|
||||
// Predefine configuration
|
||||
freemarker.template.Configuration configuration = configurer.createConfiguration();
|
||||
|
||||
configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||
|
||||
if (haloProperties.isProductionEnv()) {
|
||||
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||
}
|
||||
|
||||
configuration.setSharedVariables(new HashMap<>() {{
|
||||
put("layout", freemarkerLayoutDirectives());
|
||||
}
|
||||
});
|
||||
|
||||
// Set predefined freemarker configuration
|
||||
configurer.setConfiguration(configuration);
|
||||
|
||||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuring multipartResolver for large file upload..
|
||||
*
|
||||
* @return new multipartResolver
|
||||
*/
|
||||
@Bean(name = "multipartResolver")
|
||||
MultipartResolver multipartResolver(MultipartProperties multipartProperties) {
|
||||
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
MultipartResolver multipartResolver(MultipartProperties multipartProperties)
|
||||
throws IOException {
|
||||
MultipartConfigElement multipartConfigElement = multipartProperties.createMultipartConfig();
|
||||
CommonsMultipartResolver resolver = new CommonsMultipartResolver() {
|
||||
@Override
|
||||
|
@ -156,10 +117,15 @@ public class HaloMvcConfiguration implements WebMvcConfigurer {
|
|||
resolver.setDefaultEncoding("UTF-8");
|
||||
resolver.setMaxUploadSize(multipartConfigElement.getMaxRequestSize());
|
||||
resolver.setMaxUploadSizePerFile(multipartConfigElement.getMaxFileSize());
|
||||
var location = multipartProperties.getLocation();
|
||||
if (StringUtils.hasText(location)) {
|
||||
FileUrlResource resource = new FileUrlResource(location);
|
||||
resolver.setUploadTempDir(resource);
|
||||
}
|
||||
|
||||
//lazy multipart parsing, throwing parse exceptions once the application attempts to
|
||||
// obtain multipart files
|
||||
resolver.setResolveLazily(true);
|
||||
resolver.setResolveLazily(multipartProperties.isResolveLazily());
|
||||
|
||||
return resolver;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ import static run.halo.app.model.support.HaloConst.USER_HOME;
|
|||
import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import run.halo.app.model.enums.Mode;
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.controller.content;
|
|||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
@ -12,7 +13,8 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.controller.content.model.CategoryModel;
|
||||
import run.halo.app.controller.content.model.JournalModel;
|
||||
|
@ -66,8 +68,6 @@ public class ContentContentController {
|
|||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
@ -82,7 +82,6 @@ public class ContentContentController {
|
|||
OptionService optionService,
|
||||
PostService postService,
|
||||
SheetService sheetService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
AuthenticationService authenticationService,
|
||||
CategoryService categoryService) {
|
||||
this.postModel = postModel;
|
||||
|
@ -95,7 +94,6 @@ public class ContentContentController {
|
|||
this.optionService = optionService;
|
||||
this.postService = postService;
|
||||
this.sheetService = sheetService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.authenticationService = authenticationService;
|
||||
this.categoryService = categoryService;
|
||||
}
|
||||
|
@ -126,12 +124,14 @@ public class ContentContentController {
|
|||
Sheet sheet = sheetService.getBySlug(prefix);
|
||||
return sheetModel.content(sheet, token, model);
|
||||
}
|
||||
throw new NotFoundException("Not Found");
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/page/{page:\\d+}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable(value = "page") Integer page,
|
||||
HttpServletRequest request,
|
||||
Model model) {
|
||||
if (optionService.getArchivesPrefix().equals(prefix)) {
|
||||
return postModel.archives(page, model);
|
||||
|
@ -145,7 +145,7 @@ public class ContentContentController {
|
|||
return photoModel.list(page, model);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Not Found");
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/{slug}")
|
||||
|
@ -186,7 +186,7 @@ public class ContentContentController {
|
|||
return sheetModel.content(sheet, token, model);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Not Found");
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/{slug}/page/{page:\\d+}")
|
||||
|
@ -202,7 +202,7 @@ public class ContentContentController {
|
|||
return tagModel.listPost(model, slug, page);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Not Found");
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{year:\\d+}/{month:\\d+}/{slug}")
|
||||
|
@ -217,7 +217,7 @@ public class ContentContentController {
|
|||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Not Found");
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{year:\\d+}/{month:\\d+}/{day:\\d+}/{slug}")
|
||||
|
@ -233,7 +233,7 @@ public class ContentContentController {
|
|||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Not Found");
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@PostMapping(value = "content/{type}/{slug:.*}/authentication")
|
||||
|
@ -254,6 +254,17 @@ public class ContentContentController {
|
|||
return "redirect:" + redirectUrl;
|
||||
}
|
||||
|
||||
private NotFoundException buildPathNotFoundException() {
|
||||
var requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
|
||||
var requestUri = "";
|
||||
if (requestAttributes instanceof ServletRequestAttributes) {
|
||||
requestUri =
|
||||
((ServletRequestAttributes) requestAttributes).getRequest().getRequestURI();
|
||||
}
|
||||
return new NotFoundException("无法定位到该路径:" + requestUri);
|
||||
}
|
||||
|
||||
private String doAuthenticationPost(
|
||||
String slug, String password) throws UnsupportedEncodingException {
|
||||
Post post = postService.getBy(PostStatus.INTIMATE, slug);
|
||||
|
|
|
@ -1,234 +0,0 @@
|
|||
package run.halo.app.controller.core;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.DEFAULT_ERROR_PATH;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
import run.halo.app.exception.AbstractHaloException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.utils.FilenameUtils;
|
||||
|
||||
/**
|
||||
* Error page Controller
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2017-12-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping("${server.error.path:${error.path:/error}}")
|
||||
public class CommonController extends AbstractErrorController {
|
||||
|
||||
private static final String NOT_FOUND_TEMPLATE = "404.ftl";
|
||||
|
||||
private static final String INTERNAL_ERROR_TEMPLATE = "500.ftl";
|
||||
|
||||
private static final String ERROR_TEMPLATE = "error.ftl";
|
||||
|
||||
private static final String COULD_NOT_RESOLVE_VIEW_WITH_NAME_PREFIX =
|
||||
"Could not resolve view with name '";
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final ErrorProperties errorProperties;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public CommonController(ThemeService themeService,
|
||||
ErrorAttributes errorAttributes,
|
||||
ServerProperties serverProperties,
|
||||
OptionService optionService) {
|
||||
super(errorAttributes);
|
||||
this.themeService = themeService;
|
||||
this.errorProperties = serverProperties.getError();
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle error
|
||||
*
|
||||
* @param request request
|
||||
* @return String
|
||||
*/
|
||||
@GetMapping
|
||||
public String handleError(HttpServletRequest request, HttpServletResponse response,
|
||||
Model model) {
|
||||
handleCustomException(request);
|
||||
|
||||
ErrorAttributeOptions options = getErrorAttributeOptions(request);
|
||||
|
||||
Map<String, Object> errorDetail =
|
||||
Collections.unmodifiableMap(getErrorAttributes(request, options));
|
||||
model.addAttribute("error", errorDetail);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
log.debug("Error detail: [{}]", errorDetail);
|
||||
|
||||
HttpStatus status = getStatus(request);
|
||||
|
||||
response.setStatus(status.value());
|
||||
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
|
||||
return contentInternalError();
|
||||
} else if (status.equals(HttpStatus.NOT_FOUND)) {
|
||||
return contentNotFound();
|
||||
} else {
|
||||
return defaultErrorHandler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 404 error page
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
@GetMapping(value = "/404")
|
||||
public String contentNotFound() {
|
||||
if (themeService.templateExists(ERROR_TEMPLATE)) {
|
||||
return getActualTemplatePath(ERROR_TEMPLATE);
|
||||
}
|
||||
|
||||
if (themeService.templateExists(NOT_FOUND_TEMPLATE)) {
|
||||
return getActualTemplatePath(NOT_FOUND_TEMPLATE);
|
||||
}
|
||||
|
||||
return defaultErrorHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 500 error page
|
||||
*
|
||||
* @return template path:
|
||||
*/
|
||||
@GetMapping(value = "/500")
|
||||
public String contentInternalError() {
|
||||
if (themeService.templateExists(ERROR_TEMPLATE)) {
|
||||
return getActualTemplatePath(ERROR_TEMPLATE);
|
||||
}
|
||||
|
||||
if (themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) {
|
||||
return getActualTemplatePath(INTERNAL_ERROR_TEMPLATE);
|
||||
}
|
||||
|
||||
return defaultErrorHandler();
|
||||
}
|
||||
|
||||
private String defaultErrorHandler() {
|
||||
return DEFAULT_ERROR_PATH;
|
||||
}
|
||||
|
||||
private String getActualTemplatePath(@NonNull String template) {
|
||||
Assert.hasText(template, "FTL template must not be blank");
|
||||
|
||||
StringBuilder path = new StringBuilder();
|
||||
path.append("themes/")
|
||||
.append(themeService.getActivatedTheme().getFolderName())
|
||||
.append('/')
|
||||
.append(FilenameUtils.getBasename(template));
|
||||
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles custom exception, like HaloException.
|
||||
*
|
||||
* @param request http servlet request must not be null
|
||||
*/
|
||||
private void handleCustomException(@NonNull HttpServletRequest request) {
|
||||
Assert.notNull(request, "Http servlet request must not be null");
|
||||
|
||||
Object throwableObject = request.getAttribute("javax.servlet.error.exception");
|
||||
if (throwableObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Throwable throwable = (Throwable) throwableObject;
|
||||
|
||||
if (throwable instanceof NestedServletException) {
|
||||
log.error("Captured an exception: [{}]", throwable.getMessage());
|
||||
Throwable rootCause = ((NestedServletException) throwable).getRootCause();
|
||||
if (rootCause instanceof AbstractHaloException) {
|
||||
if (!(rootCause instanceof NotFoundException)) {
|
||||
log.error("Caused by", rootCause);
|
||||
}
|
||||
AbstractHaloException haloException = (AbstractHaloException) rootCause;
|
||||
request.setAttribute("javax.servlet.error.status_code",
|
||||
haloException.getStatus().value());
|
||||
request.setAttribute("javax.servlet.error.exception", rootCause);
|
||||
request.setAttribute("javax.servlet.error.message", haloException.getMessage());
|
||||
}
|
||||
} else if (StringUtils.startsWithIgnoreCase(throwable.getMessage(),
|
||||
COULD_NOT_RESOLVE_VIEW_WITH_NAME_PREFIX)) {
|
||||
log.debug("Captured an exception", throwable);
|
||||
request.setAttribute("javax.servlet.error.status_code", HttpStatus.NOT_FOUND.value());
|
||||
|
||||
NotFoundException viewNotFound = new NotFoundException("该路径没有对应的模板");
|
||||
request.setAttribute("javax.servlet.error.exception", viewNotFound);
|
||||
request.setAttribute("javax.servlet.error.message", viewNotFound.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the error page.
|
||||
*
|
||||
* @return the error path
|
||||
*/
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return this.errorProperties.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the stacktrace attribute should be included.
|
||||
*
|
||||
* @param request the source request
|
||||
* @return if the stacktrace attribute should be included
|
||||
*/
|
||||
private boolean isIncludeStackTrace(HttpServletRequest request) {
|
||||
ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
|
||||
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
|
||||
return true;
|
||||
}
|
||||
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
|
||||
return getTraceParameter(request);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ErrorAttributeOptions .
|
||||
*
|
||||
* @param request the source request
|
||||
* @return {@link ErrorAttributeOptions}
|
||||
*/
|
||||
private ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request) {
|
||||
ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
|
||||
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
|
||||
return ErrorAttributeOptions.of(ErrorAttributeOptions.Include.STACK_TRACE);
|
||||
}
|
||||
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM
|
||||
&& getTraceParameter(request)) {
|
||||
return ErrorAttributeOptions.of(ErrorAttributeOptions.Include.STACK_TRACE);
|
||||
}
|
||||
return ErrorAttributeOptions.defaults();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package run.halo.app.controller.error;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
import run.halo.app.exception.AbstractHaloException;
|
||||
|
||||
/**
|
||||
* Default error controller.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultErrorController extends BasicErrorController {
|
||||
|
||||
public DefaultErrorController(
|
||||
ErrorAttributes errorAttributes,
|
||||
ServerProperties serverProperties,
|
||||
List<ErrorViewResolver> errorViewResolvers) {
|
||||
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpStatus getStatus(HttpServletRequest request) {
|
||||
var status = super.getStatus(request);
|
||||
// deduce status
|
||||
var exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
|
||||
if (exception instanceof NestedServletException) {
|
||||
var nse = (NestedServletException) exception;
|
||||
if (nse.getCause() instanceof AbstractHaloException) {
|
||||
status = resolveHaloException((AbstractHaloException) nse.getCause(), request);
|
||||
}
|
||||
} else if (exception instanceof AbstractHaloException) {
|
||||
status = resolveHaloException((AbstractHaloException) exception, request);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private HttpStatus resolveHaloException(AbstractHaloException haloException,
|
||||
HttpServletRequest request) {
|
||||
HttpStatus status = haloException.getStatus();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.error("Halo exception occurred.", haloException);
|
||||
}
|
||||
// reset status
|
||||
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, status.value());
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, haloException);
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, haloException.getMessage());
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, haloException.getClass());
|
||||
return status;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package run.halo.app.controller.error;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatus.Series;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
||||
@Component
|
||||
public class DefaultErrorViewResolver implements ErrorViewResolver {
|
||||
|
||||
private static final Map<Series, String> SERIES_VIEWS;
|
||||
|
||||
static {
|
||||
EnumMap<Series, String> views = new EnumMap<>(Series.class);
|
||||
views.put(Series.CLIENT_ERROR, "4xx");
|
||||
views.put(Series.SERVER_ERROR, "5xx");
|
||||
SERIES_VIEWS = Collections.unmodifiableMap(views);
|
||||
}
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final TemplateAvailabilityProviders templateAvailabilityProviders;
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
public DefaultErrorViewResolver(ThemeService themeService,
|
||||
ApplicationContext applicationContext) {
|
||||
this.themeService = themeService;
|
||||
this.applicationContext = applicationContext;
|
||||
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
|
||||
Map<String, Object> model) {
|
||||
// for compatibility
|
||||
var errorModel = new HashMap<>(model);
|
||||
|
||||
// resolve with status code. eg: 400.ftl
|
||||
var modelAndView = resolve(String.valueOf(status.value()), errorModel);
|
||||
|
||||
// resolve with status series. eg: 4xx.ftl
|
||||
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
|
||||
modelAndView = resolve(SERIES_VIEWS.get(status.series()), errorModel);
|
||||
}
|
||||
|
||||
// resolve error template. eg: error.ftl
|
||||
if (modelAndView == null) {
|
||||
modelAndView = resolve("error", errorModel);
|
||||
}
|
||||
|
||||
if (modelAndView == null) {
|
||||
// resolve common error template
|
||||
modelAndView = new ModelAndView("common/error/error", errorModel);
|
||||
}
|
||||
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
private ModelAndView resolve(String viewName, Map<String, Object> model) {
|
||||
var errorViewName = this.themeService.render(viewName);
|
||||
var provider =
|
||||
this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
|
||||
if (provider != null) {
|
||||
return new ModelAndView(errorViewName, model);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ import org.springframework.http.converter.json.AbstractJackson2HttpMessageConver
|
|||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
|
@ -61,16 +62,28 @@ public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object>
|
|||
Object returnBody = bodyContainer.getValue();
|
||||
|
||||
if (returnBody instanceof BaseResponse) {
|
||||
// If the return body is instance of BaseResponse
|
||||
// If the return body is instance of BaseResponse, then just do nothing
|
||||
BaseResponse<?> baseResponse = (BaseResponse<?>) returnBody;
|
||||
response.setStatusCode(HttpStatus.resolve(baseResponse.getStatus()));
|
||||
HttpStatus status = HttpStatus.resolve(baseResponse.getStatus());
|
||||
if (status == null) {
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
response.setStatusCode(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap the return body
|
||||
BaseResponse<?> baseResponse = BaseResponse.ok(returnBody);
|
||||
// get status
|
||||
var status = HttpStatus.OK;
|
||||
if (response instanceof ServletServerHttpResponse) {
|
||||
var servletResponse =
|
||||
((ServletServerHttpResponse) response).getServletResponse();
|
||||
status = HttpStatus.resolve(servletResponse.getStatus());
|
||||
if (status == null) {
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
var baseResponse = new BaseResponse<>(status.value(), status.getReasonPhrase(), returnBody);
|
||||
bodyContainer.setValue(baseResponse);
|
||||
response.setStatusCode(HttpStatus.valueOf(baseResponse.getStatus()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package run.halo.app.exception;
|
||||
|
||||
/**
|
||||
* Theme not found exception.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2020-03-01
|
||||
*/
|
||||
public class ThemeNotFoundException extends BadRequestException {
|
||||
|
||||
public ThemeNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ThemeNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.listener;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
|
@ -135,7 +136,7 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
|
|||
}
|
||||
|
||||
/**
|
||||
* Init internal themes
|
||||
* Init internal themes.
|
||||
*/
|
||||
private void initThemes() {
|
||||
// Whether the blog has initialized
|
||||
|
@ -175,6 +176,9 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
|
|||
log.debug("Skipped copying theme folder due to existence of theme folder");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof FileNotFoundException) {
|
||||
log.error("Please check location: classpath:{}", ThemeService.THEME_FOLDER);
|
||||
}
|
||||
log.error("Initialize internal theme to user path error!", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,12 @@ package run.halo.app.listener.freemarker;
|
|||
import static run.halo.app.model.support.HaloConst.OPTIONS_CACHE_KEY;
|
||||
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.TemplateModel;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.BlockDirective;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.PutDirective;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
@ -11,6 +16,7 @@ import org.springframework.core.Ordered;
|
|||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.core.freemarker.inheritance.ThemeExtendsDirective;
|
||||
import run.halo.app.event.options.OptionUpdatedEvent;
|
||||
import run.halo.app.event.theme.ThemeActivatedEvent;
|
||||
import run.halo.app.event.theme.ThemeUpdatedEvent;
|
||||
|
@ -51,13 +57,27 @@ public class FreemarkerConfigAwareListener {
|
|||
ThemeService themeService,
|
||||
ThemeSettingService themeSettingService,
|
||||
UserService userService,
|
||||
AbstractStringCacheStore cacheStore) {
|
||||
AbstractStringCacheStore cacheStore) throws TemplateModelException {
|
||||
this.optionService = optionService;
|
||||
this.configuration = configuration;
|
||||
this.themeService = themeService;
|
||||
this.themeSettingService = themeSettingService;
|
||||
this.userService = userService;
|
||||
this.cacheStore = cacheStore;
|
||||
|
||||
this.initFreemarkerConfig();
|
||||
}
|
||||
|
||||
private Map<String, TemplateModel> freemarkerLayoutDirectives() {
|
||||
Map<String, TemplateModel> freemarkerLayoutDirectives = new HashMap<>();
|
||||
freemarkerLayoutDirectives.put("extends", new ThemeExtendsDirective());
|
||||
freemarkerLayoutDirectives.put("block", new BlockDirective());
|
||||
freemarkerLayoutDirectives.put("put", new PutDirective());
|
||||
return freemarkerLayoutDirectives;
|
||||
}
|
||||
|
||||
private void initFreemarkerConfig() throws TemplateModelException {
|
||||
configuration.setSharedVariable("layout", freemarkerLayoutDirectives());
|
||||
}
|
||||
|
||||
@EventListener
|
||||
|
@ -72,15 +92,14 @@ public class FreemarkerConfigAwareListener {
|
|||
}
|
||||
|
||||
@EventListener
|
||||
public void onThemeActivatedEvent(ThemeActivatedEvent themeActivatedEvent)
|
||||
throws TemplateModelException {
|
||||
public void onThemeActivatedEvent(ThemeActivatedEvent themeActivatedEvent) {
|
||||
log.debug("Received theme activated event");
|
||||
|
||||
loadThemeConfig();
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onThemeUpdatedEvent(ThemeUpdatedEvent event) throws TemplateModelException {
|
||||
public void onThemeUpdatedEvent(ThemeUpdatedEvent event) {
|
||||
log.debug("Received theme updated event");
|
||||
|
||||
loadThemeConfig();
|
||||
|
|
|
@ -5,9 +5,7 @@ import java.util.Optional;
|
|||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 公共常量
|
||||
* </pre>
|
||||
* Halo constants.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2017/12/29
|
||||
|
@ -17,12 +15,12 @@ public class HaloConst {
|
|||
/**
|
||||
* User home directory.
|
||||
*/
|
||||
public static final String USER_HOME = System.getProperties().getProperty("user.home");
|
||||
public static final String USER_HOME = System.getProperty("user.home");
|
||||
|
||||
/**
|
||||
* Temporary directory.
|
||||
*/
|
||||
public static final String TEMP_DIR = "/tmp/run.halo.app";
|
||||
public static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
|
||||
|
||||
public static final String PROTOCOL_HTTPS = "https://";
|
||||
|
||||
|
@ -65,7 +63,7 @@ public class HaloConst {
|
|||
*/
|
||||
public static final String POST_PASSWORD_TEMPLATE = "post_password";
|
||||
/**
|
||||
* Suffix of freemarker template file
|
||||
* Suffix of freemarker template file.
|
||||
*/
|
||||
public static final String SUFFIX_FTL = ".ftl";
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.nio.file.Paths;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
@ -25,6 +26,7 @@ import run.halo.app.event.options.OptionUpdatedEvent;
|
|||
import run.halo.app.exception.AlreadyExistsException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.exception.ServiceException;
|
||||
import run.halo.app.exception.ThemeNotFoundException;
|
||||
import run.halo.app.exception.ThemeNotSupportException;
|
||||
import run.halo.app.handler.theme.config.support.ThemeProperty;
|
||||
import run.halo.app.model.entity.Option;
|
||||
|
@ -67,16 +69,30 @@ public class ThemeRepositoryImpl
|
|||
public ThemeProperty getActivatedThemeProperty() {
|
||||
ThemeProperty themeProperty = this.currentTheme;
|
||||
if (themeProperty == null) {
|
||||
AtomicBoolean fallbackTheme = new AtomicBoolean(false);
|
||||
synchronized (this) {
|
||||
if (this.currentTheme == null) {
|
||||
// get current theme id
|
||||
String currentThemeId = this.optionRepository.findByKey(THEME.getValue())
|
||||
.map(Option::getValue)
|
||||
.orElse(DEFAULT_THEME_ID);
|
||||
|
||||
// fetch current theme
|
||||
this.currentTheme = this.getThemeByThemeId(currentThemeId);
|
||||
this.currentTheme =
|
||||
this.fetchThemeByThemeId(currentThemeId).orElseGet(() -> {
|
||||
if (!StringUtils.equalsIgnoreCase(currentThemeId, DEFAULT_THEME_ID)) {
|
||||
fallbackTheme.set(true);
|
||||
return this.getThemeByThemeId(DEFAULT_THEME_ID);
|
||||
}
|
||||
throw new ThemeNotFoundException(
|
||||
"Default theme: " + DEFAULT_THEME_ID + " was not found!");
|
||||
});
|
||||
}
|
||||
}
|
||||
if (fallbackTheme.get()) {
|
||||
// need set default theme as fallback theme
|
||||
setActivatedTheme(DEFAULT_THEME_ID);
|
||||
}
|
||||
}
|
||||
return this.currentTheme;
|
||||
}
|
||||
|
@ -188,16 +204,22 @@ public class ThemeRepositoryImpl
|
|||
@Override
|
||||
public void onApplicationEvent(OptionUpdatedEvent event) {
|
||||
synchronized (this) {
|
||||
// reset current theme with null
|
||||
this.currentTheme = null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected ThemeProperty getThemeByThemeId(String themeId) {
|
||||
return fetchThemeByThemeId(themeId).orElseThrow(
|
||||
() -> new ThemeNotFoundException("Failed to find theme with id: " + themeId));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Optional<ThemeProperty> fetchThemeByThemeId(String themeId) {
|
||||
return ThemePropertyScanner.INSTANCE.scan(getThemeRootPath(), null)
|
||||
.stream()
|
||||
.filter(property -> Objects.equals(themeId, property.getId()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,15 +72,12 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
|
|||
private final AbstractStringCacheStore cacheStore;
|
||||
private final Map<String, PropertyEnum> propertyEnumMap;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public OptionServiceImpl(HaloProperties haloProperties,
|
||||
OptionRepository optionRepository,
|
||||
public OptionServiceImpl(OptionRepository optionRepository,
|
||||
ApplicationContext applicationContext,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
super(optionRepository);
|
||||
this.haloProperties = haloProperties;
|
||||
this.optionRepository = optionRepository;
|
||||
this.applicationContext = applicationContext;
|
||||
this.cacheStore = cacheStore;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.DEFAULT_ERROR_PATH;
|
||||
import static run.halo.app.utils.FileUtils.copyFolder;
|
||||
import static run.halo.app.utils.FileUtils.deleteFolderQuietly;
|
||||
import static run.halo.app.utils.VersionUtil.compareVersion;
|
||||
|
@ -314,18 +313,14 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
|
||||
@Override
|
||||
public String render(String pageName) {
|
||||
return fetchActivatedTheme()
|
||||
.map(themeProperty ->
|
||||
String.format(RENDER_TEMPLATE, themeProperty.getFolderName(), pageName))
|
||||
.orElse(DEFAULT_ERROR_PATH);
|
||||
var folderName = getActivatedTheme().getFolderName();
|
||||
return "themes/" + folderName + "/" + pageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderWithSuffix(String pageName) {
|
||||
// Get activated theme
|
||||
ThemeProperty activatedTheme = getActivatedTheme();
|
||||
// Build render url
|
||||
return String.format(RENDER_TEMPLATE_SUFFIX, activatedTheme.getFolderName(), pageName);
|
||||
var folderName = getActivatedTheme().getFolderName();
|
||||
return "themes/" + folderName + "/" + pageName + ".ftl";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -337,13 +332,13 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
@Override
|
||||
@NonNull
|
||||
public ThemeProperty getActivatedTheme() {
|
||||
return themeRepository.getActivatedThemeProperty();
|
||||
return fetchActivatedTheme().orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Optional<ThemeProperty> fetchActivatedTheme() {
|
||||
return fetchThemePropertyBy(getActivatedThemeId());
|
||||
return Optional.of(themeRepository.getActivatedThemeProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
server:
|
||||
port: 8090
|
||||
forward-headers-strategy: native
|
||||
error:
|
||||
include-message: always
|
||||
compression:
|
||||
enabled: false
|
||||
spring:
|
||||
|
@ -40,7 +42,14 @@ spring:
|
|||
multipart:
|
||||
max-file-size: 10240MB
|
||||
max-request-size: 10240MB
|
||||
location: /tmp/run.halo.app
|
||||
resolve-lazily: true
|
||||
freemarker:
|
||||
suffix: .ftl
|
||||
settings:
|
||||
auto_import: /common/macro/global_macro.ftl as global
|
||||
template-loader-path:
|
||||
- file:///${halo.work-dir}templates/
|
||||
- classpath:/templates/
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
|
@ -60,4 +69,5 @@ springfox:
|
|||
|
||||
halo:
|
||||
download-timeout: 5m
|
||||
cache: memory
|
||||
cache: memory
|
||||
work-dir: ${user.home}/.halo/
|
|
@ -1 +0,0 @@
|
|||
404 Not Found
|
|
@ -1 +0,0 @@
|
|||
500 Internal Error
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="${atom_url!}">
|
||||
<title>${(error.status)!500} | ${(error.error)!'未知错误'}</title>
|
||||
<title>${(status)!500} | ${(error)!'未知错误'}</title>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
|
@ -121,9 +121,9 @@
|
|||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2>${(error.status)!500}</h2>
|
||||
<h1 class="title">${(error.error)!'未知错误'}.</h1>
|
||||
<p>${(error.message)!'未知错误!可能存在的原因:未正确设置主题或主题文件缺失。'}</p>
|
||||
<h2>${(status)!500}</h2>
|
||||
<h1 class="title">${(error)!'未知错误'}.</h1>
|
||||
<p>${(message)!'未知错误!可能存在的原因:未正确设置主题或主题文件缺失。'}</p>
|
||||
<div class="back-home">
|
||||
<button onclick="window.location.href='${blog_url!}'">首页</button>
|
||||
</div>
|
||||
|
|
|
@ -59,14 +59,14 @@ class ThemeRepositoryImplTest {
|
|||
expectedTheme.setActivated(true);
|
||||
|
||||
given(optionRepository.findByKey(THEME.getValue())).willReturn(Optional.empty());
|
||||
doReturn(expectedTheme).when(themeRepository)
|
||||
.getThemeByThemeId(HaloConst.DEFAULT_THEME_ID);
|
||||
doReturn(Optional.of(expectedTheme)).when(themeRepository)
|
||||
.fetchThemeByThemeId(HaloConst.DEFAULT_THEME_ID);
|
||||
|
||||
ThemeProperty resultTheme = themeRepository.getActivatedThemeProperty();
|
||||
assertEquals(expectedTheme, resultTheme);
|
||||
|
||||
verify(optionRepository, times(1)).findByKey(any());
|
||||
verify(themeRepository, times(1)).getThemeByThemeId(any());
|
||||
verify(themeRepository, times(1)).fetchThemeByThemeId(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -76,8 +76,8 @@ class ThemeRepositoryImplTest {
|
|||
expectedTheme.setActivated(true);
|
||||
|
||||
given(optionRepository.findByKey(THEME.getValue())).willReturn(Optional.empty());
|
||||
doReturn(expectedTheme).when(themeRepository)
|
||||
.getThemeByThemeId(HaloConst.DEFAULT_THEME_ID);
|
||||
doReturn(Optional.of(expectedTheme)).when(themeRepository)
|
||||
.fetchThemeByThemeId(HaloConst.DEFAULT_THEME_ID);
|
||||
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(10);
|
||||
// define tasks
|
||||
|
@ -96,7 +96,7 @@ class ThemeRepositoryImplTest {
|
|||
});
|
||||
|
||||
verify(optionRepository, times(1)).findByKey(any());
|
||||
verify(themeRepository, times(1)).getThemeByThemeId(any());
|
||||
verify(themeRepository, times(1)).fetchThemeByThemeId(any());
|
||||
}
|
||||
|
||||
}
|
|
@ -133,6 +133,7 @@ class GitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Due to time-consumption fetching")
|
||||
void getBranchesFromRemote() throws GitAPIException {
|
||||
Map<String, Ref> refMap = Git.lsRemoteRepository()
|
||||
.setRemote("https://github.com/halo-dev/halo.git")
|
||||
|
|
Loading…
Reference in New Issue