【Java】Hutool发送邮件功能

news2025/1/23 10:40:31

目录

  • 开通qq邮箱的stmp
  • 实战
    • pom.xml
    • application.yml
    • controller
    • service
    • 实体类
    • 辅助类

需要实现一个通过邮箱找回密码的功能

  • 正常来说,找回密码的验证码,一般来说,都是通过手机号来找回的居多,那为什么会有通过邮箱找回的方式
  • 该说不说,免费的舒服

在这里插入图片描述

  • 业务的大致流程

开通qq邮箱的stmp

在这里插入图片描述

  • 点击设置
    在这里插入图片描述
  • 点击账号
    在这里插入图片描述
  • 正常这里是开启服务,因为我已经开启过,所以显示管理服务
  • 会需要手机号发生一段话给某个指定的手机号来做验证,存在发不通的情况(建议换一个号码,指定的手机号,有两个,都试一下)
  • 到这一步,会生成一个密码。你可以理解为邮箱的密码

实战

pom.xml

  <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.6.5</version>
        </dependency>

        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.1</version>
        </dependency>

         <!--邮件-->
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.6.2</version>
        </dependency>
  • javax.mail对应的依赖需要额外导入

application.yml

#邮件配置
email:
  #邮箱账户
  emailAccount:  123@qq.com
  #邮箱密码
  emailPassword: 123
  #发件人名称
  senderName: 发送人名称
  ##发件服务器 stmp的服务协议的服务器
  sendServer: smtp.qq.com
  #发件服务器是否遵循安全协议(0,不是 1是,默认0)
  sendServerIsSSL: 1
  ##发件服务器端口(一般是若sendServerIsSSL = 1,端口则是25,若不是,则为465)
  sendPort: 465
  • 只放了关键代码,修改邮箱账号以及邮箱密码(上面开通qq邮箱时,返回的密码)改为自己的
  • 注意: 这是qq邮箱的设置版本,如果是163邮箱版本,个别配置需要微调

controller

package com.zyee.iopace.web.controller;

@Api(tags = "用户管理")
@RestController
@RequestMapping("/user")
public class UserController {
  
    @Autowired
    private UserService userService;

    @ApiOperation(value = "通过邮箱发送验证码")
    @GetMapping("/sendEmailCode")
    public ResponseResult sendEmailCode(@RequestParam String email){
        return userService.sendEmailCode(email);
    }

    @ApiOperation(value = "验证邮箱的验证码以及重置密码")
    @PostMapping("/checkEmailCode")
    public ResponseResult checkEmailCode(@RequestBody CheckEmailVo checkEmailVo){
        return userService.checkEmailCode(checkEmailVo);
    }

}

service

    /**
     * 发送邮件code
     */
    public static  final String EMAIL_CODE = "email:code:";
    /**
     * 发送邮件次数
     */
    public static  final String EMAIL_COUNT = "email:count:";
    /**
     * 验证邮箱验证码错误次数
     */
    public static  final String EMAIL_CHECK_COUNT = "email:check:count:";

  @Value("${email.emailAccount}")
    private String emailAccount;

    @Value("${email.emailPassword}")
    private String emailPassword;

    @Value("${email.senderName}")
    private String senderName;

    @Value("${email.sendServer}")
    private String sendServer;

    @Value("${email.sendServerIsSSL}")
    private Integer sendServerIsSSL;

    @Value("${email.sendPort}")
    private Integer sendPort;

 @Override
    public ResponseResult sendEmailCode(String email) {
        if(StringUtils.isEmpty(email)){
            return ResponseResult.FAILURE("邮箱不能为空!");
        }
        User user = getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, email));
        if(Objects.isNull(user)){
            return ResponseResult.FAILURE("请输入正确的邮箱!");
        }

        String key = RedisConstant.EMAIL_COUNT+user.getId();
        if(checkSendLimit(key,5)){
            return ResponseResult.FAILURE("一个小时以内只能发送5次!");
        }

        //产生6位数的随机码
        Random random = new Random();
        int code = random.nextInt(900000) + 10000;

        //发送验证码
        Boolean flag = sendEmailUtil.sendEmailMsg(emailAccount, emailPassword, senderName, sendServer, sendServerIsSSL, sendPort, email,code);
        if(flag){
            String codeKey = RedisConstant.EMAIL_CODE+user.getId();
            //验证码的有效时长为5分钟
            redisUtils.set(codeKey,code,5 * 60);
            // 增加发送次数
            incrementSendCount(key,3600L);
        }else{
            return ResponseResult.FAILURE("发送邮箱验证码失败,请检查邮箱是否合理!");
        }
        return ResponseResult.SUCCESS();
    }

    @Override
    public ResponseResult checkEmailCode(CheckEmailVo checkEmailVo) {
        if(Objects.isNull(checkEmailVo.getCode()) ||
                StringUtils.isEmpty(checkEmailVo.getEmail()) ||
                StringUtils.isEmpty(checkEmailVo.getPwd())){
           return ResponseResult.FAILURE("参数不能为空!");
        }

        //先验证验证码是否正确
        User user = getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, checkEmailVo.getEmail()));
        if(Objects.isNull(user)){
            return ResponseResult.FAILURE("请输入正确的邮箱!");
        }

        //错误次数key
        String checkKey = RedisConstant.EMAIL_CHECK_COUNT+user.getId();
        if(checkSendLimit(checkKey,3)){
            return ResponseResult.FAILURE("5分钟之内错误3次!");
        }

        String codekey = RedisConstant.EMAIL_CODE+user.getId();
        Integer code = redisUtils.getInteger(codekey);
        if(Objects.isNull(code)){
            return ResponseResult.FAILURE("验证码已失效!");
        }else{
            if(code.intValue() != checkEmailVo.getCode().intValue()){
                //错误增加次数
                incrementSendCount(checkKey,5 * 60L);
                return ResponseResult.FAILURE("验证码错误,请重新输入!");
            }

            //验证码正常修改密码
            User updateUser = new User();
            updateUser.setId(user.getId());
            updateUser.setPassword(checkEmailVo.getPwd());
            updateById(updateUser);
        }
        return ResponseResult.SUCCESS();
    }

    /**
     * 验证次数
     * @param key
     * @return
     */
    public boolean checkSendLimit(String key,Integer maxSendCount) {
        Integer sendCount = 0;
        // 最大发送次数限制
        // 获取当前发送次数
        sendCount = redisUtils.getInteger(key);
        if(Objects.isNull(sendCount)){
            sendCount = 0;
        }
        // 超过发送次数限制返回 true,否则返回 false
        return sendCount >= maxSendCount;
    }

    /**
     * 增加验证码发送次数
     * @param key
     * @param expire 过期秒数
     */
    public void incrementSendCount(String key,Long expire) {
        // 获取当前发送次数
        Object countObj = redisUtils.get(key);
        int sendCount = (countObj != null) ? (Integer) countObj : 0;
        // 增加发送次数
        sendCount++;


        if(sendCount != 1){
            expire = redisUtils.getExpire(key);
        }

        // 存储一小时内的发送次数
        redisUtils.set(key, sendCount, expire);
    }
  • 三个变量是redis的变量
  • 主要是用来控制,发送邮箱验证码时,一个用户1个小时以内只能发送5次
  • 输入验证码进行修改密码时,5分钟只能只能错3次,防止有人暴力破解

实体类

package com.zyee.iopace.web.vo.email;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailInfoVo {

    /**
     * 邮箱账户
     */
    private String emailAccount;

    /**
     * 邮箱密码
     */
    private String emailPassword;

    /**
     * 发件人名称
     */
    private String senderName;


    /**
     * 发件服务器
     */
    private String sendServer;

    /**
     * 发件服务器是否遵循安全协议(0,不是 1是,默认0)
     */
    private Integer sendServerIsSSL = 0;

    /**
     * 发件服务器端口(一般是若sendServerIsSSL = 1,端口则是25,若不是,则为465)
     */
    private Integer sendPort;

}

辅助类

