spring boot3登录开发-2(3邮件验证码接口实现)

⛰️个人主页: 蒾酒

🔥系列专栏:《spring boot实战》


目录

写在前面

上文衔接

接口设计与实现

1.接口分析

2.实现思路

3.代码实现

1.定义验证码短信HTML模板枚举类

2.定义验证码业务接口

[3. 验证码业务接口实现](#3. 验证码业务接口实现)

4.控制层代码

4.测试

写在最后


最近发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。

点击跳转到学习网站

写在前面

本文介绍了springboot开发后端服务中,邮件验证码接口功能的设计与实现,坚持看完相信对你有帮助。

同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。

上文衔接

实现发送验证码邮件接口的前提是项目中已经整合好了邮件服务。上篇文章介绍了springboot整合QQ邮箱SMTP服务:spring boot3整合邮件服务实现邮件发送功能_spring-boot-starter-mail springboot3-CSDN博客https://blog.csdn.net/qq_62262918/article/details/139244604

接口设计与实现

1.接口分析

关键点:

  1. 邮件验证码60s发送间隔
  2. 连发多次封禁24H
  3. 5分钟有效期

2.实现思路

接口接收一个邮箱号参数,先去redis查询该邮箱是否有未过期的验证码,如果有先判断发送次数,超过5次,先修改过期时间为24H,再抛发送次数过多异常,少于五次,就再判断发送频率,距离上次发送间隔少于60s,就抛出发送频繁异常, 如果没有未过期的验证码,代表这是第一次发送就直接生成验证码发送短信,接着redis存入一个哈希,key通过手机号生成,value就是,三个键值对,验证码,上次发送时间戳,发送次数(初始为1),默认过期时间5分钟。

  1. 接收手机号参数。
  2. 查询 Redis 中该手机号是否有未过期的验证码。
  3. 如果有未过期的验证码:
    1. 判断发送次数是否超过5次,如果超过5次,修改过期时间为24小时并抛出发送次数过多异常。
    2. 如果发送次数未超过5次,再判断距离上次发送时间间隔是否少于60秒,如果少于60秒,抛出发送频繁异常。
  4. 如果没有未过期的验证码,代表第一次发送:
    1. 生成验证码并发送短信。
    2. 将验证码信息存入 Redis,包括验证码、上次发送时间戳和发送次数(初始为1),设置过期时间为5分钟。

注意事项:

考虑到验证码本身的敏感性,虽然存入Redis是临时的,但考虑安全建议对验证码内容进行加密存储,增强数据的安全性。

考虑对接口调用方进行身份验证,确保只有合法的客户端可以请求发送验证码。

整合redis:

Spring Boot3整合Redis_springboot3整合redis-CSDN博客文章浏览阅读6.6k次,点赞103次,收藏111次。⛰️个人主页: 蒾酒🔥系列专栏:《spring boot实战》目录前置条件1.导依赖2.配置连接信息以及连接池参数3.配置序列化方式4.编写测试已经初始化好一个spring boot项目且版本为3X,项目可正常启动。作者版本为3.2.2初始化教程:新版idea(2023)创建spring boot3项目-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135785412?spm=1001.2014.3001.5501pom.xml:_springboot3整合redishttps://blog.csdn.net/qq_62262918/article/details/136067550?spm=1001.2014.3001.5501

3.代码实现

1.定义验证码短信HTML模板枚举类

在实际业务中可能需要发送各种类型的邮件通知,将不同类型的邮件定义为模板维护在枚举中也是种不错选择。

java 复制代码
import lombok.Getter;
import java.util.Locale;

/**
 * @author mijiupro
 */
@Getter
public enum EmailTemplateEnum {

    // 验证码邮件
    VERIFICATION_CODE_EMAIL_HTML("<html><body>用户你好,你的验证码是:<h1>%s</h1></body></html>","登录验证"),

    // 用户被封禁邮件通知
    USER_BANNED_EMAIL("用户你好,你已经被管理员封禁,封禁原因:%s", "封禁通知"),

    ;

    private final String template;

    private final String subject;

    EmailTemplateEnum(String template, String subject) {
        this.template = template;
        this.subject = subject;
    }

    public String value() {
        return this.template;
    }

    /**
     * 模板参数填充
     * @param args 参数集
     * @return 填充后的字符串
     */
    public String set(Object... args) {
        return String.format(Locale.ROOT, this.template, args);
    }
}

2.定义验证码业务接口

java 复制代码
public interface CaptchaService {

    /**
     * 获取邮箱验证码
     * @param email 邮箱
     */
    void getMailCaptcha(String email);

}

3. 验证码业务接口实现

java 复制代码
import com.mijiu.commom.enumerate.EmailTemplateEnum;
import com.mijiu.commom.exception.GeneralBusinessException;
import com.mijiu.api.email.EmailApi;
import com.mijiu.api.sms.SmsApi;
import com.mijiu.service.CaptchaService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

/**
 * @author mijiupro
 */
@Service
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {
    private final StringRedisTemplate stringRedisTemplate;
    private final SmsApi smsUtil;
    private final EmailApi emailUtil;


    public CaptchaServiceImpl(StringRedisTemplate stringRedisTemplate, SmsApi smsUtil, EmailApi emailUtil) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.smsUtil = smsUtil;
        this.emailUtil = emailUtil;
    }

    
    @Override
    public void getMailCaptcha(String email) {
        getCaptcha("login:email:captcha:" + email);
    }
    

    private void getCaptcha(String hashKey) {

        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(hashKey);

        // 初始检查
        String lastSendTimestamp = hashOps.get("lastSendTimestamp");
        String sendCount = hashOps.get("sendCount");
        String captcha = hashOps.get("captcha");


        // 判断发送次数是否超过限制
        if (StringUtils.isNotBlank(sendCount) && Integer.parseInt(sendCount) >= 5) {
            hashOps.expire(24, TimeUnit.HOURS); // 重新设置过期时间为24H
            throw new GeneralBusinessException("发送次数过多,请24H后再试");
        }

        // 判断发送频率是否过高
        if (StringUtils.isNotBlank(lastSendTimestamp)) {
            long lastSendTime = Long.parseLong(lastSendTimestamp);
            long currentTime = System.currentTimeMillis();
            long elapsedTime = currentTime - lastSendTime;
            long interval = 60 * 1000; // 60秒
            if (elapsedTime < interval) {
                throw new GeneralBusinessException("发送频繁,请稍后再试");
            }
        }

        // 更新发送次数
        int newSendCount = StringUtils.isNotBlank(sendCount) ? Integer.parseInt(sendCount) + 1 : 1;

        // 生成新验证码
        if (StringUtils.isBlank(captcha)) {
            captcha = RandomStringUtils.randomNumeric(6);
        }

        // 发送邮件或短信
        sendCaptcha(hashKey, captcha);

        // 更新 Redis 中的信息
        hashOps.put("captcha", captcha);
        hashOps.put("lastSendTimestamp", String.valueOf(System.currentTimeMillis()));
        hashOps.put("sendCount", String.valueOf(newSendCount));
        hashOps.expire(5, TimeUnit.MINUTES); // 设置过期时间为5分钟
    }

    private void sendCaptcha(String hashKey, String captcha) {
        // 根据hashKey判断是发送邮件还是短信,然后调用相应的发送方法
        if("email".equals(hashKey.split(":")[1])){
            if (!emailUtil.sendHtmlEmail(EmailTemplateEnum.VERIFICATION_CODE_EMAIL_HTML.getSubject(),
                    EmailTemplateEnum.VERIFICATION_CODE_EMAIL_HTML.set(captcha),hashKey.split(":")[3])) {
                throw new GeneralBusinessException("发送邮件失败");
            }
        } else if ( "sms".equals(hashKey.split(":")[1])) {
            if (!smsUtil.sendSms(hashKey.split(":")[3], captcha)) {
                throw new GeneralBusinessException("发送短信失败");
            }
        }
    }

}

