mirror of https://github.com/halo-dev/halo
* feat: #1225 * fix: compatible with jdk1.8 * fix: format code * fix: fix the problem that the status of the recycle bin file is incorrect when revocering * fix: format code * fix: format code * fix: fix the post cannot be converted to recyling mode * fix: post cannot be published on deleting the category password * fix: fix jpa error * fix: format code * fix: encryption type extracted into enum * fix: format code * fix: format code * fix: changes requested * fix: format code * fix: revert checkstyle.xml * fix: change request * fix: not encrypt 方法改为重载方法 * fix: 修复因调整 git 版本被回退的代码 Co-authored-by: xiangbei.yzx <xiangbei.yzx@alibaba-inc.com>pull/1256/head
parent
f28d833fa8
commit
7b88fcab5f
|
@ -51,8 +51,8 @@ public class AdminController {
|
|||
@GetMapping(value = "/is_installed")
|
||||
@ApiOperation("Checks Installation status")
|
||||
public boolean isInstall() {
|
||||
return optionService
|
||||
.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false);
|
||||
return optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class,
|
||||
false);
|
||||
}
|
||||
|
||||
@PostMapping("login/precheck")
|
||||
|
|
|
@ -56,10 +56,10 @@ public class CategoryController {
|
|||
@SortDefault(sort = "createTime", direction = DESC) Sort sort,
|
||||
@RequestParam(name = "more", required = false, defaultValue = "false") boolean more) {
|
||||
if (more) {
|
||||
return postCategoryService.listCategoryWithPostCountDto(sort);
|
||||
return postCategoryService.listCategoryWithPostCountDto(sort, true);
|
||||
}
|
||||
|
||||
return categoryService.convertTo(categoryService.listAll(sort));
|
||||
return categoryService.convertTo(categoryService.listAll(sort, true));
|
||||
}
|
||||
|
||||
@GetMapping("tree_view")
|
||||
|
@ -81,7 +81,8 @@ public class CategoryController {
|
|||
@PutMapping("{categoryId:\\d+}")
|
||||
@ApiOperation("Updates category")
|
||||
public CategoryDTO updateBy(@PathVariable("categoryId") Integer categoryId,
|
||||
@RequestBody @Valid CategoryParam categoryParam) {
|
||||
@RequestBody @Valid CategoryParam categoryParam
|
||||
) {
|
||||
Category categoryToUpdate = categoryService.getById(categoryId);
|
||||
categoryParam.update(categoryToUpdate);
|
||||
return categoryService.convertTo(categoryService.update(categoryToUpdate));
|
||||
|
|
|
@ -35,8 +35,8 @@ public class MigrateController {
|
|||
@PostMapping("halo")
|
||||
@ApiOperation("Migrate from Halo")
|
||||
public void migrateHalo(@RequestPart("file") MultipartFile file) {
|
||||
if (optionService
|
||||
.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
|
||||
if (optionService.getByPropertyOrDefault(
|
||||
PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
|
||||
throw new BadRequestException("无法在博客初始化完成之后迁移数据");
|
||||
}
|
||||
migrateService.migrate(file, MigrateType.HALO);
|
||||
|
|
|
@ -2,13 +2,11 @@ package run.halo.app.controller.admin.api;
|
|||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
@ -22,12 +20,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||
import run.halo.app.model.dto.post.BasePostSimpleDTO;
|
||||
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.params.PostContentParam;
|
||||
import run.halo.app.model.params.PostParam;
|
||||
|
@ -50,15 +46,12 @@ public class PostController {
|
|||
|
||||
private final PostService postService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
|
||||
public PostController(PostService postService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
OptionService optionService) {
|
||||
this.postService = postService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
|
@ -70,7 +63,7 @@ public class PostController {
|
|||
@RequestParam(value = "more", defaultValue = "true") Boolean more) {
|
||||
Page<Post> postPage = postService.pageBy(postQuery, pageable);
|
||||
if (more) {
|
||||
return postService.convertToListVo(postPage);
|
||||
return postService.convertToListVo(postPage, true);
|
||||
}
|
||||
|
||||
return postService.convertToSimple(postPage);
|
||||
|
@ -92,7 +85,7 @@ public class PostController {
|
|||
Page<Post> posts = postService.pageBy(status, pageable);
|
||||
|
||||
if (more) {
|
||||
return postService.convertToListVo(posts);
|
||||
return postService.convertToListVo(posts, true);
|
||||
}
|
||||
|
||||
return postService.convertToSimple(posts);
|
||||
|
@ -102,7 +95,7 @@ public class PostController {
|
|||
@ApiOperation("Gets a post")
|
||||
public PostDetailVO getBy(@PathVariable("postId") Integer postId) {
|
||||
Post post = postService.getById(postId);
|
||||
return postService.convertToDetailVo(post);
|
||||
return postService.convertToDetailVo(post, true);
|
||||
}
|
||||
|
||||
@PutMapping("{postId:\\d+}/likes")
|
||||
|
@ -114,8 +107,8 @@ public class PostController {
|
|||
@PostMapping
|
||||
@ApiOperation("Creates a post")
|
||||
public PostDetailVO createBy(@Valid @RequestBody PostParam postParam,
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false")
|
||||
Boolean autoSave) {
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave
|
||||
) {
|
||||
// Convert to
|
||||
Post post = postParam.convertTo();
|
||||
return postService.createBy(post, postParam.getTagIds(), postParam.getCategoryIds(),
|
||||
|
@ -126,8 +119,8 @@ public class PostController {
|
|||
@ApiOperation("Updates a post")
|
||||
public PostDetailVO updateBy(@Valid @RequestBody PostParam postParam,
|
||||
@PathVariable("postId") Integer postId,
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false")
|
||||
Boolean autoSave) {
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave
|
||||
) {
|
||||
// Get the post info
|
||||
Post postToUpdate = postService.getById(postId);
|
||||
|
||||
|
@ -186,11 +179,6 @@ public class PostController {
|
|||
|
||||
BasePostMinimalDTO postMinimalDTO = postService.convertToMinimal(post);
|
||||
|
||||
String token = IdUtil.simpleUUID();
|
||||
|
||||
// cache preview token
|
||||
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
|
||||
|
||||
StringBuilder previewUrl = new StringBuilder();
|
||||
|
||||
if (!optionService.isEnabledAbsolutePath()) {
|
||||
|
@ -199,14 +187,6 @@ public class PostController {
|
|||
|
||||
previewUrl.append(postMinimalDTO.getFullPath());
|
||||
|
||||
if (optionService.getPostPermalinkType().equals(PostPermalinkType.ID)) {
|
||||
previewUrl.append("&token=")
|
||||
.append(token);
|
||||
} else {
|
||||
previewUrl.append("?token=")
|
||||
.append(token);
|
||||
}
|
||||
|
||||
// build preview post url and return
|
||||
return previewUrl.toString();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
@ -24,12 +22,17 @@ import run.halo.app.controller.content.model.PostModel;
|
|||
import run.halo.app.controller.content.model.SheetModel;
|
||||
import run.halo.app.controller.content.model.TagModel;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.exception.UnsupportedException;
|
||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.enums.SheetPermalinkType;
|
||||
import run.halo.app.service.AuthenticationService;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.SheetService;
|
||||
|
@ -65,6 +68,10 @@ public class ContentContentController {
|
|||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
public ContentContentController(PostModel postModel,
|
||||
SheetModel sheetModel,
|
||||
CategoryModel categoryModel,
|
||||
|
@ -75,7 +82,9 @@ public class ContentContentController {
|
|||
OptionService optionService,
|
||||
PostService postService,
|
||||
SheetService sheetService,
|
||||
AbstractStringCacheStore cacheStore) {
|
||||
AbstractStringCacheStore cacheStore,
|
||||
AuthenticationService authenticationService,
|
||||
CategoryService categoryService) {
|
||||
this.postModel = postModel;
|
||||
this.sheetModel = sheetModel;
|
||||
this.categoryModel = categoryModel;
|
||||
|
@ -87,6 +96,8 @@ public class ContentContentController {
|
|||
this.postService = postService;
|
||||
this.sheetService = sheetService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.authenticationService = authenticationService;
|
||||
this.categoryService = categoryService;
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}")
|
||||
|
@ -225,14 +236,32 @@ public class ContentContentController {
|
|||
throw new NotFoundException("Not Found");
|
||||
}
|
||||
|
||||
@PostMapping(value = "archives/{slug:.*}/password")
|
||||
@PostMapping(value = "archives/{type}/{slug:.*}/password")
|
||||
@CacheLock(traceRequest = true, expired = 2)
|
||||
public String password(@PathVariable("slug") String slug,
|
||||
public String password(@PathVariable("type") String type,
|
||||
@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "password") String password) throws UnsupportedEncodingException {
|
||||
|
||||
String redirectUrl;
|
||||
|
||||
if (EncryptTypeEnum.POST.getName().equals(type)) {
|
||||
redirectUrl = doAuthenticationPost(slug, password);
|
||||
} else if (EncryptTypeEnum.CATEGORY.getName().equals(type)) {
|
||||
redirectUrl = doAuthenticationCategory(slug, password);
|
||||
} else {
|
||||
throw new UnsupportedException("未知的加密类型");
|
||||
}
|
||||
return "redirect:" + redirectUrl;
|
||||
}
|
||||
|
||||
private String doAuthenticationPost(
|
||||
String slug, String password) throws UnsupportedEncodingException {
|
||||
Post post = postService.getBy(PostStatus.INTIMATE, slug);
|
||||
|
||||
post.setSlug(URLEncoder.encode(post.getSlug(), StandardCharsets.UTF_8.name()));
|
||||
|
||||
authenticationService.postAuthentication(post, password);
|
||||
|
||||
BasePostMinimalDTO postMinimalDTO = postService.convertToMinimal(post);
|
||||
|
||||
StringBuilder redirectUrl = new StringBuilder();
|
||||
|
@ -243,19 +272,22 @@ public class ContentContentController {
|
|||
|
||||
redirectUrl.append(postMinimalDTO.getFullPath());
|
||||
|
||||
if (password.equals(post.getPassword())) {
|
||||
String token = IdUtil.simpleUUID();
|
||||
cacheStore.putAny(token, token, 10, TimeUnit.SECONDS);
|
||||
return redirectUrl.toString();
|
||||
}
|
||||
|
||||
if (optionService.getPostPermalinkType().equals(PostPermalinkType.ID)) {
|
||||
redirectUrl.append("&token=")
|
||||
.append(token);
|
||||
} else {
|
||||
redirectUrl.append("?token=")
|
||||
.append(token);
|
||||
}
|
||||
private String doAuthenticationCategory(String slug, String password) {
|
||||
Category category = categoryService.getBySlugOfNonNull(slug, true);
|
||||
|
||||
authenticationService.categoryAuthentication(category.getId(), password);
|
||||
|
||||
StringBuilder redirectUrl = new StringBuilder();
|
||||
|
||||
if (!optionService.isEnabledAbsolutePath()) {
|
||||
redirectUrl.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
|
||||
return "redirect:" + redirectUrl;
|
||||
redirectUrl.append(optionService.getCategoriesPrefix()).append(slug);
|
||||
|
||||
return redirectUrl.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package run.halo.app.controller.content.api;
|
|||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
@ -14,11 +15,13 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.AuthenticationService;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostService;
|
||||
|
@ -39,12 +42,16 @@ public class CategoryController {
|
|||
|
||||
private final PostService postService;
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
public CategoryController(CategoryService categoryService,
|
||||
PostCategoryService postCategoryService,
|
||||
PostService postService) {
|
||||
PostService postService,
|
||||
AuthenticationService authenticationService) {
|
||||
this.categoryService = categoryService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postService = postService;
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
|
@ -53,7 +60,7 @@ public class CategoryController {
|
|||
@SortDefault(sort = "updateTime", direction = DESC) Sort sort,
|
||||
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
|
||||
if (more) {
|
||||
return postCategoryService.listCategoryWithPostCountDto(sort);
|
||||
return postCategoryService.listCategoryWithPostCountDto(sort, false);
|
||||
}
|
||||
return categoryService.convertTo(categoryService.listAll(sort));
|
||||
}
|
||||
|
@ -61,13 +68,19 @@ public class CategoryController {
|
|||
@GetMapping("{slug}/posts")
|
||||
@ApiOperation("Lists posts by category slug")
|
||||
public Page<PostListVO> listPostsBy(@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "password", required = false) String password,
|
||||
@PageableDefault(sort = {"topPriority", "updateTime"}, direction = DESC)
|
||||
Pageable pageable) {
|
||||
// Get category by slug
|
||||
Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
Category category = categoryService.getBySlugOfNonNull(slug, true);
|
||||
|
||||
if (!authenticationService.categoryAuthentication(category.getId(), password)) {
|
||||
throw new ForbiddenException("您没有该分类的访问权限");
|
||||
}
|
||||
|
||||
Page<Post> postPage =
|
||||
postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
|
||||
postCategoryService.pagePostBy(category.getId(),
|
||||
Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE), pageable);
|
||||
return postService.convertToListVo(postPage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
|
||||
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
@ -12,8 +15,10 @@ import org.springframework.ui.Model;
|
|||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.AuthenticationService;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
|
@ -39,14 +44,20 @@ public class CategoryModel {
|
|||
|
||||
private final OptionService optionService;
|
||||
|
||||
public CategoryModel(CategoryService categoryService, ThemeService themeService,
|
||||
PostCategoryService postCategoryService, PostService postService,
|
||||
OptionService optionService) {
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
public CategoryModel(CategoryService categoryService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
PostService postService,
|
||||
OptionService optionService,
|
||||
AuthenticationService authenticationService) {
|
||||
this.categoryService = categoryService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postService = postService;
|
||||
this.optionService = optionService;
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,15 +82,27 @@ public class CategoryModel {
|
|||
* @return template name
|
||||
*/
|
||||
public String listPost(Model model, String slug, Integer page) {
|
||||
|
||||
// Get category by slug
|
||||
final Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
final Category category = categoryService.getBySlugOfNonNull(slug, true);
|
||||
|
||||
if (!authenticationService.categoryAuthentication(category.getId(), null)) {
|
||||
model.addAttribute("slug", category.getSlug());
|
||||
model.addAttribute("type", EncryptTypeEnum.CATEGORY.getName());
|
||||
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
|
||||
return themeService.render(POST_PASSWORD_TEMPLATE);
|
||||
}
|
||||
return "common/template/" + POST_PASSWORD_TEMPLATE;
|
||||
}
|
||||
|
||||
CategoryDTO categoryDTO = categoryService.convertTo(category);
|
||||
|
||||
final Pageable pageable = PageRequest.of(page - 1,
|
||||
optionService.getArchivesPageSize(),
|
||||
Sort.by(DESC, "topPriority", "createTime"));
|
||||
Page<Post> postPage =
|
||||
postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
|
||||
postCategoryService.pagePostBy(category.getId(), Sets
|
||||
.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE), pageable);
|
||||
Page<PostListVO> posts = postService.convertToListVo(postPage);
|
||||
|
||||
// Generate meta description.
|
||||
|
|
|
@ -13,15 +13,16 @@ import org.springframework.data.domain.Sort;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
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.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostEditorType;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.ArchiveYearVO;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.AuthenticationService;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
|
@ -59,6 +60,8 @@ public class PostModel {
|
|||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
public PostModel(PostService postService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
|
@ -67,7 +70,8 @@ public class PostModel {
|
|||
PostTagService postTagService,
|
||||
TagService tagService,
|
||||
OptionService optionService,
|
||||
AbstractStringCacheStore cacheStore) {
|
||||
AbstractStringCacheStore cacheStore,
|
||||
AuthenticationService authenticationService) {
|
||||
this.postService = postService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
|
@ -77,32 +81,27 @@ public class PostModel {
|
|||
this.tagService = tagService;
|
||||
this.optionService = optionService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
public String content(Post post, String token, Model model) {
|
||||
|
||||
if (post.getStatus().equals(PostStatus.INTIMATE) && StringUtils.isEmpty(token)) {
|
||||
if (post.getStatus().equals(PostStatus.INTIMATE)
|
||||
&& !authenticationService.postAuthentication(post, null)) {
|
||||
model.addAttribute("slug", post.getSlug());
|
||||
model.addAttribute("type", EncryptTypeEnum.POST.getName());
|
||||
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
|
||||
return themeService.render(POST_PASSWORD_TEMPLATE);
|
||||
}
|
||||
return "common/template/" + POST_PASSWORD_TEMPLATE;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
post = postService.getBy(PostStatus.PUBLISHED, post.getSlug());
|
||||
post = postService.getById(post.getId());
|
||||
|
||||
if (post.getEditorType().equals(PostEditorType.MARKDOWN)) {
|
||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
||||
} else {
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class)
|
||||
.orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该文章的访问权限");
|
||||
}
|
||||
if (post.getEditorType().equals(PostEditorType.MARKDOWN)) {
|
||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
||||
} else {
|
||||
post.setFormatContent(post.getOriginalContent());
|
||||
}
|
||||
post.setFormatContent(post.getOriginalContent());
|
||||
}
|
||||
|
||||
postService.publishVisitEvent(post.getId());
|
||||
|
@ -112,7 +111,7 @@ public class PostModel {
|
|||
postService.getNextPost(post).ifPresent(
|
||||
nextPost -> model.addAttribute("nextPost", postService.convertToDetailVo(nextPost)));
|
||||
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(post.getId(), false);
|
||||
List<Tag> tags = postTagService.listTagsBy(post.getId());
|
||||
List<PostMeta> metas = postMetaService.listBy(post.getId());
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public class CategoryTagDirective implements TemplateDirectiveModel {
|
|||
switch (method) {
|
||||
case "list":
|
||||
env.setVariable("categories", builder.build().wrap(postCategoryService
|
||||
.listCategoryWithPostCountDto(Sort.by(DESC, "createTime"))));
|
||||
.listCategoryWithPostCountDto(Sort.by(DESC, "createTime"), false)));
|
||||
break;
|
||||
case "tree":
|
||||
env.setVariable("categories", builder.build()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
|
@ -77,12 +78,14 @@ public class PostTagDirective implements TemplateDirectiveModel {
|
|||
case "listByCategoryId":
|
||||
Integer categoryId = Integer.parseInt(params.get("categoryId").toString());
|
||||
env.setVariable("posts", builder.build().wrap(postService.convertToListVo(
|
||||
postCategoryService.listPostBy(categoryId, PostStatus.PUBLISHED))));
|
||||
postCategoryService.listPostBy(categoryId,
|
||||
Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE)))));
|
||||
break;
|
||||
case "listByCategorySlug":
|
||||
String categorySlug = params.get("categorySlug").toString();
|
||||
List<Post> posts =
|
||||
postCategoryService.listPostBy(categorySlug, PostStatus.PUBLISHED);
|
||||
postCategoryService.listPostBy(categorySlug,
|
||||
Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE));
|
||||
env.setVariable("posts",
|
||||
builder.build().wrap(postService.convertToListVo(posts)));
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package run.halo.app.exception;
|
||||
|
||||
/**
|
||||
* @author ZhiXiang Yuan
|
||||
* @date 2021/01/18 20:13
|
||||
*/
|
||||
public class UnsupportedException extends ServiceException {
|
||||
public UnsupportedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,8 @@ public class CategoryDTO implements OutputConverter<CategoryDTO, Category> {
|
|||
|
||||
private Integer parentId;
|
||||
|
||||
private String password;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
private String fullPath;
|
||||
|
|
|
@ -73,6 +73,12 @@ public class Category extends BaseEntity {
|
|||
@ColumnDefault("0")
|
||||
private Integer parentId;
|
||||
|
||||
/**
|
||||
* Category password.
|
||||
*/
|
||||
@Column(name = "password")
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public void prePersist() {
|
||||
super.prePersist();
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
/**
|
||||
* @author zhixiang.yuan
|
||||
* @since 2021/01/24 10:45:33
|
||||
*/
|
||||
public enum EncryptTypeEnum {
|
||||
|
||||
POST("post"), CATEGORY("category");
|
||||
|
||||
private final String name;
|
||||
|
||||
EncryptTypeEnum(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
|
@ -31,6 +31,9 @@ public class CategoryParam implements InputConverter<Category> {
|
|||
@Size(max = 1023, message = "封面图链接的字符长度不能超过 {max}")
|
||||
private String thumbnail;
|
||||
|
||||
@Size(max = 255, message = "分类密码的字符长度不能超过 {max}")
|
||||
private String password;
|
||||
|
||||
private Integer parentId = 0;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -55,6 +55,19 @@ public interface PostCategoryRepository extends BaseRepository<PostCategory, Int
|
|||
Set<Integer> findAllPostIdsByCategoryId(@NonNull Integer categoryId,
|
||||
@NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Finds all post ids by category id and post status.
|
||||
*
|
||||
* @param categoryId category id must not be null
|
||||
* @param status post status must not be empty
|
||||
* @return a set of post id
|
||||
*/
|
||||
@NonNull
|
||||
@Query("select postCategory.postId from PostCategory postCategory, Post post where"
|
||||
+ " postCategory.categoryId = ?1 and post.id = postCategory.postId and post.status in (?2)")
|
||||
Set<Integer> findAllPostIdsByCategoryId(
|
||||
@NonNull Integer categoryId, @NonNull Set<PostStatus> status);
|
||||
|
||||
/**
|
||||
* Finds all post categories by post id in.
|
||||
*
|
||||
|
@ -104,4 +117,14 @@ public interface PostCategoryRepository extends BaseRepository<PostCategory, Int
|
|||
+ ", pc.categoryId) from PostCategory pc group by pc.categoryId")
|
||||
@NonNull
|
||||
List<CategoryPostCountProjection> findPostCount();
|
||||
|
||||
/**
|
||||
* Finds all post categories by category id list.
|
||||
*
|
||||
* @param categoryIdList category id list must not be empty
|
||||
* @return a list of post category
|
||||
*/
|
||||
@Query("select pc from PostCategory pc where pc.categoryId in (?1)")
|
||||
@NonNull
|
||||
List<PostCategory> findAllByCategoryIdList(List<Integer> categoryIdList);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import run.halo.app.model.entity.Post;
|
||||
|
||||
/**
|
||||
* Authentication service
|
||||
*
|
||||
* @author ZhiXiang Yuan
|
||||
* @date 2021/01/20 17:39
|
||||
*/
|
||||
public interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* post authentication
|
||||
*
|
||||
* @param post post
|
||||
* @param password password
|
||||
* @return authentication success or fail
|
||||
*/
|
||||
boolean postAuthentication(Post post, String password);
|
||||
|
||||
/**
|
||||
* category authentication
|
||||
*
|
||||
* @param categoryId category id
|
||||
* @param password password
|
||||
* @return authentication success or fail
|
||||
*/
|
||||
boolean categoryAuthentication(Integer categoryId, String password);
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author ZhiXiang Yuan
|
||||
* @date 2021/01/20 17:40
|
||||
*/
|
||||
public interface AuthorizationService {
|
||||
|
||||
/**
|
||||
* Build post token
|
||||
*
|
||||
* @param postId post id
|
||||
* @return token
|
||||
*/
|
||||
static String buildPostToken(Integer postId) {
|
||||
return "POST:" + postId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build category token
|
||||
*
|
||||
* @param categoryId category id
|
||||
* @return token
|
||||
*/
|
||||
static String buildCategoryToken(Integer categoryId) {
|
||||
return "CATEGORY:" + categoryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post authorization
|
||||
*
|
||||
* @param postId post id
|
||||
*/
|
||||
void postAuthorization(Integer postId);
|
||||
|
||||
/**
|
||||
* CategoryAuthorization
|
||||
*
|
||||
* @param categoryId category id
|
||||
*/
|
||||
void categoryAuthorization(Integer categoryId);
|
||||
|
||||
/**
|
||||
* Get access permission store
|
||||
*
|
||||
* @return access permission store
|
||||
*/
|
||||
Set<String> getAccessPermissionStore();
|
||||
|
||||
/**
|
||||
* Delete article authorization
|
||||
*
|
||||
* @param postId post id
|
||||
*/
|
||||
void deletePostAuthorization(Integer postId);
|
||||
|
||||
/**
|
||||
* Delete category Authorization
|
||||
*
|
||||
* @param categoryId category id
|
||||
*/
|
||||
void deleteCategoryAuthorization(Integer categoryId);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
@ -35,7 +36,6 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
* @param slug slug
|
||||
* @return Category
|
||||
*/
|
||||
@NonNull
|
||||
Category getBySlug(@NonNull String slug);
|
||||
|
||||
/**
|
||||
|
@ -47,6 +47,16 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
@NonNull
|
||||
Category getBySlugOfNonNull(String slug);
|
||||
|
||||
/**
|
||||
* Get category by slug
|
||||
*
|
||||
* @param slug slug
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return Category
|
||||
*/
|
||||
@NonNull
|
||||
Category getBySlugOfNonNull(String slug, boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Get Category by name.
|
||||
*
|
||||
|
@ -64,6 +74,14 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
@Transactional
|
||||
void removeCategoryAndPostCategoryBy(Integer categoryId);
|
||||
|
||||
/**
|
||||
* Refresh post status, when the post is under the encryption category or is has a password,
|
||||
* it is changed to private, otherwise it is changed to public.
|
||||
*
|
||||
* @param affectedPostIdList affected post id list
|
||||
*/
|
||||
void refreshPostStatus(List<Integer> affectedPostIdList);
|
||||
|
||||
/**
|
||||
* List categories by parent id.
|
||||
*
|
||||
|
@ -72,6 +90,26 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
*/
|
||||
List<Category> listByParentId(@NonNull Integer id);
|
||||
|
||||
/**
|
||||
* List all category not encrypt.
|
||||
*
|
||||
* @param sort sort
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return list of category.
|
||||
*/
|
||||
@NonNull
|
||||
List<Category> listAll(Sort sort, boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* List all by ids
|
||||
*
|
||||
* @param ids ids
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return List
|
||||
*/
|
||||
@NonNull
|
||||
List<Category> listAllByIds(Collection<Integer> ids, boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Converts to category dto.
|
||||
*
|
||||
|
@ -89,4 +127,22 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
*/
|
||||
@NonNull
|
||||
List<CategoryDTO> convertTo(@Nullable List<Category> categories);
|
||||
|
||||
/**
|
||||
* Filter encrypt category
|
||||
*
|
||||
* @param categories this categories is not a category list tree
|
||||
* @return category list
|
||||
*/
|
||||
@NonNull
|
||||
List<Category> filterEncryptCategory(@Nullable List<Category> categories);
|
||||
|
||||
/**
|
||||
* Determine whether the category is encrypted.
|
||||
*
|
||||
* @param categoryId category id
|
||||
* @return whether to encrypt
|
||||
*/
|
||||
@NonNull
|
||||
Boolean categoryHasEncrypt(Integer categoryId);
|
||||
}
|
||||
|
|
|
@ -35,14 +35,26 @@ public interface PostCategoryService extends CrudService<PostCategory, Integer>
|
|||
@NonNull
|
||||
List<Category> listCategoriesBy(@NonNull Integer postId);
|
||||
|
||||
/**
|
||||
* Lists category by post id.
|
||||
*
|
||||
* @param postId post id must not be null
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return a list of category
|
||||
*/
|
||||
@NonNull
|
||||
List<Category> listCategoriesBy(@NonNull Integer postId, @NonNull boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* List category list map by post id collection.
|
||||
*
|
||||
* @param postIds post id collection
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return a category list map (key: postId, value: a list of category)
|
||||
*/
|
||||
@NonNull
|
||||
Map<Integer, List<Category>> listCategoryListMap(@Nullable Collection<Integer> postIds);
|
||||
Map<Integer, List<Category>> listCategoryListMap(
|
||||
@Nullable Collection<Integer> postIds, @NonNull boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Lists post by category id.
|
||||
|
@ -63,6 +75,16 @@ public interface PostCategoryService extends CrudService<PostCategory, Integer>
|
|||
@NonNull
|
||||
List<Post> listPostBy(@NonNull Integer categoryId, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Lists post by category id and post status.
|
||||
*
|
||||
* @param categoryId category id must not be null
|
||||
* @param status post status
|
||||
* @return a list of post
|
||||
*/
|
||||
@NonNull
|
||||
List<Post> listPostBy(@NonNull Integer categoryId, @NonNull Set<PostStatus> status);
|
||||
|
||||
/**
|
||||
* Lists post by category slug and post status.
|
||||
*
|
||||
|
@ -73,6 +95,16 @@ public interface PostCategoryService extends CrudService<PostCategory, Integer>
|
|||
@NonNull
|
||||
List<Post> listPostBy(@NonNull String slug, @NonNull PostStatus status);
|
||||
|
||||
/**
|
||||
* Lists post by category slug and post status.
|
||||
*
|
||||
* @param slug category slug must not be null
|
||||
* @param status post status
|
||||
* @return a list of post
|
||||
*/
|
||||
@NonNull
|
||||
List<Post> listPostBy(@NonNull String slug, @NonNull Set<PostStatus> status);
|
||||
|
||||
/**
|
||||
* Pages post by category id.
|
||||
*
|
||||
|
@ -95,6 +127,18 @@ public interface PostCategoryService extends CrudService<PostCategory, Integer>
|
|||
Page<Post> pagePostBy(@NonNull Integer categoryId, @NonNull PostStatus status,
|
||||
Pageable pageable);
|
||||
|
||||
/**
|
||||
* Pages post by category id and post status.
|
||||
*
|
||||
* @param categoryId category id must not be null
|
||||
* @param status post status
|
||||
* @param pageable pageable
|
||||
* @return page of post
|
||||
*/
|
||||
@NonNull
|
||||
Page<Post> pagePostBy(
|
||||
@NonNull Integer categoryId, @NonNull Set<PostStatus> status, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Merges or creates post categories by post id and category id set if absent.
|
||||
*
|
||||
|
@ -157,8 +201,19 @@ public interface PostCategoryService extends CrudService<PostCategory, Integer>
|
|||
* Lists category with post count.
|
||||
*
|
||||
* @param sort sort info
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return a list of category dto
|
||||
*/
|
||||
@NonNull
|
||||
List<CategoryWithPostCountDTO> listCategoryWithPostCountDto(@NonNull Sort sort);
|
||||
List<CategoryWithPostCountDTO> listCategoryWithPostCountDto(
|
||||
@NonNull Sort sort, @NonNull boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Lists by category id.
|
||||
*
|
||||
* @param categoryIdList category id must not be empty
|
||||
* @return a list of post category
|
||||
*/
|
||||
@NonNull
|
||||
List<PostCategory> listByCategoryIdList(@NonNull List<Integer> categoryIdList);
|
||||
}
|
||||
|
|
|
@ -248,6 +248,16 @@ public interface PostService extends BasePostService<Post> {
|
|||
*/
|
||||
Page<PostDetailVO> convertToDetailVo(@NonNull Page<Post> postPage);
|
||||
|
||||
/**
|
||||
* Converts to detail vo.
|
||||
*
|
||||
* @param post post must not be null
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return post detail vo
|
||||
*/
|
||||
@NonNull
|
||||
PostDetailVO convertToDetailVo(@NonNull Post post, @NonNull boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Converts to a page of post list vo.
|
||||
*
|
||||
|
@ -257,6 +267,16 @@ public interface PostService extends BasePostService<Post> {
|
|||
@NonNull
|
||||
Page<PostListVO> convertToListVo(@NonNull Page<Post> postPage);
|
||||
|
||||
/**
|
||||
* Converts to a page of post list vo.
|
||||
*
|
||||
* @param postPage post page must not be null
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return a page of post list vo
|
||||
*/
|
||||
@NonNull
|
||||
Page<PostListVO> convertToListVo(@NonNull Page<Post> postPage, boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Converts to a list of post list vo.
|
||||
*
|
||||
|
@ -266,6 +286,14 @@ public interface PostService extends BasePostService<Post> {
|
|||
@NonNull
|
||||
List<PostListVO> convertToListVo(@NonNull List<Post> posts);
|
||||
|
||||
/**
|
||||
* Converts to a list of post list vo.
|
||||
*
|
||||
* @param posts post must not be null
|
||||
* @param queryEncryptCategory whether to query encryption category
|
||||
* @return a list of post list vo
|
||||
*/
|
||||
List<PostListVO> convertToListVo(List<Post> posts, boolean queryEncryptCategory);
|
||||
|
||||
/**
|
||||
* Publish a post visit event.
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.stereotype.Service;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.repository.CategoryRepository;
|
||||
import run.halo.app.repository.PostCategoryRepository;
|
||||
import run.halo.app.service.AuthenticationService;
|
||||
import run.halo.app.service.AuthorizationService;
|
||||
|
||||
|
||||
/**
|
||||
* @author ZhiXiang Yuan
|
||||
* @date 2021/01/20 17:56
|
||||
*/
|
||||
@Service
|
||||
public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
private final AuthorizationService authorizationService;
|
||||
|
||||
private final PostCategoryRepository postCategoryRepository;
|
||||
|
||||
public AuthenticationServiceImpl(PostCategoryRepository postCategoryRepository,
|
||||
CategoryRepository categoryRepository,
|
||||
AuthorizationService authorizationService
|
||||
) {
|
||||
this.postCategoryRepository = postCategoryRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean postAuthentication(Post post, String password) {
|
||||
Set<String> accessPermissionStore = authorizationService.getAccessPermissionStore();
|
||||
|
||||
if (StrUtil.isNotBlank(post.getPassword())) {
|
||||
if (accessPermissionStore.contains(AuthorizationService.buildPostToken(post.getId()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (post.getPassword().equals(password)) {
|
||||
authorizationService.postAuthorization(post.getId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<Integer> allCategoryIdSet = postCategoryRepository
|
||||
.findAllCategoryIdsByPostId(post.getId());
|
||||
|
||||
if (allCategoryIdSet.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Integer categoryId : allCategoryIdSet) {
|
||||
if (categoryAuthentication(categoryId, password)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean categoryAuthentication(Integer categoryId, String password) {
|
||||
|
||||
Map<Integer, Category> idToCategoryMap = categoryRepository.findAll().stream()
|
||||
.collect(Collectors.toMap(Category::getId, Function.identity()));
|
||||
|
||||
Set<String> accessPermissionStore = authorizationService.getAccessPermissionStore();
|
||||
|
||||
return doCategoryAuthentication(
|
||||
idToCategoryMap, accessPermissionStore, categoryId, password);
|
||||
}
|
||||
|
||||
private boolean doCategoryAuthentication(Map<Integer, Category> idToCategoryMap,
|
||||
Set<String> accessPermissionStore,
|
||||
Integer categoryId, String password) {
|
||||
|
||||
Category category = idToCategoryMap.get(categoryId);
|
||||
|
||||
if (StrUtil.isNotBlank(category.getPassword())) {
|
||||
if (accessPermissionStore.contains(
|
||||
AuthorizationService.buildCategoryToken(category.getId()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (category.getPassword().equals(password)) {
|
||||
authorizationService.categoryAuthorization(category.getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (category.getParentId() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return doCategoryAuthentication(
|
||||
idToCategoryMap, accessPermissionStore, category.getParentId(), password);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.service.AuthorizationService;
|
||||
|
||||
/**
|
||||
* @author ZhiXiang Yuan
|
||||
* @date 2021/01/21 11:28
|
||||
*/
|
||||
@Service
|
||||
public class AuthorizationServiceImpl implements AuthorizationService {
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
public AuthorizationServiceImpl(AbstractStringCacheStore cacheStore) {
|
||||
this.cacheStore = cacheStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAuthorization(Integer postId) {
|
||||
doAuthorization(AuthorizationService.buildPostToken(postId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void categoryAuthorization(Integer categoryId) {
|
||||
doAuthorization(AuthorizationService.buildCategoryToken(categoryId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAccessPermissionStore() {
|
||||
return cacheStore.getAny(buildAccessPermissionKey(), Set.class).orElse(new HashSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePostAuthorization(Integer postId) {
|
||||
doDeleteAuthorization(AuthorizationService.buildPostToken(postId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCategoryAuthorization(Integer categoryId) {
|
||||
doDeleteAuthorization(AuthorizationService.buildCategoryToken(categoryId));
|
||||
}
|
||||
|
||||
private void doDeleteAuthorization(String value) {
|
||||
Set<String> accessStore = getAccessPermissionStore();
|
||||
|
||||
accessStore.remove(value);
|
||||
|
||||
cacheStore.putAny(buildAccessPermissionKey(), accessStore, 1, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
private void doAuthorization(String value) {
|
||||
Set<String> accessStore = getAccessPermissionStore();
|
||||
|
||||
accessStore.add(value);
|
||||
|
||||
cacheStore.putAny(buildAccessPermissionKey(), accessStore, 1, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
private String buildAccessPermissionKey() {
|
||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
|
||||
.getRequestAttributes();
|
||||
|
||||
HttpServletRequest request = requestAttributes.getRequest();
|
||||
|
||||
return "ACCESS_PERMISSION: " + request.getSession().getId();
|
||||
}
|
||||
|
||||
}
|
|
@ -291,11 +291,6 @@ public abstract class BasePostServiceImpl<POST extends BasePost>
|
|||
post.setFormatContent(post.getOriginalContent());
|
||||
}
|
||||
|
||||
// if password is not empty,change status to intimate
|
||||
if (StringUtils.isNotEmpty(post.getPassword()) && post.getStatus() != PostStatus.DRAFT) {
|
||||
post.setStatus(PostStatus.INTIMATE);
|
||||
}
|
||||
|
||||
// Create or update post
|
||||
if (ServiceUtils.isEmptyId(post.getId())) {
|
||||
// The sheet will be created
|
||||
|
|
|
@ -2,12 +2,23 @@ package run.halo.app.service.impl;
|
|||
|
||||
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.common.base.Objects;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -16,14 +27,22 @@ import org.springframework.util.Assert;
|
|||
import org.springframework.util.CollectionUtils;
|
||||
import run.halo.app.exception.AlreadyExistsException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.exception.UnsupportedException;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostCategory;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.CategoryVO;
|
||||
import run.halo.app.repository.CategoryRepository;
|
||||
import run.halo.app.service.AuthenticationService;
|
||||
import run.halo.app.service.AuthorizationService;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.base.AbstractCrudService;
|
||||
import run.halo.app.utils.BeanUtils;
|
||||
import run.halo.app.utils.ServiceUtils;
|
||||
|
||||
/**
|
||||
|
@ -44,13 +63,29 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final AuthorizationService authorizationService;
|
||||
|
||||
private PostService postService;
|
||||
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
public CategoryServiceImpl(CategoryRepository categoryRepository,
|
||||
PostCategoryService postCategoryService,
|
||||
OptionService optionService) {
|
||||
OptionService optionService,
|
||||
AuthenticationService authenticationService,
|
||||
AuthorizationService authorizationService) {
|
||||
super(categoryRepository);
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.optionService = optionService;
|
||||
this.authenticationService = authenticationService;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
public void setPostService(PostService postService) {
|
||||
this.postService = postService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,6 +113,10 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(category.getPassword())) {
|
||||
category.setPassword(category.getPassword().trim());
|
||||
}
|
||||
|
||||
// Create it
|
||||
return super.create(category);
|
||||
}
|
||||
|
@ -97,7 +136,7 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
// Concrete the tree
|
||||
concreteTree(topLevelCategory, categories);
|
||||
concreteTree(topLevelCategory, categories, false);
|
||||
|
||||
return topLevelCategory.getChildren();
|
||||
}
|
||||
|
@ -107,8 +146,13 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
*
|
||||
* @param parentCategory parent category vo must not be null
|
||||
* @param categories a list of category
|
||||
* @param fillPassword whether to fill in the password
|
||||
*/
|
||||
private void concreteTree(CategoryVO parentCategory, List<Category> categories) {
|
||||
private void concreteTree(
|
||||
CategoryVO parentCategory,
|
||||
List<Category> categories,
|
||||
Boolean fillPassword
|
||||
) {
|
||||
Assert.notNull(parentCategory, "Parent category must not be null");
|
||||
|
||||
if (CollectionUtils.isEmpty(categories)) {
|
||||
|
@ -142,6 +186,10 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
|
||||
child.setFullPath(fullPath.toString());
|
||||
|
||||
if (!fillPassword) {
|
||||
child.setPassword(null);
|
||||
}
|
||||
|
||||
// Add child
|
||||
parentCategory.getChildren().add(child);
|
||||
});
|
||||
|
@ -152,7 +200,7 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
// Foreach children vos
|
||||
if (!CollectionUtils.isEmpty(parentCategory.getChildren())) {
|
||||
parentCategory.getChildren()
|
||||
.forEach(childCategory -> concreteTree(childCategory, categories));
|
||||
.forEach(childCategory -> concreteTree(childCategory, categories, fillPassword));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,18 +222,58 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
|
||||
@Override
|
||||
public Category getBySlug(String slug) {
|
||||
return categoryRepository.getBySlug(slug).orElse(null);
|
||||
Optional<Category> bySlug = categoryRepository.getBySlug(slug);
|
||||
if (bySlug.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Category category = bySlug.get();
|
||||
|
||||
if (authenticationService.categoryAuthentication(category.getId(), null)) {
|
||||
return category;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getBySlugOfNonNull(String slug) {
|
||||
return categoryRepository.getBySlug(slug)
|
||||
.orElseThrow(() -> new NotFoundException("查询不到该分类的信息").setErrorData(slug));
|
||||
|
||||
Category category = categoryRepository
|
||||
.getBySlug(slug)
|
||||
.orElseThrow(() -> new NotFoundException("查询不到该分类的信息").setErrorData(slug));
|
||||
|
||||
if (authenticationService.categoryAuthentication(category.getId(), null)) {
|
||||
return category;
|
||||
}
|
||||
|
||||
throw new NotFoundException("查询不到该分类的信息").setErrorData(slug);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getBySlugOfNonNull(String slug, boolean queryEncryptCategory) {
|
||||
if (queryEncryptCategory) {
|
||||
return categoryRepository.getBySlug(slug)
|
||||
.orElseThrow(() -> new NotFoundException("查询不到该分类的信息").setErrorData(slug));
|
||||
} else {
|
||||
return this.getBySlugOfNonNull(slug);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getByName(String name) {
|
||||
return categoryRepository.getByName(name).orElse(null);
|
||||
Optional<Category> byName = categoryRepository.getByName(name);
|
||||
if (byName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Category category = byName.get();
|
||||
|
||||
if (authenticationService.categoryAuthentication(category.getId(), null)) {
|
||||
return category;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -198,10 +286,44 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
update(category);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove category
|
||||
removeById(categoryId);
|
||||
// Remove post categories
|
||||
postCategoryService.removeByCategoryId(categoryId);
|
||||
List<Integer> affectedPostIdList = postCategoryService.removeByCategoryId(categoryId)
|
||||
.stream().map(PostCategory::getPostId).collect(Collectors.toList());
|
||||
|
||||
refreshPostStatus(affectedPostIdList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshPostStatus(List<Integer> affectedPostIdList) {
|
||||
if (CollectionUtil.isEmpty(affectedPostIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Integer postId : affectedPostIdList) {
|
||||
Post post = postService.getById(postId);
|
||||
|
||||
post.setStatus(null);
|
||||
|
||||
if (StrUtil.isNotBlank(post.getPassword())) {
|
||||
post.setStatus(PostStatus.INTIMATE);
|
||||
} else {
|
||||
postCategoryService.listByPostId(postId)
|
||||
.stream().map(PostCategory::getCategoryId)
|
||||
.filter(this::categoryHasEncrypt)
|
||||
.findAny()
|
||||
.ifPresent(id -> post.setStatus(PostStatus.INTIMATE));
|
||||
}
|
||||
|
||||
if (post.getStatus() == null) {
|
||||
post.setStatus(PostStatus.PUBLISHED);
|
||||
}
|
||||
|
||||
postService.update(post);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -243,4 +365,302 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
.map(this::convertTo)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> filterEncryptCategory(List<Category> categories) {
|
||||
if (CollectionUtil.isEmpty(categories)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Concrete the tree
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
concreteTree(topLevelCategory, categories, true);
|
||||
|
||||
List<CategoryVO> childrenList = topLevelCategory.getChildren();
|
||||
|
||||
// filter encrypt category
|
||||
doFilterEncryptCategory(childrenList);
|
||||
|
||||
List<Category> collectorList = new ArrayList<>();
|
||||
|
||||
collectAllChild(collectorList, childrenList, true);
|
||||
|
||||
for (Category category : collectorList) {
|
||||
category.setPassword(null);
|
||||
}
|
||||
|
||||
return collectorList;
|
||||
}
|
||||
|
||||
/**
|
||||
* do filter encrypt category
|
||||
*
|
||||
* @param categoryList category list
|
||||
*/
|
||||
private void doFilterEncryptCategory(List<CategoryVO> categoryList) {
|
||||
if (CollectionUtil.isEmpty(categoryList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CategoryVO categoryVO : categoryList) {
|
||||
if (!authenticationService.categoryAuthentication(categoryVO.getId(), null)) {
|
||||
// if parent category is not certified, the child category is not displayed.
|
||||
categoryVO.setChildren(null);
|
||||
} else {
|
||||
doFilterEncryptCategory(categoryVO.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all child from tree
|
||||
*
|
||||
* @param collectorList collector
|
||||
* @param childrenList contains categories of children
|
||||
* @param doNotCollectEncryptedCategory true is not collect, false is collect
|
||||
*/
|
||||
private void collectAllChild(List<Category> collectorList,
|
||||
List<CategoryVO> childrenList,
|
||||
Boolean doNotCollectEncryptedCategory) {
|
||||
if (CollectionUtil.isEmpty(childrenList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CategoryVO categoryVO : childrenList) {
|
||||
|
||||
Category category = new Category();
|
||||
BeanUtils.updateProperties(categoryVO, category);
|
||||
|
||||
collectorList.add(category);
|
||||
|
||||
if (doNotCollectEncryptedCategory
|
||||
&& !authenticationService.categoryAuthentication(category.getId(), null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CollectionUtil.isNotEmpty(categoryVO.getChildren())) {
|
||||
collectAllChild(collectorList,
|
||||
categoryVO.getChildren(), doNotCollectEncryptedCategory);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect sub-categories under the specified category.
|
||||
*
|
||||
* @param collectorList collector
|
||||
* @param childrenList contains categories of children
|
||||
* @param categoryId category id
|
||||
* @param doNotCollectEncryptedCategory true is not collect, false is collect
|
||||
*/
|
||||
private void collectAllChildByCategoryId(List<Category> collectorList,
|
||||
List<CategoryVO> childrenList,
|
||||
Integer categoryId,
|
||||
Boolean doNotCollectEncryptedCategory) {
|
||||
if (CollectionUtil.isEmpty(childrenList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CategoryVO categoryVO : childrenList) {
|
||||
if (categoryVO.getId().equals(categoryId)) {
|
||||
collectAllChild(collectorList,
|
||||
categoryVO.getChildren(), doNotCollectEncryptedCategory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAll(Sort sort, boolean queryEncryptCategory) {
|
||||
if (queryEncryptCategory) {
|
||||
return super.listAll(sort);
|
||||
} else {
|
||||
return this.listAll(sort);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAll() {
|
||||
return filterEncryptCategory(super.listAll());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAll(Sort sort) {
|
||||
return filterEncryptCategory(super.listAll(sort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Category> listAll(Pageable pageable) {
|
||||
// To prevent developers from querying encrypted categories,
|
||||
// so paging query operations are not supported here. If you
|
||||
// really need to use this method, refactor this method to do memory paging.
|
||||
throw new UnsupportedException("Does not support business layer paging query.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAllByIds(Collection<Integer> integers, boolean queryEncryptCategory) {
|
||||
if (queryEncryptCategory) {
|
||||
return super.listAllByIds(integers);
|
||||
} else {
|
||||
return this.listAllByIds(integers);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAllByIds(Collection<Integer> integers) {
|
||||
return filterEncryptCategory(super.listAllByIds(integers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAllByIds(Collection<Integer> integers, Sort sort) {
|
||||
return filterEncryptCategory(super.listAllByIds(integers, sort));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Category update(Category category) {
|
||||
Category update = super.update(category);
|
||||
|
||||
if (StrUtil.isNotBlank(category.getPassword())) {
|
||||
doEncryptPost(category);
|
||||
} else {
|
||||
doDecryptPost(category);
|
||||
}
|
||||
|
||||
// Remove authorization every time an category is updated.
|
||||
authorizationService.deleteCategoryAuthorization(category.getId());
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypting a category requires encrypting all articles under the category
|
||||
*
|
||||
* @param category need encrypt category
|
||||
*/
|
||||
private void doEncryptPost(Category category) {
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
concreteTree(topLevelCategory, super.listAll(), true);
|
||||
|
||||
List<Category> collectorList = new ArrayList<>();
|
||||
|
||||
collectAllChildByCategoryId(collectorList,
|
||||
topLevelCategory.getChildren(), category.getId(), true);
|
||||
|
||||
Optional.of(collectorList.stream().map(Category::getId).collect(Collectors.toList()))
|
||||
.map(categoryIdList -> {
|
||||
categoryIdList.add(category.getId());
|
||||
return categoryIdList;
|
||||
})
|
||||
.map(postCategoryService::listByCategoryIdList)
|
||||
|
||||
.filter(postCategoryList -> !postCategoryList.isEmpty())
|
||||
.map(postCategoryList -> postCategoryList
|
||||
.stream().map(PostCategory::getPostId).distinct().collect(Collectors.toList()))
|
||||
|
||||
.filter(postIdList -> !postIdList.isEmpty())
|
||||
.map(postIdList -> postService.listAllByIds(postIdList))
|
||||
|
||||
.filter(postList -> !postList.isEmpty())
|
||||
.map(postList -> postList.stream()
|
||||
.filter(post -> PostStatus.PUBLISHED.equals(post.getStatus()))
|
||||
.map(Post::getId).collect(Collectors.toList()))
|
||||
|
||||
.filter(postIdList -> !postIdList.isEmpty())
|
||||
.map(postIdList -> postService.updateStatusByIds(postIdList, PostStatus.INTIMATE));
|
||||
}
|
||||
|
||||
private void doDecryptPost(Category category) {
|
||||
|
||||
List<Category> allCategoryList = super.listAll();
|
||||
|
||||
Map<Integer, Category> idToCategoryMap = allCategoryList.stream().collect(
|
||||
Collectors.toMap(Category::getId, Function.identity()));
|
||||
|
||||
if (doCategoryHasEncrypt(idToCategoryMap, category.getParentId())) {
|
||||
// If the parent category is encrypted, there is no need to update the encryption status
|
||||
return;
|
||||
}
|
||||
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
concreteTree(topLevelCategory, allCategoryList, true);
|
||||
|
||||
List<Category> collectorList = new ArrayList<>();
|
||||
|
||||
// Only collect unencrypted sub-categories under the category.
|
||||
collectAllChildByCategoryId(collectorList,
|
||||
topLevelCategory.getChildren(), category.getId(), false);
|
||||
// Collect the currently decrypted category
|
||||
collectorList.add(category);
|
||||
|
||||
Optional.of(collectorList.stream().map(Category::getId).collect(Collectors.toList()))
|
||||
.map(postCategoryService::listByCategoryIdList)
|
||||
|
||||
.filter(postCategoryList -> !postCategoryList.isEmpty())
|
||||
.map(postCategoryList -> postCategoryList
|
||||
.stream().map(PostCategory::getPostId).distinct().collect(Collectors.toList()))
|
||||
|
||||
.filter(postIdList -> !postIdList.isEmpty())
|
||||
.map(postIdList -> postService.listAllByIds(postIdList))
|
||||
|
||||
.filter(postList -> !postList.isEmpty())
|
||||
.map(postList -> postList.stream()
|
||||
.filter(post -> StrUtil.isBlank(post.getPassword()))
|
||||
.filter(post -> PostStatus.INTIMATE.equals(post.getStatus()))
|
||||
.map(Post::getId).collect(Collectors.toList()))
|
||||
|
||||
.filter(postIdList -> !postIdList.isEmpty())
|
||||
.map(postIdList -> postService.updateStatusByIds(postIdList, PostStatus.PUBLISHED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean categoryHasEncrypt(Integer categoryId) {
|
||||
List<Category> allCategoryList = super.listAll();
|
||||
|
||||
Map<Integer, Category> idToCategoryMap = allCategoryList.stream().collect(
|
||||
Collectors.toMap(Category::getId, Function.identity()));
|
||||
|
||||
return doCategoryHasEncrypt(idToCategoryMap, categoryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find whether the parent category is encrypted.
|
||||
*
|
||||
* @param idToCategoryMap find category by id
|
||||
* @param categoryId category id
|
||||
* @return whether to encrypt
|
||||
*/
|
||||
private boolean doCategoryHasEncrypt(
|
||||
Map<Integer, Category> idToCategoryMap, Integer categoryId) {
|
||||
|
||||
if (categoryId == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Category category = idToCategoryMap.get(categoryId);
|
||||
|
||||
if (StrUtil.isNotBlank(category.getPassword())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return doCategoryHasEncrypt(idToCategoryMap, category.getParentId());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Category> updateInBatch(Collection<Category> categories) {
|
||||
if (CollectionUtils.isEmpty(categories)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ArrayList<Category> resultList = new ArrayList<>();
|
||||
for (Category category : categories) {
|
||||
resultList.add(update(category));
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ import java.util.HashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
@ -23,9 +26,9 @@ import run.halo.app.model.entity.Post;
|
|||
import run.halo.app.model.entity.PostCategory;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.projection.CategoryPostCountProjection;
|
||||
import run.halo.app.repository.CategoryRepository;
|
||||
import run.halo.app.repository.PostCategoryRepository;
|
||||
import run.halo.app.repository.PostRepository;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.base.AbstractCrudService;
|
||||
|
@ -47,33 +50,44 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
|
||||
private final PostRepository postRepository;
|
||||
|
||||
private final CategoryRepository categoryRepository;
|
||||
private CategoryService categoryService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public PostCategoryServiceImpl(PostCategoryRepository postCategoryRepository,
|
||||
PostRepository postRepository,
|
||||
CategoryRepository categoryRepository,
|
||||
OptionService optionService) {
|
||||
super(postCategoryRepository);
|
||||
this.postCategoryRepository = postCategoryRepository;
|
||||
this.postRepository = postRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
public void setCategoryService(CategoryService categoryService) {
|
||||
this.categoryService = categoryService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listCategoriesBy(Integer postId) {
|
||||
return listCategoriesBy(postId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listCategoriesBy(Integer postId, boolean queryEncryptCategory) {
|
||||
Assert.notNull(postId, "Post id must not be null");
|
||||
|
||||
// Find all category ids
|
||||
Set<Integer> categoryIds = postCategoryRepository.findAllCategoryIdsByPostId(postId);
|
||||
|
||||
return categoryRepository.findAllById(categoryIds);
|
||||
return categoryService.listAllByIds(categoryIds, queryEncryptCategory);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<Integer, List<Category>> listCategoryListMap(Collection<Integer> postIds) {
|
||||
public Map<Integer, List<Category>> listCategoryListMap(
|
||||
Collection<Integer> postIds, boolean queryEncryptCategory) {
|
||||
if (CollectionUtils.isEmpty(postIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
@ -86,7 +100,7 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
ServiceUtils.fetchProperty(postCategories, PostCategory::getCategoryId);
|
||||
|
||||
// Find all categories
|
||||
List<Category> categories = categoryRepository.findAllById(categoryIds);
|
||||
List<Category> categories = categoryService.listAllByIds(categoryIds, queryEncryptCategory);
|
||||
|
||||
// Convert to category map
|
||||
Map<Integer, Category> categoryMap = ServiceUtils.convertToMap(categories, Category::getId);
|
||||
|
@ -124,13 +138,45 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
return postRepository.findAllById(postIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Post> listPostBy(Integer categoryId, Set<PostStatus> status) {
|
||||
Assert.notNull(categoryId, "Category id must not be null");
|
||||
Assert.notNull(status, "Post status must not be null");
|
||||
|
||||
// Find all post ids
|
||||
Set<Integer> postIds = postCategoryRepository
|
||||
.findAllPostIdsByCategoryId(categoryId, status);
|
||||
|
||||
return postRepository.findAllById(postIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Post> listPostBy(String slug, Set<PostStatus> status) {
|
||||
Assert.notNull(slug, "Category slug must not be null");
|
||||
Assert.notNull(status, "Post status must not be null");
|
||||
|
||||
Category category = categoryService.getBySlug(slug);
|
||||
|
||||
if (Objects.isNull(category)) {
|
||||
throw new NotFoundException("查询不到该分类的信息").setErrorData(slug);
|
||||
}
|
||||
|
||||
Set<Integer> postsIds = postCategoryRepository
|
||||
.findAllPostIdsByCategoryId(category.getId(), status);
|
||||
|
||||
return postRepository.findAllById(postsIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Post> listPostBy(String slug, PostStatus status) {
|
||||
Assert.notNull(slug, "Category slug must not be null");
|
||||
Assert.notNull(status, "Post status must not be null");
|
||||
|
||||
Category category = categoryRepository.getBySlug(slug)
|
||||
.orElseThrow(() -> new NotFoundException("查询不到该分类的信息").setErrorData(slug));
|
||||
Category category = categoryService.getBySlug(slug);
|
||||
|
||||
if (Objects.isNull(category)) {
|
||||
throw new NotFoundException("查询不到该分类的信息").setErrorData(slug);
|
||||
}
|
||||
|
||||
Set<Integer> postsIds =
|
||||
postCategoryRepository.findAllPostIdsByCategoryId(category.getId(), status);
|
||||
|
@ -155,6 +201,19 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
Assert.notNull(status, "Post status must not be null");
|
||||
Assert.notNull(pageable, "Page info must not be null");
|
||||
|
||||
// Find all post ids
|
||||
Set<Integer> postIds = postCategoryRepository
|
||||
.findAllPostIdsByCategoryId(categoryId, status);
|
||||
|
||||
return postRepository.findAllByIdIn(postIds, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Post> pagePostBy(Integer categoryId, Set<PostStatus> status, Pageable pageable) {
|
||||
Assert.notNull(categoryId, "Category id must not be null");
|
||||
Assert.notNull(status, "Post status must not be null");
|
||||
Assert.notNull(pageable, "Page info must not be null");
|
||||
|
||||
// Find all post ids
|
||||
Set<Integer> postIds =
|
||||
postCategoryRepository.findAllPostIdsByCategoryId(categoryId, status);
|
||||
|
@ -245,10 +304,10 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryWithPostCountDTO> listCategoryWithPostCountDto(Sort sort) {
|
||||
public List<CategoryWithPostCountDTO> listCategoryWithPostCountDto(
|
||||
Sort sort, boolean queryEncryptCategory) {
|
||||
Assert.notNull(sort, "Sort info must not be null");
|
||||
|
||||
List<Category> categories = categoryRepository.findAll(sort);
|
||||
List<Category> categories = categoryService.listAll(sort, queryEncryptCategory);
|
||||
|
||||
// Query category post count
|
||||
Map<Integer, Long> categoryPostCountMap = ServiceUtils
|
||||
|
@ -284,4 +343,10 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PostCategory> listByCategoryIdList(List<Integer> categoryIdList) {
|
||||
Assert.notEmpty(categoryIdList, "category id list not empty");
|
||||
return postCategoryRepository.findAllByCategoryIdList(categoryIdList);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.service.impl;
|
|||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.ArrayList;
|
||||
|
@ -60,6 +61,7 @@ import run.halo.app.model.vo.PostListVO;
|
|||
import run.halo.app.model.vo.PostMarkdownVO;
|
||||
import run.halo.app.repository.PostRepository;
|
||||
import run.halo.app.repository.base.BasePostRepository;
|
||||
import run.halo.app.service.AuthorizationService;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
|
@ -106,6 +108,8 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final AuthorizationService authorizationService;
|
||||
|
||||
public PostServiceImpl(BasePostRepository<Post> basePostRepository,
|
||||
OptionService optionService,
|
||||
PostRepository postRepository,
|
||||
|
@ -115,7 +119,8 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
PostCategoryService postCategoryService,
|
||||
PostCommentService postCommentService,
|
||||
ApplicationEventPublisher eventPublisher,
|
||||
PostMetaService postMetaService) {
|
||||
PostMetaService postMetaService,
|
||||
AuthorizationService authorizationService) {
|
||||
super(basePostRepository, optionService);
|
||||
this.postRepository = postRepository;
|
||||
this.tagService = tagService;
|
||||
|
@ -126,6 +131,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
this.eventPublisher = eventPublisher;
|
||||
this.postMetaService = postMetaService;
|
||||
this.optionService = optionService;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -501,10 +507,16 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
|
||||
@Override
|
||||
public PostDetailVO convertToDetailVo(Post post) {
|
||||
return convertToDetailVo(post, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PostDetailVO convertToDetailVo(Post post, boolean queryEncryptCategory) {
|
||||
// List tags
|
||||
List<Tag> tags = postTagService.listTagsBy(post.getId());
|
||||
// List categories
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
|
||||
List<Category> categories = postCategoryService
|
||||
.listCategoriesBy(post.getId(), queryEncryptCategory);
|
||||
// List metas
|
||||
List<PostMeta> metas = postMetaService.listBy(post.getId());
|
||||
// Convert to detail vo
|
||||
|
@ -552,6 +564,11 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
|
||||
@Override
|
||||
public Page<PostListVO> convertToListVo(Page<Post> postPage) {
|
||||
return convertToListVo(postPage, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<PostListVO> convertToListVo(Page<Post> postPage, boolean queryEncryptCategory) {
|
||||
Assert.notNull(postPage, "Post page must not be null");
|
||||
|
||||
List<Post> posts = postPage.getContent();
|
||||
|
@ -563,7 +580,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
|
||||
// Get category list map
|
||||
Map<Integer, List<Category>> categoryListMap = postCategoryService
|
||||
.listCategoryListMap(postIds);
|
||||
.listCategoryListMap(postIds, queryEncryptCategory);
|
||||
|
||||
// Get comment count
|
||||
Map<Integer, Long> commentCountMap = postCommentService.countByPostIds(postIds);
|
||||
|
@ -612,6 +629,11 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
|
||||
@Override
|
||||
public List<PostListVO> convertToListVo(List<Post> posts) {
|
||||
return convertToListVo(posts, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PostListVO> convertToListVo(List<Post> posts, boolean queryEncryptCategory) {
|
||||
Assert.notNull(posts, "Post page must not be null");
|
||||
|
||||
Set<Integer> postIds = ServiceUtils.fetchProperty(posts, Post::getId);
|
||||
|
@ -621,7 +643,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
|
||||
// Get category list map
|
||||
Map<Integer, List<Category>> categoryListMap = postCategoryService
|
||||
.listCategoryListMap(postIds);
|
||||
.listCategoryListMap(postIds, queryEncryptCategory);
|
||||
|
||||
// Get comment count
|
||||
Map<Integer, Long> commentCountMap = postCommentService.countByPostIds(postIds);
|
||||
|
@ -800,6 +822,24 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
Assert.notNull(post, "Post param must not be null");
|
||||
|
||||
// Create or update post
|
||||
Boolean needEncrypt = Optional.ofNullable(categoryIds)
|
||||
.filter(CollectionUtil::isNotEmpty)
|
||||
.map(categoryIdSet -> {
|
||||
for (Integer categoryId : categoryIdSet) {
|
||||
if (categoryService.categoryHasEncrypt(categoryId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}).orElse(Boolean.FALSE);
|
||||
|
||||
// if password is not empty or parent category has encrypt, change status to intimate
|
||||
if (post.getStatus() != PostStatus.DRAFT
|
||||
&& (StringUtils.isNotEmpty(post.getPassword()) || needEncrypt)
|
||||
) {
|
||||
post.setStatus(PostStatus.INTIMATE);
|
||||
}
|
||||
|
||||
post = super.createOrUpdateBy(post);
|
||||
|
||||
postTagService.removeByPostId(post.getId());
|
||||
|
@ -810,7 +850,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
List<Tag> tags = tagService.listAllByIds(tagIds);
|
||||
|
||||
// List all categories
|
||||
List<Category> categories = categoryService.listAllByIds(categoryIds);
|
||||
List<Category> categories = categoryService.listAllByIds(categoryIds, true);
|
||||
|
||||
// Create post tags
|
||||
List<PostTag> postTags = postTagService.mergeOrCreateByIfAbsent(post.getId(),
|
||||
|
@ -830,10 +870,34 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
.createOrUpdateByPostId(post.getId(), metas);
|
||||
log.debug("Created post metas: [{}]", postMetaList);
|
||||
|
||||
// Remove authorization every time an post is created or updated.
|
||||
authorizationService.deletePostAuthorization(post.getId());
|
||||
|
||||
// Convert to post detail vo
|
||||
return convertTo(post, tags, categories, postMetaList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Post updateStatus(PostStatus status, Integer postId) {
|
||||
super.updateStatus(status, postId);
|
||||
if (PostStatus.PUBLISHED.equals(status)) {
|
||||
// When the update status is published, it is necessary to determine whether
|
||||
// the post status should be converted to a intimate post
|
||||
categoryService.refreshPostStatus(Collections.singletonList(postId));
|
||||
}
|
||||
return getById(postId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<Post> updateStatusByIds(List<Integer> ids, PostStatus status) {
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return ids.stream().map(id -> updateStatus(status, id)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishVisitEvent(Integer postId) {
|
||||
eventPublisher.publishEvent(new PostVisitEvent(this, postId));
|
||||
|
|
|
@ -152,9 +152,9 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<form method="post" action="${blog_url!}/archives/${slug!}/password">
|
||||
<form method="post" action="${blog_url!}/archives/${type!}/${slug!}/password">
|
||||
<div class="password-input">
|
||||
<input type="password" name="password" placeholder="请输入文章访问密码">
|
||||
<input type="password" name="password" placeholder="请输入访问密码">
|
||||
<span class="bottom"></span>
|
||||
<span class="right"></span>
|
||||
<span class="top"></span>
|
||||
|
|
Loading…
Reference in New Issue