簡單實現(xiàn)發(fā)送郵件的小功能
1.準備
需要一個郵箱躬翁,并開啟POP3/IMAP/SMTP服務(wù)蠢箩。
以QQ郵箱為例挨决,郵箱設(shè)置-賬戶印颤,就能開啟對應(yīng)的服務(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.編寫一個工具類
@Data
@Slf4j
@Component
public class MailUtil {
//注入郵件服務(wù)
@Autowired
private JavaMailSenderImpl mailSender;
//誰來發(fā)
@Value("${spring.mail.username}")
private String from;
/**
* 發(fā)送簡單郵件
* @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ā)送驗證碼為例
@Async("myExecutor")
public void sendCodeMail(String email) {
//六位隨機數(shù)的驗證碼
String code = CodeUtil.randomCode(6);
//郵件主題
String subject = "驗證碼";
//郵件內(nèi)容
String text = "驗證碼:" + code +"某宪,10分鐘之內(nèi)有效仿村。";
Map<String,Object> map = new HashMap<>(2);
map.put("code",code);
map.put("createTime",System.currentTimeMillis());
//將驗證碼緩存進redis中,并設(shè)置過期時間兴喂,方便后續(xù)做校驗
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ā)送郵件蔼囊,可能會比較耗時間,所以需要異步調(diào)用衣迷。
* 這里的處理方式是畏鼓,配置一個線程池,通過@Async("myExecutor")壶谒,另啟線程調(diào)用云矫。
* 偷懶的話,可以在啟動類添加注解@EnableAsync汗菜,再在方法上用注解@Async让禀,也可以開啟異步調(diào)用。
*/
/**
* 限制一分鐘內(nèi)只能發(fā)一條陨界,避免惡意攻擊
*/
public boolean checkSendToLegal(String email) {
//利用正則表達式,驗證郵箱是否符合郵箱的格式
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.編寫測試方法
@Test
public void sendCodeMail(){
//發(fā)給誰
String email = "";
//限制一分鐘內(nèi)只能發(fā)一條巡揍,避免惡意攻擊
if(checkSendToLegal(email)){
log.error("操作過于頻繁,請一分鐘后再試菌瘪!");
}
//發(fā)送
sendCodeMail(email);
}
效果截圖:
[圖片上傳失敗...(image-20a105-1634463311376)]
7.發(fā)送復雜郵件腮敌,以給郵箱發(fā)送激活鏈接為例
- 在工具類中添加如下方法
/**
* 發(fā)送定制郵件
* @param template 模板
*/
public void sendMimeMail(MailTemplate template){
try {
//true表示支持復雜類型
MimeMessageHelper messageHelper = new MimeMessageHelper(mailSender.createMimeMessage(), true);
//郵件發(fā)信人
messageHelper.setFrom(from);
//郵件收信人
if (!ObjectUtils.isEmpty(template.getTo())) {
messageHelper.setTo(template.getTo());
}
//給誰回復
if(StringUtils.hasText(template.getReplyTo())){
messageHelper.setReplyTo(template.getReplyTo());
}
//郵件主題
if (StringUtils.hasText(template.getSubject())) {
messageHelper.setSubject(template.getSubject());
}
//郵件內(nèi)容,true時,顯示html代碼效果,默認為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ā)送時間
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;
//回復給誰
private String replyTo;
//抄送
private String[] cc;
//密送
private String[] bcc;
//發(fā)送時間
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;
}
}
- 添加一個發(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>點擊下面的按鈕即可激活賬戶</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信息弊添,校驗緩存中是否存在
*/
public boolean verifyLink(String token) {
String email = JWTUtil.getUsernameByToken(token);
Object o = redisTemplate.opsForValue().get(ConstantUtil.USER_MAIL_LINK + email);
return ObjectUtils.isEmpty(o);
}
/**
* 簡單的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("激活失敗,認證信息已經(jīng)失效捌木!");
}
// todo 激活賬戶相關(guān)業(yè)務(wù)
// todo 最后油坝,刪除對應(yīng)的緩存
return R.ok("success");
}
效果截圖: