最近项目准备进入测试阶段,时间相对充沛些,便对邮箱的信息发送记录下!
邮箱设置-开启smtp协议及获取授权码
以QQ邮箱为例,其他邮箱大同小异!
开启协议
获取授权码
具体代码
基于javax.mail实现
原文可看 前辈帖子
pom.xml
<!-- 邮件 -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version >1.5.4 </version >
</dependency>
EmailVO.java类(前端上送参数)
package com.example.demo.vo;
import lombok.Data;
import java.util.List;
/**
* @Auther: lr
* @Date: 2024/6/12 16:14
* @Description:
*/
@Data
public class EmailVO {
private String content; //正文
private String subject; //主题
private List<String> toEmailPerson; //收件人
private List<String> ccMail; //抄送人
private List<String> bccMail; //密送人
private List<String> fileNames; //附件
}
EmailPropertiesConstant常量信息(一般会在系统设置中配置)
package com.example.demo.constant;
import lombok.Data;
/**
* @Auther: lr
* @Date: 2024/6/12 17:11
* @Description:
*/
@Data
public class EmailPropertiesConstant {
// 发件人smtp邮箱服务地址
private String emailSmtpHost = "smtp.qq.com";
// 发件人smtp端口
private String emailSmtpPort = "25";
// 开启TLS加密
private String emailSmtpIsneedssl = "1";
// 开启验证
private String emailSmtpIsneedauth = "1";
// 发件人邮箱地址
private String userName = "xxx@qq.com";
// smtp邮箱授权码
private String userPassword = "xxx";
// 发件人昵称
private String userNicky = "测试人";
// 邮箱开关状态 0关闭 1开启
private String status = "1";
}
EmailUtil类(发送邮件)
package com.example.demo.util;
import com.example.demo.constant.EmailPropertiesConstant;
import com.example.demo.vo.EmailVO;
import com.sun.mail.util.MailSSLSocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.util.CollectionUtils;
import javax.activation.FileDataSource;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;
/**
* @Auther: lr
* @Date: 2024/6/13 10:38
* @Description:
*/
public class EmailUtil {
private static final Logger logger = LoggerFactory.getLogger(EmailUtil.class);
/**
* 发送邮件
* @param emailPropertiesConstant 系统信息配置
* @param emailVO 邮件信息
* @return
*/
public static HashMap<String, Object> sendEmail(EmailPropertiesConstant emailPropertiesConstant,
EmailVO emailVO) {
HashMap<String, Object> resultMap = new HashMap<>();
if ("0".equals(emailPropertiesConstant.getStatus())) {
logger.error("邮箱未开启");
resultMap.put("code", 500);
resultMap.put("message", "邮箱未开启!");
return resultMap;
}
if (CollectionUtils.isEmpty(emailVO.getToEmailPerson())) {
logger.error("邮件收件人为空");
resultMap.put("code", 500);
resultMap.put("message", "邮件收件人为空!");
return resultMap;
}
try {
// 设置参数
Properties properties = System.getProperties();
// smtp服务地址
properties.put("mail.smtp.host", emailPropertiesConstant.getEmailSmtpHost());
// smtp服务端口
properties.put("mail.smtp.port", emailPropertiesConstant.getEmailSmtpPort());
// 开启验证
properties.put("mail.smtp.auth", "1".equals(emailPropertiesConstant.getEmailSmtpIsneedauth()) ? "true" : "false");
// 开启TLS加密
properties.put("mail.smtp.starttls.enable", "1".equals(emailPropertiesConstant.getEmailSmtpIsneedssl()) ? "true" : "false");
// 是否启用socketFactory,默认为true
properties.put("mail.smtp.socketFactory.fallback", "true");
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
properties.put("mail.smtp.ssl.enable", "true");
properties.put("mail.smtp.ssl.socketFactory", sf);
// 建立会话,利用内部类将邮箱授权给jvm
Session session = Session.getDefaultInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(emailPropertiesConstant.getUserName(), emailPropertiesConstant.getUserPassword());
}
});
// 设置为true可以在控制台打印发送过程,生产环境关闭
session.setDebug(true);
// 创建邮件对象
MimeMessage message = new MimeMessage(session);
// 通过MimeMessageHelper设置正文和附件,否则会导致两者显示不全
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
//设置发件人 to为收件人,cc为抄送,bcc为密送
helper.setFrom(new InternetAddress(emailPropertiesConstant.getUserName(), emailPropertiesConstant.getUserNicky()));
helper.setTo(InternetAddress.parse(String.join(",", emailVO.getToEmailPerson()), false));
if (!CollectionUtils.isEmpty(emailVO.getCcMail())) {
helper.setCc(InternetAddress.parse(String.join(",", emailVO.getCcMail()), false));
}
if (!CollectionUtils.isEmpty(emailVO.getBccMail())) {
helper.setBcc(InternetAddress.parse(String.join(",", emailVO.getBccMail()), false));
}
// 设置邮件主题
helper.setSubject(emailVO.getSubject());
//设置邮件正文内容
helper.setText(emailVO.getContent());
//设置发送的日期
helper.setSentDate(new Date());
// 设置附件(注意这里的fileName必须是服务器本地文件名,不能是远程文件链接)
if (!CollectionUtils.isEmpty(emailVO.getFileNames())) {
for (String fileName : emailVO.getFileNames()) {
FileDataSource fileDataSource = new FileDataSource(fileName);
helper.addAttachment(fileDataSource.getName(), fileDataSource);
}
}
//调用Transport的send方法去发送邮件
Transport.send(message);
logger.info("发送邮件成功****************************!");
resultMap.put("code", 200);
resultMap.put("message", "邮件发送成功!");
return resultMap;
} catch (Exception e) {
e.printStackTrace();
logger.error("邮件发送失败****************************!", e);
resultMap.put("code", 500);
resultMap.put("message", "邮件发送失败");
resultMap.put("data", e);
return resultMap;
}
}
}
EmailController类
package com.example.demo.controller;
import com.example.demo.constant.EmailPropertiesConstant;
import com.example.demo.util.EmailUtil;
import com.example.demo.vo.EmailVO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* @Auther: lr
* @Date: 2024/6/12 10:30
* @Description:
*/
@RestController
@RequestMapping("/email")
public class EmailController {
@PostMapping("/testEmail")
public HashMap<String, Object> testEmail(@RequestBody EmailVO emailVO) {
HashMap<String, Object> hashMap = EmailUtil.sendEmail(new EmailPropertiesConstant(), emailVO);
return hashMap;
}
}
测试
注意点
mail.smtp.auth,默认是true。一般用来测试QQ邮箱需要开启认证,而当时项目需求是客户方用内网连接邮箱服务器,此时不需要账号密码验证,所以设置为mail.smtp.auth=fale即可。但是当时生产上同时设置了mail.smtp.starttls.enable=false,未出现使用异常,到目前还是比较疑惑这个参数的设置。
发送邮件时 需要有网!!! 局域网也可。
org.springframework.mail.MailSendException: Mail server connection failed; nested exception is javax.mail.MessagingException: Exception reading response;
nested exception is:
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?. Failed messages: javax.mail.MessagingException: Exception reading response;
nested exception is:
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?解决办法:mail.smtp.socketFactory.fallback=true
com.sun.mail.smtp.SMTPSendFailedException: 503 Error: need EHLO and AUTH first !
解决办法:mail.smtp.auth=true
基于spring-boot-starter-mail实现
系统参数是配置到application.yml文件中的,不利于系统维护邮件配置信息!!!
pom.xml
<!-- 邮件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
application.yml文件
spring:
mail:
host: smtp.qq.com #发件人smtp邮箱服务地址
username: xxxx@qq.com #发件人邮箱地址
password: xxx #smtp邮箱授权码
default-encoding: UTF-8
properties:
userNicky: test #发件人昵称
status: 1 #邮箱开关状态 0关闭 1开启
mail:
smtp:
auth: true #开启加密
ssl:
enable: true
starttls:
enable: true #开启TLS验证
socketFactory:
fallback: true
debug: true
port: 25 #发件人smtp端口
EmailUtil文件
package com.example.demo.util;
import com.example.demo.vo.EmailVO;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.activation.FileDataSource;
import javax.mail.internet.MimeMessage;
import java.util.HashMap;
/**
* @Auther: lr
* @Date: 2024/6/14 10:14
* @Description:
*/
@Component
@AllArgsConstructor
public class EmailUtil {
private static final Logger logger = LoggerFactory.getLogger(EmailUtil.class);
@Autowired
JavaMailSender javaMailSender;
@Autowired
MailProperties mailProperties;
/**
* 发送邮件
* @param emailVO 邮件信息
* @return
*/
public HashMap<String, Object> sendEmail(EmailVO emailVO){
HashMap<String, Object> resultMap = new HashMap<>();
if ("0".equals(mailProperties.getProperties().get("status"))) {
logger.error("邮箱未开启");
resultMap.put("code", 500);
resultMap.put("message", "邮箱未开启!");
return resultMap;
}
if (CollectionUtils.isEmpty(emailVO.getToEmailPerson())) {
logger.error("邮件收件人为空");
resultMap.put("code", 500);
resultMap.put("message", "邮件收件人为空!");
return resultMap;
}
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(mailProperties.getUsername(), mailProperties.getProperties().get("userNicky"));
helper.setTo(String.join(",", emailVO.getToEmailPerson()));
if (!CollectionUtils.isEmpty(emailVO.getCcMail())) {
helper.setCc(String.join(",", emailVO.getCcMail()));
}
if (!CollectionUtils.isEmpty(emailVO.getBccMail())) {
helper.setBcc(String.join(",", emailVO.getBccMail()));
}
helper.setSubject(emailVO.getSubject());
helper.setText(emailVO.getContent());
// 设置附件(注意这里的fileName必须是服务器本地文件名,不能是远程文件链接)
if (!CollectionUtils.isEmpty(emailVO.getFileNames())) {
for (String fileName : emailVO.getFileNames()) {
FileDataSource fileDataSource = new FileDataSource(fileName);
helper.addAttachment(fileDataSource.getName(), fileDataSource);
}
}
javaMailSender.send(message);
logger.info("发送邮件成功****************************!");
resultMap.put("code", 200);
resultMap.put("message", "邮件发送成功!");
return resultMap;
}catch (Exception e) {
e.printStackTrace();
logger.error("邮件发送失败****************************!", e);
resultMap.put("code", 500);
resultMap.put("message", "邮件发送失败");
resultMap.put("data", e);
return resultMap;
}
}
}
controller
package com.example.demo.controller;
import com.example.demo.constant.EmailPropertiesConstant;
import com.example.demo.util.EmailUtil;
import com.example.demo.vo.EmailVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* @Auther: lr
* @Date: 2024/6/12 10:30
* @Description:
*/
@RestController
@RequestMapping("/email")
public class EmailController {
@Autowired
EmailUtil emailUtil;
@PostMapping("/test")
public HashMap<String, Object> test(@RequestBody EmailVO emailVO) {
HashMap<String, Object> hashMap = emailUtil.sendEmail(emailVO);
return hashMap;
}
}
测试
目前接触到的项目使用过这两种方式,简单记录下!
伪代码-简单记录逻辑(代码不通,介意勿看!)
为了方便查看邮件的进度情况,一般会新建一张消息通知表记录。
遍历要发送邮件的用户,对常见情况进行判断
1.系统邮箱开关未开启
2.邮件发送过于频繁
3.邮箱格式不正确
4.用户邮箱已存在出现重复
5.个人邮箱开关未开启
6.不在邮箱白名单内
7.用户邮箱为空
消息情况的处理逻辑
/**
* @param mailSubject 主题
* @param content 内容
* @param mailAffixDir 附件地址
* @param userIdList 发送用户集合list
* @return
*/
public Map<String, Object> sendSMSMessage(String mailSubject,String content,List<String> mailAffixDir,
String attachment,List<Integer> userIdList) {
//1.获取系统邮箱配置信息
EmailPropertiesConstant propertiesConstant=new EmailPropertiesConstant();
//根据用户ID集合查询用户信息
List<UserInfo> userInfoList=new ArrayList<>();
//用户邮箱集合
Set<String> setEmail = new HashSet<>();
//消息集合
List<Notice> noticeList = new ArrayList<>();
if("1".equals(propertiesConstant.getStatus())){
//2.遍历用户对常见情况进行判断
for(UserInfo userInfo:userInfoList) {
//消息对象
Notice notice = new Notice();
notice.setuserid(userInfo.getid());
notice.setsendstatus("1"); //1 进行中 2成功 3失败
//若用户的邮箱非空及个人邮箱开关开着及在白名单内
if (userInfo.getEmail() != null && "1".equals(userInfo.getEmailStatus()) && "1".equals(userInfo.getRecvEmail())) {
if (setEmail.contains(userInfo.getEmail())) {
//用户邮箱已存在出现重复 TODO
notice.seterrorInfo("用户邮箱已存在出现重复!");
notice.setsendstatus("3");
noticeMapper.addNotice(notice);
continue;
}
//判断用户的发送频率
boolean ifSend = this.isSendTime(content,userInfo.getId(), taskNoticeType);
if (ifSend) {
// 正则验证邮箱
if (RegularUtil.emailRegular(userUtil.getEmail()) == true) {
setEmail.add(userUtil.getEmail());
noticeList.add(notice);
} else {
//邮箱格式不正确! TODO
notice.seterrorInfo("邮箱格式不正确!");
notice.setsendstatus("3");
}
} else {
//邮件发送过于频繁 TODO
notice.seterrorInfo("邮件发送过于频繁!");
notice.setsendstatus("3");
}
}else if (!"1".equals(userUtil.getIsOpenEmail())) {
//个人邮箱开关未开启! TODO
notice.seterrorInfo("个人邮箱开关未开启!");
notice.setsendstatus("3");
}else if (!"1".equals(userInfo.getRecvEmail())){
//不在邮箱白名单内!TODO
notice.seterrorInfo("不在邮箱白名单内!");
notice.setsendstatus("3");
}else if (StringUtil.isEmpty(userInfo.getEmail())) {
//用户邮箱为空! TODO
notice.seterrorInfo("用户邮箱为空!");
notice.setsendstatus("3");
}
//消息通知入库 TODO
noticeMapper.add(notice);
}
}else{
//系统邮箱未开启 TODO
}
//3.整理发送邮件内容 TODO
propertiesConstant.setMailSubject(mailSubject);
propertiesConstant.setMailContent(content);
propertiesConstant.setMailAffixDir(attachment);
String emailPersons=String.join(",",setEmail);
propertiesConstant.setMailTo(emailPersons);
if(propertiesConstant!=null&&propertiesConstant.getMailTo()!=null&&!"".equals(propertiesConstant.getMailTo())) {
//4.异步发送邮件 TODO
emailWriteTxt(propertiesConstant, noticeList,3);
}
}
异步发送邮件(异步方法不可与主方法在同一个类中)及重试
详情可见 @Async注解的坑
@Override
@Async("asyncServiceEmail")
public void emailWriteTxt(EmailPropertiesConstant propertiesConstant, List<Notice> noticeList,int retryTimes) {
logger.info("线程(Email)-" + Thread.currentThread().getId() + "在执行写入");
if(propertiesConstant!=null){
//发送邮件 TODO
HashMap<String,Object> resultMap=sendEmailUtil.sendEmail();
//邮件发送成功
if("200".equals(resultMap.get("code"))){
logger.info("邮箱:" + propertiesConstant.getMailTo() + "发送成功!");
// 遍历消息集合 对状态为非失败的消息 根据消息id 修改状态为成功 TODO
}else{
//邮件发送失败后重试 TODO
retryTimes(propertiesConstant,noticeList,retryTimes);
}
}
}
//重试
public boolean retryTimes(EmailPropertiesConstant propertiesConstant, List<Notice> noticeList,int retryTimes) {
String result = "";
int i = 1;
HashMap<String,Object> resultMap = new HashMap<>();
// 如果失败 则进行重试
while (i <= retryTimes) {
//发送邮件 TODO
resultMap=sendEmailUtil.sendEmail();
//邮件发送成功
if("200".equals(resultMap.get("code"))){
// 遍历消息集合 对状态为非失败的消息 根据消息id 修改状态为成功 TODO
return true;
}
result =resultMap.get("data").toString();
logger.error("邮箱号:" + propertiesConstant.getMailTo() + "第" + i + "次重试……失败……"+result);
i++;
}
// 遍历消息集合 对状态为非失败的消息 根据消息id 修改状态为失败 TODO
for (Notice notice : noticeList) {
if(!notice.getsendstatus().equals("2")){
noticeMapper.update(notice.getId(),"3","邮箱服务器异常!"+result,3);
}
}
return false;
}