基于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);}
}

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

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

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

相关文章

Bouncy Castle集成SM2与SM3

在Bouncy Castle库中&#xff0c;SM2和SM3是两种分别用于非对称加密和数字签名的密码算法&#xff0c;它们也可以结合使用&#xff0c;形成一种高安全性的加密签名方案&#xff0c;即SM2withSM3。以下是对SM2SM3的详细解释&#xff1a; 一、SM2算法 SM2是一种由中国国家密码管…

GEE:设置ui.Map.Layer上交互矢量边界填充颜色为空,只显示边界

一、目标 最近在GEE的交互功能鼓捣一些事情&#xff0c;在利用buffer功能实现了通过选点建立一个矩形后&#xff0c;需要将该矩形填充颜色设为空&#xff0c;只留边界。 然而通过正常设置layer的可视化参数并不能实现这一目的。因此只能另辟蹊径&#xff0c;改为定义矢量边界…

VMware 上安装 CentOS 7 教程 (包含网络设置)

**建议先看一些我安装VMware的教程&#xff0c;有些网络配置需要做一下 1.打开VMware&#xff0c;创建虚拟机 2.勾选自定义&#xff0c;点击下一步 3.点击下一步 4.勾选“稍后安装操作系统”&#xff0c;点击下一步 5.勾选linux&#xff0c;勾选centos7&#xff0c;点击下一步…

pytorch-训练自定义数据集实战

目录 1. 步骤2. 加载数据2.1 继承Dataset2.1.1 生成name2label2.1.2 生成image path, label的文件2.1.3 __len__2.1.3 __getitem__2.1.4 数据切分为train、val、test 3. 建立模型4. 训练和测试4. 完整代码 1. 步骤 加载数据创建模型训练和测试迁移学习 2. 加载数据 这里以宝…

Minos 多主机分布式 docker-compose 集群部署

参考 docker-compose搭建多主机分布式minio - 会bk的鱼 - 博客园 (cnblogs.com) 【运维】docker-compose安装minio集群-CSDN博客 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能 中文地址&#xff1a;MinIO | 用于AI的S3 …

自学JavaScript(放假在家自学第一天)

目录 JavaScript介绍分为以下几点 1.1 JavaScript 是什么 1.2JavaScript书写位置 1.3 Javascript注释 1.4 Javascript结束符 1.5 Javascript输入输出语法 JavaScript(是什么?) 是一种运行在客户端(浏览器)的编程语言&#xff0c;实现人机交互效果。 2.作用(做什么?)网…

PCL-基于超体聚类的LCCP点云分割

目录 一、LCCP方法二、代码实现三、实验结果四、总结五、相关链接 一、LCCP方法 LCCP指的是Local Convexity-Constrained Patch&#xff0c;即局部凸约束补丁的意思。LCCP方法的基本思想是在图像中找到局部区域内的凸结构&#xff0c;并将这些结构用于分割图像或提取特征。这种…

入门 PyQt6 看过来(案例)13~ 制作一个颜色调节器

本文给大家带来一个利用pyqt制作的颜色调节器&#xff0c;通过拨动滚动条或者旋钮就可以调整rgb三色进行颜色的微调&#xff0c;效果如下&#xff1a; 本文实现的是不同的UI设计&#xff0c;实现的相同的功能&#xff0c;我们先分析以下思路&#xff1a; 首先进行UI页面设计分析…

SSL/TLS和SSL VPN

1、SSL/TLS SSL安全套接字层&#xff1a;是一种加密协议&#xff0c;用于在网络通信中建立安全连接。它在应用层和传输层&#xff08;TCP/IP&#xff09;之间提供数据加密、服务器身份验证以及信息完整性验证 SSL只保护TCP流量&#xff0c;不保护UDP协议 TLS&#xff1a;传输层…

VulnHub:cengbox1

靶机下载地址&#xff0c;下载完成后&#xff0c;用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机&#xff08;192.168.31.218&#xff09;同网段存活主机确认目标机ip&#xff0c;并对目标机进行全面扫描。 nmap 192.168.31.…

【VS2019安装+QT配置】

【VS2019安装QT配置】 1. 前言2. 下载visual studio20193. visual studio2019安装4. 环境配置4.1 系统环境变量配置4.2 qt插件开发 5. Visual Studio导入QT项目6. 总结 1. 前言 前期安装了qt&#xff0c;发现creator编辑器并不好用&#xff0c;一点都不时髦。在李大师的指导下&…

[网鼎杯 2020 朱雀组]Nmap(详细解读版)

这道题考察nmap的一些用法,以及escapeshellarg和escapeshellcmd两个函数的绕过&#xff0c;可以看这里PHP escapeshellarg()escapeshellcmd() 之殇 (seebug.org) 两种解题方法&#xff1a; 第一种通过nmap的-iL参数读取扫描一个文件到指定文件中第二种是利用nmap的参数写入we…

昇思25天学习打卡营第1天|快速入门-构建基于MNIST数据集的手写数字识别模型

非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从今天起&#xff0c;我将以打卡的方式&#xff0c;结合原文搬运和个人思考&#xff0c;分享25天的学习内容与成果。为了提升文章质量和阅读体验&#xff0c;我会将思考部分放在最后&#xff0c;供大家探索讨论…

java-数据结构与算法-02-数据结构-05-栈

文章目录 1. 栈1. 概述2. 链表实现3. 数组实现4. 应用 2. 习题E01. 有效的括号-Leetcode 20E02. 后缀表达式求值-Leetcode 120E03. 中缀表达式转后缀E04. 双栈模拟队列-Leetcode 232E05. 单队列模拟栈-Leetcode 225 1. 栈 1. 概述 计算机科学中&#xff0c;stack 是一种线性的…

[python游戏开发]用Python代码制作中国象棋游戏,适合新手小白练手

Pygame 做的中国象棋&#xff0c;一直以来喜欢下象棋&#xff0c;写了 python 就拿来做一个试试&#xff0c;水平有限&#xff0c;希望源码能帮助大家更好的学习 python。总共分为四个文件&#xff0c;chinachess.py 为主文件&#xff0c;constants.py 数据常量&#xff0c;pie…

新版海螺影视主题模板M3.1全解密版本多功能苹果CMSv10后台自适应主题

苹果CMS2022新版海螺影视主题M3.1版本&#xff0c;这个主题我挺喜欢的&#xff0c;之前也有朋友给我提供过原版主题&#xff0c;一直想要破解但是后来找了几个SG11解密的大哥都表示解密需要大几百大洋&#xff0c;所以一直被搁置了。这个版本是完全解密的&#xff0c;无需SG11加…

前端模块化CommonJS、AMD、CMD、ES6

在前端开发中&#xff0c;模块化是一种重要的代码组织方式&#xff0c;它有助于将复杂的代码拆分成可管理的小块&#xff0c;提高代码的可维护性和可重用性。CommonJS、AMD&#xff08;异步模块定义&#xff09;和CMD&#xff08;通用模块定义&#xff09;是三种不同的模块规范…

1、hadoop环境搭建

1、环境配置 ip(/etc/sysconfig/network-scripts) # 网卡1 DEVICEeht0 TYPEEthernet ONBOOTyes NM_CONTROLLEDyes BOOTPROTOstatic IPADDR192.168.59.11 GATEWAY192.168.59.1 NETMASK 255.255.255.0 # 网卡2 DEVICEeht0 TYPEEthernet ONBOOTyes NM_CONTROLLEDyes BOOTPROTOdh…

【React1】React概述、基本使用、脚手架、JSX、组件

文章目录 1. React基础1.1 React 概述1.1.1 什么是React1.1.2 React 的特点声明式基于组件学习一次,随处使用1.2 React 的基本使用1.2.1 React的安装1.2.2 React的使用1.2.3 React常用方法说明React.createElement()ReactDOM.render()1.3 React 脚手架的使用1.3.1 React 脚手架…

基于tkinter的学生信息管理系统之登录界面和主界面菜单设计

目录 一、tkinter的介绍 二、登陆界面的设计 1、登陆界面完整代码 2、部分代码讲解 3、登录的数据模型设计 4、效果展示 三、学生主界面菜单设计 1、学生主界面菜单设计完整代码 2、 部分代码讲解 3、效果展示 四、数据库的模型设计 欢迎大家进来学习和支持&#xff01…