package com.zyee.iopace.web.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import com.zyee.iopace.web.vo.email.EmailInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class SendEmailUtil {

    public static void main(String[] args) {
        String emailAccount = "123@qq.com";
        String emailPassword = "123";
        String senderName = "465";
        String sendServer = "smtp.qq.com";
        Integer sendServerIsSSL = 1;
        Integer sendPort = 465;
        String toAccount = "123@qq.com";
        //产生6位数的随机码
        Random random = new Random();
        int code = random.nextInt(900000) + 10000;
        SendEmailUtil sendEmailUtil = new SendEmailUtil();
        sendEmailUtil.sendEmailMsg(emailAccount, emailPassword, senderName, sendServer,sendServerIsSSL ,sendPort,toAccount,code);
    }

    /**
     * 发送邮箱消息
     * @param emailAccount
     * @param emailPassword
     * @param senderName
     * @param sendServer
     * @param sendServerIsSSL
     * @param sendPort
     * @param code
     */
    public  Boolean sendEmailMsg(String emailAccount,
                                     String emailPassword,
                                     String senderName,
                                     String sendServer,
                                     Integer sendServerIsSSL,
                                     Integer sendPort,
                                     String toAcccount,
                                 Integer code) {
        Boolean flag = false;
        try {
            EmailInfoVo info = new EmailInfoVo();
            info.setEmailAccount(emailAccount);
            info.setEmailPassword(emailPassword);
            info.setSendPort(sendPort);
            //遵循stmp的服务协议的服务器,可以百度,很多的
            info.setSendServer(sendServer);
            info.setSenderName(senderName);
            info.setSendServerIsSSL(sendServerIsSSL);
            MailAccount account = getMailAccount(info);
            String send = MailUtil.send(account, toAcccount, "【大气气候与环境观测网站】您的注册码", "您的验证码是:" + code + ",五分钟有效,请及时完成注册。若不是本人操作请忽略", false);
            log.info("发送验证码 msg={} 验证码={}",toAcccount,code);
            flag = true;
        }catch (Exception e){
            log.info("发送验证码 出现异常msg={}",e.getMessage());
        }
        return flag;
    }


    /**
     * 获取邮箱参数对象
     *
     * @param emailInfo
     * @return
     */
    private static MailAccount getMailAccount(EmailInfoVo emailInfo) {
        MailAccount account = new MailAccount();

        if (ObjectUtil.isNotEmpty(emailInfo.getSenderName())) {
            StringBuilder sb = new StringBuilder();
            sb.append(emailInfo.getSenderName())
                    .append('<')
                    .append(emailInfo.getEmailAccount())
                    .append('>');
            account.setFrom(sb.toString());
        } else {
            account.setFrom(emailInfo.getEmailAccount());
            account.setUser(emailInfo.getEmailAccount());
        }
        account.setPass(emailInfo.getEmailPassword());
        account.setHost(emailInfo.getSendServer());
        account.setPort(emailInfo.getSendPort());
        account.setAuth(true);
        account.setSocketFactoryClass("javax.net.ssl.SSLSocketFactory");
        account.setSocketFactoryFallback(true);
        account.setSocketFactoryPort(emailInfo.getSendPort());
        if (emailInfo.getSendServerIsSSL() == 0) {
            account.setSslEnable(false);
        } else {
            account.setSslEnable(true);
        }
        account.setTimeout(3000);
        return account;
    }
}

  • 直接运行main方法,调整成你的邮箱和密码
  • toAccount就是你要发送到那个邮箱
    在这里插入图片描述
    -这是成功的效果

在网上找了几个别人做的前台效果图,大家可以参考一下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1789407.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

壁纸取图小程序源码系统 可植入广告 低成本搭建 带完整的安装代码包以及搭建教程

系统概述 本壁纸取图小程序源码系统是一套基于微信小程序框架的完整解决方案&#xff0c;旨在为开发者提供一套高效、稳定、易用的壁纸资源管理与分发平台。系统采用模块化设计&#xff0c;具有良好的扩展性和可维护性&#xff0c;同时支持多种广告形式&#xff0c;为开发者提…

