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; package run.halo.app.event.category;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
import org.springframework.lang.Nullable;
import run.halo.app.model.entity.Category; import run.halo.app.model.entity.Category;
/** /**
@ -11,14 +12,29 @@ import run.halo.app.model.entity.Category;
*/ */
public class CategoryUpdatedEvent extends ApplicationEvent { public class CategoryUpdatedEvent extends ApplicationEvent {
private final Category beforeUpdated;
private final Category category; 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); super(source);
this.category = category; this.category = category;
this.beforeUpdated = beforeUpdated;
this.beforeIsPrivate = beforeIsPrivate;
} }
@Nullable
public Category getCategory() { public Category getCategory() {
return category; 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; package run.halo.app.listener.post;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
@ -48,20 +50,119 @@ public class PostRefreshStatusListener {
@EventListener(CategoryUpdatedEvent.class) @EventListener(CategoryUpdatedEvent.class)
public void categoryUpdatedListener(CategoryUpdatedEvent event) { public void categoryUpdatedListener(CategoryUpdatedEvent event) {
Category category = event.getCategory(); 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; return;
} }
boolean isPrivate = categoryService.isPrivate(category.getId());
// now
boolean isPrivate = categoryService.isPrivate(category.getId());
List<Post> posts = findPostsByCategoryIdRecursively(category.getId()); List<Post> posts = findPostsByCategoryIdRecursively(category.getId());
if (isPrivate) { if (isPrivate) {
posts.forEach(post -> post.setStatus(PostStatus.INTIMATE)); posts.forEach(post -> {
if (post.getStatus() == PostStatus.PUBLISHED) {
post.setStatus(PostStatus.INTIMATE);
}
});
} else { } 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); 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 @NonNull
private List<Post> findPostsByCategoryIdRecursively(Integer categoryId) { private List<Post> findPostsByCategoryIdRecursively(Integer categoryId) {
Set<Integer> categoryIds = Set<Integer> categoryIds =
@ -84,13 +185,23 @@ public class PostRefreshStatusListener {
if (!postService.existsById(post.getId())) { if (!postService.existsById(post.getId())) {
return; return;
} }
PostStatus status = post.getStatus();
boolean isPrivate = postCategoryService.listByPostId(post.getId()) boolean isPrivate = postCategoryService.listByPostId(post.getId())
.stream() .stream()
.anyMatch(postCategory -> categoryService.isPrivate(postCategory.getCategoryId())); .anyMatch(postCategory -> categoryService.isPrivate(postCategory.getCategoryId()));
if (post.getStatus() != PostStatus.DRAFT) {
if (isPrivate || StringUtils.isNotBlank(post.getPassword())) { if (StringUtils.isNotEmpty(post.getPassword())) {
post.setStatus(PostStatus.INTIMATE); status = PostStatus.INTIMATE;
postService.update(post); } 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 @Override
public Category update(Category category) { 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); Category updated = super.update(category);
applicationContext.publishEvent(new CategoryUpdatedEvent(this, category)); applicationContext.publishEvent(
new CategoryUpdatedEvent(this, category, beforeUpdated, beforeIsPrivate));
return updated; return updated;
} }
@ -192,7 +198,7 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
// Remove post categories // Remove post categories
postCategoryService.removeByCategoryId(categoryId); postCategoryService.removeByCategoryId(categoryId);
applicationContext.publishEvent(new CategoryUpdatedEvent(this, category)); applicationContext.publishEvent(new CategoryUpdatedEvent(this, null, category, false));
} }
@Override @Override
@ -358,10 +364,15 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
return categoryRepository.findAllById(categoryIds) return categoryRepository.findAllById(categoryIds)
.stream() .stream()
.map(categoryToUpdate -> { .map(categoryToUpdate -> {
// 将持久化状态的对象转非session管理对象否则数据会被更新
Category categoryBefore = BeanUtils.transformFrom(categoryToUpdate, Category.class);
boolean beforeIsPrivate = isPrivate(categoryToUpdate.getId());
Category categoryParam = idCategoryParamMap.get(categoryToUpdate.getId()); Category categoryParam = idCategoryParamMap.get(categoryToUpdate.getId());
BeanUtils.updateProperties(categoryParam, categoryToUpdate); BeanUtils.updateProperties(categoryParam, categoryToUpdate);
Category categoryUpdated = update(categoryToUpdate); Category categoryUpdated = update(categoryToUpdate);
applicationContext.publishEvent(new CategoryUpdatedEvent(this, categoryUpdated)); applicationContext.publishEvent(new CategoryUpdatedEvent(this,
categoryUpdated, categoryBefore, beforeIsPrivate));
return categoryUpdated; return categoryUpdated;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());

View File

@ -584,13 +584,6 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
Set<Integer> categoryIds, Set<PostMeta> metas) { Set<Integer> categoryIds, Set<PostMeta> metas) {
Assert.notNull(post, "Post param must not be null"); 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); post = super.createOrUpdateBy(post);
postTagService.removeByPostId(post.getId()); postTagService.removeByPostId(post.getId());

View File

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