springboot 實現郵箱找回密碼(使用到redis, stmp)

weiewiyi發表於2023-01-05

背景

小組專案要做一個郵箱找回密碼的功能。 需要用到redis

image.png

大概流程如下:

  1. 第一步,使用者填寫郵箱,並點選“獲取驗證碼”,瀏覽器傳送請求,呼叫獲取驗證碼介面。然後,服務端根據郵箱,生成驗證碼,傳送驗證碼給這個郵箱,並將驗證碼和郵箱和有效期放到redis/記憶體中
  2. 使用者在郵箱中查到驗證碼後→填寫到登入介面→點選登入→前端請求登入介面,攜帶郵箱和驗證碼引數。
  3. 伺服器收到請求後,到redis中取到這個郵箱對應的資訊→校驗驗證碼是否正確,並驗證是否過期,如果驗證碼正確且沒有過期,則正常登入。

image.png

Redis

先介紹一下redis

Redis 是完全開源免費的,,是一個高效能的key-value非關係性資料庫(NoSql)。

Redis 與其他 key - value 快取產品有以下三個特點:

  • Redis支援資料的持久化,可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次載入進行使用。
  • Redis不僅僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。



Redis可以儲存鍵與5種不同資料結構型別之間的對映,這5種資料結構型別分別為String(字串)、List(列表)、Set(集合)、Hash(雜湊)和 Zset(有序集合)。
image.png

下載Redis

直接訪問github網址:https://github.com/MSOpenTech...
如下圖所示:

我下載的是windows版本
image.png

啟動Redis



image.png


redis.windows.conf為redis配置檔案,相關引數可以在這裡配置,如:埠等,我這裡使用預設引數,暫不修改,預設埠為6379。雙擊redis-server.exe啟動,則出現如下圖所示,則啟動成功。

image.png

SpringBoot中使用Redis

在專案的pom.xml中新增如下:

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

application.yml 中配置如下

  # redis 配置
  redis:
    # 地址
    host: localhost
    # 埠,預設為6379
    port: 6379
    # 資料庫索引
    database: 0
    # 密碼
    password:
    # 連線超時時間
    timeout: 10s
    lettuce:
      pool:
        # 連線池中的最小空閒連線
        min-idle: 0
        # 連線池中的最大空閒連線
        max-idle: 8
        # 連線池的最大資料庫連線數
        max-active: 8
        # #連線池最大阻塞等待時間(使用負值表示沒有限制)
        max-wait: -1ms

編寫Redis操作工具類

我們對redis操作需要用到RedisTemplate

RedisTemplate 是簡化 Redis 資料庫訪問程式碼的助手類,也是 Spring Data Redis 對 Redis 支援的中心類。

我們這裡將RedisTemplate例項包裝成一個工具類,便於對redis進行資料操作。

建立spring redis 工具類

@Component
public class RedisCache {
    @Autowired
    public RedisTemplate redisTemplate;
    
    // redis序列化器 型別為string
    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        this.redisTemplate = redisTemplate;
    }

}

下面編寫工具方法

1.設定快取物件

  /**
     * 快取基本的物件,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);
    }

比如說我們要將驗證碼存入redis,就可以這麼做

// 驗證碼存入redis並設定過期時間
redisCache.setCacheObject("mail_code:" + username, verifyCode, 5, TimeUnit.MINUTES);

這裡我們設定

key為 mail_code: + username,表示某個使用者的郵箱驗證碼

value為 verifycode , 表示驗證碼

timeout為5, timeUnit為 MINUTES, 表示過期時間為5分鐘。

2.獲得快取的基本物件。

/**
     * 獲得快取的基本物件。
     *
     * @param key 快取鍵值
     * @return 快取鍵值對應的資料
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

比如使用如下: 根據key 獲取 value

String verifyCode = redisCache.getCacheObject("mail_code: + usrname);

3.設定有效時間

/**
     * 設定有效時間
     *
     * @param key     Redis鍵
     * @param timeout 超時時間
     * @param unit    時間單位
     * @return true=設定成功;false=設定失敗
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

比如我們在使用者成功使用完該驗證碼後,想將該驗證碼設定為過期:

// 將驗證碼過期
redisCache.expire("mail_code: + usrname, 0, TimeUnit.SECONDS);

表示該鍵值對會在0秒後過期。

4.刪除物件

/**
 * 刪除單個物件
 *
 * @param key
 */
