mirror of https://github.com/halo-dev/halo
commit
75b5c9efba
|
@ -1,6 +1,8 @@
|
|||
name: Java CI
|
||||
name: Halo CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -8,10 +10,22 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Cache Gradle
|
||||
id: cache-gradle
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew test
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -x test
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "default-theme"]
|
||||
path = src/main/resources/templates/themes/anatole
|
||||
url = https://github.com/halo-dev/halo-theme-anatole
|
11
build.gradle
11
build.gradle
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
id 'org.springframework.boot' version '2.2.1.RELEASE'
|
||||
id 'org.springframework.boot' version '2.2.2.RELEASE'
|
||||
id "io.freefair.lombok" version "3.6.6"
|
||||
// id 'war'
|
||||
id 'java'
|
||||
|
@ -59,6 +59,10 @@ ext {
|
|||
image4jVersion = '0.7zensight1'
|
||||
flywayVersion = '6.1.0'
|
||||
h2Version = '1.4.196'
|
||||
levelDbVersion = '0.12'
|
||||
jsonVersion = '20190722'
|
||||
fastJsonVersion = '1.2.56'
|
||||
templateInheritance = "0.4.RELEASE"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -67,6 +71,7 @@ dependencies {
|
|||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-undertow'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-freemarker'
|
||||
implementation "kr.pe.kwonnam.freemarker:freemarker-template-inheritance:$templateInheritance"
|
||||
|
||||
implementation "io.github.biezhi:oh-my-email:$ohMyEmailVersion"
|
||||
implementation "cn.hutool:hutool-core:$hutoolVersion"
|
||||
|
@ -103,6 +108,10 @@ dependencies {
|
|||
implementation "net.sf.image4j:image4j:$image4jVersion"
|
||||
implementation "org.flywaydb:flyway-core:$flywayVersion"
|
||||
|
||||
implementation "org.json:json:$jsonVersion"
|
||||
implementation "com.alibaba:fastjson:$fastJsonVersion"
|
||||
|
||||
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
|
||||
runtimeOnly "com.h2database:h2:$h2Version"
|
||||
runtimeOnly 'mysql:mysql-connector-java'
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<code_scheme name="halo" version="173">
|
||||
<DBN-PSQL>
|
||||
<case-options enabled="true">
|
||||
<option name="KEYWORD_CASE" value="lower" />
|
||||
<option name="FUNCTION_CASE" value="lower" />
|
||||
<option name="PARAMETER_CASE" value="lower" />
|
||||
<option name="DATATYPE_CASE" value="lower" />
|
||||
<option name="OBJECT_CASE" value="preserve" />
|
||||
</case-options>
|
||||
<formatting-settings enabled="false" />
|
||||
</DBN-PSQL>
|
||||
<DBN-SQL>
|
||||
<case-options enabled="true">
|
||||
<option name="KEYWORD_CASE" value="lower" />
|
||||
<option name="FUNCTION_CASE" value="lower" />
|
||||
<option name="PARAMETER_CASE" value="lower" />
|
||||
<option name="DATATYPE_CASE" value="lower" />
|
||||
<option name="OBJECT_CASE" value="preserve" />
|
||||
</case-options>
|
||||
<formatting-settings enabled="false">
|
||||
<option name="STATEMENT_SPACING" value="one_line" />
|
||||
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
|
||||
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
|
||||
</formatting-settings>
|
||||
</DBN-SQL>
|
||||
</code_scheme>
|
|
@ -33,6 +33,7 @@ public class Application extends SpringBootServletInitializer {
|
|||
|
||||
// Run application
|
||||
context = SpringApplication.run(Application.class, args);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.iq80.leveldb.*;
|
||||
import org.iq80.leveldb.impl.Iq80DBFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* level-db cache store
|
||||
* Create by Pencilso on 2020/1/9 7:20 下午
|
||||
*/
|
||||
@Slf4j
|
||||
public class LevelCacheStore extends StringCacheStore {
|
||||
/**
|
||||
* Cleaner schedule period. (ms)
|
||||
*/
|
||||
private final static long PERIOD = 60 * 1000;
|
||||
|
||||
private static DB leveldb;
|
||||
|
||||
|
||||
private Timer timer;
|
||||
|
||||
@Autowired
|
||||
private HaloProperties haloProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (leveldb != null) return;
|
||||
try {
|
||||
//work path
|
||||
File folder = new File(haloProperties.getWorkDir() + ".leveldb");
|
||||
DBFactory factory = new Iq80DBFactory();
|
||||
Options options = new Options();
|
||||
options.createIfMissing(true);
|
||||
//open leveldb store folder
|
||||
leveldb = factory.open(folder, options);
|
||||
timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
|
||||
} catch (Exception ex) {
|
||||
log.error("init leveldb error ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
try {
|
||||
leveldb.close();
|
||||
timer.cancel();
|
||||
} catch (IOException e) {
|
||||
log.error("close leveldb error ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<CacheWrapper<String>> getInternal(String key) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
byte[] bytes = leveldb.get(stringToBytes(key));
|
||||
if (bytes != null) {
|
||||
String valueJson = bytesToString(bytes);
|
||||
return StringUtils.isEmpty(valueJson) ? Optional.empty() : jsonToCacheWrapper(valueJson);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
void putInternal(String key, CacheWrapper<String> cacheWrapper) {
|
||||
putInternalIfAbsent(key, cacheWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
Boolean putInternalIfAbsent(String key, CacheWrapper<String> cacheWrapper) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||
try {
|
||||
leveldb.put(
|
||||
stringToBytes(key),
|
||||
stringToBytes(JsonUtils.objectToJson(cacheWrapper))
|
||||
);
|
||||
return true;
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Put cache fail json2object key: [{}] value:[{}]", key, cacheWrapper);
|
||||
}
|
||||
log.debug("Cache key: [{}], original cache wrapper: [{}]", key, cacheWrapper);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
leveldb.delete(stringToBytes(key));
|
||||
log.debug("cache remove key: [{}]", key);
|
||||
}
|
||||
|
||||
|
||||
private byte[] stringToBytes(String str) {
|
||||
return str.getBytes(Charset.defaultCharset());
|
||||
}
|
||||
|
||||
private String bytesToString(byte[] bytes) {
|
||||
return new String(bytes, Charset.defaultCharset());
|
||||
}
|
||||
|
||||
private Optional<CacheWrapper<String>> jsonToCacheWrapper(String json) {
|
||||
Assert.hasText(json, "json value must not be null");
|
||||
CacheWrapper<String> cacheWrapper = null;
|
||||
try {
|
||||
cacheWrapper = JsonUtils.jsonToObject(json, CacheWrapper.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.debug("erro json to wrapper value bytes: [{}]", json, e);
|
||||
}
|
||||
return Optional.ofNullable(cacheWrapper);
|
||||
}
|
||||
|
||||
private class CacheExpiryCleaner extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//batch
|
||||
WriteBatch writeBatch = leveldb.createWriteBatch();
|
||||
|
||||
DBIterator iterator = leveldb.iterator();
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<byte[], byte[]> next = iterator.next();
|
||||
if (next.getKey() == null || next.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String valueJson = bytesToString(next.getValue());
|
||||
Optional<CacheWrapper<String>> stringCacheWrapper = StringUtils.isEmpty(valueJson) ? Optional.empty() : jsonToCacheWrapper(valueJson);
|
||||
if (stringCacheWrapper.isPresent()) {
|
||||
//get expireat time
|
||||
long expireAtTime = stringCacheWrapper.map(CacheWrapper::getExpireAt)
|
||||
.map(Date::getTime)
|
||||
.orElse(0L);
|
||||
//if expire
|
||||
if (expireAtTime != 0 && currentTimeMillis > expireAtTime) {
|
||||
writeBatch.delete(next.getKey());
|
||||
log.debug("deleted the cache: [{}] for expiration", bytesToString(next.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
leveldb.write(writeBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import run.halo.app.cache.InMemoryCacheStore;
|
||||
import run.halo.app.cache.LevelCacheStore;
|
||||
import run.halo.app.cache.StringCacheStore;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.filter.CorsFilter;
|
||||
|
@ -63,7 +64,22 @@ public class HaloConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public StringCacheStore stringCacheStore() {
|
||||
return new InMemoryCacheStore();
|
||||
StringCacheStore stringCacheStore;
|
||||
switch (haloProperties.getCache()) {
|
||||
case "level":
|
||||
stringCacheStore = new LevelCacheStore();
|
||||
break;
|
||||
|
||||
case "memory":
|
||||
default:
|
||||
//memory or default
|
||||
stringCacheStore = new InMemoryCacheStore();
|
||||
break;
|
||||
|
||||
}
|
||||
log.info("halo cache store load impl : [{}]", stringCacheStore.getClass());
|
||||
return stringCacheStore;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import freemarker.core.TemplateClassResolver;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateExceptionHandler;
|
||||
import freemarker.template.TemplateModel;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.BlockDirective;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.ExtendsDirective;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.PutDirective;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.jackson.JsonComponentModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -11,15 +15,20 @@ import org.springframework.context.annotation.ComponentScan;
|
|||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
|
||||
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.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
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;
|
||||
|
@ -28,32 +37,39 @@ 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.List;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||
import static run.halo.app.model.support.HaloConst.HALO_ADMIN_RELATIVE_PATH;
|
||||
import static run.halo.app.utils.HaloUtils.*;
|
||||
|
||||
/**
|
||||
* Mvc configuration.
|
||||
* Spring mvc configuration.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2018-01-02
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@ComponentScan(basePackages = "run.halo.app.controller")
|
||||
@PropertySource(value = "classpath:application.yaml", ignoreResourceNotFound = true, encoding = "UTF-8")
|
||||
public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||
public class WebMvcAutoConfiguration extends WebMvcConfigurationSupport {
|
||||
|
||||
private static final String FILE_PROTOCOL = "file:///";
|
||||
|
||||
private final PageableHandlerMethodArgumentResolver pageableResolver;
|
||||
|
||||
private final SortHandlerMethodArgumentResolver sortResolver;
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public WebMvcAutoConfiguration(HaloProperties haloProperties) {
|
||||
public WebMvcAutoConfiguration(PageableHandlerMethodArgumentResolver pageableResolver,
|
||||
SortHandlerMethodArgumentResolver sortResolver,
|
||||
HaloProperties haloProperties) {
|
||||
this.pageableResolver = pageableResolver;
|
||||
this.sortResolver = sortResolver;
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
|
@ -74,6 +90,8 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||
resolvers.add(new AuthenticationArgumentResolver());
|
||||
resolvers.add(pageableResolver);
|
||||
resolvers.add(sortResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,13 +102,17 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
String workDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getWorkDir(), FILE_SEPARATOR);
|
||||
String backupDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getBackupDir(), FILE_SEPARATOR);
|
||||
|
||||
// register /** resource handler.
|
||||
registry.addResourceHandler("/**")
|
||||
.addResourceLocations(workDir + "templates/themes/")
|
||||
.addResourceLocations(workDir + "templates/admin/")
|
||||
.addResourceLocations("classpath:/admin/")
|
||||
.addResourceLocations(workDir + "static/");
|
||||
|
||||
// register /themes/** resource handler.
|
||||
registry.addResourceHandler("/themes/**")
|
||||
.addResourceLocations(workDir + "templates/themes/");
|
||||
|
||||
String uploadUrlPattern = ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
|
||||
String adminPathPattern = ensureSuffix(haloProperties.getAdminPath(), URL_SEPARATOR) + "**";
|
||||
|
||||
|
@ -109,11 +131,22 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Map<String, TemplateModel> freemarkerLayoutDirectives() {
|
||||
Map<String, TemplateModel> freemarkerLayoutDirectives = new HashMap<>(5);
|
||||
freemarkerLayoutDirectives.put("extends", new ExtendsDirective());
|
||||
freemarkerLayoutDirectives.put("block", new BlockDirective());
|
||||
freemarkerLayoutDirectives.put("put", new PutDirective());
|
||||
|
||||
return freemarkerLayoutDirectives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuring freemarker template file path.
|
||||
*
|
||||
|
@ -142,6 +175,13 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
// Set predefined freemarker configuration
|
||||
configurer.setConfiguration(configuration);
|
||||
|
||||
// Set layout variable
|
||||
Map<String, Object> freemarkerVariables = new HashMap<>(3);
|
||||
|
||||
freemarkerVariables.put("layout", freemarkerLayoutDirectives());
|
||||
|
||||
configurer.setFreemarkerVariables(freemarkerVariables);
|
||||
|
||||
return configurer;
|
||||
}
|
||||
|
||||
|
@ -162,4 +202,54 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
resolver.setContentType("text/html; charset=UTF-8");
|
||||
registry.viewResolver(resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
|
||||
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.info("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("/swagger-ui.html");
|
||||
blackPatterns.add("/csrf");
|
||||
blackPatterns.add("/webjars/**");
|
||||
blackPatterns.add(uploadUrlPattern);
|
||||
blackPatterns.add(adminPathPattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,14 @@ public class HaloProperties {
|
|||
*/
|
||||
private Duration downloadTimeout = Duration.ofSeconds(30);
|
||||
|
||||
/**
|
||||
* cache store impl
|
||||
* memory
|
||||
* level
|
||||
*/
|
||||
private String cache = "memory";
|
||||
|
||||
|
||||
public HaloProperties() throws IOException {
|
||||
// Create work directory if not exist
|
||||
Files.createDirectories(Paths.get(workDir));
|
||||
|
|
|
@ -16,7 +16,6 @@ import run.halo.app.security.token.AuthToken;
|
|||
import run.halo.app.service.AdminService;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import run.halo.app.service.DataProcessService;
|
||||
import run.halo.app.service.ThemeSettingService;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2019-12-29
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/data/process")
|
||||
public class DataProcessController {
|
||||
|
||||
private final DataProcessService dataProcessService;
|
||||
|
||||
private final ThemeSettingService themeSettingService;
|
||||
|
||||
public DataProcessController(DataProcessService dataProcessService,
|
||||
ThemeSettingService themeSettingService) {
|
||||
this.dataProcessService = dataProcessService;
|
||||
this.themeSettingService = themeSettingService;
|
||||
}
|
||||
|
||||
@PutMapping("url/replace")
|
||||
@ApiOperation("Replace url in all table.")
|
||||
public void replaceUrl(@RequestParam("oldUrl") String oldUrl,
|
||||
@RequestParam("newUrl") String newUrl) {
|
||||
dataProcessService.replaceAllUrl(oldUrl, newUrl);
|
||||
}
|
||||
|
||||
@DeleteMapping("themes/settings/inactivated")
|
||||
@ApiOperation("Delete inactivated theme settings.")
|
||||
public void deleteInactivatedThemeSettings() {
|
||||
themeSettingService.deleteInactivated();
|
||||
}
|
||||
|
||||
@DeleteMapping("tags/unused")
|
||||
@ApiOperation("Delete unused tags")
|
||||
public void deleteUnusedTags() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@DeleteMapping("categories/unused")
|
||||
@ApiOperation("Delete unused categories")
|
||||
public void deleteUnusedCategories() {
|
||||
// TODO
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import run.halo.app.model.properties.NetlifyStaticDeployProperties;
|
||||
import run.halo.app.model.support.StaticPageFile;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.StaticPageService;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Static page controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-25
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/static_page")
|
||||
public class StaticPageController {
|
||||
|
||||
private final static String DEPLOY_API = "https://api.netlify.com/api/v1/sites/%s/deploys";
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final RestTemplate httpsRestTemplate;
|
||||
|
||||
private final StaticPageService staticPageService;
|
||||
|
||||
public StaticPageController(StaticPageService staticPageService,
|
||||
OptionService optionService,
|
||||
RestTemplate httpsRestTemplate) {
|
||||
this.staticPageService = staticPageService;
|
||||
this.optionService = optionService;
|
||||
this.httpsRestTemplate = httpsRestTemplate;
|
||||
|
||||
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
|
||||
this.httpsRestTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("List static page files.")
|
||||
public List<StaticPageFile> list() {
|
||||
return staticPageService.listFile();
|
||||
}
|
||||
|
||||
@GetMapping("generate")
|
||||
@ApiOperation("Generate static page files.")
|
||||
public void generate() {
|
||||
staticPageService.generate();
|
||||
}
|
||||
|
||||
@PostMapping("deploy")
|
||||
@ApiOperation("Deploy static page to remove platform")
|
||||
public void deploy() {
|
||||
staticPageService.deploy();
|
||||
}
|
||||
|
||||
@GetMapping("netlify")
|
||||
public void testNetlify() throws FileNotFoundException {
|
||||
String domain = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_DOMAIN).toString();
|
||||
String siteId = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_SITE_ID).toString();
|
||||
String token = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_TOKEN).toString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
headers.set("Content-Type", "application/zip");
|
||||
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
|
||||
|
||||
Path path = staticPageService.zipStaticPagesDirectory();
|
||||
|
||||
byte[] bytes = FileUtil.readBytes(path.toFile());
|
||||
|
||||
HttpEntity<byte[]> httpEntity = new HttpEntity<>(bytes, headers);
|
||||
|
||||
ResponseEntity<Object> responseEntity = httpsRestTemplate.postForEntity(String.format(DEPLOY_API, siteId), httpEntity, Object.class);
|
||||
}
|
||||
}
|
|
@ -1,35 +1,19 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import run.halo.app.cache.StringCacheStore;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostMeta;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.*;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Blog archive page controller
|
||||
*
|
||||
|
@ -45,129 +29,27 @@ public class ContentArchiveController {
|
|||
|
||||
private final PostService postService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final PostMetaService postMetaService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final StringCacheStore cacheStore;
|
||||
|
||||
|
||||
public ContentArchiveController(PostService postService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
PostMetaService postMetaService,
|
||||
PostTagService postTagService,
|
||||
OptionService optionService,
|
||||
StringCacheStore cacheStore) {
|
||||
this.postService = postService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postMetaService = postMetaService;
|
||||
this.postTagService = postTagService;
|
||||
this.optionService = optionService;
|
||||
this.cacheStore = cacheStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post archives page.
|
||||
*
|
||||
* @param model model
|
||||
* @return template path : themes/{theme}/archives.ftl
|
||||
*/
|
||||
@GetMapping
|
||||
public String archives(Model model) {
|
||||
return this.archives(model, 1, Sort.by(DESC, "createTime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post archives page.
|
||||
*
|
||||
* @param model model
|
||||
* @return template path : themes/{theme}/archives.ftl
|
||||
*/
|
||||
@GetMapping(value = "page/{page}")
|
||||
public String archives(Model model,
|
||||
@PathVariable(value = "page") Integer page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), sort);
|
||||
|
||||
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> postListVos = postService.convertToListVo(postPage);
|
||||
int[] pageRainbow = PageUtil.rainbow(page, postListVos.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute("is_archives", true);
|
||||
model.addAttribute("pageRainbow", pageRainbow);
|
||||
model.addAttribute("posts", postListVos);
|
||||
|
||||
return themeService.render("archives");
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post page.
|
||||
*
|
||||
* @param url post slug url.
|
||||
* @param token view token.
|
||||
* @param model model
|
||||
* @return template path: themes/{theme}/post.ftl
|
||||
*/
|
||||
@GetMapping("{url}")
|
||||
public String post(@PathVariable("url") String url,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
Post post = postService.getByUrl(url);
|
||||
|
||||
if (post.getStatus().equals(PostStatus.INTIMATE) && StringUtils.isEmpty(token)) {
|
||||
String redirect = String.format("%s/archives/%s/password", optionService.getBlogBaseUrl(), post.getUrl());
|
||||
return "redirect:" + redirect;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
post = postService.getBy(PostStatus.PUBLISHED, url);
|
||||
} else {
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该文章的访问权限");
|
||||
}
|
||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
||||
}
|
||||
postService.publishVisitEvent(post.getId());
|
||||
postService.getNextPost(post.getCreateTime()).ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
|
||||
postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost));
|
||||
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
|
||||
List<Tag> tags = postTagService.listTagsBy(post.getId());
|
||||
List<PostMeta> metas = postMetaService.listBy(post.getId());
|
||||
|
||||
model.addAttribute("is_post", true);
|
||||
model.addAttribute("post", postService.convertToDetailVo(post));
|
||||
model.addAttribute("categories", categories);
|
||||
model.addAttribute("tags", tags);
|
||||
model.addAttribute("metas", postMetaService.convertToMap(metas));
|
||||
|
||||
// TODO,Will be deprecated
|
||||
model.addAttribute("comments", Page.empty());
|
||||
|
||||
if (themeService.templateExists(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate() + HaloConst.SUFFIX_FTL)) {
|
||||
return themeService.render(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate());
|
||||
}
|
||||
|
||||
return themeService.render("post");
|
||||
}
|
||||
|
||||
@GetMapping(value = "{url}/password")
|
||||
@GetMapping(value = "{url:.*}/password")
|
||||
public String password(@PathVariable("url") String url,
|
||||
Model model) {
|
||||
model.addAttribute("url", url);
|
||||
return "common/template/post_password";
|
||||
}
|
||||
|
||||
@PostMapping(value = "{url}/password")
|
||||
@PostMapping(value = "{url:.*}/password")
|
||||
@CacheLock(traceRequest = true, expired = 2)
|
||||
public String password(@PathVariable("url") String url,
|
||||
@RequestParam(value = "password") String password) {
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.*;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Category controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-20
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping(value = "/categories")
|
||||
public class ContentCategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public ContentCategoryController(CategoryService categoryService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
PostService postService, OptionService optionService) {
|
||||
this.categoryService = categoryService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postService = postService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render category list page
|
||||
*
|
||||
* @return template path: themes/{theme}/categories.ftl
|
||||
*/
|
||||
@GetMapping
|
||||
public String categories(Model model) {
|
||||
model.addAttribute("is_categories", true);
|
||||
return themeService.render("categories");
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post list page by category
|
||||
*
|
||||
* @param model model
|
||||
* @param slugName slugName
|
||||
* @return template path: themes/{theme}/category.ftl
|
||||
*/
|
||||
@GetMapping(value = "{slugName}")
|
||||
public String categories(Model model,
|
||||
@PathVariable("slugName") String slugName) {
|
||||
return this.categories(model, slugName, 1, Sort.by(DESC, "createTime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post list page by category
|
||||
*
|
||||
* @param model model
|
||||
* @param slugName slugName
|
||||
* @param page current page number
|
||||
* @return template path: themes/{theme}/category.ftl
|
||||
*/
|
||||
@GetMapping("{slugName}/page/{page}")
|
||||
public String categories(Model model,
|
||||
@PathVariable("slugName") String slugName,
|
||||
@PathVariable("page") Integer page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
// Get category by slug name
|
||||
final Category category = categoryService.getBySlugNameOfNonNull(slugName);
|
||||
|
||||
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), sort);
|
||||
Page<Post> postPage = postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute("is_category", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("rainbow", rainbow);
|
||||
model.addAttribute("category", category);
|
||||
return themeService.render("category");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import run.halo.app.controller.content.model.CategoryModel;
|
||||
import run.halo.app.controller.content.model.PostModel;
|
||||
import run.halo.app.controller.content.model.SheetModel;
|
||||
import run.halo.app.controller.content.model.TagModel;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
import run.halo.app.model.properties.PermalinkProperties;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.SheetService;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping
|
||||
public class ContentContentController {
|
||||
|
||||
private final PostModel postModel;
|
||||
|
||||
private final SheetModel sheetModel;
|
||||
|
||||
private final CategoryModel categoryModel;
|
||||
|
||||
private final TagModel tagModel;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
public ContentContentController(PostModel postModel,
|
||||
SheetModel sheetModel,
|
||||
CategoryModel categoryModel,
|
||||
TagModel tagModel,
|
||||
OptionService optionService,
|
||||
PostService postService,
|
||||
SheetService sheetService) {
|
||||
this.postModel = postModel;
|
||||
this.sheetModel = sheetModel;
|
||||
this.categoryModel = categoryModel;
|
||||
this.tagModel = tagModel;
|
||||
this.optionService = optionService;
|
||||
this.postService = postService;
|
||||
this.sheetService = sheetService;
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
Model model) {
|
||||
String archivesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
|
||||
String categoriesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
|
||||
String tagsPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
|
||||
|
||||
if (archivesPrefix.equals(prefix)) {
|
||||
return postModel.list(1, model, "is_archives", "archives");
|
||||
} else if (categoriesPrefix.equals(prefix)) {
|
||||
return categoryModel.list(model);
|
||||
} else if (tagsPrefix.equals(prefix)) {
|
||||
return tagModel.list(model);
|
||||
} else {
|
||||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/page/{page:\\d+}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable(value = "page") Integer page,
|
||||
Model model) {
|
||||
String archivesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
|
||||
if (archivesPrefix.equals(prefix)) {
|
||||
return postModel.list(page, model, "is_archives", "archives");
|
||||
} else {
|
||||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/{url:.+}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable("url") String url,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
|
||||
String archivesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
|
||||
String sheetPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.SHEET_PREFIX, String.class, PermalinkProperties.SHEET_PREFIX.defaultValue());
|
||||
String categoriesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
|
||||
String tagsPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
|
||||
|
||||
if (postPermalinkType.equals(PostPermalinkType.DEFAULT) && archivesPrefix.equals(prefix)) {
|
||||
Post post = postService.getByUrl(url);
|
||||
return postModel.content(post, token, model);
|
||||
} else if (sheetPrefix.equals(prefix)) {
|
||||
Sheet sheet = sheetService.getByUrl(url);
|
||||
return sheetModel.content(sheet, token, model);
|
||||
} else if (categoriesPrefix.equals(prefix)) {
|
||||
return categoryModel.listPost(model, url, 1);
|
||||
} else if (tagsPrefix.equals(prefix)) {
|
||||
return tagModel.listPost(model, url, 1);
|
||||
} else {
|
||||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/{url}/page/{page:\\d+}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable("url") String url,
|
||||
@PathVariable("page") Integer page,
|
||||
Model model) {
|
||||
String categoriesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
|
||||
String tagsPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
|
||||
|
||||
if (categoriesPrefix.equals(prefix)) {
|
||||
return categoryModel.listPost(model, url, page);
|
||||
} else if (tagsPrefix.equals(prefix)) {
|
||||
return tagModel.listPost(model, url, page);
|
||||
} else {
|
||||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{year:\\d+}/{month:\\d+}/{url:.+}")
|
||||
public String content(@PathVariable("year") Integer year,
|
||||
@PathVariable("month") Integer month,
|
||||
@PathVariable("url") String url,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
|
||||
if (postPermalinkType.equals(PostPermalinkType.DATE)) {
|
||||
Post post = postService.getBy(year, month, url);
|
||||
return postModel.content(post, token, model);
|
||||
} else {
|
||||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{year:\\d+}/{month:\\d+}/{day:\\d+}/{url:.+}")
|
||||
public String content(@PathVariable("year") Integer year,
|
||||
@PathVariable("month") Integer month,
|
||||
@PathVariable("day") Integer day,
|
||||
@PathVariable("url") String url,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
|
||||
if (postPermalinkType.equals(PostPermalinkType.DAY)) {
|
||||
Post post = postService.getBy(year, month, day, url);
|
||||
return postModel.content(post, token, model);
|
||||
} else {
|
||||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +1,18 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import run.halo.app.controller.content.model.PostModel;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.properties.PostProperties;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Blog index page controller
|
||||
|
@ -36,25 +29,34 @@ public class ContentIndexController {
|
|||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
private final PostModel postModel;
|
||||
|
||||
public ContentIndexController(PostService postService,
|
||||
OptionService optionService,
|
||||
ThemeService themeService) {
|
||||
PostModel postModel) {
|
||||
this.postService = postService;
|
||||
this.optionService = optionService;
|
||||
this.themeService = themeService;
|
||||
this.postModel = postModel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render blog index
|
||||
*
|
||||
* @param p post id
|
||||
* @param model model
|
||||
* @return template path: themes/{theme}/index.ftl
|
||||
*/
|
||||
@GetMapping
|
||||
public String index(Model model) {
|
||||
public String index(Integer p, String token, Model model) {
|
||||
|
||||
PostPermalinkType permalinkType = optionService.getPostPermalinkType();
|
||||
|
||||
if (PostPermalinkType.ID.equals(permalinkType) && !Objects.isNull(p)) {
|
||||
Post post = postService.getById(p);
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
return this.index(model, 1);
|
||||
}
|
||||
|
||||
|
@ -68,18 +70,6 @@ public class ContentIndexController {
|
|||
@GetMapping(value = "page/{page}")
|
||||
public String index(Model model,
|
||||
@PathVariable(value = "page") Integer page) {
|
||||
String indexSort = optionService.getByPropertyOfNonNull(PostProperties.INDEX_SORT).toString();
|
||||
int pageSize = optionService.getPostPageSize();
|
||||
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "topPriority").and(Sort.by(DESC, indexSort)));
|
||||
|
||||
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
|
||||
int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute("is_index", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("rainbow", rainbow);
|
||||
return themeService.render("index");
|
||||
return postModel.list(page, model, "is_index", "index");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
@ -10,17 +9,9 @@ import org.springframework.ui.Model;
|
|||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import run.halo.app.cache.StringCacheStore;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.dto.PhotoDTO;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.model.vo.SheetDetailVO;
|
||||
import run.halo.app.service.PhotoService;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
|
@ -35,22 +26,14 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
|
|||
public class ContentSheetController {
|
||||
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PhotoService photoService;
|
||||
|
||||
private final StringCacheStore cacheStore;
|
||||
|
||||
public ContentSheetController(SheetService sheetService,
|
||||
ThemeService themeService,
|
||||
PhotoService photoService,
|
||||
StringCacheStore cacheStore) {
|
||||
this.sheetService = sheetService;
|
||||
public ContentSheetController(ThemeService themeService,
|
||||
PhotoService photoService) {
|
||||
this.themeService = themeService;
|
||||
this.photoService = photoService;
|
||||
this.cacheStore = cacheStore;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,50 +74,4 @@ public class ContentSheetController {
|
|||
public String links() {
|
||||
return themeService.render("links");
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom sheet
|
||||
*
|
||||
* @param url sheet url
|
||||
* @param token view token
|
||||
* @param model model
|
||||
* @return template path: themes/{theme}/sheet.ftl
|
||||
*/
|
||||
@GetMapping(value = "/s/{url}")
|
||||
public String sheet(@PathVariable(value = "url") String url,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
|
||||
Sheet sheet = sheetService.getByUrl(url);
|
||||
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
sheet = sheetService.getBy(PostStatus.PUBLISHED, url);
|
||||
} else {
|
||||
// render markdown to html when preview sheet
|
||||
sheet.setFormatContent(MarkdownUtils.renderHtml(sheet.getOriginalContent()));
|
||||
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该页面的访问权限"));
|
||||
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该页面的访问权限");
|
||||
}
|
||||
}
|
||||
sheetService.publishVisitEvent(sheet.getId());
|
||||
|
||||
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);
|
||||
|
||||
// sheet and post all can use
|
||||
model.addAttribute("sheet", sheetDetailVO);
|
||||
model.addAttribute("post", sheetDetailVO);
|
||||
model.addAttribute("is_sheet", true);
|
||||
|
||||
// TODO,Will be deprecated
|
||||
model.addAttribute("comments", Page.empty());
|
||||
|
||||
if (themeService.templateExists(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
|
||||
return themeService.render(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate());
|
||||
}
|
||||
return themeService.render("sheet");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.*;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Tag controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-21
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping(value = "/tags")
|
||||
public class ContentTagController {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
public ContentTagController(TagService tagService,
|
||||
PostService postService,
|
||||
PostTagService postTagService,
|
||||
OptionService optionService,
|
||||
ThemeService themeService) {
|
||||
this.tagService = tagService;
|
||||
this.postService = postService;
|
||||
this.postTagService = postTagService;
|
||||
this.optionService = optionService;
|
||||
this.themeService = themeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* All of tags
|
||||
*
|
||||
* @return template path: themes/{theme}/tags.ftl
|
||||
*/
|
||||
@GetMapping
|
||||
public String tags(Model model) {
|
||||
model.addAttribute("is_tags", true);
|
||||
return themeService.render("tags");
|
||||
}
|
||||
|
||||
/**
|
||||
* List tags by tag slug
|
||||
*
|
||||
* @param model model
|
||||
* @param slugName slug name
|
||||
* @return template path: themes/{theme}/tag.ftl
|
||||
*/
|
||||
@GetMapping(value = "{slugName}")
|
||||
public String tags(Model model,
|
||||
@PathVariable("slugName") String slugName) {
|
||||
return this.tags(model, slugName, 1, Sort.by(DESC, "createTime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* List tags by tag slug
|
||||
*
|
||||
* @param model model
|
||||
* @param slugName slug name
|
||||
* @param page current page
|
||||
* @return template path: themes/{theme}/tag.ftl
|
||||
*/
|
||||
@GetMapping(value = "{slugName}/page/{page}")
|
||||
public String tags(Model model,
|
||||
@PathVariable("slugName") String slugName,
|
||||
@PathVariable("page") Integer page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
// Get tag by slug name
|
||||
final Tag tag = tagService.getBySlugNameOfNonNull(slugName);
|
||||
|
||||
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), sort);
|
||||
Page<Post> postPage = postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute("is_tag", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("rainbow", rainbow);
|
||||
model.addAttribute("tag", tag);
|
||||
return themeService.render("tag");
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import run.halo.app.model.support.HaloConst;
|
|||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.UserService;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -48,7 +47,7 @@ public class MainController {
|
|||
}
|
||||
|
||||
@GetMapping("${halo.admin-path:admin}")
|
||||
public void admin(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
public void admin(HttpServletResponse response) throws IOException {
|
||||
String adminIndexRedirectUri = StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/") + INDEX_REDIRECT_URI;
|
||||
response.sendRedirect(adminIndexRedirectUri);
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ import run.halo.app.service.PostService;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Archive portal controller.
|
||||
* Content archive controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 4/2/19
|
||||
* @date 2019-04-02
|
||||
*/
|
||||
@RestController("ApiContentArchiveController")
|
||||
@RequestMapping("/api/content/archives")
|
||||
|
|
|
@ -21,10 +21,10 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Category portal controller.
|
||||
* Content category controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 6/9/19
|
||||
* @date 2019-06-09
|
||||
*/
|
||||
@RestController("ApiContentCategoryController")
|
||||
@RequestMapping("/api/content/categories")
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Content Journal controller.
|
||||
* Content journal controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
|
|
|
@ -15,11 +15,11 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Portal link controller.
|
||||
* Content link controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 4/3/19
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentLinkController")
|
||||
@RequestMapping("/api/content/links")
|
||||
|
|
|
@ -15,11 +15,11 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Portal menu controller.
|
||||
* Content menu controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 4/3/19
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentMenuController")
|
||||
@RequestMapping("/api/content/menus")
|
||||
|
|
|
@ -13,10 +13,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Portal option controller.
|
||||
* Content option controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 4/3/19
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentOptionController")
|
||||
@RequestMapping("/api/content/options")
|
||||
|
|
|
@ -26,10 +26,10 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Portal post controller.
|
||||
* Content post controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 4/2/19
|
||||
* @date 2019-04-02
|
||||
*/
|
||||
@RestController("ApiContentPostController")
|
||||
@RequestMapping("/api/content/posts")
|
||||
|
@ -130,6 +130,7 @@ public class PostController {
|
|||
@ApiOperation("Comments a post")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {
|
||||
postCommentService.validateCommentBlackListStatus();
|
||||
return postCommentService.convertTo(postCommentService.createBy(postCommentParam));
|
||||
}
|
||||
|
||||
|
|
|
@ -25,11 +25,11 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Sheet controller.
|
||||
* Content sheet controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 19-4-26
|
||||
* @date 2019-04-26
|
||||
*/
|
||||
@RestController("ApiContentSheetController")
|
||||
@RequestMapping("/api/content/sheets")
|
||||
|
|
|
@ -9,7 +9,7 @@ import run.halo.app.model.dto.StatisticWithUserDTO;
|
|||
import run.halo.app.service.StatisticService;
|
||||
|
||||
/**
|
||||
* Statistic controller.
|
||||
* Content statistic controller.
|
||||
*
|
||||
* @author ryan0up
|
||||
* @date 2019-12-16
|
||||
|
|
|
@ -22,11 +22,11 @@ import java.util.List;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Portal tag controller.
|
||||
* Content tag controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 4/2/19
|
||||
* @date 2019-04-02
|
||||
*/
|
||||
@RestController("ApiContentTagController")
|
||||
@RequestMapping("/api/content/tags")
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.handler.theme.config.support.ThemeProperty;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.ThemeSettingService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Content theme controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-17
|
||||
*/
|
||||
@RestController("ApiContentThemeController")
|
||||
@RequestMapping("/api/content/themes")
|
||||
public class ThemeController {
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final ThemeSettingService themeSettingService;
|
||||
|
||||
public ThemeController(ThemeService themeService, ThemeSettingService themeSettingService) {
|
||||
this.themeService = themeService;
|
||||
this.themeSettingService = themeSettingService;
|
||||
}
|
||||
|
||||
@GetMapping("activation")
|
||||
@ApiOperation("Gets activated theme property")
|
||||
public ThemeProperty getBy() {
|
||||
return themeService.getThemeOfNonNullBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@GetMapping("activation/settings")
|
||||
@ApiOperation("Lists activated theme settings")
|
||||
public Map<String, Object> listSettingsBy() {
|
||||
return themeSettingService.listAsMapBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
}
|
|
@ -8,10 +8,10 @@ import run.halo.app.model.dto.UserDTO;
|
|||
import run.halo.app.service.UserService;
|
||||
|
||||
/**
|
||||
* Portal user controller.
|
||||
* Content user controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 4/3/19
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentUserController")
|
||||
@RequestMapping("/api/content/users")
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.*;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Category Model.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-11
|
||||
*/
|
||||
@Component
|
||||
public class CategoryModel {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public CategoryModel(CategoryService categoryService, ThemeService themeService, PostCategoryService postCategoryService, PostService postService, OptionService optionService) {
|
||||
this.categoryService = categoryService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postService = postService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
public String list(Model model) {
|
||||
model.addAttribute("is_categories", true);
|
||||
return themeService.render("categories");
|
||||
}
|
||||
|
||||
public String listPost(Model model, String slugName, Integer page) {
|
||||
// Get category by slug name
|
||||
final Category category = categoryService.getBySlugNameOfNonNull(slugName);
|
||||
|
||||
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), Sort.by(DESC, "createTime"));
|
||||
Page<Post> postPage = postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute("is_category", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("rainbow", rainbow);
|
||||
model.addAttribute("category", category);
|
||||
return themeService.render("category");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.cache.StringCacheStore;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostMeta;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.model.vo.AdjacentPostVO;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.*;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Post Model
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
@Component
|
||||
public class PostModel {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostMetaService postMetaService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final StringCacheStore cacheStore;
|
||||
|
||||
public PostModel(PostService postService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
CategoryService categoryService,
|
||||
PostMetaService postMetaService,
|
||||
PostTagService postTagService,
|
||||
TagService tagService,
|
||||
OptionService optionService,
|
||||
StringCacheStore cacheStore) {
|
||||
this.postService = postService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.categoryService = categoryService;
|
||||
this.postMetaService = postMetaService;
|
||||
this.postTagService = postTagService;
|
||||
this.tagService = tagService;
|
||||
this.optionService = optionService;
|
||||
this.cacheStore = cacheStore;
|
||||
}
|
||||
|
||||
public String content(Post post, String token, Model model) {
|
||||
|
||||
if (post.getStatus().equals(PostStatus.INTIMATE) && StringUtils.isEmpty(token)) {
|
||||
String redirect = String
|
||||
.format("%s/archives/%s/password", optionService.getBlogBaseUrl(),
|
||||
post.getUrl());
|
||||
return "redirect:" + redirect;
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(token)) {
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class)
|
||||
.orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该文章的访问权限");
|
||||
}
|
||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
||||
}
|
||||
postService.publishVisitEvent(post.getId());
|
||||
|
||||
AdjacentPostVO adjacentPostVO = postService.getAdjacentPosts(post);
|
||||
adjacentPostVO.getOptionalPrePost().ifPresent(prePost -> model.addAttribute("prePost", prePost));
|
||||
adjacentPostVO.getOptionalNextPost().ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
|
||||
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
|
||||
List<Tag> tags = postTagService.listTagsBy(post.getId());
|
||||
List<PostMeta> metas = postMetaService.listBy(post.getId());
|
||||
|
||||
model.addAttribute("is_post", true);
|
||||
model.addAttribute("post", postService.convertToDetailVo(post));
|
||||
model.addAttribute("categories", categoryService.convertTo(categories));
|
||||
model.addAttribute("tags", tagService.convertTo(tags));
|
||||
model.addAttribute("metas", postMetaService.convertToMap(metas));
|
||||
|
||||
// TODO,Will be deprecated
|
||||
model.addAttribute("comments", Page.empty());
|
||||
|
||||
if (themeService.templateExists(
|
||||
ThemeService.CUSTOM_POST_PREFIX + post.getTemplate() + HaloConst.SUFFIX_FTL)) {
|
||||
return themeService.render(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate());
|
||||
}
|
||||
|
||||
return themeService.render("post");
|
||||
}
|
||||
|
||||
public String list(Integer page, Model model, String decide, String template) {
|
||||
int pageSize = optionService.getPostPageSize();
|
||||
Pageable pageable = PageRequest
|
||||
.of(page >= 1 ? page - 1 : page, pageSize, postService.getPostDefaultSort());
|
||||
|
||||
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
|
||||
int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute(decide, true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("rainbow", rainbow);
|
||||
model.addAttribute("pageRainbow", rainbow);
|
||||
return themeService.render(template);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.cache.StringCacheStore;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.model.vo.SheetDetailVO;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
|
||||
/**
|
||||
* Sheet model.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
@Component
|
||||
public class SheetModel {
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final StringCacheStore cacheStore;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
public SheetModel(SheetService sheetService, StringCacheStore cacheStore, ThemeService themeService) {
|
||||
this.sheetService = sheetService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.themeService = themeService;
|
||||
}
|
||||
|
||||
public String content(Sheet sheet, String token, Model model) {
|
||||
if (!StringUtils.isEmpty(token)) {
|
||||
// render markdown to html when preview sheet
|
||||
sheet.setFormatContent(MarkdownUtils.renderHtml(sheet.getOriginalContent()));
|
||||
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该页面的访问权限"));
|
||||
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该页面的访问权限");
|
||||
}
|
||||
}
|
||||
sheetService.publishVisitEvent(sheet.getId());
|
||||
|
||||
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);
|
||||
|
||||
// sheet and post all can use
|
||||
model.addAttribute("sheet", sheetDetailVO);
|
||||
model.addAttribute("post", sheetDetailVO);
|
||||
model.addAttribute("is_sheet", true);
|
||||
|
||||
// TODO,Will be deprecated
|
||||
model.addAttribute("comments", Page.empty());
|
||||
|
||||
if (themeService.templateExists(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
|
||||
return themeService.render(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate());
|
||||
}
|
||||
return themeService.render("sheet");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.*;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
/**
|
||||
* Tag Model.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-11
|
||||
*/
|
||||
@Component
|
||||
public class TagModel {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
public TagModel(TagService tagService, PostService postService, PostTagService postTagService, OptionService optionService, ThemeService themeService) {
|
||||
this.tagService = tagService;
|
||||
this.postService = postService;
|
||||
this.postTagService = postTagService;
|
||||
this.optionService = optionService;
|
||||
this.themeService = themeService;
|
||||
}
|
||||
|
||||
public String list(Model model) {
|
||||
model.addAttribute("is_tags", true);
|
||||
return themeService.render("tags");
|
||||
}
|
||||
|
||||
public String listPost(Model model, String slugName, Integer page) {
|
||||
// Get tag by slug name
|
||||
final Tag tag = tagService.getBySlugNameOfNonNull(slugName);
|
||||
|
||||
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), Sort.by(DESC, "createTime"));
|
||||
Page<Post> postPage = postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
|
||||
|
||||
model.addAttribute("is_tag", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("rainbow", rainbow);
|
||||
model.addAttribute("tag", tag);
|
||||
return themeService.render("tag");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of tools.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-17
|
||||
*/
|
||||
@Component
|
||||
public class ToolTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
public ToolTagDirective(Configuration configuration) {
|
||||
configuration.setSharedVariable("toolTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "rainbowPage":
|
||||
int page = Integer.parseInt(params.get("page").toString());
|
||||
int total = Integer.parseInt(params.get("total").toString());
|
||||
int display = Integer.parseInt(params.get("display").toString());
|
||||
env.setVariable("numbers", builder.build().wrap(PageUtil.rainbow(page, total, display)));
|
||||
break;
|
||||
case "random":
|
||||
int min = Integer.parseInt(params.get("min").toString());
|
||||
int max = Integer.parseInt(params.get("max").toString());
|
||||
env.setVariable("number", builder.build().wrap(RandomUtil.randomInt(min, max)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package run.halo.app.exception;
|
||||
|
||||
/**
|
||||
* Theme not support exception.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-02-03
|
||||
*/
|
||||
public class ThemeNotSupportException extends BadRequestException {
|
||||
|
||||
public ThemeNotSupportException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ThemeNotSupportException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import run.halo.app.utils.ImageUtils;
|
|||
import java.awt.image.BufferedImage;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
@ -142,14 +143,14 @@ public class LocalFileHandler implements FileHandler {
|
|||
// Check file type
|
||||
if (FileHandler.isImageType(uploadResult.getMediaType()) && !isSvg) {
|
||||
lock.lock();
|
||||
try {
|
||||
try (InputStream uploadFileInputStream = new FileInputStream(uploadPath.toFile())) {
|
||||
// Upload a thumbnail
|
||||
String thumbnailBasename = basename + THUMBNAIL_SUFFIX;
|
||||
String thumbnailSubFilePath = subDir + thumbnailBasename + '.' + extension;
|
||||
Path thumbnailPath = Paths.get(workDir + thumbnailSubFilePath);
|
||||
|
||||
// Read as image
|
||||
BufferedImage originalImage = ImageUtils.getImageFromFile(new FileInputStream(uploadPath.toFile()), extension);
|
||||
BufferedImage originalImage = ImageUtils.getImageFromFile(uploadFileInputStream, extension);
|
||||
// Set width and height
|
||||
uploadResult.setWidth(originalImage.getWidth());
|
||||
uploadResult.setHeight(originalImage.getHeight());
|
||||
|
@ -232,7 +233,7 @@ public class LocalFileHandler implements FileHandler {
|
|||
log.debug("Generated thumbnail image, and wrote the thumbnail to [{}]", thumbPath.toString());
|
||||
result = true;
|
||||
} catch (Throwable t) {
|
||||
log.warn("Failed to generate thumbnail: [{}]", thumbPath);
|
||||
log.warn("Failed to generate thumbnail: " + thumbPath, t);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.handler.migrate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.model.enums.MigrateType;
|
||||
|
||||
|
@ -9,6 +11,8 @@ import run.halo.app.model.enums.MigrateType;
|
|||
* @author ryanwang
|
||||
* @date 2019-10-30
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CnBlogsMigrateHandler implements MigrateHandler {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,7 +31,7 @@ public class MigrateHandlers {
|
|||
|
||||
public MigrateHandlers(ApplicationContext applicationContext) {
|
||||
// Add all migrate handler
|
||||
addFileHandlers(applicationContext.getBeansOfType(MigrateHandler.class).values());
|
||||
addMigrateHandlers(applicationContext.getBeansOfType(MigrateHandler.class).values());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -56,7 +56,7 @@ public class MigrateHandlers {
|
|||
* @return current migrate handlers
|
||||
*/
|
||||
@NonNull
|
||||
private MigrateHandlers addFileHandlers(@Nullable Collection<MigrateHandler> migrateHandlers) {
|
||||
private MigrateHandlers addMigrateHandlers(@Nullable Collection<MigrateHandler> migrateHandlers) {
|
||||
if (!CollectionUtils.isEmpty(migrateHandlers)) {
|
||||
this.migrateHandlers.addAll(migrateHandlers);
|
||||
}
|
||||
|
|
|
@ -1,34 +1,28 @@
|
|||
package run.halo.app.handler.migrate;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.exception.ServiceException;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.handler.migrate.converter.Converter;
|
||||
import run.halo.app.handler.migrate.converter.WordPressConverter;
|
||||
import run.halo.app.handler.migrate.support.vo.PostVO;
|
||||
import run.halo.app.handler.migrate.support.wordpress.Rss;
|
||||
import run.halo.app.model.enums.MigrateType;
|
||||
import run.halo.app.service.*;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
import run.halo.app.utils.WordPressMigrateUtils;
|
||||
import run.halo.app.utils.XmlMigrateUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WordPress migrate handler
|
||||
*
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-10-28
|
||||
*/
|
||||
@Slf4j
|
||||
|
@ -90,138 +84,21 @@ public class WordPressMigrateHandler implements MigrateHandler {
|
|||
public void migrate(MultipartFile file) {
|
||||
try {
|
||||
String migrationContent = FileCopyUtils.copyToString(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8));
|
||||
Element rootElement = WordPressMigrateUtils.getRootElement(new FileInputStream(migrationContent));
|
||||
Map<String, Object> resultSetMapping = WordPressMigrateUtils.getResultSetMapping(rootElement);
|
||||
String jsonString = XmlMigrateUtils.xml2jsonString(migrationContent);
|
||||
JSONObject json = JSONObject.parseObject(jsonString);
|
||||
Rss rss = json.getObject("rss", Rss.class);
|
||||
|
||||
// Handle categories
|
||||
List<Category> categories = handleCategories(resultSetMapping.get("wp:category"));
|
||||
// 转换
|
||||
Converter<Rss, List<PostVO>> converter = new WordPressConverter();
|
||||
|
||||
// Handle tags
|
||||
List<Tag> tags = handleTags(resultSetMapping.get("wp:tag"));
|
||||
List<PostVO> postVoList = converter.convertFrom(rss);
|
||||
|
||||
// Handle posts
|
||||
List<BasePost> posts = handlePosts(resultSetMapping.get("item"));
|
||||
|
||||
log.debug("Migrated posts: [{}]", posts);
|
||||
log.debug("Migrated posts: [{}]", postVoList);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("WordPress 导出文件 " + file.getOriginalFilename() + " 读取失败", e);
|
||||
throw new ServiceException("WordPress 入出文件 " + file.getOriginalFilename() + " 读取失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Category> handleCategories(@Nullable Object categoriesObject) {
|
||||
|
||||
if (!(categoriesObject instanceof List)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> categoryObjectList = (List<Object>) categoriesObject;
|
||||
|
||||
List<Category> result = new LinkedList<>();
|
||||
|
||||
categoryObjectList.forEach(categoryObject -> {
|
||||
|
||||
if (!(categoryObject instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> categoryMap = (Map<String, Object>) categoryObject;
|
||||
|
||||
String slugName = categoryMap.getOrDefault("wp:category_nicename", "").toString();
|
||||
|
||||
Category category = categoryService.getBySlugName(slugName);
|
||||
|
||||
if (null == category) {
|
||||
category = new Category();
|
||||
category.setName(categoryMap.getOrDefault("wp:cat_name", "").toString());
|
||||
category.setSlugName(slugName);
|
||||
category = categoryService.create(category);
|
||||
}
|
||||
|
||||
try {
|
||||
result.add(category);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to migrate a category", e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Tag> handleTags(@Nullable Object tagsObject) {
|
||||
|
||||
if (!(tagsObject instanceof List)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> tagObjectList = (List<Object>) tagsObject;
|
||||
|
||||
List<Tag> result = new LinkedList<>();
|
||||
|
||||
tagObjectList.forEach(tagObject -> {
|
||||
if (!(tagObject instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> tagMap = (Map<String, Object>) tagObject;
|
||||
|
||||
String slugName = tagMap.getOrDefault("wp:tag_slug", "").toString();
|
||||
|
||||
Tag tag = tagService.getBySlugName(slugName);
|
||||
|
||||
if (null == tag) {
|
||||
tag = new Tag();
|
||||
tag.setName(tagMap.getOrDefault("wp:tag_name", "").toString());
|
||||
tag.setSlugName(slugName);
|
||||
tag = tagService.create(tag);
|
||||
}
|
||||
|
||||
try {
|
||||
result.add(tag);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to migrate a tag", e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<BasePost> handlePosts(@Nullable Object postsObject) {
|
||||
if (!(postsObject instanceof List)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> postObjectList = (List<Object>) postsObject;
|
||||
|
||||
List<BasePost> result = new LinkedList<>();
|
||||
|
||||
postObjectList.forEach(postObject -> {
|
||||
if (!(postObject instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> postMap = (Map<String, Object>) postObject;
|
||||
|
||||
BasePost post = new BasePost();
|
||||
post.setTitle(postMap.getOrDefault("title", "").toString());
|
||||
post.setUrl(postMap.getOrDefault("wp:post_name", "").toString());
|
||||
post.setOriginalContent(MarkdownUtils.renderMarkdown(postMap.getOrDefault("content:encoded", "").toString()));
|
||||
post.setFormatContent(postMap.getOrDefault("content:encoded", "").toString());
|
||||
post.setSummary(postMap.getOrDefault("excerpt:encoded", "").toString());
|
||||
|
||||
String url = postMap.getOrDefault("wp:post_name", "").toString();
|
||||
|
||||
Post temp = postService.getByUrl(url);
|
||||
|
||||
if (temp != null) {
|
||||
post.setUrl(post.getUrl() + "_1");
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportType(MigrateType type) {
|
||||
return MigrateType.WORDPRESS.equals(type);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package run.halo.app.handler.migrate.converter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 博客迁移数据转换器,只定义从SOURCE转TARGET的单向转换
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:45
|
||||
*/
|
||||
public interface Converter<SOURCE, TARGET> {
|
||||
|
||||
/**
|
||||
* 将source转换为target
|
||||
*
|
||||
* @param s 需要转换的源对象
|
||||
* @return 返回转换得到的结果对象
|
||||
*/
|
||||
TARGET convertFrom(SOURCE s);
|
||||
|
||||
/**
|
||||
* 从SOURCE转为TARGET
|
||||
*
|
||||
* @param s source,需要转换的原始SOURCE集合
|
||||
* @param function 具体转换逻辑
|
||||
* @return 返回转换得到的TARGET对象
|
||||
*/
|
||||
default TARGET convertFrom(SOURCE s, Function<SOURCE, TARGET> function) {
|
||||
return function.apply(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量从SOURCE转换得到TARGET
|
||||
*
|
||||
* @param list 需要转换的原始SOURCE集合
|
||||
* @param function 具体转换逻辑
|
||||
* @return 返回转换得到的TARGET集合结果
|
||||
*/
|
||||
default List<TARGET> batchConverterFromDto(List<SOURCE> list, Function<SOURCE, TARGET> function) {
|
||||
return list.stream().map(s -> convertFrom(s, function)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package run.halo.app.handler.migrate.converter;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import run.halo.app.handler.migrate.support.vo.PostVO;
|
||||
import run.halo.app.handler.migrate.support.wordpress.*;
|
||||
import run.halo.app.handler.migrate.utils.RelationMapperUtils;
|
||||
import run.halo.app.model.entity.BaseComment;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* WordPress 博客数据迁移转换器
|
||||
*
|
||||
* @author guqing
|
||||
* @author ryanwang
|
||||
* @date 2020-01-18 16:50
|
||||
*/
|
||||
public class WordPressConverter implements Converter<Rss, List<PostVO>> {
|
||||
|
||||
@Override
|
||||
public List<PostVO> convertFrom(Rss rss) {
|
||||
return convertFrom(rss, this::apply);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义转换规则
|
||||
*/
|
||||
public List<PostVO> apply(Rss rss) {
|
||||
if (Objects.isNull(rss) || Objects.isNull(rss.getChannel())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Channel channel = rss.getChannel();
|
||||
List<Item> items = channel.getItems();
|
||||
|
||||
if (items == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return getBasePost(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets post vo list from items.
|
||||
*
|
||||
* @param items wordpress items.
|
||||
* @return a list of post vo.
|
||||
*/
|
||||
private List<PostVO> getBasePost(List<Item> items) {
|
||||
List<PostVO> posts = new ArrayList<>();
|
||||
if (items == null) {
|
||||
return posts;
|
||||
}
|
||||
|
||||
for (Item item : items) {
|
||||
PostVO postVo = new PostVO();
|
||||
// 设置文章
|
||||
BasePost post = getBasePostFromItem(item);
|
||||
postVo.setBasePost(post);
|
||||
|
||||
// 获取标签和分类
|
||||
List<WpCategory> categories = item.getCategories();
|
||||
List<Category> categoryModelList = new ArrayList<>();
|
||||
List<Tag> tags = new ArrayList<>();
|
||||
if (categories != null) {
|
||||
categories.forEach(category -> {
|
||||
String domain = category.getDomain();
|
||||
if ("post_tag".equals(domain)) {
|
||||
Tag tag = RelationMapperUtils.convertFrom(category, Tag.class);
|
||||
tags.add(tag);
|
||||
} else if ("category".equals(domain)) {
|
||||
Category categoryModel = RelationMapperUtils.convertFrom(category, Category.class);
|
||||
categoryModelList.add(categoryModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 设置标签和分类
|
||||
postVo.setCategories(categoryModelList);
|
||||
postVo.setTags(tags);
|
||||
|
||||
// 设置评论
|
||||
List<BaseComment> comments = getCommentsFromItem(item);
|
||||
postVo.setComments(comments);
|
||||
|
||||
posts.add(postVo);
|
||||
}
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets item's comments.
|
||||
*
|
||||
* @param item wordpress item.
|
||||
* @return a list of baseComment.
|
||||
*/
|
||||
private List<BaseComment> getCommentsFromItem(Item item) {
|
||||
List<BaseComment> baseComments = new ArrayList<>();
|
||||
if (Objects.isNull(item) || Objects.isNull(item.getComments())) {
|
||||
return baseComments;
|
||||
}
|
||||
|
||||
List<Comment> comments = item.getComments();
|
||||
for (Comment comment : comments) {
|
||||
BaseComment baseComment = RelationMapperUtils.convertFrom(comment, BaseComment.class);
|
||||
Date commentDate = DateUtil.parseDateTime(comment.getCommentDate());
|
||||
baseComment.setCreateTime(commentDate);
|
||||
baseComment.setUpdateTime(commentDate);
|
||||
baseComments.add(baseComment);
|
||||
}
|
||||
|
||||
return baseComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets base post from item.
|
||||
*
|
||||
* @param item wordpress item.
|
||||
* @return base post.
|
||||
*/
|
||||
private BasePost getBasePostFromItem(Item item) {
|
||||
BasePost post = RelationMapperUtils.convertFrom(item, BasePost.class);
|
||||
Date postDate = DateUtil.parseDateTime(item.getPostDate());
|
||||
if (StringUtils.isNoneEmpty(post.getFormatContent())) {
|
||||
post.setOriginalContent(MarkdownUtils.renderMarkdown(post.getFormatContent()));
|
||||
}
|
||||
post.setCreateTime(postDate);
|
||||
post.setUpdateTime(postDate);
|
||||
post.setEditTime(postDate);
|
||||
return post;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package run.halo.app.handler.migrate.support.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import run.halo.app.model.entity.BaseComment;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:52
|
||||
*/
|
||||
@Data
|
||||
public class PostVO {
|
||||
private BasePost basePost;
|
||||
private List<Tag> tags;
|
||||
private List<Category> categories;
|
||||
private List<BaseComment> comments;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* WordPress导出的xml中对应的channel节点下的子节点将被映射为该类的属性
|
||||
* </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2019-11-17 13:57
|
||||
*/
|
||||
@Data
|
||||
public class Channel {
|
||||
@JSONField(name = "title")
|
||||
private String title;
|
||||
private String link;
|
||||
private String description;
|
||||
private String pubDate;
|
||||
private String language;
|
||||
private String author;
|
||||
|
||||
@JSONField(name = "item")
|
||||
private List<Item> items;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import run.halo.app.handler.migrate.utils.PropertyMappingTo;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* WordPress导出的xml中对于的comment节点下的子节点值将被映射为该类的属性,
|
||||
* 最终会被转换为{@link run.halo.app.model.entity.PostComment}
|
||||
* </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @author ryanwang
|
||||
* @date 2019-11-17 14:01
|
||||
*/
|
||||
@Data
|
||||
public class Comment {
|
||||
|
||||
@JSONField(name = "wp:comment_id")
|
||||
@PropertyMappingTo("id")
|
||||
private String commentId;
|
||||
|
||||
@JSONField(name = "wp:comment_author")
|
||||
@PropertyMappingTo("author")
|
||||
private String commentAuthor;
|
||||
|
||||
@JSONField(name = "wp:comment_author_email")
|
||||
@PropertyMappingTo("email")
|
||||
private String commentAuthorEmail;
|
||||
|
||||
@JSONField(name = "wp:comment_author_url")
|
||||
@PropertyMappingTo("authorUrl")
|
||||
private String commentAuthorUrl;
|
||||
|
||||
@JSONField(name = "wp:comment_author_IP")
|
||||
@PropertyMappingTo("ipAddress")
|
||||
private String commentAuthorIp;
|
||||
|
||||
@JSONField(name = "wp:comment_date")
|
||||
private String commentDate;
|
||||
|
||||
@JSONField(name = "wp:comment_content")
|
||||
@PropertyMappingTo("content")
|
||||
private String commentContent;
|
||||
|
||||
@JSONField(name = "wp:comment_approved")
|
||||
private String commentApproved;
|
||||
|
||||
@JSONField(name = "wp:comment_parent")
|
||||
@PropertyMappingTo("parentId")
|
||||
private Long commentParent;
|
||||
|
||||
@JSONField(name = "wp:comment_user_id")
|
||||
private String commentUserId;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import run.halo.app.handler.migrate.utils.PropertyMappingTo;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p> WordPress导出的xml中对于的item子节点的值将会被映射到该类的属性上,最终被解析为文章属性{@link BasePost} </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @author ryanwang
|
||||
* @date 2019-11-17 13:59
|
||||
*/
|
||||
@Data
|
||||
public class Item {
|
||||
|
||||
private String title;
|
||||
|
||||
private String pubDate;
|
||||
|
||||
private String description;
|
||||
|
||||
@JSONField(name = "content:encoded")
|
||||
@PropertyMappingTo("formatContent")
|
||||
private String content;
|
||||
|
||||
@JSONField(name = "excerpt:encoded")
|
||||
@PropertyMappingTo("summary")
|
||||
private String excerpt;
|
||||
|
||||
@JSONField(name = "wp:post_date")
|
||||
private String postDate;
|
||||
|
||||
@JSONField(name = "wp:comment_status")
|
||||
private String commentStatus;
|
||||
|
||||
@JSONField(name = "wp:post_name")
|
||||
@PropertyMappingTo("url")
|
||||
private String postName;
|
||||
|
||||
@JSONField(name = "wp:status")
|
||||
private String status;
|
||||
|
||||
@JSONField(name = "wp:post_password")
|
||||
@PropertyMappingTo("password")
|
||||
private String postPassword;
|
||||
|
||||
@JSONField(name = "wp:is_sticky")
|
||||
@PropertyMappingTo("topPriority")
|
||||
private Integer isSticky;
|
||||
|
||||
@JSONField(name = "wp:comment")
|
||||
private List<Comment> comments;
|
||||
|
||||
@JSONField(name = "category")
|
||||
private List<WpCategory> categories;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* WordPress导出的xml数据中对应的rss节点下的子节点channel将会被映射到该类属性
|
||||
* 如果不写这个Rss类包装想要的Channel,会无法解析到想要的结果
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-17 00:45
|
||||
*/
|
||||
@Data
|
||||
public class Rss {
|
||||
private Channel channel;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import lombok.Data;
|
||||
import run.halo.app.handler.migrate.utils.PropertyMappingTo;
|
||||
import run.halo.app.model.entity.PostCategory;
|
||||
|
||||
/**
|
||||
* WordPress导出的xml数据中对应的category的子节点将会被映射到该类属性,
|
||||
* 最终会被转换为文章分类{@link PostCategory}
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:09
|
||||
*/
|
||||
@Data
|
||||
public class WpCategory {
|
||||
private String domain;
|
||||
|
||||
@PropertyMappingTo("slugName")
|
||||
private String nicename;
|
||||
|
||||
@PropertyMappingTo("name")
|
||||
private String content;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package run.halo.app.handler.migrate.utils;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 该注解用于定义两个对象之间的属性映射关系
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-1-19 13:51
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PropertyMappingTo {
|
||||
/**
|
||||
* value对应的是目标对象的属性名称
|
||||
*
|
||||
* @return 返回源对象属性对应的目标对象的属性名
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package run.halo.app.handler.migrate.utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 关系映射工具类,用于使用@PropertyMappingTo注解映射源对象与目标对象属性之间的关系
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-19 01:47
|
||||
*/
|
||||
public class RelationMapperUtils {
|
||||
private RelationMapperUtils() {
|
||||
}
|
||||
|
||||
public static <SOURCE, TARGET> TARGET convertFrom(SOURCE source, Class<TARGET> targetClazz) {
|
||||
Map<String, Class> methodNameAndTypeMap = new HashMap<>(16);
|
||||
TARGET target;
|
||||
try {
|
||||
// 通过类的详情信息,创建目标对象 这一步等同于Hello target = new Hello();
|
||||
target = targetClazz.getConstructor().newInstance();
|
||||
Field[] targetFields = targetClazz.getDeclaredFields();
|
||||
for (Field field : targetFields) {
|
||||
methodNameAndTypeMap.put(field.getName(), field.getType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("目标对象创建失败");
|
||||
}
|
||||
|
||||
return propertyMapper(source, target, methodNameAndTypeMap);
|
||||
}
|
||||
|
||||
private static <SOURCE, TARGET> TARGET propertyMapper(SOURCE source, TARGET target, Map<String, Class> methodNameAndTypeMap) {
|
||||
// 判断传入源数据是否为空,如果空,则抛自定义异常
|
||||
if (null == source) {
|
||||
throw new IllegalArgumentException("数据源不能为空");
|
||||
}
|
||||
// 获取源对象的类的详情信息
|
||||
Class<?> sourceClazz = source.getClass();
|
||||
// 获取源对象的所有属性
|
||||
Field[] sourceFields = sourceClazz.getDeclaredFields();
|
||||
|
||||
// 循环取到源对象的单个属性
|
||||
for (Field sourceField : sourceFields) {
|
||||
boolean sourceFieldHasAnnotation = sourceField.isAnnotationPresent(PropertyMappingTo.class);
|
||||
|
||||
String sourceFieldName = sourceField.getName();
|
||||
// 如果源对象没有添加注解则默认使用源对象属性名去寻找对于的目标对象属性名
|
||||
if (sourceFieldHasAnnotation) {
|
||||
PropertyMappingTo propertyMapping = sourceField.getAnnotation(PropertyMappingTo.class);
|
||||
String targetFieldName = propertyMapping.value();
|
||||
copyProperty(sourceFieldName, targetFieldName, source, target, sourceField.getType(), methodNameAndTypeMap);
|
||||
} else if (methodNameAndTypeMap.containsKey(sourceFieldName)) {
|
||||
// 如果源对象和目标对象的属性名相同则直接设置
|
||||
// 获取目标对象的属性名,将属性名首字母大写,拼接如:setUsername、setId的字符串
|
||||
// 判断源对象的属性名、属性类型是否和目标对象的属性名、属性类型一致
|
||||
copyProperty(sourceFieldName, sourceFieldName, source, target, sourceField.getType(), methodNameAndTypeMap);
|
||||
}
|
||||
}
|
||||
// 返回赋值得到的目标对象
|
||||
return target;
|
||||
}
|
||||
|
||||
private static <SOURCE, TARGET> void copyProperty(String sourceFieldName, String targetFieldName,
|
||||
SOURCE source, TARGET target, Class<?> sourceType,
|
||||
Map<String, Class> methodNameAndTypeMap) {
|
||||
try {
|
||||
Class<?> sourceClazz = source.getClass();
|
||||
Class<?> targetClazz = target.getClass();
|
||||
// 获取源对象的属性名,将属性名首字母大写,拼接如:getUsername、getId的字符串
|
||||
String sourceGetMethodName = getterName(sourceFieldName);
|
||||
// 获得属性的get方法
|
||||
Method sourceMethod = sourceClazz.getMethod(sourceGetMethodName);
|
||||
// 调用get方法
|
||||
Object sourceFieldValue = sourceMethod.invoke(source);
|
||||
|
||||
Class methodType = methodNameAndTypeMap.get(targetFieldName);
|
||||
// 通过字段名称得到set方法名称
|
||||
String targetSetMethodName = setterName(targetFieldName);
|
||||
if (methodType == sourceType) {
|
||||
// 调用方法,并将源对象get方法返回值作为参数传入
|
||||
Method targetSetMethod = targetClazz.getMethod(targetSetMethodName, sourceType);
|
||||
targetSetMethod.invoke(target, sourceFieldValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("转换失败,请检查属性类型是否匹配");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过属性名称得到对于的get或set方法
|
||||
*
|
||||
* @param fieldName 属性名称
|
||||
* @param methodType 方法类型可选值为:get或set
|
||||
* @return 返回对象的get或set方法的名称
|
||||
*/
|
||||
private static String getMethodNameFromField(String fieldName, String methodType) {
|
||||
if (fieldName == null || fieldName.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the second char is upper, make 'get' + field name as getter name. For example, eBlog -> getBlog
|
||||
*/
|
||||
if (fieldName.length() > 2) {
|
||||
String second = fieldName.substring(1, 2);
|
||||
if (second.equals(second.toUpperCase())) {
|
||||
return methodType + fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
// Common situation
|
||||
fieldName = methodType + fieldName.substring(0, 1).toUpperCase() +
|
||||
fieldName.substring(1);
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
private static String getterName(String fieldName) {
|
||||
return getMethodNameFromField(fieldName, "get");
|
||||
}
|
||||
|
||||
private static String setterName(String fieldName) {
|
||||
return getMethodNameFromField(fieldName, "set");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package run.halo.app.handler.staticdeploy;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.enums.StaticDeployType;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Git deploy handler.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class GitStaticDeployHandler implements StaticDeployHandler {
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public GitStaticDeployHandler(OptionService optionService) {
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deploy() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportType(StaticDeployType type) {
|
||||
return StaticDeployType.GIT.equals(type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package run.halo.app.handler.staticdeploy;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import run.halo.app.model.enums.StaticDeployType;
|
||||
import run.halo.app.model.properties.NetlifyStaticDeployProperties;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.StaticPageService;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Netlify deploy handler.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NetlifyStaticDeployHandler implements StaticDeployHandler {
|
||||
|
||||
private final static String DEPLOY_API = "https://api.netlify.com/api/v1/sites/%s/deploys";
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final RestTemplate httpsRestTemplate;
|
||||
|
||||
private final StaticPageService staticPageService;
|
||||
|
||||
public NetlifyStaticDeployHandler(OptionService optionService,
|
||||
RestTemplate httpsRestTemplate,
|
||||
StaticPageService staticPageService) {
|
||||
this.optionService = optionService;
|
||||
this.httpsRestTemplate = httpsRestTemplate;
|
||||
this.staticPageService = staticPageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deploy() {
|
||||
String domain = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_DOMAIN).toString();
|
||||
String siteId = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_SITE_ID).toString();
|
||||
String token = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_TOKEN).toString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
headers.set("Content-Type", "application/zip");
|
||||
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
|
||||
|
||||
Path path = staticPageService.zipStaticPagesDirectory();
|
||||
|
||||
byte[] bytes = FileUtil.readBytes(path.toFile());
|
||||
|
||||
HttpEntity<byte[]> httpEntity = new HttpEntity<>(bytes, headers);
|
||||
|
||||
ResponseEntity<Object> responseEntity = httpsRestTemplate.postForEntity(String.format(DEPLOY_API, siteId), httpEntity, Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportType(StaticDeployType type) {
|
||||
return StaticDeployType.NETLIFY.equals(type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package run.halo.app.handler.staticdeploy;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import run.halo.app.model.enums.StaticDeployType;
|
||||
|
||||
/**
|
||||
* Static deploy handler interface class.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
public interface StaticDeployHandler {
|
||||
|
||||
/**
|
||||
* do deploy.
|
||||
*/
|
||||
void deploy();
|
||||
|
||||
/**
|
||||
* Checks if the given type is supported.
|
||||
*
|
||||
* @param type deploy type
|
||||
* @return true if supported; false or else
|
||||
*/
|
||||
boolean supportType(@Nullable StaticDeployType type);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package run.halo.app.handler.staticdeploy;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import run.halo.app.exception.FileOperationException;
|
||||
import run.halo.app.model.enums.StaticDeployType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Static deploy handlers.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class StaticDeployHandlers {
|
||||
|
||||
private final Collection<StaticDeployHandler> staticDeployHandlers = new LinkedList<>();
|
||||
|
||||
public StaticDeployHandlers(ApplicationContext applicationContext) {
|
||||
// Add all static deploy handler
|
||||
addStaticDeployHandlers(applicationContext.getBeansOfType(StaticDeployHandler.class).values());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* do deploy.
|
||||
*
|
||||
* @param staticDeployType static deploy type
|
||||
*/
|
||||
public void deploy(@NonNull StaticDeployType staticDeployType) {
|
||||
Assert.notNull(staticDeployType, "Static deploy type must not be null");
|
||||
|
||||
for (StaticDeployHandler staticDeployHandler : staticDeployHandlers) {
|
||||
if (staticDeployHandler.supportType(staticDeployType)) {
|
||||
staticDeployHandler.deploy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileOperationException("No available static deploy handler to deploy static pages").setErrorData(staticDeployType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds static deploy handlers.
|
||||
*
|
||||
* @param staticDeployHandlers static deploy handler collection
|
||||
* @return current file handlers
|
||||
*/
|
||||
@NonNull
|
||||
public StaticDeployHandlers addStaticDeployHandlers(@Nullable Collection<StaticDeployHandler> staticDeployHandlers) {
|
||||
if (!CollectionUtils.isEmpty(staticDeployHandlers)) {
|
||||
this.staticDeployHandlers.addAll(staticDeployHandlers);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -54,6 +54,11 @@ public class ThemeProperty {
|
|||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* Require halo version.
|
||||
*/
|
||||
private String require;
|
||||
|
||||
/**
|
||||
* Theme author.
|
||||
*/
|
||||
|
|
|
@ -10,8 +10,10 @@ import org.springframework.core.annotation.Order;
|
|||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.event.options.OptionUpdatedEvent;
|
||||
import run.halo.app.event.theme.ThemeActivatedEvent;
|
||||
import run.halo.app.event.theme.ThemeUpdatedEvent;
|
||||
import run.halo.app.event.user.UserUpdatedEvent;
|
||||
import run.halo.app.handler.theme.config.support.ThemeProperty;
|
||||
import run.halo.app.model.properties.OtherProperties;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
@ -22,7 +24,8 @@ import run.halo.app.service.UserService;
|
|||
* Freemarker config aware listener.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 19-4-20
|
||||
* @author ryanwang
|
||||
* @date 2019-04-20
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
|
@ -67,6 +70,13 @@ public class FreemarkerConfigAwareListener {
|
|||
loadThemeConfig();
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onThemeUpdatedEvent(ThemeUpdatedEvent event) throws TemplateModelException {
|
||||
log.debug("Received theme updated event");
|
||||
|
||||
loadThemeConfig();
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onUserUpdate(UserUpdatedEvent event) throws TemplateModelException {
|
||||
log.debug("Received user updated event, user id: [{}]", event.getUserId());
|
||||
|
@ -96,9 +106,21 @@ public class FreemarkerConfigAwareListener {
|
|||
}
|
||||
|
||||
private void loadThemeConfig() throws TemplateModelException {
|
||||
|
||||
// Get current activated theme.
|
||||
ThemeProperty activatedTheme = themeService.getActivatedTheme();
|
||||
|
||||
Boolean enabledAbsolutePath = optionService.getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class, true);
|
||||
|
||||
String themeBasePath = (enabledAbsolutePath ? optionService.getBlogBaseUrl() : "") + "/themes/" + activatedTheme.getFolderName();
|
||||
|
||||
configuration.setSharedVariable("theme", activatedTheme);
|
||||
configuration.setSharedVariable("static", optionService.getBlogBaseUrl() + "/" + activatedTheme.getFolderName());
|
||||
|
||||
// TODO: It will be removed in future versions
|
||||
configuration.setSharedVariable("static", themeBasePath);
|
||||
|
||||
configuration.setSharedVariable("theme_base", themeBasePath);
|
||||
|
||||
configuration.setSharedVariable("settings", themeSettingService.listAsMapBy(themeService.getActivatedThemeId()));
|
||||
log.debug("Loaded theme and settings");
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ import java.util.Date;
|
|||
* Category output dto.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/19/19
|
||||
* @author ryanwang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
|
@ -30,4 +31,6 @@ public class CategoryDTO implements OutputConverter<CategoryDTO, Category> {
|
|||
private Integer parentId;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
private String fullPath;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import java.util.Date;
|
|||
* Tag output dto.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/19/19
|
||||
* @author ryanwang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@Data
|
||||
public class TagDTO implements OutputConverter<TagDTO, Tag> {
|
||||
|
@ -22,4 +23,6 @@ public class TagDTO implements OutputConverter<TagDTO, Tag> {
|
|||
private String slugName;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
private String fullPath;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import java.util.Date;
|
|||
* Base post minimal output dto.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
|
@ -32,4 +34,6 @@ public class BasePostMinimalDTO implements OutputConverter<BasePostMinimalDTO, B
|
|||
private Date createTime;
|
||||
|
||||
private Date editTime;
|
||||
|
||||
private String fullPath;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package run.halo.app.model.entity;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* comment_black_list
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "comment_black_list")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CommentBlackList extends BaseEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "ip_address", columnDefinition = "VARCHAR(127) NOT NULL")
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 封禁时间
|
||||
*/
|
||||
@Column(name = "ban_time")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date banTime;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 封禁状态
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/5
|
||||
*/
|
||||
@Getter
|
||||
public enum BanStatusEnum {
|
||||
/**
|
||||
* 封禁状态
|
||||
*/
|
||||
NORMAL(0),
|
||||
;
|
||||
|
||||
private int status;
|
||||
|
||||
BanStatusEnum(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 评论违规类型枚举
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/4
|
||||
*/
|
||||
@Getter
|
||||
public enum CommentViolationTypeEnum {
|
||||
/**
|
||||
* 评论违规类型
|
||||
*/
|
||||
NORMAL(0),
|
||||
/**
|
||||
* 频繁
|
||||
*/
|
||||
FREQUENTLY(1),
|
||||
;
|
||||
|
||||
private int type;
|
||||
|
||||
CommentViolationTypeEnum(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
/**
|
||||
* Global path type.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-02-01
|
||||
*/
|
||||
public enum GlobalPathType implements ValueEnum<Integer> {
|
||||
|
||||
/**
|
||||
* Relative path.
|
||||
*/
|
||||
RELATIVE(0),
|
||||
|
||||
/**
|
||||
* Absolute path.
|
||||
*/
|
||||
ABSOLUTE(1);
|
||||
|
||||
private Integer value;
|
||||
|
||||
GlobalPathType(Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
/**
|
||||
* Post Permalink type enum.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
public enum PostPermalinkType implements ValueEnum<Integer> {
|
||||
|
||||
/**
|
||||
* /archives/${url}
|
||||
*/
|
||||
DEFAULT(0),
|
||||
|
||||
/**
|
||||
* /1970/01/01/${url}
|
||||
*/
|
||||
DATE(1),
|
||||
|
||||
/**
|
||||
* /1970/01/${url}
|
||||
*/
|
||||
DAY(2),
|
||||
|
||||
/**
|
||||
* /?p=${id}
|
||||
*/
|
||||
ID(3);
|
||||
|
||||
private Integer value;
|
||||
|
||||
PostPermalinkType(Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
/**
|
||||
* Static deploy type.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
public enum StaticDeployType implements ValueEnum<Integer> {
|
||||
|
||||
/**
|
||||
* Deploy static pages in remote git repository, such as github pages,gitee pages,coding pages.etc.
|
||||
*/
|
||||
GIT(0),
|
||||
|
||||
/**
|
||||
* Deploy static pages in netlify.
|
||||
*/
|
||||
NETLIFY(1);
|
||||
|
||||
private Integer value;
|
||||
|
||||
StaticDeployType(Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enum value.
|
||||
*
|
||||
* @return enum value
|
||||
*/
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,12 @@ public enum CommentProperties implements PropertyEnum {
|
|||
|
||||
CONTENT_PLACEHOLDER("comment_content_placeholder", String.class, ""),
|
||||
|
||||
INTERNAL_PLUGIN_JS("comment_internal_plugin_js", String.class, "//cdn.jsdelivr.net/gh/halo-dev/halo-comment@latest/dist/halo-comment.min.js");
|
||||
INTERNAL_PLUGIN_JS("comment_internal_plugin_js", String.class, "//cdn.jsdelivr.net/gh/halo-dev/halo-comment@latest/dist/halo-comment.min.js"),
|
||||
|
||||
COMMENT_BAN_TIME("comment_ban_time", Integer.class, "10"),
|
||||
|
||||
COMMENT_RANGE("comment_range", Integer.class, "30");
|
||||
|
||||
|
||||
private final String value;
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package run.halo.app.model.properties;
|
||||
|
||||
/**
|
||||
* Git static deploy properties.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
public enum GitStaticDeployProperties implements PropertyEnum {
|
||||
|
||||
/**
|
||||
* Git static deploy domain.
|
||||
*/
|
||||
GIT_DOMAIN("git_static_deploy_domain", String.class, ""),
|
||||
|
||||
/**
|
||||
* Git static deploy repository.
|
||||
*/
|
||||
GIT_REPOSITORY("git_static_deploy_repository", String.class, ""),
|
||||
|
||||
/**
|
||||
* Git static deploy branch.
|
||||
*/
|
||||
GIT_BRANCH("git_static_deploy_branch", String.class, "master"),
|
||||
|
||||
/**
|
||||
* Git static deploy username.
|
||||
*/
|
||||
GIT_USERNAME("git_static_deploy_username", String.class, ""),
|
||||
|
||||
/**
|
||||
* Git static deploy email.
|
||||
*/
|
||||
GIT_EMAIL("git_static_deploy_email", String.class, ""),
|
||||
|
||||
/**
|
||||
* Git static deploy token.
|
||||
*/
|
||||
GIT_TOKEN("git_static_deploy_token", String.class, ""),
|
||||
|
||||
/**
|
||||
* Git static deploy cname.
|
||||
*/
|
||||
GIT_CNAME("git_static_deploy_cname", String.class, "");
|
||||
|
||||
private final String value;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String defaultValue;
|
||||
|
||||
GitStaticDeployProperties(String value, Class<?> type, String defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
if (!PropertyEnum.isSupportedType(type)) {
|
||||
throw new IllegalArgumentException("Unsupported blog property type: " + type);
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package run.halo.app.model.properties;
|
||||
|
||||
/**
|
||||
* Netlify static deploy properties.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
public enum NetlifyStaticDeployProperties implements PropertyEnum {
|
||||
|
||||
/**
|
||||
* Netlify static deploy domain.
|
||||
*/
|
||||
NETLIFY_DOMAIN("netlify_static_deploy_domain", String.class, ""),
|
||||
|
||||
/**
|
||||
* Netlify static deploy site id.
|
||||
*/
|
||||
NETLIFY_SITE_ID("netlify_static_deploy_site_id", String.class, ""),
|
||||
|
||||
/**
|
||||
* Netlify static deploy token.
|
||||
*/
|
||||
NETLIFY_TOKEN("netlify_static_deploy_token", String.class, "");
|
||||
|
||||
private final String value;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String defaultValue;
|
||||
|
||||
NetlifyStaticDeployProperties(String value, Class<?> type, String defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
if (!PropertyEnum.isSupportedType(type)) {
|
||||
throw new IllegalArgumentException("Unsupported blog property type: " + type);
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,12 @@ public enum OtherProperties implements PropertyEnum {
|
|||
/**
|
||||
* Statistics platform code,such as Google Analytics.
|
||||
*/
|
||||
STATISTICS_CODE("blog_statistics_code", String.class, "");
|
||||
STATISTICS_CODE("blog_statistics_code", String.class, ""),
|
||||
|
||||
/**
|
||||
* Global absolute path enabled.
|
||||
*/
|
||||
GLOBAL_ABSOLUTE_PATH_ENABLED("global_absolute_path_enabled", Boolean.class, "true");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package run.halo.app.model.properties;
|
||||
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
|
||||
/**
|
||||
* Permalink properties enum.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
public enum PermalinkProperties implements PropertyEnum {
|
||||
|
||||
/**
|
||||
* Post Permalink type.
|
||||
*/
|
||||
POST_PERMALINK_TYPE("post_permalink_type", PostPermalinkType.class, PostPermalinkType.DEFAULT.name()),
|
||||
|
||||
/**
|
||||
* Categories prefix
|
||||
* such as: /categories or /categories/${slugName}
|
||||
*/
|
||||
CATEGORIES_PREFIX("categories_prefix", String.class, "categories"),
|
||||
|
||||
/**
|
||||
* Tags prefix
|
||||
* such as: /tags or /tags/${slugName}
|
||||
*/
|
||||
TAGS_PREFIX("tags_prefix", String.class, "tags"),
|
||||
|
||||
/**
|
||||
* Archives prefix.
|
||||
* such as: /archives
|
||||
*/
|
||||
ARCHIVES_PREFIX("archives_prefix", String.class, "archives"),
|
||||
|
||||
/**
|
||||
* Sheet prefix
|
||||
* such as: /s/${url}
|
||||
*/
|
||||
SHEET_PREFIX("sheet_prefix", String.class, "s"),
|
||||
|
||||
/**
|
||||
* Path suffix
|
||||
* such as: .html or .jsp
|
||||
*/
|
||||
PATH_SUFFIX("path_suffix", String.class, "");
|
||||
|
||||
private final String value;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String defaultValue;
|
||||
|
||||
PermalinkProperties(String value, Class<?> type, String defaultValue) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -155,6 +155,10 @@ public interface PropertyEnum extends ValueEnum<String> {
|
|||
propertyEnumClasses.add(SeoProperties.class);
|
||||
propertyEnumClasses.add(UpOssProperties.class);
|
||||
propertyEnumClasses.add(ApiProperties.class);
|
||||
propertyEnumClasses.add(StaticDeployProperties.class);
|
||||
propertyEnumClasses.add(GitStaticDeployProperties.class);
|
||||
propertyEnumClasses.add(NetlifyStaticDeployProperties.class);
|
||||
propertyEnumClasses.add(PermalinkProperties.class);
|
||||
|
||||
Map<String, PropertyEnum> result = new HashMap<>();
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package run.halo.app.model.properties;
|
||||
|
||||
import run.halo.app.model.enums.StaticDeployType;
|
||||
|
||||
/**
|
||||
* Static deploy properties.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
public enum StaticDeployProperties implements PropertyEnum {
|
||||
|
||||
/**
|
||||
* static deploy type
|
||||
*/
|
||||
DEPLOY_TYPE("static_deploy_type", StaticDeployType.class, StaticDeployType.GIT.name());
|
||||
|
||||
private final String value;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String defaultValue;
|
||||
|
||||
|
||||
StaticDeployProperties(String value, Class<?> type, String defaultValue) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -30,6 +30,11 @@ public class HaloConst {
|
|||
*/
|
||||
public final static String HALO_BACKUP_PREFIX = "halo-backup-";
|
||||
|
||||
/**
|
||||
* Static pages pack prefix.
|
||||
*/
|
||||
public final static String STATIC_PAGE_PACK_PREFIX = "static-pages-";
|
||||
|
||||
/**
|
||||
* Default theme name.
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,8 @@ import java.util.List;
|
|||
@ToString
|
||||
public class StaticFile implements Comparator<StaticFile> {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String path;
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package run.halo.app.model.support;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Static page dto.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-26
|
||||
*/
|
||||
@Data
|
||||
public class StaticPageFile implements Comparator<StaticPageFile> {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private Boolean isFile;
|
||||
|
||||
private List<StaticPageFile> children;
|
||||
|
||||
@Override
|
||||
public int compare(StaticPageFile leftFile, StaticPageFile rightFile) {
|
||||
if (leftFile.isFile && !rightFile.isFile) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!leftFile.isFile && rightFile.isFile) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return leftFile.getName().compareTo(rightFile.getName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package run.halo.app.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import run.halo.app.model.entity.Post;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* AdjacentPost class
|
||||
*
|
||||
* @author zhouchunjie
|
||||
* @date 2020/1/12
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AdjacentPostVO {
|
||||
|
||||
private Post prePost;
|
||||
private Post nextPost;
|
||||
|
||||
public Optional<Post> getOptionalPrePost() {
|
||||
return Optional.ofNullable(this.getPrePost());
|
||||
}
|
||||
|
||||
public Optional<Post> getOptionalNextPost() {
|
||||
return Optional.ofNullable(this.getNextPost());
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ import java.util.List;
|
|||
*
|
||||
* @author johnniang
|
||||
* @author guqing
|
||||
* @author ryanwang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package run.halo.app.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import run.halo.app.model.entity.CommentBlackList;
|
||||
import run.halo.app.repository.base.BaseRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 评论黑名单Repository
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
public interface CommentBlackListRepository extends BaseRepository<CommentBlackList, Long> {
|
||||
|
||||
/**
|
||||
* 根据IP地址获取数据
|
||||
*
|
||||
* @param ipAddress
|
||||
* @return
|
||||
*/
|
||||
Optional<CommentBlackList> findByIpAddress(String ipAddress);
|
||||
|
||||
/**
|
||||
* Update Comment BlackList By IPAddress
|
||||
*
|
||||
* @param commentBlackList
|
||||
* @return result
|
||||
*/
|
||||
@Modifying
|
||||
@Query("UPDATE CommentBlackList SET banTime=:#{#commentBlackList.banTime} WHERE ipAddress=:#{#commentBlackList.ipAddress}")
|
||||
int updateByIpAddress(@Param("commentBlackList") CommentBlackList commentBlackList);
|
||||
}
|
|
@ -8,6 +8,7 @@ import run.halo.app.model.projection.CommentCountProjection;
|
|||
import run.halo.app.repository.base.BaseCommentRepository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -45,4 +46,15 @@ public interface PostCommentRepository extends BaseCommentRepository<PostComment
|
|||
@NonNull
|
||||
@Override
|
||||
List<CommentChildrenCountProjection> findDirectChildrenCount(@NonNull Collection<Long> commentIds);
|
||||
|
||||
/**
|
||||
* 根据时间范围和IP地址统计评论次数
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 评论次数
|
||||
*/
|
||||
@Query("SELECT COUNT(id) FROM PostComment WHERE ipAddress=?1 AND updateTime BETWEEN ?2 AND ?3 AND status <> 2")
|
||||
int countByIpAndTime(String ipAddress, Date startTime, Date endTime);
|
||||
}
|
||||
|
|
|
@ -2,15 +2,20 @@ package run.halo.app.repository;
|
|||
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.repository.base.BasePostRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* Post repository.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
public interface PostRepository extends BasePostRepository<Post>, JpaSpecificationExecutor<Post> {
|
||||
|
||||
|
@ -32,4 +37,51 @@ public interface PostRepository extends BasePostRepository<Post>, JpaSpecificati
|
|||
@Query("select sum(p.likes) from Post p")
|
||||
Long countLike();
|
||||
|
||||
/**
|
||||
* Find by post year and month and url.
|
||||
*
|
||||
* @param year post create year
|
||||
* @param month post create month
|
||||
* @param url post url
|
||||
* @return a optional of post
|
||||
*/
|
||||
@Query("select post from Post post where DateUtil.year(post.createTime) = :year and DateUtil.month(post.createTime) = :month and post.url = :url")
|
||||
Optional<Post> findBy(@Param("year") Integer year, @Param("month") Integer month, @Param("url") String url);
|
||||
|
||||
/**
|
||||
* Find by post year and month and url and status.
|
||||
*
|
||||
* @param year post create year
|
||||
* @param month post create month
|
||||
* @param url post url
|
||||
* @param status post status
|
||||
* @return a optional of post
|
||||
*/
|
||||
@Query("select post from Post post where DateUtil.year(post.createTime) = :year and DateUtil.month(post.createTime) = :month and post.url = :url and post.status = :status")
|
||||
Optional<Post> findBy(@Param("year") Integer year, @Param("month") Integer month, @Param("url") String url, @Param("status") PostStatus status);
|
||||
|
||||
/**
|
||||
* Find by post year and month and day and url.
|
||||
*
|
||||
* @param year post create year
|
||||
* @param month post create month
|
||||
* @param day post create day
|
||||
* @param url post url
|
||||
* @return a optional of post
|
||||
*/
|
||||
@Query("select post from Post post where DateUtil.year(post.createTime) = :year and DateUtil.month(post.createTime) = :month and DateUtil.dayOfMonth(post.createTime) = :day and post.url = :url")
|
||||
Optional<Post> findBy(@Param("year") Integer year, @Param("month") Integer month, @Param("day") Integer day, @Param("url") String url);
|
||||
|
||||
/**
|
||||
* Find by post year and month and day and url and status.
|
||||
*
|
||||
* @param year post create year
|
||||
* @param month post create month
|
||||
* @param day post create day
|
||||
* @param url post url
|
||||
* @param status post status
|
||||
* @return a optional of post
|
||||
*/
|
||||
@Query("select post from Post post where DateUtil.year(post.createTime) = :year and DateUtil.month(post.createTime) = :month and DateUtil.dayOfMonth(post.createTime) = :day and post.url = :url and post.status = :status")
|
||||
Optional<Post> findBy(@Param("year") Integer year, @Param("month") Integer month, @Param("day") Integer day, @Param("url") String url, @Param("status") PostStatus status);
|
||||
}
|
||||
|
|
|
@ -42,4 +42,11 @@ public interface ThemeSettingRepository extends BaseRepository<ThemeSetting, Int
|
|||
*/
|
||||
@NonNull
|
||||
Optional<ThemeSetting> findByThemeIdAndKey(@NonNull String themeId, @NonNull String key);
|
||||
|
||||
/**
|
||||
* Deletes inactivated theme settings.
|
||||
*
|
||||
* @param activatedThemeId activated theme id.
|
||||
*/
|
||||
void deleteByThemeIdIsNot(@NonNull String activatedThemeId);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.Optional;
|
|||
* Base post repository.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
public interface BasePostRepository<POST extends BasePost> extends BaseRepository<POST, Integer> {
|
||||
|
@ -99,6 +100,16 @@ public interface BasePostRepository<POST extends BasePost> extends BaseRepositor
|
|||
@NonNull
|
||||
Optional<POST> getByUrlAndStatus(@NonNull String url, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Gets post by id and status.
|
||||
*
|
||||
* @param id id must not be blank
|
||||
* @param status status must not be null
|
||||
* @return an optional post
|
||||
*/
|
||||
@NonNull
|
||||
Optional<POST> getByIdAndStatus(@NonNull Integer id, @NonNull PostStatus status);
|
||||
|
||||
|
||||
/**
|
||||
* Counts posts by status and type.
|
||||
|
|
|
@ -7,8 +7,6 @@ import run.halo.app.model.params.LoginParam;
|
|||
import run.halo.app.model.params.ResetPasswordParam;
|
||||
import run.halo.app.security.token.AuthToken;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Admin service interface.
|
||||
*
|
||||
|
|
|
@ -83,4 +83,13 @@ public interface AttachmentService extends CrudService<Attachment, Integer> {
|
|||
* @return list of type.
|
||||
*/
|
||||
List<AttachmentType> listAllType();
|
||||
|
||||
/**
|
||||
* Replace attachment url in batch.
|
||||
*
|
||||
* @param oldUrl old blog url.
|
||||
* @param newUrl new blog url.
|
||||
* @return replaced attachments.
|
||||
*/
|
||||
List<Attachment> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import run.halo.app.model.enums.CommentViolationTypeEnum;
|
||||
|
||||
/**
|
||||
* Comment BlackList Service
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
public interface CommentBlackListService {
|
||||
/**
|
||||
* 评论封禁状态
|
||||
*
|
||||
* @param ipAddress ip地址
|
||||
* @return boolean
|
||||
*/
|
||||
CommentViolationTypeEnum commentsBanStatus(String ipAddress);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Data process service interface.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-29
|
||||
*/
|
||||
public interface DataProcessService {
|
||||
|
||||
/**
|
||||
* Replace all url.
|
||||
*
|
||||
* @param oldUrl old url must not be null.
|
||||
* @param newUrl new url must not be null.
|
||||
*/
|
||||
void replaceAllUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
}
|
|
@ -10,6 +10,7 @@ import run.halo.app.exception.MissingPropertyException;
|
|||
import run.halo.app.model.dto.OptionDTO;
|
||||
import run.halo.app.model.dto.OptionSimpleDTO;
|
||||
import run.halo.app.model.entity.Option;
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
import run.halo.app.model.params.OptionParam;
|
||||
import run.halo.app.model.params.OptionQuery;
|
||||
|
@ -338,6 +339,22 @@ public interface OptionService extends CrudService<Option, Integer> {
|
|||
*/
|
||||
long getBirthday();
|
||||
|
||||
/**
|
||||
* Get post permalink type.
|
||||
*
|
||||
* @return PostPermalinkType
|
||||
*/
|
||||
PostPermalinkType getPostPermalinkType();
|
||||
|
||||
/**
|
||||
* Replace option url in batch.
|
||||
*
|
||||
* @param oldUrl old blog url.
|
||||
* @param newUrl new blog url.
|
||||
* @return replaced options.
|
||||
*/
|
||||
List<OptionDTO> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
|
||||
/**
|
||||
* Converts to option output dto.
|
||||
*
|
||||
|
|
|
@ -80,4 +80,13 @@ public interface PhotoService extends CrudService<Photo, Integer> {
|
|||
* @return list of teams
|
||||
*/
|
||||
List<String> listAllTeams();
|
||||
|
||||
/**
|
||||
* Replace photo url in batch.
|
||||
*
|
||||
* @param oldUrl old blog url.
|
||||
* @param newUrl new blog url.
|
||||
* @return replaced photos.
|
||||
*/
|
||||
List<PhotoDTO> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
}
|
||||
|
|
|
@ -49,4 +49,9 @@ public interface PostCommentService extends BaseCommentService<PostComment> {
|
|||
|
||||
@NonNull
|
||||
Page<PostCommentWithPostVO> pageTreeBy(@NonNull CommentQuery commentQuery, @NonNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Validate CommentBlackList Status
|
||||
*/
|
||||
void validateCommentBlackListStatus();
|
||||
}
|
||||
|
|
|
@ -2,18 +2,16 @@ package run.halo.app.service;
|
|||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostMeta;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.params.PostQuery;
|
||||
import run.halo.app.model.vo.ArchiveMonthVO;
|
||||
import run.halo.app.model.vo.ArchiveYearVO;
|
||||
import run.halo.app.model.vo.PostDetailVO;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.model.vo.*;
|
||||
import run.halo.app.service.base.BasePostService;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -96,6 +94,54 @@ public interface PostService extends BasePostService<Post> {
|
|||
@Override
|
||||
Post getBy(@NonNull PostStatus status, @NonNull String url);
|
||||
|
||||
/**
|
||||
* Gets post by post year and month and url.
|
||||
*
|
||||
* @param year post create year.
|
||||
* @param month post create month.
|
||||
* @param url post url.
|
||||
* @return post info
|
||||
*/
|
||||
@NonNull
|
||||
Post getBy(@NonNull Integer year, @NonNull Integer month, @NonNull String url);
|
||||
|
||||
/**
|
||||
* Gets post by post year and month and url.
|
||||
*
|
||||
* @param year post create year.
|
||||
* @param month post create month.
|
||||
* @param url post url.
|
||||
* @param status post status.
|
||||
* @return post info
|
||||
*/
|
||||
@NonNull
|
||||
Post getBy(@NonNull Integer year, @NonNull Integer month, @NonNull String url, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Gets post by post year and month and url.
|
||||
*
|
||||
* @param year post create year.
|
||||
* @param month post create month.
|
||||
* @param day post create day.
|
||||
* @param url post url.
|
||||
* @return post info
|
||||
*/
|
||||
@NonNull
|
||||
Post getBy(@NonNull Integer year, @NonNull Integer month, @NonNull Integer day, @NonNull String url);
|
||||
|
||||
/**
|
||||
* Gets post by post year and month and url.
|
||||
*
|
||||
* @param year post create year.
|
||||
* @param month post create month.
|
||||
* @param day post create day.
|
||||
* @param url post url.
|
||||
* @param status post status.
|
||||
* @return post info
|
||||
*/
|
||||
@NonNull
|
||||
Post getBy(@NonNull Integer year, @NonNull Integer month, @NonNull Integer day, @NonNull String url, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Removes posts in batch.
|
||||
*
|
||||
|
@ -167,14 +213,6 @@ public interface PostService extends BasePostService<Post> {
|
|||
@NonNull
|
||||
Page<PostListVO> convertToListVo(@NonNull Page<Post> postPage);
|
||||
|
||||
/**
|
||||
* Converts to a page of post detail dto.
|
||||
*
|
||||
* @param postPage post page must not be null
|
||||
* @return a page of post detail dto
|
||||
*/
|
||||
Page<BasePostDetailDTO> convertToDetailDto(@NonNull Page<Post> postPage);
|
||||
|
||||
/**
|
||||
* Converts to a page of detail vo.
|
||||
*
|
||||
|
@ -189,4 +227,23 @@ public interface PostService extends BasePostService<Post> {
|
|||
* @param postId postId must not be null
|
||||
*/
|
||||
void publishVisitEvent(@NonNull Integer postId);
|
||||
|
||||
/**
|
||||
* Gets pre && next post.
|
||||
*
|
||||
* @param currentPost post must not be null
|
||||
* @return AdjacentPostVO. it contains prePost and nextPost.
|
||||
* AdjacentPostVO will not be null. But prePost and nextPost may be null.
|
||||
*/
|
||||
@NotNull
|
||||
AdjacentPostVO getAdjacentPosts(Post currentPost);
|
||||
|
||||
/**
|
||||
* Get Post Pageable default sort
|
||||
*
|
||||
* @return
|
||||
* @Desc contains three parts. First, Top Priority; Second, From Custom index sort; Third, basic id sort
|
||||
*/
|
||||
@NotNull
|
||||
Sort getPostDefaultSort();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import run.halo.app.model.support.StaticPageFile;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||
import static run.halo.app.model.support.HaloConst.TEMP_DIR;
|
||||
import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||
|
||||
/**
|
||||
* Static Page service interface.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-25
|
||||
*/
|
||||
public interface StaticPageService {
|
||||
|
||||
/**
|
||||
* Static page folder location.
|
||||
*/
|
||||
String PAGES_FOLDER = "static_pages";
|
||||
|
||||
|
||||
String STATIC_PAGE_PACK_DIR = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "static-pages-pack" + FILE_SEPARATOR;
|
||||
|
||||
String[] USELESS_FILE_SUFFIX = {"ftl", "md", "yaml", "yml", "gitignore"};
|
||||
|
||||
/**
|
||||
* Generate pages.
|
||||
*/
|
||||
void generate();
|
||||
|
||||
/**
|
||||
* Deploy static pages.
|
||||
*/
|
||||
void deploy();
|
||||
|
||||
/**
|
||||
* Zip static pages directory.
|
||||
*
|
||||
* @return zip path
|
||||
*/
|
||||
Path zipStaticPagesDirectory();
|
||||
|
||||
/**
|
||||
* List file of generated static page.
|
||||
*
|
||||
* @return a list of generated static page.
|
||||
*/
|
||||
List<StaticPageFile> listFile();
|
||||
}
|
|
@ -64,6 +64,11 @@ public interface ThemeService {
|
|||
*/
|
||||
String RENDER_TEMPLATE = "themes/%s/%s";
|
||||
|
||||
/**
|
||||
* Render template with suffix.
|
||||
*/
|
||||
String RENDER_TEMPLATE_SUFFIX = "themes/%s/%s.ftl";
|
||||
|
||||
/**
|
||||
* Theme cache key.
|
||||
*/
|
||||
|
@ -231,6 +236,15 @@ public interface ThemeService {
|
|||
@NonNull
|
||||
String render(@NonNull String pageName);
|
||||
|
||||
/**
|
||||
* Renders a theme page.
|
||||
*
|
||||
* @param pageName must not be blank
|
||||
* @return full path of the theme page
|
||||
*/
|
||||
@NonNull
|
||||
String renderWithSuffix(@NonNull String pageName);
|
||||
|
||||
/**
|
||||
* Gets current theme id.
|
||||
*
|
||||
|
|
|
@ -55,4 +55,18 @@ public interface ThemeSettingService {
|
|||
*/
|
||||
@NonNull
|
||||
Map<String, Object> listAsMapBy(@NonNull String themeId);
|
||||
|
||||
/**
|
||||
* Replace theme setting url in batch.
|
||||
*
|
||||
* @param oldUrl old blog url.
|
||||
* @param newUrl new blog url.
|
||||
* @return replaced theme settings.
|
||||
*/
|
||||
List<ThemeSetting> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
|
||||
/**
|
||||
* Delete unused theme setting.
|
||||
*/
|
||||
void deleteInactivated();
|
||||
}
|
||||
|
|
|
@ -304,4 +304,13 @@ public interface BaseCommentService<COMMENT extends BaseComment> extends CrudSer
|
|||
*/
|
||||
<T extends BaseCommentDTO> Page<T> filterIpAddress(@NonNull Page<T> commentPage);
|
||||
|
||||
/**
|
||||
* Replace comment url in batch.
|
||||
*
|
||||
* @param oldUrl old blog url.
|
||||
* @param newUrl new blog url.
|
||||
* @return replaced comments.
|
||||
*/
|
||||
List<BaseCommentDTO> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ import java.util.Optional;
|
|||
* Base post service implementation.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 19-4-24
|
||||
* @author ryanwang
|
||||
* @date 2019-04-24
|
||||
*/
|
||||
public interface BasePostService<POST extends BasePost> extends CrudService<POST, Integer> {
|
||||
|
||||
|
@ -63,6 +64,16 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
|
|||
@NonNull
|
||||
POST getBy(@NonNull PostStatus status, @NonNull String url);
|
||||
|
||||
/**
|
||||
* Gets post by post status and id.
|
||||
*
|
||||
* @param status post status must not be null
|
||||
* @param id post id must not be blank
|
||||
* @return post info
|
||||
*/
|
||||
@NonNull
|
||||
POST getBy(@NonNull PostStatus status, @NonNull Integer id);
|
||||
|
||||
/**
|
||||
* Lists all posts by post status.
|
||||
*
|
||||
|
@ -287,4 +298,14 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
|
|||
*/
|
||||
@NonNull
|
||||
List<POST> updateStatusByIds(@NonNull List<Integer> ids, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Replace post blog url in batch.
|
||||
*
|
||||
* @param oldUrl old blog url.
|
||||
* @param newUrl new blog url.
|
||||
* @return replaced posts.
|
||||
*/
|
||||
@NonNull
|
||||
List<BasePostDetailDTO> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.file.FileReader;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
@ -38,9 +37,6 @@ import run.halo.app.service.*;
|
|||
import run.halo.app.utils.FileUtils;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
@ -49,6 +45,8 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -477,6 +475,8 @@ public class AdminServiceImpl implements AdminService {
|
|||
|
||||
File file = new File(haloProperties.getWorkDir(), LOG_PATH);
|
||||
|
||||
List<String> linesArray = new ArrayList<>();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
if (!file.exists()) {
|
||||
|
@ -497,8 +497,7 @@ public class AdminServiceImpl implements AdminService {
|
|||
randomAccessFile.seek(pos);
|
||||
if (randomAccessFile.readByte() == '\n') {
|
||||
String line = randomAccessFile.readLine();
|
||||
result.append(new String(line.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
|
||||
result.append(StringUtils.LF);
|
||||
linesArray.add(new String(line.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
|
||||
count++;
|
||||
if (count == lines) {
|
||||
break;
|
||||
|
@ -507,8 +506,7 @@ public class AdminServiceImpl implements AdminService {
|
|||
}
|
||||
if (pos == 0) {
|
||||
randomAccessFile.seek(0);
|
||||
result.append(new String(randomAccessFile.readLine().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
|
||||
result.append(StringUtils.LF);
|
||||
linesArray.add(new String(randomAccessFile.readLine().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -522,6 +520,14 @@ public class AdminServiceImpl implements AdminService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(linesArray);
|
||||
|
||||
linesArray.forEach(line -> {
|
||||
result.append(line)
|
||||
.append(StringUtils.LF);
|
||||
});
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import run.halo.app.model.entity.Attachment;
|
|||
import run.halo.app.model.enums.AttachmentType;
|
||||
import run.halo.app.model.params.AttachmentQuery;
|
||||
import run.halo.app.model.properties.AttachmentProperties;
|
||||
import run.halo.app.model.properties.OtherProperties;
|
||||
import run.halo.app.model.support.UploadResult;
|
||||
import run.halo.app.repository.AttachmentRepository;
|
||||
import run.halo.app.service.AttachmentService;
|
||||
|
@ -157,13 +158,15 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
|
|||
// Get blog base url
|
||||
String blogBaseUrl = optionService.getBlogBaseUrl();
|
||||
|
||||
Boolean enabledAbsolutePath = optionService.getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class, true);
|
||||
|
||||
// Convert to output dto
|
||||
AttachmentDTO attachmentDTO = new AttachmentDTO().convertFrom(attachment);
|
||||
|
||||
if (Objects.equals(attachmentDTO.getType(), AttachmentType.LOCAL)) {
|
||||
// Append blog base url to path and thumbnail
|
||||
String fullPath = StringUtils.join(blogBaseUrl, "/", attachmentDTO.getPath());
|
||||
String fullThumbPath = StringUtils.join(blogBaseUrl, "/", attachmentDTO.getThumbPath());
|
||||
String fullPath = StringUtils.join(enabledAbsolutePath ? blogBaseUrl : "", "/", attachmentDTO.getPath());
|
||||
String fullThumbPath = StringUtils.join(enabledAbsolutePath ? blogBaseUrl : "", "/", attachmentDTO.getThumbPath());
|
||||
|
||||
// Set full path and full thumb path
|
||||
attachmentDTO.setPath(fullPath);
|
||||
|
@ -183,6 +186,22 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
|
|||
return attachmentRepository.findAllType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Attachment> replaceUrl(String oldUrl, String newUrl) {
|
||||
List<Attachment> attachments = listAll();
|
||||
List<Attachment> replaced = new ArrayList<>();
|
||||
attachments.forEach(attachment -> {
|
||||
if (StringUtils.isNotEmpty(attachment.getPath())) {
|
||||
attachment.setPath(attachment.getPath().replaceAll(oldUrl, newUrl));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(attachment.getThumbPath())) {
|
||||
attachment.setThumbPath(attachment.getThumbPath().replaceAll(oldUrl, newUrl));
|
||||
}
|
||||
replaced.add(attachment);
|
||||
});
|
||||
return updateInBatch(replaced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment create(Attachment attachment) {
|
||||
Assert.notNull(attachment, "Attachment must not be null");
|
||||
|
|
|
@ -610,6 +610,20 @@ public abstract class BaseCommentServiceImpl<COMMENT extends BaseComment> extend
|
|||
return commentPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BaseCommentDTO> replaceUrl(String oldUrl, String newUrl) {
|
||||
List<COMMENT> comments = listAll();
|
||||
List<COMMENT> replaced = new ArrayList<>();
|
||||
comments.forEach(comment -> {
|
||||
if (StringUtils.isNotEmpty(comment.getAuthorUrl())) {
|
||||
comment.setAuthorUrl(comment.getAuthorUrl().replaceAll(oldUrl, newUrl));
|
||||
}
|
||||
replaced.add(comment);
|
||||
});
|
||||
List<COMMENT> updated = updateInBatch(replaced);
|
||||
return convertTo(updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get children comments recursively.
|
||||
*
|
||||
|
|
|
@ -29,10 +29,7 @@ import run.halo.app.utils.HaloUtils;
|
|||
import run.halo.app.utils.MarkdownUtils;
|
||||
import run.halo.app.utils.ServiceUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -97,6 +94,16 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
|
|||
return postOptional.orElseThrow(() -> new NotFoundException("查询不到该文章的信息").setErrorData(url));
|
||||
}
|
||||
|
||||
@Override
|
||||
public POST getBy(PostStatus status, Integer id) {
|
||||
Assert.notNull(status, "Post status must not be null");
|
||||
Assert.notNull(id, "Post id must not be null");
|
||||
|
||||
Optional<POST> postOptional = basePostRepository.getByIdAndStatus(id, status);
|
||||
|
||||
return postOptional.orElseThrow(() -> new NotFoundException("查询不到该文章的信息").setErrorData(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<POST> listAllBy(PostStatus status) {
|
||||
Assert.notNull(status, "Post status must not be null");
|
||||
|
@ -395,6 +402,26 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
|
|||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BasePostDetailDTO> replaceUrl(String oldUrl, String newUrl) {
|
||||
List<POST> posts = listAll();
|
||||
List<POST> replaced = new ArrayList<>();
|
||||
posts.forEach(post -> {
|
||||
if (StringUtils.isNotEmpty(post.getThumbnail())) {
|
||||
post.setThumbnail(post.getThumbnail().replaceAll(oldUrl, newUrl));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(post.getOriginalContent())) {
|
||||
post.setOriginalContent(post.getOriginalContent().replaceAll(oldUrl, newUrl));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(post.getFormatContent())) {
|
||||
post.setFormatContent(post.getFormatContent().replaceAll(oldUrl, newUrl));
|
||||
}
|
||||
replaced.add(post);
|
||||
});
|
||||
List<POST> updated = updateInBatch(replaced);
|
||||
return updated.stream().map(this::convertToDetail).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public POST create(POST post) {
|
||||
// Check title
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue