* fix: #988

* refactor: word count

* refactor: word count.

* fix: index posts sort field.
pull/1009/head
Ryan Wang 2020-08-01 23:58:35 +08:00 committed by GitHub
parent d824ccac29
commit 21ed13aff6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 211 additions and 1465 deletions

View File

@ -31,12 +31,15 @@ public class RedisCacheStore extends AbstractStringCacheStore {
* Cache container.
*/
private final static ConcurrentHashMap<String, CacheWrapper<String>> CACHE_CONTAINER = new ConcurrentHashMap<>();
private volatile static JedisCluster REDIS;
protected HaloProperties haloProperties;
/**
* Lock.
*/
private Lock lock = new ReentrantLock();
private final Lock lock = new ReentrantLock();
protected HaloProperties haloProperties;
public RedisCacheStore(HaloProperties haloProperties) {
this.haloProperties = haloProperties;

View File

@ -1,92 +0,0 @@
//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);
// }
//}

View File

@ -16,7 +16,6 @@ import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.PostEditorType;
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.ArchiveYearVO;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.*;
@ -96,9 +95,8 @@ public class PostModel {
postService.publishVisitEvent(post.getId());
AdjacentPostVO adjacentPostVO = postService.getAdjacentPosts(post);
adjacentPostVO.getOptionalPrevPost().ifPresent(prevPost -> model.addAttribute("prevPost", postService.convertToDetailVo(prevPost)));
adjacentPostVO.getOptionalNextPost().ifPresent(nextPost -> model.addAttribute("nextPost", postService.convertToDetailVo(nextPost)));
postService.getPrevPost(post).ifPresent(prevPost -> model.addAttribute("prevPost", postService.convertToDetailVo(prevPost)));
postService.getNextPost(post).ifPresent(nextPost -> model.addAttribute("nextPost", postService.convertToDetailVo(nextPost)));
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
List<Tag> tags = postTagService.listTagsBy(post.getId());

View File

@ -1,33 +0,0 @@
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);
}
}

View File

@ -1,67 +0,0 @@
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);
}
}

View File

@ -1,26 +0,0 @@
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);
}

View File

@ -1,65 +0,0 @@
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;
}
}

View File

@ -1,34 +0,0 @@
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 prevPost;
private Post nextPost;
public Optional<Post> getOptionalPrevPost() {
return Optional.ofNullable(this.getPrevPost());
}
public Optional<Post> getOptionalNextPost() {
return Optional.ofNullable(this.getNextPost());
}
}

View File

