Fix the problem of being able to search private posts after making post private (#3859)

#### What type of PR is this?

/kind bug
/area core
/milestone 2.5.x

#### What this PR does / why we need it:

This PR adds PostVisibleChangedEvent to synchronizing post indices when post visible is changed, whether from public to private or from private to public.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/3438

#### Special notes for your reviewer:

1. Install Search plugin
2. Create a post
3. Try to search the post
4. Make post private
5. Try to search the post
6. Make post public
7. Try to search the post

#### Does this PR introduce a user-facing change?

```release-note
修复隐藏的文章已然能够被搜索到问题
```
pull/3838/head^2
John Niang 2023-04-26 18:50:14 +08:00 committed by GitHub
parent 7b8613049a
commit 4cd6c2f67c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 43 deletions

View File

@ -10,7 +10,7 @@ import java.util.Set;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import run.halo.app.content.PostService; import run.halo.app.content.PostService;
@ -21,6 +21,7 @@ import run.halo.app.core.extension.content.Post;
import run.halo.app.core.extension.content.Snapshot; import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.event.post.PostPublishedEvent; import run.halo.app.event.post.PostPublishedEvent;
import run.halo.app.event.post.PostUnpublishedEvent; import run.halo.app.event.post.PostUnpublishedEvent;
import run.halo.app.event.post.PostVisibleChangedEvent;
import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ExtensionOperator; import run.halo.app.extension.ExtensionOperator;
import run.halo.app.extension.MetadataUtil; import run.halo.app.extension.MetadataUtil;
@ -57,7 +58,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
private final PostPermalinkPolicy postPermalinkPolicy; private final PostPermalinkPolicy postPermalinkPolicy;
private final CounterService counterService; private final CounterService counterService;
private final ApplicationContext applicationContext; private final ApplicationEventPublisher eventPublisher;
@Override @Override
public Result reconcile(Request request) { public Result reconcile(Request request) {
@ -93,7 +94,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
&& Objects.equals(false, post.getSpec().getPublish())) { && Objects.equals(false, post.getSpec().getPublish())) {
boolean success = unPublishReconcile(name); boolean success = unPublishReconcile(name);
if (success) { if (success) {
applicationContext.publishEvent(new PostUnpublishedEvent(this, name)); eventPublisher.publishEvent(new PostUnpublishedEvent(this, name));
} }
return; return;
} }
@ -163,7 +164,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
status.setLastModifyTime(releasedSnapshotOpt.get().getSpec().getLastModifyTime()); status.setLastModifyTime(releasedSnapshotOpt.get().getSpec().getLastModifyTime());
client.update(post); client.update(post);
applicationContext.publishEvent(new PostPublishedEvent(this, name)); eventPublisher.publishEvent(new PostPublishedEvent(this, name));
}); });
} }
@ -231,8 +232,11 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
} else { } else {
labels.put(Post.DELETED_LABEL, Boolean.FALSE.toString()); labels.put(Post.DELETED_LABEL, Boolean.FALSE.toString());
} }
fireVisibleChangedEventIfChanged(post);
labels.put(Post.VISIBLE_LABEL, labels.put(Post.VISIBLE_LABEL,
Objects.requireNonNullElse(spec.getVisible(), Post.VisibleEnum.PUBLIC).name()); Objects.requireNonNullElse(spec.getVisible(), Post.VisibleEnum.PUBLIC).name());
labels.put(Post.OWNER_LABEL, spec.getOwner()); labels.put(Post.OWNER_LABEL, spec.getOwner());
Instant publishTime = post.getSpec().getPublishTime(); Instant publishTime = post.getSpec().getPublishTime();
if (publishTime != null) { if (publishTime != null) {
@ -254,6 +258,23 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
}); });
} }
private void fireVisibleChangedEventIfChanged(Post post) {
var labels = post.getMetadata().getLabels();
if (labels == null) {
return;
}
var name = post.getMetadata().getName();
var oldVisibleStr = labels.get(Post.VISIBLE_LABEL);
if (oldVisibleStr != null) {
var oldVisible = Post.VisibleEnum.valueOf(oldVisibleStr);
var expectVisible = post.getSpec().getVisible();
if (!Objects.equals(oldVisible, expectVisible)) {
eventPublisher.publishEvent(
new PostVisibleChangedEvent(name, oldVisible, expectVisible));
}
}
}
private void reconcileStatus(String name) { private void reconcileStatus(String name) {
client.fetch(Post.class, name).ifPresent(post -> { client.fetch(Post.class, name).ifPresent(post -> {
final Post oldPost = JsonUtils.deepCopy(post); final Post oldPost = JsonUtils.deepCopy(post);

View File

@ -0,0 +1,27 @@
package run.halo.app.event.post;
import lombok.Data;
import run.halo.app.core.extension.content.Post;
@Data
public class PostVisibleChangedEvent implements PostEvent {
private final String postName;
private final Post.VisibleEnum oldVisible;
private final Post.VisibleEnum newVisible;
public PostVisibleChangedEvent(String postName, Post.VisibleEnum oldVisible,
Post.VisibleEnum newVisible) {
this.postName = postName;
this.oldVisible = oldVisible;
this.newVisible = newVisible;
}
@Override
public String getName() {
return postName;
}
}

View File

@ -1,10 +1,11 @@
package run.halo.app.search.post; package run.halo.app.search.post;
import static run.halo.app.core.extension.content.Post.VisibleEnum.PUBLIC;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.SmartLifecycle; import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
@ -14,6 +15,7 @@ import run.halo.app.event.post.PostEvent;
import run.halo.app.event.post.PostPublishedEvent; import run.halo.app.event.post.PostPublishedEvent;
import run.halo.app.event.post.PostRecycledEvent; import run.halo.app.event.post.PostRecycledEvent;
import run.halo.app.event.post.PostUnpublishedEvent; import run.halo.app.event.post.PostUnpublishedEvent;
import run.halo.app.event.post.PostVisibleChangedEvent;
import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController; import run.halo.app.extension.controller.DefaultController;
@ -51,21 +53,20 @@ public class PostEventReconciler implements Reconciler<PostEvent>, SmartLifecycl
@Override @Override
public Result reconcile(PostEvent postEvent) { public Result reconcile(PostEvent postEvent) {
if (postEvent instanceof PostPublishedEvent) { if (postEvent instanceof PostPublishedEvent) {
try {
addPostDoc(postEvent.getName()); addPostDoc(postEvent.getName());
} catch (InterruptedException e) {
throw Exceptions.propagate(e);
}
} }
if (postEvent instanceof PostUnpublishedEvent if (postEvent instanceof PostUnpublishedEvent
|| postEvent instanceof PostRecycledEvent) { || postEvent instanceof PostRecycledEvent) {
try {
deletePostDoc(postEvent.getName()); deletePostDoc(postEvent.getName());
} catch (InterruptedException e) { }
throw Exceptions.propagate(e); if (postEvent instanceof PostVisibleChangedEvent visibleChangedEvent) {
if (PUBLIC.equals(visibleChangedEvent.getOldVisible())) {
deletePostDoc(postEvent.getName());
} else if (PUBLIC.equals(visibleChangedEvent.getNewVisible())) {
addPostDoc(postEvent.getName());
} }
} }
return null; return Result.doNotRetry();
} }
@Override @Override
@ -95,9 +96,14 @@ public class PostEventReconciler implements Reconciler<PostEvent>, SmartLifecycl
postEventQueue.addImmediately(recycledEvent); postEventQueue.addImmediately(recycledEvent);
} }
void addPostDoc(String postName) throws InterruptedException { @EventListener(PostVisibleChangedEvent.class)
var latch = new CountDownLatch(1); public void handlePostVisibleChanged(PostVisibleChangedEvent event) {
var disposable = postFinder.getByName(postName) postEventQueue.addImmediately(event);
}
void addPostDoc(String postName) {
postFinder.getByName(postName)
.filter(postVo -> PUBLIC.equals(postVo.getSpec().getVisible()))
.map(PostDocUtils::from) .map(PostDocUtils::from)
.flatMap(postDoc -> extensionGetter.getEnabledExtension(PostSearchService.class) .flatMap(postDoc -> extensionGetter.getEnabledExtension(PostSearchService.class)
.doOnNext(searchService -> { .doOnNext(searchService -> {
@ -108,21 +114,12 @@ public class PostEventReconciler implements Reconciler<PostEvent>, SmartLifecycl
} }
}) })
) )
.doFinally(signalType -> latch.countDown()) .then()
.subscribe(service -> { .block();
}, throwable -> {
throw Exceptions.propagate(throwable);
});
try {
latch.await();
} finally {
disposable.dispose();
}
} }
void deletePostDoc(String postName) throws InterruptedException { void deletePostDoc(String postName) {
var latch = new CountDownLatch(1); extensionGetter.getEnabledExtension(PostSearchService.class)
var disposable = extensionGetter.getEnabledExtension(PostSearchService.class)
.doOnNext(searchService -> { .doOnNext(searchService -> {
try { try {
searchService.removeDocuments(Set.of(postName)); searchService.removeDocuments(Set.of(postName));
@ -130,16 +127,8 @@ public class PostEventReconciler implements Reconciler<PostEvent>, SmartLifecycl
throw Exceptions.propagate(e); throw Exceptions.propagate(e);
} }
}) })
.doFinally(signalType -> latch.countDown()) .then()
.subscribe(service -> { .block();
}, throwable -> {
throw Exceptions.propagate(throwable);
});
try {
latch.await();
} finally {
disposable.dispose();
}
} }
@Override @Override

View File

@ -19,7 +19,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.content.ContentWrapper; import run.halo.app.content.ContentWrapper;
import run.halo.app.content.PostService; import run.halo.app.content.PostService;
@ -50,7 +50,7 @@ class PostReconcilerTest {
private PostService postService; private PostService postService;
@Mock @Mock
private ApplicationContext applicationContext; private ApplicationEventPublisher eventPublisher;
@InjectMocks @InjectMocks
private PostReconciler postReconciler; private PostReconciler postReconciler;
@ -157,7 +157,7 @@ class PostReconcilerTest {
verify(client, times(4)).update(captor.capture()); verify(client, times(4)).update(captor.capture());
Post value = captor.getValue(); Post value = captor.getValue();
assertThat(value.getStatus().getLastModifyTime()).isEqualTo(lastModifyTime); assertThat(value.getStatus().getLastModifyTime()).isEqualTo(lastModifyTime);
verify(applicationContext).publishEvent(any(PostPublishedEvent.class)); verify(eventPublisher).publishEvent(any(PostPublishedEvent.class));
} }
@Test @Test