Because the article is too long, it is divided into two parts

The second half: juejin.cn/post/692204…

The development of front and back end separation is the mainstream of today’s development. Starting from scratch, this article uses SpringBoot and Vue step by step to realize the most common login functions in daily development, as well as the user management functions after login. With this example, you can quickly start the development of SpringBoot+Vue front-end and back-end separation.

preface

1. Introduction of separation of front and rear ends

Here is a brief description of what front-end separation and single-page applications are: The core idea of the front and back end separation is that the front page calls the Restuful API of the back end through Ajax for data interaction, while the single Page Web Application (SPA) has only one page. And the Web application dynamically updates the page as the user interacts with the application.

2. Introduction to the technology used in the example

Briefly describe the techniques used in this example, as shown below:

The back-end

  • SpringBoot: SpringBoot is currently the most popular Java backend framework. You can simply think of SSM(H) as a simplified, contract-based development that greatly speeds up development.

    IO /projects/sp…

  • MybatisPlus: MyBatis-Plus (MP for short) is a MyBatis enhancement tool, on the basis of MyBatis only do enhancement do not change, to simplify the development, improve efficiency.

    Official website: Mybatis. Plus /

Front end:

  • Vue :Vue is a set of progressive frameworks for building user interfaces. Although Vue3 has been released, at least for some time vue2. X has been the dominant application, so vuE2.

    Website: cn.vuejs.org/

  • ElementUI: ElementUI is the most popular Vue UI framework in China. Component is rich, style is numerous, also more accord with the public aesthetic. Although there was talk of discontinuing the maintenance update, with the release of Vue3, ElementPlus for Vue3 was officially Beta.

    Official website: element.eleme.cn/#/zh-CN

Database:

  • MySQL: MySQL is a popular open source relational database.

    Official website: www.mysql.com/

The techniques used in this example have been briefly described above, so it is a good idea to have some familiarity with them before starting this example.

First, environmental preparation

1, the front end

1.1. Install Node.js

The front-end project uses the scaffolding of VEu-CLI, vue-CLI needs to be installed through NPM, yes and NPM is integrated in Node.js, so the first step is to install Node.js, visit nodejs.org/en/, home page can download.

Run the installation package once you’ve downloaded it, the next step along the way. Then enter node -v in CMD to check whether the installation is successful.

As shown in the figure, the version number appears (according to the version at the time of download), indicating that the installation has been successful. In addition, the NPM package has been installed successfully. You can enter NPM -v to view the version number

1.2. Configure the NPM source

The original source of NPM is on a foreign server and downloading things is slow.

Download speeds can be increased in two ways.

  • Specify the source when downloading

    / / the source download from taobao warehouse NPM - registry=https://registry.npm.taobao.org installCopy the code
  • The configuration source is taobao warehouse

    // Set taobao source NPM configset registry https://registry.npm.taobao.org
    Copy the code

You can also install CNPM, but you may encounter some problems with it.

1.3. Install vuE-CLI scaffolding

Install vue-CLI scaffolding using the following command:

npm install -g vue-cli
Copy the code

X Vue CLI is installed in this mode. You need to run NPM install -g@vue/CLI to install the latest version. The new version allows you to initialize projects using a graphical interface and includes things like project health monitoring.

1.4, VS Code

The front-end development tool uses the most popular front-end development tool VS Code.

Website: code.visualstudio.com

Download the corresponding version and install it step by step. After installation, the initial interface is as follows:

After VS Code is installed, we usually need to search and install some needed plug-ins to aid in development. Installing the plug-in is easy, just look it up in the search panel and install it.

These plugins are typically installed:

  • Chinese: Chinese language plug-in
  • Vetur: Vue multifunctional integration plug-in, including: syntax highlighting, smart tips, Emmet, error tips, formatting, autocomplete, debugger. Vue plugin is an official Vue plugin for Vue developers.
  • ESLint: ESLint is a syntactic rule and code style checker that can be used to ensure that syntactically correct and consistent code is written.
  • VS Code – Debugger for Chrome – Debugger for Chrome
  • Beautify: The Beautify plug-in allows you to quickly format your code, making your messy code structure instantly more organized.

1.5, Chrome

Chrome is a popular browser and a common tool for our front-end development.

There are many ways to download Chrome.

After Chrome has been downloaded and installed, it is recommended to install a plug-in Vue. Js devTools, which is a very useful Vue debugging tool.

Google store download address: chrome.google.com/webstore/de…