@ -90,6 +90,50 @@ public interface BasePostRepository<POST extends BasePost> extends BaseRepositor
@NonNull
Page<POST> findAllByStatusAndCreateTimeAfter(@NonNull PostStatus status, @NonNull Date createTime, @NonNull Pageable pageable);
/**
* Finds all post by status and edit time before.
*
* @param status status must not be null
* @param editTime edit time must not be null
* @param pageable page info must not be null
* @return a page of post
*/
@NonNull
Page<POST> findAllByStatusAndEditTimeBefore(@NonNull PostStatus status, @NonNull Date editTime, @NonNull Pageable pageable);
/**
* Finds all post by status and edit time after.
*
* @param status status must not be null
* @param editTime edit time must not be null
* @param pageable page info must not be null
* @return a page of post
*/
@NonNull
Page<POST> findAllByStatusAndEditTimeAfter(@NonNull PostStatus status, @NonNull Date editTime, @NonNull Pageable pageable);
/**
* Finds all post by status and visits before.
*
* @param status status must not be null
* @param visits visits must not be null
* @param pageable page info must not be null
* @return a page of post
*/
@NonNull
Page<POST> findAllByStatusAndVisitsBefore(@NonNull PostStatus status, @NonNull Long visits, @NonNull Pageable pageable);
/**
* Finds all post by status and visits after.
*
* @param status status must not be null
* @param visits visits must not be null
* @param pageable page info must not be null
* @return a page of post
*/
@NonNull
Page<POST> findAllByStatusAndVisitsAfter(@NonNull PostStatus status, @NonNull Long visits, @NonNull Pageable pageable);
/**
* Gets post by slug and status.
*

View File

@ -8,7 +8,10 @@ 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.*;
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.service.base.BasePostService;
import javax.validation.constraints.NotNull;
@ -254,16 +257,6 @@ public interface PostService extends BasePostService<Post> {
*/
void publishVisitEvent(@NonNull Integer postId);
/**
* Gets pre && next post.
*
* @param currentPost post must not be null
* @return AdjacentPostVO. it contains prevPost and nextPost.
* AdjacentPostVO will not be null. But prevPost and nextPost may be null.
*/
@NotNull
AdjacentPostVO getAdjacentPosts(Post currentPost);
/**
* Get Post Pageable default sort
*

View File

@ -1,53 +0,0 @@
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();
}

View File

@ -343,7 +343,7 @@ public interface ThemeService {
/**
* Fetches a specific release
*
* @param uri theme remote uri must not be null
* @param uri theme remote uri must not be null
* @param tagName release tag name must not be null
* @return theme property
*/
@ -353,7 +353,7 @@ public interface ThemeService {
/**
* Fetches a specific branch (clone)
*
* @param uri theme remote uri must not be null
* @param uri theme remote uri must not be null
* @param branchName wanted branch must not be null
* @return theme property
*/

View File

@ -10,7 +10,6 @@ import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.BasePost;
import run.halo.app.model.enums.PostStatus;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@ -86,40 +85,40 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
/**
* Lists previous posts.
*
* @param date date must not be null
* @param post post must not be null
* @param size previous max post size
* @return a list of previous post
*/
@NonNull
List<POST> listPrevPosts(@NonNull Date date, int size);
List<POST> listPrevPosts(@NonNull POST post, int size);
/**
* Lits next posts.
*
* @param date date must not be null
* @param post post must not be null
* @param size next max post size
* @return a list of next post
*/
@NonNull
List<POST> listNextPosts(@NonNull Date date, int size);
List<POST> listNextPosts(@NonNull POST post, int size);
/**
* Gets previous post.
*
* @param date date must not be null
* @param post post must not be null
* @return an optional post
*/
@NonNull
Optional<POST> getPrevPost(@NonNull Date date);
Optional<POST> getPrevPost(@NonNull POST post);
/**
* Gets next post.
*
* @param date date must not be null
* @param post post must not be null
* @return an optional post
*/
@NonNull
Optional<POST> getNextPost(@NonNull Date date);
Optional<POST> getNextPost(@NonNull POST post);
/**
* Pages latest posts.

View File

@ -30,7 +30,10 @@ import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.MarkdownUtils;
import run.halo.app.utils.ServiceUtils;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -114,35 +117,55 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
@Override
public List<POST> listPrevPosts(Date date, int size) {
Assert.notNull(date, "Date must not be null");
public List<POST> listPrevPosts(POST post, int size) {
Assert.notNull(post, "Post must not be null");
return basePostRepository.findAllByStatusAndCreateTimeAfter(PostStatus.PUBLISHED,
date,
PageRequest.of(0, size, Sort.by(ASC, "createTime")))
.getContent();
String indexSort = optionService.getByPropertyOfNonNull(PostProperties.INDEX_SORT).toString();
PageRequest pageRequest = PageRequest.of(0, size, Sort.by(ASC, indexSort));
switch (indexSort) {
case "createTime":
return basePostRepository.findAllByStatusAndCreateTimeAfter(PostStatus.PUBLISHED, post.getCreateTime(), pageRequest).getContent();
case "editTime":
return basePostRepository.findAllByStatusAndEditTimeAfter(PostStatus.PUBLISHED, post.getEditTime(), pageRequest).getContent();
case "visits":
return basePostRepository.findAllByStatusAndVisitsAfter(PostStatus.PUBLISHED, post.getVisits(), pageRequest).getContent();
default:
return Collections.emptyList();
}
}
@Override
public List<POST> listNextPosts(Date date, int size) {
Assert.notNull(date, "Date must not be null");
public List<POST> listNextPosts(POST post, int size) {
Assert.notNull(post, "Post must not be null");
return basePostRepository.findAllByStatusAndCreateTimeBefore(PostStatus.PUBLISHED,
date,
PageRequest.of(0, size, Sort.by(DESC, "createTime")))
.getContent();
String indexSort = optionService.getByPropertyOfNonNull(PostProperties.INDEX_SORT).toString();
PageRequest pageRequest = PageRequest.of(0, size, Sort.by(DESC, indexSort));
switch (indexSort) {
case "createTime":
return basePostRepository.findAllByStatusAndCreateTimeBefore(PostStatus.PUBLISHED, post.getCreateTime(), pageRequest).getContent();
case "editTime":
return basePostRepository.findAllByStatusAndEditTimeBefore(PostStatus.PUBLISHED, post.getEditTime(), pageRequest).getContent();
case "visits":
return basePostRepository.findAllByStatusAndVisitsBefore(PostStatus.PUBLISHED, post.getVisits(), pageRequest).getContent();
default:
return Collections.emptyList();
}
}
@Override
public Optional<POST> getPrevPost(Date date) {
List<POST> posts = listPrevPosts(date, 1);
public Optional<POST> getPrevPost(POST post) {
List<POST> posts = listPrevPosts(post, 1);
return CollectionUtils.isEmpty(posts) ? Optional.empty() : Optional.of(posts.get(0));
}
@Override
public Optional<POST> getNextPost(Date date) {
List<POST> posts = listNextPosts(date, 1);
public Optional<POST> getNextPost(POST post) {
List<POST> posts = listNextPosts(post, 1);
return CollectionUtils.isEmpty(posts) ? Optional.empty() : Optional.of(posts.get(0));
}
@ -232,7 +255,7 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
Assert.notNull(post, "Post must not be null");
String originalContent = post.getOriginalContent();
originalContent = run.halo.app.utils.StringUtils.htmlToString(originalContent);
originalContent = HaloUtils.cleanHtmlTag(originalContent);
post.setWordCount((long) originalContent.length());

View File

@ -131,6 +131,6 @@ public class LinkServiceImpl extends AbstractCrudService<Link, Integer> implemen
}
return links.stream().map(link -> (LinkDTO) new LinkDTO().convertFrom(link))
.collect(Collectors.toList());
.collect(Collectors.toList());
}
}

View File

@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
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.jpa.domain.Specification;
@ -28,7 +27,10 @@ import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostParam;
import run.halo.app.model.params.PostQuery;
import run.halo.app.model.properties.PostProperties;
import run.halo.app.model.vo.*;
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.repository.PostRepository;
import run.halo.app.repository.base.BasePostRepository;
import run.halo.app.service.*;
@ -561,8 +563,6 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
postListVO.setFullPath(buildFullPath(post));
postListVO.setWordCount(post.getWordCount());
return postListVO;
});
}
@ -621,8 +621,6 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
postListVO.setFullPath(buildFullPath(post));
postListVO.setWordCount(post.getWordCount());
return postListVO;
}).collect(Collectors.toList());
}
@ -804,76 +802,11 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
eventPublisher.publishEvent(new PostVisitEvent(this, postId));
}
@Override
public @NotNull AdjacentPostVO getAdjacentPosts(Post currentPost) {
Assert.notNull(currentPost, "Post must not be null");
// get pageable post list
List<Post> postList = new ArrayList<>();
// init fist page && default page size
int page = 1;
int defaultPageSize = 500;
boolean needNext = true;
// get custom sort type
Sort sort = getPostDefaultSort();
Pageable pageable = null;
PostStatus postStatus = PostStatus.PUBLISHED;
long totalCount = countByStatus(postStatus);
while (needNext && totalCount > postList.size()) {
pageable = PageRequest
.of(page >= 1 ? page - 1 : page, defaultPageSize, sort);
Page<Post> postPage = pageBy(postStatus, pageable);
List<Post> pageablePostList = postPage.getContent();
if (pageablePostList.size() == 0) {
break;
}
postList.addAll(postPage.getContent());
if (postList.stream().filter(it -> it.getId().equals(currentPost.getId())).count() == 1
&& !postList.stream().reduce((first, second) -> second).get().getId()
.equals(currentPost.getId())) {
// contains the post && the post is not in the end
needNext = false;
}
page++;
}
if (CollectionUtils.isEmpty(postList)) {
// if post list is empty, return empty object
return AdjacentPostVO.builder().build();
}
// get current post index in post list
List<Integer> idList = postList.stream().map(Post::getId).collect(Collectors.toList());
int index = idList.indexOf(currentPost.getId());
if (index == -1) {
// if not found, return empty object
return AdjacentPostVO.builder().build();
}
AdjacentPostVO adjacentPostVO = new AdjacentPostVO();
// setup pre
if (index > 0) {
adjacentPostVO.setPrevPost(postList.get(index - 1));
}
// setup next
if (index < postList.size() - 1) {
adjacentPostVO.setNextPost(postList.get(index + 1));
}
return adjacentPostVO;
}
@Override
public @NotNull Sort getPostDefaultSort() {
String indexSort = optionService.getByPropertyOfNonNull(PostProperties.INDEX_SORT)
.toString();
return Sort.by(DESC, "topPriority").and(Sort.by(DESC, indexSort)).and(Sort.by(DESC, "id"));
return Sort.by(DESC, "topPriority").and(Sort.by(DESC, indexSort).and(Sort.by(DESC, "id")));
}
private String buildFullPath(Post post) {

View File

@ -1,838 +0,0 @@
package run.halo.app.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.PageUtil;
import freemarker.template.Template;
import freemarker.template.TemplateException;
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.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.ui.ModelMap;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.Assert;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.ServiceException;
import run.halo.app.handler.staticdeploy.StaticDeployHandlers;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.model.entity.*;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.enums.StaticDeployType;
import run.halo.app.model.properties.PostProperties;
import run.halo.app.model.properties.StaticDeployProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.support.StaticPageFile;
import run.halo.app.model.vo.AdjacentPostVO;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.service.*;
import run.halo.app.utils.DateTimeUtils;
import run.halo.app.utils.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Static Page service implementation.
*
* @author ryanwang
* @date 2019-12-25
*/
@Slf4j
@Service
public class StaticPageServiceImpl implements StaticPageService {
private final Path pagesDir;
private final PostService postService;
private final PostCategoryService postCategoryService;
private final PostTagService postTagService;
private final PostMetaService postMetaService;
private final SheetService sheetService;
private final CategoryService categoryService;
private final TagService tagService;
private final LinkService linkService;
private final PhotoService photoService;
private final JournalService journalService;
private final ThemeService themeService;
private final HaloProperties haloProperties;
private final OptionService optionService;
private final FreeMarkerConfigurer freeMarkerConfigurer;
private final StaticDeployHandlers staticDeployHandlers;
public StaticPageServiceImpl(PostService postService,
PostCategoryService postCategoryService,
PostTagService postTagService,
PostMetaService postMetaService,
SheetService sheetService,
CategoryService categoryService,
TagService tagService,
LinkService linkService,
PhotoService photoService,
JournalService journalService,
ThemeService themeService,
HaloProperties haloProperties,
OptionService optionService,
FreeMarkerConfigurer freeMarkerConfigurer,
StaticDeployHandlers staticDeployHandlers) throws IOException {
this.postService = postService;
this.postCategoryService = postCategoryService;
this.postTagService = postTagService;
this.postMetaService = postMetaService;
this.sheetService = sheetService;
this.categoryService = categoryService;
this.tagService = tagService;
this.linkService = linkService;
this.photoService = photoService;
this.journalService = journalService;
this.themeService = themeService;
this.haloProperties = haloProperties;
this.optionService = optionService;
this.freeMarkerConfigurer = freeMarkerConfigurer;
this.staticDeployHandlers = staticDeployHandlers;
pagesDir = Paths.get(haloProperties.getWorkDir(), PAGES_FOLDER);
FileUtils.createIfAbsent(pagesDir);
Files.createDirectories(Paths.get(STATIC_PAGE_PACK_DIR));
}
@Override
public void generate() {
try {
this.cleanFolder();
this.generateIndex(1);
this.generatePost();
this.generateArchives(1);
this.generateSheet();
this.generateLink();
this.generatePhoto(1);
this.generateCategories();
this.generateTags();
this.generateRss();
this.generateAtom();
this.generateSiteMapHtml();
this.generateSiteMapXml();
this.generateRobots();
this.generateReadme();
this.copyThemeFolder();
this.copyUpload();
this.copyStatic();
} catch (Exception e) {
throw new ServiceException("生成静态页面失败!", e);
}
}
@Override
public void deploy() {
StaticDeployType type = getStaticDeployType();
staticDeployHandlers.deploy(type);
}
@Override
public Path zipStaticPagesDirectory() {
try {
String staticPagePackName = HaloConst.STATIC_PAGE_PACK_PREFIX +
DateTimeUtils.format(LocalDateTime.now(), DateTimeUtils.HORIZONTAL_LINE_DATETIME_FORMATTER) +
IdUtil.simpleUUID().hashCode() + ".zip";
Path staticPageZipPath = Files.createFile(Paths.get(STATIC_PAGE_PACK_DIR, staticPagePackName));
FileUtils.zip(pagesDir, staticPageZipPath);
return staticPageZipPath;
} catch (IOException e) {
throw new ServiceException("Failed to zip static pages directory", e);
}
}
@Override
public List<StaticPageFile> listFile() {
return listStaticPageFileTree(pagesDir);
}
@Nullable
private List<StaticPageFile> listStaticPageFileTree(@NonNull Path topPath) {
Assert.notNull(topPath, "Top path must not be null");
if (!Files.isDirectory(topPath)) {
return null;
}
try (Stream<Path> pathStream = Files.list(topPath)) {
List<StaticPageFile> staticPageFiles = new LinkedList<>();
pathStream.forEach(path -> {
StaticPageFile staticPageFile = new StaticPageFile();
staticPageFile.setId(IdUtil.fastSimpleUUID());
staticPageFile.setName(path.getFileName().toString());
staticPageFile.setIsFile(Files.isRegularFile(path));
if (Files.isDirectory(path)) {
staticPageFile.setChildren(listStaticPageFileTree(path));
}
staticPageFiles.add(staticPageFile);
});
staticPageFiles.sort(new StaticPageFile());
return staticPageFiles;
} catch (IOException e) {
throw new ServiceException("Failed to list sub files", e);
}
}
/**
* Clean static pages folder
*/
private void cleanFolder() {
FileUtils.deleteFolderQuietly(pagesDir);
}
/**
* Generate index.html and page/{page}/index.html.
*
* @param page current page.
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateIndex(int page) throws IOException, TemplateException {
if (!themeService.templateExists("index.ftl")) {
log.warn("index.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
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);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("index"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateIndex(postPage.getNumber() + 2);
log.info("Generate page/{}/index.html", postPage.getNumber() + 2);
}
}
/**
* Generate archives/index.html and archives/page/{page}/index.html.
*
* @param page current page
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateArchives(int page) throws IOException, TemplateException {
if (!themeService.templateExists("archives.ftl")) {
log.warn("archives.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), Sort.by(DESC, "topPriority"));
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);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("archives"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("archives/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("archives/page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateArchives(postPage.getNumber() + 2);
log.info("Generate page/{}/index.html", postPage.getNumber() + 2);
}
}
/**
* Generate archives/{slug}/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generatePost() throws IOException, TemplateException {
if (!themeService.templateExists("post.ftl")) {
log.warn("post.ftl not found,skip!");
return;
}
List<Post> posts = postService.listAllBy(PostStatus.PUBLISHED);
for (Post post : posts) {
log.info("Generate archives/{}/index.html", post.getSlug());
ModelMap model = new ModelMap();
AdjacentPostVO adjacentPostVO = postService.getAdjacentPosts(post);
adjacentPostVO.getOptionalPrevPost().ifPresent(prevPost -> model.addAttribute("prevPost", prevPost));
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", categories);
model.addAttribute("tags", tags);
model.addAttribute("metas", postMetaService.convertToMap(metas));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("post"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("archives/" + post.getSlug() + "/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate archives/{}/index.html succeed.", post.getSlug());
}
}
/**
* Generate s/{slug}/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateSheet() throws IOException, TemplateException {
if (!themeService.templateExists("sheet.ftl")) {
log.warn("sheet.ftl not found,skip!");
return;
}
List<Sheet> sheets = sheetService.listAllBy(PostStatus.PUBLISHED);
for (Sheet sheet : sheets) {
log.info("Generate s/{}/index.html", sheet.getSlug());
ModelMap model = new ModelMap();
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);
model.addAttribute("sheet", sheetDetailVO);
model.addAttribute("post", sheetDetailVO);
model.addAttribute("is_sheet", true);
String templateName = "sheet";
if (themeService.templateExists(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
templateName = ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate();
}
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix(templateName));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("s/" + sheet.getSlug() + "/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate s/{}/index.html succeed.", sheet.getSlug());
}
}
/**
* Generate links/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateLink() throws IOException, TemplateException {
log.info("Generate links.html");
if (!themeService.templateExists("links.ftl")) {
log.warn("links.ftl not found,skip!");
return;
}
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("links"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, null);
FileWriter fileWriter = new FileWriter(getPageFile("links/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate links.html succeed.");
}
/**
* Generate photos/index.html and photos/page/{page}/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generatePhoto(int page) throws IOException, TemplateException {
log.info("Generate photos.html");
if (!themeService.templateExists("photos.ftl")) {
log.warn("photos.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, 10, Sort.by(DESC, "createTime"));
Page<PhotoDTO> photos = photoService.pageBy(pageable);
model.addAttribute("photos", photos);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("photos/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("photos/page/" + page + "/photos.html"), "UTF-8");
}
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("photos"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
fileWriter.write(html);
log.info("Generate photos.html succeed.");
if (photos.hasNext()) {
generatePhoto(photos.getNumber() + 2);
log.info("Generate page/{}/index.html", photos.getNumber() + 2);
}
}
/**
* Generate categories/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateCategories() throws IOException, TemplateException {
log.info("Generate categories.html");
ModelMap model = new ModelMap();
if (!themeService.templateExists("categories.ftl")) {
log.warn("categories.ftl not found,skip!");
return;
}
model.addAttribute("is_categories", true);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("categories"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("categories/index.html"), "UTF-8");
fileWriter.write(html);
List<Category> categories = categoryService.listAll();
for (Category category : categories) {
generateCategory(1, category);
}
log.info("Generate categories.html succeed.");
}
/**
* Generate categories/{slug}/index.html and categories/{slug}/{page}/index.html.
*
* @param page current page
* @param category current category
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateCategory(int page, Category category) throws IOException, TemplateException {
if (!themeService.templateExists("category.ftl")) {
log.warn("category.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
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);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("category"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("categories/" + category.getSlug() + "/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("categories/" + category.getSlug() + "/page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateCategory(postPage.getNumber() + 2, category);
log.info("Generate categories/{}/page/{}/index.html", category.getSlug(), postPage.getNumber() + 2);
}
}
/**
* Generate tags/{slug}/index.html and tags/{slug}/{page}/index.html.
*
* @param page current page
* @param tag current tag
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateTag(int page, Tag tag) throws IOException, TemplateException {
if (!themeService.templateExists("tag.ftl")) {
log.warn("tag.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
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);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("tag"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("tags/" + tag.getSlug() + "/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("tags/" + tag.getSlug() + "/page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateTag(postPage.getNumber() + 2, tag);
log.info("Generate tags/{}/page/{}/index.html", tag.getSlug(), postPage.getNumber() + 2);
}
}
/**
* Generate tags/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateTags() throws IOException, TemplateException {
log.info("Generate tags.html");
ModelMap model = new ModelMap();
if (!themeService.templateExists("tags.ftl")) {
log.warn("tags.ftl not found,skip!");
return;
}
model.addAttribute("is_tags", true);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("tags"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("tags/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate tags.html succeed.");
List<Tag> tags = tagService.listAll();
for (Tag tag : tags) {
generateTag(1, tag);
}
}
/**
* Generate rss.xml and feed.xml
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateRss() throws IOException, TemplateException {
log.info("Generate rss.xml/feed.xml");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/rss.ftl");
String xml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter rssWriter = new FileWriter(getPageFile("rss.xml"), "UTF-8");
rssWriter.write(xml);
FileWriter feedWriter = new FileWriter(getPageFile("feed.xml"), "UTF-8");
feedWriter.write(xml);
log.info("Generate rss.xml/feed.xml succeed.");
}
/**
* Generate atom.xml
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateAtom() throws IOException, TemplateException {
log.info("Generate atom.xml");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/atom.ftl");
String xml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("atom.xml"), "UTF-8");
fileWriter.write(xml);
log.info("Generate atom.xml succeed.");
}
/**
* Generate sitemap.html
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateSiteMapHtml() throws IOException, TemplateException {
log.info("Generate sitemap.html");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/sitemap_html.ftl");
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("sitemap.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate sitemap.html succeed.");
}
/**
* Generate sitemap.xml
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateSiteMapXml() throws IOException, TemplateException {
log.info("Generate sitemap.xml");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/sitemap_xml.ftl");
String xml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("sitemap.xml"), "UTF-8");
fileWriter.write(xml);
log.info("Generate sitemap.xml succeed.");
}
/**
* Generate robots.txt
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateRobots() throws IOException, TemplateException {
log.info("Generate robots.txt");
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/robots.ftl");
String txt = FreeMarkerTemplateUtils.processTemplateIntoString(template, null);
FileWriter fileWriter = new FileWriter(getPageFile("robots.txt"), "UTF-8");
fileWriter.write(txt);
log.info("Generate robots.txt succeed.");
}
/**
* Generate README.md.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateReadme() throws IOException, TemplateException {
log.info("Generate readme.md");
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/readme.ftl");
String txt = FreeMarkerTemplateUtils.processTemplateIntoString(template, null);
FileWriter fileWriter = new FileWriter(getPageFile("README.md"), "UTF-8");
fileWriter.write(txt);
log.info("Generate readme.md succeed.");
}
/**
* Copy current theme folder.
*
* @throws IOException IOException
*/
private void copyThemeFolder() throws IOException {
ThemeProperty activatedTheme = themeService.getActivatedTheme();
Path path = Paths.get(pagesDir.toString(), activatedTheme.getFolderName());
FileUtils.createIfAbsent(path);
FileUtils.copyFolder(Paths.get(activatedTheme.getThemePath()), path);
cleanThemeFolder(Paths.get(pagesDir.toString(), activatedTheme.getFolderName()));
}
private void cleanThemeFolder(Path themePath) {
if (!Files.isDirectory(themePath)) {
return;
}
try (Stream<Path> pathStream = Files.list(themePath)) {
pathStream.forEach(path -> {
if (!Files.isDirectory(path)) {
for (String suffix : USELESS_FILE_SUFFIX) {
if (suffix.contains(FileUtil.extName(path.toFile()))) {
try {
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
if (path.getFileName().toString().contains(".git")) {
FileUtils.deleteFolderQuietly(path);
} else {
cleanThemeFolder(path);
}
}
});
} catch (IOException e) {
throw new ServiceException("Failed to list sub files", e);
}
}
/**
* Copy upload folder.
*
* @throws IOException IOException
*/
private void copyUpload() throws IOException {
Path path = Paths.get(pagesDir.toString(), "upload");
FileUtils.createIfAbsent(path);
FileUtils.copyFolder(Paths.get(haloProperties.getWorkDir(), "upload"), path);
}
/**
* Copy static folder.
*
* @throws IOException IOException
*/
private void copyStatic() throws IOException {
FileUtils.copyFolder(Paths.get(haloProperties.getWorkDir(), "static"), pagesDir);
}
/**
* Build posts for feed
*
* @param pageable pageable
* @return List<Post>
*/
private List<PostDetailVO> buildPosts(@NonNull Pageable pageable) {
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostDetailVO> posts = postService.convertToDetailVo(postPage);
posts.getContent().forEach(postListVO -> {
try {
// Encode post slug
postListVO.setSlug(URLEncoder.encode(postListVO.getSlug(), StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
log.warn("Failed to encode url: " + postListVO.getSlug(), e);
}
});
return posts.getContent();
}
/**
* Builds page info for post.
*
* @param size page size
* @return page info
*/
@NonNull
private Pageable buildPostPageable(int size) {
return PageRequest.of(0, size, Sort.by(DESC, "createTime"));
}
private File getPageFile(String subPath) {
Path path = Paths.get(pagesDir.toString(), subPath);
return path.toFile();
}
/**
* Get static deploy type from options.
*
* @return static deploy type
*/
@NonNull
private StaticDeployType getStaticDeployType() {
return optionService.getEnumByPropertyOrDefault(StaticDeployProperties.DEPLOY_TYPE, StaticDeployType.class, StaticDeployType.GIT);
}
}

View File

@ -78,19 +78,19 @@ public class ThemeServiceImpl implements ThemeService {
private final ApplicationEventPublisher eventPublisher;
private final AtomicReference<String> activeThemeId = new AtomicReference<>();
/**
* Activated theme id.
*/
@Nullable
private volatile String activatedThemeId;
/**
* Activated theme property.
*/
private volatile ThemeProperty activatedTheme;
private final AtomicReference<String> activeThemeId = new AtomicReference<>();
public ThemeServiceImpl(HaloProperties haloProperties,
OptionService optionService,
AbstractStringCacheStore cacheStore,
@ -358,12 +358,6 @@ public class ThemeServiceImpl implements ThemeService {
return activatedTheme;
}
@Override
@NonNull
public Optional<ThemeProperty> fetchActivatedTheme() {
return fetchThemePropertyBy(getActivatedThemeId());
}
/**
* Sets activated theme.
*
@ -374,6 +368,12 @@ public class ThemeServiceImpl implements ThemeService {
this.activatedThemeId = Optional.ofNullable(activatedTheme).map(ThemeProperty::getId).orElse(null);
}
@Override
@NonNull
public Optional<ThemeProperty> fetchActivatedTheme() {
return fetchThemePropertyBy(getActivatedThemeId());
}
@Override
@NonNull
public ThemeProperty activateTheme(@NonNull String themeId) {
@ -590,7 +590,7 @@ public class ThemeServiceImpl implements ThemeService {
public List<ThemeProperty> fetchBranches(String uri) {
Assert.hasText(uri, "Theme remote uri must not be blank");
String repoUrl = StringUtils.appendIfMissingIgnoreCase(uri, ".git",".git");
String repoUrl = StringUtils.appendIfMissingIgnoreCase(uri, ".git", ".git");
List<String> branches = GitUtils.getAllBranches(repoUrl);
List<ThemeProperty> themeProperties = new ArrayList<>();
@ -717,7 +717,7 @@ public class ThemeServiceImpl implements ThemeService {
// Get branch
String branch = StringUtils.isBlank(themeProperty.getBranch()) ?
DEFAULT_REMOTE_BRANCH : themeProperty.getBranch();
DEFAULT_REMOTE_BRANCH : themeProperty.getBranch();
Git git = null;
@ -728,8 +728,8 @@ public class ThemeServiceImpl implements ThemeService {
// Add all changes
git.add()
.addFilepattern(".")
.call();
.addFilepattern(".")
.call();
// Commit the changes
git.commit().setMessage("Commit by halo automatically").call();
@ -744,31 +744,31 @@ public class ThemeServiceImpl implements ThemeService {
// Force to set remote name
git.remoteRemove().setRemoteName(THEME_PROVIDER_REMOTE_NAME).call();
RemoteConfig remoteConfig = git.remoteAdd()
.setName(THEME_PROVIDER_REMOTE_NAME)
.setUri(new URIish(themeProperty.getRepo()))
.call();
.setName(THEME_PROVIDER_REMOTE_NAME)
.setUri(new URIish(themeProperty.getRepo()))
.call();
// Check out to specified branch
if (!StringUtils.equalsIgnoreCase(branch, git.getRepository().getBranch())) {
boolean present = git.branchList()
.call()
.stream()
.map(Ref::getName)
.anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch));
.call()
.stream()
.map(Ref::getName)
.anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch));
git.checkout()
.setCreateBranch(true)
.setForced(!present)
.setName(branch)
.call();
.setCreateBranch(true)
.setForced(!present)
.setName(branch)
.call();
}
// Pull with rebasing
PullResult pullResult = git.pull()
.setRemote(remoteConfig.getName())
.setRemoteBranchName(branch)
.setRebase(true)
.call();
.setRemote(remoteConfig.getName())
.setRemoteBranchName(branch)
.setRebase(true)
.call();
if (!pullResult.isSuccessful()) {
log.debug("Rebase result: [{}]", pullResult.getRebaseResult());
@ -787,9 +787,9 @@ public class ThemeServiceImpl implements ThemeService {
if (StringUtils.isNotEmpty(updatedThemeProperty.getRequire()) && !VersionUtil.compareVersion(HaloConst.HALO_VERSION, updatedThemeProperty.getRequire())) {
// reset theme version
git.reset()
.setMode(ResetCommand.ResetType.HARD)
.setRef(lastCommit.getName())
.call();
.setMode(ResetCommand.ResetType.HARD)
.setRef(lastCommit.getName())
.call();
throw new ThemeNotSupportException("新版本主题仅支持 Halo " + updatedThemeProperty.getRequire() + " 以上的版本");
}
} finally {

View File

@ -34,17 +34,15 @@ public enum ThemePropertyScanner {
INSTANCE;
private final ThemePropertyResolver propertyResolver = new YamlThemePropertyResolver();
/**
* Theme property file name.
*/
private static final String[] THEME_PROPERTY_FILE_NAMES = {"theme.yaml", "theme.yml"};
/**
* Theme screenshots name.
*/
private static final String THEME_SCREENSHOTS_NAME = "screenshot";
private final ThemePropertyResolver propertyResolver = new YamlThemePropertyResolver();
/**
* Scan theme properties.

View File

@ -137,8 +137,8 @@ public class FileUtils {
// if zip file has root file
if (files.size() == 1 && files.get(0).isDirectory()) {
String rootPath = files.get(0).toPath().toString();
String rootFile = rootPath.substring(rootPath.lastIndexOf("/", rootPath.length() - 1) + 1,rootPath.length());
List<File> propertyFiles = Arrays.asList(files.get(0).listFiles());
String rootFile = rootPath.substring(rootPath.lastIndexOf("/") + 1);
File[] propertyFiles = files.get(0).listFiles();
for (File propertyFile : propertyFiles) {
String filePath = propertyFile.toPath().toString();
String destPath = filePath.replace(rootFile, "");

View File

@ -13,7 +13,10 @@ import org.springframework.util.Assert;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Git utilities.
@ -70,11 +73,11 @@ public class GitUtils {
Git git = null;
try {
git = Git.cloneRepository()
.setURI(repoUrl)
.setDirectory(targetPath.toFile())
.setBranchesToClone(Collections.singletonList("refs/heads/" + branchName))
.setBranch("refs/heads/" + branchName)
.call();
.setURI(repoUrl)
.setDirectory(targetPath.toFile())
.setBranchesToClone(Collections.singletonList("refs/heads/" + branchName))
.setBranch("refs/heads/" + branchName)
.call();
} finally {
closeQuietly(git);
}
@ -84,9 +87,9 @@ public class GitUtils {
List<String> branches = new ArrayList<>();
try {
Collection<Ref> refs = Git.lsRemoteRepository()
.setHeads(true)
.setRemote(repoUrl)
.call();
.setHeads(true)
.setRemote(repoUrl)
.call();
for (Ref ref : refs) {
branches.add(ref.getName().substring(ref.getName().lastIndexOf("/") + 1));
}

View File

@ -24,6 +24,7 @@ public class GithubUtils {
/**
* Get latest release
*
* @param uri repository url must not be null
* @return the map object containning tagname and zipfile url
*/
@ -49,6 +50,7 @@ public class GithubUtils {
/**
* Get release information
*
* @param uri repository url must not be null
* @return list of tagname of releases
*/
@ -74,7 +76,8 @@ public class GithubUtils {
/**
* Get release information
* @param uri repository url must not be null
*
* @param uri repository url must not be null
* @param tagName tag must not be null
* @return the map object containning tagname and zipfile url
*/
@ -99,7 +102,8 @@ public class GithubUtils {
/**
* Get the content of theme.yaml/theme.yml
* @param uri repository url must not be null
*
* @param uri repository url must not be null
* @param branch branch must not be null
* @return content of the file
*/
@ -110,7 +114,7 @@ public class GithubUtils {
GithubFile githubFile = new GithubFile(repoUrl, branch);
Thread thread = new Thread(githubFile);
thread.start();
thread.join(10 * 1000);
@ -125,18 +129,21 @@ public class GithubUtils {
private static class GithubRelease implements Runnable {
/**
* should be in format of "username/reponame"
*/
private final String repoUrl;
/**
* repository tag name
*/
private final String tagName;
/**
* The return result is zip url and tag name etc.
*/
private HashMap<String, Object> result;
/**
* should be in format of "username/reponame"
*/
private String repoUrl;
private String tagName;
public GithubRelease(String repoUrl, String tagName) {
this.repoUrl = repoUrl;
this.tagName = tagName;
@ -156,8 +163,8 @@ public class GithubUtils {
}
Optional<GHRelease> res = ghReleaseList.stream()
.filter(release -> StringUtils.equalsIgnoreCase(release.getTagName(), tagName))
.findFirst();
.filter(release -> StringUtils.equalsIgnoreCase(release.getTagName(), tagName))
.findFirst();
if (res.isPresent()) {
GHRelease ghRelease = res.get();
@ -194,10 +201,9 @@ public class GithubUtils {
private static class GithubReleases implements Runnable {
private final String repoUrl;
private List<String> result;
private String repoUrl;
public GithubReleases(String repoUrl) {
this.repoUrl = repoUrl;
result = null;
@ -241,16 +247,15 @@ public class GithubUtils {
private static class GithubLatestRelease implements Runnable {
/**
* should be in format of "username/reponame"
*/
private final String repoUrl;
/**
* The return result is zip url and tag name etc.
*/
private HashMap<String, Object> result;
/**
* should be in format of "username/reponame"
*/
private String repoUrl;
public GithubLatestRelease(String repoUrl) {
this.repoUrl = repoUrl;
result = null;
@ -300,20 +305,18 @@ public class GithubUtils {
private static class GithubFile implements Runnable {
/**
* result is file content
*/
private String result;
/**
* should be in format of "username/reponame"
*/
private String repoUrl;
private final String repoUrl;
/**
* the branch name
*/
private String branch;
private final String branch;
/**
* result is file content
*/
private String result;
public GithubFile(String repoUrl, String branch) {
this.repoUrl = repoUrl;

View File

@ -51,14 +51,14 @@ public class HttpClientUtils {
@NonNull
public static CloseableHttpClient createHttpsClient(int timeout) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, (certificate, authType) -> true)
.build();
.loadTrustMaterial(null, (certificate, authType) -> true)
.build();
return resolveProxySetting(HttpClients.custom())
.setSSLContext(sslContext)
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.setDefaultRequestConfig(getRequestConfig(timeout))
.build();
.setSSLContext(sslContext)
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.setDefaultRequestConfig(getRequestConfig(timeout))
.build();
}
/**
@ -77,7 +77,7 @@ public class HttpClientUtils {
//set proxy credentials
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(httpHost.getHostName(), httpHost.getPort()),
new UsernamePasswordCredentials(httpProxy.get(1), httpProxy.get(2)));
new UsernamePasswordCredentials(httpProxy.get(1), httpProxy.get(2)));
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
}
@ -126,10 +126,10 @@ public class HttpClientUtils {
*/
private static RequestConfig getRequestConfig(int timeout) {
return RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
}

View File

@ -70,6 +70,17 @@ public class MarkdownUtils {
private static final Parser PARSER = Parser.builder(OPTIONS).build();
private static final HtmlRenderer RENDERER = HtmlRenderer.builder(OPTIONS).build();
private static final Pattern FRONT_MATTER = Pattern.compile("^---[\\s\\S]*?---");
// /**
// * Render html document to markdown document.
// *
// * @param html html document
// * @return markdown document
// */
// public static String renderMarkdown(String html) {
// return FlexmarkHtmlParser.parse(html);
// }
/**
* Render Markdown content
@ -102,16 +113,6 @@ public class MarkdownUtils {
return RENDERER.render(document);
}
// /**
// * Render html document to markdown document.
// *
// * @param html html document
// * @return markdown document
// */
// public static String renderMarkdown(String html) {
// return FlexmarkHtmlParser.parse(html);
// }
/**
* Get front-matter
*
@ -125,8 +126,6 @@ public class MarkdownUtils {
return visitor.getData();
}
private static final Pattern FRONT_MATTER = Pattern.compile("^---[\\s\\S]*?---");
/**
* remove front matter
*

View File

@ -1,42 +0,0 @@
package run.halo.app.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @program: halo
* @description: processing strings Utlis
* @author: coor.top
* @create: 2020-07-14 01:24
**/
public class StringUtils {
private StringUtils() {
}
/**
* html convert to string
*
* @param htmlStr
*/
public static String htmlToString(String htmlStr) {
htmlStr = htmlStr.trim().replaceAll("\"", "'"); //如果有双引号将其先转成单引号
String regExScript = "<script[^>]*?>[\\s\\S]*?<\\/script>"; // 定义script的正则表达式
String regExStyle = "<style[^>]*?>[\\s\\S]*?<\\/style>"; // 定义style的正则表达式
String regExHtml = "<[^>]+>"; // 定义HTML标签的正则表达式
Pattern pScript = Pattern.compile(regExScript, Pattern.CASE_INSENSITIVE);
Matcher mScript = pScript.matcher(htmlStr);
htmlStr = mScript.replaceAll(""); // 过滤script标签
Pattern pStyle = Pattern.compile(regExStyle, Pattern.CASE_INSENSITIVE);
Matcher mStyle = pStyle.matcher(htmlStr);
htmlStr = mStyle.replaceAll(""); // 过滤style标签
Pattern pHtml = Pattern.compile(regExHtml, Pattern.CASE_INSENSITIVE);
Matcher mHtml = pHtml.matcher(htmlStr);
htmlStr = mHtml.replaceAll(""); // 过滤html标签
return htmlStr;
}
}

View File

@ -86,7 +86,7 @@ class TimeBasedOneTimePasswordUtil {
/**
* set to the number of digits to control 0 prefix, set to 0 for no prefix
*/
private static int NUM_DIGITS_OUTPUT = 6;
private static final int NUM_DIGITS_OUTPUT = 6;
static {
char[] chars = new char[NUM_DIGITS_OUTPUT];