LTD-ManaagerBot/src/main/kotlin/top/r3944realms/ltdmanager/module/MailModule.kt

177 lines
7.2 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/授权码登录")
}
}
}