Skip to content

Commit 975a670

Browse files
authored
162 notify mail (#166), close issue #28
* add notify option * add option notify mail smtp account alias * add notify mail test api * add system option notify page * update changelog for pr #166
1 parent dffe937 commit 975a670

20 files changed

+249
-6
lines changed

CHANGELOG.MD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
## Features
66
- 添加用户更新接口
77
- 添加用户密码更新接口
8+
- 添加系统通知的邮件方式的配置
9+
- 添加邮件测试接口
810

911
## Pages
1012
- 添加用户设置页,对接服务端用户更新接口和密码更新接口
1113
- 添加了Ikaros的Logo页面对应的地方
14+
- 添加邮件配置页面和邮件测试页面,并对接服务端
1215

1316
## Documentations
1417
- 添加了Ikaros的Logo,并更新了Readme文档

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
implementation 'org.springframework.boot:spring-boot-starter-security'
4343
implementation 'org.springframework.boot:spring-boot-starter-validation'
4444
implementation 'org.springframework.boot:spring-boot-starter-web'
45+
implementation "org.springframework.boot:spring-boot-starter-mail"
4546

4647
implementation "io.springfox:springfox-boot-starter:$swaggerVersion"
4748

src/main/java/run/ikaros/server/constants/DefaultConst.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package run.ikaros.server.constants;
22

33
import run.ikaros.server.enums.FilePlace;
4+
import run.ikaros.server.enums.MailProtocol;
5+
import run.ikaros.server.enums.NotifyMethod;
46

57
public interface DefaultConst {
68
String OPTION_APP_IS_INIT = Boolean.TRUE.toString();
@@ -51,6 +53,14 @@ public interface DefaultConst {
5153
String OPTION_QBITTORRENT_USERNAME = "admin";
5254
String OPTION_QBITTORRENT_PASSWORD = "adminadmin";
5355

56+
String OPTION_NOTIFY_MAIL_ENABLE = Boolean.FALSE.toString();
57+
String OPTION_NOTIFY_MAIL_PROTOCOL = MailProtocol.SMTP.name();
58+
String OPTION_NOTIFY_MAIL_SMTP_HOST = "";
59+
String OPTION_NOTIFY_MAIL_SMTP_PORT = "";
60+
String OPTION_NOTIFY_MAIL_SMTP_ACCOUNT = "";
61+
String OPTION_NOTIFY_MAIL_SMTP_PASSWORD = "";
62+
String OPTION_NOTIFY_MAIL_SMTP_ACCOUNT_ALIAS = "Ikaros";
63+
5464

5565

5666
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package run.ikaros.server.core.service;
2+
3+
import run.ikaros.server.model.request.NotifyMailTestRequest;
4+
5+
import javax.mail.MessagingException;
6+
7+
public interface NotifyService {
8+
void mailTest(NotifyMailTestRequest notifyMailTestRequest) throws MessagingException;
9+
}

src/main/java/run/ikaros/server/core/service/OptionService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import run.ikaros.server.model.dto.OptionDTO;
1111
import run.ikaros.server.model.dto.OptionItemDTO;
1212
import run.ikaros.server.model.dto.OptionNetworkDTO;
13+
import run.ikaros.server.model.dto.OptionNotifyDTO;
1314
import run.ikaros.server.model.dto.OptionQbittorrentDTO;
1415
import run.ikaros.server.model.request.AppInitRequest;
1516
import run.ikaros.server.model.request.SaveOptionRequest;
@@ -59,4 +60,7 @@ OptionEntity findOptionValueByCategoryAndKey(@Nonnull OptionCategory category,
5960

6061
@Nonnull
6162
OptionQbittorrentDTO getOptionQbittorrentDTO();
63+
64+
@Nonnull
65+
OptionNotifyDTO getOptionNotifyDTO();
6266
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package run.ikaros.server.enums;
2+
3+
public enum MailProtocol {
4+
SMTP
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package run.ikaros.server.enums;
2+
3+
public enum NotifyMethod {
4+
MAIL
5+
}

src/main/java/run/ikaros/server/enums/OptionCategory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public enum OptionCategory {
3131
* @see <a href="https://github.com/jellyfin/jellyfin">jellyfin</a>
3232
*/
3333
JELLYFIN,
34+
NOTIFY,
3435
OTHER;
3536

3637
public static final Set<String> CATEGORY_SET = Arrays.stream(OptionCategory.values())
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package run.ikaros.server.enums;
2+
3+
public enum OptionNotify {
4+
MAIL_ENABLE,
5+
MAIL_PROTOCOL,
6+
MAIL_SMTP_HOST,
7+
MAIL_SMTP_PORT,
8+
MAIL_SMTP_ACCOUNT,
9+
MAIL_SMTP_PASSWORD,
10+
MAIL_SMTP_ACCOUNT_ALIAS
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package run.ikaros.server.model.dto;
2+
3+
import lombok.Data;
4+
import run.ikaros.server.enums.MailProtocol;
5+
6+
@Data
7+
public class OptionNotifyDTO {
8+
private Boolean mailEnable;
9+
private MailProtocol mailProtocol;
10+
private String mailSmtpHost;
11+
private Integer mailSmtpPort;
12+
private String mailSmtpAccount;
13+
private String mailSmtpAccountAlias;
14+
private String mailSmtpPassword;
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package run.ikaros.server.model.request;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class NotifyMailTestRequest {
7+
private String address;
8+
private String subject;
9+
private String content;
10+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package run.ikaros.server.openapi;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.PostMapping;
5+
import org.springframework.web.bind.annotation.RequestBody;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
import run.ikaros.server.core.service.NotifyService;
9+
import run.ikaros.server.model.request.NotifyMailTestRequest;
10+
import run.ikaros.server.result.CommonResult;
11+
12+
import javax.mail.MessagingException;
13+
14+
@RestController
15+
@RequestMapping("/notify")
16+
public class NotifyRestController {
17+
18+
private final NotifyService notifyService;
19+
20+
public NotifyRestController(NotifyService notifyService) {
21+
this.notifyService = notifyService;
22+
}
23+
24+
25+
@PostMapping("/mail/test")
26+
public CommonResult<Boolean> mailTest(@RequestBody NotifyMailTestRequest notifyMailTestRequest)
27+
throws MessagingException {
28+
notifyService.mailTest(notifyMailTestRequest);
29+
return CommonResult.ok(Boolean.TRUE);
30+
}
31+
32+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package run.ikaros.server.service;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.beans.factory.InitializingBean;
5+
import org.springframework.mail.javamail.JavaMailSenderImpl;
6+
import org.springframework.mail.javamail.MimeMessageHelper;
7+
import org.springframework.stereotype.Service;
8+
import run.ikaros.server.core.service.NotifyService;
9+
import run.ikaros.server.core.service.OptionService;
10+
import run.ikaros.server.exceptions.RuntimeIkarosException;
11+
import run.ikaros.server.model.dto.OptionNotifyDTO;
12+
import run.ikaros.server.model.request.NotifyMailTestRequest;
13+
import run.ikaros.server.utils.AssertUtils;
14+
15+
import javax.mail.MessagingException;
16+
import javax.mail.internet.MimeMessage;
17+
import java.nio.charset.StandardCharsets;
18+
import java.util.Properties;
19+
20+
@Slf4j
21+
@Service
22+
public class NotifyServiceImpl implements NotifyService, InitializingBean {
23+
24+
private final OptionService optionService;
25+
private JavaMailSenderImpl mailSender;
26+
27+
public NotifyServiceImpl(OptionService optionService) {
28+
this.optionService = optionService;
29+
}
30+
31+
@Override
32+
public void mailTest(NotifyMailTestRequest notifyMailTestRequest) throws MessagingException {
33+
AssertUtils.notNull(notifyMailTestRequest, "notifyMailTestRequest");
34+
AssertUtils.notBlank(notifyMailTestRequest.getAddress(), "notifyMailTestRequest address");
35+
AssertUtils.notBlank(notifyMailTestRequest.getSubject(), "notifyMailTestRequest subject");
36+
AssertUtils.notBlank(notifyMailTestRequest.getContent(), "notifyMailTestRequest content");
37+
38+
if (mailSender == null) {
39+
throw new RuntimeIkarosException("please config notify mail smtp items");
40+
}
41+
42+
OptionNotifyDTO optionNotifyDTO = optionService.getOptionNotifyDTO();
43+
MimeMessage message = mailSender.createMimeMessage();
44+
MimeMessageHelper helper = new MimeMessageHelper(message, true);
45+
String fromSB = optionNotifyDTO.getMailSmtpAccountAlias()
46+
+ "<"
47+
+ optionNotifyDTO.getMailSmtpAccount()
48+
+ ">";
49+
helper.setFrom(fromSB);
50+
helper.setTo(notifyMailTestRequest.getAddress());
51+
helper.setSubject(notifyMailTestRequest.getSubject());
52+
helper.setText(notifyMailTestRequest.getContent(), true);
53+
54+
mailSender.send(message);
55+
log.info("send mail to {} success, mail subject is {}",
56+
notifyMailTestRequest.getAddress(), notifyMailTestRequest.getSubject());
57+
}
58+
59+
@Override
60+
public void afterPropertiesSet() throws Exception {
61+
OptionNotifyDTO optionNotifyDTO = optionService.getOptionNotifyDTO();
62+
if (optionNotifyDTO.getMailEnable()) {
63+
mailSender = new JavaMailSenderImpl();
64+
mailSender.setHost(optionNotifyDTO.getMailSmtpHost());
65+
mailSender.setPort(optionNotifyDTO.getMailSmtpPort());
66+
mailSender.setProtocol(optionNotifyDTO.getMailProtocol().name().toLowerCase());
67+
mailSender.setUsername(optionNotifyDTO.getMailSmtpAccount());
68+
mailSender.setPassword(optionNotifyDTO.getMailSmtpPassword());
69+
mailSender.setDefaultEncoding(StandardCharsets.UTF_8.name());
70+
Properties properties = new Properties();
71+
properties.setProperty("mail.smtp.auth", Boolean.TRUE.toString());
72+
properties.setProperty("mail.smtp.starttls.enable", Boolean.TRUE.toString());
73+
properties.setProperty("mail.smtp.starttls.required", Boolean.TRUE.toString());
74+
properties.setProperty("mail.smtp.socketFactory.fallback", Boolean.FALSE.toString());
75+
properties.setProperty("mail.smtp.socketFactory.port",
76+
String.valueOf(optionNotifyDTO.getMailSmtpPort()));
77+
properties.setProperty("mail.smtp.socketFactory.class",
78+
"javax.net.ssl.SSLSocketFactory");
79+
mailSender.setJavaMailProperties(properties);
80+
}
81+
}
82+
}

src/main/java/run/ikaros/server/service/OptionServiceImpl.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import run.ikaros.server.core.service.OptionService;
1515
import run.ikaros.server.core.service.UserService;
1616
import run.ikaros.server.entity.OptionEntity;
17+
import run.ikaros.server.enums.MailProtocol;
1718
import run.ikaros.server.enums.OptionApp;
1819
import run.ikaros.server.enums.OptionBgmTv;
1920
import run.ikaros.server.enums.OptionCategory;
@@ -22,6 +23,7 @@
2223
import run.ikaros.server.enums.OptionJellyfin;
2324
import run.ikaros.server.enums.OptionMikan;
2425
import run.ikaros.server.enums.OptionNetwork;
26+
import run.ikaros.server.enums.OptionNotify;
2527
import run.ikaros.server.enums.OptionQbittorrent;
2628
import run.ikaros.server.enums.OptionSeo;
2729
import run.ikaros.server.event.BgmTvHttpProxyUpdateEvent;
@@ -32,6 +34,7 @@
3234
import run.ikaros.server.model.dto.OptionDTO;
3335
import run.ikaros.server.model.dto.OptionItemDTO;
3436
import run.ikaros.server.model.dto.OptionNetworkDTO;
37+
import run.ikaros.server.model.dto.OptionNotifyDTO;
3538
import run.ikaros.server.model.dto.OptionQbittorrentDTO;
3639
import run.ikaros.server.model.request.AppInitRequest;
3740
import run.ikaros.server.model.request.SaveOptionRequest;
@@ -246,6 +249,22 @@ public boolean appInit(@Nonnull AppInitRequest appInitRequest, boolean isCoerce)
246249
saveOptionItem(new OptionItemDTO(OptionJellyfin.MEDIA_DIR_PATH.name(),
247250
DefaultConst.OPTION_JELLYFIN_MEDIA_DIR_PATH, OptionCategory.JELLYFIN));
248251

252+
// init option notify
253+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_ENABLE.name(),
254+
DefaultConst.OPTION_NOTIFY_MAIL_ENABLE, OptionCategory.NOTIFY));
255+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_PROTOCOL.name(),
256+
DefaultConst.OPTION_NOTIFY_MAIL_PROTOCOL, OptionCategory.NOTIFY));
257+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_SMTP_HOST.name(),
258+
DefaultConst.OPTION_NOTIFY_MAIL_SMTP_HOST, OptionCategory.NOTIFY));
259+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_SMTP_PORT.name(),
260+
DefaultConst.OPTION_NOTIFY_MAIL_SMTP_PORT, OptionCategory.NOTIFY));
261+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_SMTP_ACCOUNT.name(),
262+
DefaultConst.OPTION_NOTIFY_MAIL_SMTP_ACCOUNT, OptionCategory.NOTIFY));
263+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_SMTP_PASSWORD.name(),
264+
DefaultConst.OPTION_NOTIFY_MAIL_SMTP_PASSWORD, OptionCategory.NOTIFY));
265+
saveOptionItem(new OptionItemDTO(OptionNotify.MAIL_SMTP_ACCOUNT_ALIAS.name(),
266+
DefaultConst.OPTION_NOTIFY_MAIL_SMTP_ACCOUNT_ALIAS, OptionCategory.NOTIFY));
267+
249268
return true;
250269
}
251270