“JS逆向 | Python爬虫 | 动态cookie如何破~”

案例目标 目标网址:aHR0cHMlM0EvL21hdGNoLnl1YW5yZW54dWUuY29tL21hdGNoLzI= 本题目标:提取全部 5 页发布日热度的值,计算所有值的加和,并提交答案 常规 JavaScript 逆向思路 JavaScript 逆向工程通常分为以下三步: 寻找入口:逆向工程的核心在于找出加密参数的生成方式。…

【设计模式】结构型-组合模式

前言 在软件开发中&#xff0c;设计模式是一种被广泛应用的解决问题的方法论。其中&#xff0c;结构性设计模式是一类特别重要的模式&#xff0c;它们用于处理类或对象之间的组合关系&#xff0c;其中之一就是组合模式。组合模式允许客户端统一对待单个对象和对象的组合&#…

实习面试题(答案自敲)、

1、为什么要重写equals方法&#xff0c;为什么重写了equals方法后&#xff0c;就必须重写hashcode方法&#xff0c;为什么要有hashcode方法&#xff0c;你能介绍一下hashcode方法吗&#xff1f; equals方法默认是比较内存地址&#xff1b;为了实现内容比较&#xff0c;我们需要…

RF自动化框架-环境搭建

一、RobotFrame框架简介 &#xff08;1&#xff09;RobotFramework简称&#xff1a;RF框架&#xff0c;Robotframework, 采用PO设计模式(page objeck&#xff0c; 页面即对象&#xff0c;将一个实现过程分成不同层次&#xff0c;其实就是一个分层与封装的模式) &#xff08;2…

一键设置常用纸张和页面边距-Word插件-大珩助手

Word大珩助手是一款功能丰富的Office Word插件&#xff0c;旨在提高用户在处理文档时的效率。它具有多种实用的功能&#xff0c;能够帮助用户轻松修改、优化和管理Word文件&#xff0c;从而打造出专业而精美的文档。 【新功能】常用纸张和常用边距 1、一键设定符合中国人常用…

非递归实现快排排序及归并排序(尾篇)

1.快速排序&#xff08;双指针实现&#xff09; 2.非递归实现快排 3.递归实现归并排序 4.非递归实现归并排序 5.总代码 1.快速排序&#xff08;双指针实现&#xff09; 俩有个指针一前一后的排放着&#xff0c;cur先走并且去找比kye对应值小的数组值&#xff0c;一旦找到后…

我国液碱产量逐渐增长 行业集中度有望不断提升

我国液碱产量逐渐增长 行业集中度有望不断提升 液碱是由氢氧化钠&#xff08;NaOH&#xff09;、氢氧化钾&#xff08;KOH&#xff09;等化合物以及水组成的一种碱性化合物。液碱的相对分子质量为40.00&#xff0c;密度为1.318g/cm&#xff0c;在常温常压下多表现为一种无色、无…

萨科微的“Slkor”和金航标“Kinghelm”

宋仕强说&#xff0c;我国的科学研究和先进技术&#xff0c;与先进国家相比还有差距&#xff0c;这一点还体现在社会生产效率和人均GDP上面。我们只有抓住科技进步的风口如人工智能&#xff08;AI&#xff09;&#xff0c;再加上公司内部的研发和管理等环节的微创新&#xff0c…

ai怎么导出jpg?让我告诉你答案【详】

在设计和创意工作中&#xff0c;Adobe Illustrator&#xff08;AI&#xff09;是一款不可或缺的工具。然而&#xff0c;当我们将设计作品导出为JPG格式时&#xff0c;可能会遇到一些问题。ai怎么导出jpg&#xff1f;如何确保导出的JPG图片保持高质量&#xff1f;接下来&#xf…

原子阿波罗STM32F429程序的控制器改为STM32F407

以前&#xff0c;学习原子的探索者开发板&#xff0c;有STM32F407ZGT6开发板&#xff0c;现在想学习阿波罗开发板&#xff0c;但手头没有F429开发板&#xff0c;于是&#xff0c;想把STM32F429芯片替换为STM32F407芯片&#xff0c;本以为没有什么难度&#xff0c;但是替换后发下…

