In order to let more students learn to the front and back end separation management system of the construction process, here I wrote a detailed development process of the document, using springSecurity + JWT + VUE stack combination, if it is helpful, don’t forget to click a like and pay attention to my public number ha!


The first public account: MarkerHub

Author: Lu Yiming

Project source: concern public number MarkerHub reply [234] to obtain

Video of the project: www.bilibili.com/video/BV1af…

Reprint please keep this statement, thank you!

1. Introduction

To build a project skeleton from scratch, it is best to choose appropriate and familiar technologies, which are easy to expand in the future and suitable for micro-service system. So generally Springboot as our framework foundation, which is inseparable.

Then the data layer, we commonly used is Mybatis, easy to use, convenient maintenance. But the single table operation is more difficult, especially when adding or reducing fields, more tedious, so here I recommend using Mybatis Plus (mp.baomidou.com/), to simplify the development of the life, only… CRUD operations to save a lot of time.

As a skeleton of a project, permissions should not be ignored. In the last project vueblog, we used shiro, but some students wanted to learn from SpringSecurity, so this time we used security as a framework for our permissions control and session control.

Considering that the project may need to deploy multiple units, some information to be shared is stored in the middleware. Redis is now the mainstream cache middleware, which is also suitable for our project.

However, due to the separation of the front and back ends, we use JWT as our user identity certificate, and we will disable session, so that we may not be able to use the way used in traditional projects, which needs to be noted.

Ok, let’s start scaffolding our project now!

Technology stack:

  • SpringBoot
  • mybatis plus
  • spring security
  • lombok
  • redis
  • hibernate validatior
  • jwt

2. Create the SpringBoot project and pay attention to the version

Here, we use IDEA to develop our project. The new step is relatively simple, so we will not take screenshots.

Development tools and environment:

  • idea
  • mysql
  • jdk 8
  • maven3.3.9

The new project structure is as follows, with SpringBoot version using the current latest version 2.4.0

Pom jar package import as follows:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> < version > 2.4.0 < / version > < relativePath / > < / parent > < groupId > com. Markerhub < / groupId > <artifactId>vueadmin-java</artifactId> <version>0.0.1 -snapshot </version> <name>vueadmin-java</name> <description> public id: MarkerHub</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>Copy the code
  • Devtools: hot loaded restart plug-in for the project
  • Lombok: A tool for simplifying code

3. Integrate Mybatis Plus and generate codes

Next, we will integrate Mybatis Plus, so that the project can complete the basic add, delete, change and check operation. The steps are simple: go to mp.baomidou.com/guide/

Step 1: Import the JAR package

Import the Jar package of Mybatis Plus into the POM. Since code generation is involved later, we also need to import the page template engine. In this case, we used Freemarker.

<! Mybatis plus https://baomidou.com/--> <dependency> <groupId>com.baomidou</groupId> < artifactId > mybatis - plus - the boot - starter < / artifactId > < version > 3.4.1 track < / version > < / dependency > <! <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> < version > 3.4.1 track < / version > < / dependency > < the dependency > < groupId > org. Freemarker < / groupId > <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>Copy the code

Step 2: Then write the configuration file

server: port: 8081 # DataSource Config spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/vueadmin? useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: admin mybatis-plus: mapper-locations: classpath*:/mapper/**Mapper.xmlCopy the code

In addition to configuring the database information above, also configure myabtis Plus mapper XML file scan path, this step should not be forgotten. And since the front segment defaults to port 8080, we set the back end to port 8081 to prevent port conflicts.

Step 3: Enable mapper interface scanning and add pagination and anti-full table update plug-ins

Create a new package: specify the package with the @mapperscan annotation where the interface to become the implementation class is located, and all the interfaces under the package will generate the corresponding implementation class when compiled.

  • com.markerhub.config.MybatisPlusConfig
@configuration @mapperscan ("com.markerhub.mapper") public class MybatisPlusConfig {/** * * MybatisConfiguration#useDeprecatedExecutor = false * to avoid caching problems (this property will be removed when the old plugin is removed) */ @bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); / / prevent full table update, and delete interceptor. AddInnerInterceptor (new BlockAttackInnerInterceptor ()); return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.setUseDeprecatedExecutor(false); }}Copy the code