@@ -546,4 +565,40 @@ public OptionQbittorrentDTO getOptionQbittorrentDTO() {
546565
}
547566
return qbittorrentDTO;
548567
}
568+
569+
@Nonnull
570+
@Override
571+
public OptionNotifyDTO getOptionNotifyDTO() {
572+
OptionNotifyDTO notifyDTO = new OptionNotifyDTO();
573+
List<OptionEntity> optionEntityList = findOptionByCategory(OptionCategory.NOTIFY);
574+
for (OptionEntity optionEntity : optionEntityList) {
575+
final String key = optionEntity.getKey();
576+
final String value = optionEntity.getValue();
577+
if (OptionNotify.MAIL_ENABLE.name().equalsIgnoreCase(key)
578+
&& StringUtils.isNotBlank(value)) {
579+
notifyDTO.setMailEnable(Boolean.valueOf(value));
580+
}
581+
if (OptionNotify.MAIL_PROTOCOL.name().equalsIgnoreCase(key)
582+
&& StringUtils.isNotBlank(value)) {
583+
notifyDTO.setMailProtocol(MailProtocol.valueOf(value));
584+
}
585+
if (OptionNotify.MAIL_SMTP_HOST.name().equalsIgnoreCase(key)) {
586+
notifyDTO.setMailSmtpHost(value);
587+
}
588+
if (OptionNotify.MAIL_SMTP_PORT.name().equalsIgnoreCase(key)
589+
&& StringUtils.isNotBlank(value)) {
590+
notifyDTO.setMailSmtpPort(Integer.valueOf(value));
591+
}
592+
if (OptionNotify.MAIL_SMTP_ACCOUNT.name().equalsIgnoreCase(key)) {
593+
notifyDTO.setMailSmtpAccount(value);
594+
}
595+
if (OptionNotify.MAIL_SMTP_ACCOUNT_ALIAS.name().equalsIgnoreCase(key)) {
596+
notifyDTO.setMailSmtpAccountAlias(value);
597+
}
598+
if (OptionNotify.MAIL_SMTP_PASSWORD.name().equalsIgnoreCase(key)) {
599+
notifyDTO.setMailSmtpPassword(value);
600+
}
601+
}
602+
return notifyDTO;
603+
}
549604
}

