refactor: transition behavior of post state after category updated (#1785)

* refactor: transition behavior of post state after category updated

* fix: post status

* refactor: category encryption

* refactor: modify unit test case

* fix: category encryption when post has a password
pull/1791/head^2
guqing 2022-03-30 00:58:40 +08:00 committed by GitHub
parent a7825f3f45
commit 2bccfb1abb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 20 deletions

View File

@ -1,6 +1,7 @@
package run.halo.app.event.category;
import org.springframework.context.ApplicationEvent;
import org.springframework.lang.Nullable;
import run.halo.app.model.entity.Category;
/**
@ -11,14 +12,29 @@ import run.halo.app.model.entity.Category;
*/
public class CategoryUpdatedEvent extends ApplicationEvent {
private final Category beforeUpdated;
private final Category category;
private final boolean beforeIsPrivate;
public CategoryUpdatedEvent(Object source, Category category) {
public CategoryUpdatedEvent(Object source, Category category,
Category beforeUpdated, boolean beforeIsPrivate) {
super(source);
this.category = category;
this.beforeUpdated = beforeUpdated;
this.beforeIsPrivate = beforeIsPrivate;
}
@Nullable
public Category getCategory() {
return category;
}
@Nullable
public Category getBeforeUpdated() {
return beforeUpdated;
}
public boolean isBeforeIsPrivate() {
return beforeIsPrivate;
}
}

View File

@ -1,7 +1,9 @@
package run.halo.app.listener.post;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
@ -48,20 +50,119 @@ public class PostRefreshStatusListener {
@EventListener(CategoryUpdatedEvent.class)
public void categoryUpdatedListener(CategoryUpdatedEvent event) {
Category category = event.getCategory();
if (!categoryService.existsById(category.getId())) {
Category beforeUpdated = event.getBeforeUpdated();
boolean beforeIsPrivate = event.isBeforeIsPrivate();
RecordState recordState = determineRecordState(beforeUpdated, category);
if (RecordState.DELETED.equals(recordState) || category == null) {
return;
}
boolean isPrivate = categoryService.isPrivate(category.getId());
// now
boolean isPrivate = categoryService.isPrivate(category.getId());
List<Post> posts = findPostsByCategoryIdRecursively(category.getId());
if (isPrivate) {
posts.forEach(post -> post.setStatus(PostStatus.INTIMATE));
posts.forEach(post -> {
if (post.getStatus() == PostStatus.PUBLISHED) {
post.setStatus(PostStatus.INTIMATE);
}
});
} else {
posts.forEach(post -> post.setStatus(PostStatus.DRAFT));
if (RecordState.UPDATED.equals(recordState)) {
Set<Integer> encryptedCategories =
pickUpEncryptedFromUpdatedRecord(category.getId());
for (Post post : posts) {
boolean belongsToEncryptedCategory =
postBelongsToEncryptedCategory(post.getId(), encryptedCategories);
if (!belongsToEncryptedCategory && StringUtils.isBlank(post.getPassword())
&& beforeIsPrivate
&& post.getStatus() == PostStatus.INTIMATE) {
post.setStatus(PostStatus.PUBLISHED);
}
}
}
}
postService.updateInBatch(posts);
}
private boolean postBelongsToEncryptedCategory(Integer postId,
Set<Integer> encryptedCategories) {
Set<Integer> categoryIds =
postCategoryService.listCategoryIdsByPostId(postId);
boolean encrypted = false;
for (Integer categoryId : categoryIds) {
if (encryptedCategories.contains(categoryId)) {
encrypted = true;
break;
}
}
return encrypted;
}
private Set<Integer> pickUpEncryptedFromUpdatedRecord(Integer categoryId) {
Set<Integer> privateCategories = new HashSet<>();
List<Category> categories = categoryService.listAllByParentId(categoryId);
Map<Integer, Category> categoryMap =
ServiceUtils.convertToMap(categories, Category::getId);
categories.forEach(category -> {
boolean privateBy = isPrivateBy(category.getId(), categoryMap);
if (privateBy) {
privateCategories.add(category.getId());
}
});
return privateCategories;
}
private boolean isPrivateBy(Integer categoryId, Map<Integer, Category> categoryMap) {
return findFirstEncryptedCategoryBy(categoryMap, categoryId) != null;
}
private Category findFirstEncryptedCategoryBy(Map<Integer, Category> idToCategoryMap,
Integer categoryId) {
Category category = idToCategoryMap.get(categoryId);
if (categoryId == 0 || category == null) {
return null;
}
if (StringUtils.isNotBlank(category.getPassword())) {
return category;
}
return findFirstEncryptedCategoryBy(idToCategoryMap, category.getParentId());
}
private RecordState determineRecordState(Category before, Category updated) {
if (before == null) {
if (updated != null) {
// created: null -> record
return RecordState.CREATED;
} else {
// unchanged: null -> null
return RecordState.UNCHANGED;
}
} else {
if (updated == null) {
// deleted: record -> null
return RecordState.DELETED;
} else {
// updated: record -> record
return RecordState.UPDATED;
}
}
}
/**
* effective state for database record.
*/
enum RecordState {
CREATED,
UPDATED,
DELETED,
UNCHANGED
}
@NonNull
private List<Post> findPostsByCategoryIdRecursively(Integer categoryId) {
Set<Integer> categoryIds =
@ -84,13 +185,23 @@ public class PostRefreshStatusListener {
if (!postService.existsById(post.getId())) {
return;
}
PostStatus status = post.getStatus();
boolean isPrivate = postCategoryService.listByPostId(post.getId())
.stream()
.anyMatch(postCategory -> categoryService.isPrivate(postCategory.getCategoryId()));
if (isPrivate || StringUtils.isNotBlank(post.getPassword())) {
post.setStatus(PostStatus.INTIMATE);
if (post.getStatus() != PostStatus.DRAFT) {
if (StringUtils.isNotEmpty(post.getPassword())) {
status = PostStatus.INTIMATE;
} else if (isPrivate) {
status = PostStatus.INTIMATE;
} else {
status = PostStatus.PUBLISHED;
}
} else if (!isPrivate && StringUtils.isBlank(post.getPassword())) {
status = PostStatus.DRAFT;
}
post.setStatus(status);
postService.update(post);
}
}
}

View File

@ -105,8 +105,14 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
@Override
public Category update(Category category) {
Category persisted = getById(category.getId());
Category beforeUpdated = new Category();
BeanUtils.updateProperties(persisted, beforeUpdated);
boolean beforeIsPrivate = isPrivate(category.getId());
Category updated = super.update(category);
applicationContext.publishEvent(new CategoryUpdatedEvent(this, category));
applicationContext.publishEvent(
new CategoryUpdatedEvent(this, category, beforeUpdated, beforeIsPrivate));
return updated;
}
@ -192,7 +198,7 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
// Remove post categories
postCategoryService.removeByCategoryId(categoryId);
applicationContext.publishEvent(new CategoryUpdatedEvent(this, category));
applicationContext.publishEvent(new CategoryUpdatedEvent(this, null, category, false));
}
@Override
@ -358,10 +364,15 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
return categoryRepository.findAllById(categoryIds)
.stream()
.map(categoryToUpdate -> {
// 将持久化状态的对象转非session管理对象否则数据会被更新
Category categoryBefore = BeanUtils.transformFrom(categoryToUpdate, Category.class);
boolean beforeIsPrivate = isPrivate(categoryToUpdate.getId());
Category categoryParam = idCategoryParamMap.get(categoryToUpdate.getId());
BeanUtils.updateProperties(categoryParam, categoryToUpdate);
Category categoryUpdated = update(categoryToUpdate);
applicationContext.publishEvent(new CategoryUpdatedEvent(this, categoryUpdated));
applicationContext.publishEvent(new CategoryUpdatedEvent(this,
categoryUpdated, categoryBefore, beforeIsPrivate));
return categoryUpdated;
})
.collect(Collectors.toList());

View File

@ -584,13 +584,6 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
Set<Integer> categoryIds, Set<PostMeta> metas) {
Assert.notNull(post, "Post param must not be null");
// if password is not empty
if (post.getStatus() != PostStatus.DRAFT
&& (StringUtils.isNotEmpty(post.getPassword()))
) {
post.setStatus(PostStatus.INTIMATE);
}
post = super.createOrUpdateBy(post);
postTagService.removeByPostId(post.getId());

View File

@ -72,7 +72,6 @@ public class PostRefreshStatusListenerTest {
PostDetailVO postDetailVO =
postService.createBy(post, Set.of(), Set.of(2), Set.of(), false);
assertThat(postDetailVO).isNotNull();
assertThat(postDetailVO.getStatus()).isEqualTo(PostStatus.INTIMATE);
category1.setPassword(null);
categoryService.update(category1);