2, the back end

  • The JDK version used by the backend is 1.8. For details about installation, see Win10 system installation and configuration JDK1.8
  • The maven version is 3.5. For installation and configuration, refer to the Maven series textbook (2) – Download and Configure Maven.
  • The development tool uses Idea, please find your own installation.

3. Database

Win10 configuration free installation version MySQL5.7

Ii. Project construction

1. Build front-end projects

1.1. Create a project

Here you use the command line to create the project, creating a new directory under the working file.

Then run the vue init webpack demo-vue command, where webpack uses webpack as the template to generate the project, and can also be replaced with pWA, simple and other parameters, which will not be described here. Demo-vue is the project name, but it can also be called something else.

There will be some prompts in the process of program execution, you can follow the default Settings all the way to enter, can also be modified as needed.

Vue – Router is the key to building a single page application.

OK, you can see that the project is completed under the directory. The basic structure is as follows.

1.2. Project operation

Use VS Code to open the completed initialized VUE project.

Click terminal in VS Code and enter the command NPM run dev to run the project.

Successful project operation:

Visit http://localhost:8080 to view the Demo.

1.3 description of project structure

In VS Code you can see the project structure as follows:

Detailed description of catalog items:

Let’s focus on a few files with the red flag.

1.3.1, index. HTML

The initial code for the home page file is as follows:

<! DOCTYPE HTML > < HTML > <head> <meta charset=" utF-8 "> <meta name="viewport" content="width=device-width,initial-scale=1.0">  <title>demo-vue</title> </head> <body> <div id="app"></div> <! -- built files will be auto injected --> </body> </html>Copy the code

Note that the

line is coded, and the following line is commented. The built file will be automatically injected, which means that everything else we wrote will be displayed in this div.

A single page application is a project that has only one HTML file, and when we open the application, we can have many pages on the surface, but they are all dynamically loaded into a div.

1.3.2, App. Vue

This file is called the root component because it contains all the other components.

A. Vue file is a custom file type that is similar to HTML in structure. A. Vue file is a VUE component. Take a look at its initial code:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
Copy the code

There is also a

, but it has nothing to do with the one in index.html. This is just a normal DIV block.

The content in the

The most important thing about this file is that line 4,
, is a container named “Routing view”, which means that the current route (URL) points to will be displayed in this container. That is, even if the other components have their own routes (urls, which need to be defined in the router folder’s index.js file), they are only ostensibly a single page, but in fact only in the root component app.vue.

1.3.3, main. Js

How does app.vue relate to index.html? The key is this file:

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})
Copy the code

The vue module is in node_modules, App is the component defined in app. vue, and router is the route defined in the router folder.

Vue. Config. productionTip = false prevents Vue from generating production prompts at startup.

In this js file, we create a Vue object (instance). The EL attribute provides a DOM element that already exists on the page as the mount target of the Vue object. Router means that the object contains the Vue Router and uses the route defined in the project. Components represent the Vue components that the object contains, and template is used as a string template to identify the Vue instance, similar to defining an HTML tag.

1.3.4, router/index. Js

Router /index.js router/index.js router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

export default new Router({
  routes: [{path: '/'.name: 'HelloWorld'.component: HelloWorld
    }
  ]
})
Copy the code

Import several components from the top, and define the route in the Routes array. You can see that the/path is routed to the HelloWorld component, so visit http://localhost:8080/ and you will see the interface above. Vue SRC \ Components \ helloWorld.vue: SRC \ Components \ helloWorld.vue: SRC \ Components \ helloWorld.vue

<template>
  <div id="demo">
    {{msg}}
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Hello Vue! '}}}</script><! -- Add"scoped" attribute to limit CSS to this component only -->
<style scoped>
#demo{
  background-color: bisque;
  font-size: 20pt;
  color:darkcyan;
  margin-left: 30%;
  margin-right: 30%;
}
</style>
Copy the code

Vue-cli will hot update our changes, open http://localhost:8080/ again, the interface changes:

2. Back-end project construction

2.1. Back-end project creation

The backend project is created as follows:

  • Open the Idea,New Project, the choice ofSpring Intializr

  • Fill in the relevant information about the project

  • The SpringBoot version is 2.3.8, and the Web and MySQL driver dependencies are selected

  • Create completed projects

  • Project complete POM.xml
<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0. 0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>  <version>2.38..RELEASE</version> <relativePath/> <! -- lookup parent from repository --> </parent> <groupId>cn.fighter3</groupId> <artifactId>demo-java</artifactId> <version>0.01.-SNAPSHOT</version>
    <name>demo-java</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion>  </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Copy the code

2.3. Introduce MybatisPlus

If you are not familiar with MybatisPlus, you can refer to SpringBoot learning notes (17: MybatisPlus).

For more information, check out the official website.

2.3.1 Introduce MP dependency

<! <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.41.</version>
        </dependency>
Copy the code

Because the database table of this example is very simple, there is only one single table, so here we directly write the basic add, delete, change query

2.3.2 Database creation

The database design is very simple, with only one table.

The construction sentences are as follows:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `login_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Login name',
  `user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Username',
  `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'password',
  `sex` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'gender',
  `email` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'email',
  `address` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'address'.PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

2.3.3, configuration,

Write the relevant configuration to application.properties:

Server.port =8088Spring datasource. Driver -class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo? characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
Copy the code

Add the @mapperscan annotation to the startup class and scan the Mapper folder:

@SpringBootApplication
@MapperScan("cn.fighter3.mapper")
public class DemoJavaApplication {

    public static void main(String[] args) { SpringApplication.run(DemoJavaApplication.class, args); }}Copy the code

2.3.3 Relevant codes

MP provides the function of code generator, which can generate the code of Controller, Service, Mapper and entity class by module. In the case of a large number of database tables, it can improve the development efficiency. There is a Demo on the official website, if you are interested, you can view it yourself.

  • Entity class
/ * * *@Author: Three points of evil *@Date: 2021/1/17
 * @Description: user entity class **/
@TableName(value = "user")
public class User {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String loginName;
    private String userName;
    private String password;
    private String sex;
    private String email;
    private String address;
    // omit getters, setters, etc
}
Copy the code
  • Mapper interface: Inherit BaseMapper
/ * * *@Author: Three points of evil *@Date: 2021/1/17
 * @Description: TODO
 **/

public interface UserMapper extends BaseMapper<User> {}Copy the code

OK, the add, delete, change and check function of this single table has been completed, isn’t it very simple?

You can write a unit test to test it.

2.3.4 unit test

@SpringBootTest
class UserMapperTest {
    @Autowired
    UserMapper userMapper;

    @Test
    @displayName (" Insert data ")
    public void testInsert(a){
        User user=new User("test1"."test"."t123"."Male"."[email protected]"."Mantown");
        Integer id=userMapper.insert(user);
        System.out.printf(id.toString());
    }

    @Test
    @displayName (" Find by ID ")
    public void testSelectById(a){
        User user=userMapper.selectById(1);
        System.out.println(user.toString());
    }

    @Test
    @displayName (" Find all ")
    public void testSelectAll(a){
        List userList=userMapper.selectObjs(null);
        System.out.println(userList.size());
    }

    @Test
    @ DisplayName (" update ")
    public void testUpdate(a){
        User user=new User();
        user.setId(1);
        user.setAddress("Golden Gourd Town");
        Integer id=userMapper.updateById(user);
        System.out.println(id);
    }

    @Test
    @ DisplayName (" delete ")
    public void testDelete(a){
        userMapper.deleteById(1); }}Copy the code

At this point, the basic construction of the front and back end projects is completed, and then the functional development begins.

Third, login function development

1. Front-end development

1.1. Login interface

This image is introduced in the root component — SRC \ app. vue. Comment or remove the following line.

Create a folder views in the SRC directory and create a file login.vue in the views directory

<input type="text" V-model ="loginForm. LoginName "placeholder=" please input username "/> <br><br> <input type="password" V-model ="loginForm. Password "placeholder=" please input password" /> <br><br> <button> </template> <script> export default { name: 'Login', data () { return { loginForm: { loginName: '', password: '' }, responseResult: [] } }, methods: { } } </script>Copy the code

1.2. Add a route

Add the route to config\index.js with the following code:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
// Import the login page component
import Login from '@/views/login.vue'

Vue.use(Router)

export default new Router({
  routes: [{path: '/'.name: 'HelloWorld'.component: HelloWorld
    },
    // Add a route to the login page
    {
      path:'/login'.name: 'Login'.component: Login
    }
  ]
})

Copy the code

OK, now enter http://localhost:8080/#/login in your browser to access the login page:

It doesn’t matter if the page is a little rough and ready, but we can bring in ElmentUI and use components that are already in ElementUI.

1.3. Introduce ElementUI to beautify the interface

The official address of Element is element.elem. IO /#/ zh-cn. The official document is easier to understand.

1.3.1 install Element UI

Open the terminal in vscode and run the command NPM I element-ui -s to install the latest version of element UI – currently 2.15.0

1.3.2. Introduce Element

Introduction can be divided into two modes: complete introduction and on-demand introduction. On-demand introduction can reduce the volume of the project, so we choose complete introduction here.

According to the documentation, we need to modify main.js to the following:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
/ / introduce ElementUI
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

/* eslint-disable no-new */

Vue.use(ElementUI)

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

Copy the code

1.3.3 use ElementUI to beautify the login page

Now let’s start beautifying our login interface with ElementUI and CSS. The modified login.vue code looks like this:

<template>
  <body id="login-page">
    <el-form class="login-container" label-position="left" label-width="0px">
      <h3 class="login_title">System login</h3>
      <el-form-item>
        <el-input
          type="text"
          v-model="loginForm.loginName"
          auto-complete="off"
          placeholder="Account"
        ></el-input>
      </el-form-item>
      <el-form-item>
        <el-input
          type="password"
          v-model="loginForm.password"
          auto-complete="off"
          placeholder="Password"
        ></el-input>
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button
          type="primary"
          style="width: 100%; border: none"
          >Login < / el - button ></el-form-item>
    </el-form>
  </body>
</template>

<script>
export default {
  name: "Login".data() {
    return {
      loginForm: {
        loginName: "".password: "",},responseResult: [],}; },methods: {}};</script>

<style scoped>
#login-page {
  background: url(".. /assets/img/bg.jpg") no-repeat;
  background-position: center;
  height: 100%;
  width: 100%;
  background-size: cover;
  position: fixed;
}
body {
  margin: 0px;
}
.login-container {
  border-radius: 15px;
  background-clip: padding-box;
  margin: 90px auto;
  width: 350px;
  padding: 35px 35px 15px 35px;
  background: #fff;
  border: 1px solid #eaeaea;
  box-shadow: 0 0 25px #cac6c6;
}

.login_title {
  margin: 0px auto 40px auto;
  text-align: center;
  color: # 505458;
}
</style>


Copy the code

Note:

  • Create a new folder, img, under the SRC \ Assets path, and place an uncopyrighted image found online as the background image in img

  • Delete a line of code from app.vue, otherwise there would be blank:

    margin-top: 60px;
    Copy the code

Ok, let’s have a look at our modified login screen:

OK, the face of the login interface is done, but the inside is still empty, and there is no way to interact with the background.

1.4. Introduce AXIOS to initiate requests

I’m sure you’re all familiar with Ajax. In the case of separation of the front and back ends, the front and back ends interact with each other using asynchronous requests from the front end and JSON returns from the back end.

Axios is an HTTP client based on Promise for browsers and NodeJS, which is essentially a wrapper around native XHR, except that it is an implementation of Promise that complies with the latest ES specification. All we need to know here is that it is a very powerful network request processing library and is widely used.

Run the command NPM install –save axios in the project directory to install the module:

Register axios globally in main.js:

var axios = require('axios')
// Register globally, then send data in other components via this.$axios
Vue.prototype.$axios = axios
Copy the code

So how do you make a request using Axios?

Add methods to login.vue:

  methods: {
     login () {
        this.$axios
          .post('/login', {
            loginName: this.loginForm.loginName,
            password: this.loginForm.password
          })
          .then(successResponse= > {
            if (successResponse.data.code === 200) {
              this.$router.replace({path: '/'})
            }
          })
          .catch(failResponse= >{})}},Copy the code

This method makes a request to the background via AXIos, and if it returns a successful result it goes to/route.

Trigger this method in the login button:

        <el-button
          type="primary"
          style="width: 100%; border: none"
          @click="login"</el-button >Copy the code

So now I can make a request to the background? Yet.

1.5. Front-end configuration

  • The reverse proxy

    Modify SRC \main.js to add the reverse proxy configuration:

    / / set the reverse proxy, the front-end request sent to the http://localhost:8888/api by default
    axios.defaults.baseURL = 'http://localhost:8088/api'
    Copy the code

So, we write in front of the login request, visit the backend address is http://localhost:8088/api/login

  • Cross-domain configuration

    There is a problem with front – and back-end separation – cross-domain, which is not explained here. In config\index.js, find the proxyTable location and change it to the following:

        proxyTable: {
          '/api': {
            target: 'http://localhost:8088'.changeOrigin: true.pathRewrite: {
              '^/api': ' '}}},Copy the code

2. Back-end development

2.1. Unified result encapsulation

Here we create a Result class to encapsulate the results returned asynchronously. In general, there are several elements necessary for a result

  • Code can be used to indicate success (for example, 200 indicates success and 400 indicates exception).
  • Result message
  • The resulting data
/ * * *@Author: Three points of evil *@Date: 2021/1/17
 * @Description: unified result encapsulation **/

public class Result {
    / / code accordingly
    private Integer code;
    / / information
    private String message;
    // Return data
    private Object data;
    // omit getters, setters, constructors
}
Copy the code

In fact, since the response code is fixed, the code property should be an enumerated value, which simplifies things a bit.

2.2. Login business entity class

To receive front-end login data, we create a business entity class for login:

public class LoginDTO {
    private String loginName;
    private String password;
    // omit the getter and setter
}
Copy the code

2.3. Control layer

LoginController to make a service response:

/ * * *@Author: Three points of evil *@Date: 2021/1/17
 * @Description: TODO
 **/
@RestController
public class LoginController {
    @Autowired
    LoginService loginService;

    @PostMapping(value = "/api/login")
    @CrossOrigin       // The backend is cross-domain
    public Result login(@RequestBody LoginDTO loginDTO){
      returnloginService.login(loginDTO); }}Copy the code

2.4. Business Layer

The business layer does the actual business processing.

  • LoginService:
public interface LoginService {
    public Result login(LoginDTO loginDTO);
}
Copy the code
  • LoginServiceImpl:
/ * * *@Author: Three points of evil *@Date: 2021/1/17
 * @Description: * * /
@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public Result login(LoginDTO loginDTO) {
        if (StringUtils.isEmpty(loginDTO.getLoginName())){
            return new Result(400."The account cannot be empty."."");
        }
        if (StringUtils.isEmpty(loginDTO.getPassword())){
            return new Result(400."Password cannot be empty."."");
        }
        // Query users by login name
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.eq("login_name", loginDTO.getLoginName());
        User uer=userMapper.selectOne(wrapper);
        // Compare passwords
        if(uer! =null&&uer.getPassword().equals(loginDTO.getPassword())){
            return new Result(200."",uer);
        }
        return new Result(400."Login failed".""); }}Copy the code

Start the back-end project:

The login page is displayed as follows:

This is a simple login, and we’ll refine it later.

4. Perfect login function

Although the previous implementation of login, but only a simple login jump, in fact, can not distinguish the user’s login state, next we further improve the login function.

Start developing the back end first.

1. Back-end development

1.1. Interceptors

When the front and back ends are separated, the popular authentication scheme is JWT authentication. Different from traditional session authentication, JWT is a stateless authentication method, that is, the server does not save any authentication information. For the sake of space, we will not introduce JWT here, but simply check whether the token exists in the request header at the front end. Those interested in JWT certification can see the article SpringBoot Learning Notes (13: JWT).

  • createinterceptorPackage, new interceptor under the packageLoginInterceptor
/ * * *@Author: Three points of evil *@Date: 2021/1/18
 * @Description: User login interceptor **/

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

        // Get the token from the header
        String token = request.getHeader("token");
        // If the token is empty
        if (StringUtils.isBlank(token)) {
            setReturn(response,401."User not logged in, please log in first.");
            return false;
        }
        // In real use:
        // 1. Verify whether the token can decrypt user information to obtain visitors
        // 2. Whether the token has expired

        return true;
    }



    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}// Returns an error message in JSON format
    private static void setReturn(HttpServletResponse response, Integer code, String msg) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-Control-Allow-Credentials"."true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
        / / the utf-8 encoding
        httpResponse.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        Result result = new Result(code,msg,"");
        ObjectMapper objectMapper = newObjectMapper(); String json = objectMapper.writeValueAsString(result); httpResponse.getWriter().print(json); }}Copy the code
  • In order to return json results to the front end, there is also a utility class called NewutilUtil package to create a new utility classHttpContextUtil
/ * * *@Author: Three points of evil *@Date: 2021/1/18
 * @Description: HTTP context **/


public class HttpContextUtil {
    public static HttpServletRequest getHttpServletRequest(a) {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    public static String getDomain(a) {
        HttpServletRequest request = getHttpServletRequest();
        StringBuffer url = request.getRequestURL();
        return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
    }

    public static String getOrigin(a) {
        HttpServletRequest request = getHttpServletRequest();
        return request.getHeader("Origin"); }}Copy the code

1.2. Interceptor configuration

Once the interceptor is created, it needs to be configured.

/ * * *@Author: Three points of evil *@Date: 2021/1/18
 * @Description: Web configuration **/
@Configuration
public class DemoWebConfig implements WebMvcConfigurer {


    /** * Interceptor configuration **@param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Add interceptor
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/**")
                // Multiple paths can be added
                .excludePathPatterns("/api/login"); }}Copy the code

1.3. Cross-domain Configuration

In the background interface, there is a annotation @crossorigin, which is used for cross-domain configuration. It is not convenient to write the annotation for each interface. Here we create the cross-domain configuration class and add the unified cross-domain configuration:

/ * * *@AuthorThree points *@Date 2021/1/25
 * @DescriptionCross-domain configuration */
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter(a) {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // Allow source access. This allows access to all sources
        corsConfiguration.addAllowedOrigin("*");
        // Allow all headers
        corsConfiguration.addAllowedHeader("*");
        // Allow all methods
        corsConfiguration.addAllowedMethod("*");
        source.registerCorsConfiguration("/ * *", corsConfiguration);
        return newCorsFilter(source); }}Copy the code

1.3. Log in to service

The back end needs to generate a token to return to the front end, so change the login method in LoginServiceImpl.

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public Result login(LoginDTO loginDTO) {
        if (StringUtils.isEmpty(loginDTO.getLoginName())){
            return new Result(400."The account cannot be empty."."");
        }
        if (StringUtils.isEmpty(loginDTO.getPassword())){
            return new Result(400."Password cannot be empty."."");
        }
        // Query users by login name
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.eq("login_name", loginDTO.getLoginName());
        User uer=userMapper.selectOne(wrapper);
        // Compare passwords
        if(uer! =null&&uer.getPassword().equals(loginDTO.getPassword())){
            LoginVO loginVO=new LoginVO();
            loginVO.setId(uer.getId());
            // The token uses a uUID directly
            // In the case of JWT, a JWT token is generated, which contains user information
            loginVO.setToken(UUID.randomUUID().toString());
            loginVO.setUser(uer);
            return new Result(200."",loginVO);
        }
        return new Result(401."Login failed".""); }}Copy the code

VO encapsulates the returned data:

/ * * *@Author: Three points of evil *@Date: 2021/1/18
 * @DescriptionLog in to VO **/

public class LoginVO implements Serializable {
    private Integer id;
    private String token;
    private User user;
    // omit the getter and setter
}
Copy the code

Finally, test the login interface:

OK, no problem.

2. Front-end development

Previously we used a back-end interceptor, and now we’ll try to implement similar functionality with a front-end.

To realize the front-end login, it is necessary to judge the user login status in the front-end. We can set a status flag in the component’s data as we did before, but the login status should be treated as a global property, not written only to a component. So we need to introduce a new tool, Vuex, which is a state management solution developed specifically for Vue, where we can define variables and methods that need to be passed between components.

2.1 introduced Vuex

First install vuex on the terminal using the command NPM install vuex –save.

Create an index.js file in the SRC folder store and add vue and vuex to the file as follows:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
Copy the code

Next, set the required state variables and methods in index.js. To implement the login interceptor, we need a variable that records the token. In order to use user information globally, we also need a variable that records user information. Mutations also need to change the value of the variable. The complete code is as follows:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        token: sessionStorage.getItem("token"),
        user: JSON.parse(sessionStorage.getItem("user"))},mutations: {
        // set
        SET_TOKENN: (state, token) = > {
            state.token = token
            sessionStorage.setItem("token", token)
        },
        SET_USER: (state, user) = > {
            state.user = user
            sessionStorage.setItem("user".JSON.stringify(user))
        },
        REMOVE_INFO : (state) = > {
            state.token = ' '
            state.user = {}
            sessionStorage.setItem("token".' ')
            sessionStorage.setItem("user".JSON.stringify(' '}})),getters: {},actions: {},modules: {}})Copy the code

Here we also use sessionStorage, using sessionStorage, when closing the browser will be cleared, compared with localStorage, more conducive to ensure real-time.

2.2. Modify route configurations

In order to distinguish which routes need to be intercepted, we add metadata requireAuth to the routes to determine whether to intercept:

    {
      path: '/'.name: 'HelloWorld'.component: HelloWorld,
      meta: {
        requireAuth: true}},Copy the code

The complete SRC \router\index.js code is as follows:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
// Import the login page component
import Login from '@/views/login.vue'

Vue.use(Router)

export default new Router({
  routes: [{path: '/'.name: 'HelloWorld'.component: HelloWorld,
      meta: {
        requireAuth: true}},// Add a route to the login page
    {
      path:'/login'.name: 'Login'.component: Login
    }
  ]
})

Copy the code

2.3. Use hook functions to determine whether to intercept

Above we added requireAuth, which we’ll use next.

Hook functions and functions that will be called at some point. Here we use router.beforeeach (), which means called beforeEach route is accessed.

Open SRC \main.js and first add a reference to store

import store from './store'
Copy the code

And modify the contents of the vue object to make store globally usable:

new Vue({
  el: '#app',
  router,
  // Notice here
  store,
  components: { App },
  template: '<App/>'
})
Copy the code

We write beforeEach(). The logic is very simple: determine whether you need to log in, if so, determine whether there is a token in the store, if so, pass, otherwise jump to the login page.

// The hook function is called before accessing the route
router.beforeEach((to, from, next) = > {
  // Routes require authentication
  if (to.meta.requireAuth) {
    // Check whether there is a token in store
    if (store.state.token) {
      next()
    } else {
      next({
        path: 'login'.query: { redirect: to.fullPath }
      })
    }
  } else {
    next()
  }
}
)
Copy the code

The complete main.js code is as follows:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
/ / introduce ElementUI
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import store from './store'
var axios = require('axios')
// Register globally, then send data in other components via this.$axios
Vue.prototype.$axios = axios
/ / set the reverse proxy, the front-end request sent to the http://localhost:8888/api by default
axios.defaults.baseURL = 'http://localhost:8088/api'
Vue.config.productionTip = false

/* eslint-disable no-new */

Vue.use(ElementUI)

// The hook function is called before accessing the route
router.beforeEach((to, from, next) = > {
  // Routes require authentication
  if (to.meta.requireAuth) {
    // Check whether there is a token in store
    if (store.state.token) {
      next()
    } else {
      next({
        path: 'login'.query: { redirect: to.fullPath }
      })
    }
  } else {
    next()
  }
}
)


new Vue({
  el: '#app',
  router,
  // Notice here
  store,
  components: { App },
  template: '<App/>'
})

Copy the code

2.4 request encapsulation

The backend interceptor intercepts the request with a token in the header.

The answer is to wrap Axios.

Create the utils directory in the SRC directory and request.js file in the uitls directory.

First import axios and store:

import axios from 'axios'
import store from '@/store'
Copy the code

Next in the request interceptor, add a token to the request header:

// request Request interception
service.interceptors.request.use(
    config= > {

        if (store.state.token) {
            config.headers['token'] = window.sessionStorage.getItem("token")}return config
    },
    error= > {
        // do something with request error
        console.log(error) // for debug
        return Promise.reject(error)
    }
)
Copy the code

Complete the request. Js:

import axios from 'axios'
import store from '@/store'

//const baseURL="localhost:8088/api"

// Create an axios instance
const service = axios.create({
    baseURL: process.env.BASE_API, / / API base_url
})

// request Request interception
service.interceptors.request.use(
    config= > {

        if (store.getters.getToken) {
            config.headers['token'] = window.sessionStorage.getItem("token")}return config
    },
    error= > {
        // do something with request error
        console.log(error) // for debug
        return Promise.reject(error)
    }
)

//response Response interception
axios.interceptors.response.use(response= > {
    let res = response.data;
    console.log(res)

    if (res.code === 200) {
        return response
    } else {
        return Promise.reject(response.data.msg)
    }
},
    error= > {
        console.log(error)
        if (error.response.data) {
            error.message = error.response.data.msg
        }

        if (error.response.status === 401) {
            router.push("/login")}return Promise.reject(error)
    }
)


export default service


Copy the code

Note that baseUrl is used to create the axios instance, modify the configuration in config\dev.env.js:

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'.BASE_API: '"http://localhost:8088/api"',})Copy the code

With this encapsulation, we don’t have to manually cancel tokens on every request, or do some uniform exception handling, once and for all. And our API can be dynamically switched based on the env environment variable.

2.5. Encapsulate the API

Now that request.js is wrapped, it’s time to start using it.

We can add axios to main.js like the one above, so it can be called globally. But there are better ways to use it.

In general projects, Viess delegated the view of our various business modules. For these business modules, we created corresponding API to encapsulate the requests to the background, so that the relationship was still relatively clear even though there were many business modules.

Create API folder under SRC, and create user.js under API folder. In user.js we encapsulate the background request for login:

import request from '@/utils/request'

export function userLogin(data) {
    return request({
        url: '/login'.method: 'post',
        data
    })
}
Copy the code

Of course, it’s actually inappropriate to log in to Request.js because Request.js intercepts tokens, but the whole point of logging in is to get tokens — so 😅 makes do with it.

2.6, the login. Vue

In the previous login component, we just judged the status code returned from the back end and redirected it to the home page if it was 200. After the previous configuration, we need to modify the login logic to finally implement login interception.

The modified logic is as follows:

1. Click the login button to send data to the back end. 2. Obtain the path before login and jump to the home page. If the path does not exist, jump to the home page

The modified login() method is as follows:

    login() {
      var _this = this;
      userLogin({
        loginName: this.loginForm.loginName,
        password: this.loginForm.password,
      }).then((resp) = > {
        let code=resp.data.code;
        if(code===200) {let data=resp.data.data;
          let token=data.token;
          let user=data.user;
          / / store token
          _this.$store.commit('SET_TOKENN', token);
          // Store the user. A more elegant way is to obtain the token and user separately
          _this.$store.commit('SET_USER', user);
          console.log(_this.$store.state.token);
          var path = this.$route.query.redirect
          this.$router.replace({path: path === '/' || path === undefined ? '/' : path})
        }
      });
Copy the code

Complete the login. Vue:

<template>
  <body id="login-page">
    <el-form class="login-container" label-position="left" label-width="0px">
      <h3 class="login_title">System login</h3>
      <el-form-item>
        <el-input
          type="text"
          v-model="loginForm.loginName"
          auto-complete="off"
          placeholder="Account"
        ></el-input>
      </el-form-item>
      <el-form-item>
        <el-input
          type="password"
          v-model="loginForm.password"
          auto-complete="off"
          placeholder="Password"
        ></el-input>
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button
          type="primary"
          style="width: 100%; border: none"
          @click="login"
          >Login < / el - button ></el-form-item>
    </el-form>
  </body>
</template>

<script>
import { userLogin } from "@/api/user";
export default {
  name: "Login".data() {
    return {
      loginForm: {
        loginName: "".password: "",},responseResult: [],}; },methods: {
    login() {
      var _this = this;
      userLogin({
        loginName: this.loginForm.loginName,
        password: this.loginForm.password,
      }).then((resp) = > {
        let code=resp.data.code;
        if(code===200) {let data=resp.data.data;
          let token=data.token;
          let user=data.user;
          / / store token
          _this.$store.commit('SET_TOKENN', token);
          // Store the user. A more elegant way is to obtain the token and user separately
          _this.$store.commit('SET_USER', user);
          console.log(_this.$store.state.token);
          var path = this.$route.query.redirect
          this.$router.replace({path: path === '/' || path === undefined ? '/': path}) } }); ,}}};</script>

<style scoped>
#login-page {
  background: url(".. /assets/img/bg.jpg") no-repeat;
  background-position: center;
  height: 100%;
  width: 100%;
  background-size: cover;
  position: fixed;
}
body {
  margin: 0px;
}
.login-container {
  border-radius: 15px;
  background-clip: padding-box;
  margin: 90px auto;
  width: 350px;
  padding: 35px 35px 15px 35px;
  background: #fff;
  border: 1px solid #eaeaea;
  box-shadow: 0 0 25px #cac6c6;
}

.login_title {
  margin: 0px auto 40px auto;
  text-align: center;
  color: # 505458;
}
</style>

Copy the code

2.7, the HelloWorld. Vue

As you will recall, so far our/path points to the helloworld. vue component. To demonstrate global use of vuex state, we made a few changes and added a lifecycle hook function to retrieve the user name stored in the store:

  computed: {
    userName() {
      return this.$store.state.user.userName
    }
  }
Copy the code

Complete helloworld.vue:

<template>
  <div id="demo">
    {{userName}}
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Hello Vue! '}},computed: {
    userName() {
      return this.$store.state.user.userName
    }
  }
}
</script><! -- Add"scoped" attribute to limit CSS to this component only -->
<style scoped>
#demo{
  background-color: bisque;
  font-size: 20pt;
  color:darkcyan;
  margin-left: 30%;
  margin-right: 30%;
}
</style>

Copy the code

Let’s take a look at the overall effect:

If you access the home page, the login page is automatically displayed. After the login succeeds, the login status is recorded.

F12 Open Google Developer Tools:

  • Open theApplicationIn theSession StorageTo see our stored information

  • Open thevueDevelopment tools, inVuexYou can also see usstoreThe data in the

  • Log in again, open Network, you can find asynchronous request request header has been addedtoken

Again, this is a bit of a snip. It doesn’t make sense for a login to use a encapsulated common request method. After all, the login is for tokens, and Request.js blocks tokens. A good way to do this is to refer to vue-element-admin and write action in the store for login.

Because the article is too long, it is divided into two parts

The second half: juejin.cn/post/692204…