一、背景介绍

在实际开发中,邮件发送功能几乎是每个Web应用都需要的基础功能。Spring Boot通过整合JavaMailSender,为我们提供了简单高效的邮件发送解决方案。但在实际学习过程中,我还是遇到了不少"坑"。本文将记录我的学习历程,特别是遇到的问题和解决方案。

二、环境搭建与基础配置

2.1 创建Spring Boot项目

首先创建一个新的Spring Boot项目,添加以下关键依赖:

xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2 基础配置文件

yaml

# application.yml
spring:
  mail:
    host: smtp.163.com  # 以163邮箱为例
    port: 465
    username: your-email@163.com
    password: your-authorization-code  # 注意:不是登录密码
    protocol: smtps
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
          ssl:
            enable: true

三、问题记录与解决过程

3.1 问题一:端口和SSL配置错误

问题描述:第一次配置时,直接使用了SMTP的默认端口25,结果抛出连接异常。

java

// 错误日志示例
javax.mail.MessagingException: Could not connect to SMTP host: smtp.163.com, port: 25;

解决过程

  1. 查阅邮箱服务商文档,发现现在大多数邮箱都要求使用SSL加密连接

  2. 将端口改为465或587

  3. 启用SSL配置

yaml

# 修正后的配置
spring:
  mail:
    host: smtp.163.com
    port: 465
    protocol: smtps  # 注意这里是smtps,不是smtp
    properties:
      mail:
        smtp:
          ssl:
            enable: true

3.2 问题二:使用登录密码而非授权码

问题描述:配置了正确的邮箱和密码,但一直提示"535 Error: authentication failed"

java

// 错误信息
org.springframework.mail.MailAuthenticationException: Authentication failed

解决过程

  1. 登录邮箱网页版,进入设置

  2. 找到"POP3/SMTP/IMAP"服务

  3. 开启SMTP服务,获取授权码(不是登录密码!)

  4. 使用授权码作为配置中的password

图示:获取授权码步骤

text

邮箱设置 → 客户端授权密码 → 开启服务 → 获取授权码

3.3 问题三:附件名称乱码

问题描述:发送的附件在接收端显示为乱码文件名

解决方案

java

@Component
public class MailService {
    
    public void sendMailWithAttachment(String to, String subject, String content, 
                                       MultipartFile file) throws MessagingException {
        
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
        
        // 设置邮件基本信息
        helper.setFrom(from);
        helper.setTo(to);
        helper.setSubject(subject);
        helper.setText(content, true);
        
        // 解决附件名乱码问题
        String fileName = new String(file.getOriginalFilename().getBytes(StandardCharsets.UTF_8), 
                                    StandardCharsets.ISO_8859_1);
        
        helper.addAttachment(fileName, file);
        
        mailSender.send(message);
    }
}

3.4 问题四:HTML内容显示不正确

问题描述:发送的HTML邮件在某些客户端显示为纯文本

解决方案

java

public void sendHtmlMail(String to, String subject, String content) {
    try {
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        
        helper.setFrom(from);
        helper.setTo(to);
        helper.setSubject(subject);
        
        // 关键:第二个参数设置为true,表示是HTML内容
        helper.setText(content, true);
        
        mailSender.send(message);
    } catch (MessagingException e) {
        throw new RuntimeException("发送邮件失败", e);
    }
}

3.5 问题五:异步发送导致的事务问题

问题描述:在事务中发送邮件,如果邮件发送失败,会影响主业务流程

解决方案:使用异步发送

java

@Service
public class UserService {
    
    @Autowired
    private MailService mailService;
    
    @Autowired
    private AsyncTaskExecutor taskExecutor;
    
    @Transactional
    public void register(User user) {
        // 保存用户到数据库
        userRepository.save(user);
        
        // 异步发送邮件,不影响主流程
        taskExecutor.execute(() -> {
            try {
                mailService.sendWelcomeMail(user.getEmail());
            } catch (Exception e) {
                // 记录日志,但不影响用户注册
                log.error("发送欢迎邮件失败: {}", e.getMessage());
            }
        });
    }
}

四、完整的邮件发送工具类

java

@Component
@Slf4j
public class EmailUtil {
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Value("${spring.mail.username}")
    private String from;
    
