欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于SpringBoot實(shí)現(xiàn)驗(yàn)證碼功能(兩種驗(yàn)證碼方式)

 更新時(shí)間:2024年08月02日 11:34:07   作者:凌晨去看海  
這篇文章主要介紹了基于SpringBoot實(shí)現(xiàn)驗(yàn)證碼功能,今天我們介紹的是兩種主流的驗(yàn)證碼,一種就是進(jìn)行計(jì)算的驗(yàn)證碼,另外一種就是不需要計(jì)算,直接輸入的驗(yàn)證碼,需要的朋友可以參考下

基于SpringBoot實(shí)現(xiàn)驗(yàn)證碼功能

現(xiàn)在的登錄都需要輸入驗(yàn)證碼用來檢測是否是真人登錄,所以驗(yàn)證碼功能在現(xiàn)在是非常普遍的,那么接下來我們就基于springboot來實(shí)現(xiàn)驗(yàn)證碼功能。

一 實(shí)現(xiàn)思路

        今天我們介紹的是兩種主流的驗(yàn)證碼,一種就是上圖所示的需要進(jìn)行計(jì)算的驗(yàn)證碼,另外一種就是不需要計(jì)算,直接輸入的驗(yàn)證碼。 

1.如果驗(yàn)證碼的類型是一個(gè)計(jì)算類型驗(yàn)證碼

* 那么我們就需要分析一下:* 比如一個(gè)計(jì)算類型的驗(yàn)證碼:1+2=?* 那么我們到時(shí)候應(yīng)該填入的驗(yàn)證碼的答案是:3* 所以這個(gè)驗(yàn)證碼它包含了兩個(gè)部分,一個(gè)是"1+2=?" 另外一個(gè)是"3"* 其實(shí)生成這個(gè)計(jì)算問題是我們自己來實(shí)現(xiàn)的,我們的工具類已經(jīng)封裝好了計(jì)算提,比如:“1+2=3”* 所以我們現(xiàn)在要做的主要有三步:* 1.得到這個(gè)算式后,將算是“1+2=3”分割成兩個(gè)部分,“1+2=?” 和 “3”* 2.然后我們需要把它們存儲(chǔ)到緩存中去,前面可以當(dāng)作key,后面可以當(dāng)作value,然后進(jìn)行驗(yàn)證答案的正確性* 3.最后,我們需要把“1+2=?”制作成一個(gè)圖片返回給前端,用于展示給用戶,

 2.如果驗(yàn)證碼的類型是一個(gè)普通的圖形驗(yàn)證碼

    那么我們不要分為表達(dá)式和答案兩個(gè)部分,我們只需要把生成的這個(gè)圖形直接存入緩存中去就可以了。

    但是因?yàn)槲覀冞@兩種類型是在同一個(gè)類中進(jìn)行判斷的,所以最好還是用兩個(gè)變量來接收。

上面這兩中類型的驗(yàn)證碼判斷完成之后,不管是那種類型,最后都需要把數(shù)據(jù)存到緩存中,并且都會(huì)生成一個(gè)Base64編碼的一個(gè)圖片,我們只需要返回這個(gè)圖片即可,還需要一個(gè)uuid,因?yàn)檫@個(gè)是用來作為key的唯一標(biāo)識(shí)。

二 代碼實(shí)現(xiàn)

開始代碼之前,先介紹一下我注入的幾個(gè)bean:

    @Autowired
    private ISysConfigService configService ;  //判斷是否開啟驗(yàn)證碼
    @Autowired
    private FeiSiConfig feiSiConfig ; //配置信息
    @Autowired
    private KaptchaTextCreator kaptchaTextCreator ; //隨機(jī)生成驗(yàn)證碼表達(dá)式
    @Autowired
    private RedisCache redisCache ; //存儲(chǔ)數(shù)據(jù)到緩存
    @Resource(name = "captchaProducer")
    private Producer captchaProducer; //生成普通類型驗(yàn)證碼圖片
    @Resource(name="captchaProducerMath")
    private Producer  captchaProducerMath;  //生成計(jì)算類型驗(yàn)證碼圖片

① 先判斷有沒有開啟驗(yàn)證碼功能。

我們有一個(gè)網(wǎng)頁功能的數(shù)據(jù)庫表,里面可以選擇是否開啟驗(yàn)證碼功能,并且在類加載的時(shí)候,我們就已經(jīng)使用 @PostConstruct(標(biāo)記在方法上, 當(dāng)這個(gè)bean創(chuàng)建完成,自動(dòng)執(zhí)行這個(gè)注解標(biāo)記方法,要求這個(gè)方法無參 類似 init-method配置) 注解將數(shù)據(jù)庫的數(shù)據(jù)緩存在了redis里面。

所以我們可以判斷先判斷有沒有開啟驗(yàn)證碼功能:

//        先要判斷一下是否開啟了驗(yàn)證碼功能,如果沒有開啟,則不需要進(jìn)行生成驗(yàn)證碼的操作了
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (!captchaEnabled){
            return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回沒有開啟驗(yàn)證碼信息給前端
        }
configService這個(gè)類就是我們用來初始化緩存數(shù)據(jù)的,這個(gè)類代碼如下:
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  //標(biāo)記在方法上, 當(dāng)這個(gè)bean創(chuàng)建完成,自動(dòng)執(zhí)行這個(gè)注解標(biāo)記方法,要求這個(gè)方法無參  類似 init-method配置
    public void  init(){
        loadingConfigCache();
    }
//    初始化數(shù)據(jù),當(dāng)系統(tǒng)加載的時(shí)候,把數(shù)據(jù)存入到緩存中去
    @Override
    public void loadingConfigCache() {
        System.out.println("初始化數(shù)據(jù)...");
        //查詢數(shù)據(jù)庫
        List<SysConfig> sysConfigs = sysConfigMapper.selectList(null);
        //保存到redis緩存中
        sysConfigs.forEach((sysConfig)->{
            redisCache.setCacheObject(getCacheKey(sysConfig.getConfigKey()),sysConfig.getConfigValue());
        });
    }
    /**
     * 構(gòu)建參數(shù)配置表的redis的key
     * @param configKey
     * @return
     */
//    獲取redis緩存中的key
    private String getCacheKey(String configKey){
        return CacheConstants.SYS_CONFIG_KEY+configKey;
    }
//    判斷賬號(hào)的驗(yàn)證碼是否以及開啟
    @Override
    public boolean selectCaptchaEnabled() {
        String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");
        if (StrUtil.isBlank(cacheObject)){
//            如果查詢出來的是空,則說明該賬號(hào)是第一次登錄,則默認(rèn)開啟驗(yàn)證碼
            return true ;
        }
//        如果不是空,那么查詢到的數(shù)據(jù)不是true就是false,但是存到數(shù)據(jù)庫的并不是Boolean類型而是String類型,
//        所以我們借用工具直接把字符串轉(zhuǎn)成對(duì)應(yīng)的Boolean類型
        return Convert.toBool(cacheObject) ;
    }
//    根據(jù)賬號(hào)的key獲取緩存中的value
    @Override
    public String selectConfigByKey(String configKey) {
//       1. 如果從redis中得到了數(shù)據(jù),那么直接返回
        String cacheObject = redisCache.getCacheObject(getCacheKey(configKey));
//        我們需要返回的是一個(gè)String類型,所以需要用工具包轉(zhuǎn)為String類型
        String toStr = Convert.toStr(cacheObject);
        if (StrUtil.isNotBlank(toStr)){
            return toStr ;
        }
//       2.如果沒有得到數(shù)據(jù),那么我們需要從sys_config數(shù)據(jù)庫表中查詢數(shù)據(jù),同時(shí)把查詢到的數(shù)據(jù)存入緩存中
        LambdaQueryWrapper<SysConfig> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(SysConfig::getConfigKey,configKey);
        SysConfig sysConfig = sysConfigMapper.selectOne(queryWrapper);
//        如果查到有數(shù)據(jù),就存入redis并且返回
        if(Objects.nonNull(sysConfig)){  //這里判空的方法不能繼續(xù)和上面一樣,因?yàn)閟ysConfig并不是字符串類型,而是一個(gè)對(duì)象
//            獲取到值
            String configValue = sysConfig.getConfigValue();
            redisCache.setCacheObject(getCacheKey(configKey),configValue);
            return configValue;
        }
//        否則沒有查到數(shù)據(jù),則說明沒有信息
        return null ;
    }
}

