前置设置

  • 存储邮箱以及验证码需要提前配置好redis

maven中加入redis配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml中加入redis的配置

spring:
  data:
    redis:
      host: localhost  # Redis 服务器地址
      port: 6379       # Redis 端口号
      password: ''     # Redis 密码(如果未设置密码,可留空)
      lettuce:
        pool:
          max-active: 8       # 最大连接数
          max-idle: 8         # 最大空闲连接数
          min-idle: 0         # 最小空闲连接数

RedisCache

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

/**
 * @Title: RedisCache
 * @Author David
 * @Package com.work.spring.springbootwork.config
 * @Date 2024/9/11 下午1:34
 */

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }
}

SpringBoot内置包解决

application.yml中加入mail的配置

spring:
  mail:
    host: smtp.163.com # 你邮箱的smtp地址
    username: xxxxxxxx@163.com # 申请了smtp的邮箱
    password: xxxxxxxxxxx # SMTP服务的秘钥
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true

maven中加入mail配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

创建测试服务类MailServiceImp

概述:

  • generateSixDigitCode 用于生成验证码。
  • checkEmail 检查邮箱是否存在。
  • sendMail 用于向用户发送包含验证码的邮件。
  • resetPassword 处理用户重置密码的逻辑。
  • updateEmailList 是一个定时任务,用于每小时更新 Redis 中的学生邮箱数据。

1. generateSixDigitCode 方法

  • 功能:生成一个六位的随机数字验证码。
  • 说明:该方法使用随机数生成器,生成一个包含六位数字的验证码。这个验证码通常用于身份验证,如重置密码时发送给用户。

2. checkEmail 方法

  • 功能:检查给定的邮箱是否存在于 Redis 缓存中。
  • 说明:该方法从 Redis 中获取存储的学生邮箱列表,并遍历它以检查给定邮箱是否在列表中。如果找到了对应的邮箱,则返回 true,否则返回 false。这确保了只有系统中存在的邮箱才可以发送验证码。

3. sendMail 方法

  • 功能:向指定邮箱发送验证码邮件。
  • 说明:首先,调用 checkEmail 验证邮箱是否存在。如果邮箱存在,生成验证码并通过邮件发送给用户。邮件内容包含验证码及其有效期(通常为 15 分钟)。邮件发送后,验证码会存入 Redis,并设置过期时间为 15 分钟。这确保了验证码的时效性。

4. resetPassword 方法

  • 功能:根据用户输入的验证码和邮箱重置密码。
  • 说明:该方法首先检查验证码是否已过期(通过检查 Redis 中是否有与该邮箱关联的验证码)。如果验证码有效,且与用户输入的验证码匹配,则认为用户身份验证成功,可以进行密码重置操作。操作成功后,系统会删除 Redis 中的验证码记录,以确保每个验证码只能使用一次。

5. updateEmailList 方法

  • 功能:定时从数据库中获取学生邮箱列表并将其缓存到 Redis 中。
  • 说明:这是一个定时任务,每隔一小时从数据库中获取学生邮箱列表,并将其存储到 Redis 中。这样,系统可以确保每次发送验证码时都能使用最新的邮箱数据。同时,Redis 中的邮箱列表过期时间被设置为两小时,以保证数据的及时性并避免可能的缓存过期问题。
import com.work.spring.springbootwork.config.RedisCache;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Title: MailServiceImp
 * @Author David
 * @Package com.work.spring.springbootwork.service
 * @Date 2024/9/11 下午4:40
 */

@Service
public class MailServiceImp {

    @Resource
    private JavaMailSender mailSender;

    @Value("${spring.mail.username}")
    private String from;

    @Resource
    private RedisCache redisCache;

    // 生成六位数字验证码
    public String generateSixDigitCode() {
        String digits = "0123456789";
        StringBuilder code = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            code.append(digits.charAt(random.nextInt(digits.length())));
        }
        return code.toString();
    }

    // 检查邮箱是否在 Redis 中
    public boolean checkEmail(String email) {
        String[] studentEmailSet = redisCache.getCacheObject("Student_Email");
        for (Object str : studentEmailSet) {
            if (str.equals(email)) {
                return true;
            }
        }
        return false;
    }

    // 发送验证码邮件
    public String sendMail(String email) {

        if (!checkEmail(email)) {
            return "邮箱不存在";
        }

        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject("【验证码】");
        String code = generateSixDigitCode();

        // 邮件内容
        message.setText("您好,\n\n您正在尝试重置密码,您的验证码是:" + code +
                "。\n验证码有效期为15分钟,请及时使用。\n\n如果非本人操作,请忽略此邮件,感谢您的理解!");

        message.setTo(email);
        message.setFrom(from);

        // 发送邮件
        mailSender.send(message);

        // 将验证码存入Redis,设置15分钟过期
        redisCache.setCacheObject(email, code, 15, TimeUnit.MINUTES);

        return "邮件发送成功";
    }

    // 重置密码逻辑
    public String resetPassword(String email, String code, String password) {
        // 检查验证码是否过期
        if (!redisCache.hasKey(email)) {
            return "验证码已过期,请重新获取";
        }

        // 获取Redis中的验证码
        String cachedCode = redisCache.getCacheObject(email);

        // 验证码匹配
        if (cachedCode != null && cachedCode.equals(code)) {
            // 重置密码逻辑(此处可以加入实际的重置密码功能)
            redisCache.deleteObject(email); // 删除缓存中的验证码
            return "密码重置成功,新密码为:" + password;
        }

        return "验证码错误或已过期,请重新尝试";
    }

    // 定时任务:每小时更新学生邮箱数据
    @Scheduled(fixedDelay = 3600000L) // 1小时执行一次(单位:毫秒, 1小时 = 60 * 60 * 1000)
    public void updateEmailList() {

        // 模拟从数据库中获取邮箱
        List<String> emailList = new ArrayList<>();
        emailList.add("xxxxxx@163.com");
        HashSet<Object> studentEmailSet = new HashSet<>(emailList);
        String[] emailArray = studentEmailSet.toArray(new String[studentEmailSet.size()]);

        // 将邮箱存入Redis,设置2小时过期,防止定时任务运行慢了
        redisCache.setCacheObject("Student_Email", emailArray, 2, TimeUnit.HOURS);
    }
}

