当前位置: 首页 > news >正文

基于SpringBoot实现验证码功能

目录

一 实现思路

二 代码实现

三 代码汇总


现在的登录都需要输入验证码用来检测是否是真人登录,所以验证码功能在现在是非常普遍的,那么接下来我们就基于springboot来实现验证码功能。

一 实现思路

        今天我们介绍的是两种主流的验证码,一种就是上图所示的需要进行计算的验证码,另外一种就是不需要计算,直接输入的验证码。 

1.如果验证码的类型是一个计算类型验证码

* 那么我们就需要分析一下:
* 比如一个计算类型的验证码:1+2=?
* 那么我们到时候应该填入的验证码的答案是:3
* 所以这个验证码它包含了两个部分,一个是"1+2=?" 另外一个是"3"
* 其实生成这个计算问题是我们自己来实现的,我们的工具类已经封装好了计算提,比如:“1+2=3”
* 所以我们现在要做的主要有三步:
* 1.得到这个算式后,将算是“1+2=3”分割成两个部分,“1+2=?” 和 “3”
* 2.然后我们需要把它们存储到缓存中去,前面可以当作key,后面可以当作value,然后进行验证答案的正确性
* 3.最后,我们需要把“1+2=?”制作成一个图片返回给前端,用于展示给用户,

 2.如果验证码的类型是一个普通的图形验证码

    那么我们不要分为表达式和答案两个部分,我们只需要把生成的这个图形直接存入缓存中去就可以了。

    但是因为我们这两种类型是在同一个类中进行判断的,所以最好还是用两个变量来接收。

上面这两中类型的验证码判断完成之后,不管是那种类型,最后都需要把数据存到缓存中,并且都会生成一个Base64编码的一个图片,我们只需要返回这个图片即可,还需要一个uuid,因为这个是用来作为key的唯一标识。

二 代码实现

开始代码之前,先介绍一下我注入的几个bean:

    @Autowiredprivate ISysConfigService configService ;  //判断是否开启验证码@Autowiredprivate FeiSiConfig feiSiConfig ; //配置信息@Autowiredprivate KaptchaTextCreator kaptchaTextCreator ; //随机生成验证码表达式@Autowiredprivate RedisCache redisCache ; //存储数据到缓存@Resource(name = "captchaProducer")private Producer captchaProducer; //生成普通类型验证码图片@Resource(name="captchaProducerMath")private Producer  captchaProducerMath;  //生成计算类型验证码图片

① 先判断有没有开启验证码功能。

我们有一个网页功能的数据库表,里面可以选择是否开启验证码功能,并且在类加载的时候,我们就已经使用 @PostConstruct(标记在方法上, 当这个bean创建完成,自动执行这个注解标记方法,要求这个方法无参 类似 init-method配置) 注解将数据库的数据缓存在了redis里面。

所以我们可以判断先判断有没有开启验证码功能:

