google邮箱发送验证码
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package org.dromara.common.mail.config;
|
||||
|
||||
import cn.hutool.extra.mail.MailAccount;
|
||||
import org.dromara.common.mail.config.properties.GmailProperties;
|
||||
import org.dromara.common.mail.config.properties.MailProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -13,7 +14,7 @@ import org.springframework.context.annotation.Bean;
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(MailProperties.class)
|
||||
@EnableConfigurationProperties({MailProperties.class, GmailProperties.class})
|
||||
public class MailConfig {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.dromara.common.mail.config.properties;
|
||||
|
||||
import cn.hutool.extra.mail.MailAccount;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Gmail 邮件服务配置属性。
|
||||
* <p>
|
||||
* 独立于框架默认的 {@link MailProperties}(prefix=mail),专用于 Gmail SMTP 发信。
|
||||
* <p>
|
||||
* <b>Gmail 口径</b>:主机固定 {@code smtp.gmail.com},端口 465 走 SSL(或 587 走 STARTTLS)。
|
||||
* Gmail 已停用「低安全性应用访问」,{@link #pass} 必须填 Google 账号生成的
|
||||
* <b>16 位应用专用密码</b>(非账号登录密码),且 {@link #from} 必须等于 {@link #user},否则会被拒收。
|
||||
*
|
||||
* @author Qoder
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "gmail")
|
||||
public class GmailProperties {
|
||||
|
||||
/**
|
||||
* 是否启用 Gmail 发信功能。
|
||||
*/
|
||||
private Boolean enabled = false;
|
||||
|
||||
/**
|
||||
* SMTP 服务器域名,Gmail 固定为 smtp.gmail.com。
|
||||
*/
|
||||
private String host = "smtp.gmail.com";
|
||||
|
||||
/**
|
||||
* SMTP 服务端口:465(SSL)或 587(STARTTLS)。
|
||||
*/
|
||||
private Integer port = 465;
|
||||
|
||||
/**
|
||||
* 是否需要用户名密码验证。
|
||||
*/
|
||||
private Boolean auth = true;
|
||||
|
||||
/**
|
||||
* 发信账号(Gmail 地址),建议由环境变量注入。
|
||||
*/
|
||||
private String user;
|
||||
|
||||
/**
|
||||
* 发信凭据:Gmail 的 16 位应用专用密码,建议由环境变量注入。
|
||||
*/
|
||||
private String pass;
|
||||
|
||||
/**
|
||||
* 发件人,留空则回落到 {@link #user}。
|
||||
*/
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* 使用 SSL 安全连接(465 端口用 true)。
|
||||
*/
|
||||
private Boolean sslEnable = true;
|
||||
|
||||
/**
|
||||
* 使用 STARTTLS 升级连接(587 端口用 true,与 {@link #sslEnable} 互斥)。
|
||||
*/
|
||||
private Boolean starttlsEnable = false;
|
||||
|
||||
/**
|
||||
* SMTP 读写超时时长,单位毫秒。
|
||||
*/
|
||||
private Long timeout = 10000L;
|
||||
|
||||
/**
|
||||
* Socket 连接超时值,单位毫秒。Gmail 国内不可达时快速失败,避免挂死请求线程。
|
||||
*/
|
||||
private Long connectionTimeout = 5000L;
|
||||
|
||||
/**
|
||||
* 转换为 Hutool 邮件账户配置。
|
||||
*
|
||||
* @return 邮件账户配置
|
||||
*/
|
||||
public MailAccount toMailAccount() {
|
||||
MailAccount account = new MailAccount();
|
||||
account.setHost(host);
|
||||
account.setPort(port);
|
||||
account.setAuth(auth);
|
||||
account.setUser(user);
|
||||
account.setPass(pass);
|
||||
account.setFrom(StringUtils.isBlank(from) ? user : from);
|
||||
account.setSocketFactoryPort(port);
|
||||
account.setStarttlsEnable(starttlsEnable);
|
||||
account.setSslEnable(sslEnable);
|
||||
account.setTimeout(timeout);
|
||||
account.setConnectionTimeout(connectionTimeout);
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已具备真实发信条件(账号与凭据均已配置)。
|
||||
*
|
||||
* @return true 已配置;false 未配置
|
||||
*/
|
||||
public boolean isConfigured() {
|
||||
return StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.dromara.common.mail.core;
|
||||
|
||||
import cn.hutool.extra.mail.MailAccount;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.mail.config.properties.GmailProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Gmail 验证码邮件发送服务。
|
||||
* <p>
|
||||
* 仅负责「用 Gmail 账户把验证码邮件发出去」,验证码的生成、限流、缓存由调用方(Controller)承担,
|
||||
* 保持本服务的单一职责,便于在各业务模块复用。
|
||||
*
|
||||
* @author Qoder
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GmailCodeService {
|
||||
|
||||
private final GmailProperties gmailProperties;
|
||||
|
||||
/**
|
||||
* 通过 Gmail 发送邮箱验证码。
|
||||
*
|
||||
* @param to 收件邮箱
|
||||
* @param code 验证码
|
||||
* @param expiryMinutes 有效期(分钟),仅用于文案展示
|
||||
*/
|
||||
public void sendVerifyCode(String to, String code, int expiryMinutes) {
|
||||
if (!Boolean.TRUE.equals(gmailProperties.getEnabled())) {
|
||||
throw new ServiceException("当前系统未开启 Gmail 邮件功能!");
|
||||
}
|
||||
if (!gmailProperties.isConfigured()) {
|
||||
throw new ServiceException("Gmail 发信账号或应用专用密码未配置!");
|
||||
}
|
||||
MailAccount account = gmailProperties.toMailAccount();
|
||||
try {
|
||||
MailBuilder.of(account)
|
||||
.to(to)
|
||||
.subject("邮箱验证码")
|
||||
.html(buildHtml(code, expiryMinutes))
|
||||
.send();
|
||||
log.info("[Gmail] 验证码邮件已发送:收件邮箱={}", to);
|
||||
} catch (Exception e) {
|
||||
// 不记录验证码本身,避免真实发信失败时把验证码写进日志
|
||||
log.error("[Gmail] 验证码邮件发送异常 to={} host={}:{}(若为连接超时,请确认服务器可访问 smtp.gmail.com)",
|
||||
to, gmailProperties.getHost(), gmailProperties.getPort(), e);
|
||||
throw new ServiceException("验证码邮件发送失败,请稍后重试!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造验证码邮件正文(纯 HTML,无外部资源,避免被邮件客户端拦截)。
|
||||
*
|
||||
* @param code 验证码
|
||||
* @param expiryMinutes 有效期(分钟)
|
||||
* @return HTML 正文
|
||||
*/
|
||||
private String buildHtml(String code, int expiryMinutes) {
|
||||
return "<div style=\"font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;"
|
||||
+ "font-size:14px;line-height:1.6;color:#2c2c2a\">"
|
||||
+ "<p>您本次操作的邮箱验证码为:</p>"
|
||||
+ "<p style=\"font-size:24px;font-weight:500;letter-spacing:4px;margin:16px 0\">" + code + "</p>"
|
||||
+ "<p>验证码 " + expiryMinutes + " 分钟内有效,请勿泄露给他人。</p>"
|
||||
+ "<p style=\"color:#5f5e5a\">若非本人操作,请忽略本邮件。</p>"
|
||||
+ "</div>";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user