基于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:

java 复制代码
    @Autowired
    private ISysConfigService configService ;  //判断是否开启验证码

    @Autowired
    private FeiSiConfig feiSiConfig ; //配置信息

    @Autowired
    private KaptchaTextCreator kaptchaTextCreator ; //随机生成验证码表达式

    @Autowired
    private RedisCache redisCache ; //存储数据到缓存

    @Resource(name = "captchaProducer")
    private Producer captchaProducer; //生成普通类型验证码图片

    @Resource(name="captchaProducerMath")
    private Producer  captchaProducerMath;  //生成计算类型验证码图片

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

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

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

java 复制代码
//        先要判断一下是否开启了验证码功能,如果没有开启,则不需要进行生成验证码的操作了
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (!captchaEnabled){
            return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回没有开启验证码信息给前端
        }
复制代码
configService这个类就是我们用来初始化缓存数据的,这个类代码如下:
java 复制代码
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 {
    @Autowired
    private SysConfigMapper sysConfigMapper;

    @Autowired
    private RedisCache redisCache;

    @PostConstruct  //标记在方法上, 当这个bean创建完成,自动执行这个注解标记方法,要求这个方法无参  类似 init-method配置
    public void  init(){
        loadingConfigCache();
    }

//    初始化数据,当系统加载的时候,把数据存入到缓存中去
    @Override
    public 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缓存中的key
    private String getCacheKey(String configKey){
        return CacheConstants.SYS_CONFIG_KEY+configKey;
    }

//    判断账号的验证码是否以及开启
    @Override
    public 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
    @Override
    public 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

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

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

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

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

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

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

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

java 复制代码
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(",");

    @Override
    public 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.得到表达式之后,我们需要对这个表达式进行分割,分成表达式和答案两部分

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

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

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

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

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

java 复制代码
    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即可。

java 复制代码
        //    然后把答案存入到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层主要的逻辑部分代码:

java 复制代码
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{
    @Autowired
    private ISysConfigService configService ;  //判断是否开启验证码

    @Autowired
    private FeiSiConfig feiSiConfig ; //配置信息

    @Autowired
    private KaptchaTextCreator kaptchaTextCreator ; //随机生成验证码表达式

    @Autowired
    private 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类代码:

java 复制代码
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 {
    @Autowired
    private SysConfigMapper sysConfigMapper;

    @Autowired
    private RedisCache redisCache;

    @PostConstruct  //标记在方法上, 当这个bean创建完成,自动执行这个注解标记方法,要求这个方法无参  类似 init-method配置
    public void  init(){
        loadingConfigCache();
    }

//    初始化数据,当系统加载的时候,把数据存入到缓存中去
    @Override
    public 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缓存中的key
    private String getCacheKey(String configKey){
        return CacheConstants.SYS_CONFIG_KEY+configKey;
    }

//    判断账号的验证码是否以及开启
    @Override
    public 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
    @Override
    public 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的代码

java 复制代码
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配置文件:

XML 复制代码
# 项目相关配置
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类代码

java 复制代码
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(",");

    @Override
    public 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类的代码:

java 复制代码
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
{
    @Autowired
    public 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);
    }
}

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

相关推荐
一直学习永不止步1 分钟前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
兆。1 分钟前
掌握 PyQt5:从零开始的桌面应用开发
开发语言·爬虫·python·qt
尘浮生4 分钟前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料12 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
明月看潮生24 分钟前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
雷神乐乐28 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|32 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
南宫理的日知录35 分钟前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
逊嘘1 小时前
【Java语言】抽象类与接口
java·开发语言·jvm