基于SpringBoot實現(xiàn)驗證碼功能的代碼及思路

現(xiàn)在的登錄都需要輸入驗證碼用來檢測是否是真人登錄,所以驗證碼功能在現(xiàn)在是非常普遍的,那么接下來我們就基于springboot來實現(xiàn)驗證碼功能。
一 實現(xiàn)思路
今天我們介紹的是兩種主流的驗證碼,一種就是上圖所示的需要進行計算的驗證碼,另外一種就是不需要計算,直接輸入的驗證碼。
1.如果驗證碼的類型是一個計算類型驗證碼
* 那么我們就需要分析一下:
* 比如一個計算類型的驗證碼:1+2=?
* 那么我們到時候應該填入的驗證碼的答案是:3
* 所以這個驗證碼它包含了兩個部分,一個是"1+2=?" 另外一個是"3"
* 其實生成這個計算問題是我們自己來實現(xiàn)的,我們的工具類已經(jīng)封裝好了計算提,比如:“1+2=3”
* 所以我們現(xiàn)在要做的主要有三步:
* 1.得到這個算式后,將算是“1+2=3”分割成兩個部分,“1+2=?” 和 “3”
* 2.然后我們需要把它們存儲到緩存中去,前面可以當作key,后面可以當作value,然后進行驗證答案的正確性
* 3.最后,我們需要把“1+2=?”制作成一個圖片返回給前端,用于展示給用戶,
2.如果驗證碼的類型是一個普通的圖形驗證碼
那么我們不要分為表達式和答案兩個部分,我們只需要把生成的這個圖形直接存入緩存中去就可以了。
但是因為我們這兩種類型是在同一個類中進行判斷的,所以最好還是用兩個變量來接收。
上面這兩中類型的驗證碼判斷完成之后,不管是那種類型,最后都需要把數(shù)據(jù)存到緩存中,并且都會生成一個Base64編碼的一個圖片,我們只需要返回這個圖片即可,還需要一個uuid,因為這個是用來作為key的唯一標識。
二 代碼實現(xiàn)
開始代碼之前,先介紹一下我注入的幾個bean:
@Autowired
private ISysConfigService configService ; //判斷是否開啟驗證碼
@Autowired
private FeiSiConfig feiSiConfig ; //配置信息
@Autowired
private KaptchaTextCreator kaptchaTextCreator ; //隨機生成驗證碼表達式
@Autowired
private RedisCache redisCache ; //存儲數(shù)據(jù)到緩存
@Resource(name = "captchaProducer")
private Producer captchaProducer; //生成普通類型驗證碼圖片
@Resource(name="captchaProducerMath")
private Producer captchaProducerMath; //生成計算類型驗證碼圖片① 先判斷有沒有開啟驗證碼功能。
我們有一個網(wǎng)頁功能的數(shù)據(jù)庫表,里面可以選擇是否開啟驗證碼功能,并且在類加載的時候,我們就已經(jīng)使用 @PostConstruct(標記在方法上, 當這個bean創(chuàng)建完成,自動執(zhí)行這個注解標記方法,要求這個方法無參 類似 init-method配置) 注解將數(shù)據(jù)庫的數(shù)據(jù)緩存在了redis里面。
所以我們可以判斷先判斷有沒有開啟驗證碼功能:
// 先要判斷一下是否開啟了驗證碼功能,如果沒有開啟,則不需要進行生成驗證碼的操作了
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (!captchaEnabled){
return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回沒有開啟驗證碼信息給前端
}configService這個類就是我們用來初始化緩存數(shù)據(jù)的,這個類代碼如下:
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創(chuàng)建完成,自動執(zhí)行這個注解標記方法,要求這個方法無參 類似 init-method配置
public void init(){
loadingConfigCache();
}
// 初始化數(shù)據(jù),當系統(tǒng)加載的時候,把數(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;
}
// 判斷賬號的驗證碼是否以及開啟
@Override
public boolean selectCaptchaEnabled() {
String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");
if (StrUtil.isBlank(cacheObject)){
// 如果查詢出來的是空,則說明該賬號是第一次登錄,則默認開啟驗證碼
return true ;
}
// 如果不是空,那么查詢到的數(shù)據(jù)不是true就是false,但是存到數(shù)據(jù)庫的并不是Boolean類型而是String類型,
// 所以我們借用工具直接把字符串轉(zhuǎn)成對應的Boolean類型
return Convert.toBool(cacheObject) ;
}
// 根據(jù)賬號的key獲取緩存中的value
@Override
public String selectConfigByKey(String configKey) {
// 1. 如果從redis中得到了數(shù)據(jù),那么直接返回
String cacheObject = redisCache.getCacheObject(getCacheKey(configKey));
// 我們需要返回的是一個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ù)據(jù)存入緩存中
LambdaQueryWrapper<SysConfig> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(SysConfig::getConfigKey,configKey);
SysConfig sysConfig = sysConfigMapper.selectOne(queryWrapper);
// 如果查到有數(shù)據(jù),就存入redis并且返回
if(Objects.nonNull(sysConfig)){ //這里判空的方法不能繼續(xù)和上面一樣,因為sysConfig并不是字符串類型,而是一個對象
// 獲取到值
String configValue = sysConfig.getConfigValue();
redisCache.setCacheObject(getCacheKey(configKey),configValue);
return configValue;
}
// 否則沒有查到數(shù)據(jù),則說明沒有信息
return null ;
}
}
在進行正式寫邏輯代碼之前,我們需要引入幾個變量。
按照我們上面分析的,如果是一個計算類型的驗證碼,那么我們一個需要四個變量:
一個變量是表達式的前半部分,一個變量是表達式的答案,
一個變量是用于存儲生成的驗證碼圖片的Base64編碼,
最后一個就是驗證碼數(shù)據(jù)存儲在redis作為唯一標識key的uuid
BufferedImage image = null ;//圖片
String expression ; //表達式部分
String answer ; //答案部分
String uuid = UUID.randomUUID().toString();; //uuid② 判斷開啟后,我們接下來需要判斷使用的是哪一種驗證碼,具體使用哪一種是我們自己配置好的,我們規(guī)定math是計算類型的驗證碼,char是普通驗證碼。
/**
* 至于這個captchaType的值是根據(jù)配置文件設置的,因為這個FeisiConfig是一個配置類
* 它加了‘@ConfigurationProperties(prefix = "fs")'這個注解,
* 而配置文件規(guī)定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.正常來說,接下來我們需要把數(shù)據(jù)存到緩存中去,但是因為普通類型的驗證碼也有這一步,所以這一部分重復邏輯就放在最后,我們現(xiàn)在需要根據(jù)前面的表達式部分來生成一張圖片。
// 最后,我們需要把“1+2=?”制作成一個圖片返回給前端,用于展示給用戶,
// 制作成圖片也有專門的工具類,然后我們需要把這個圖片轉(zhuǎn)成64編碼返回給前端,這個功能代碼其實是固定的
//生成驗證碼圖片(google.code.kaptcha提供的工具類Product)
image = captchaProducerMath.createImage(expression);captchaProducerMath是谷歌提供的工具類,我們只需要注入使用就行。
4.正常來說,現(xiàn)在應該是將驗證碼圖片轉(zhuǎn)成Base64編碼,然后返回給前端展示,但是和上面緩存數(shù)據(jù)一樣,兩種編碼類型都是獲取到驗證碼數(shù)據(jù)緩存在redis并且把生成的驗證碼圖片以Base64編碼格式返回給前端,所以我們接下來就是為普通驗證碼類型獲取驗證碼數(shù)據(jù)和生成圖片。
④ 如果是普通類型的驗證碼
else {
/**
* 如果不是計算式類型的驗證碼,那就是圖形類型的驗證碼,那么就更簡單了。
* 只需要把圖形驗證碼存到緩存中,然后再把圖片返回給前端就好
*/
// 圖形驗證碼的話不能和上面一樣生成表達式了,而是隨機生成一個文本,然后把文本賦值給exception和answer,這樣方便存儲
expression = answer= captchaProducer.createText();
// 再把這個文本轉(zhuǎn)成驗證碼圖片
image = captchaProducer.createImage(expression) ;
}captchaProducer和captchaProducerMath其實是一個類,知識取了不一樣的名字方便區(qū)分。
⑤ 上面兩種類型的驗證碼都已經(jīng)成功得到表達式(exception),答案(anwser)(普通類型這個都一樣),生成的圖片(image)。
因為上面是哪一種類型就會生成哪一種驗證碼的數(shù)據(jù),所以我們最后只需要生成一個uuid唯一標識作為key,然后把answer作為value存儲在redis中。然后把image轉(zhuǎn)換成Base64編碼。
最后返回給前端image,uuid即可。
// 然后把答案存入到redis中,當然了key不能直接用表達式,因為有可能會重復
// 所以我們用uuid來作為key,然后把uuid給前端,前端在訪問的時候再把uuid傳來進行驗證
uuid = UUID.randomUUID().toString();; //uuid
//Constants.CAPTCHA_EXPIRATION :表示2 , TimeUnit.MINUTES:表示分鐘,即這個驗證碼數(shù)據(jù)只存儲2分鐘
redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY+uuid,answer,Constants.CAPTCHA_EXPIRATION , TimeUnit.MINUTES);
//最后再把生成的驗證碼圖片轉(zhuǎn)成Base64編碼
//轉(zhuǎn)之前需要先把圖片轉(zhuǎn)成字節(jié)數(shù)組
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
ImageIO.write(image,"jpeg",os);//把圖片通過jpeg類型寫進字節(jié)數(shù)組
//再轉(zhuǎn)成Base64編碼
String base64Str = Base64.encode(os.toByteArray());
//得到的Base64編碼不能直接返回,還需要按照規(guī)定添加頭部
String base64Img = "data:image/jpeg;base64," +base64Str;
//BASE64對密文進行傳輸加密時,可能出現(xiàn)\r\n
//原因: RFC2045中有規(guī)定:即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 ; //存儲數(shù)據(jù)到緩存
@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的值是根據(jù)配置文件設置的,因為這個FeisiConfig是一個配置類
* 它加了‘@ConfigurationProperties(prefix = "fs")'這個注解,
* 而配置文件規(guī)定math是計算類型驗證碼,char是圖形類型驗證碼
*/
String captchaType = feiSiConfig.getCaptchaType();
if (captchaType.equals("math")){
// 如果驗證碼的類型是一個計算類型驗證碼
/**
* 那么我們就需要分析一下:
* 比如一個計算類型的驗證碼:1+2=?
* 那么我們到時候應該填入的驗證碼的答案是:3
* 所以這個驗證碼它包含了兩個部分,一個是"1+2=?" 另外一個是"3"
* 其實生成這個計算問題是我們自己來實現(xiàn)的,我們的工具類已經(jīng)封裝好了計算提,比如:“1+2=3”
* 所以我們現(xiàn)在要做的主要有三步:
* 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=?”制作成一個圖片返回給前端,用于展示給用戶,
// 制作成圖片也有專門的工具類,然后我們需要把這個圖片轉(zhuǎn)成64編碼返回給前端,這個功能代碼其實是固定的
//生成驗證碼圖片(google.code.kaptcha提供的工具類Product)
image = captchaProducerMath.createImage(expression);
}else {
/**
* 如果不是計算式類型的驗證碼,那就是圖形類型的驗證碼,那么就更簡單了。
* 只需要把圖形驗證碼存到緩存中,然后再把圖片返回給前端就好
*/
// 圖形驗證碼的話不能和上面一樣生成表達式了,而是隨機生成一個文本,然后把文本賦值給exception和answer,這樣方便存儲
expression = answer= captchaProducer.createText();
// 再把這個文本轉(zhuǎn)成驗證碼圖片
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:表示分鐘,即這個驗證碼數(shù)據(jù)只存儲2分鐘
redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY+uuid,answer,Constants.CAPTCHA_EXPIRATION , TimeUnit.MINUTES);
//最后再把生成的驗證碼圖片轉(zhuǎn)成Base64編碼
//轉(zhuǎn)之前需要先把圖片轉(zhuǎn)成字節(jié)數(shù)組
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
ImageIO.write(image,"jpeg",os);//把圖片通過jpeg類型寫進字節(jié)數(shù)組
//再轉(zhuǎn)成Base64編碼
String base64Str = Base64.encode(os.toByteArray());
//得到的Base64編碼不能直接返回,還需要按照規(guī)定添加頭部
String base64Img = "data:image/jpeg;base64," +base64Str;
//BASE64對密文進行傳輸加密時,可能出現(xiàn)\r\n
//原因: RFC2045中有規(guī)定:即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創(chuàng)建完成,自動執(zhí)行這個注解標記方法,要求這個方法無參 類似 init-method配置
public void init(){
loadingConfigCache();
}
// 初始化數(shù)據(jù),當系統(tǒng)加載的時候,把數(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;
}
// 判斷賬號的驗證碼是否以及開啟
@Override
public boolean selectCaptchaEnabled() {
String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");
if (StrUtil.isBlank(cacheObject)){
// 如果查詢出來的是空,則說明該賬號是第一次登錄,則默認開啟驗證碼
return true ;
}
// 如果不是空,那么查詢到的數(shù)據(jù)不是true就是false,但是存到數(shù)據(jù)庫的并不是Boolean類型而是String類型,
// 所以我們借用工具直接把字符串轉(zhuǎn)成對應的Boolean類型
return Convert.toBool(cacheObject) ;
}
// 根據(jù)賬號的key獲取緩存中的value
@Override
public String selectConfigByKey(String configKey) {
// 1. 如果從redis中得到了數(shù)據(jù),那么直接返回
String cacheObject = redisCache.getCacheObject(getCacheKey(configKey));
// 我們需要返回的是一個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ù)據(jù)存入緩存中
LambdaQueryWrapper<SysConfig> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(SysConfig::getConfigKey,configKey);
SysConfig sysConfig = sysConfigMapper.selectOne(queryWrapper);
// 如果查到有數(shù)據(jù),就存入redis并且返回
if(Objects.nonNull(sysConfig)){ //這里判空的方法不能繼續(xù)和上面一樣,因為sysConfig并不是字符串類型,而是一個對象
// 獲取到值
String configValue = sysConfig.getConfigValue();
redisCache.setCacheObject(getCacheKey(configKey),configValue);
return configValue;
}
// 否則沒有查到數(shù)據(jù),則說明沒有信息
return null ;
}
}
驗證碼類型配置信息的配置類feiSiConfig , 以及配置文件yam的代碼
package com.fs.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 讀取項目相關(guān)配置
*
*/
@Component
@ConfigurationProperties(prefix = "fs")
public class FeiSiConfig
{
/** 項目名稱 */
private String name;
/** 版本 */
private String version;
/** 版權(quán)年份 */
private String copyrightYear;
/** 上傳路徑 */
private static String profile;
/** 獲取地址開關(guān) */
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配置文件:
# 項目相關(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 # 驗證碼類型 math 數(shù)字計算 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,把數(shù)據(jù)存儲在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 緩存鍵值對應的數(shù)據(jù)
*/
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數(shù)據(jù)
*
* @param key 緩存的鍵值
* @param dataList 待緩存的List數(shù)據(jù)
* @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 緩存鍵值對應的數(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ù)的對象
*/
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中的對象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 獲取多個Hash中的數(shù)據(jù)
*
* @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中的某條數(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;
}
/**
* 獲得緩存的基本對象列表
*
* @param pattern 字符串前綴
* @return 對象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
以上就是實現(xiàn)驗證碼功能的整個后端代碼。
總結(jié)
到此這篇關(guān)于基于SpringBoot實現(xiàn)驗證碼功能的代碼及思路的文章就介紹到這了,更多相關(guān)SpringBoot實現(xiàn)驗證碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring占位符Placeholder的實現(xiàn)原理解析
這篇文章主要介紹了Spring占位符Placeholder的實現(xiàn)原理,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
關(guān)于Maven parent.relativePath說明
Maven中的relativePath用于指定父項目pom.xml的相對路徑,默認值為../pom.xml,這個配置幫助Maven在構(gòu)建時定位父模塊的位置,確保模塊間的依賴關(guān)系正確,relativePath可以指向本地或遠程倉庫中的父項目,如果不需要尋找父項目,可以將其設置為空2024-09-09
Java中使用@CrossOrigin和Proxy解決跨域問題詳解
這篇文章主要介紹了Java中使用@CrossOrigin和Proxy解決跨域問題詳解,在Web開發(fā)中,如果前端頁面和后端接口不在同一個域名下,就會發(fā)生跨域請求的問題,同源策略是瀏覽器的一種安全策略,它限制了來自不同源的客戶端腳本在瀏覽器中運行時的交互,需要的朋友可以參考下2023-12-12

