google邮箱发送验证码

This commit is contained in:
cnc
2026-09-18 17:58:10 +08:00
parent e3b01e7b44
commit e4ef35c795
6 changed files with 289 additions and 1 deletions

View File

@@ -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 {
/**

View File

@@ -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);
}
}

View File

@@ -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>";
}
}