public boolean deleteObject(final String key) {
    return redisTemplate.delete(key);
}


除這幾個常用的外,還編寫了幾個類,比如根據key,寫入新的值,覆蓋舊的值等,在此就不一一列出。

同時我們還可以編寫,對Set,Hash,Map,List 型別的操作工具類,但是我沒有用到,所以沒有編寫。

使用郵箱傳送驗證碼

這裡用到SMTP服務

SMTP

SMTP的全稱是“Simple Mail Transfer Protocol”,即簡單郵件傳輸協議。

什麼是 SMTP?
SMTP代表簡單郵件傳輸協議,它是電子郵件傳送的行業標準協議。
使用 SMTP,你可以從郵件客戶端(如 Microsoft Outlook)向接收電子郵件伺服器傳送、中繼或轉發郵件。發件人將使用SMTP 伺服器來執行傳送電子郵件的過程。
在考慮是使用 SMTP 還是 IMAP 時要記住的關鍵是 SMTP 是關於傳送電子郵件的。因此,如果你希望在你的應用程式中啟用電子郵件傳送,那麼你需要繼續使用 SMTP over IMAP。

什麼是 IMAP?
如果 SMTP 是關於傳送的,那麼 IMAP 是什麼?
簡而言之,IMAP(Internet 訪問訊息協議)是一種電子郵件協議,用於管理和檢索來自接收伺服器的電子郵件訊息。
由於 IMAP 處理訊息檢索,因此你將無法使用 IMAP 協議傳送電子郵件。相反,IMAP 將用於接收訊息。

除了 IMAP,還有另一種用於接收電子郵件的協議 — 稱為 POP3。感興趣可以查一下

簡單來說, SMTP用於傳送, IMAP用於接收
image.png

由於只傳送,所以我只用到SMTP。

發件郵箱要求

這裡使用的是163郵箱,

1.設定POP3/SMTP/IMAP
image.png

2.開啟服務
開啟會讓設定授權碼,授權碼要記牢!後面需要寫在配置裡

image.png

3.檢視伺服器地址
再往下翻有163 SMTP伺服器地址 等下需要配置在application.yml中

image.png

程式碼實現

1.pom依賴

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

2.application.yml配置

  # 配置郵箱伺服器,賬號密碼等
  mail:
    host: smtp.163.com
    username: xxx@163.com
    password: xxx
    port:
    default-encoding: utf-8
    protocol: smtp
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.starttls.required: true
      mail.smtp.socketFactory.port: 465
      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
      mail.smtp.socketFactory.fallback: false

3.獲取驗證碼controller

@RestController
@RequestMapping("/mail")
public class MailController {

    @Autowired
    private MailService mailService;

    /**
     * 獲取重置密碼的驗證碼
     */
    @GetMapping("/getCode")
    public void getCode(@RequestParam String staffNumber, @RequestParam String mailAddress){
         mailService.getCode(staffNumber,mailAddress);
    }
}

4. ServiceImpl層

@Service
public class MailServiceImpl implements MailService {

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private UserRepository userRepository;

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

    @Value("${mail.code.overtime}")
    private Integer overtime; // 過期時間