在進(jìn)行正式寫邏輯代碼之前,我們需要引入幾個(gè)變量。

按照我們上面分析的,如果是一個(gè)計(jì)算類型的驗(yàn)證碼,那么我們一個(gè)需要四個(gè)變量:

一個(gè)變量是表達(dá)式的前半部分,一個(gè)變量是表達(dá)式的答案,

一個(gè)變量是用于存儲(chǔ)生成的驗(yàn)證碼圖片的Base64編碼,

最后一個(gè)就是驗(yàn)證碼數(shù)據(jù)存儲(chǔ)在redis作為唯一標(biāo)識(shí)key的uuid

        BufferedImage image = null ;//圖片
        String expression ; //表達(dá)式部分
        String answer ; //答案部分
        String uuid  = UUID.randomUUID().toString();; //uuid

② 判斷開啟后,我們接下來需要判斷使用的是哪一種驗(yàn)證碼,具體使用哪一種是我們自己配置好的,我們規(guī)定math是計(jì)算類型的驗(yàn)證碼,char是普通驗(yàn)證碼。

         /**
         * 至于這個(gè)captchaType的值是根據(jù)配置文件設(shè)置的,因?yàn)檫@個(gè)FeisiConfig是一個(gè)配置類
         * 它加了‘@ConfigurationProperties(prefix = "fs")'這個(gè)注解,
         * 而配置文件規(guī)定math是計(jì)算類型驗(yàn)證碼,char是圖形類型驗(yàn)證碼
         */
        String captchaType = feiSiConfig.getCaptchaType();

③ 如果是計(jì)算類型的驗(yàn)證碼,那么我們就需要按照以下步驟走:

1.首先,得到生成的計(jì)算表達(dá)式(由我們封裝的工具類生成)

//            獲取一個(gè)隨機(jī)生成的表達(dá)式(隨機(jī)生成的工具封裝在KaptchaTextCreator類中)
            String text = kaptchaTextCreator.getText();

  生成表達(dá)式的工具類代碼如下:

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.得到表達(dá)式之后,我們需要對(duì)這個(gè)表達(dá)式進(jìn)行分割,分成表達(dá)式和答案兩部分

//            這個(gè)表達(dá)式其實(shí)我們?cè)诠ぞ哳惿傻臅r(shí)候做了處理,text其實(shí)是“1+2=@3”,這樣就方便分離表達(dá)式和答案
//            分割表達(dá)式
             expression = text.substring(0,text.lastIndexOf("@")) ; //"1+2=?"
             answer = text.substring(text.lastIndexOf("@")+1) ; //"@"

3.正常來說,接下來我們需要把數(shù)據(jù)存到緩存中去,但是因?yàn)槠胀愋偷尿?yàn)證碼也有這一步,所以這一部分重復(fù)邏輯就放在最后,我們現(xiàn)在需要根據(jù)前面的表達(dá)式部分來生成一張圖片。

//            最后,我們需要把“1+2=?”制作成一個(gè)圖片返回給前端,用于展示給用戶,
//            制作成圖片也有專門的工具類,然后我們需要把這個(gè)圖片轉(zhuǎn)成64編碼返回給前端,這個(gè)功能代碼其實(shí)是固定的
            //生成驗(yàn)證碼圖片(google.code.kaptcha提供的工具類Product)
            image = captchaProducerMath.createImage(expression);

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

4.正常來說,現(xiàn)在應(yīng)該是將驗(yàn)證碼圖片轉(zhuǎn)成Base64編碼,然后返回給前端展示,但是和上面緩存數(shù)據(jù)一樣,兩種編碼類型都是獲取到驗(yàn)證碼數(shù)據(jù)緩存在redis并且把生成的驗(yàn)證碼圖片以Base64編碼格式返回給前端,所以我們接下來就是為普通驗(yàn)證碼類型獲取驗(yàn)證碼數(shù)據(jù)和生成圖片。

