Skip to content

Commit

Permalink
refactor: transition behavior of post state after category updated (h…
Browse files Browse the repository at this point in the history
…alo-dev#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
  • Loading branch information
guqing authored Mar 29, 2022
1 parent a7825f3 commit 2bccfb1
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -11,14 +12,29 @@
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -48,20 +50,119 @@ public PostRefreshStatusListener(PostService postService,
@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 =
Expand All @@ -84,13 +185,23 @@ public void postUpdatedListener(PostUpdatedEvent event) {
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);
postService.update(post);
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);
}
}
17 changes: 14 additions & 3 deletions src/main/java/run/halo/app/service/impl/CategoryServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,14 @@ public Category create(Category category) {

@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;
}

Expand Down Expand Up @@ -192,7 +198,7 @@ public void removeCategoryAndPostCategoryBy(Integer categoryId) {
// Remove post categories
postCategoryService.removeByCategoryId(categoryId);

applicationContext.publishEvent(new CategoryUpdatedEvent(this, category));
applicationContext.publishEvent(new CategoryUpdatedEvent(this, null, category, false));
}

@Override
Expand Down Expand Up @@ -358,10 +364,15 @@ public List<Category> updateInBatch(Collection<Category> categories) {
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());
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/run/halo/app/service/impl/PostServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -584,13 +584,6 @@ private PostDetailVO createOrUpdate(@NonNull Post post, Set<Integer> tagIds,
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public void clearCategoryPasswordOfTopLevelTest() {
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);
Expand Down

0 comments on commit 2bccfb1

Please sign in to comment.