初出茅庐.开胃菜·代码思路·验证码
初出茅庐.开胃菜·代码思路·验证码
在看验证码这部分代码的时候需要带着一些问题来看代码
1.验证码是什么时候生成的?
2.验证码的有效期是如何实现的?
3.验证码是如何返回给前端的?
4.登录时验证码是如何比对的?
5.登录一般都会使用到缓存,我们的系统中是否有使用到缓存,用的是什么缓存,存了什么?
思路
1.验证码是实在首页登录的时候使用的,在首页初使用浏览器F12查看前端是如何调用验证码接口的
但是目前我们还没有启动代码,可以通过传智提供的在线体验项目来查看
http://huike-crm.itheima.net/#/login
2.对应的在后端找到对应后端代码,逐行跟进对应的工具包里的代码的实现逻辑不用深挖
1.操作步骤:
1.1确定接口位置
验证码在浏览前端页面的时候可以看到在登录处有显示一个验证码

通过浏览器F12,可以定位前端调用了那个接口

1.2定位后端代码
可以看到调用的接口是
使用全局搜索ctrl+shift+r搜索

直接定位到接口位置
CaptchaController
package com.huike.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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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;
import com.huike.common.constant.Constants;
import com.huike.common.core.domain.AjaxResult;
import com.huike.common.core.redis.RedisCache;
import com.huike.common.utils.sign.Base64;
import com.huike.common.utils.uuid.IdUtils;
/**
* 验证码操作处理
*
*
*/
//@Api("验证码")
@RestController
public class CaptchaController {
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisCache redisCache;
// 验证码类型
@Value("${huike.captchaType}")
private String captchaType;
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException{
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
// 生成验证码
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
}
1.3 验证码生成流程图
具体流程如下图所示:
1.4 对流程图的具体分析
具体获取验证码流程:
1.4.1.起点--前端发起请求触发接口
前端触发调用验证码接口获取验证码。
1.4.2.后端入口
由于后端提供了具体的controller,并且提供了对应的验证码接口,后端能够被触发
@RestController
public class CaptchaController {
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException{
开始执行生成验证码的逻辑
1.4.3 验证码生成逻辑
验证码生成,使用了
生成的数字验证码的代码,并不需要我们过多关心,我们只需要调用api即可,kaptcha可以生成数字或字符串的不用的验证码,系统中使用配置文件的方式进行配置
通过@Value注解的方式读取application.yml里的配置信息
@Value("${huike.captchaType}")
private String captchaType;

那么这里读取的配置就是配置文件里配置的字符串math
具体执行的代码
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisCache redisCache;
// 验证码类型
@Value("${huike.captchaType}")
private String captchaType;
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException{
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
// 生成验证码
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
第一遍看代码的时候,遇到自己不明白的地方先跳过
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException{
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
这里初始化了一堆东西,是什么,有什么用,现在知不知道,不知道?
怎么办?先跳过,反正就是生成一个一堆字符串呗,也不知道干了什么
未来读代码的时候也是这样遇到不明白的部分就先跳过,等用到的时候再来研究 👈
// 生成验证码
if ("math".equals(captchaType)){
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}else if ("char".equals(captchaType)){
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
再来看这部分代码
if ("math".equals(captchaType)){
xxx
}else if ("char".equals(captchaType)){
xxx
}
这部分是什么意思?能不能看明白
判断配置文件里的信息,captchaType现在在配置文件里配置的是什么?

那么代码会走哪部分
if ("math".equals(captchaType)){
xxx
}else if ("char".equals(captchaType)){
xxx
}
一定是上半部分 math的部分
在这里面又做了什么
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
这里就有几个问题了是什么?
都是什么,一开始定义了什么?是截取字符串,我们回头来看定义的部分
String capStr = null, code = null;
BufferedImage image = null;
if ("math".equals(captchaType)){
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
也就是说capStr,code,image都是提前定义好的,一开始都是null
我们可以打断点debug的方式来看具体都是什么内容,为什么要截取字符串

打上三个断点然后debug启动
前端刷新重新获取验证码,可以看到进入断点

可以看到capText的内容是7*0=?@0
按照@来截取字符串 capStr是7*0=? code的内容是0
这是什么?
这个过程具体是怎么生成的我们需要管吗 这都是 String capText = captchaProducerMath.createText();替我们生成好的 而 captchaProducerMath 正是调用了google的kaptcha的工具包所以具体它是如何生成的我们并不需要关心
我们通过截取字符串的形式截取出验证码的结果code和验证码的公式capStr
但是我们返回给前端的不是一个公式,而是一个图片验证码,这个还需要调用captchaProducerMath的createImage方法将公式传入,工具包会给我们生成一个图片,通过BufferedImage来接收

将其生成具体的图片后保存在内存中
1.4.4 验证码如何返回给前端
现在继续思考,我们在了,
1)我不能把内存中的信息返回给前端,我应该怎么将图片返回给前端呢
2)现在后端生成了验证码的结果,内存中也存储了图片了,后续将图片返回给前端了,在登录的时候如何比对这个验证码的结果呢? 比如现在张三获取了验证码1 ,李四获取了验证码2 ,这两个人都拿到了验证码,我如何区分这两个人呢?总不能张三提交的验证码和李四生成的验证码进行对比把
3)我们用过别人的系统,,现在还没看到超时时间如何设置
带着这两个问题我们继续来看代码
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
这里我们看到代码做了什么事,先不用思考为什么,和具体做了什么?
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
往redis里存了一堆东西
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
操作数据流,不知道做了什么 但是看到一个jpg似乎是个图片
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
这里我知道 构建了一个结果集AjaxResult对象返回 并往这个对象里设置了一些属性 uuid 和 img不知道是什么
我们一个一个来分析
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
redisCache.setCacheObject是什么意思往redis里添加属性,redis里添加属性一般都是key,value形式的数据
那么verifyKey就是redis的key,
code在redis中是value ,存的是验证码的值
那么我们回头来看verifyKey是怎么生成的
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String uuid = IdUtils.simpleUUID();
这就说明这是一个简易的id生成工具类,有兴趣的同学可以了解一下什么是uuid
然后定义了一个verifyKey是一个常量+刚刚生成的id组合而来
然后作为一个redis的key存入到redis中
存储redis的时候还设置了一个redis的超时时间

这样我们就往redis里存了验证码的结果
具体怎么存的呢:定义了一个id作为Key,value是验证码的值,并且设置了一个过期时间是2分钟
这样有什么意义呢?先不管,反正先这样往redis里存储了一份
然后继续看代码
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
定义了一个outputStream这是一个输出流
然后将图片里的信息写入到输出流里,那么这个输出流怎么用不知道
继续看代码
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
在构建结果集的时候这里使用了,os是输出流,将输出流里的信息进行base64位转码这是要做什么
这是一种文件的传输方式,
那么后端图片是如何传递给前端的呢?将图片先通过工具构建出来在内存中,并将其通过base64进行转码,然后通过包装类返回给前端
然后我们来看这个包装类里还有什么,将我们一开始生成的一个id也封装进去了,尝试思考,为什么要有这个id,有什么用? 当然如果想不明白也没有关系,我们可以先跳过这个问题,但是大概的关于验证码生成这部分的逻辑就已经全部讲完了,我们总结一下
1.5 总结
步骤:
1.前端调用接口
2.后端读取配置文件,基于配置文件里的信息生成验证码
3.生成了验证码以后,将验证码的结果保存在redis中,并设置超时时间2分钟,其中key为一个id,值为验证码的值
4.封装结果集返回前端,返回我们生成的一个id,和base64转码后的图片
疑问?
如果现在你看到这里还有不明白的点,可能还有超时时间是如何设置的?,这个id是什么?为什么要存在redis中等问题,我们需要接下去看登录部分。到此验证码生成部分已经全部讲完了