④ 如果是普通類型的驗(yàn)證碼

    else {
            /**
             * 如果不是計(jì)算式類型的驗(yàn)證碼,那就是圖形類型的驗(yàn)證碼,那么就更簡單了。
             * 只需要把圖形驗(yàn)證碼存到緩存中,然后再把圖片返回給前端就好
             */
//            圖形驗(yàn)證碼的話不能和上面一樣生成表達(dá)式了,而是隨機(jī)生成一個(gè)文本,然后把文本賦值給exception和answer,這樣方便存儲(chǔ)
            expression = answer= captchaProducer.createText();
//            再把這個(gè)文本轉(zhuǎn)成驗(yàn)證碼圖片
            image  = captchaProducer.createImage(expression) ;
        }
captchaProducer和captchaProducerMath其實(shí)是一個(gè)類,知識(shí)取了不一樣的名字方便區(qū)分。

⑤ 上面兩種類型的驗(yàn)證碼都已經(jīng)成功得到表達(dá)式(exception),答案(anwser)(普通類型這個(gè)都一樣),生成的圖片(image)。

因?yàn)樯厦媸悄囊环N類型就會(huì)生成哪一種驗(yàn)證碼的數(shù)據(jù),所以我們最后只需要生成一個(gè)uuid唯一標(biāo)識(shí)作為key,然后把a(bǔ)nswer作為value存儲(chǔ)在redis中。然后把image轉(zhuǎn)換成Base64編碼。

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

        //    然后把答案存入到redis中,當(dāng)然了key不能直接用表達(dá)式,因?yàn)橛锌赡軙?huì)重復(fù)
//            所以我們用uuid來作為key,然后把uuid給前端,前端在訪問的時(shí)候再把uuid傳來進(jìn)行驗(yàn)證
        uuid  = UUID.randomUUID().toString();; //uuid
        //Constants.CAPTCHA_EXPIRATION :表示2 , TimeUnit.MINUTES:表示分鐘,即這個(gè)驗(yàn)證碼數(shù)據(jù)只存儲(chǔ)2分鐘
        redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY+uuid,answer,Constants.CAPTCHA_EXPIRATION , TimeUnit.MINUTES);
        //最后再把生成的驗(yàn)證碼圖片轉(zhuǎn)成Base64編碼
        //轉(zhuǎn)之前需要先把圖片轉(zhuǎn)成字節(jié)數(shù)組
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        ImageIO.write(image,"jpeg",os);//把圖片通過jpeg類型寫進(jìn)字節(jié)數(shù)組
        //再轉(zhuǎn)成Base64編碼
        String base64Str = Base64.encode(os.toByteArray());
        //得到的Base64編碼不能直接返回,還需要按照規(guī)定添加頭部
        String base64Img = "data:image/jpeg;base64," +base64Str;
        //BASE64對(duì)密文進(jìn)行傳輸加密時(shí),可能出現(xiàn)\r\n
        //原因: RFC2045中有規(guī)定:即Base64一行不能超過76字符,超過則添加回車換行符。
        //解決方案: BASE64加密后,對(duì)\r\n進(jìn)行去除
        base64Img= base64Img.replaceAll("\r|\n", "");
//            最后把這個(gè)Base64編碼的表達(dá)式圖片驗(yàn)證碼和用于表示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;
/**
 * 驗(yàn)證碼操作處理
 *
 */
@RestController
public class CaptchaController{
    @Autowired
    private ISysConfigService configService ;  //判斷是否開啟驗(yàn)證碼
    @Autowired
    private FeiSiConfig feiSiConfig ; //配置信息
    @Autowired
    private KaptchaTextCreator kaptchaTextCreator ; //隨機(jī)生成驗(yàn)證碼表達(dá)式
    @Autowired
    private RedisCache redisCache ; //存儲(chǔ)數(shù)據(jù)到緩存
    @Resource(name = "captchaProducer")
    private Producer captchaProducer; //生成普通類型驗(yàn)證碼圖片
    @Resource(name="captchaProducerMath")
    private Producer  captchaProducerMath;  //生成計(jì)算類型驗(yàn)證碼圖片
    /**
     * 生成驗(yàn)證碼
     */
    @GetMapping("/captchaImage")
    public AjaxResult getCode(HttpServletResponse response) throws IOException
    {
        AjaxResult ajaxResult = AjaxResult.success() ;
        /**
         * 思路:
         * 我們目前驗(yàn)證分為兩種,一種是計(jì)算類型驗(yàn)證碼,一種是單純的圖形驗(yàn)證碼
         * 所以,我們第一步就是判斷驗(yàn)證碼的類型,而驗(yàn)證碼的類型是我們?cè)谂渲梦募渲煤玫?
         */
//        先要判斷一下是否開啟了驗(yàn)證碼功能,如果沒有開啟,則不需要進(jìn)行生成驗(yàn)證碼的操作了
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (!captchaEnabled){
            return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回沒有開啟驗(yàn)證碼信息給前端
        }
        BufferedImage image = null ;//圖片
        String expression ; //表達(dá)式部分
        String answer ; //答案部分
        String uuid  ;; //uuid
//        否則說明開啟了驗(yàn)證碼,那么需要判斷使用的是什么類型的驗(yàn)證碼
        /**
         * 至于這個(gè)captchaType的值是根據(jù)配置文件設(shè)置的,因?yàn)檫@個(gè)FeisiConfig是一個(gè)配置類
         * 它加了‘@ConfigurationProperties(prefix = "fs")'這個(gè)注解,
         * 而配置文件規(guī)定math是計(jì)算類型驗(yàn)證碼,char是圖形類型驗(yàn)證碼
         */
        String captchaType = feiSiConfig.getCaptchaType();
        if (captchaType.equals("math")){
//            如果驗(yàn)證碼的類型是一個(gè)計(jì)算類型驗(yàn)證碼
            /**
             * 那么我們就需要分析一下:
             * 比如一個(gè)計(jì)算類型的驗(yàn)證碼:1+2=?
             * 那么我們到時(shí)候應(yīng)該填入的驗(yàn)證碼的答案是:3
             * 所以這個(gè)驗(yàn)證碼它包含了兩個(gè)部分,一個(gè)是"1+2=?" 另外一個(gè)是"3"
             * 其實(shí)生成這個(gè)計(jì)算問題是我們自己來實(shí)現(xiàn)的,我們的工具類已經(jīng)封裝好了計(jì)算提,比如:“1+2=3”
             * 所以我們現(xiàn)在要做的主要有三步:
             * 1.得到這個(gè)算式后,將算是“1+2=3”分割成兩個(gè)部分,“1+2=?” 和 “3”
             * 2.然后我們需要把它們存儲(chǔ)到緩存中去,前面可以當(dāng)作key,后面可以當(dāng)作value,然后進(jìn)行驗(yàn)證答案的正確性
             * 3.最后,我們需要把“1+2=?”制作成一個(gè)圖片返回給前端,用于展示給用戶,
             */
//            獲取一個(gè)隨機(jī)生成的表達(dá)式(隨機(jī)生成的工具封裝在KaptchaTextCreator類中)
            String text = kaptchaTextCreator.getText();
//            這個(gè)表達(dá)式其實(shí)我們?cè)诠ぞ哳惿傻臅r(shí)候做了處理,text其實(shí)是“1+2=@3”,這樣就方便分離表達(dá)式和答案
//            分割表達(dá)式
             expression = text.substring(0,text.lastIndexOf("@")) ; //"1+2=?"
             answer = text.substring(text.lastIndexOf("@")+1) ; //"@"
//            最后,我們需要把“1+2=?”制作成一個(gè)圖片返回給前端,用于展示給用戶,
//            制作成圖片也有專門的工具類,然后我們需要把這個(gè)圖片轉(zhuǎn)成64編碼返回給前端,這個(gè)功能代碼其實(shí)是固定的
            //生成驗(yàn)證碼圖片(google.code.kaptcha提供的工具類Product)
            image = captchaProducerMath.createImage(expression);
        }else {
            /**
             * 如果不是計(jì)算式類型的驗(yàn)證碼,那就是圖形類型的驗(yàn)證碼,那么就更簡單了。
             * 只需要把圖形驗(yàn)證碼存到緩存中,然后再把圖片返回給前端就好
             */
//            圖形驗(yàn)證碼的話不能和上面一樣生成表達(dá)式了,而是隨機(jī)生成一個(gè)文本,然后把文本賦值給exception和answer,這樣方便存儲(chǔ)
            expression = answer= captchaProducer.createText();
//            再把這個(gè)文本轉(zhuǎn)成驗(yàn)證碼圖片
            image  = captchaProducer.createImage(expression) ;
        }
        System.out.println(expression+":"+answer);
        //    然后把答案存入到redis中,當(dāng)然了key不能直接用表達(dá)式,因?yàn)橛锌赡軙?huì)重復(fù)
//            所以我們用uuid來作為key,然后把uuid給前端,前端在訪問的時(shí)候再把uuid傳來進(jìn)行驗(yàn)證
        uuid  = UUID.randomUUID().toString();; //uuid
        //Constants.CAPTCHA_EXPIRATION :表示2 , TimeUnit.MINUTES:表示分鐘,即這個(gè)驗(yàn)證碼數(shù)據(jù)只存儲(chǔ)2分鐘
        redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY+uuid,answer,Constants.CAPTCHA_EXPIRATION , TimeUnit.MINUTES);
        //最后再把生成的驗(yàn)證碼圖片轉(zhuǎn)成Base64編碼
        //轉(zhuǎn)之前需要先把圖片轉(zhuǎn)成字節(jié)數(shù)組
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        ImageIO.write(image,"jpeg",os);//把圖片通過jpeg類型寫進(jìn)字節(jié)數(shù)組
        //再轉(zhuǎn)成Base64編碼
        String base64Str = Base64.encode(os.toByteArray());
        //得到的Base64編碼不能直接返回,還需要按照規(guī)定添加頭部
        String base64Img = "data:image/jpeg;base64," +base64Str;
        //BASE64對(duì)密文進(jìn)行傳輸加密時(shí),可能出現(xiàn)\r\n
        //原因: RFC2045中有規(guī)定:即Base64一行不能超過76字符,超過則添加回車換行符。
        //解決方案: BASE64加密后,對(duì)\r\n進(jìn)行去除
        base64Img= base64Img.replaceAll("\r|\n", "");
//            最后把這個(gè)Base64編碼的表達(dá)式圖片驗(yàn)證碼和用于表示key的uuid返回給前端
        ajaxResult.put("img",base64Img);
        ajaxResult.put("uuid",uuid) ;
        os.close();
        return ajaxResult ;
    }
}

 用于判斷是否開啟驗(yàn)證碼的功能的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  //標(biāo)記在方法上, 當(dāng)這個(gè)bean創(chuàng)建完成,自動(dòng)執(zhí)行這個(gè)注解標(biāo)記方法,要求這個(gè)方法無參  類似 init-method配置
    public void  init(){
        loadingConfigCache();
    }
//    初始化數(shù)據(jù),當(dāng)系統(tǒng)加載的時(shí)候,把數(shù)據(jù)存入到緩存中去
    @Override
    public void loadingConfigCache() {
        System.out.println("初始化數(shù)據(jù)...");
        //查詢數(shù)據(jù)庫
        List<SysConfig> sysConfigs = sysConfigMapper.selectList(null);
        //保存到redis緩存中
        sysConfigs.forEach((sysConfig)->{
            redisCache.setCacheObject(getCacheKey(sysConfig.getConfigKey()),sysConfig.getConfigValue());
        });
    }
    /**
     * 構(gòu)建參數(shù)配置表的redis的key
     * @param configKey
     * @return
     */
//    獲取redis緩存中的key
    private String getCacheKey(String configKey){
        return CacheConstants.SYS_CONFIG_KEY+configKey;
    }
//    判斷賬號(hào)的驗(yàn)證碼是否以及開啟
    @Override
    public boolean selectCaptchaEnabled() {
        String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");
        if (StrUtil.isBlank(cacheObject)){
//            如果查詢出來的是空,則說明該賬號(hào)是第一次登錄,則默認(rèn)開啟驗(yàn)證碼
            return true ;
        }
//        如果不是空,那么查詢到的數(shù)據(jù)不是true就是false,但是存到數(shù)據(jù)庫的并不是Boolean類型而是String類型,
//        所以我們借用工具直接把字符串轉(zhuǎn)成對(duì)應(yīng)的Boolean類型
        return Convert.toBool(cacheObject) ;
    }
