feat: custom permalink.

pull/484/head
ruibaby 2020-01-07 18:46:19 +08:00
parent f975a20fea
commit 13ab09b799
15 changed files with 282 additions and 56 deletions

View File

@ -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;
/**

View File

@ -3,7 +3,6 @@ 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;
@ -14,18 +13,12 @@ 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.controller.content.model.PostModel;
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 java.util.List;
import java.util.concurrent.TimeUnit;
import static org.springframework.data.domain.Sort.Direction.DESC;
@ -57,13 +50,16 @@ public class ContentArchiveController {
private final StringCacheStore cacheStore;
private final PostModel postModel;
public ContentArchiveController(PostService postService,
ThemeService themeService,
PostCategoryService postCategoryService,
PostMetaService postMetaService,
PostTagService postTagService,
OptionService optionService,
StringCacheStore cacheStore) {
StringCacheStore cacheStore,
PostModel postModel) {
this.postService = postService;
this.themeService = themeService;
this.postCategoryService = postCategoryService;
@ -71,6 +67,7 @@ public class ContentArchiveController {
this.postTagService = postTagService;
this.optionService = optionService;
this.cacheStore = cacheStore;
this.postModel = postModel;
}
/**
@ -121,43 +118,7 @@ public class ContentArchiveController {
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");
return postModel.post(post, token, model);
}
@GetMapping(value = "{url}/password")

View File

@ -11,7 +11,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.RequestMapping;
import run.halo.app.controller.content.model.PostModel;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.properties.PostProperties;
import run.halo.app.model.vo.PostListVO;
@ -19,6 +21,8 @@ import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.ThemeService;
import java.util.Objects;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
@ -38,23 +42,36 @@ public class ContentIndexController {
private final ThemeService themeService;
private final PostModel postModel;
public ContentIndexController(PostService postService,
OptionService optionService,
ThemeService themeService) {
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.post(post, token, model);
}
return this.index(model, 1);
}

View File

@ -0,0 +1,95 @@
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.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.service.*;
import run.halo.app.utils.MarkdownUtils;
import java.util.List;
/**
* @author ryan0up
* @date 2020/1/7
*/
@Component
public class PostModel {
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 PostModel(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;
}
public String post(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());
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");
}
}

View File

@ -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;
}
}

View File

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

View File

@ -158,6 +158,7 @@ public interface PropertyEnum extends ValueEnum<String> {
propertyEnumClasses.add(StaticDeployProperties.class);
propertyEnumClasses.add(GitStaticDeployProperties.class);
propertyEnumClasses.add(NetlifyStaticDeployProperties.class);
propertyEnumClasses.add(PermalinkProperties.class);
Map<String, PropertyEnum> result = new HashMap<>();

View File

@ -14,12 +14,15 @@ import java.util.List;
*
* @author johnniang
* @author guqing
* @author ryanwang
* @date 2019-03-19
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PostListVO extends BasePostSimpleDTO {
private String fullPath;
private Long commentCount;
private List<TagDTO> tags;

View File

@ -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.

View File

@ -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.
*

View File

@ -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,13 @@ public interface OptionService extends CrudService<Option, Integer> {
*/
long getBirthday();
/**
* Get post permalink type.
*
* @return PostPermalinkType
*/
PostPermalinkType getPostPermalinkType();
/**
* Replace option url in batch.
*

View File

@ -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.
*

View File

@ -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;

View File

@ -94,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");

View File

@ -22,6 +22,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;
@ -461,6 +462,11 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
});
}
@Override
public PostPermalinkType getPostPermalinkType() {
return getEnumByPropertyOrDefault(PermalinkProperties.POST_PERMALINK_TYPE, PostPermalinkType.class, PostPermalinkType.DEFAULT);
}
@Override
public List<OptionDTO> replaceUrl(String oldUrl, String newUrl) {
List<Option> options = listAll();