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

現(xiàn)在的登錄都需要輸入驗(yàn)證碼用來(lái)檢測(cè)是否是真人登錄,所以驗(yàn)證碼功能在現(xiàn)在是非常普遍的,那么接下來(lái)我們就基于springboot來(lái)實(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ì)算問(wèn)題是我們自己來(lái)實(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è)變量來(lái)接收。
上面這兩中類型的驗(yàn)證碼判斷完成之后,不管是那種類型,最后都需要把數(shù)據(jù)存到緩存中,并且都會(huì)生成一個(gè)Base64編碼的一個(gè)圖片,我們只需要返回這個(gè)圖片即可,還需要一個(gè)uuid,因?yàn)檫@個(gè)是用來(lái)作為key的唯一標(biāo)識(shí)。
二 代碼實(shí)現(xiàn)
開(kāi)始代碼之前,先介紹一下我注入的幾個(gè)bean:
@Autowired
private ISysConfigService configService ; //判斷是否開(kāi)啟驗(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)證碼圖片① 先判斷有沒(méi)有開(kāi)啟驗(yàn)證碼功能。
我們有一個(gè)網(wǎng)頁(yè)功能的數(shù)據(jù)庫(kù)表,里面可以選擇是否開(kāi)啟驗(yàn)證碼功能,并且在類加載的時(shí)候,我們就已經(jīng)使用 @PostConstruct(標(biāo)記在方法上, 當(dāng)這個(gè)bean創(chuàng)建完成,自動(dòng)執(zhí)行這個(gè)注解標(biāo)記方法,要求這個(gè)方法無(wú)參 類似 init-method配置) 注解將數(shù)據(jù)庫(kù)的數(shù)據(jù)緩存在了redis里面。
所以我們可以判斷先判斷有沒(méi)有開(kāi)啟驗(yàn)證碼功能:
// 先要判斷一下是否開(kāi)啟了驗(yàn)證碼功能,如果沒(méi)有開(kāi)啟,則不需要進(jìn)行生成驗(yàn)證碼的操作了
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (!captchaEnabled){
return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回沒(méi)有開(kāi)啟驗(yàn)證碼信息給前端
}configService這個(gè)類就是我們用來(lái)初始化緩存數(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è)方法無(wú)參 類似 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ù)庫(kù)
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)證碼是否以及開(kāi)啟
@Override
public boolean selectCaptchaEnabled() {
String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");
if (StrUtil.isBlank(cacheObject)){
// 如果查詢出來(lái)的是空,則說(shuō)明該賬號(hào)是第一次登錄,則默認(rèn)開(kāi)啟驗(yàn)證碼
return true ;
}
// 如果不是空,那么查詢到的數(shù)據(jù)不是true就是false,但是存到數(shù)據(jù)庫(kù)的并不是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.如果沒(méi)有得到數(shù)據(jù),那么我們需要從sys_config數(shù)據(jù)庫(kù)表中查詢數(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;
}
// 否則沒(méi)有查到數(shù)據(jù),則說(shuō)明沒(méi)有信息
return null ;
}
}在進(jìn)行正式寫(xiě)邏輯代碼之前,我們需要引入幾個(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② 判斷開(kāi)啟后,我們接下來(lái)需要判斷使用的是哪一種驗(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.正常來(lái)說(shuō),接下來(lái)我們需要把數(shù)據(jù)存到緩存中去,但是因?yàn)槠胀愋偷尿?yàn)證碼也有這一步,所以這一部分重復(fù)邏輯就放在最后,我們現(xiàn)在需要根據(jù)前面的表達(dá)式部分來(lái)生成一張圖片。
// 最后,我們需要把“1+2=?”制作成一個(gè)圖片返回給前端,用于展示給用戶,
// 制作成圖片也有專門的工具類,然后我們需要把這個(gè)圖片轉(zhuǎn)成64編碼返回給前端,這個(gè)功能代碼其實(shí)是固定的
//生成驗(yàn)證碼圖片(google.code.kaptcha提供的工具類Product)
image = captchaProducerMath.createImage(expression);captchaProducerMath是谷歌提供的工具類,我們只需要注入使用就行。
4.正常來(lái)說(shuō),現(xiàn)在應(yīng)該是將驗(yàn)證碼圖片轉(zhuǎn)成Base64編碼,然后返回給前端展示,但是和上面緩存數(shù)據(jù)一樣,兩種編碼類型都是獲取到驗(yàn)證碼數(shù)據(jù)緩存在redis并且把生成的驗(yàn)證碼圖片以Base64編碼格式返回給前端,所以我們接下來(lái)就是為普通驗(yàn)證碼類型獲取驗(yàn)證碼數(shù)據(jù)和生成圖片。
④ 如果是普通類型的驗(yàn)證碼
else {
/**
* 如果不是計(jì)算式類型的驗(yàn)證碼,那就是圖形類型的驗(yàn)證碼,那么就更簡(jiǎ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來(lái)作為key,然后把uuid給前端,前端在訪問(wèn)的時(shí)候再把uuid傳來(lái)進(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);//把圖片通過(guò)jpeg類型寫(xiě)進(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一行不能超過(guò)76字符,超過(guò)則添加回車換行符。
//解決方案: 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 ; //判斷是否開(kāi)啟驗(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è)谂渲梦募渲煤玫?
*/
// 先要判斷一下是否開(kāi)啟了驗(yàn)證碼功能,如果沒(méi)有開(kāi)啟,則不需要進(jìn)行生成驗(yàn)證碼的操作了
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (!captchaEnabled){
return ajaxResult.put("captchaOnOff",captchaEnabled) ; //返回沒(méi)有開(kāi)啟驗(yàn)證碼信息給前端
}
BufferedImage image = null ;//圖片
String expression ; //表達(dá)式部分
String answer ; //答案部分
String uuid ;; //uuid
// 否則說(shuō)明開(kāi)啟了驗(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ì)算問(wèn)題是我們自己來(lái)實(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)證碼,那么就更簡(jiǎ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來(lái)作為key,然后把uuid給前端,前端在訪問(wèn)的時(shí)候再把uuid傳來(lái)進(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);//把圖片通過(guò)jpeg類型寫(xiě)進(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一行不能超過(guò)76字符,超過(guò)則添加回車換行符。
//解決方案: 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 ;
}
}用于判斷是否開(kāi)啟驗(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è)方法無(wú)參 類似 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ù)庫(kù)
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)證碼是否以及開(kāi)啟
@Override
public boolean selectCaptchaEnabled() {
String cacheObject = redisCache.getCacheObject("sys.account.captchaOnOff");
if (StrUtil.isBlank(cacheObject)){
// 如果查詢出來(lái)的是空,則說(shuō)明該賬號(hào)是第一次登錄,則默認(rèn)開(kāi)啟驗(yàn)證碼
return true ;
}
// 如果不是空,那么查詢到的數(shù)據(jù)不是true就是false,但是存到數(shù)據(jù)庫(kù)的并不是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.如果沒(méi)有得到數(shù)據(jù),那么我們需要從sys_config數(shù)據(jù)庫(kù)表中查詢數(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;
}
// 否則沒(méi)有查到數(shù)據(jù),則說(shuō)明沒(méi)有信息
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;
/** 獲取地址開(kāi)關(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地址開(kāi)關(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)文章希望大家以后多多支持腳本之家!
- SpringBoot實(shí)現(xiàn)發(fā)送驗(yàn)證碼功能(圖片驗(yàn)證碼)
- 基于SpringBoot和Hutool工具包實(shí)現(xiàn)驗(yàn)證碼的案例
- SpringBoot使用hutool-captcha實(shí)現(xiàn)驗(yàn)證碼生成與驗(yàn)證
- SpringBoot 分布式驗(yàn)證碼登錄方案示例詳解
- SpringBoot整合Kaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能
- springboot驗(yàn)證碼的生成與驗(yàn)證的兩種方法
- SpringBoot前后端分離實(shí)現(xiàn)驗(yàn)證碼操作
- vue+springboot實(shí)現(xiàn)登錄驗(yàn)證碼
- SpringBoot登錄驗(yàn)證碼實(shí)現(xiàn)過(guò)程詳解
相關(guān)文章
Mybatis一對(duì)多延遲加載實(shí)現(xiàn)代碼解析
這篇文章主要介紹了Mybatis一對(duì)多延遲加載實(shí)現(xiàn)代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
java中的日期時(shí)間類Date和SimpleDateFormat
這篇文章主要介紹了java中的日期時(shí)間類Date和SimpleDateFormat,Date類的對(duì)象在Java中代表的是當(dāng)前所在系統(tǒng)的此刻日期時(shí)間,說(shuō)白了就是你計(jì)算機(jī)上現(xiàn)實(shí)的時(shí)間,需要的朋友可以參考下2023-09-09
Java實(shí)現(xiàn)短信發(fā)送驗(yàn)證碼功能
這篇文章主要介紹了Java實(shí)現(xiàn)短信發(fā)送驗(yàn)證碼功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-10-10
JAVA面試題 簡(jiǎn)談你對(duì)synchronized關(guān)鍵字的理解
這篇文章主要介紹了JAVA面試題 請(qǐng)談?wù)勀銓?duì)Sychronized關(guān)鍵字的理解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
詳解MyBatis開(kāi)發(fā)Dao層的兩種方式(Mapper動(dòng)態(tài)代理方式)
這篇文章主要介紹了詳解MyBatis開(kāi)發(fā)Dao層的兩種方式(Mapper動(dòng)態(tài)代理方式),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12
java算法題解LeetCode30包含min函數(shù)的棧實(shí)例
這篇文章主要為大家介紹了java算法題解LeetCode30包含min函數(shù)的棧實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Eclipse項(xiàng)目有紅感嘆號(hào)的解決方法
這篇文章主要為大家詳細(xì)介紹了Eclipse項(xiàng)目有紅感嘆號(hào)的解決方法,給出了Eclipse項(xiàng)目有紅感嘆號(hào)的原因,以及如何解決?,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
mybatis-config.xml文件中的mappers標(biāo)簽使用
在MyBatis配置中,<mapper>標(biāo)簽關(guān)鍵用于指定SQL?Mapper的XML文件路徑,主要有三種指定方式:resource、url和class,Resource方式從類的根路徑開(kāi)始,適合放在項(xiàng)目?jī)?nèi)部保障移植性,URL方式指定絕對(duì)路徑,移植性差,適用于外部路徑2024-10-10

