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.AdminService;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; 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.IdUtil;
import cn.hutool.core.util.PageUtil; import cn.hutool.core.util.PageUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@ -14,18 +13,12 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import run.halo.app.cache.StringCacheStore; import run.halo.app.cache.StringCacheStore;
import run.halo.app.cache.lock.CacheLock; import run.halo.app.cache.lock.CacheLock;
import run.halo.app.exception.ForbiddenException; import run.halo.app.controller.content.model.PostModel;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post; 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.enums.PostStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.PostListVO; import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.*; import run.halo.app.service.*;
import run.halo.app.utils.MarkdownUtils;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.springframework.data.domain.Sort.Direction.DESC; import static org.springframework.data.domain.Sort.Direction.DESC;
@ -57,13 +50,16 @@ public class ContentArchiveController {
private final StringCacheStore cacheStore; private final StringCacheStore cacheStore;
private final PostModel postModel;
public ContentArchiveController(PostService postService, public ContentArchiveController(PostService postService,
ThemeService themeService, ThemeService themeService,
PostCategoryService postCategoryService, PostCategoryService postCategoryService,
PostMetaService postMetaService, PostMetaService postMetaService,
PostTagService postTagService, PostTagService postTagService,
OptionService optionService, OptionService optionService,
StringCacheStore cacheStore) { StringCacheStore cacheStore,
PostModel postModel) {
this.postService = postService; this.postService = postService;
this.themeService = themeService; this.themeService = themeService;
this.postCategoryService = postCategoryService; this.postCategoryService = postCategoryService;
@ -71,6 +67,7 @@ public class ContentArchiveController {
this.postTagService = postTagService; this.postTagService = postTagService;
this.optionService = optionService; this.optionService = optionService;
this.cacheStore = cacheStore; this.cacheStore = cacheStore;
this.postModel = postModel;
} }
/** /**
@ -121,43 +118,7 @@ public class ContentArchiveController {
Model model) { Model model) {
Post post = postService.getByUrl(url); Post post = postService.getByUrl(url);
if (post.getStatus().equals(PostStatus.INTIMATE) && StringUtils.isEmpty(token)) { return postModel.post(post, token, model);
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")

View File

@ -11,7 +11,9 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; 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.entity.Post;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.enums.PostStatus; import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.properties.PostProperties; import run.halo.app.model.properties.PostProperties;
import run.halo.app.model.vo.PostListVO; 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.PostService;
import run.halo.app.service.ThemeService; import run.halo.app.service.ThemeService;
import java.util.Objects;
import static org.springframework.data.domain.Sort.Direction.DESC; import static org.springframework.data.domain.Sort.Direction.DESC;
/** /**
@ -38,23 +42,36 @@ public class ContentIndexController {
private final ThemeService themeService; private final ThemeService themeService;
private final PostModel postModel;
public ContentIndexController(PostService postService, public ContentIndexController(PostService postService,
OptionService optionService, OptionService optionService,
ThemeService themeService) { ThemeService themeService,
PostModel postModel) {
this.postService = postService; this.postService = postService;
this.optionService = optionService; this.optionService = optionService;
this.themeService = themeService; this.themeService = themeService;
this.postModel = postModel;
} }
/** /**
* Render blog index * Render blog index
* *
* @param p post id
* @param model model * @param model model
* @return template path: themes/{theme}/index.ftl * @return template path: themes/{theme}/index.ftl
*/ */
@GetMapping @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); 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(StaticDeployProperties.class);
propertyEnumClasses.add(GitStaticDeployProperties.class); propertyEnumClasses.add(GitStaticDeployProperties.class);
propertyEnumClasses.add(NetlifyStaticDeployProperties.class); propertyEnumClasses.add(NetlifyStaticDeployProperties.class);
propertyEnumClasses.add(PermalinkProperties.class);
Map<String, PropertyEnum> result = new HashMap<>(); Map<String, PropertyEnum> result = new HashMap<>();

View File

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

View File

@ -18,6 +18,7 @@ import java.util.Optional;
* Base post repository. * Base post repository.
* *
* @author johnniang * @author johnniang
* @author ryanwang
* @date 2019-03-22 * @date 2019-03-22
*/ */
public interface BasePostRepository<POST extends BasePost> extends BaseRepository<POST, Integer> { public interface BasePostRepository<POST extends BasePost> extends BaseRepository<POST, Integer> {
@ -99,6 +100,16 @@ public interface BasePostRepository<POST extends BasePost> extends BaseRepositor
@NonNull @NonNull
Optional<POST> getByUrlAndStatus(@NonNull String url, @NonNull PostStatus status); 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. * 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.model.params.ResetPasswordParam;
import run.halo.app.security.token.AuthToken; import run.halo.app.security.token.AuthToken;
import javax.servlet.http.HttpServletResponse;
/** /**
* Admin service interface. * 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.OptionDTO;
import run.halo.app.model.dto.OptionSimpleDTO; import run.halo.app.model.dto.OptionSimpleDTO;
import run.halo.app.model.entity.Option; 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.enums.ValueEnum;
import run.halo.app.model.params.OptionParam; import run.halo.app.model.params.OptionParam;
import run.halo.app.model.params.OptionQuery; import run.halo.app.model.params.OptionQuery;
@ -338,6 +339,13 @@ public interface OptionService extends CrudService<Option, Integer> {
*/ */
long getBirthday(); long getBirthday();
/**
* Get post permalink type.
*
* @return PostPermalinkType
*/
PostPermalinkType getPostPermalinkType();
/** /**
* Replace option url in batch. * Replace option url in batch.
* *

View File

@ -18,7 +18,8 @@ import java.util.Optional;
* Base post service implementation. * Base post service implementation.
* *
* @author johnniang * @author johnniang
* @date 19-4-24 * @author ryanwang
* @date 2019-04-24
*/ */
public interface BasePostService<POST extends BasePost> extends CrudService<POST, Integer> { public interface BasePostService<POST extends BasePost> extends CrudService<POST, Integer> {
@ -63,6 +64,16 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
@NonNull @NonNull
POST getBy(@NonNull PostStatus status, @NonNull String url); 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. * Lists all posts by post status.
* *

View File

@ -1,6 +1,5 @@
package run.halo.app.service.impl; package run.halo.app.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileReader; import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.RandomUtil; 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.FileUtils;
import run.halo.app.utils.HaloUtils; 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.File;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; 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)); 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 @Override
public List<POST> listAllBy(PostStatus status) { public List<POST> listAllBy(PostStatus status) {
Assert.notNull(status, "Post status must not be null"); 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.OptionDTO;
import run.halo.app.model.dto.OptionSimpleDTO; import run.halo.app.model.dto.OptionSimpleDTO;
import run.halo.app.model.entity.Option; 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.enums.ValueEnum;
import run.halo.app.model.params.OptionParam; import run.halo.app.model.params.OptionParam;
import run.halo.app.model.params.OptionQuery; 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 @Override
public List<OptionDTO> replaceUrl(String oldUrl, String newUrl) { public List<OptionDTO> replaceUrl(String oldUrl, String newUrl) {
List<Option> options = listAll(); List<Option> options = listAll();