//        先要判断一下是否开启了验证码功能,如果没有开启,则不需要进行生成验证码的操作了boolean captchaEnabled = configService.selectCaptchaEnabled();if (!captchaEnabled){return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回没有开启验证码信息给前端}
configService这个类就是我们用来初始化缓存数据的,这个类代码如下:
package com.fs.system.service.ipml;import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fs.common.constant.CacheConstants;
import com.fs.common.core.pojo.SysConfig;
import com.fs.common.util.RedisCache;
import com.fs.common.util.StringUtils;
import com.fs.system.mapper.SysConfigMapper;
import com.fs.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;@Service
public class SysConfigServiceImpl  implements ISysConfigService {@Autowiredprivate SysConfigMapper sysConfigMapper;@Autowiredprivate RedisCache redisCache;@PostConstruct  //标记在方法上, 当这个bean创建完成,自动执行这个注解标记方法,要求这个方法无参  类似 init-method配置public void  init(){loadingConfigCache();}//    初始化数据,当系统加载的时候,把数据存入到缓存中去@Overridepublic void loadingConfigCache() {System.out.println("初始化数据...");//查询数据库List<SysConfig> sysConfigs = sysConfigMapper.selectList(null);//保存到redis缓存中sysConfigs.forEach((sysConfig)->{redisCache.setCacheObject(getCacheKey(sysConfig.getConfigKey()),sysConfig.getConfigValue());});}/*** 构建参数配置表的redis的key* @param configKey* @return*/
//    获取redis缓存中的keyprivate String getCacheKey(String configKey){return CacheConstants.SYS_CONFIG_KEY+configKey;}//    判断账号的验证码是否以及开启@Overridepublic boolean selectCaptchaEnabled() {String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");if (StrUtil.isBlank(cacheObject)){
//            如果查询出来的是空,则说明该账号是第一次登录,则默认开启验证码return true ;}
//        如果不是空,那么查询到的数据不是true就是false,但是存到数据库的并不是Boolean类型而是String类型,
//        所以我们借用工具直接把字符串转成对应的Boolean类型return Convert.toBool(cacheObject) ;}//    根据账号的key获取缓存中的value@Overridepublic String selectConfigByKey(String configKey) {
//       1. 如果从redis中得到了数据,那么直接返回String cacheObject = redisCache.getCacheObject(getCacheKey(configKey));
//        我们需要返回的是一个String类型,所以需要用工具包转为String类型String toStr = Convert.toStr(cacheObject);if (StrUtil.isNotBlank(toStr)){return toStr ;}
//       2.如果没有得到数据,那么我们需要从sys_config数据库表中查询数据,同时把查询到的数据存入缓存中LambdaQueryWrapper<SysConfig> queryWrapper=new LambdaQueryWrapper<>();queryWrapper.eq(SysConfig::getConfigKey,configKey);SysConfig sysConfig = sysConfigMapper.selectOne(queryWrapper);
//        如果查到有数据,就存入redis并且返回if(Objects.nonNull(sysConfig)){  //这里判空的方法不能继续和上面一样,因为sysConfig并不是字符串类型,而是一个对象
//            获取到值String configValue = sysConfig.getConfigValue();redisCache.setCacheObject(getCacheKey(configKey),configValue);return configValue;}
//        否则没有查到数据,则说明没有信息return null ;}
}

在进行正式写逻辑代码之前,我们需要引入几个变量。

按照我们上面分析的,如果是一个计算类型的验证码,那么我们一个需要四个变量:

一个变量是表达式的前半部分,一个变量是表达式的答案,

一个变量是用于存储生成的验证码图片的Base64编码,

最后一个就是验证码数据存储在redis作为唯一标识key的uuid

        BufferedImage image = null ;//图片String expression ; //表达式部分String answer ; //答案部分String uuid  = UUID.randomUUID().toString();; //uuid

② 判断开启后,我们接下来需要判断使用的是哪一种验证码,具体使用哪一种是我们自己配置好的,我们规定math是计算类型的验证码,char是普通验证码。

         /*** 至于这个captchaType的值是根据配置文件设置的,因为这个FeisiConfig是一个配置类* 它加了‘@ConfigurationProperties(prefix = "fs")’这个注解,* 而配置文件规定math是计算类型验证码,char是图形类型验证码*/String captchaType = feiSiConfig.getCaptchaType();

③ 如果是计算类型的验证码,那么我们就需要按照以下步骤走:

1.首先,得到生成的计算表达式(由我们封装的工具类生成)

//            获取一个随机生成的表达式(随机生成的工具封装在KaptchaTextCreator类中)String text = kaptchaTextCreator.getText();

  生成表达式的工具类代码如下:

package com.fs.system.util;import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import org.springframework.stereotype.Component;import java.util.Random;@Component
public class KaptchaTextCreator  extends DefaultTextCreator {private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");@Overridepublic String getText(){Integer result = 0;Random random = new Random();int x = random.nextInt(10);int y = random.nextInt(10);StringBuilder suChinese = new StringBuilder();int randomoperands = random.nextInt(3);if (randomoperands == 0){result = x * y;suChinese.append(CNUMBERS[x]);suChinese.append("*");suChinese.append(CNUMBERS[y]);}else if (randomoperands == 1){if ((x != 0) && y % x == 0){result = y / x;suChinese.append(CNUMBERS[y]);suChinese.append("/");suChinese.append(CNUMBERS[x]);}else{result = x + y;suChinese.append(CNUMBERS[x]);suChinese.append("+");suChinese.append(CNUMBERS[y]);}}else{if (x >= y){result = x - y;suChinese.append(CNUMBERS[x]);suChinese.append("-");suChinese.append(CNUMBERS[y]);}else{result = y - x;suChinese.append(CNUMBERS[y]);suChinese.append("-");suChinese.append(CNUMBERS[x]);}}suChinese.append("=?@" + result);return suChinese.toString();  //5+6=?@11}
}

2.得到表达式之后,我们需要对这个表达式进行分割,分成表达式和答案两部分

//            这个表达式其实我们在工具类生成的时候做了处理,text其实是“1+2=@3”,这样就方便分离表达式和答案
//            分割表达式expression = text.substring(0,text.lastIndexOf("@")) ; //"1+2=?"answer = text.substring(text.lastIndexOf("@")+1) ; //"@"

3.正常来说,接下来我们需要把数据存到缓存中去,但是因为普通类型的验证码也有这一步,所以这一部分重复逻辑就放在最后,我们现在需要根据前面的表达式部分来生成一张图片。

//            最后,我们需要把“1+2=?”制作成一个图片返回给前端,用于展示给用户,
//            制作成图片也有专门的工具类,然后我们需要把这个图片转成64编码返回给前端,这个功能代码其实是固定的//生成验证码图片(google.code.kaptcha提供的工具类Product)image = captchaProducerMath.createImage(expression);

   

captchaProducerMath是谷歌提供的工具类,我们只需要注入使用就行。

4.正常来说,现在应该是将验证码图片转成Base64编码,然后返回给前端展示,但是和上面缓存数据一样,两种编码类型都是获取到验证码数据缓存在redis并且把生成的验证码图片以Base64编码格式返回给前端,所以我们接下来就是为普通验证码类型获取验证码数据和生成图片。

④ 如果是普通类型的验证码

    else {/*** 如果不是计算式类型的验证码,那就是图形类型的验证码,那么就更简单了。* 只需要把图形验证码存到缓存中,然后再把图片返回给前端就好*/
//            图形验证码的话不能和上面一样生成表达式了,而是随机生成一个文本,然后把文本赋值给exception和answer,这样方便存储expression = answer= captchaProducer.createText();
//            再把这个文本转成验证码图片image  = captchaProducer.createImage(expression) ;}

captchaProducer和captchaProducerMath其实是一个类,知识取了不一样的名字方便区分。

⑤ 上面两种类型的验证码都已经成功得到表达式(exception),答案(anwser)(普通类型这个都一样),生成的图片(image)。

因为上面是哪一种类型就会生成哪一种验证码的数据,所以我们最后只需要生成一个uuid唯一标识作为key,然后把answer作为value存储在redis中。然后把image转换成Base64编码。

最后返回给前端image,uuid即可。

//    然后把答案存入到redis中,当然了key不能直接用表达式,因为有可能会重复
//            所以我们用uuid来作为key,然后把uuid给前端,前端在访问的时候再把uuid传来进行验证uuid  = UUID.randomUUID().toString();; //uuid//Constants.CAPTCHA_EXPIRATION :表示2 , TimeUnit.MINUTES:表示分钟,即这个验证码数据只存储2分钟redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY+uuid,answer,Constants.CAPTCHA_EXPIRATION , TimeUnit.MINUTES);//最后再把生成的验证码图片转成Base64编码//转之前需要先把图片转成字节数组FastByteArrayOutputStream os = new FastByteArrayOutputStream();ImageIO.write(image,"jpeg",os);//把图片通过jpeg类型写进字节数组//再转成Base64编码String base64Str = Base64.encode(os.toByteArray());//得到的Base64编码不能直接返回,还需要按照规定添加头部String base64Img = "data:image/jpeg;base64," +base64Str;//BASE64对密文进行传输加密时,可能出现\r\n//原因: RFC2045中有规定:即Base64一行不能超过76字符,超过则添加回车换行符。//解决方案: BASE64加密后,对\r\n进行去除base64Img= base64Img.replaceAll("\r|\n", "");
//            最后把这个Base64编码的表达式图片验证码和用于表示key的uuid返回给前端ajaxResult.put("img",base64Img);ajaxResult.put("uuid",uuid) ;os.close();return ajaxResult ;

三 代码汇总

最后,我将完整的代码展出,并且包括了需要用到的工具类的代码

Controller层主要的逻辑部分代码:

package com.fs.system.web.controller.common;import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.UUID;
import com.fs.common.config.FeiSiConfig;
import com.fs.common.constant.CacheConstants;
import com.fs.common.constant.Constants;
import com.fs.common.core.vo.AjaxResult;
import com.fs.common.util.RedisCache;
import com.fs.system.service.ISysConfigService;
import com.fs.system.util.KaptchaTextCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.google.code.kaptcha.Producer;/*** 验证码操作处理**/
@RestController
public class CaptchaController{@Autowiredprivate ISysConfigService configService ;  //判断是否开启验证码@Autowiredprivate FeiSiConfig feiSiConfig ; //配置信息@Autowiredprivate KaptchaTextCreator kaptchaTextCreator ; //随机生成验证码表达式@Autowiredprivate RedisCache redisCache ; //存储数据到缓存@Resource(name = "captchaProducer")private Producer captchaProducer; //生成普通类型验证码图片@Resource(name="captchaProducerMath")private Producer  captchaProducerMath;  //生成计算类型验证码图片/*** 生成验证码*/@GetMapping("/captchaImage")public AjaxResult getCode(HttpServletResponse response) throws IOException{AjaxResult ajaxResult = AjaxResult.success() ;/*** 思路:* 我们目前验证分为两种,一种是计算类型验证码,一种是单纯的图形验证码* 所以,我们第一步就是判断验证码的类型,而验证码的类型是我们在配置文件配置好的*///        先要判断一下是否开启了验证码功能,如果没有开启,则不需要进行生成验证码的操作了boolean captchaEnabled = configService.selectCaptchaEnabled();if (!captchaEnabled){return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回没有开启验证码信息给前端}BufferedImage image = null ;//图片String expression ; //表达式部分String answer ; //答案部分String uuid  ;; //uuid//        否则说明开启了验证码,那么需要判断使用的是什么类型的验证码/*** 至于这个captchaType的值是根据配置文件设置的,因为这个FeisiConfig是一个配置类* 它加了‘@ConfigurationProperties(prefix = "fs")’这个注解,* 而配置文件规定math是计算类型验证码,char是图形类型验证码*/String captchaType = feiSiConfig.getCaptchaType();if (captchaType.equals("math")){
//            如果验证码的类型是一个计算类型验证码/*** 那么我们就需要分析一下:* 比如一个计算类型的验证码:1+2=?* 那么我们到时候应该填入的验证码的答案是:3* 所以这个验证码它包含了两个部分,一个是"1+2=?" 另外一个是"3"* 其实生成这个计算问题是我们自己来实现的,我们的工具类已经封装好了计算提,比如:“1+2=3”* 所以我们现在要做的主要有三步:* 1.得到这个算式后,将算是“1+2=3”分割成两个部分,“1+2=?” 和 “3”* 2.然后我们需要把它们存储到缓存中去,前面可以当作key,后面可以当作value,然后进行验证答案的正确性* 3.最后,我们需要把“1+2=?”制作成一个图片返回给前端,用于展示给用户,*/
//            获取一个随机生成的表达式(随机生成的工具封装在KaptchaTextCreator类中)String text = kaptchaTextCreator.getText();
//            这个表达式其实我们在工具类生成的时候做了处理,text其实是“1+2=@3”,这样就方便分离表达式和答案
//            分割表达式expression = text.substring(0,text.lastIndexOf("@")) ; //"1+2=?"answer = text.substring(text.lastIndexOf("@")+1) ; //"@"//            最后,我们需要把“1+2=?”制作成一个图片返回给前端,用于展示给用户,
//            制作成图片也有专门的工具类,然后我们需要把这个图片转成64编码返回给前端,这个功能代码其实是固定的//生成验证码图片(google.code.kaptcha提供的工具类Product)image = captchaProducerMath.createImage(expression);}else {/*** 如果不是计算式类型的验证码,那就是图形类型的验证码,那么就更简单了。* 只需要把图形验证码存到缓存中,然后再把图片返回给前端就好*/
//            图形验证码的话不能和上面一样生成表达式了,而是随机生成一个文本,然后把文本赋值给exception和answer,这样方便存储expression = answer= captchaProducer.createText();
//            再把这个文本转成验证码图片image  = captchaProducer.createImage(expression) ;}System.out.println(expression+":"+answer);//    然后把答案存入到redis中,当然了key不能直接用表达式,因为有可能会重复
//            所以我们用uuid来作为key,然后把uuid给前端,前端在访问的时候再把uuid传来进行验证uuid  = UUID.randomUUID().toString();; //uuid//Constants.CAPTCHA_EXPIRATION :表示2 , TimeUnit.MINUTES:表示分钟,即这个验证码数据只存储2分钟redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY+uuid,answer,Constants.CAPTCHA_EXPIRATION , TimeUnit.MINUTES);//最后再把生成的验证码图片转成Base64编码//转之前需要先把图片转成字节数组FastByteArrayOutputStream os = new FastByteArrayOutputStream();ImageIO.write(image,"jpeg",os);//把图片通过jpeg类型写进字节数组//再转成Base64编码String base64Str = Base64.encode(os.toByteArray());//得到的Base64编码不能直接返回,还需要按照规定添加头部String base64Img = "data:image/jpeg;base64," +base64Str;//BASE64对密文进行传输加密时,可能出现\r\n//原因: RFC2045中有规定:即Base64一行不能超过76字符,超过则添加回车换行符。//解决方案: BASE64加密后,对\r\n进行去除base64Img= base64Img.replaceAll("\r|\n", "");
//            最后把这个Base64编码的表达式图片验证码和用于表示key的uuid返回给前端ajaxResult.put("img",base64Img);ajaxResult.put("uuid",uuid) ;os.close();return ajaxResult ;}
}

 用于判断是否开启验证码的功能的configService类代码:

package com.fs.system.service.ipml;import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fs.common.constant.CacheConstants;
import com.fs.common.core.pojo.SysConfig;
import com.fs.common.util.RedisCache;
import com.fs.common.util.StringUtils;
import com.fs.system.mapper.SysConfigMapper;
import com.fs.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;@Service
public class SysConfigServiceImpl  implements ISysConfigService {@Autowiredprivate SysConfigMapper sysConfigMapper;@Autowiredprivate RedisCache redisCache;@PostConstruct  //标记在方法上, 当这个bean创建完成,自动执行这个注解标记方法,要求这个方法无参  类似 init-method配置public void  init(){loadingConfigCache();}//    初始化数据,当系统加载的时候,把数据存入到缓存中去@Overridepublic void loadingConfigCache() {System.out.println("初始化数据...");//查询数据库List<SysConfig> sysConfigs = sysConfigMapper.selectList(null);//保存到redis缓存中sysConfigs.forEach((sysConfig)->{redisCache.setCacheObject(getCacheKey(sysConfig.getConfigKey()),sysConfig.getConfigValue());});}/*** 构建参数配置表的redis的key* @param configKey* @return*/
//    获取redis缓存中的keyprivate String getCacheKey(String configKey){return CacheConstants.SYS_CONFIG_KEY+configKey;}//    判断账号的验证码是否以及开启@Overridepublic boolean selectCaptchaEnabled() {String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");if (StrUtil.isBlank(cacheObject)){
//            如果查询出来的是空,则说明该账号是第一次登录,则默认开启验证码return true ;}
//        如果不是空,那么查询到的数据不是true就是false,但是存到数据库的并不是Boolean类型而是String类型,
//        所以我们借用工具直接把字符串转成对应的Boolean类型return Convert.toBool(cacheObject) ;}//    根据账号的key获取缓存中的value@Overridepublic String selectConfigByKey(String configKey) {
//       1. 如果从redis中得到了数据,那么直接返回String cacheObject = redisCache.getCacheObject(getCacheKey(configKey));
//        我们需要返回的是一个String类型,所以需要用工具包转为String类型String toStr = Convert.toStr(cacheObject);if (StrUtil.isNotBlank(toStr)){return toStr ;}
//       2.如果没有得到数据,那么我们需要从sys_config数据库表中查询数据,同时把查询到的数据存入缓存中LambdaQueryWrapper<SysConfig> queryWrapper=new LambdaQueryWrapper<>();queryWrapper.eq(SysConfig::getConfigKey,configKey);SysConfig sysConfig = sysConfigMapper.selectOne(queryWrapper);
//        如果查到有数据,就存入redis并且返回if(Objects.nonNull(sysConfig)){  //这里判空的方法不能继续和上面一样,因为sysConfig并不是字符串类型,而是一个对象
//            获取到值String configValue = sysConfig.getConfigValue();redisCache.setCacheObject(getCacheKey(configKey),configValue);return configValue;}
//        否则没有查到数据,则说明没有信息return null ;}
}

验证码类型配置信息的配置类feiSiConfig , 以及配置文件yam的代码

package com.fs.common.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** 读取项目相关配置**/
@Component
@ConfigurationProperties(prefix = "fs")
public class FeiSiConfig
{/** 项目名称 */private String name;/** 版本 */private String version;/** 版权年份 */private String copyrightYear;/** 上传路径 */private static String profile;/** 获取地址开关 */private static boolean addressEnabled;/** 验证码类型 */private static String captchaType;public String getName(){return name;}public void setName(String name){this.name = name;}public String getVersion(){return version;}public void setVersion(String version){this.version = version;}public String getCopyrightYear(){return copyrightYear;}public void setCopyrightYear(String copyrightYear){this.copyrightYear = copyrightYear;}public static String getProfile(){return profile;}public void setProfile(String profile){FeiSiConfig.profile = profile;}public static boolean isAddressEnabled(){return addressEnabled;}public void setAddressEnabled(boolean addressEnabled){FeiSiConfig.addressEnabled = addressEnabled;}public static String getCaptchaType() {return captchaType;}public void setCaptchaType(String captchaType) {FeiSiConfig.captchaType = captchaType;}/*** 获取导入上传路径*/public static String getImportPath(){return getProfile() + "/import";}/*** 获取头像上传路径*/public static String getAvatarPath(){return getProfile() + "/avatar";}/*** 获取下载路径*/public static String getDownloadPath(){return getProfile() + "/download/";}/*** 获取上传路径*/public static String getUploadPath(){return getProfile() + "/upload";}
}

 yml配置文件:

# 项目相关配置
fs:# 名称name: FeiSi# 版本version: 1.0.0# 版权年份copyrightYear: 2023# 文件路径 示例( Windows配置D:/feisi/uploadPath,Linux配置 /home/feisi/uploadPath)profile: D:/feisi/uploadPath# 获取ip地址开关addressEnabled: false# 验证码类型 math 数字计算 char 字符验证captchaType: math

生成随机计算类型表达式的 kaptchaTextCreator类代码

package com.fs.system.util;import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import org.springframework.stereotype.Component;import java.util.Random;@Component
public class KaptchaTextCreator  extends DefaultTextCreator {private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");@Overridepublic String getText(){Integer result = 0;Random random = new Random();int x = random.nextInt(10);int y = random.nextInt(10);StringBuilder suChinese = new StringBuilder();int randomoperands = random.nextInt(3);if (randomoperands == 0){result = x * y;suChinese.append(CNUMBERS[x]);suChinese.append("*");suChinese.append(CNUMBERS[y]);}else if (randomoperands == 1){if ((x != 0) && y % x == 0){result = y / x;suChinese.append(CNUMBERS[y]);suChinese.append("/");suChinese.append(CNUMBERS[x]);}else{result = x + y;suChinese.append(CNUMBERS[x]);suChinese.append("+");suChinese.append(CNUMBERS[y]);}}else{if (x >= y){result = x - y;suChinese.append(CNUMBERS[x]);suChinese.append("-");suChinese.append(CNUMBERS[y]);}else{result = y - x;suChinese.append(CNUMBERS[y]);suChinese.append("-");suChinese.append(CNUMBERS[x]);}}suChinese.append("=?@" + result);return suChinese.toString();  //5+6=?@11}
}

封装redis,把数据存储在redis缓存的工具类的redisCache类的代码:

package com.fs.common.util;import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;/*** spring redis 工具类***/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,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);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @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);}/*** 获取有效时间** @param key Redis键* @return 有效时间*/public long getExpire(final String key){return redisTemplate.getExpire(key);}/*** 判断 key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key){return redisTemplate.hasKey(key);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public boolean deleteObject(final Collection collection){return redisTemplate.delete(collection) > 0;}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 删除Hash中的某条数据** @param key Redis键* @param hKey Hash键* @return 是否成功*/public boolean deleteCacheMapValue(final String key, final String hKey){return redisTemplate.opsForHash().delete(key, hKey) > 0;}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}

以上就是实现验证码功能的整个后端代码。

相关文章:

基于SpringBoot实现验证码功能

目录 一 实现思路 二 代码实现 三 代码汇总 现在的登录都需要输入验证码用来检测是否是真人登录&#xff0c;所以验证码功能在现在是非常普遍的&#xff0c;那么接下来我们就基于springboot来实现验证码功能。 一 实现思路 今天我们介绍的是两种主流的验证码&#xff0c;一…...

字节测开面筋大总结!!!!

字节测开 字节 测开 一二三面 面经字节测开实习凉经字节测开一面字节测开一面凉经字节测开一面凉经字节测开一面凉经字节测开一面凉经字节跳动测开&#xff08;电商&#xff09;一面字节测开实习二面字节测开面经字节测开面经字节测开实习一面字节测开一面&#xff08;挂&#…...

Mindspore框架DCGAN模型实现漫画头像生成|(二)DCGAN模型构建

Mindspore框架DCGAN模型实现漫画头像生成 Mindspore框架DCGAN模型实现漫画头像生成|&#xff08;一&#xff09;漫画头像数据集准备Mindspore框架DCGAN模型实现漫画头像生成|&#xff08;二&#xff09;DCGAN模型构建Mindspore框架DCGAN模型实现漫画头像生成|&#xff08;三&a…...

mongo-csharp-driver:MongoDB官方的C#客户端驱动程序!

MongoDB一个开源、高性能、无模式的文档型数据库&#xff0c;在日常项目开发中&#xff0c;运用也是非常广泛。 MongoDB官方也针对各门编程语言&#xff0c;都推出相应的客户端驱动程序&#xff0c;下面一起了解下C#版本。 01 项目简介 mongo-csharp-driver是 MongoDB官方C#…...

网络流量分析>>pcapng文件快速分析有用价值解析

引言 在网络安全和流量管理中&#xff0c;解析网络协议数据包是了解网络行为和检测潜在威胁的关键步骤。本文介绍了如何使用Python解析和分析TCP、UDP和ICMP协议的数据包&#xff0c;并统计端口的访问次数。本文的示例代码展示了如何处理不同协议的数据包&#xff0c;提取关键…...

【大模型系列篇】Vanna-ai基于检索增强(RAG)的sql生成框架

简介 Vanna是基于检索增强(RAG)的sql生成框架 Vanna 使用一种称为 LLM&#xff08;大型语言模型&#xff09;的生成式人工智能。简而言之&#xff0c;这些模型是在大量数据&#xff08;包括一堆在线可用的 SQL 查询&#xff09;上进行训练的&#xff0c;并通过预测响应提示中最…...

【Nacos安装】

这里写目录标题 Nacos安装jar包启动Docker单体Docker集群 Nacos相关配置日志配置数据库配置 Nacos安装 jar包启动 下载jar包 在官方github&#xff0c;根据需求选择相应的版本下载。 解压 tar -zxvf nacos-server-2.4.0.1.tar.gz或者解压到指定目录 tar -zxvf nacos-serv…...

js、ts、argular、nodejs学习心得

工作中需要前端argular开发桌面程序&#xff0c;后端用nodejs开发服务器&#xff0c;商用软件架构...

【Unity】RPG2D龙城纷争(十八)平衡模拟器

更新日期:2024年7月31日。 项目源码:第五章发布(正式开始游戏逻辑的章节) 索引 简介一、BalanceSimulator 类二、RoleAgent 角色代理类三、绘制代理角色四、模拟攻击简介 平衡模拟器用于实时模拟测试角色属性以及要诀属性的数值,以寻找数值设计的平衡性。 介于运行正式游…...

java.lang.IllegalStateException: Duplicate key InventoryDetailDO

以下总结自以下链接 Java8 Duplicate key 异常解决-CSDN博客 原因&#xff1a;由于我们使用了jdk8的新特性中的stream流&#xff0c;将list转换为map集合&#xff0c;但是原来的list集合中存在重复的值&#xff0c;我们不知道如何进行取舍&#xff0c;所以报错 解决方式&…...

Python使用selenium访问网页完成登录——装饰器重试机制汇总

文章目录 示例一:常见装饰器编写重试机制示例二&#xff1a;使用类实现装饰器示例三&#xff1a;使用函数装饰器并返回闭包示例四&#xff1a;使用 wrapt 模块 示例一:常见装饰器编写重试机制 示例代码 import time import traceback import logging from typing import Call…...

“微软蓝屏”事件引发的深度思考:网络安全与系统稳定性的挑战与应对

“微软蓝屏”事件暴露了网络安全哪些问题&#xff1f; 近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件&#xff0c;源于美国电脑安全…...

2024.07纪念一 debezium : spring-boot结合debezium

使用前提&#xff1a; 一、mysql开启了logibin 在mysql的安装路径下的my.ini中 【mysqlid】下 添加 log-binmysql-bin # 开启 binlog binlog-formatROW # 选择 ROW 模式 server_id1 # 配置 MySQL replaction 需要定义&#xff0c;不要和 canal 的 slaveId 重复 参考gitee的项目…...

mysql怎么查询json里面的字段

mysql怎么查询json里面的字段&#xff1a; 要在 MySQL 数据库中查询 JSON 字段中的 city 值&#xff0c;你可以使用 MySQL 提供的 JSON 函数。假设表名是 your_table&#xff0c;包含一个名为 json_column 的 JSON 字段。 以下是一个查询示例&#xff0c;展示如何从 json_colu…...

C++ 右值 左值引用

一.什么是左值引用 右值引用 1.左值引用 左值是一个表示数据的表达式(如变量名或解引用的指针)&#xff0c;我们可以获取它的地址可以对它赋值。定义时const修饰符后的左值&#xff0c;不能给他赋值&#xff0c;但是可以取它的地址。左值引用就是给左值的引用&#xff0c;给左…...

「JavaEE」Spring IoC 1:Bean 的存储

&#x1f387;个人主页 &#x1f387;所属专栏&#xff1a;Spring &#x1f387;欢迎点赞收藏加关注哦&#xff01; IoC 简介 IoC 全称 Inversion of Control&#xff0c;即控制反转 控制反转是指控制权反转&#xff1a;获得依赖对象的过程被反转了 传统开发模式中&…...

springBoot快速搭建WebSocket

添加依赖 在pom.xml中加入WebSocket相关依赖&#xff1a; <dependencies><!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>…...

掌控授权的艺术:Laravel自定义策略模式深度解析

掌控授权的艺术&#xff1a;Laravel自定义策略模式深度解析 在现代Web应用开发中&#xff0c;权限管理是核心功能之一。Laravel框架通过其策略模式提供了一种优雅的方式来处理授权问题。然而&#xff0c;随着应用的复杂性增加&#xff0c;内置的策略可能不足以满足所有需求。这…...

Git操作指令(随时更新)

Git操作指令 一、安装git 1、设置配置信息&#xff1a; # global全局配置 git config --global user.name "Your username" git config --global user.email "Your email"# 显示颜色 git config --global color.ui true# 配置别名&#xff0c;各种指令都…...

SpringSecurity自定义登录方式

自定义登录&#xff1a; 定义Token定义Filter定义Provider配置类中定义登录的接口 自定义AuthenticationToken public class EmailAuthenticationToken extends UsernamePasswordAuthenticationToken{public EmailAuthenticationToken(Object principal, Object credentials) …...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...