前言
由于项目打算在运行,采用了谷歌邮箱来发邮件给别人激活绑定,因此用到了gmail
Java发送邮件
这个没啥好说的,参考网上文档就行了,很多文章,这里放几篇参考
gmail配置
百度安全验证 , 不过imap 从 2025 年1月1号就默认开启了,所以配不配都无所谓
特殊的时gmail 不支持直接用邮箱密码来进行Java调用,比如你的邮箱密码如下
xxx.@gmail.com / 123456
直接用这个去调用去没办法访问,会报 535-5.7.8 Username and Password not accepted 错误
这个问题有两种解决方案
1 . 把安全性降低: https://myaccount.google.com/lesssecureapps 去这个地址把支持不安全的请求开关打开,但是有些账号是不支持关的,比如我的,所以我用的是第二种方式
2.1. 添加两步认证, https://myaccount.google.com/u/0/security 到这里找到两步认证,然后把它打开,需要输入备用手机号
2.2 如果你已经打开了直接来这一步即可,到 https://myaccount.google.com/apppasswords设置专用的应用密码,如下所示
应用专用密码
接着输入应用名称,然后点击生成即可,然后会生成一个16位的字符串给你,你拿这个密码去请求就可以正常发邮件了,注意空格要去掉
看它这里的设计应该是同个密码都可以使用的,但是你如果不同应用也是可以创建多个应用的,密码记得自己保存好,这里没有展示
注意这个链接是隐藏的,我找了好久才找到
扩展
发送邮件的邮箱可能有几种类型的,这里也可以考虑做个设计模式,抽象一个发送邮箱的接口出来,然后提供不同的实现,哪个发不出去的时候可以用哪个发送一下
发邮件这个东西最好用异步发送,因为时间还是比较久的
发送失败的超时时间最好也设置一下,别到时因为这个功能引起系统的崩溃,设置个5秒内发不成功就退出好了,然后打印日志一般,也可以发到告警平台里面去进行人工介入处理, 参考: Java 邮件发送超时时间问题及优化方案详解!-腾讯云开发者社区-腾讯云
设计模式
还是采用了设计模式,这里就直接贴结构图和代码,也可以做个参考
代码如下,其实并不复杂,设计模式就是这样,第一次没意义,后面第二次就有意义了
@Slf4j
@Component
public abstract class AbstractEmailSender {
public static final String GMAIL_SOURCE = "gmail_source";
public static final String TENCENT_ENTERPRISE_SOURCE = "tencent_enterprise_source";
@Resource
private MessageSourceComponent messageSourceComponent;
public void sendGmailActivationCode(String toUser, Object code) {
log.info("sendGmailActivationCode给用户发送激活码,用户为{},激活码为{}", toUser, code);
String subject = messageSourceComponent.getMessage(OverSeaErrorConst.EMAIL_SUBJECT);
String content = messageSourceComponent.getMessage(OverSeaErrorConst.EMAIL_CONTENT, new Object[]{code});
sendGmail(new EmailSendBo(toUser, subject, content));
}
public void sendGmail(EmailSendBo emailSendBo) {
String source = getSource();
EmailCoreBo emailCoreBo = getEmailCoreBo();
Properties properties = new Properties();
//设置proper信息
doSendEmailProperTits(properties);
// 设置超时参数
properties.put("mail.smtp.connectiontimeout", "5000"); // 连接超时,单位为毫秒
properties.put("mail.smtp.timeout", "5000"); // 读超时
properties.put("mail.smtp.writetimeout", "5000"); // 写超时
// 获取会话对象
Session session = Session.getInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(emailCoreBo.getUserName(), emailCoreBo.getPassword());
}
});
try {
// 创建邮件消息
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(emailCoreBo.getUserName()));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(emailSendBo.getToUser()));
message.setSubject(emailSendBo.getSubject());
message.setText(emailSendBo.getContent());
log.info("发送sendGmail邮箱数据,来源{}", source);
// 发送邮件
Transport.send(message);
log.info("发送sendGmail邮箱成功,来源{}", source);
} catch (MessagingException e) {
log.error("发送sendGmail邮箱失败,来源{}", source, e);
}
}
protected abstract void doSendEmailProperTits(Properties properties);
protected abstract EmailCoreBo getEmailCoreBo();
protected abstract String getSource();
@Slf4j
@Component
public class GmailEmailSender extends AbstractEmailSender {
//gmail邮箱相关配置-其实可以专门搞个类,但是目前先不弄了,知道这回事就行
private static final String GMAIL_HOST = "smtp.gmail.com";
private static final String GMAIL_USER = "xxx@gmail.com";
private static final String GMAIL_PORT = "587";
private static final String GMAIL_PASSWORD = "xxxxxxxxx"; //应用专用密码
@Override
public void doSendEmailProperTits(Properties properties) {
properties.put("mail.smtp.host", GMAIL_HOST);
properties.put("mail.smtp.port", GMAIL_PORT);
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.starttls.enable", "true"); // 启用 STARTTLS
}
@Override
public EmailCoreBo getEmailCoreBo() {
return new EmailCoreBo(GMAIL_USER, GMAIL_PASSWORD);
}
@Override
public String getSource() {
return AbstractEmailSender.GMAIL_SOURCE;
}
@Component
public class EmailSenderContext {
@Resource
private List<AbstractEmailSender> emailSenderList;
public AbstractEmailSender defaultEmailSender() {
return getEmailSender(AbstractEmailSender.GMAIL_SOURCE);
}
public AbstractEmailSender getEmailSender(String source) {
for (AbstractEmailSender abstractEmailSender : emailSenderList) {
if(Objects.equals(source, abstractEmailSender.getSource())) {
return abstractEmailSender;
}
}
throw new AppException(ErrorCode.SYS_ERROR.code(), "not found emailSender");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmailCoreBo {
private String userName;
private String password;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmailSendBo {
private String toUser;
private String subject;
private String content;
}
@Slf4j
@Component
public class TencentEnterpriseEmailSender extends AbstractEmailSender {
//腾讯企业邮箱邮箱相关配置
private static final String TENCENT_HOST = "smtp.exmail.qq.com";
private static final String TENCENT_PORT = "465";
private static final String TENCENT_USER = "ff@11.cn";
private static final String TENCENT_PASSWORD = "23232322"; //应用专用密码
@Override
public void doSendEmailProperTits(Properties properties) {
properties.put("mail.smtp.host", TENCENT_HOST);
properties.put("mail.smtp.port", TENCENT_PORT);
// properties.put("mail.smtp.auth", "true");
// properties.put("mail.smtp.starttls.enable", "true"); // 启用 STARTTLS
}
@Override
public EmailCoreBo getEmailCoreBo() {
return new EmailCoreBo(TENCENT_USER, TENCENT_PASSWORD);
}
@Override
public String getSource() {
return AbstractEmailSender.TENCENT_ENTERPRISE_SOURCE;
}
}
总结
其实代码反而简单,毕竟有别人整理好的,主要是gmail的二步认证是个新鲜的东西,以前很少开发海外的东西,这次就当做下简单的整理
注意
1. 本地就算开了vpn好像也过不去,会报错,可能是vpn配置问题,所以你最好有个海外的服务器看才是最准的,但是其实你是能过去的
Got bad greeting from SMTP host: smtp.gmail.com, port: 587, response: [EOF]
2. 要使用二次验证专用密码,否则直接用邮箱密码可能过不去