基于SpringBoot实现验证码功能

news2024/11/24 10:39:55

目录

一 实现思路

二 代码实现

三 代码汇总


现在的登录都需要输入验证码用来检测是否是真人登录,所以验证码功能在现在是非常普遍的,那么接下来我们就基于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:

    @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里面。

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

//        先要判断一下是否开启了验证码功能,如果没有开启,则不需要进行生成验证码的操作了
        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 {
    @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

        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(",");

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

//            这个表达式其实我们在工具类生成的时候做了处理,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{
    @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类代码:

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的代码

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(",");

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

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

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

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

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

相关文章

IP地址专用SSL/https证书——10分钟签发

一般常用的SSL证书多为域名型SSL证书&#xff0c;即需要提供准确的域名。如果不能提供域名&#xff0c;只能提供IP地址&#xff0c;则需要一种特殊的SSL证书——IP地址证书。下面是IP地址证书的申请教程 IP地址专用SSL证书获取链接https://www.joyssl.com/certificate/select/…

SQL中的LEFT JOIN、RIGHT JOIN和INNER JOIN

在SQL中&#xff0c;JOIN操作是连接两个或多个数据库表&#xff0c;并根据两个表之间的共同列&#xff08;通常是主键和外键&#xff09;返回数据的重要方法。其中&#xff0c;LEFT JOIN&#xff08;左连接&#xff09;、RIGHT JOIN&#xff08;右连接&#xff09;和INNER JOIN…

Open3D 将点云投影到球面

目录 一、概述 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2投影后点云 前期试读&#xff0c;后续会将博客加入下列链接的专栏&#xff0c;欢迎订阅 Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSDN博客 一、概述…

【表单组件】地址组件新增精简模式

07/17 主要更新模块概览 快速筛选 精简模式 触发条件 自定义域名 01 表单管理 1.1 【表单组件】-数据关联组件新增快速筛选功能 说明&#xff1a; 数据关联组件新增快速筛选功能&#xff0c;用户在数据关联组件选择数据时&#xff0c;可以通过快速筛选功能&#xff0…

黑马头条Day07-app端文章搜索

一、今日内容介绍 1. App端搜索效果图 2. 今日内容 &#xff08;1&#xff09;文章搜索 Elasticsearch环境搭建索引库创建文章搜索多条件复合查询索引数据同步 &#xff08;2&#xff09;搜索历史记录 MongoDB环境搭建异步保存搜索历史查看搜索历史列表删除搜索历史 &…

Linux源码安装的Redis如何配置systemd管理并设置开机启动

文章目录 实验前提实验 实验前提 已完成源码安装并能正常启动redis /usr/local/bin/redis-server能正常启动redis 实验 vim /etc/systemd/system/redis.service内容如下&#xff1a; [unit] Descriptionredis-server Afternetwork.target[Service] Typeforking ExecStart/…

从零开始创建vue3项目——包含项目初始化、element-plus、eslint、axios、router、pinia、echarts

项目启动 初始化vue3项目 这里建议先下载pnpm&#xff0c;下载速度更快&#xff0c;如果还没下载可以使用 npm install -g pnpm 如果遇到报错问题&#xff0c;如下 可以在命令行输入下面的指令以切换到淘宝镜像源 npm config set registry https://registry.npm.taobao.org…

Facebook云手机引流运营方法

Facebook&#xff0c;作为全球用户数达到30亿的最大社交媒体平台&#xff0c;汇聚了各类客户群体&#xff0c;蕴藏着巨大的商业潜力。对于外贸电商而言&#xff0c;Facebook是不可或缺的营销平台。一方面&#xff0c;我们可以在Facebook上发布产品信息&#xff0c;寻找并筛选目…

linux系统进程占cpu 100%解决步骤

1.查找进程 ps aux 查看指定进程: ps aux | grep process_name2.根据进程查找对应的主进程 pstree -p | grep process_name 3.查看主进程目录并删除 ps -axu | grep process_name rm -rf /usr/bin/2cbbb

PDF-Extract-Kit

文章目录 一、关于 PDF-Extract-Kit整体介绍效果展示 二、评测指标1、布局检测2、公式检测3、公式识别 三、安装四、模型下载1、安装 Git LFS2、从 Hugging Face 下载模型3、从 ModelScope 下载模型SDK 下载Git 下载 五、运行提取脚本六、其它待办事项协议致谢 一、关于 PDF-Ex…

Spark实时(三):Structured Streaming入门案例

文章目录 Structured Streaming入门案例 一、Scala代码如下 二、Java 代码如下 三、以上代码注意点如下 Structured Streaming入门案例 我们使用Structured Streaming来监控socket数据统计WordCount。这里我们使用Spark版本为3.4.3版本&#xff0c;首先在Maven pom文件中导…

Delphi 11.2 配置Android SDK 环境

打开 Delphi 11 点击 Tools–Options… 然后点击 Deployment–SDK Manager–Add… 这里如果配置64位就选 Android 64-bit&#xff0c;如果配置32位就选 Android 32-bit 点击 Select an SDK version–Add New… 有警告图标的就是有问题的项&#xff0c;需要手动更新一下&#xf…

NO.1 Hadoop概述

1.1 Hadoop是什么 1.2 Hadoop优势 1.3 Hadoop组成 1.3.1 HDFS架构概述 1.3.2 YARN架构概述 1.3.3 MapReduce架构概述 1.3.4 HDFS、YARN、MapReduce三者关系 1.4 大数据技术生态体系 1.5 推荐系统框架图

【UE5】可反射的射线检测

目录 效果 步骤 一、准备射线 二、生成第一次反射后的射线 三、多次反射 四、通过循环进行多次反射 效果 步骤 一、准备射线 1. 新建一个工程&#xff0c;添加一个俯视角游戏资源包 2. 双击打开俯视角游戏地图 删除大纲中的后期处理体积使得地图可以正常显示 3. 添加一…

【JavaEE初阶】线程的概念及创建

目录 &#x1f4d5; 前言 &#x1f4d5; 认识线程&#xff08;Thread&#xff09; &#x1f6a9; 概念 &#x1f60a;线程是什么 &#x1f642; 为啥要有线程 &#x1f62d; 进程和线程的区别&#xff08;面试题重点&#xff09; &#x1f92d; Java的线程和操作系统线程…

黑马JavaWeb企业级开发(知识清单)01——前端介绍,HTML实现标题:排版

文章目录 前言一、认识web前端、HTML、CSS二、VS Code开发工具&#xff08;插件弃用问题&#xff09;三、HTML结构标签介绍1. 标签页标题< title >2. 图片标签< img >1) 常见属性2) src路径书写方式 3. 标题标签< h >4. 水平分页线标签< hr > 四、用Vs…

“萝卜快跑”自动驾驶技术,夺走了谁的方向盘?

在前几年&#xff0c;科幻电影中无人驾驶车自如地穿梭在城市大街小巷的场景&#xff0c;似乎还遥不可及&#xff0c;然而&#xff0c;随着“萝卜快跑”无人驾驶车辆在多个城市的成功运营&#xff0c;这一愿景已悄然变为现实。由百度Apollo倾力打造的“萝卜快跑”&#xff0c;以…

基于FPGA的以太网设计(3)----详解各类xMII接口

1、什么是xMII接口 MII (Media Independent Interface)接口,即介质无关接口或称为媒体独立接口,它是IEEE-802.3定义的以太网行业标准。“介质无关” 表明在不对MAC硬件重新设计或替换的情况下,任何类型的PHY设备都可以正常工作。 MII接口是MAC和PHY之间的通信接口,MAC产生…

STM32(七):STM32指南者-串口实验

目录 一、基本概念通讯基本概念1、串行和并行2、同步通讯与异步通讯3、全双工、半双工、单工4、通讯速率 串口基本概念1、串口通讯基本概念2、物理层3、协议层 指南者的串口USART 二、串口实验前期准备1、安装安装 USB 转串口驱动_CH3402、野火多功能调试助手3、使用USB转串口&…

RedHat9 | Ansible 编写循环和条件任务

环境版本说明 RedHat9 [Red Hat Enterprise Linux release 9.0]Ansible [core 2.13.3]Python [3.9.10]jinja [3.1.2] 1. 利用循环迭代任务 通过利用循环&#xff0c;管理员无需编写多个使用同一模块的任务。Ansible支持使用loop关键字对一组项目迭代任务&#xff0c;通过配置…