⛰️个人主页: 蒾酒
🔥系列专栏:《spring boot实战》
🌊山高路远,行路漫漫,终有归途
目录
写在前面
上文衔接
内容简介
1.依赖导入
2.接口分析
3.实现思路
3.功能实现
创建发送短信工具类
配置阿里云短信服务
接口代码实现
4.功能测试
写在最后
本文介绍了springboot开发后端服务中,短信验证码接口功能的设计与实现,坚持看完相信对你有帮助。
同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。
本文衔接上文,可以看一下:
spring boot3登录开发-2(1图形验证码接口实现)_用户登录验证码接口设计-CSDN博客文章浏览阅读5.2k次,点赞146次,收藏109次。上文我们已经整合好了jwt,本文我们开始实现图形验证码接口的实现。通过糊涂工具包的图形验证码工具完成获取验证码接口通过redis缓存key(验证码id)-value(验证码内容)_用户登录验证码接口设计https://blog.csdn.net/qq_62262918/article/details/136064820?spm=1001.2014.3001.5502
上文我们已经整合好了jwt,本文我们开始实现短信验证码接口的实现。这里我选用阿里云的短信服务。本文侧重点在于设计思路,阿里云控制台开通短信服务,你跟着流程走一遍就可以。个人也是可以申请的。
导入阿里云短信服务SDK
pom.xml:
com.aliyun dysmsapi201705252.0.24
关键点:
- 60s发送间隔
- 连发多次封禁24H
接口接收一个手机号参数,先去redis查询该手机是否有未过期的验证码,如果有先判断发送次数,超过5次,先修改过期时间为24H,再抛发送次数过多异常,少于五次,就再判断发送频率,距离上次发送间隔少于60s,就抛出发送频繁异常, 如果没有未过期的验证码,代表这是第一次发送就直接生成验证码发送短信,接着redis存入一个哈希,key通过手机号生成,value就是,三个键值对,验证码,上次发送时间戳,发送次数(初始为1),默认过期时间5分钟
- 接收手机号参数。
- 查询 Redis 中该手机号是否有未过期的验证码。
- 如果有未过期的验证码:
- 判断发送次数是否超过5次,如果超过5次,修改过期时间为24小时并抛出发送次数过多异常。
- 如果发送次数未超过5次,再判断距离上次发送时间间隔是否少于60秒,如果少于60秒,抛出发送频繁异常。
- 如果没有未过期的验证码,代表第一次发送:
- 生成验证码并发送短信。
- 将验证码信息存入 Redis,包括验证码、上次发送时间戳和发送次数(初始为1),设置过期时间为5分钟。
注意事项:
考虑到验证码本身的敏感性,虽然存入Redis是临时的,但考虑安全建议对验证码内容进行加密存储,增强数据的安全性。
考虑对接口调用方进行身份验证,确保只有合法的客户端可以请求发送验证码。
整合redis:
Spring Boot3整合Redis_springboot 3 集成redis-CSDN博客文章浏览阅读5.4k次,点赞101次,收藏105次。spring boot整合redis简单四步即可。_springboot 3 集成redishttps://blog.csdn.net/qq_62262918/article/details/136067550?spm=1001.2014.3001.5501
import com.aliyun.dysmsapi20170525.Client; import com.aliyun.dysmsapi20170525.models.SendSmsRequest; import com.aliyun.dysmsapi20170525.models.SendSmsResponse; import com.aliyun.teaopenapi.models.Config; import com.aliyun.teautil.models.RuntimeOptions; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author mijiupro */ @Data @Component @ConfigurationProperties(prefix = "aliyun.sms") @Slf4j public class SmsUtil { private String accessKeyId;// 访问密钥id private String accessKeySecret;// 访问密钥secret private String endpoint;// 短信 API 的访问域名 private String signName;// 短信签名 private String templateCode;// 短信模板ID // 发送短信 public Boolean sendSms(String phone, String code) { // 创建阿里云客户端 Config config = new Config() .setAccessKeyId(accessKeyId)// 配置访问密钥 ID .setAccessKeySecret(accessKeySecret);// 配置密钥 config.endpoint = endpoint;// 配置访问端点 Client client; try { client = new Client(config); } catch (Exception e) { log.error("阿里云短信服务初始化失败", e); return false; } // 构建发送请求 SendSmsRequest sendSmsRequest = new SendSmsRequest() .setSignName(signName) // 设置签名 .setTemplateCode(templateCode) // 设置模板 .setPhoneNumbers(phone) // 设置手机号为参数传入的值 .setTemplateParam("{\"code\":\"" + code + "\"}"); // 设置模板参数为传入的验证码 RuntimeOptions runtime = new RuntimeOptions();// 运行时选择,可以设置不同的属性来配置运行时环境的参数。 try { // 复制代码运行请自行打印 API 的返回值 SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); if (!"OK".equals(sendSmsResponse.getBody().getCode())) { log.error("短信发送失败:{}", sendSmsResponse.getBody().getMessage()); return false; } log.info("短信发送成功"); return true; } catch (Exception error) { log.error("短信发送失败:{}", error.getMessage()); return false; } } }
application.yml:
此处修改为你的信息,这里我乱写的
aliyun: sms: access-key-id: Lih5disdjisdid access-key-secret: sdisajfdisf6s7d88sd endpoint: dysmsapi.aliyuncs.com signName: mijiu templateCode: SMS_46544097
接口
public interface CaptchaService { /** * 获取短信验证码 * @param phone */ void getSmsCaptcha(String phone); }
实现类
import com.mijiu.commom.exception.GeneralBusinessException; import com.mijiu.commom.util.SmsUtil; 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 SmsUtil smsUtil; public CaptchaServiceImpl(StringRedisTemplate stringRedisTemplate, SmsUtil smsUtil) { this.stringRedisTemplate = stringRedisTemplate; this.smsUtil = smsUtil; } @Override public void getSmsCaptcha(String phone) { String hashKey = "login:sms:captcha:" + phone; BoundHashOperationshashOps = stringRedisTemplate.boundHashOps(hashKey); // 初始检查 String lastSendTimestamp = hashOps.get("lastSendTimestamp"); String sendCount = hashOps.get("sendCount"); String captcha = hashOps.get("captcha"); hashOps.expire(5, TimeUnit.MINUTES); // 设置过期时间为5分钟 // 判断发送次数是否超过限制 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); } // 发送短信 if (!smsUtil.sendSms(phone, captcha)) { throw new GeneralBusinessException("发送短信失败"); } // 更新 Redis 中的信息 hashOps.put("captcha", captcha); hashOps.put("lastSendTimestamp", String.valueOf(System.currentTimeMillis())); hashOps.put("sendCount", String.valueOf(newSendCount)); } }
控制器
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("/sms-captcha/{phone}") @Operation(summary = "获取短信验证码") public void getSmsCaptcha(@PathVariable String phone) { captchaService.getSmsCaptcha(phone); } }
说明:
代码里面我直接返回void是因为我做了全局响应结果拦截统一封装,实际应用为了考虑防盗刷等因素并不会返回空。
我这里是整合了swagger3的,相关文章在本专栏:
Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135761392?spm=1001.2014.3001.5502
直接发一次
60内重复发送
连发5次
对接阿里云短信服务实现短信验证码接口到这里就结束了,接口设计我尽可能简单并有一定安全性考虑。希望看完对你有帮助。后面我会出一篇仿百度云短信验证接口的设计实现(考虑更多防盗刷策略),欢迎大家订阅本专栏。任何问题评论区或私信讨论,欢迎指正。
上一篇:【MySQL】基本查询(2)