mirror of https://github.com/halo-dev/halo
feat: custom permalink.
parent
f975a20fea
commit
13ab09b799
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
/**
|
||||
* Post Permalink type enum.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
public enum PostPermalinkType implements ValueEnum<Integer> {
|
||||
|
||||
/**
|
||||
* /archives/${url}
|
||||
*/
|
||||
DEFAULT(0),
|
||||
|
||||
/**
|
||||
* /1970/01/01/${url}
|
||||
*/
|
||||
DATE(1),
|
||||
|
||||
/**
|
||||
* /1970/01/${url}
|
||||
*/
|
||||
DAY(2),
|
||||
|
||||
/**
|
||||
* /?p=${id}
|
||||
*/
|
||||
ID(3);
|
||||
|
||||
private Integer value;
|
||||
|
||||
PostPermalinkType(Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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;
|
||||
}
|
||||
}
|
|
@ -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<>();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.Optional;
|
|||
* Base post repository.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
public interface BasePostRepository<POST extends BasePost> extends BaseRepository<POST, Integer> {
|
||||
|
@ -99,6 +100,16 @@ public interface BasePostRepository<POST extends BasePost> extends BaseRepositor
|
|||
@NonNull
|
||||
Optional<POST> getByUrlAndStatus(@NonNull String url, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Gets post by id and status.
|
||||
*
|
||||
* @param id id must not be blank
|
||||
* @param status status must not be null
|
||||
* @return an optional post
|
||||
*/
|
||||
@NonNull
|
||||
Optional<POST> getByIdAndStatus(@NonNull Integer id, @NonNull PostStatus status);
|
||||
|
||||
|
||||
/**
|
||||
* Counts posts by status and type.
|
||||
|
|
|
@ -7,8 +7,6 @@ import run.halo.app.model.params.LoginParam;
|
|||
import run.halo.app.model.params.ResetPasswordParam;
|
||||
import run.halo.app.security.token.AuthToken;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Admin service interface.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue