使用Spring Mail和FreeMaker发送HTML邮件

2023-10-18 15:39:41 浏览数 (1)

使用Spring Mail和FreeMaker发送HTML邮件

引言

最近在写自己的博客项目,有收到新评论后发送邮件通知的功能,使用MQ通知服务,使用了没接触过的FreeMaker和JavaMail,记录一下实现过程,代码仓库:https://github.com/mashirot/MashiroBlog/tree/master/mail-service

实现

项目结构:

代码语言:javascript复制
└─src
   └─main
      ├─kotlin
      │  └─ski
      │      └─mashiro
      │          │  MailServiceApplication.kt
      │          │
      │          ├─annotation
      │          │      Slf4j.kt
      │          │
      │          ├─config
      │          │      JacksonConfig.kt
      │          │
      │          ├─constant
      │          │      RabbitMQConsts.kt
      │          │
      │          ├─dto
      │          │      CommentMailDTO.kt
      │          │
      │          ├─listener
      │          │      MailQueueListener.kt
      │          │
      │          └─service
      │              │  MailService.kt
      │              │
      │              └─impl
      │                      MailServiceImpl.kt
      │
      └─resources
          │  application.yml
          │
          └─templates
                  CommentAdvice.ftlh

环境

Springboot 3.1.2JDK17Kotlin 1.8.22

依赖

代码语言:javascript复制
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-amqp")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-freemarker")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.amqp:spring-rabbit-test")
    implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2")
}

代码

Jackson配置
代码语言:javascript复制
/**
 * @author MashiroT
 */
@Configuration
class JacksonConfig {
    @Bean
    fun objectMapper(): ObjectMapper {
        return ObjectMapper().registerModule(JavaTimeModule())
    }
}
@Slf4j注解
代码语言:javascript复制
/**
 * @author MashiroT
 */
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Slf4j {
    companion object {
        val <reified T> T.log: Logger
            inline get() = LoggerFactory.getLogger(T::class.java)
    }
}
MQ监听
代码语言:javascript复制
/**
 * @author MashiroT
 */
@Component
class MailQueueListener(
    val mailService: MailService
) {
    @RabbitListener(queues = [RabbitMQConsts.MAIL_QUEUE])
    fun listenMailQueue(msg: String) {
        mailService.sendNewCommentAdvice2Owner(msg)
    }
}
MailService
代码语言:javascript复制
/**
 * @author MashiroT
 */
@Service
@Slf4j
class MailServiceImpl(
    val objectMapper: ObjectMapper,
    val mailSender: JavaMailSender,
    val freeMakerConfiguration: Configuration,

) : MailService {
    @Value("${blog.from}")
    private var from: String? = null

    @Value("${blog.to}")
    private var to: String? = null

    override fun sendNewCommentAdvice2Owner(msg: String) {
        try {
            val commentMailDTO = objectMapper.readValue(msg, CommentMailDTO::class.java)

            // 使用MimeMessage发送复杂邮件,如无需求可使用MailSender配合SimpleMailMessage使用
            val mimeMessage = mailSender.createMimeMessage()
            val mimeMessageHelper = MimeMessageHelper(mimeMessage, "utf-8")
            // 发送人邮箱
            mimeMessageHelper.setFrom(from!!)
            // 收件人邮箱
            mimeMessageHelper.setTo(to!!)
            val title = "你的帖子「${commentMailDTO.articleTitle}」有新回复"
            // 邮件主题
            mimeMessageHelper.setSubject(title)

            val map = mapOf(
                "title" to title,
                "senderNickname" to commentMailDTO.senderNickname!!,
                "content" to if (commentMailDTO.content!!.length > 50) commentMailDTO.content.substring(
                    0,
                    50
                ) else commentMailDTO.content
            )
            // 获取Template模板,默认路径为classpath:/templates/
            val template = freeMakerConfiguration.getTemplate("CommentAdvice.ftlh")
            // 传入参数并转为string
            val text = FreeMarkerTemplateUtils.processTemplateIntoString(template, map)

            // 邮件正文
            mimeMessageHelper.setText(text, true)
            // 发送日期(不填默认当前)
            mimeMessageHelper.setSentDate(Date())

            mailSender.send(mimeMessage)
            log.info("文章:${commentMailDTO.articleTitle} 收到新评论,已发送邮件通知")
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

}
配置
代码语言:javascript复制
mail:
    protocol: smtp
    host: smtp.office365.com
    port: 587
    username: shiinamashiro@outlook.com
    password: sakurasou
    properties:
      mail:
#        debug: true
        smtp:
          starttls:
            enable: true
#          socks:
#            host: 127.0.0.1
#            port: 7890

遇到的问题

SMTPSendFailedException: 501 5.1.7 Invalid address

message的from属性设置错误,应为发送者邮箱

451 5.7.3 STARTTLS is required to send mail

Outlook的smtp使用starttls协议,在mail的配置中增加如下:

代码语言:javascript复制
properties:
      mail:
#        debug: true
        smtp:
          starttls:
            enable: true

参考文章

  1. Java 发送邮件实现(JavaMail 和 Spring 实现)
  2. Spring Boot整合JavaMail实现邮件发送
  3. Spring Boot Freemarker 中的弯弯绕!
  4. FreeMarker简单入门,这篇就够了

0 人点赞