Taotao's ruoyi learning notes - introduction of a complete set of verification codes - I (acquisition of verification codes)

Posted by synted on Thu, 13 Jan 2022 17:11:38 +0100

preface

In the last chapter, we started the project. The first thing to enter is the login page. Let's see how the verification code is implemented.

Click the verification code, and the network request will have two requests

code request

data:image/gif

Obviously, the first request is to send a verification code acquisition request to the back end;
The second request is a gif.
Popular science here

  • The full name of GIF is Graphics Interchange Format, which can be translated into graphics exchange format. It is used to display indexed color images in Hypertext Markup Language. It has been widely used in the Internet and other online service systems. GIF is a public image file format standard, and the copyright belongs to Compu Serve*

Look at the buttons they write

 <el-form-item prop="code" v-if="captchaOnOff">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          placeholder="Verification Code"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
        </div>
</el-form-item>

Why can't I write it???

Read the code

      <el-form-item prop="code" v-if="captchaOnOff">
        <!-- el At the beginning, yes element UI   Official website: https://element.eleme.cn/#/zh-CN/component/installation -->
        <!-- code It's the verification code you entered. Let's get the verification code first
        stay Vue In, the relationship between parent and child components can be summarized as props down, events up. Parent component passed props The data is passed down to the sub component through events Send a message to the parent component.
        Obviously this code Is the parent component passed prop Passing data to subcomponents
         -->
        <!-- captchaOnOff The initial value is true,Is the verification code switch -->
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          placeholder="Verification Code"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
        <!-- Bound here loginForm.code,this code It's in the form object code,Not as mentioned above code

        auto-complete="off"   What does it mean to turn off automatic code completion? Put the mouse cursor in the input box, and the drop-down box will not appear automatically. If it is changed to on,The recently entered data will appear.
        autocomplete Property specifies whether the input field should have autocomplete enabled. It is enabled by default, that is, when you click input
        After getting the focus, the browser will automatically display the previous input records as filling options. This is HTML5 New property in, not supported in HTML5 It's useless under your browser.
        notes: autocomplete Properties apply to <form>,And the following <input> Type: text, search, url, telephone, email, password, datepickers, range as well as color. 
        In some browsers, autocomplete Property is closed by default. If you need to open it, open it. Because this is input Bring your own math, will be in your own div Added a layer div of ul List.

        placeholder Property provides a prompt that describes the expected value of the input field( hint). 
        The prompt is displayed when the input field is empty and disappears when the field gets focus.
        @keyup.enter  yes  vue Monitor keyboard enter event
        If the component is encapsulated, use
        @keyup.enter.native

        Finally, press enter to call handleLogin method
         -->
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
          <!-- Vue in slot Use of   https://www.cnblogs.com/qq735675958/p/8377761.html
           It is an extension of components through slot The slot transmits content to the specified location inside the component through slot It can be passed from father to son
           Namely slot The appearance of is so that the parent component can openly add content to the child component.
<span slot="header">hello world</span>
<slot  name="header">This is named slot Default content for</slot>
      Obviously this is called prefix of slot As mentioned above gif
           -->
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
          <!-- according to codeUrl Display the verification code image, click this img Will call getCode method -->
        </div>
      </el-form-item>

I Look at the front-end logic

1. When the login page is created, the getCode() method will be called

2. Take a look at getCode()

    getCode() {
      getCodeImg().then(res => {
        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
        if (this.captchaOnOff) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    }

unscramble

    getCode() {
      getCodeImg().then(res => {
        // The getCodeImg() interface will be called to process the returned res
        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
        //If the returned captchaOnOff is undefined, it is estimated that nothing has been accessed, it returns true, otherwise it returns false
        // What the hell? Take a closer look at captchaOnOff in data. Ouch, it is the verification code switch. The initial value is true, so the code in this line is whether you can continue to request the verification code
        //If it is true, it means that if nothing is returned, the request can certainly continue. If it is false, the request for verification code cannot continue
        //Let's see where captchaOnOff is used. Oh, it's < El form item prop = "code" V-IF = "captchaOnOff" >
        //Obviously, if captchaOnOff is false, this component will not be displayed
        if (this.captchaOnOff) {  //If there is a verification code
          this.codeUrl = "data:image/gif;base64," + res.img; //The value of codeUrl is changed to data:image/gif;base64,res.img
          //If you don't understand base64, look at this https://blog.csdn.net/weixin_38465623/article/details/80199999
          //Of course, you can also see my summary
          /**
           * base64             data:image/gif;base64," + res.img
           * data Indicates the contract name for obtaining data. image/gif is the name of the data type. Base64 is the encoding method of the data. After the comma is the base64 encoded data of the image/gif file.
           * Currently, Data URI scheme supports the following types:
              data:,Text data
              data:text/plain,Text data
              data:text/html,HTML code
              data:text/html;base64,base64 Encoded HTML code
              data:text/css,CSS code
              data:text/css;base64,base64 Coded CSS code
              data:text/javascript,Javascript code
              data:text/javascript;base64,base64 Coded Javascript code
              data:image/gif;base64,base64 Encoded gif picture data
              data:image/png;base64,base64 Encoded png picture data
              data:image/jpeg;base64,base64 Encoded jpeg picture data
            base64 In short, it translates some 8-bit data into standard ASCII characters. There are many free base64 encoding and decoding tools on the Internet. The function base64 can be used in PHP_ Encode()
Take an example of a picture:
A picture in the web page can be displayed as follows:
            <img src="http://www.aimks.com/images/wg.png"/>
It can also be displayed like this
            <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEU
            gAAAAEAAAAkCAYAAABIdFAMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZS
            BJbWFnZVJlYWR5ccllPAAAAHhJREFUeNo8zjsOxCAMBFB/KEAUFFR
            0Cbng3nQPw68ArZdAlOZppPFIBhH5EAB8b+Tlt9MYQ6i1BuqFaq1C
            KSVcxZ2Acs6406KUgpt5/LCKuVgz5BDCSb13ZO99ZOdcZGvt4mJjz
            MVKqcha68iIePB86GAiOv8CDADlIUQBs7MD3wAAAABJRU5ErkJggg%3D%3D"/>
           */
          //Obviously, res.img is the picture transmitted. The picture is generated at the back end, the base64 picture is converted into characters and transmitted back to the front end, and then the characters at the front end are converted into pictures and displayed to the user. Wonderful seconds
          this.loginForm.uuid = res.uuid;
          //And the UUID of the form is set to res.uuid
        }
      });
    }

The first line of the getCode() method calls another method, getCodeImg()
Let's see what this is

3. Take a look at getCodeImg()

Good guy, let me find it. This method is not in methods
here

Fortunately, I'm smart. ctrl click the method name to find it directly

// Get verification code
export function getCodeImg() {
  return request({
    url: '/code',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  })
}

There is nothing to interpret. Export a method getCodeImg(),
This method calls a get request / code. The isToken value of the request header is false, no token is required, and the tineout is 20000. We'll see how to use this parameter later.

II Look at the back-end logic

Now start looking at the back end
The front end above sent a / code request. We saw it in the code and the network request. Let's take a look at the network request

How does the backend receive this / code request?

1. Enter RouterFunctionConfiguration class first

The gateway is the first step for all front-end interfaces to access the back-end
Here, the request is received by the RouterFunctionConfiguration under the config package

Read it

package com.ruoyi.gateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import com.ruoyi.gateway.handler.ValidateCodeHandler;

/**
 * Routing configuration information
 *
 * @author ruoyi
 */
@Configuration
public class RouterFunctionConfiguration {
    @Autowired  // Inject an object of ValidateCodeHandler, which is equivalent to the service injected in Collectors, and is used to encapsulate logic
    private ValidateCodeHandler validateCodeHandler;

    @SuppressWarnings("rawtypes")
    @Bean
    public RouterFunction routerFunction() {
        /**  reference resources https://www.cnblogs.com/somefuture/p/15433565.html
         * Spring The framework provides us with two http endpoint exposure methods to hide the servlet principle:
         *  One is based on the annotation form @ Controller or @ RestController and other annotations, such as @ RequestMapping, @ GetMapping, etc.
         *  The other is to configure RouterFunction and HandlerFunction based on routing, which is called "functional WEB".
         */
        return RouterFunctions.route(
                // Receive / code requests
                /**  Spring WebFlux Is an asynchronous non blocking Web framework, which can make full use of the hardware resources of multi-core CPU to process a large number of concurrent requests.
                 *   Spring Cloud Gateway Match routes as part of the Spring WebFlux HandlerMapping infrastructure.
                 *    Spring Cloud Gateway Includes many built-in routing assertion factories. All of these assertions match different attributes of the HTTP request. Multiple route assertion factories can be used in conjunction with logical and statements.
                 *    reference resources https://www.cnblogs.com/h--d/p/12741901.html
                 */
                RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), //TEXT_PLAIN = new MediaType("text", "plain");
                // Obviously, this line of code will forward the code to the validateCodeHandler for processing
                // text/html means to set the content type of the file to the form of text/html. When the browser obtains this file, it will automatically call the html parser to process the file accordingly.
                //text/plain means to set the file as plain text. The browser will not process this file when it gets it.
                validateCodeHandler);
    }
} 

2. Skip to ValidateCodeFilter

Obviously, this is a verification code filter, here
The non login / registration request or verification code is closed and will not be processed

So let go

3. Skip to ValidateCodeHandler

Direct interpretation

package com.ruoyi.gateway.handler;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.ruoyi.common.core.exception.CaptchaException;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.gateway.service.ValidateCodeService;
import reactor.core.publisher.Mono;

/**
 * Verification code acquisition
 *
 * @author ruoyi
 */
@Component
public class ValidateCodeHandler implements HandlerFunction<ServerResponse> {
    @Autowired
    private ValidateCodeService validateCodeService;  //Verification code service object

    @Override
    public Mono<ServerResponse> handle(ServerRequest serverRequest) {
        //  Reactive programming Reactor's concepts about Flux and Mono
        /**
         * Flux Similar to RxJava Observable, it can trigger zero to multiple events and end processing or trigger errors according to the actual situation.
         * Mono Only one event can be triggered at most
         */
        AjaxResult ajax;  //The operation message reminder is a unified standard response body
        try {
            ajax = validateCodeService.createCapcha(); //Create a verification code
        } catch (CaptchaException | IOException e) {
            return Mono.error(e);
        }
        //
        return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax));
    }
}

