mirror of https://github.com/halo-dev/halo
fix: incorrect unsubscribe link for email notification (#4695)
#### What type of PR is this? /kind bug /area core /milestone 2.10.x #### What this PR does / why we need it: 修复邮件通知中取消订阅链接不正确的问题 #### Does this PR introduce a user-facing change? ```release-note 修复邮件通知中取消订阅链接不正确的问题 ```pull/4703/head
parent
d97c1e6e3d
commit
1ff1b4f2a5
|
@ -5,7 +5,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
@ -135,6 +134,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
.subscription(subscription)
|
.subscription(subscription)
|
||||||
.reasonType(notificationContent.reasonType())
|
.reasonType(notificationContent.reasonType())
|
||||||
.notificationTitle(notificationContent.title())
|
.notificationTitle(notificationContent.title())
|
||||||
|
.reasonAttributes(notificationContent.reasonAttributes())
|
||||||
.notificationRawBody(defaultString(notificationContent.rawBody()))
|
.notificationRawBody(defaultString(notificationContent.rawBody()))
|
||||||
.notificationHtmlBody(defaultString(notificationContent.htmlBody()))
|
.notificationHtmlBody(defaultString(notificationContent.htmlBody()))
|
||||||
.build()
|
.build()
|
||||||
|
@ -180,12 +180,13 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Builder
|
private ReasonAttributes toReasonAttributes(Reason reason) {
|
||||||
record NotificationElement(ReasonType reasonType, Reason reason,
|
var model = new ReasonAttributes();
|
||||||
Subscription subscription, NotifierDescriptor descriptor,
|
var attributes = reason.getSpec().getAttributes();
|
||||||
String notificationTitle,
|
if (attributes != null) {
|
||||||
String notificationRawBody,
|
model.putAll(attributes);
|
||||||
String notificationHtmlBody) {
|
}
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mono<NotificationContext> notificationContextFrom(NotificationElement element) {
|
Mono<NotificationContext> notificationContextFrom(NotificationElement element) {
|
||||||
|
@ -198,7 +199,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
messagePayload.setTitle(element.notificationTitle());
|
messagePayload.setTitle(element.notificationTitle());
|
||||||
messagePayload.setRawBody(element.notificationRawBody());
|
messagePayload.setRawBody(element.notificationRawBody());
|
||||||
messagePayload.setHtmlBody(element.notificationHtmlBody());
|
messagePayload.setHtmlBody(element.notificationHtmlBody());
|
||||||
messagePayload.setAttributes(reason.getSpec().getAttributes());
|
messagePayload.setAttributes(element.reasonAttributes());
|
||||||
|
|
||||||
var message = new NotificationContext.Message();
|
var message = new NotificationContext.Message();
|
||||||
message.setRecipient(subscription.getSpec().getSubscriber().getName());
|
message.setRecipient(subscription.getSpec().getSubscriber().getName());
|
||||||
|
@ -239,11 +240,6 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Builder
|
|
||||||
record NotificationContent(String title, String rawBody, String htmlBody, ReasonType reasonType,
|
|
||||||
Map<String, Object> reasonProperties) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Mono<NotificationContent> inferenceTemplate(Reason reason, Subscription subscription,
|
Mono<NotificationContent> inferenceTemplate(Reason reason, Subscription subscription,
|
||||||
Locale locale) {
|
Locale locale) {
|
||||||
var reasonTypeName = reason.getSpec().getReasonType();
|
var reasonTypeName = reason.getSpec().getReasonType();
|
||||||
|
@ -252,7 +248,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
.flatMap(reasonType -> notificationTemplateSelector.select(reasonTypeName, locale)
|
.flatMap(reasonType -> notificationTemplateSelector.select(reasonTypeName, locale)
|
||||||
.flatMap(template -> {
|
.flatMap(template -> {
|
||||||
final var templateContent = template.getSpec().getTemplate();
|
final var templateContent = template.getSpec().getTemplate();
|
||||||
Map<String, Object> model = toReasonAttributes(reason);
|
var model = toReasonAttributes(reason);
|
||||||
var identity = UserIdentity.of(subscriber.getName());
|
var identity = UserIdentity.of(subscriber.getName());
|
||||||
var subscriberInfo = new HashMap<>();
|
var subscriberInfo = new HashMap<>();
|
||||||
if (identity.isAnonymous()) {
|
if (identity.isAnonymous()) {
|
||||||
|
@ -266,7 +262,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
|
|
||||||
var builder = NotificationContent.builder()
|
var builder = NotificationContent.builder()
|
||||||
.reasonType(reasonType)
|
.reasonType(reasonType)
|
||||||
.reasonProperties(model);
|
.reasonAttributes(model);
|
||||||
|
|
||||||
var titleMono = notificationTemplateRender
|
var titleMono = notificationTemplateRender
|
||||||
.render(templateContent.getTitle(), model)
|
.render(templateContent.getTitle(), model)
|
||||||
|
@ -285,17 +281,22 @@ public class DefaultNotificationCenter implements NotificationCenter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
record NotificationContent(String title, String rawBody, String htmlBody, ReasonType reasonType,
|
||||||
|
ReasonAttributes reasonAttributes) {
|
||||||
|
}
|
||||||
|
|
||||||
String getUnsubscribeUrl(Subscription subscription) {
|
String getUnsubscribeUrl(Subscription subscription) {
|
||||||
return subscriptionRouter.getUnsubscribeUrl(subscription);
|
return subscriptionRouter.getUnsubscribeUrl(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> toReasonAttributes(Reason reason) {
|
@Builder
|
||||||
Map<String, Object> model = new HashMap<>();
|
record NotificationElement(ReasonType reasonType, Reason reason,
|
||||||
var attributes = reason.getSpec().getAttributes();
|
Subscription subscription, NotifierDescriptor descriptor,
|
||||||
if (attributes != null) {
|
String notificationTitle,
|
||||||
model.putAll(attributes);
|
String notificationRawBody,
|
||||||
}
|
String notificationHtmlBody,
|
||||||
return model;
|
ReasonAttributes reasonAttributes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Mono<ReasonType> getReasonType(String reasonTypeName) {
|
Mono<ReasonType> getReasonType(String reasonTypeName) {
|
||||||
|
|
|
@ -145,12 +145,11 @@ public class EmailNotifier implements ReactiveNotifier {
|
||||||
|
|
||||||
Mono<String> appendHtmlBodyFooter(ReasonAttributes attributes) {
|
Mono<String> appendHtmlBodyFooter(ReasonAttributes attributes) {
|
||||||
return notificationTemplateRender.render("""
|
return notificationTemplateRender.render("""
|
||||||
---
|
<div class="footer" style="font-size: 12px; color: #666;">
|
||||||
<div class="footer" style="font-size: 12px; color: #666">
|
|
||||||
<a th:href="${site.url}" th:text="${site.title}"></a>
|
<a th:href="${site.url}" th:text="${site.title}"></a>
|
||||||
<p class="unsubscribe">
|
<p class="unsubscribe">
|
||||||
—<br />请勿直接回复此回邮件,
|
—<br />请勿直接回复此回邮件,
|
||||||
<a th:href="|${site.url}/console|">查看通知</a>
|
<a th:href="|${site.url}/console/users/-/notifications|">查看通知</a>
|
||||||
或
|
或
|
||||||
<a th:href="${unsubscribeUrl}">取消订阅</a>。
|
<a th:href="${unsubscribeUrl}">取消订阅</a>。
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package run.halo.app.notification.endpoint;
|
package run.halo.app.notification.endpoint;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
|
import java.net.URI;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
|
@ -82,7 +84,8 @@ public class SubscriptionRouter {
|
||||||
public String getUnsubscribeUrl(Subscription subscription) {
|
public String getUnsubscribeUrl(Subscription subscription) {
|
||||||
var name = subscription.getMetadata().getName();
|
var name = subscription.getMetadata().getName();
|
||||||
var token = subscription.getSpec().getUnsubscribeToken();
|
var token = subscription.getSpec().getUnsubscribeToken();
|
||||||
return UriComponentsBuilder.fromUri(externalUrlSupplier.get())
|
var externalUrl = defaultIfNull(externalUrlSupplier.getRaw(), URI.create("/"));
|
||||||
|
return UriComponentsBuilder.fromUriString(externalUrl.toString())
|
||||||
.path(UNSUBSCRIBE_PATTERN)
|
.path(UNSUBSCRIBE_PATTERN)
|
||||||
.queryParam("token", token)
|
.queryParam("token", token)
|
||||||
.build(name)
|
.build(name)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.notification.endpoint;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -11,7 +12,6 @@ import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import run.halo.app.core.extension.notification.Subscription;
|
import run.halo.app.core.extension.notification.Subscription;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
|
||||||
import run.halo.app.infra.ExternalUrlSupplier;
|
import run.halo.app.infra.ExternalUrlSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,9 +23,6 @@ import run.halo.app.infra.ExternalUrlSupplier;
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class SubscriptionRouterTest {
|
class SubscriptionRouterTest {
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ReactiveExtensionClient client;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ExternalUrlSupplier externalUrlSupplier;
|
private ExternalUrlSupplier externalUrlSupplier;
|
||||||
|
|
||||||
|
@ -33,8 +30,8 @@ class SubscriptionRouterTest {
|
||||||
SubscriptionRouter subscriptionRouter;
|
SubscriptionRouter subscriptionRouter;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getUnsubscribeUrlTest() {
|
void getUnsubscribeUrlTest() throws MalformedURLException {
|
||||||
when(externalUrlSupplier.get()).thenReturn(URI.create("https://halo.run"));
|
when(externalUrlSupplier.getRaw()).thenReturn(URI.create("https://halo.run").toURL());
|
||||||
var subscription = new Subscription();
|
var subscription = new Subscription();
|
||||||
subscription.setMetadata(new Metadata());
|
subscription.setMetadata(new Metadata());
|
||||||
subscription.getMetadata().setName("fake-subscription");
|
subscription.getMetadata().setName("fake-subscription");
|
||||||
|
@ -46,5 +43,4 @@ class SubscriptionRouterTest {
|
||||||
+ "/subscriptions/fake-subscription/unsubscribe"
|
+ "/subscriptions/fake-subscription/unsubscribe"
|
||||||
+ "?token=fake-unsubscribe-token");
|
+ "?token=fake-unsubscribe-token");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue