177 lines
7.2 KiB
Kotlin
177 lines
7.2 KiB
Kotlin
package top.r3944realms.ltdmanager.module
|
||
|
||
import jakarta.mail.*
|
||
import jakarta.mail.internet.InternetAddress
|
||
import jakarta.mail.internet.MimeMessage
|
||
import top.r3944realms.ltdmanager.core.mail.Mail
|
||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||
import java.util.*
|
||
import java.util.concurrent.LinkedBlockingQueue
|
||
import kotlin.concurrent.thread
|
||
|
||
class MailModule(
|
||
moduleName: String,
|
||
private val protocol: String = "SMTP",
|
||
private val host: String,
|
||
private val port: Int,
|
||
private val senderEmailAddress: String,
|
||
private val authToken: String,
|
||
private val enableAuth: Boolean = true,
|
||
private val enableTLS: Boolean = true,
|
||
private val intervalMillis: Long = 2000L // 每封邮件之间的间隔(默认 2s)
|
||
) : BaseModule(Modules.MAIL, moduleName) {
|
||
|
||
private lateinit var session: Session
|
||
private val queue = LinkedBlockingQueue<Mail>() // 邮件队列
|
||
private var workerThread: Thread? = null
|
||
@Volatile private var running = false
|
||
|
||
override fun onLoad() {
|
||
LoggerUtil.logger.info("[$name] 模块加载中,初始化邮件会话...")
|
||
/*
|
||
163 邮箱(以及大多数 SMTP 服务商)区别是:
|
||
|
||
465 👉 “隐式 SSL”,必须启用 mail.smtp.ssl.enable=true。
|
||
|
||
587 👉 “明文 + STARTTLS”,必须启用 mail.smtp.starttls.enable=true。
|
||
|
||
而注释中 onLoad() 写死了:
|
||
|
||
put("mail.smtp.starttls.enable", enableTLS)
|
||
|
||
所以当用 465 端口时,服务端要求立即握手 SSL,但程序还在用明文 STARTTLS,直接就被 EOF 掉了。
|
||
* */
|
||
// val props = Properties().apply {
|
||
// put("mail.transport.protocol", protocol)
|
||
// put("mail.smtp.host", host)
|
||
// put("mail.smtp.port", port)
|
||
// put("mail.smtp.auth", enableAuth)
|
||
// put("mail.smtp.starttls.enable", enableTLS)
|
||
// }
|
||
val props = Properties().apply {
|
||
put("mail.transport.protocol", protocol)
|
||
put("mail.smtp.host", host)
|
||
put("mail.smtp.port", port)
|
||
put("mail.smtp.auth", enableAuth)
|
||
|
||
when (port) {
|
||
465 -> {
|
||
// 隐式 SSL
|
||
put("mail.smtp.ssl.enable", "true")
|
||
put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory")
|
||
put("mail.smtp.socketFactory.port", port.toString())
|
||
}
|
||
587 -> {
|
||
// STARTTLS
|
||
if (enableTLS) {
|
||
put("mail.smtp.starttls.enable", "true")
|
||
put("mail.smtp.starttls.required", "true")
|
||
}
|
||
}
|
||
else -> {
|
||
// 普通 25 端口或其他情况
|
||
if (enableTLS) {
|
||
put("mail.smtp.starttls.enable", "true")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
session = Session.getInstance(props, object : Authenticator() {
|
||
override fun getPasswordAuthentication(): PasswordAuthentication {
|
||
return PasswordAuthentication(senderEmailAddress, authToken)
|
||
}
|
||
})
|
||
|
||
running = true
|
||
workerThread = thread(start = true, name = "MailSender-Worker") {
|
||
LoggerUtil.logger.info("[$name] 邮件发送线程启动")
|
||
while (running && loaded) {
|
||
try {
|
||
val mail = queue.take() // 阻塞等待新任务
|
||
LoggerUtil.logger.info("[$name] 开始发送邮件 -> 收件人: ${mail.to.joinToString(",")}")
|
||
sendInternal(mail)
|
||
LoggerUtil.logger.info("[$name] 邮件发送完成 -> ${mail.to.joinToString(",")}")
|
||
Thread.sleep(intervalMillis) // 限流
|
||
} catch (e: InterruptedException) {
|
||
LoggerUtil.logger.info("[$name] 邮件发送线程被中断,准备退出")
|
||
break
|
||
} catch (e: Exception) {
|
||
LoggerUtil.logger.error("[$name] 邮件发送出现异常", e)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
override suspend fun onUnload() {
|
||
LoggerUtil.logger.info("[$name] 模块卸载,停止邮件发送线程")
|
||
running = false
|
||
workerThread?.interrupt()
|
||
workerThread = null
|
||
}
|
||
|
||
|
||
/**
|
||
* 加入发送队列
|
||
*/
|
||
fun enqueue(mail: Mail) {
|
||
if (!loaded) throw IllegalStateException("MailModule 未加载,不能发送邮件")
|
||
LoggerUtil.logger.info("[$name] 邮件加入队列 -> 收件人: ${mail.to.joinToString(",")}, 主题: ${mail.subject}")
|
||
queue.put(mail)
|
||
}
|
||
|
||
/**
|
||
* 真正的发送逻辑(内部调用)
|
||
*/
|
||
private fun sendInternal(mail: Mail) {
|
||
val message = MimeMessage(session).apply {
|
||
setFrom(InternetAddress(senderEmailAddress,mail.from ?: senderEmailAddress, "UTF-8"))
|
||
setRecipients(Message.RecipientType.TO, mail.to.joinToString(","))
|
||
if (mail.cc.isNotEmpty()) {
|
||
setRecipients(Message.RecipientType.CC, mail.cc.joinToString(","))
|
||
}
|
||
if (mail.bcc.isNotEmpty()) {
|
||
setRecipients(Message.RecipientType.BCC, mail.bcc.joinToString(","))
|
||
}
|
||
subject = mail.subject
|
||
setContent(
|
||
mail.body,
|
||
if (mail.isHtml) "text/html;charset=UTF-8" else "text/plain;charset=UTF-8"
|
||
)
|
||
}
|
||
|
||
Transport.send(message)
|
||
}
|
||
override fun info(): String {
|
||
return buildString {
|
||
appendLine("[$name] 邮件发送模块")
|
||
appendLine("功能: 异步发送邮件,支持收件人/抄送/密送,支持 HTML 或纯文本邮件。")
|
||
appendLine("SMTP 配置:")
|
||
appendLine(" - 协议: $protocol")
|
||
appendLine(" - 主机: $host")
|
||
appendLine(" - 端口: $port")
|
||
appendLine(" - 发件人邮箱: $senderEmailAddress")
|
||
appendLine(" - 身份认证: ${if (enableAuth) "启用" else "禁用"}")
|
||
appendLine(" - TLS/SSL: ${if (enableTLS) "启用" else "禁用"}")
|
||
appendLine("队列行为:")
|
||
appendLine(" - 邮件发送间隔: ${intervalMillis}ms")
|
||
appendLine(" - 队列长度: ${queue.size}")
|
||
appendLine(" - 当前发送线程状态: ${if (workerThread?.isAlive == true) "运行中" else "未运行"}")
|
||
}
|
||
}
|
||
|
||
override fun help(): String {
|
||
return buildString {
|
||
appendLine("📖 [$name] 使用帮助:")
|
||
appendLine("1. 创建 Mail 对象,设置收件人、主题和正文")
|
||
appendLine(" 例如: Mail(to = listOf(\"example@mail.com\"), subject = \"测试\", body = \"Hello\")")
|
||
appendLine("2. 调用 enqueue(mail) 加入发送队列")
|
||
appendLine(" 邮件将异步发送,间隔 $intervalMillis ms")
|
||
appendLine("3. 模块卸载时会自动停止发送线程")
|
||
appendLine()
|
||
appendLine("注意:")
|
||
appendLine(" - 确保 SMTP 配置正确,否则发送失败")
|
||
appendLine(" - 发件人邮箱需要允许 SMTP/授权码登录")
|
||
}
|
||
}
|
||
}
|