Finally, the generated verification code is transmitted to the front end as a response body.

4. Take a look at validatecodeservice Createcapcha() did a good job

It turned out that a verification code was generated and directly interpreted

  /**
     * Generate verification code
     */
    @Override
    public AjaxResult createCapcha() throws IOException, CaptchaException {
        AjaxResult ajax = AjaxResult.success();  //Unified response body
        System.out.println("captchaProperties Initial value----" + "captchaProperties.getType()---" + captchaProperties.getType() + "---captchaProperties.getEnabled()---" + captchaProperties.getEnabled());
        //Print result: initial value of captchaproperties ----- captchaproperties getType()---math---captchaProperties. getEnabled()---true
        boolean captchaOnOff = captchaProperties.getEnabled();  //The default value of boolean is false
        ajax.put("captchaOnOff", captchaOnOff); //This is the verification code switch required by the front end. If the default value is false, the front end will close the verification code request
        if (!captchaOnOff) {   //I still don't know what this code means. What I read is always executed, because it is not initialized, and the default value is false
            return ajax;  //If any children's shoes are found, please point out the maze in the comment area
        }

        // Save verification code information
        String uuid = IdUtils.simpleUUID(); //Generate unique UUID
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; //Key stored as redis

        String capStr = null, code = null;  //Verification code string
        BufferedImage image = null;

        String captchaType = captchaProperties.getType();  //The verification code type (math array calculates char character) is the same, and the assignment is not found
        // Generate verification code
        if ("math".equals(captchaType)) {  //math type
            String capText = captchaProducerMath.createText();
            System.out.println("captchaProducerMath.createText()Initial value---"+capText);
            //Print results: captchaproducermath Createtext() initial value -- 8-5 =@ three
            capStr = capText.substring(0, capText.lastIndexOf("@")); //  lastIndexOf returns the index of the last occurrence of the specified character in this string
            // captchaProducerMath.createText() creates a mathematical string, such as 8-5 =@ three
            // The string is split according to @
            //  The picture to be displayed is the picture generated by the string in front of @
            //  The verification code should be the character generated for the string following @
            //  Wonderful! Seconds
            code = capText.substring(capText.lastIndexOf("@") + 1);
            image = captchaProducerMath.createImage(capStr);
            //Through this mathematical verification code, the producer, according to capStr, is 8-5 =? Assign a value to the BufferedImage object
        } else if ("char".equals(captchaType)) {  //char type
            capStr = code = captchaProducer.createText();
            /**
             *captchaProducer.createImage(capStr)
             * Generate an image that looks like the content of capStr
             * Character to image
             */
            image = captchaProducer.createImage(capStr);
        }
/**
 * Finally, capStr is the code generated according to the verification code
 */
        redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);  //redis stores the verification code, and the key is captcha_codes+UUID value is the value of verification code, and the storage time is one minute
        // Conversion stream information write out
        /**
         * Byte array output stream creates a byte array buffer in memory, and all data sent to the output stream is saved in the byte array buffer.
         * FastByteArrayOutputStream The internal implementation is composed of a LinkedList < byte [] >. Each expansion allocates an array space,
         * And when the data is put into the List. The length of the array to be allocated is determined by calling the write method of FastByteArrayOutputStream.
         * ByteArrayOutputStream is internally implemented as an array. Each expansion needs to reallocate space and copy data to a new array,
         * This is the main difference between FastByteArrayOutputStream and ByteArrayOutputStream.
         */
        // Fast byte array output stream
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try {
            //
            /**
             * Write an image to File using any ImageWriter that supports the given format. If a File already exists, its contents are discarded.
             * im - The RenderedImage to write.
             * formatName - A String containing an informal name.
             * output - The File in which data will be written.
             * If no suitable writer is found, false is returned.
             */
            //Obviously, the image created by captchaproducer math is written to the output stream in jpg format
            //The purpose of this is to convert the image into characters next
            //Image to image character transfer
            ImageIO.write(image, "jpg", os);
        } catch (IOException e) {
            return AjaxResult.error(e.getMessage());
        }

        ajax.put("uuid", uuid);
           // Image strings are transmitted through Base64 encoding
           // Base64 encoding is a process from binary to character, which can be used to transfer long identification information in HTTP environment. Base64 encoding is unreadable and can only be read after decoding.
           // For encryption
        ajax.put("img", Base64.encode(os.toByteArray()));
        return ajax;
    }

