springboot项目实现验证码功能(短信防刷机制实现)

为什么写这篇文章

项目里面有用到用户手机号注册发短信功能,需要做短信防刷机制。主要验证逻辑如下

  1. 图形验证码验证
  2. IP验证,同一个IP每日限制发送20条
  3. 发送时判断5分钟之内是否发送了验证码,如果有的话就发送重复的至用户手机,减少有时候并发引起的重复发送不同的验证码用户不知道用哪个

通过这一套逻辑,可以防刷99%的用户了

maven包

<!--验证码-->
<dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version>
</dependency>

验证码枚举类

public enum LoginCodeEnum {/*** 算数*/ARITHMETIC,/*** 中文*/CHINESE,/*** 中文闪图*/CHINESE_GIF,/*** 闪图*/GIF,SPEC
}

验证码的基本信息类

@Data
public class LoginCode {/*** 验证码配置*/private LoginCodeEnum codeType;/*** 验证码有效期 分钟*/private Long expiration = 120L;/*** 验证码内容长度*/private int length = 2;/*** 验证码宽度*/private int width = 111;/*** 验证码高度*/private int height = 36;/*** 验证码字体*/private String fontName;/*** 字体大小*/private int fontSize = 25;/*** 验证码前缀** @return*/private String codeKey;
}

配置类

@Configuration
public class ConfigBeanConfiguration {@Bean@ConfigurationProperties(prefix = "login")public LoginProperties loginProperties() {return new LoginProperties();}
}
@Data
public class LoginProperties {private LoginCode loginCode;/*** 获取验证码生产类* @return*/public Captcha getCaptcha(){if(Objects.isNull(loginCode)){loginCode = new LoginCode();if(Objects.isNull(loginCode.getCodeType())){loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);}}return switchCaptcha(loginCode);}/*** 依据配置信息生产验证码* @param loginCode* @return*/private Captcha switchCaptcha(LoginCode loginCode){Captcha captcha = null;synchronized (this){switch (loginCode.getCodeType()){case ARITHMETIC:captcha = new FixedArithmeticCaptcha(loginCode.getWidth(),loginCode.getHeight());captcha.setLen(loginCode.getLength());break;case CHINESE:captcha = new ChineseCaptcha(loginCode.getWidth(),loginCode.getHeight());captcha.setLen(loginCode.getLength());break;case CHINESE_GIF:captcha = new ChineseGifCaptcha(loginCode.getWidth(),loginCode.getHeight());captcha.setLen(loginCode.getLength());break;case GIF:captcha = new GifCaptcha(loginCode.getWidth(),loginCode.getHeight());captcha.setLen(loginCode.getLength());break;case SPEC:captcha = new SpecCaptcha(loginCode.getWidth(),loginCode.getHeight());captcha.setLen(loginCode.getLength());default:System.out.println("验证码配置信息错误!正确配置查看 LoginCodeEnum ");}}if(StringUtils.isNotBlank(loginCode.getFontName())){captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN,loginCode.getFontSize()));}return captcha;}static  class FixedArithmeticCaptcha extends ArithmeticCaptcha {public FixedArithmeticCaptcha(int width,int height){super(width,height);}@Overrideprotected char[] alphas() {// 生成随机数字和运算符int n1 = num(1, 10), n2 = num(1, 10);int opt = num(3);// 计算结果int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];// 转换为字符运算符char optChar = "+-x".charAt(opt);this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));this.chars = String.valueOf(res);return chars.toCharArray();}}
}

Controller

@RestController
@RequestMapping("/v1/captcha")
@Api(tags = "业务-验证码接口")
@RequiredArgsConstructor
@Slf4j
public class CaptchaController extends BaseController {private final LoginProperties loginProperties;private final RedisService redisService;@ApiOperation(value = "获取图形验证码", notes = "获取图形验证码")@GetMapping("/code")public ResultInfo<CaptchaCodeDto> getCode() {Captcha captcha = loginProperties.getCaptcha();String uuid = "code-key-" + IdUtil.simpleUUID();// 当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型String captchaValue = captcha.text();if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) {captchaValue = captchaValue.split("\\.")[0];}// 保存redisService.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration());// 验证码信息CaptchaCodeDto captchaCodeDto = new CaptchaCodeDto();captchaCodeDto.setImg(captcha.toBase64());captchaCodeDto.setId(uuid);return ResultInfo.success(captchaCodeDto);}
}

验证

@Log(title = "common.org", businessType = BusinessType.INSERT, operatorType = OperatorType.BUSINESS)
@RequestMapping(value = "/v1/sendMsg", method = RequestMethod.POST)
@ApiOperation(value = "发送验证码", notes = "发送验证码")
public AjaxResult sendMsg(@RequestBody @Validated SendMsgDto dto) {// 图形验证码验证Object captchaResult = redisService.get(dto.getId());redisService.del(dto.getId());Utils.assertNotEmpty(captchaResult, "图形验证码已过期,请点击图形验证码刷新后重试!");Utils.assertEquals(captchaResult.toString(), dto.getCaptchaResult(), "图形验证码错误!");// IP验证String ip = IpUtils.getIpAddr(ServletUtils.getRequest());Integer count = Optional.ofNullable((Integer) redisService.get(ip)).orElse(0);Utils.assertTrue(count < 20, "今日使用验证码次数超限!");redisService.set(ip, ++count, 86400);// 判断5分钟之内是否发送了验证码,如果有的话就发送重复的至用户手机,减少有时候并发引起的重复发送不同的验证码用户不知道用哪个String key = RedisKey.SMSCODE + dto.getPhone();// 查询redis中是否有此号码的验证码,如果有就发送老的Integer verificationCode = Integer.valueOf(RandomValueUtils.randomCode(4));Object oldVerificationCode = redisService.get(key);if (Utils.isNotEmpty(oldVerificationCode)) {verificationCode = (Integer) oldVerificationCode;}msg = "#code#=" + verificationCode;redisService.set(key, verificationCode, 300);// 执行真正的发短信逻辑
}
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;/*** 获取IP方法* * @author Runner*/
public class IpUtils
{public static String getIpAddr(HttpServletRequest request){if (request == null){return "unknown";}String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getHeader("X-Forwarded-For");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getHeader("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;}public static boolean internalIp(String ip){byte[] addr = textToNumericFormatV4(ip);return internalIp(addr) || "0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip);}private static boolean internalIp(byte[] addr){if(addr == null)return false;final byte b0 = addr[0];final byte b1 = addr[1];// 10.x.x.x/8final byte SECTION_1 = 0x0A;// 172.16.x.x/12final byte SECTION_2 = (byte) 0xAC;final byte SECTION_3 = (byte) 0x10;final byte SECTION_4 = (byte) 0x1F;// 192.168.x.x/16final byte SECTION_5 = (byte) 0xC0;final byte SECTION_6 = (byte) 0xA8;switch (b0){case SECTION_1:return true;case SECTION_2:if (b1 >= SECTION_3 && b1 <= SECTION_4){return true;}case SECTION_5:switch (b1){case SECTION_6:return true;}default:return false;}}/*** 将IPv4地址转换成字节* * @param IPv4地址* @return byte 字节*/public static byte[] textToNumericFormatV4(String text){if (text.length() == 0){return null;}byte[] bytes = new byte[4];String[] elements = text.split("\\.", -1);try{long l;int i;switch (elements.length){case 1:l = Long.parseLong(elements[0]);if ((l < 0L) || (l > 4294967295L))return null;bytes[0] = (byte) (int) (l >> 24 & 0xFF);bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 2:l = Integer.parseInt(elements[0]);if ((l < 0L) || (l > 255L))return null;bytes[0] = (byte) (int) (l & 0xFF);l = Integer.parseInt(elements[1]);if ((l < 0L) || (l > 16777215L))return null;bytes[1] = (byte) (int) (l >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 3:for (i = 0; i < 2; ++i){l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L))return null;bytes[i] = (byte) (int) (l & 0xFF);}l = Integer.parseInt(elements[2]);if ((l < 0L) || (l > 65535L))return null;bytes[2] = (byte) (int) (l >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 4:for (i = 0; i < 4; ++i){l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L))return null;bytes[i] = (byte) (int) (l & 0xFF);}break;default:return null;}}catch (NumberFormatException e){return null;}return bytes;}public static String getHostIp(){try{return InetAddress.getLocalHost().getHostAddress();}catch (UnknownHostException e){}return "127.0.0.1";}public static String getHostName(){try{return InetAddress.getLocalHost().getHostName();}catch (UnknownHostException e){}return "未知";}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/18526.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JDBC的书写

文章目录 基本概念操作数据库方式一&#xff08;不建议使用这种查询&#xff0c;可以sql注入&#xff09;读取properties文件 事务转账示例 获取id连接池 基本概念 持久化:把数据放在磁盘上&#xff0c;断电后还是有数据。使用execute 执行增删改返回false,查返回true 操作数…

AI 绘画Stable Diffusion 研究(三)sd模型种类介绍及安装使用详解

本文使用工具&#xff0c;作者:秋葉aaaki 免责声明: 工具免费提供 无任何盈利目的 大家好&#xff0c;我是风雨无阻。 今天为大家带来的是 AI 绘画Stable Diffusion 研究&#xff08;三&#xff09;sd模型种类介绍及安装使用详解。 目前&#xff0c;AI 绘画Stable Diffusion的…

css3 hover border 流动效果

/* Hover 边线流动 */.hoverDrawLine {border: 0 !important;position: relative;border-radius: 5px;--border-color: #60daaa; } .hoverDrawLine::before, .hoverDrawLine::after {box-sizing: border-box;content: ;position: absolute;border: 2px solid transparent;borde…

生成对抗网络DCGAN学习实践

在AI内容生成领域&#xff0c;有三种常见的AI模型技术&#xff1a;GAN、VAE、Diffusion。其中&#xff0c;Diffusion是较新的技术&#xff0c;相关资料较为稀缺。VAE通常更多用于压缩任务&#xff0c;而GAN由于其问世较早&#xff0c;相关的开源项目和科普文章也更加全面&#…

【机器学习】Gradient Descent

Gradient Descent for Linear Regression 1、梯度下降2、梯度下降算法的实现(1) 计算梯度(2) 梯度下降(3) 梯度下降的cost与迭代次数(4) 预测 3、绘图4、学习率 首先导入所需的库&#xff1a; import math, copy import numpy as np import matplotlib.pyplot as plt plt.styl…

Devops系统中jira平台迁移

需求:把aws中的devops系统迁移到华为云中,其中主要是jira系统中的数据迁移,主要方法为在华为云中建立一套 与aws相同的devops平台,再把数据库和文件系统中的数据迁移,最后进行测试。 主要涉及到的服务集群CCE、数据库mysql、弹性文件服务SFS、数据复制DRS、弹性负载均衡ELB。 迁…

问道管理:补仓什么意思?怎么补仓可以降低成本?

补仓这个术语我们在理财出资中经常听到&#xff0c;例如基金补仓&#xff0c;股票补仓。那么&#xff0c;补仓什么意思&#xff1f;怎样补仓能够降低成本&#xff1f;问道管理为我们预备了相关内容&#xff0c;以供参阅。 补仓什么意思&#xff1f; 股票补仓是指出资者在某一只…

Debian 12.1 “书虫 “发布,包含 89 个错误修复和 26 个安全更新

导读Debian 项目今天宣布&#xff0c;作为最新 Debian GNU/Linux 12 “书虫 “操作系统系列的首个 ISO 更新&#xff0c;Debian 12.1 正式发布并全面上市。 Debian 12.1 是在 Debian GNU/Linux 12 “书虫 “发布六周后推出的&#xff0c;目的是为那些希望在新硬件上部署操作系统…

Java学习一 --- Java简介

Java是一种高级编程语言&#xff0c;由Sun Microsystems创建并于1995年发布。它是一种面向对象的语言&#xff0c;被广泛应用于Web、移动设备、桌面应用程序、游戏、数据库等领域。Java具有跨平台特性&#xff0c;即一次编写&#xff0c;多平台运行&#xff0c;所以它也被称为“…

Vivado进行自定义IP封装

一. 简介 本篇文章将介绍如何使用Vivado来对上篇文章(FPGA驱动SPI屏幕)中的代码进行一个IP封装&#xff0c;Vivado自带的IP核应该都使用过&#xff0c;非常方便。 这里将其封装成IP核的目的主要是为了后续项目的调用&#xff0c;否则当我新建一个项目的时候&#xff0c;我需要将…

探讨未来的法规变革:以算法备案为例

在进入科技高速发展的二十一世纪&#xff0c;越来越多的公司、组织和个人开始采用复杂的算法来优化决策、提升生产力并开创创新。同时&#xff0c;对这些算法的治理、监管和备案也成为了社会关注的焦点。本文将尝试探讨未来算法备案的法规变革&#xff0c;以期给予所有与此相关…

VirtualBox Ubuntu无法安装增强功能以及无法复制粘贴踩坑记录

在VirtualBox安装增强功能想要和主机双向复制粘贴&#xff0c;中间查了很多资料&#xff0c;终于是弄好了。记录一下过程&#xff0c;可能对后来人也有帮助&#xff0c;我把我参考的几篇主要的博客都贴上来了&#xff0c;如果觉得我哪里讲得不清楚的&#xff0c;可以去对应的博…

Shell脚本学习-Shell函数

函数的作用就是将程序里多次被调用的相同代码组合起来&#xff08;函数体&#xff09;&#xff0c;并为其取一个名字&#xff0c;即函数名。其他所有想重复调用这部分代码的地方都只需要调用这个名字就可以了。当需要修改这部分代码时候&#xff0c;只需要修改函数体内的这部分…

NOI2023 打金记

Day -4 最后一场模拟赛&#xff0c;肯定要用力打啊&#xff01; 然而一题不会&#xff0c;呜呜呜。 于是开始拼暴力&#xff0c;写了 90 60 60 210 90 60 60 210 906060210&#xff0c;结果挂成 40 60 60 160 40 60 60 160 406060160。 T1 我将题目转化为&am…

【简单认识GFS分布式文件系统】

文章目录 一.GlusterFS 概述1.GlusterFS简介2.特点3.GlusterFS 术语4.模块化堆栈式架构5.GlusterFS 的工作流程6.GlusterFS的卷类型1、**分布式卷&#xff08;Distribute volume&#xff09;**2、条带卷&#xff08;Stripe volume&#xff09;3、复制卷&#xff08;Replica vol…

JS检测属性位于对象本身还是来自于其原型链

学习原型链时有这个疑问&#xff0c;之前查过了但是老是忘记&#xff0c;现在记录一下&#xff0c;避免忘记。 参考&#xff1a;https://blog.csdn.net/weixin_40920953/article/details/88295651 1.in操作符 in 操作符会在通过对象能够访问给定属性时返回true&#xff0c;无…

Web后端基本设计思想

JavaWeb应用的后端一般基于MVC和三层架构思想实现。 MVC是一种设计模式&#xff0c;用于开发用户界面和交互式应用程序。M即Model&#xff0c;业务模型&#xff0c;负责处理应用程序的业务逻辑和数据&#xff1b;V即View&#xff0c;视图&#xff0c;负责给用户展示界面和数据&…

在一台电脑上,如何配置多个SSH Key?

场景描述 开发人员通常只会生成一个SSH Key&#xff0c;名字叫id_rsa&#xff0c;然后提交到多个不同的网站&#xff08;如&#xff1a;GitHub、CodeArts或Gitee&#xff09;。 但是也存在另一种需要&#xff0c;在同一个网站上&#xff0c;注册了两个用户名&#xff0c;通常…

快速创建vue3+vite+ts项目

安装nodejs 创建项目 npm init vitelatest 默认之后回车 选择项目名字my-vue-project 选择vue框架 选择ts 运行项目 cd my-vue-project npm install --registryhttps://registry.npm.taobao.org npm run dev

Vue2 第十二节 Vue组件化编程(一)

1.模块与组件&#xff0c;模块化与组件化概念 2. 非单文件组件 3. 组件编写注意事项 4. 组件的嵌套 一. 模块与组件&#xff0c;模块化与组件化 传统方式编写存在的问题 &#xff08;1&#xff09;依赖关系混乱&#xff0c;不好维护 &#xff08;2&#xff09;代码的复用…