feat: SMTP support

This commit is contained in:
devezhao 2021-03-07 18:23:27 +08:00
parent ecb57fe0d0
commit 5bf6e1d53a
13 changed files with 197 additions and 67 deletions

View file

@ -412,5 +412,10 @@
<artifactId>redisson</artifactId>
<version>3.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
</project>

View file

@ -33,6 +33,7 @@ public enum ConfigurationItem {
// 邮件
MailUser, MailPassword, MailAddr, MailName(AppName),
MailSmtpServer,
// 短信
SmsUser, SmsPassword, SmsSign(AppName),

View file

@ -107,11 +107,17 @@ public class RebuildConfiguration extends KVStorage {
/**
* 邮件账号
*
* @return returns [MailUser, MailPassword, MailAddr, MailName]
* @return returns [MailUser, MailPassword, MailAddr, MailName, MailSmtpServer]
*/
public static String[] getMailAccount() {
return getsNoUnset(false,
String[] set = getsNoUnset(false,
ConfigurationItem.MailUser, ConfigurationItem.MailPassword, ConfigurationItem.MailAddr, ConfigurationItem.MailName);
if (set == null) return null;
String smtpServer = get(ConfigurationItem.MailSmtpServer);
return new String[] {
set[0], set[1], set[2], set[3], StringUtils.defaultIfBlank(smtpServer, null)
};
}
/**

View file

@ -25,6 +25,8 @@ import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@ -34,7 +36,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* SUBMAIL SMS/MAIL 发送
* SUBMAIL SMS/MAIL 发送
*
* @author devezhao
* @since 01/03/2019
@ -44,6 +46,9 @@ public class SMSender {
private static final String STATUS_OK = "success";
private static final int TYPE_SMS = 1;
private static final int TYPE_EMAIL = 2;
/**
* @param to
* @param subject
@ -84,13 +89,7 @@ public class SMSender {
throw new ConfigurationException(Language.L("SomeAccountConfError", "Email"));
}
Map<String, Object> params = new HashMap<>();
params.put("appid", specAccount[0]);
params.put("signature", specAccount[1]);
params.put("to", to);
params.put("from", specAccount[2]);
params.put("from_name", specAccount[3]);
params.put("subject", subject);
// 使用邮件模板
if (useTemplate) {
Element mailbody = getMailTemplate();
@ -101,9 +100,34 @@ public class SMSender {
htmlContent = htmlContent.replace("%TO%", to);
htmlContent = htmlContent.replace("%TIME%", CalendarUtils.getUTCDateTimeFormat().format(CalendarUtils.now()));
htmlContent = htmlContent.replace("%APPNAME%", RebuildConfiguration.get(ConfigurationItem.AppName));
params.put("html", htmlContent);
content = htmlContent;
}
final String logContent = "" + subject + "" + content;
// Use SMTP
if (specAccount.length >= 5 && specAccount[4] != null) {
String emailId;
try {
emailId = sendMailViaSmtp(to, subject, content, specAccount);
} catch (EmailException ex) {
log.error("Mail failed to send : " + to + " > " + subject, ex);
return null;
}
createLog(to, logContent, TYPE_EMAIL, emailId, null);
return emailId;
}
Map<String, Object> params = new HashMap<>();
params.put("appid", specAccount[0]);
params.put("signature", specAccount[1]);
params.put("to", to);
params.put("from", specAccount[2]);
params.put("from_name", specAccount[3]);
params.put("subject", subject);
if (useTemplate) {
params.put("html", content);
} else {
params.put("text", content);
}
@ -118,22 +142,45 @@ public class SMSender {
return null;
}
final String scontent = "" + subject + "" + content;
JSONArray returns = rJson.getJSONArray("return");
if (STATUS_OK.equalsIgnoreCase(rJson.getString("status")) && !returns.isEmpty()) {
String sendId = ((JSONObject) returns.get(0)).getString("send_id");
createLog(to, scontent, 2, sendId, null);
createLog(to, logContent, TYPE_EMAIL, sendId, null);
return sendId;
} else {
log.error("Mail failed to send : " + to + " > " + subject + "\nError : " + rJson);
createLog(to, scontent, 2, null, rJson.getString("msg"));
createLog(to, logContent, TYPE_EMAIL, null, rJson.getString("msg"));
return null;
}
}
/**
* SMTP 发送
*
* @param to
* @param subject
* @param htmlContent
* @param specAccount
* @return
* @throws ConfigurationException
*/
private static String sendMailViaSmtp(String to, String subject, String htmlContent, String[] specAccount) throws EmailException {
HtmlEmail email = new HtmlEmail();
email.addTo(to);
email.setSubject(subject);
email.setHtmlMsg(htmlContent);
email.setAuthentication(specAccount[0], specAccount[1]);
email.setFrom(specAccount[2], specAccount[3]);
String[] hostPort = specAccount[4].split(":");
email.setHostName(hostPort[0]);
if (hostPort.length > 1) email.setSmtpPort(Integer.parseInt(hostPort[1]));
return email.send();
}
private static Element MT_CACHE = null;
/**
* @return
@ -216,19 +263,18 @@ public class SMSender {
if (STATUS_OK.equalsIgnoreCase(rJson.getString("status"))) {
String sendId = rJson.getString("send_id");
createLog(to, content, 1, sendId, null);
createLog(to, content, TYPE_SMS, sendId, null);
return sendId;
} else {
log.error("SMS failed to send : " + to + " > " + content + "\nError : " + rJson);
createLog(to, content, 1, null, rJson.getString("msg"));
createLog(to, content, TYPE_SMS, null, rJson.getString("msg"));
return null;
}
}
/**
* 发送日志
* 记录发送日志
*
* @param to
* @param content

View file

@ -201,7 +201,8 @@ public class ConfigurationController extends BaseController {
String[] specAccount = new String[]{
data.getString("MailUser"), data.getString("MailPassword"),
data.getString("MailAddr"), data.getString("MailName")
data.getString("MailAddr"), data.getString("MailName"),
data.getString("MailSmtpServer")
};
if (specAccount[1].contains("******")) {
specAccount[1] = RebuildConfiguration.get(ConfigurationItem.MailPassword);

View file

@ -408,7 +408,6 @@
"GeneralSet": "General",
"Integration": "Integration",
"IntegrationStorage": "Cloud Storage",
"IntegrationMsgs": "SMS & EMail",
"BizAndEntity": "Business Entity",
"SomeManage": "{0} Management",
"DataImport": "Data Import",
@ -456,7 +455,7 @@
"HelpDoc": "Documentation",
"TechSupport": "Tech support",
"DefaultLanguage": "Default language",
"SmsEmail": "SMS & EMail",
"SmsEmail": "EMail & SMS",
"Submail": "SUBMAIL",
"Qiniu": "QINIU",
"SomeService": "{0} Service",
@ -1323,6 +1322,11 @@
"SortByModified": "Recently Modified",
"SortByCreated": "Recently Created",
"SortByCreatedAsc": "Earliest Created",
"SmtpServer": "SMTP Server Address",
"SmtpUser": "SMTP User",
"SmtpPassword": "SMTP Password",
"SwitchEmailSmtp": "Use SMTP",
"SwitchEmailSubmail": "Use SUBMAIL",
"s.__": "STATE",
"s.ApprovalState.DRAFT": "Draft",

View file

@ -408,7 +408,6 @@
"GeneralSet": "通用配置",
"Integration": "服务集成",
"IntegrationStorage": "云存储",
"IntegrationMsgs": "短信 & 邮件",
"BizAndEntity": "业务实体",
"SomeManage": "{0}管理",
"DataImport": "数据导入",
@ -456,9 +455,9 @@
"HelpDoc": "帮助文档",
"TechSupport": "技术支持",
"DefaultLanguage": "默认语言",
"SmsEmail": "短信 & 邮件",
"SmsEmail": "邮件 & 短信",
"Submail": "赛邮 SUBMAIL",
"Qiniu": "七牛 QINIU",
"Qiniu": "七牛 QINIU",
"SomeService": "{0}服务",
"MailServName": "发件人名称",
"MailServAddr": "发件人地址",
@ -1323,6 +1322,11 @@
"SortByModified": "最近修改",
"SortByCreated": "最近创建",
"SortByCreatedAsc": "最早创建",
"SmtpServer": "SMTP 服务器地址",
"SmtpUser": "SMTP 用户名",
"SmtpPassword": "SMTP 密码",
"SwitchEmailSmtp": "使用 SMTP",
"SwitchEmailSubmail": "使用赛邮",
"s.__": "状态",
"s.ApprovalState.DRAFT": "草稿",

View file

@ -408,7 +408,6 @@
"GeneralSet": "通用配寘",
"Integration": "服務集成",
"IntegrationStorage": "雲存儲",
"IntegrationMsgs": "簡訊 & 郵件",
"BizAndEntity": "業務實體",
"SomeManage": "{0}管理",
"DataImport": "數據導入",
@ -456,9 +455,9 @@
"HelpDoc": "幫助文檔",
"TechSupport": "技術支援",
"DefaultLanguage": "默認語言",
"SmsEmail": "簡訊&郵件",
"Submail": "賽郵SUBMAIL",
"Qiniu": "七牛QINIU",
"SmsEmail": "郵件 & 簡訊",
"Submail": "賽郵 SUBMAIL",
"Qiniu": "七牛QINIU",
"SomeService": "{0}服務",
"MailServName": "發件人名稱",
"MailServAddr": "發件人地址",
@ -1323,6 +1322,11 @@
"SortByModified": "最近修改",
"SortByCreated": "最近創建",
"SortByCreatedAsc": "最早創建",
"SmtpServer": "SMTP 服務器地址",
"SmtpUser": "SMTP 用戶名",
"SmtpPassword": "SMTP 密碼",
"SwitchEmailSmtp": "使用 SMTP",
"SwitchEmailSubmail": "使用賽郵",
"s.__": "狀態",
"s.ApprovalState.DRAFT": "草稿",

View file

@ -19,7 +19,7 @@
<div class="content">
<ul>
<li th:class="${active == 'integration-storage'} ? 'active'"><a th:href="@{/admin/integration/storage}">[[${bundle.L('IntegrationStorage')}]]</a></li>
<li th:class="${active == 'integration-submail'} ? 'active'"><a th:href="@{/admin/integration/submail}">[[${bundle.L('IntegrationMsgs')}]]</a></li>
<li th:class="${active == 'integration-submail'} ? 'active'"><a th:href="@{/admin/integration/submail}">[[${bundle.L('SmsEmail')}]]</a></li>
</ul>
</div>
</div>

View file

@ -15,11 +15,11 @@
<div class="col-lg-9 col-12">
<div class="card">
<div class="card-header pb-1">
[[${bundle.L('Qiniu')}]]
[[${bundle.L('CloudStorage')}]]
<a href="#modfiy" class="float-right"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('Modify')}]]</a>
</div>
<div class="card-body">
<h5>[[${bundle.L('CloudStorage')}]]</h5>
<h5>[[${bundle.L('Qiniu')}]]</h5>
<table class="table">
<tbody>
<tr>

View file

@ -14,6 +14,16 @@
.row.stats .col-4 {
height: 48px;
}
.smtp .smtp-hide,
.smtp-show {
display: none;
}
.smtp .smtp-show {
display: inline-block;
}
.smtp tr.smtp-show {
display: table-row;
}
</style>
</head>
<body>
@ -30,46 +40,33 @@
<a href="#modfiy" class="float-right"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('Modify')}]]</a>
</div>
<div class="card-body">
<h5>[[${bundle.L('SomeService,Sms')}]]</h5>
<table class="table">
<tbody>
<tr>
<td width="40%">APPID</td>
<td data-id="SmsUser" th:data-value="${smsAccount == null ? '' : smsAccount[0]}">[[${smsAccount == null ? bundle.L('Unset') : smsAccount[0]}]]</td>
</tr>
<tr>
<td>APPKEY</td>
<td data-id="SmsPassword" th:data-value="${smsAccount == null ? '' : smsAccount[1]}">[[${smsAccount == null ? bundle.L('Unset') : smsAccount[1]}]]</td>
</tr>
<tr>
<td>[[${bundle.L('SmsSign')}]]</td>
<td data-id="SmsSign" th:data-value="${smsAccount == null ? '' : smsAccount[2]}">[[${smsAccount == null ? bundle.L('Unset') : smsAccount[2]}]]</td>
</tr>
<tr class="show-on-edit">
<td></td>
<td><button class="btn btn-primary btn-outline J_test-sms">[[${bundle.L('SendTest')}]]</button></td>
</tr>
</tbody>
</table>
<div th:if="${smsAccount == null}" class="alert alert-danger alert-icon mt-6">
<div class="icon"><span class="zmdi zmdi-close-circle-o"></span></div>
<div class="message">[[${bundle.L('SomeServiceUnsetTips,Sms')}]]</div>
</div>
<h5>[[${bundle.L('SomeService,Mail')}]]</h5>
<table class="table">
<table class="table email-set">
<tbody>
<tr class="smtp-show">
<td width="40%">[[${bundle.L('SmtpServer')}]]</td>
<td data-id="MailSmtpServer" data-ignore="true" th:data-value="${mailAccount == null ? '' : mailAccount[4]}">
[[${mailAccount == null ? bundle.L('Unset') : mailAccount[4]}]]
</td>
</tr>
<tr>
<td width="40%">APPID</td>
<td width="40%">
<span class="smtp-hide">APPID</span>
<span class="smtp-show">[[${bundle.L('SmtpUser')}]]</span>
</td>
<td data-id="MailUser" th:data-value="${mailAccount == null ? '' : mailAccount[0]}">[[${mailAccount == null ? bundle.L('Unset') : mailAccount[0]}]]</td>
</tr>
<tr>
<td>APPKEY</td>
<td>
<span class="smtp-hide">APPKEY</span>
<span class="smtp-show">[[${bundle.L('SmtpPassword')}]]</span>
</td>
<td data-id="MailPassword" th:data-value="${mailAccount == null ? '' : mailAccount[1]}">[[${mailAccount == null ? bundle.L('Unset') : mailAccount[1]}]]</td>
</tr>
<tr>
<td>
[[${bundle.L('MailServAddr')}]]
<p>[[${bundle.L('MailServAddrTips')}]]</p>
<p class="smtp-hide">[[${bundle.L('MailServAddrTips')}]]</p>
</td>
<td data-id="MailAddr" th:data-value="${mailAccount == null ? '' : mailAccount[2]}">[[${mailAccount == null ? bundle.L('Unset') : mailAccount[2]}]]</td>
</tr>
@ -79,14 +76,43 @@
</tr>
<tr class="show-on-edit">
<td></td>
<td><button class="btn btn-primary btn-outline J_test-email">[[${bundle.L('SendTest')}]]</button></td>
<td>
<button class="btn btn-primary btn-outline J_test-email">[[${bundle.L('SendTest')}]]</button>
<button class="btn btn-link J_switch-email-set smtp-hide">[[${bundle.L('SwitchEmailSmtp')}]]</button>
<button class="btn btn-link J_switch-email-set smtp-show">[[${bundle.L('SwitchEmailSubmail')}]]</button>
</td>
</tr>
</tbody>
</table>
<div th:if="${mailAccount == null}" class="alert alert-danger alert-icon mt-6 mb-6">
<div th:if="${mailAccount == null}" class="alert alert-danger alert-icon mt-6">
<div class="icon"><span class="zmdi zmdi-close-circle-o"></span></div>
<div class="message">[[${bundle.L('SomeServiceUnsetTips,Mail')}]]</div>
</div>
<h5>[[${bundle.L('SomeService,Sms')}]]</h5>
<table class="table">
<tbody>
<tr>
<td width="40%">APPID</td>
<td data-id="SmsUser" th:data-value="${smsAccount == null ? '' : smsAccount[0]}">[[${smsAccount == null ? bundle.L('Unset') : smsAccount[0]}]]</td>
</tr>
<tr>
<td>APPKEY</td>
<td data-id="SmsPassword" th:data-value="${smsAccount == null ? '' : smsAccount[1]}">[[${smsAccount == null ? bundle.L('Unset') : smsAccount[1]}]]</td>
</tr>
<tr>
<td>[[${bundle.L('SmsSign')}]]</td>
<td data-id="SmsSign" th:data-value="${smsAccount == null ? '' : smsAccount[2]}">[[${smsAccount == null ? bundle.L('Unset') : smsAccount[2]}]]</td>
</tr>
<tr class="show-on-edit">
<td></td>
<td><button class="btn btn-primary btn-outline J_test-sms">[[${bundle.L('SendTest')}]]</button></td>
</tr>
</tbody>
</table>
<div th:if="${smsAccount == null}" class="alert alert-danger alert-icon mt-6 mb-6">
<div class="icon"><span class="zmdi zmdi-close-circle-o"></span></div>
<div class="message">[[${bundle.L('SomeServiceUnsetTips,Sms')}]]</div>
</div>
<div class="edit-footer">
<button class="btn btn-link">[[${bundle.L('Cancel')}]]</button>
<button class="btn btn-primary">[[${bundle.L('Save')}]]</button>

View file

@ -5,10 +5,34 @@ rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
// eslint-disable-next-line no-undef
postBefore = function (data) {
const isSmtp = $('.email-set').hasClass('smtp')
if (isSmtp) {
data['MailSmtpServer'] = $('input[name="MailSmtpServer"]').val()
if (!data['MailSmtpServer']) {
RbHighbar.create($L('SomeNotEmpty,SmtpServer'))
return false
}
} else {
data['MailSmtpServer'] = ''
}
return data
}
$(document).ready(() => {
$('.J_test-email').click(() => renderRbcomp(<TestSend type="email" />))
$('.J_test-sms').click(() => renderRbcomp(<TestSend type="sms" />))
// Use SMTP
if ($('td[data-id="MailSmtpServer"]').attr('data-value')) {
$('.email-set').addClass('smtp')
}
// Switch SMTP
$('.J_switch-email-set').click(() => {
$('.email-set').toggleClass('smtp')
})
$.get('./submail/stats', (res) => {
let $el = $('.J_stats-sms')
$el.find('strong').text(res.data.smsCount || 0)

View file

@ -11,7 +11,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
$(document).ready(() => {
$('.card-header>a').click((e) => {
e.preventDefault()
editMode()
enableEditMode()
})
$('.edit-footer>.btn-link').click(() => location.reload())
@ -25,7 +25,7 @@ const changeValue = function (e) {
}
// 激活编辑模式
const editMode = function () {
const enableEditMode = function () {
$('.syscfg table td[data-id]').each(function () {
const $item = $(this)
const name = $item.data('id')
@ -51,6 +51,8 @@ var useEditComp = function (name, value) {
const post = function (data) {
for (let k in data) {
if (!data[k]) {
if ($('td[data-id=' + k + ']').data('ignore')) continue
const field = $('td[data-id=' + k + ']')
.prev()
.text()
@ -59,6 +61,8 @@ const post = function (data) {
}
}
if (!(data = postBefore(data))) return false
const $btn = $('.edit-footer>.btn-primary').button('loading')
$.post(location.href, JSON.stringify(data), (res) => {
$btn.button('reset')
@ -66,3 +70,8 @@ const post = function (data) {
else RbHighbar.error(res.error_msg)
})
}
// 复写-保存前检查
var postBefore = function (data) {
return data
}