feat: 添加SMTP邮件通知功能

新增功能:
- 支持SMTP邮件通知平台,可通过邮件接收系统通知
- 支持配置SMTP服务器、端口、用户名、密码、发件人和收件人
- 支持TLS/SSL加密连接
- 提供美观的HTML邮件模板和纯文本备用格式

代码优化:
- 重构邮件格式化逻辑,提取buildNotificationDetails减少重复代码
- 优化前端表单验证逻辑,提取validatePlatformForm统一验证
- 清理UI中的冗余提示信息和配置项

UI改进:
- 移除SMTP配置说明文字
- 移除超时设置和忽略TLS证书验证选项
- 简化测试成功提示消息
- SMTP平台显示收件人邮箱而非URL

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Edric Li
2025-09-09 04:00:35 +08:00
parent ca79e08c81
commit 283362acd0
6 changed files with 529 additions and 44 deletions

View File

@@ -63,7 +63,8 @@ class WebhookConfigService {
'slack',
'discord',
'custom',
'bark'
'bark',
'smtp'
]
for (const platform of config.platforms) {
@@ -71,8 +72,8 @@ class WebhookConfigService {
throw new Error(`不支持的平台类型: ${platform.type}`)
}
// Bark平台使用deviceKey而不是url
if (platform.type !== 'bark') {
// Bark和SMTP平台使用标准URL
if (platform.type !== 'bark' && platform.type !== 'smtp') {
if (!platform.url || !this.isValidUrl(platform.url)) {
throw new Error(`无效的webhook URL: ${platform.url}`)
}
@@ -201,6 +202,51 @@ class WebhookConfigService {
logger.warn('⚠️ Bark点击跳转URL格式可能不正确')
}
break
case 'smtp': {
// 验证SMTP必需配置
if (!platform.host) {
throw new Error('SMTP平台必须提供主机地址')
}
if (!platform.user) {
throw new Error('SMTP平台必须提供用户名')
}
if (!platform.pass) {
throw new Error('SMTP平台必须提供密码')
}
if (!platform.to) {
throw new Error('SMTP平台必须提供接收邮箱')
}
// 验证端口
if (platform.port && (platform.port < 1 || platform.port > 65535)) {
throw new Error('SMTP端口必须在1-65535之间')
}
// 验证邮箱格式
// 支持两种格式1. 纯邮箱 user@domain.com 2. 带名称 Name <user@domain.com>
const simpleEmailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
// 验证接收邮箱
const toEmails = Array.isArray(platform.to) ? platform.to : [platform.to]
for (const email of toEmails) {
// 提取实际邮箱地址(如果是 Name <email> 格式)
const actualEmail = email.includes('<') ? email.match(/<([^>]+)>/)?.[1] : email
if (!actualEmail || !simpleEmailRegex.test(actualEmail)) {
throw new Error(`无效的接收邮箱格式: ${email}`)
}
}
// 验证发送邮箱(支持 Name <email> 格式)
if (platform.from) {
const actualFromEmail = platform.from.includes('<')
? platform.from.match(/<([^>]+)>/)?.[1]
: platform.from
if (!actualFromEmail || !simpleEmailRegex.test(actualFromEmail)) {
throw new Error(`无效的发送邮箱格式: ${platform.from}`)
}
}
break
}
}
}