初出茅庐.开胃菜·代码思路·验证码

YangeIT大约 10 分钟

初出茅庐.开胃菜·代码思路·验证码

在看验证码这部分代码的时候需要带着一些问题来看代码

1.验证码是什么时候生成的?

2.验证码的有效期是如何实现的?

3.验证码是如何返回给前端的?

4.登录时验证码是如何比对的?

5.登录一般都会使用到缓存,我们的系统中是否有使用到缓存,用的是什么缓存,存了什么?

思路

1.验证码是实在首页登录的时候使用的,在首页初使用浏览器F12查看前端是如何调用验证码接口的

但是目前我们还没有启动代码,可以通过传智提供的在线体验项目来查看

http://huike-crm.itheima.net/#/loginopen in new window

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中等问题,我们需要接下去看登录部分。到此验证码生成部分已经全部讲完了