创建测试控制类SpringMailController

import com.work.spring.springbootwork.service.MailServiceImp;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


/**
 * @Title: SpringMailController
 * @Author David
 * @Package com.work.spring.springbootwork.controller
 * @Date 2024/9/11 下午1:05
 */
@RestController
public class SpringMailController {

    @Resource
    private MailServiceImp mailService;

    // 发送邮件请求
    @GetMapping("/sendMail")
    public String sendMail(@RequestParam("email") String email) {
        return mailService.sendMail(email);
    }

    // 重置密码请求
    @GetMapping("/resetpassword")
    public String resetPassword(@RequestParam("email") String email,
                                @RequestParam("code") String code,
                                @RequestParam("password") String password) {
        return mailService.resetPassword(email, code, password);
    }
}

JavaEE自带包解决

逻辑与SpringBoot的相同,下面只给出服务层代码基础业务的实现

MailServiceImp

import jakarta.mail.Message;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import org.eclipse.angus.mail.util.MailSSLSocketFactory;
import org.springframework.stereotype.Service;

import java.util.Properties;
import java.util.Random;

/**
 * @Title: MailServiceImp
 * @Author David
 * @Package com.work.spring.springbootwork.service
 * @Date 2024/9/10 下午9:38
 * @description: 模拟发送邮件的服务类
 */

@Service
public class MailServiceImp {

    // 邮箱服务器的配置
    private static final String MAIL_HOST = "smtp.163.com";
    private static final String MAIL_USERNAME = "xxxxxx@163.com";  // 发件人邮箱
    private static final String MAIL_PASSWORD = "xxxxxxxxxxxxxxxx";  // 授权码(非登录密码)

    /**
     * 发送邮件
     * @param recipientEmail 收件人邮箱
     * @param verificationCode 验证码
     * @return 发送状态
     */
    public String sendMail(String recipientEmail, String verificationCode) throws Exception {
        Properties prop = new Properties();
        // 开启debug调试,以便在控制台查看
        prop.setProperty("mail.debug", "true");
        // 设置邮件服务器主机名
        prop.setProperty("mail.host", MAIL_HOST);
        // 发送服务器需要身份验证
        prop.setProperty("mail.smtp.auth", "true");
        // 发送邮件协议名称
        prop.setProperty("mail.transport.protocol", "smtp");

        // 开启SSL加密
        MailSSLSocketFactory sf = new MailSSLSocketFactory();
        sf.setTrustAllHosts(true);
        prop.put("mail.smtp.ssl.enable", "true");
        prop.put("mail.smtp.ssl.socketFactory", sf);

        // 创建邮件会话
        Session session = Session.getInstance(prop);
        Transport transport = session.getTransport();

        // 连接邮件服务器,使用发件人邮箱及授权码
        transport.connect(MAIL_HOST, MAIL_USERNAME, MAIL_PASSWORD);

        // 创建邮件
        Message message = createSimpleMail(session, recipientEmail, verificationCode);

        // 发送邮件
        transport.sendMessage(message, message.getAllRecipients());
        transport.close();

        return "邮件发送成功";
    }

    /**
     * 创建一个包含验证码的邮件
     * @param session 邮件会话
     * @param recipientEmail 收件人邮箱
     * @param verificationCode 验证码
     * @return 邮件对象
     * @throws Exception 创建邮件异常
     */
    private MimeMessage createSimpleMail(Session session, String recipientEmail, String verificationCode) throws Exception {
        // 创建邮件对象
        MimeMessage message = new MimeMessage(session);
        // 设置发件人
        message.setFrom(new InternetAddress(MAIL_USERNAME));
        // 设置收件人
        message.setRecipient(Message.RecipientType.TO, new InternetAddress(recipientEmail));
        // 设置邮件标题
        message.setSubject("您的验证码");

        // 设置邮件内容
        message.setContent("您好,\n\n您的验证码是:" + verificationCode + "。\n请在1分钟内使用。", "text/html;charset=UTF-8");

        return message;
    }

    /**
     * 生成六位验证码
     * @return 验证码
     */
    public String generateSixDigitCode() {
        String[] characters = {
                "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "g", "h", "j", "k", "l", "z", "x", "c", "v", "b", "n", "m",
                "A", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Z", "X", "C", "V", "B", "N", "M",
                "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
        };
        StringBuilder code = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            code.append(characters[random.nextInt(characters.length)]);
        }
        return code.toString();
    }
}