src/main/resources/admin/css/605.c836fce2.css

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.container .tab-content-pane[data-v-dc537ae8]{padding:0 16px}.ant-form-item[data-v-dc537ae8]{margin-bottom:0}

src/main/resources/admin/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!doctype html><html lang="zh-cmn-Hans"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/admin/logo.png"><title>Ikaros Admin</title><style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style><script defer="defer" src="/admin/js/chunk-vendors.5d7eb9b9.js"></script><script defer="defer" src="/admin/js/app.d4e5ed93.js"></script><link href="/admin/css/chunk-vendors.c41eca44.css" rel="stylesheet"><link href="/admin/css/app.e49b7a10.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div class="first-loading-wrp"><h1>Ikaros</h1><div class="loading-wrp"><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div><div style="display: flex; justify-content: center; align-items: center;">Ikaros Admin</div></div></div></body></html>
1+
<!doctype html><html lang="zh-cmn-Hans"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/admin/logo.png"><title>Ikaros Admin</title><style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style><script defer="defer" src="/admin/js/chunk-vendors.5d7eb9b9.js"></script><script defer="defer" src="/admin/js/app.afe6796d.js"></script><link href="/admin/css/chunk-vendors.c41eca44.css" rel="stylesheet"><link href="/admin/css/app.e49b7a10.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div class="first-loading-wrp"><h1>Ikaros</h1><div class="loading-wrp"><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div><div style="display: flex; justify-content: center; align-items: center;">Ikaros Admin</div></div></div></body></html>

src/main/resources/admin/js/605.74c54f96.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/resources/admin/js/664.ea418ce8.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/resources/admin/js/app.d4e5ed93.js renamed to src/main/resources/admin/js/app.afe6796d.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)