Skip to content

Commit 37b9552

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 5c7f6e4 + a281015 commit 37b9552

File tree

24 files changed

+783
-194
lines changed

24 files changed

+783
-194
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
package run.halo.app.notification;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4-
import java.nio.charset.StandardCharsets;
5-
import java.util.Properties;
64
import java.util.concurrent.atomic.AtomicReference;
7-
import lombok.Data;
85
import lombok.NonNull;
96
import lombok.RequiredArgsConstructor;
107
import lombok.extern.slf4j.Slf4j;
118
import org.apache.commons.lang3.StringUtils;
129
import org.springframework.data.util.Pair;
10+
import org.springframework.mail.javamail.JavaMailSender;
1311
import org.springframework.mail.javamail.JavaMailSenderImpl;
14-
import org.springframework.mail.javamail.MimeMessageHelper;
1512
import org.springframework.mail.javamail.MimeMessagePreparator;
1613
import org.springframework.stereotype.Component;
1714
import reactor.core.publisher.Mono;
1815
import reactor.core.scheduler.Schedulers;
1916
import run.halo.app.core.extension.notification.Subscription;
2017
import run.halo.app.infra.utils.JsonUtils;
18+
import run.halo.app.notification.EmailSenderHelper.EmailSenderConfig;
2119

2220
/**
2321
* <p>A notifier that can send email.</p>
@@ -34,7 +32,8 @@ public class EmailNotifier implements ReactiveNotifier {
3432

3533
private final SubscriberEmailResolver subscriberEmailResolver;
3634
private final NotificationTemplateRender notificationTemplateRender;
37-
private final AtomicReference<Pair<EmailSenderConfig, JavaMailSenderImpl>>
35+
private final EmailSenderHelper emailSenderHelper;
36+
private final AtomicReference<Pair<EmailSenderConfig, JavaMailSender>>
3837
emailSenderConfigPairRef = new AtomicReference<>();
3938

4039
@Override
@@ -48,7 +47,7 @@ public Mono<Void> notify(NotificationContext context) {
4847
return Mono.empty();
4948
}
5049

51-
JavaMailSenderImpl javaMailSender = getJavaMailSender(emailSenderConfig);
50+
JavaMailSender javaMailSender = getJavaMailSender(emailSenderConfig);
5251

5352
String recipient = context.getMessage().getRecipient();
5453
var subscriber = new Subscription.Subscriber();
@@ -83,55 +82,20 @@ public Mono<Void> notify(NotificationContext context) {
8382
}
8483

8584
@NonNull
86-
private static MimeMessagePreparator getMimeMessagePreparator(String toEmail,
85+
private MimeMessagePreparator getMimeMessagePreparator(String toEmail,
8786
EmailSenderConfig emailSenderConfig, NotificationContext.MessagePayload payload) {
88-
return mimeMessage -> {
89-
MimeMessageHelper helper =
90-
new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
91-
helper.setFrom(emailSenderConfig.getUsername(), emailSenderConfig.getDisplayName());
92-
93-
helper.setSubject(payload.getTitle());
94-
helper.setText(payload.getRawBody(), payload.getHtmlBody());
95-
helper.setTo(toEmail);
96-
};
97-
}
98-
99-
@NonNull
100-
private static JavaMailSenderImpl createJavaMailSender(EmailSenderConfig emailSenderConfig) {
101-
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
102-
javaMailSender.setHost(emailSenderConfig.getHost());
103-
javaMailSender.setPort(emailSenderConfig.getPort());
104-
javaMailSender.setUsername(emailSenderConfig.getUsername());
105-
javaMailSender.setPassword(emailSenderConfig.getPassword());
106-
107-
Properties props = javaMailSender.getJavaMailProperties();
108-
props.put("mail.transport.protocol", "smtp");
109-
props.put("mail.smtp.auth", "true");
110-
if ("SSL".equals(emailSenderConfig.getEncryption())) {
111-
props.put("mail.smtp.ssl.enable", "true");
112-
}
113-
114-
if ("TLS".equals(emailSenderConfig.getEncryption())) {
115-
props.put("mail.smtp.starttls.enable", "true");
116-
}
117-
118-
if ("NONE".equals(emailSenderConfig.getEncryption())) {
119-
props.put("mail.smtp.ssl.enable", "false");
120-
props.put("mail.smtp.starttls.enable", "false");
121-
}
122-
123-
if (log.isDebugEnabled()) {
124-
props.put("mail.debug", "true");
125-
}
126-
return javaMailSender;
87+
return emailSenderHelper.createMimeMessagePreparator(emailSenderConfig, toEmail,
88+
payload.getTitle(),
89+
payload.getRawBody(), payload.getHtmlBody());
12790
}
12891

129-
JavaMailSenderImpl getJavaMailSender(EmailSenderConfig emailSenderConfig) {
92+
JavaMailSender getJavaMailSender(EmailSenderConfig emailSenderConfig) {
13093
return emailSenderConfigPairRef.updateAndGet(pair -> {
13194
if (pair != null && pair.getFirst().equals(emailSenderConfig)) {
13295
return pair;
13396
}
134-
return Pair.of(emailSenderConfig, createJavaMailSender(emailSenderConfig));
97+
return Pair.of(emailSenderConfig,
98+
emailSenderHelper.createJavaMailSender(emailSenderConfig));
13599
}).getSecond();
136100
}
137101

@@ -156,24 +120,4 @@ Mono<String> appendHtmlBodyFooter(ReasonAttributes attributes) {
156120
</div>
157121
""", attributes);
158122
}
159-
160-
@Data
161-
static class EmailSenderConfig {
162-
private boolean enable;
163-
private String displayName;
164-
private String username;
165-
private String password;
166-
private String host;
167-
private Integer port;
168-
private String encryption;
169-
170-
/**
171-
* Gets email display name.
172-
*
173-
* @return display name if not blank, otherwise username.
174-
*/
175-
public String getDisplayName() {
176-
return StringUtils.defaultIfBlank(displayName, username);
177-
}
178-
}
179123
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package run.halo.app.notification;
2+
3+
import lombok.Data;
4+
import lombok.NonNull;
5+
import org.apache.commons.lang3.StringUtils;
6+
import org.springframework.mail.javamail.JavaMailSender;
7+
import org.springframework.mail.javamail.MimeMessagePreparator;
8+
9+
public interface EmailSenderHelper {
10+
11+
@NonNull
12+
JavaMailSender createJavaMailSender(EmailSenderConfig senderConfig);
13+
14+
@NonNull
15+
MimeMessagePreparator createMimeMessagePreparator(EmailSenderConfig senderConfig,
16+
String toEmail, String subject, String raw, String html);
17+
18+
@Data
19+
class EmailSenderConfig {
20+
private boolean enable;
21+
private String displayName;
22+
private String username;
23+
private String sender;
24+
private String password;
25+
private String host;
26+
private Integer port;
27+
private String encryption;
28+
29+
/**
30+
* Gets email display name.
31+
*
32+
* @return display name if not blank, otherwise username.
33+
*/
34+
public String getDisplayName() {
35+
return StringUtils.defaultIfBlank(displayName, username);
36+
}
37+
38+
/**
39+
* Gets email sender address.
40+
*
41+
* @return sender if not blank, otherwise username
42+
*/
43+
public String getSender() {
44+
return StringUtils.defaultIfBlank(sender, username);
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package run.halo.app.notification;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.util.Properties;
5+
import lombok.NonNull;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.mail.javamail.JavaMailSender;
8+
import org.springframework.mail.javamail.JavaMailSenderImpl;
9+
import org.springframework.mail.javamail.MimeMessageHelper;
10+
import org.springframework.mail.javamail.MimeMessagePreparator;
11+
import org.springframework.stereotype.Component;
12+
13+
/**
14+
* <p>A default implementation of {@link EmailSenderHelper}.</p>
15+
*
16+
* @author guqing
17+
* @since 2.14.0
18+
*/
19+
@Slf4j
20+
@Component
21+
public class EmailSenderHelperImpl implements EmailSenderHelper {
22+
23+
@Override
24+
@NonNull
25+
public JavaMailSender createJavaMailSender(EmailSenderConfig senderConfig) {
26+
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
27+
javaMailSender.setHost(senderConfig.getHost());
28+
javaMailSender.setPort(senderConfig.getPort());
29+
javaMailSender.setUsername(senderConfig.getUsername());
30+
javaMailSender.setPassword(senderConfig.getPassword());
31+
32+
Properties props = javaMailSender.getJavaMailProperties();
33+
props.put("mail.transport.protocol", "smtp");
34+
props.put("mail.smtp.auth", "true");
35+
if ("SSL".equals(senderConfig.getEncryption())) {
36+
props.put("mail.smtp.ssl.enable", "true");
37+
}
38+
39+
if ("TLS".equals(senderConfig.getEncryption())) {
40+
props.put("mail.smtp.starttls.enable", "true");
41+
}
42+
43+
if ("NONE".equals(senderConfig.getEncryption())) {
44+
props.put("mail.smtp.ssl.enable", "false");
45+
props.put("mail.smtp.starttls.enable", "false");
46+
}
47+
48+
if (log.isDebugEnabled()) {
49+
props.put("mail.debug", "true");
50+
}
51+
return javaMailSender;
52+
}
53+
54+
@Override
55+
@NonNull
56+
public MimeMessagePreparator createMimeMessagePreparator(EmailSenderConfig senderConfig,
57+
String toEmail, String subject, String raw, String html) {
58+
return mimeMessage -> {
59+
MimeMessageHelper helper =
60+
new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
61+
helper.setFrom(senderConfig.getSender(), senderConfig.getDisplayName());
62+
63+
helper.setSubject(subject);
64+
helper.setText(raw, html);
65+
helper.setTo(toEmail);
66+
};
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package run.halo.app.notification.endpoint;
2+
3+
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
4+
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
5+
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import java.security.Principal;
8+
import lombok.Data;
9+
import lombok.EqualsAndHashCode;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.apache.commons.lang3.StringUtils;
13+
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
14+
import org.springframework.mail.MailException;
15+
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
16+
import org.springframework.security.core.context.SecurityContext;
17+
import org.springframework.stereotype.Component;
18+
import org.springframework.web.reactive.function.server.RouterFunction;
19+
import org.springframework.web.reactive.function.server.ServerRequest;
20+
import org.springframework.web.reactive.function.server.ServerResponse;
21+
import org.springframework.web.server.ServerWebInputException;
22+
import reactor.core.publisher.Mono;
23+
import run.halo.app.core.extension.User;
24+
import run.halo.app.core.extension.endpoint.CustomEndpoint;
25+
import run.halo.app.extension.GroupVersion;
26+
import run.halo.app.extension.ReactiveExtensionClient;
27+
import run.halo.app.notification.EmailSenderHelper;
28+
29+
/**
30+
* Validation endpoint for email config.
31+
*
32+
* @author guqing
33+
* @since 2.14.0
34+
*/
35+
@Slf4j
36+
@Component
37+
@RequiredArgsConstructor
38+
public class EmailConfigValidationEndpoint implements CustomEndpoint {
39+
private static final String EMAIL_SUBJECT = "测试邮件 - 验证邮箱连通性";
40+
private static final String EMAIL_BODY = """
41+
你好!<br/>
42+
这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。<br/>
43+
此邮件由系统自动发送,请勿回复。<br/>
44+
祝好
45+
""";
46+
47+
private final EmailSenderHelper emailSenderHelper;
48+
private final ReactiveExtensionClient client;
49+
50+
@Override
51+
public RouterFunction<ServerResponse> endpoint() {
52+
var tag = "console.api.notification.halo.run/v1alpha1/Notifier";
53+
return SpringdocRouteBuilder.route()
54+
.POST("/notifiers/default-email-notifier/verify-connection",
55+
this::verifyEmailSenderConfig,
56+
builder -> builder.operationId("VerifyEmailSenderConfig")
57+
.description("Verify email sender config.")
58+
.tag(tag)
59+
.requestBody(requestBodyBuilder()
60+
.required(true)
61+
.implementation(ValidationRequest.class)
62+
)
63+
.response(responseBuilder().implementation(Void.class))
64+
)
65+
.build();
66+
}
67+
68+
private Mono<ServerResponse> verifyEmailSenderConfig(ServerRequest request) {
69+
return request.bodyToMono(ValidationRequest.class)
70+
.switchIfEmpty(
71+
Mono.error(new ServerWebInputException("Required request body is missing."))
72+
)
73+
.flatMap(validationRequest -> getCurrentUserEmail()
74+
.flatMap(recipient -> {
75+
var mailSender = emailSenderHelper.createJavaMailSender(validationRequest);
76+
var message = emailSenderHelper.createMimeMessagePreparator(validationRequest,
77+
recipient, EMAIL_SUBJECT, EMAIL_BODY, EMAIL_BODY);
78+
try {
79+
mailSender.send(message);
80+
} catch (MailException e) {
81+
String errorMsg =
82+
"Failed to send email, please check your email configuration.";
83+
log.error(errorMsg, e);
84+
throw new ServerWebInputException(errorMsg, null, e);
85+
}
86+
return ServerResponse.ok().build();
87+
})
88+
);
89+
}
90+
91+
Mono<String> getCurrentUserEmail() {
92+
return ReactiveSecurityContextHolder.getContext()
93+
.map(SecurityContext::getAuthentication)
94+
.map(Principal::getName)
95+
.flatMap(username -> client.fetch(User.class, username))
96+
.flatMap(user -> {
97+
var email = user.getSpec().getEmail();
98+
if (StringUtils.isBlank(email)) {
99+
return Mono.error(new ServerWebInputException(
100+
"Your email is missing, please set it in your profile."));
101+
}
102+
return Mono.just(email);
103+
});
104+
}
105+
106+
@Data
107+
@EqualsAndHashCode(callSuper = true)
108+
@Schema(name = "EmailConfigValidationRequest")
109+
static class ValidationRequest extends EmailSenderHelper.EmailSenderConfig {
110+
}
111+
112+
@Override
113+
public GroupVersion groupVersion() {
114+
return GroupVersion.parseAPIVersion("console.api.notification.halo.run/v1alpha1");
115+
}
116+
}

0 commit comments

Comments
 (0)