//    根據(jù)賬號(hào)的key獲取緩存中的value
    @Override
    public String selectConfigByKey(String configKey) {
//       1. 如果從redis中得到了數(shù)據(jù),那么直接返回
        String cacheObject = redisCache.getCacheObject(getCacheKey(configKey));
//        我們需要返回的是一個(gè)String類型,所以需要用工具包轉(zhuǎn)為String類型
        String toStr = Convert.toStr(cacheObject);
        if (StrUtil.isNotBlank(toStr)){
            return toStr ;
        }
//       2.如果沒有得到數(shù)據(jù),那么我們需要從sys_config數(shù)據(jù)庫表中查詢數(shù)據(jù),同時(shí)把查詢到的數(shù)據(jù)存入緩存中
        LambdaQueryWrapper<SysConfig> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(SysConfig::getConfigKey,configKey);
        SysConfig sysConfig = sysConfigMapper.selectOne(queryWrapper);
//        如果查到有數(shù)據(jù),就存入redis并且返回
        if(Objects.nonNull(sysConfig)){  //這里判空的方法不能繼續(xù)和上面一樣,因?yàn)閟ysConfig并不是字符串類型,而是一個(gè)對(duì)象
//            獲取到值
            String configValue = sysConfig.getConfigValue();
            redisCache.setCacheObject(getCacheKey(configKey),configValue);
            return configValue;
        }
//        否則沒有查到數(shù)據(jù),則說明沒有信息
        return null ;
    }
}

驗(yàn)證碼類型配置信息的配置類feiSiConfig , 以及配置文件yam的代碼

package com.fs.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * 讀取項(xiàng)目相關(guān)配置
 *
 */
@Component
@ConfigurationProperties(prefix = "fs")
public class FeiSiConfig
{
    /** 項(xiàng)目名稱 */
    private String name;
    /** 版本 */
    private String version;
    /** 版權(quán)年份 */
    private String copyrightYear;
    /** 上傳路徑 */
    private static String profile;
    /** 獲取地址開關(guān) */
    private static boolean addressEnabled;
    /** 驗(yàn)證碼類型 */
    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;
    }
    /**
     * 獲取導(dǎo)入上傳路徑
     */
    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配置文件:

# 項(xiàng)目相關(guān)配置
fs:
  # 名稱
  name: FeiSi
  # 版本
  version: 1.0.0
  # 版權(quán)年份
  copyrightYear: 2023
  # 文件路徑 示例( Windows配置D:/feisi/uploadPath,Linux配置 /home/feisi/uploadPath)
  profile: D:/feisi/uploadPath
  # 獲取ip地址開關(guān)
  addressEnabled: false
  # 驗(yàn)證碼類型 math 數(shù)字計(jì)算 char 字符驗(yàn)證
  captchaType: math