Request verification code is probably this process

5. Look at other related categories

CaptchaConfig class

package com.ruoyi.gateway.config;

import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import static com.google.code.kaptcha.Constants.*;

/**
 * Verification code configuration
 * 
 * @author ruoyi
 */
@Configuration
public class CaptchaConfig
{
    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaBean()
    {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // Whether there is a border is true by default. We can set yes and no by ourselves
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // The verification code text character color defaults to color BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
        // The width of verification code picture is 200 by default
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // The height of verification code picture is 50 by default
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // The character size of verification code text is 40 by default
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
        // The character length of verification code text is 5 by default
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        // The font style of verification code text defaults to new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // Picture style watercolor com google. code. kaptcha. impl. Waterripple fisheye.com google. code. kaptcha. impl. Fisheyegimpy shadow com google. code. kaptcha. impl. ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
    
    @Bean(name = "captchaProducerMath")
    public DefaultKaptcha getKaptchaBeanMath()
    {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // Whether there is a border is true by default. We can set yes and no by ourselves
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // The border color defaults to color BLACK
        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
        // The verification code text character color defaults to color BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        // The width of verification code picture is 200 by default
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // The height of verification code picture is 50 by default
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // The character size of verification code text is 40 by default
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
        // Verification code text generator
        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.gateway.config.KaptchaTextCreator");
        // The character spacing of verification code text is 2 by default
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
        // The character length of verification code text is 5 by default
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
        // The font style of verification code text defaults to new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // The verification code noise color defaults to color BLACK
        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
        // Interference implementation class
        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
        // Picture style watercolor com google. code. kaptcha. impl. Waterripple fisheye.com google. code. kaptcha. impl. Fisheyegimpy shadow com google. code. kaptcha. impl. ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

KaptchaTextCreator class

package com.ruoyi.gateway.config;

import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;

/**
 * Verification code text generator
 * 
 * @author ruoyi
 */
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 = (int) Math.round(Math.random() * 2);
        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 (randomoperands == 2)
        {
            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]);
            }
        }
        else
        {
            result = x + y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("+");
            suChinese.append(CNUMBERS[y]);
        }
        suChinese.append("=?@" + result);
        return suChinese.toString();
    }
}

CaptchaProperties class

package com.ruoyi.gateway.config.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

/**
 * Verification code configuration
 *
 * @author ruoyi
 */
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties {
    /**
     * Verification code switch
     */
    private Boolean enabled;

    /**
     * Verification code type (math array calculates char characters)
     */
    private String type;

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}