4.控制层代码

java 复制代码
import com.mijiu.service.CaptchaService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author mijiupro
 */
@RestController
@RequestMapping("/captcha")
@Tag(name = "验证码接口", description = "验证码接口相关操作")
public class CaptchaController {

    private final CaptchaService captchaService;
    public CaptchaController(CaptchaService captchaService) {
        this.captchaService = captchaService;
    }
   
    @GetMapping("/mail-captcha/{email}")
    @Operation(summary = "获取邮箱验证码")
    public void getEmailCaptcha(@PathVariable String email) {
        captchaService.getMailCaptcha(email);
    }
   
}

到这里接口已经实现。

4.测试

用swagger3进行测试:

Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135761392

发送请求

这是redis已经缓存了验证码信息

也收到了验证码邮件

写在最后

邮件验证码接口功能的设计与实现到这里就结束了,任何问题评论区或私信讨论,欢迎指正。

相关推荐
考虑考虑9 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯10 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
青石路14 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
Java陈序员16 小时前
企业级!一个基于 Java 开发的开源 AI 应用开发平台!
spring boot·agent·mcp
像我这样帅的人丶你还17 小时前
Java 后端详解(五):Redis 缓存
java·后端·全栈
plainGeekDev19 小时前
GreenDAO → Room
android·java·kotlin
杨运交1 天前
[041][公共模块]分布式唯一ID生成器设计与实现:一款灵活可扩展的雪花算法框架
spring boot
亦暖筑序1 天前
Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流
java·后端
敲代码的彭于晏1 天前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端
plainGeekDev1 天前
ButterKnife → ViewBinding
android·java·kotlin