Mybatis Plus adds 2 interceptors to Mybatis Plus

  • PaginationInnerInterceptor: the new page plug-in
  • BlockAttackInnerInterceptor: prevent full table update, and delete

Step 4: Create the database and tables

Because it is the permission module of the background management system, the tables we need to consider are mainly a few: user table, role table, menu permission table, and the associated user role intermediate table, menu role intermediate table. Five table, as for the field actually listen to casual, user tables in addition to the user name and password fields necessary inside, other all listen to the casual, role and the menu and then we can consider other systems, or in the process of project to do need it in add to also go, anyway, to regenerate the code is very simple thing, comprehensive consideration, The database name is vueadmin, and we build the following table sentences:

  • vueadmin.sql
/*
Navicat MySQL Data Transfer
Source Server         : localhost
Source Server Version : 50717
Source Host           : localhost:3306
Source Database       : vueadmin
Target Server Type    : MYSQL
Target Server Version : 50717
File Encoding         : 65001
Date: 2021-01-23 09:41:50
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
  `name` varchar(64) NOT NULL,
  `path` varchar(255) DEFAULT NULL COMMENT '菜单URL',
  `perms` varchar(255) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
  `component` varchar(255) DEFAULT NULL,
  `type` int(5) NOT NULL COMMENT '类型     0:目录   1:菜单   2:按钮',
  `icon` varchar(32) DEFAULT NULL COMMENT '菜单图标',
  `orderNum` int(11) DEFAULT NULL COMMENT '排序',
  `created` datetime NOT NULL,
  `updated` datetime DEFAULT NULL,
  `statu` int(5) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `code` varchar(64) NOT NULL,
  `remark` varchar(64) DEFAULT NULL COMMENT '备注',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  `statu` int(5) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`) USING BTREE,
  UNIQUE KEY `code` (`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NOT NULL,
  `menu_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `email` varchar(64) DEFAULT NULL,
  `city` varchar(64) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  `last_login` datetime DEFAULT NULL,
  `statu` int(5) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_USERNAME` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;
Copy the code

Step 5: Code generation

  1. Gets information about the tables and fields that correspond to the project database
  2. Create a new freemarker page template – sysuser.java.ftl – ${baseEntity}
  3. Provide the related dynamic data to be rendered – BaseEntity, table field, annotation, BaseEntity =SuperEntity
  4. Render using the Freemarker template engine! – SysUser.java
SELECT * FROM information_schema. TABLES WHERE TABLE_SCHEMA = (SELECT DATABASE()); SELECT * FROM information_schema. COLUMNS WHERE TABLE_SCHEMA = (SELECT DATABASE()) AND TABLE_NAME = "sys_user";Copy the code

With the database, mybatis Plus can be used now. The official provides us with a code generator. After I write my own parameters, I can directly generate entity, Service, Mapper and other interfaces and implementation classes according to the database table information. Because the code is rather long, I will not post it, to clarify the main points:

  • com.markerhub.CodeGenerator

In the above code generation process, I default that all entity classes inherit from BaseEntity and controllers inherit from BaseController, so it is best to write these two base classes before code generation:

  • com.markerhub.entity.BaseEntity
@Data
public class BaseEntity implements Serializable {
   @TableId(value = "id", type = IdType.AUTO)
   private Long id;
   private LocalDateTime created;
   private LocalDateTime updated;
   private Integer statu;
}
Copy the code
  • com.markerhub.controller.BaseController
public class BaseController {
   @Autowired
   HttpServletRequest req;
}
Copy the code

Then we run CodeGenerator’s main method separately, adjusting CodeGenerator’s database connection, account password, etc., and enter the table names separated by commas: Sys_menu, sys_ROLE, sys_ROLE_MENU, sys_USER, sys_user_ROLE The result is successful:

We then generated some code like this:

There are a few public fields created in the middle table of the associated user role and the middle table of the menu role, so we remove the BaseEntity inheritance from these two entities:

This is the last one:

@Data
public class SysRoleMenu {
   ...
}
Copy the code

Concise! Convenient! After the above steps, we have basically integrated myBatis Plus framework into the project, and also generated the basic code, saving a lot of effort. Then let’s do a simple test:

  • com.markerhub.controller.TestController
@RestController public class TestController { @Autowired SysUserService userService; @GetMapping("/test") public Object test() { return userService.list(); }}Copy the code

Sys_user then randomly adds a few pieces of data, resulting in the following:

Ok, what’s the problem? You don’t have to worry about how the password is generated, we’ll talk about that later, just fill it out now. By the way, a lot of people ask me why my browser’s JSON data looks so good. This is because I use JSONView:

4. Result encapsulation

Since the project is separated from the front and back ends, it is necessary to unify a result return encapsulation class, so that there is a unified standard when the front and back ends interact, and the data returned by the result is normal or meets an exception.

Here we use a Result class that encapsulates the Result returned by our asynchronous uniform. 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

Therefore, encapsulation can be obtained as follows:

  • com.markerhub.common.lang.Result
@Data public class Result implements Serializable { private int code; // If 200 is normal, non-200 is abnormal. private Object data; Public static Result succ(Object data) {return succ(200, "operation succeeded ", data); } public static Result succ(int code, String msg, Object data) { Result r = new Result(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; } public static Result fail(String msg) { return fail(400, msg, null); } public static Result fail(String msg, Object data) { return fail(400, msg, data); } public static Result fail(int code, String msg, Object data) { Result r = new Result(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; }}Copy the code

In addition, the code on the result encapsulation class can be used to check whether the data is normal or not. We can also check whether the access is abnormal through the HTTP status code. For example, 401 means five permission denied access, so pay attention to flexible use.

5. Global exception handling

Sometimes the inevitable server error, if the exception handling mechanism is not configured, the default will return tomcat or nginx 5XX page, for ordinary users, not very friendly, users do not understand what the situation. At this time we need to design a programmer to return a friendly simple format to the front end.

The treatment is as follows: ExceptionHandler(value = runtimeException.class) is used to specify each type of Exception to be caught. This Exception is handled globally. All of these exceptions, they run to this place.

Step 2. Define the global exception handling. @ControllerAdvice indicates the global controller exception handling, and @ExceptionHandler indicates the specific exception handling.

  • com.markerhub.common.exception.GlobalExceptionHandler
/ / @slf4j @restControllerAdvice public class GlobalExceptionHandler {@responseStatus (httpstatus.forbidden)  @ExceptionHandler(value = AccessDeniedException.class) public Result handler(AccessDeniedException e) { The info (" security permissions: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - {} ", um participant etMessage ()); Return result. fail(" permissions are insufficient "); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(value = MethodArgumentNotValidException.class) public Result Handler (MethodArgumentNotValidException e) {log. The info (" entity validation exception: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - {} ", um participant etMessage ()); BindingResult bindingResult = e.getBindingResult(); ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get(); return Result.fail(objectError.getDefaultMessage()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(value = IllegalArgumentException.class) public Result Handler (IllegalArgumentException e) {log. The error (" Assert exception: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - {} ", um participant etMessage ()); return Result.fail(e.getMessage()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(value = RuntimeException.class) public Result Handler (RuntimeException e) {log. The error (" a runtime exception: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - {} ", e); return Result.fail(e.getMessage()); }}Copy the code

Above we caught a few exceptions:

  • ShiroException: Exception thrown by Shiro, such as no permission, user login exception
  • IllegalArgumentException: Handles exceptions for Assert
  • MethodArgumentNotValidException: physical check exception handling
  • RuntimeException: Catches other exceptions

6. Integrate Spring Security

Many people do not understand Spring Security and think it is a more difficult framework than Shiro. Indeed, Security is more complex and more powerful. Let’s first look at the principle of Security….

Follow-up: Please go to the main page to see the next chapter

You can also follow the public account MarkerHub, reply 234 for more detailed development notes and source code, video, etc.