springboot简单实现邮件功能

简单实现发送邮件的小功能

1.准备

需要一个邮箱,并开启POP3/IMAP/SMTP服务。
以QQ邮箱为例,邮箱设置-账户,就能开启对应的服务,并获取授权码。


开启服务,获取邮件授权
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配置文件中,配置相应的参数
spring:
  mail:
    host: smtp.qq.com #QQ的发送邮件服务器
    username: 邮箱
    password: 授权码
4.编写一个工具类
@Data
@Slf4j
@Component
public class MailUtil {

    //注入邮件服务
    @Autowired
    private JavaMailSenderImpl mailSender;
    //谁来发
    @Value("${spring.mail.username}")
    private String from;

    /**
     * 发送简单邮件
     * @param email 发给谁
     * @param subject 邮件主题
     * @param text 邮件内容
     */
    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("发送失败!",e);
            throw new RuntimeException(e);
        }
    }
}
5.编写业务方法,以发送验证码为例
@Async("myExecutor") 
public void sendCodeMail(String email) {
    //六位随机数的验证码
    String code = CodeUtil.randomCode(6);
    //邮件主题
    String subject = "验证码";
    //邮件内容
    String text = "验证码:" + code +",10分钟之内有效。";

    Map<String,Object> map = new HashMap<>(2);
    map.put("code",code);
    map.put("createTime",System.currentTimeMillis());

    //将验证码缓存进redis中,并设置过期时间,方便后续做校验
    redisTemplate.opsForHash().putAll(ConstantUtil.USER_MAIL_CODE + email,map);
    redisTemplate.expire(ConstantUtil.USER_MAIL_CODE + email,10,TimeUnit.MINUTES);

    mailUtil.sendSimpleMail(email,subject,text);
}

/**
 * 说明:由于发送邮件,可能会比较耗时间,所以需要异步调用。
 * 这里的处理方式是,配置一个线程池,通过@Async("myExecutor"),另启线程调用。
 * 偷懒的话,可以在启动类添加注解@EnableAsync,再在方法上用注解@Async,也可以开启异步调用。
 */
 
/**
 * 限制一分钟内只能发一条,避免恶意攻击
 */
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(){
    //发给谁
    String email = "";
    //限制一分钟内只能发一条,避免恶意攻击
    if(checkSendToLegal(email)){
        log.error("操作过于频繁,请一分钟后再试!");
    }
    //发送
    sendCodeMail(email);
}

效果截图:
[图片上传失败...(image-20a105-1634463311376)]

7.发送复杂邮件,以给邮箱发送激活链接为例
  • 在工具类中添加如下方法
/**
 * 发送定制邮件
 * @param template 模板
 */
public void sendMimeMail(MailTemplate template){
  try {
      //true表示支持复杂类型
      MimeMessageHelper messageHelper = new MimeMessageHelper(mailSender.createMimeMessage(), true);
      //邮件发信人
      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());
      }
      //邮件内容,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);
          }
      }
      //发送时间
      if (!ObjectUtils.isEmpty(template.getSentDate())) {
          template.setSentDate(new Date());
          messageHelper.setSentDate(template.getSentDate());
      }
      //正式发送邮件
      mailSender.send(messageHelper.getMimeMessage());
      template.setStatus("1");
  } catch (Exception e) {
      log.error("发送失败!",e);
      throw new RuntimeException(e);
  }
}

/**
 * 邮件模板
 */
@Data
class MailTemplate {

    //发送人
    private String from;
    //接受人
    private String[] to;
    //回复给谁
    private String replyTo;
    //抄送
    private String[] cc;
    //密送
    private String[] bcc;
    //发送时间
    private Date sentDate;
    //主题
    private String subject;
    //邮件内容
    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;
    }
}

  • 添加一个发送邮件的业务方法
@Async("myExecutor")
public void sendLinkMail(String email) {
    //用email生产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);
}

/**
 * 根据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生产工具
 */
public class JWTUtil {

    private static String key = "tokenKey";

    /**
     * 根据用户名生成token
     * @param username
     * @return
     */
    public static String createToken(String username){
        return Jwts.builder().setSubject(username).signWith(SignatureAlgorithm.HS512, key).compressWith(CompressionCodecs.GZIP).compact();
    }

    /**
     * 根据token 获取用户名
     * @param token
     * @return
     */
    public static String getUsernameByToken(String token){
        return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getSubject();
    }
}
  • 添加相关的模拟接口
/**
 * 模拟 给邮箱发送激活链接
 * @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("激活失败,认证信息已经失效!");
    }
    // todo 激活账户相关业务

    // todo 最后,删除对应的缓存
    
    return R.ok("success");
}

效果截图:


激活链接
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容