mirror of https://github.com/halo-dev/halo
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 passwordpull/1791/head^2
parent
a7825f3f45
commit
2bccfb1abb
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue