簡(jiǎn)單實(shí)現(xiàn)發(fā)送郵件的小功能
1.準(zhǔn)備
需要一個(gè)郵箱,并開啟POP3/IMAP/SMTP服務(wù)。
以QQ郵箱為例,郵箱設(shè)置-賬戶,就能開啟對(duì)應(yīng)的服務(wù),并獲取授權(quán)碼。
開啟服務(wù),獲取郵件授權(quán)
2.引入依賴
<!-- 版本由springboot管理 -->
<!-- 郵件依賴 (必要) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- redis (非必要)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT (非必要) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
3.在application.yml配置文件中,配置相應(yīng)的參數(shù)
spring:
mail:
host: smtp.qq.com #QQ的發(fā)送郵件服務(wù)器
username: 郵箱
password: 授權(quán)碼
4.編寫一個(gè)工具類
@Data
@Slf4j
@Component
public class MailUtil {
//注入郵件服務(wù)
@Autowired
private JavaMailSenderImpl mailSender;
//誰來發(fā)
@Value("${spring.mail.username}")
private String from;
/**
* 發(fā)送簡(jiǎn)單郵件
* @param email 發(fā)給誰
* @param subject 郵件主題
* @param text 郵件內(nèi)容
*/
public void sendSimpleMail(String email,String subject,String text){
try {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom(from);
simpleMailMessage.setTo(email);
simpleMailMessage.setSubject(subject);
simpleMailMessage.setText(text);
mailSender.send(simpleMailMessage);
}catch (Exception e){
log.error("發(fā)送失??!",e);
throw new RuntimeException(e);
}
}
}
5.編寫業(yè)務(wù)方法,以發(fā)送驗(yàn)證碼為例
@Async("myExecutor")
public void sendCodeMail(String email) {
//六位隨機(jī)數(shù)的驗(yàn)證碼
String code = CodeUtil.randomCode(6);
//郵件主題
String subject = "驗(yàn)證碼";
//郵件內(nèi)容
String text = "驗(yàn)證碼:" + code +",10分鐘之內(nèi)有效。";
Map<String,Object> map = new HashMap<>(2);
map.put("code",code);
map.put("createTime",System.currentTimeMillis());
//將驗(yàn)證碼緩存進(jìn)redis中,并設(shè)置過期時(shí)間,方便后續(xù)做校驗(yàn)
redisTemplate.opsForHash().putAll(ConstantUtil.USER_MAIL_CODE + email,map);
redisTemplate.expire(ConstantUtil.USER_MAIL_CODE + email,10,TimeUnit.MINUTES);
mailUtil.sendSimpleMail(email,subject,text);
}
/**
* 說明:由于發(fā)送郵件,可能會(huì)比較耗時(shí)間,所以需要異步調(diào)用。
* 這里的處理方式是,配置一個(gè)線程池,通過@Async("myExecutor"),另啟線程調(diào)用。
* 偷懶的話,可以在啟動(dòng)類添加注解@EnableAsync,再在方法上用注解@Async,也可以開啟異步調(diào)用。
*/
/**
* 限制一分鐘內(nèi)只能發(fā)一條,避免惡意攻擊
*/
public boolean checkSendToLegal(String email) {
//利用正則表達(dá)式,驗(yàn)證郵箱是否符合郵箱的格式
if(!email.matches("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$")){
return true;
}
Long now = System.currentTimeMillis();
Long codeCreateTime =(Long)redisTemplate.opsForHash().get(ConstantUtil.USER_MAIL_CODE + email, "createTime");
return codeCreateTime != null && now - codeCreateTime < 60000;
}
6.編寫測(cè)試方法
@Test
public void sendCodeMail(){
//發(fā)給誰
String email = "";
//限制一分鐘內(nèi)只能發(fā)一條,避免惡意攻擊
if(checkSendToLegal(email)){
log.error("操作過于頻繁,請(qǐng)一分鐘后再試!");
}
//發(fā)送
sendCodeMail(email);
}
效果截圖:
[圖片上傳失敗...(image-20a105-1634463311376)]
7.發(fā)送復(fù)雜郵件,以給郵箱發(fā)送激活鏈接為例
- 在工具類中添加如下方法
/**
* 發(fā)送定制郵件
* @param template 模板
*/
public void sendMimeMail(MailTemplate template){
try {
//true表示支持復(fù)雜類型
MimeMessageHelper messageHelper = new MimeMessageHelper(mailSender.createMimeMessage(), true);
//郵件發(fā)信人
messageHelper.setFrom(from);
//郵件收信人
if (!ObjectUtils.isEmpty(template.getTo())) {
messageHelper.setTo(template.getTo());
}
//給誰回復(fù)
if(StringUtils.hasText(template.getReplyTo())){
messageHelper.setReplyTo(template.getReplyTo());
}
//郵件主題
if (StringUtils.hasText(template.getSubject())) {
messageHelper.setSubject(template.getSubject());
}
//郵件內(nèi)容,true時(shí),顯示html代碼效果,默認(rèn)為false
if (StringUtils.hasText(template.getText())) {
messageHelper.setText(template.getText(),template.isHtml());
}
//抄送
if (!ObjectUtils.isEmpty(template.getCc())) {
messageHelper.setCc(template.getCc());
}
//密送
if (!ObjectUtils.isEmpty(template.getBcc())) {
messageHelper.setCc(template.getBcc());
}
//添加郵件附件
if (!CollectionUtils.isEmpty(template.getMultipartFiles())) {
for (MultipartFile multipartFile : template.getMultipartFiles()) {
messageHelper.addAttachment(Objects.requireNonNull(multipartFile.getOriginalFilename()), multipartFile);
}
}
//發(fā)送時(shí)間
if (!ObjectUtils.isEmpty(template.getSentDate())) {
template.setSentDate(new Date());
messageHelper.setSentDate(template.getSentDate());
}
//正式發(fā)送郵件
mailSender.send(messageHelper.getMimeMessage());
template.setStatus("1");
} catch (Exception e) {
log.error("發(fā)送失敗!",e);
throw new RuntimeException(e);
}
}
/**
* 郵件模板
*/
@Data
class MailTemplate {
//發(fā)送人
private String from;
//接受人
private String[] to;
//回復(fù)給誰
private String replyTo;
//抄送
private String[] cc;
//密送
private String[] bcc;
//發(fā)送時(shí)間
private Date sentDate;
//主題
private String subject;
//郵件內(nèi)容
private String text;
//郵件附件
List<MultipartFile> multipartFiles;
//是否顯示html代碼效果
private boolean html;
private String status;
public void setTo(String to) {
this.to = new String[]{to};
}
public void setTo(String... to) {
this.to = to;
}
public String[] getTo() {
return this.to;
}
public void setCc(String cc) {
this.cc = new String[]{cc};
}
public void setCc(String... cc) {
this.cc = cc;
}
public String[] getCc() {
return this.cc;
}
public void setBcc(String bcc) {
this.bcc = new String[]{bcc};
}
public void setBcc(String... bcc) {
this.bcc = bcc;
}
public String[] getBcc() {
return this.bcc;
}
}
- 添加一個(gè)發(fā)送郵件的業(yè)務(wù)方法
@Async("myExecutor")
public void sendLinkMail(String email) {
//用email生產(chǎn)token串
String token = JWTUtil.createToken(email);
String subject = "賬戶激活";
String text = "<!DOCTYPE html><html><head></head>" +
"<body>" +
"<h1>點(diǎn)擊下面的按鈕即可激活賬戶</h1>\n" +
"<h3><a href='http://localhost:8080/test/verify_email?token="+token+"'>立即激活</a></h3>" +
"</body>" +
"</html>";
MailTemplate mailTemplate = new MailTemplate();
mailTemplate.setTo(email);
mailTemplate.setSentDate(new Date());
mailTemplate.setSubject(subject);
mailTemplate.setText(text);
mailTemplate.setHtml(true);
//緩存token信息,email作為key
redisTemplate.opsForValue().set(ConstantUtil.USER_MAIL_LINK + email,token,7,TimeUnit.DAYS);
mailUtil.sendMimeMail(mailTemplate);
}
/**
* 根據(jù)token獲取email信息,校驗(yàn)緩存中是否存在
*/
public boolean verifyLink(String token) {
String email = JWTUtil.getUsernameByToken(token);
Object o = redisTemplate.opsForValue().get(ConstantUtil.USER_MAIL_LINK + email);
return ObjectUtils.isEmpty(o);
}
/**
* 簡(jiǎn)單的token生產(chǎn)工具
*/
public class JWTUtil {
private static String key = "tokenKey";
/**
* 根據(jù)用戶名生成token
* @param username
* @return
*/
public static String createToken(String username){
return Jwts.builder().setSubject(username).signWith(SignatureAlgorithm.HS512, key).compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 根據(jù)token 獲取用戶名
* @param token
* @return
*/
public static String getUsernameByToken(String token){
return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getSubject();
}
}
- 添加相關(guān)的模擬接口
/**
* 模擬 給郵箱發(fā)送激活鏈接
* @param email
* @return
*/
@GetMapping("sendLink")
public R sendLinkMail(@RequestParam String email){
mailService.sendLinkMail(email);
return R.ok("success");
}
/**
* 激活鏈接url
* @param token
* @return
*/
@GetMapping("verify_email")
public R verifyEmail(@RequestParam String token){
if(mailService.verifyLink(token)){
return R.failed("激活失敗,認(rèn)證信息已經(jīng)失效!");
}
// todo 激活賬戶相關(guān)業(yè)務(wù)
// todo 最后,刪除對(duì)應(yīng)的緩存
return R.ok("success");
}
效果截圖:
激活鏈接