生成隨機(jī)計(jì)算類型表達(dá)式的 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,把數(shù)據(jù)存儲(chǔ)在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;
    /**
     * 緩存基本的對(duì)象,Integer、String、實(shí)體類等
     *
     * @param key 緩存的鍵值
     * @param value 緩存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 緩存基本的對(duì)象,Integer、String、實(shí)體類等
     *
     * @param key 緩存的鍵值
     * @param value 緩存的值
     * @param timeout 時(shí)間
     * @param timeUnit 時(shí)間顆粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
    /**
     * 設(shè)置有效時(shí)間
     *
     * @param key Redis鍵
     * @param timeout 超時(shí)時(shí)間
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }
    /**
     * 設(shè)置有效時(shí)間
     *
     * @param key Redis鍵
     * @param timeout 超時(shí)時(shí)間
     * @param unit 時(shí)間單位
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }
    /**
     * 獲取有效時(shí)間
     *
     * @param key Redis鍵
     * @return 有效時(shí)間
     */
    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);
    }
    /**
     * 獲得緩存的基本對(duì)象。
     *
     * @param key 緩存鍵值
     * @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
    /**
     * 刪除單個(gè)對(duì)象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }
    /**
     * 刪除集合對(duì)象
     *
     * @param collection 多個(gè)對(duì)象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }
    /**
     * 緩存List數(shù)據(jù)
     *
     * @param key 緩存的鍵值
     * @param dataList 待緩存的List數(shù)據(jù)
     * @return 緩存的對(duì)象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }
    /**
     * 獲得緩存的list對(duì)象
     *
     * @param key 緩存的鍵值
     * @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
    /**
     * 緩存Set
     *
     * @param key 緩存鍵值
     * @param dataSet 緩存的數(shù)據(jù)
     * @return 緩存數(shù)據(jù)的對(duì)象
     */
    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中存入數(shù)據(jù)
     *
     * @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中的數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @return Hash中的對(duì)象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }
    /**
     * 獲取多個(gè)Hash中的數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKeys Hash鍵集合
     * @return Hash對(duì)象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }
    /**
     * 刪除Hash中的某條數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }
    /**
     * 獲得緩存的基本對(duì)象列表
     *
     * @param pattern 字符串前綴
     * @return 對(duì)象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

以上就是實(shí)現(xiàn)驗(yàn)證碼功能的整個(gè)后端代碼。

到此這篇關(guān)于基于SpringBoot實(shí)現(xiàn)驗(yàn)證碼功能的文章就介紹到這了,更多相關(guān)SpringBoot驗(yàn)證碼功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(31)

    Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(31)

    下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07
  • 詳解Java5、Java6、Java7的新特性

    詳解Java5、Java6、Java7的新特性

    本編文章詳細(xì)介紹了Java5、Java6、Java7的新特性,需要的朋友可以參考下
    2017-04-04
  • 詳解Java方法method的定義與調(diào)用及重載

    詳解Java方法method的定義與調(diào)用及重載

    方法,也稱函數(shù),如果想要重復(fù)一段或者多段代碼塊的使用,可以將這些代碼封裝成一個(gè)方法,方法具體表現(xiàn)為某種行為,使用方法可以提高代碼的復(fù)用性
    2022-04-04
  • Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析

    Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析

    這篇文章主要為大家介紹了Netty分布式pipeline管道傳播事件總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • Java線程的全方位詳解

    Java線程的全方位詳解

    Java 給多線程編程提供了內(nèi)置的支持。 一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù),多線程是多任務(wù)的一種特別的形式,但多線程使用了更小的資源開銷
    2021-10-10
  • Java數(shù)組常用排序算法實(shí)例小結(jié)

    Java數(shù)組常用排序算法實(shí)例小結(jié)

    這篇文章主要介紹了Java數(shù)組常用排序算法,結(jié)合實(shí)例形式總結(jié)分析了java數(shù)組常用的4種排序算法,包括冒泡排序、數(shù)組遞增排序、快速排序及選擇排序,需要的朋友可以參考下
    2017-12-12
  • java實(shí)現(xiàn)多線程交替打印兩個(gè)數(shù)

    java實(shí)現(xiàn)多線程交替打印兩個(gè)數(shù)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)多線程交替打印兩個(gè)數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • java遍歷Map的幾種方法分析

    java遍歷Map的幾種方法分析

    這篇文章主要介紹了java遍歷Map的幾種方法,結(jié)合實(shí)例形式分析了使用循環(huán)與迭代等方法操作Map遍歷的相關(guān)技巧,需要的朋友可以參考下
    2016-08-08
  • Java垃圾回收機(jī)制簡述

    Java垃圾回收機(jī)制簡述

    這篇文章主要為大家詳細(xì)介紹了Java垃圾回收機(jī)制的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • 解讀為什么@Autowired在屬性上被警告,在setter方法上不被警告問題

    解讀為什么@Autowired在屬性上被警告,在setter方法上不被警告問題

    在Spring開發(fā)中,@Autowired注解常用于實(shí)現(xiàn)依賴注入,它可以應(yīng)用于類的屬性、構(gòu)造器或setter方法上,然而,當(dāng)@Autowired注解在屬性上使用時(shí),IntelliJIDEA等IDE會(huì)給出Fieldinjectionisnotrecommended的警告,而在setter方法上使用@Autowired時(shí)卻不會(huì)出現(xiàn)這個(gè)警告
    2025-02-02

最新評(píng)論