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; } }