Mysql:通过一张表里的父子级,递归查询并且分组分级

表&#xff1a;gc_jzst_single_base 需求&#xff1a;要求返回这张表里符合条件的数据&#xff0c;且有父子级关系的&#xff0c;展示为同一组且分级&#xff0c;给后续业务调用 代码 WITH RECURSIVE t1 AS (SELECTsingle_id,old_build_single_id,single_name,bulid_code,1 A…

ArcGIS中几个好用的空间分析工具

ArcGIS是一款经典的GIS应用&#xff0c;其空间分析能力很强&#xff0c;有着丰富的空间分析工具。今天&#xff0c;我们一起来了解几个好用的空间分析工具的功用及操作。 注&#xff1a;演示版本为ArcMap10.4.1 1.方向分布&#xff08;标准差椭圆&#xff09; 路径&#xff…

三.网络编程套接字_TCP

一.序言 在上一章中&#xff0c;我们已经实现了用udp来实现网络编程&#xff0c;这一节我们用tcp来实现网络编程&#xff0c;通过对比两者编写过程的区别&#xff0c;来加深对udp,tcp的理解&#xff01; (两者其实差别不大&#xff01;有了udp的基础&#xff0c;学习起来tcp会…

MongoDB~存储引擎了解

存储引擎 存储引擎是一个数据库的核心&#xff0c;主要负责内存、磁盘里数据的管理和维护。 MongoBD的优势&#xff0c;在于其数据模型定义的灵活性、以及可拓展性。但不要忽略&#xff0c;其存储引擎也是插件式的存在&#xff0c;支持不同类型的存储引擎&#xff0c;使用不同…

C++学习笔记(22)——多态

目录 [TOC](目录) 比喻与理解1. 多态的概念2. 多态的定义及实现2.1多态的构成条件2.2 虚函数2.3虚函数的重写2.3.1 虚函数重写的两个例外&#xff1a;1. 协变(基类与派生类虚函数返回值类型不同)2. 析构函数的重写(基类与派生类析构函数的名字不同) 2.4 C11 override 和 final2…

【Git教程】(二十)外包长历史记录 — 概述及使用要求,执行过程及其实现,替代解决方案 ~

Git教程 外包长历史记录 1️⃣ 概述2️⃣ 使用要求3️⃣ 执行过程及其实现3.1 外包项目历史3.2 链接到当前活动版本库 Git 版本库会随着时间积累越来越大&#xff0c;会影响它的内存管理效率。通常在版本库中只有源 代码文件情况下&#xff0c;这点效率影响可以忽略不计。在现…

新火种AI|倒反天罡!美国名校斯坦福AI团队抄袭中国大模型

作者&#xff1a;一号 编辑&#xff1a;美美 中国大模型被抄袭&#xff0c;怎么不算是某种层面上的国际认可呢&#xff1f; 5月29日&#xff0c;斯坦福大学的一个AI研究团队发布了一个名为「Llama3V」的模型&#xff0c;号称只要 500 美元就能训练出一个 SOTA 多模态模型&am…

精酿啤酒新风尚,FENDI CLUB盛宴启幕,品质生活触手可及

随着现代人对生活品质的追求日益提升&#xff0c;精酿啤酒作为一种新兴的生活方式&#xff0c;正逐渐引领潮流。在这个背景下&#xff0c;FENDI CLUB的盛宴盛大开启&#xff0c;为广大消费者带来了一场别具一格的品质生活体验。 一、精酿啤酒的崛起 精酿啤酒以其独特的口感、…

手机卡不缴纳违约金就不给注销?实用的处理方法大全!

我手机卡都不用了&#xff0c;为何不能注销&#xff1f;而且要缴纳违约金&#xff1f;简直是无法无天&#xff01;小编在回复粉丝问题的时候&#xff0c;经常遇到这种情况&#xff0c;现在就给大家系统整理下如何处理这个问题&#xff0c;希望能帮助到大家&#xff01; 在处理不…