    /**
     * 发送简单邮件
     */
    public void sendSimpleMail(String to, String subject, String content) {
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(from);
            message.setTo(to);
            message.setSubject(subject);
            message.setText(content);
            
            mailSender.send(message);
            log.info("简单邮件发送成功");
        } catch (Exception e) {
            log.error("发送简单邮件失败", e);
            throw new RuntimeException("邮件发送失败");
        }
    }
    
    /**
     * 发送HTML邮件
     */
    public void sendHtmlMail(String to, String subject, String content) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            
            mailSender.send(message);
            log.info("HTML邮件发送成功");
        } catch (MessagingException e) {
            log.error("发送HTML邮件失败", e);
            throw new RuntimeException("邮件发送失败");
        }
    }
    
    /**
     * 发送带附件的邮件
     */
    public void sendAttachmentMail(String to, String subject, String content, 
                                   String filePath) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            
            // 添加附件
            FileSystemResource file = new FileSystemResource(new File(filePath));
            String fileName = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
            helper.addAttachment(fileName, file);
            
            mailSender.send(message);
            log.info("带附件邮件发送成功");
        } catch (MessagingException e) {
            log.error("发送带附件邮件失败", e);
            throw new RuntimeException("邮件发送失败");
        }
    }
}

五、测试Controller

java

@RestController
@RequestMapping("/email")
@Slf4j
public class EmailController {
    
    @Autowired
    private EmailUtil emailUtil;
    
    @PostMapping("/send")
    public ResponseEntity<String> sendEmail(@RequestBody EmailRequest request) {
        try {
            emailUtil.sendHtmlMail(
                request.getTo(),
                request.getSubject(),
                request.getContent()
            );
            return ResponseEntity.ok("邮件发送成功");
        } catch (Exception e) {
            log.error("发送邮件失败", e);
            return ResponseEntity.status(500).body("邮件发送失败: " + e.getMessage());
        }
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class EmailRequest {
        private String to;
        private String subject;
        private String content;
    }
}

六、总结与最佳实践

6.1 遇到的问题总结

  1. 配置问题:端口、协议、SSL配置需要根据邮箱服务商的要求正确设置

  2. 认证问题:必须使用授权码而非登录密码

  3. 编码问题:附件名和邮件内容需要注意编码设置

  4. 异步处理:邮件发送应该异步执行,避免影响主业务流程

6.2 最佳实践建议

  1. 配置分离:将邮件配置放在单独的配置类中

java

@Configuration
public class MailConfig {
    @Bean
    public JavaMailSender javaMailSender(Environment env) {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        // 详细配置...
        return mailSender;
    }
}
  1. 模板引擎:使用Thymeleaf或FreeMarker作为邮件模板

java

@Service
public class TemplateMailService {
    @Autowired
    private TemplateEngine templateEngine;
    
    public void sendTemplateMail(String to, String templateName, 
                                 Map<String, Object> variables) {
        String content = templateEngine.process(templateName, 
                         new Context(Locale.CHINA, variables));
        // 发送邮件...
    }
}
  1. 重试机制:添加邮件发送失败的重试逻辑

java

@Retryable(value = MailException.class, maxAttempts = 3, 
           backoff = @Backoff(delay = 1000))
public void sendMailWithRetry(String to, String subject, String content) {
    // 发送邮件逻辑
}
  1. 监控与日志:记录邮件发送的成功率和性能指标

6.3 性能优化

  1. 连接池配置:配置邮件连接池避免频繁创建连接

yaml

spring:
  mail:
    properties:
      mail:
        smtp:
          connectiontimeout: 5000
          timeout: 3000
          writetimeout: 5000
  1. 批量发送:对于大量邮件,使用批量发送功能

6.4 安全性考虑

  1. 敏感信息(如授权码)应该放在配置中心或环境变量中

  2. 对邮件内容进行XSS过滤

  3. 限制发送频率,防止被用作垃圾邮件发送工具

通过本次学习,我深刻体会到Spring Boot邮件发送功能的强大和便捷,但也认识到配置细节的重要性。希望这篇记录能帮助其他开发者避免类似的"坑",更顺利地实现邮件功能。


项目结构图

text

src/main/java/
├── com.example.demo
│   ├── config
│   │   └── MailConfig.java
│   ├── controller
│   │   └── EmailController.java
│   ├── service
│   │   ├── EmailService.java
│   │   └── impl
│   │       └── EmailServiceImpl.java
│   ├── util
│   │   └── EmailUtil.java
│   └── DemoApplication.java
resources/
├── templates
│   └── email
│       └── welcome.html
└── application.yml
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