🔥系列专栏:《spring boot实战》
目录
[3. 验证码业务接口实现](#3. 验证码业务接口实现)
最近发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。
写在前面
本文介绍了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.接口分析
关键点:
- 邮件验证码60s发送间隔
- 连发多次封禁24H
- 5分钟有效期
2.实现思路
接口接收一个邮箱号参数,先去redis查询该邮箱是否有未过期的验证码,如果有先判断发送次数,超过5次,先修改过期时间为24H,再抛发送次数过多异常,少于五次,就再判断发送频率,距离上次发送间隔少于60s,就抛出发送频繁异常, 如果没有未过期的验证码,代表这是第一次发送就直接生成验证码发送短信,接着redis存入一个哈希,key通过手机号生成,value就是,三个键值对,验证码,上次发送时间戳,发送次数(初始为1),默认过期时间5分钟。
- 接收手机号参数。
- 查询 Redis 中该手机号是否有未过期的验证码。
- 如果有未过期的验证码:
- 判断发送次数是否超过5次,如果超过5次,修改过期时间为24小时并抛出发送次数过多异常。
- 如果发送次数未超过5次,再判断距离上次发送时间间隔是否少于60秒,如果少于60秒,抛出发送频繁异常。
- 如果没有未过期的验证码,代表第一次发送:
- 生成验证码并发送短信。
- 将验证码信息存入 Redis,包括验证码、上次发送时间戳和发送次数(初始为1),设置过期时间为5分钟。
注意事项:
考虑到验证码本身的敏感性,虽然存入Redis是临时的,但考虑安全建议对验证码内容进行加密存储,增强数据的安全性。
考虑对接口调用方进行身份验证,确保只有合法的客户端可以请求发送验证码。
整合redis:
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进行测试:
发送请求
这是redis已经缓存了验证码信息
也收到了验证码邮件
写在最后
邮件验证码接口功能的设计与实现到这里就结束了,任何问题评论区或私信讨论,欢迎指正。