 /**
     * 獲取重置密碼的驗證碼
     *
     * @param username 使用者賬號
     * @param mailAddress 使用者郵箱
     * @return
     */
    @Override
    public void getCode(String username, String mailAddress) {
        // 非空校驗
        if (null == username || "".equals(username)) throw new EntityNotFoundException("賬號不能為空!");
        if (null == mailAddress || "".equals(mailAddress)) throw new EntityNotFoundException("郵箱不能為空!");

        // 賬號存在校驗
        User user = userRepository.findByUsernameAndDeletedIsFalse(username);
        if (null == user) throw new EntityNotFoundException("賬號不存在!");
        if (!user.getPhone().equals(mailAddress)) throw new EntityNotFoundException("輸入郵箱和預留郵箱不一致!");

  
        verifyCode = String.valueOf(new Random().nextInt(899999) + 100000);//生成簡訊驗證碼
        
        // 驗證碼存入redis並設定過期時間
        redisCache.setCacheObject(Constants.MAIL_CODE_KEY + username, verifyCode, overtime, TimeUnit.MINUTES);

        // 編寫郵箱內容
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("<html><head><title></title></head><body>");
        stringBuilder.append("您好<br/>");
        stringBuilder.append("您的驗證碼是:").append(verifyCode).append("<br/>");
        stringBuilder.append("您可以複製此驗證碼並返回至裝置管理系統找回密碼頁面,以驗證您的郵箱。<br/>");
        stringBuilder.append("此驗證碼只能使用一次,在");
        stringBuilder.append(overtime.toString());
        stringBuilder.append("分鐘內有效。驗證成功則自動失效。<br/>");
        stringBuilder.append("如果您沒有進行上述操作,請忽略此郵件。");
        MimeMessage mimeMessage = mailSender.createMimeMessage();

        // 發件配置併傳送郵件
        try {
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            //這裡只是設定username 並沒有設定host和password,因為host和password在springboot啟動建立JavaMailSender例項的時候已經讀取了
            mimeMessageHelper.setFrom(mailUserName);
            mimeMessageHelper.setTo(mailAddress);
            mimeMessage.setSubject("郵箱驗證-重置密碼");
            mimeMessageHelper.setText(stringBuilder.toString(), true);
            mailSender.send(mimeMessage);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }

主要邏輯:

1.資料校驗
主要校驗前臺傳入資料非空,以及user賬號是否存在,輸入郵箱和預留郵箱是否一致等

// 非空校驗
if (null == username || "".equals(username)) throw new EntityNotFoundException("賬號不能為空!");
if (null == mailAddress || "".equals(mailAddress)) throw new EntityNotFoundException("郵箱不能為空!");

// 賬號存在校驗
User user = userRepository.findByUsernameAndDeletedIsFalse(username);
if (null == user) throw new EntityNotFoundException("賬號不存在!");
if (!user.getPhone().equals(mailAddress)) throw new EntityNotFoundException("輸入郵箱和預留郵箱不一致!");

2.驗證碼存入redis
首先檢視該key,是否存在於redis快取中。

 verifyCode = String.valueOf(new Random().nextInt(899999) + 100000);//生成簡訊驗證碼
// 驗證碼存入redis並設定過期時間
redisCache.setCacheObject(Constants.MAIL_CODE_KEY + username, verifyCode, overtime, TimeUnit.MINUTES);

3.編寫郵件內容
程式碼如上,此處省略

4.傳送並配置郵件
這裡用到了JavaMailSender來傳送郵件。

最早期的時候會使用JavaMail相關api來寫傳送郵件的相關程式碼

spring推出了JavaMailSender簡化了郵件傳送的過程,

在之後springboot對此進行了封裝就有了現在的spring-boot-starter-mail

 // 發件配置併傳送郵件
        try {
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            //這裡只是設定username 並沒有設定host和password,因為host和password在springboot啟動建立JavaMailSender例項的時候已經讀取了
            mimeMessageHelper.setFrom(mailUserName);
            mimeMessageHelper.setTo(mailAddress);
            mimeMessage.setSubject("郵箱驗證-重置密碼");
            mimeMessageHelper.setText(stringBuilder.toString(), true);
            mailSender.send(mimeMessage);
        } catch (
                MessagingException e) {
            e.printStackTrace();
        }
  1. 驗證驗證碼正確性

從redis中獲取該驗證碼,若從redis中獲取值為null, 則驗證碼已經過期

String cacheCode = redisCache.getCacheObject(Constants.MAIL_CODE_KEY + num); // 獲取快取中該賬號的驗證碼
if (cacheCode == null) {
    throw new EntityNotFoundException("驗證碼已過期,請重新獲取!");
}

// 驗證碼正確性校驗
if (!cacheCode.equals(codes)) {
    throw new EntityNotFoundException("驗證碼錯誤!");
}




參考
https://www.cnblogs.com/fonks...
https://www.cnblogs.com/equal...
https://juejin.cn/post/709110...
https://ost.51cto.com/posts/1262

相關文章