【40000 words】! The most suitable Springboot+Vue project for novices

More articles: https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg2NDY3NjY5NA==&action=getalbum&album_id=2053253027934863360#wechat_redirect

Hello, I’m Suoqi. This set of projects corresponds to the bilibili video. You can watch it in conjunction with the video. For some basic ones, you can just read the notes. This note has been made for a long time and has expanded a lot. For Xiaobai and partners who have accumulated experience we all help~

If it is useful, you can pay attention to it, like it and collect it~

Project Overview

1. Goal

By studying this project, you will have a deep understanding of the idea of ​​front-end and back-end separation , and have the ability to independently build front-end and back-end separation projects and the ability to expand functions

2. Development mode

3. Technology stack

front-end technology illustrate
Vue front-end framework
Vuex Global State Management Framework
ElementUI Front-end UI framework
Axios Front-end HTTP framework
view-element-admin project scaffolding
backend technology illustrate
SpringBoot Container + MVC framework
MyBatis ORM framework
MyBatis-plus MyBatis Enhancement Tool
Redis non-relational database

Redis is a non-relational database that handles caching, here is an impression

database

database xdb

1. User table

tips

In the first chapter, only the user table is needed to meet the development needs, but for the later cumbersome, come back to create the table, it is recommended to create it

 CREATE TABLE `x_user` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `username` varchar(50) NOT NULL,
   `password` varchar(100) DEFAULT NULL,
   `email` varchar(50) DEFAULT NULL,
   `phone` varchar(20) DEFAULT NULL,
   `status` int(1) DEFAULT NULL,
   `avatar` varchar(200) DEFAULT NULL,
    `deleted` INT(1) DEFAULT 0,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
 ​
 insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('1','admin','123456','[email protected]','18677778888','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
 insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('2','zhangsan','123456','[email protected]','13966667777','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
 insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('3','lisi','123456','[email protected]','13966667778','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
 insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('4','wangwu','123456','[email protected]','13966667772','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
 insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('5','zhaoer','123456','[email protected]','13966667776','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
 insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('6','songliu','123456','[email protected]','13966667771','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');

2. Role table

 CREATE TABLE `x_role` (
   `role_id` int(11) NOT NULL AUTO_INCREMENT,
   `role_name` varchar(50) DEFAULT NULL,
   `role_desc` varchar(100) DEFAULT NULL,
   PRIMARY KEY (`role_id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
 ​
 insert into `x_role` (`role_id`, `role_name`, `role_desc`) values('1','admin','super administrator');
 insert into `x_role` (`role_id`, `role_name`, `role_desc`) values('2','hr','HR');
 insert into `x_role` (`role_id`, `role_name`, `role_desc`) values('3','normal','normal employee');

3. Menu table

 CREATE TABLE `x_menu` (
   `menu_id` int(11) NOT NULL AUTO_INCREMENT,
   `component` varchar(100) DEFAULT NULL,
   `path` varchar(100) DEFAULT NULL,
   `redirect` varchar(100) DEFAULT NULL,
   `name` varchar(100) DEFAULT NULL,
   `title` varchar(100) DEFAULT NULL,
   `icon` varchar(100) DEFAULT NULL,
   `parent_id` int(11) DEFAULT NULL,
   `is_leaf` varchar(1) DEFAULT NULL,
   `hidden` tinyint(1) DEFAULT NULL,
   PRIMARY KEY (`menu_id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;
 ​
 insert into `x_menu`(`menu_id`,`component`,`path`,`redirect`,`name`,`title`,`icon`,`parent_id`,`is_leaf`,`hidden`) values ​​(1, 'Layout','/user','/user/list','userManage','user management','userManage',0,'N',0),(2,'user/user','list' ,NULL,'userList','userlist','userList',1,'Y',0),(3,'user/role','role',NULL,'roleList','role list',' role',1,'Y',0),(4,'user/permission','permission',NULL,'permissionList','permission list','permission',1,'Y',0);

4. User role mapping table

 CREATE TABLE `x_user_role` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `user_id` int(11) DEFAULT NULL,
   `role_id` int(11) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
 ​
 insert into `x_user_role` (`id`, `user_id`, `role_id`) values('1','1','1');

5. Character Menu Mapping Table

 CREATE TABLE `x_role_menu` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `role_id` int(11) DEFAULT NULL,
   `menu_id` int(11) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

Front-end Notes

1. node environment

Official Website: Node.js

Note that node can be slightly lower than this, but not higher

2. Download vue-admin-template

  • Downloads & Guides

 https://panjiachen.gitee.io/vue-element-admin-site/zh/guide/

3. Project initialization

  1. Unzip to a non-Chinese directory without spaces (to prevent exceptions)

  2. Tools such as vscode and idea open the project

I am using idea here

expand

Console input ctrl + c to terminate the service

You need to use node.js here

If you don't want to be troubled by configuration bugs, look here!

  • When installing, choose node.js version 16.12 or lower to prevent various errors in the later stage and troublesome configuration changes (scaffolding does not support new versions)

  • Create a new directory by yourself, don't confuse the nodejs files

The directory after installation is like this

  • Set Taobao mirror, speed up

Note: The npm install command must be executed in the same directory as the package.json file. This is because the npm install command will install the corresponding package according to the dependency information in the package.json file. If the package.json file is not in the current directory, npm will not be able to find it and perform the installation.

Set up mirroring for the first time for easy acceleration

 npm config set registry http://registry.npm.taobao.org/
 npm install
  1. run test

    Deploy to see if there is any problem

     npm run dev

    The login page is opened by default, and the main page is successfully entered after login

  2. configuration modification

Syntax check: lintOnSave

  • Indicates that the current development environment, ESLint will check the code when saving the file. If it is not enabled, it can be changed to false

Open browser by default: true

title: Change your favorite name~

mock: used to simulate backend data (can be deleted after the backend is established)

  • The following is the content configured in the video. Except for the name, everything else is consistent here, which is convenient and has the same effect as the video.

Click Navigator in the idea to find the corresponding file and change the name here

  • shortcut key ctrl+shift+n

  • "symbol" refers to an identifier in code, such as a class name, method name, variable name, etc. When you use the search function, you can choose to search for symbols to find codes associated with a particular identifier. We can choose symbols to locate title and other content

    shortcut key ctrl+shift+alt+n

After all changes, restart the service test

4. Modification of login page

After all changes, restart the service test

4. Modification of login page

  1. Chinese description

  2. background image

  3. Chinese description

  4. background image

  5. We can selectively modify all English words according to the English search

    Put the picture in the assets, and then modify the .login-container (the complete project has been packaged and put in,)

    background-image: url('../../assets/bg.jpeg');
    1

    Login Box Adjustment

  1. Login Username Cancel Restrictions

5. Modify the user drop-down menu in the upper right corner

  • src/layout/components/Navbar.vue

  • Suoqi feels that the drop-down menus that come with it are not bad, and there is no modification here

//Drop-down menu
<el-dropdown-item>

6. Home page breadcrumb navigation

7. Menu initialization

  1. Create a sys module directory and a test module directory in the src\views directory (for charging, and later can be used for permission assignment testing)

  2. Create two component files user.vue and role.vue under sys

    Create test1.vue, test2.vue, test3.vue under test

  3. Modify routing configuration

     {
         path: '/sys',
         component: Layout,
         redirect: '/sys/user',
         name: 'sys',
         meta: { title: 'System Management', icon: 'sys' },
         children: [
           {
             path: 'user',
             name: 'user',
             component: () => import('@/views/sys/user'),
             meta: { title: 'User Management', icon: 'userManage' }
           },
           {
             path: 'role',
             name: 'role',
             component: () => import('@/views/sys/role'),
             meta: { title: 'Role Management', icon: 'roleManage' }
           }
         ]
       },
     ​
       {
         path: '/test',
         component: Layout,
         redirect: '/test/test1',
         name: 'test',
         meta: { title: 'Functional Test', icon: 'form' },
         children: [
           {
             path: 'test1',
             name: 'test1',
             component: () => import('@/views/test/test1'),
             meta: { title: 'Test Point 1', icon: 'form' }
           },
           {
             path: 'test2',
             name: 'test2',
             component: () => import('@/views/test/test2'),
             meta: { title: 'Test point 2', icon: 'form' }
           },
           {
             path: 'test3',
             name: 'test3',
             component: () => import('@/views/test/test3'),
             meta: { title: 'Test point three', icon: 'form' }
           }
         ]
       }

    The icon svg file can be downloaded from iconfont-Alibaba Vector Icon Library

8. Tab Bar Navigation

  1. @/layout/components/AppMain.vue

    <keep-alive :include="cachedViews">
        <router-view :key="key" />
    </keep-alive>
    cachedViews() {
        return this.$store.state.tagsView.cachedViews
    }

  2. Copy the files in the vue-element-admin project to the corresponding directory

    • The idea can be directly copied and pasted (VsCode opens the folder and pastes)

    @/layout/components/TagsView @/store/modules/tagsView.js @/store/modules/permission.js

  3. Modify the file @store/getters.js

    visitedViews: state => state.tagsView.visitedViews,
    cachedViews: state => state.tagsView.cachedViews,   
    permission_routes: state => state.permission.routes

  4. Modify the file @store/index.js

     import View from 'view'
     import Vuex from 'vuex'
     import getters from './getters'
     import app from './modules/app'
     import settings from './modules/settings'
     import user from './modules/user'
     import tagsView from './modules/tagsView'
     ​
     Vue.use(Vuex)
     ​
     const store = new Vuex.Store({
       modules: {
         app,
         settings,
         user,
         tagsView
       },
       getters
     })
     ​
     export default store
     ​

  5. Modify the file @\layout\index.vue

    • Import, register

  6. Modify the file @layout\components\index.js

    Export the component under index.js, and other modules can use it by importing the TagsView component.

    export { default as TagsView } from './TagsView'
  7. Affix pin When the Affix attribute is added when declaring the route, the current tag will be fixed in the tags-view (cannot be deleted)

Error handling

  • Deep reported an error and changed it to:: v-deep

    ::v-deep {
        .el-scrollbar__bar {
          bottom: 0px;
        }
        .el-scrollbar__wrap {
          height: 49px;
        }
      }

9. Log in interface combing

  • Ready to connect to the backend

  • In the Network tab of the debug page, you can view all network requests sent and received by the browser. Each request is an entry

  • The following url can be changed (make sure the name is meaningful)

interface url method
Log in /user/login post
Get user information /user/info get
log out /user/logout post
  • code: HTTP status code, indicating the return code of the request. 20000 indicates success, other codes indicate different types of errors. (You can change the code corresponding to the success of the front end)

  • data: is the data returned by the request.

  • These data can be viewed in the preview

{
	"code": 20000,
	"data": {
		"token": "admin-token"
	}
}

{
	"code": 20000,
	"data": {
		"roles": ["admin"],
		"introduction": "I am a super administrator",
		"avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
		"name": "Super Admin"
	}
}

{
	"code": 20000,
	"data": "success"
}

expand

Convert json.cn to json format, easy to observe

10. Docking back-end interface

  1. Modify the base api in .env.development, and modify .env.production if you package and deploy

    VUE_APP_BASE_API = 'http://localhost:9999'
  2. Modify vue.config.js to block mock requests

  3. Modify src\api\user.js and remove /vue-admin-template in url

  4. Tested, cross-domain errors expected

  5. The cross-domain processing test on the backend should be successful, and the interface calls can be observed in the debug window

11. User Management

preview

  • user query

    1. define-userManager.js

    2. Page sequence number processing

       <template slot-scope="scope">
           {
            
            {(searchModel.pageNo-1) * searchModel.pageSize + scope.$index + 1}}
       </template>
       123
  • new user

    1. The data is still there after the window is closed

      Listen to close, clean up the form

    2. form data validation

      regular verification

      custom validation

    3. After the window is closed, the last verification result is still there

  • user modification

  • user delete

rear end

1. Project initialization

  1. Create a springboot project: 2.7.8

  2. pom dependencies

     <!-- web -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <!-- mysql -->
     <dependency>
         <groupId>com.mysql</groupId>
         <artifactId>mysql-connector-j</artifactId>
     </dependency>
     <!-- mybatis-plus -->
     <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.5.2</version>
     </dependency>
     <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-generator</artifactId>
         <version>3.5.2</version>
     </dependency>
     <!-- freemarker -->
     <dependency>
         <groupId>org.freemarker</groupId>
         <artifactId>freemarker</artifactId>
     </dependency>
     <!-- lombok -->
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>

    Expand dependency

    mybatis-plus-generator: This dependency is the code generator module of MyBatis-Plus. Using this module, you can automatically generate MyBatis-Plus entity classes, Mapper interfaces and XML mapping files based on database tables. This module can greatly simplify the work of developers and reduce the workload of manually writing repetitive code.

    freeMarker: FreeMarker is a powerful template engine that can help us combine data models and template files to generate various text outputs, such as HTML pages, emails, configuration files, and more. The following are the main uses of FreeMarker:

    • Web application view rendering: FreeMarker can be used as a template engine in a Web application to help us combine data models and template files to generate HTML pages.

    • Email template: FreeMarker can help us generate email content, such as email body, email header, etc.

    • Report generation: FreeMarker can help us generate various types of reports, such as PDF, Excel, Word and so on.

    • Code generation: FreeMarker can help us generate code files based on templates, such as Java classes, XML files, and so on.

    • Configuration file generation: FreeMarker can help us generate various types of configuration files, such as XML configuration files, property files, and so on.

  3. yml

    • Change the configuration file to ymlthe format and change the code using the following

    server:
      port: 9999
    
    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc:mysql:///xdb
      redis:
        port: 6379
        host: localhost
    
    logging:
      level:
    # If you want to output the debug log under that directory, you can configure which one. The groupid I set is suoqi, which is different from the video here. Don’t make mistakes~
        com.suoqi: debug
    

expand

jdbc:mysql:///databaseIndicates a database in the MySQL database connected to the local default port (3306) database. This method is equivalent to using the host name localhostor 127.0.0.1address to connect to the MySQL database.

If you want to connect to a MySQL database on another host, you can add the hostname and port number to the URL, for example:

jdbc:mysql://hostname:port/database

Among them, hostnameis the host name or IP address of the MySQL server to be connected, and portis the port number of the MySQL server, which is 3306 by default.

test

  • The connection pool can use the default one, no setting is required spring.datasource.type. If you are using an earlier version, you need to set it manually.

    The default HikariCP performs very well in terms of performance and stability, and is currently one of the fastest connection pools. However, it is not to say that HikariCP is the fastest in all cases, because the performance of the connection pool is also affected by many other factors, such as database type, database driver, JVM version, operating system, and so on.

spring:
  datasource:
    url: jdbc:mysql:///xdb
    username: root
    password: 123456
    type: com.zaxxer.hikari.HikariDataSource
#    hikari:
#      maximum-pool-size: 20
# connection-timeout: 5000 These are optional values, you can use the default if you don’t configure them. There are still many parameters that can be configured to let everyone know

expand

driver-class-nameis the full class name of the JDBC driver that tells the application which JDBC driver to use to establish a connection to the database. In Spring Boot's data source configuration, if you are using a database supported by Spring Boot by default, such as MySQL, PostgreSQL, etc., you don't need to configure it explicitly, because Spring Boot will automatically infer the class name of the driver based on the JDBC URL driver-class-name. For example, jdbc:mysql://localhost:3306/mydbSpring Boot automatically uses the MySQL driver if the JDBC URL is com.mysql.jdbc.Driver. However, if you are using another database, or if the database type information is not included in the JDBC URL, then manual configuration is required driver-class-name.

For configuration, the format is as follows, Oracle database, you can configure the data source like this:

spring:
  datasource:
    url: jdbc:oracle:thin:@localhost:1521:mydb
    username: myuser
    password: mypassword
    driver-class-name: oracle.jdbc.driver.OracleDriver

In this configuration, driver-class-namethe class name of the Oracle JDBC driver is explicitly specified.

2. Mybatis-plus code generation

official website

MyBatis-Plus

Generator code (the latest code on MybatisPlus official website does not match the project, if an error is reported, you can delete the new version of the code, the template is as follows)

public class CodeGenerator {
    public static void main(String[] args) {
        String url = "jdbc:mysql:///xdb";
        String username = "root";
        String password = "123456";
        String author = "suoqi";
        // defines the absolute path of the java directory under src
        String outPath = "F:\\projects\\java\\Springboot+Vue management system 01\\backend-admin-template-4.4.0\\src\\main\\java";
        String parentPackage = "com.suoqi";
        String moduleName = "sys";
// // Copy the absolute path of the classpath resources and add mapper+ module name after it

        String mapperLocation = "F:\\projects\\java\\Springboot+Vue管理系统01\\backend-admin-template-4.4.0\\src\\main\\resources\\mapper\\sys"; 
        FastAutoGenerator.create("url", "username", "password")
                .globalConfig(builder -> {
                    builder.author(author) // set the author
                            //.enableSwagger() // Turn on the swagger mode (we don't need to generate Swagger-related code here)
                            //.fileOverride() // Overwrite generated files
                            .outputDir(outPath); // specify the output directory
                })
                .packageConfig(builder -> {
                    builder.parent(parentPackage) // Set the parent package name
                            .moduleName(moduleName) // Set the parent package module name
                            .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // Set mapperXml generation path
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_simple") // Set the table name to be generated
                            .addTablePrefix("t_", "c_"); // Set filter table prefix
                })
                .templateEngine(new FreemarkerTemplateEngine()) // Use the Freemarker engine template, the default is the Velocity engine template
                .execute();
    }
}

Notice

mybatis-plus:
#  mapper-locations: classpath*:mapper/*.xml
#  global-config:
#    db-config:
#      id-type: auto
#      field-strategy: not_empty
#      table-prefix: mp_
#      logic-delete-value: 1
#      logic-not-delete-value: 0
#      logic-delete-field: is_deleted
  • The mapper directory needs to be consistent with the configuration to take effect

  • Use express code generator - create a class CodeGenerator below Test

  1.  package com.suoqi;
     ​
     import com.baomidou.mybatisplus.generator.FastAutoGenerator;
     import com.baomidou.mybatisplus.generator.config.OutputFile;
     import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
     import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
     ​
     import java.sql.Types;
     import java.util.Collections;
     ​
     /**
      * @author impromptu little Sochi
      * @version 1.0
      * @date 2023/6/25  1:05
      * @description
      */
     public class CodeGenerator {
         public static void main(String[] args) {
             String url = "jdbc:mysql:///xdb";
             String username = "root";
             String password = "123456";
             String author = "suoqi";
             // defines the absolute path of the java directory under src
             String outPath = "F:\\projects\\java\\Springboot+Vue management system 01\\backend-admin-template-4.4.0\\src\\main\\java";
             String parentPackage = "com.suoqi";
             String moduleName = "sys";
     // // Copy the absolute path of the classpath resources
             String mapperLocation = "F:\\projects\\java\\Springboot+Vue管理系统01\\backend-admin-template-4.4.0\\src\\main\\resources\\mapper\\sys";
             // Band, split represents multiple tables
             String tables = "x_user,x_role,x_menu,x_user_role,x_role_menu";
             FastAutoGenerator.create(url, username, password)
                     .globalConfig(builder -> {
                         builder.author(author) // set the author
                                 //.enableSwagger() // Turn on the swagger mode (we don't need to generate Swagger-related code here)
                                 //.fileOverride() // Overwrite generated files
                                 .outputDir(outPath); // specify the output directory
                     })
                     .packageConfig(builder -> {
                         builder.parent(parentPackage) // Set the parent package name
                                 .moduleName(moduleName) // Set the parent package module name
                                 .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // Set mapperXml generation path
                     })
                     .strategyConfig(builder -> {
                         builder.addInclude(tables) // Set the name of the table to be generated
                                 // Set the filter table prefix, such as setting filter x_, that is, x_user = user
                                 .addTablePrefix("x_"); // Set filter table prefix
                     })
                     .templateEngine(new FreemarkerTemplateEngine()) // Use the Freemarker engine template, the default is the Velocity engine template
                     .execute();
         }
     }
     ​

    The corresponding file can be generated by running

  2. Annotate the startup class

    //Indicates to scan all interfaces ending with Mapper under the com.suoqi package and its subpackages, and register them in MyBatis
    @MapperScan("com.suoqi.*.mapper")
  3. test

    startup class

    /**
     * RestController=Controller+ResponseBody
     * Here /user/all is customized
     */
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private IUserService userService;
        
        @GetMapping("/all")
        public List<User> getAllUser() {
            List<User> list = userService.list();
            return list;
        }
    }

expand

@Resourcesannotation

@Resource (based on the name of the class) annotation is similar to the @Autowired annotation and is also used for dependency injection. @Resource is an annotation provided at the Java level (does not depend on the Spring framework)

  • It has a name attribute. @Resource If the name attribute has a value, then Spring will directly go to the Spring container to find the Bean object according to the specified name value. If it is found, it will succeed, and if it is not found, it will report an error.

  • The search order is as follows:

    Lookup by name: If the attribute is specified name, lookup the bean by that name.

    Search by type: If no nameattribute is specified, the bean will be searched by attribute type. If multiple beans of the same type are found, an exception will be thrown.

@Autowired (based on type type) is an annotation provided by Spring, and the underlying implementation logic of their dependency injection is also different.

  • Search by type byType: If the type of the attribute has only one bean in the Spring container, the bean is automatically assembled.

  • Search by name: If the type of attribute has multiple beans in the Spring container, match the name of the bean according to the attribute name. If a bean with the same name is matched, the bean is automatically assembled; otherwise, an exception is thrown.

@ResourceAnnotations are those defined in the Java EE specification and do not depend on the Spring framework, so they can be used in any Java EE application. However, in a Spring application, it is recommended to use the @Autowiredor @Injectannotation for autowiring of beans.

3. Public response class

To ensure that the format returned by each interface is consistent with the front-end format, it is necessary to create a class with a unified format and three parameters:

  • code

  • message (description)

  • data (type undefined)

src/main/java/com/common/vo/Result.java

  • How to define success and failure

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static<T>  Result<T> success(){
        return new Result<>(20000,"success",null);
    }

    public static<T>  Result<T> success(T data){
        return new Result<>(20000,"success",data);
    }

    public static<T>  Result<T> success(T data, String message){
        return new Result<>(20000,message,data);
    }

    public static<T>  Result<T> success(String message){
        return new Result<>(20000,message,null);
    }

    public static<T>  Result<T> fail(){
        return new Result<>(20001,"fail",null);
    }

    public static<T>  Result<T> fail(Integer code){
        return new Result<>(code,"fail",null);
    }

    public static<T>  Result<T> fail(Integer code, String message){
        return new Result<>(code,message,null);
    }

    public static<T>  Result<T> fail( String message){
        return new Result<>(20001,message,null);
    }

}

Expand session, token, cookie

expand

What is a monolithic project?

  • A monolithic project means that the entire application is deployed in a single process, and all functions and modules are in the same code base.

What is Microservice Architecture?

  • The microservice architecture is a way to split a large application into multiple small services, each of which runs independently in its own process, and communicates between services through the network. Each service has its own codebase and database and can be deployed and scaled independently. The microservice architecture can improve the scalability and maintainability of the system, but it will also increase the complexity and operation and maintenance costs of the system.

What is front-end and back-end separation architecture?

  • Front-end separation is an architectural pattern that separates the development, deployment, and maintenance of the front-end and back-end of an application. In the front-end and back-end separation architecture, the front-end is mainly responsible for display and interaction logic, and the back-end is mainly responsible for data processing and business logic. The front-end and the back-end communicate through the API. The front-end calls the service provided by the back-end through the API to obtain data, and the back-end receives the request passed by the front-end through the API and returns the processing result.

Is it still useful to separate the session from the front end to the front end?

After the front-end and back-end are completely separated, because the front-end no longer accepts the rendered HTML pages from the back-end, but the back-end only provides a RESTful API interface, the front-end requests the back-end through AJAX

JSON data is rendered by itself, so there is no need to use session to save the state in the background, and the effect of using session in the front end is not good or even risky. Token (token) is used instead of session. The advantage of using tokens is that the front and back ends generate, verify, transfer, and store tokens according to the agreement, which realizes the separation of the front and back ends without state and session.

In Vue's front-end and back-end separation architecture, tokens are also used instead of sessions. In a Vue application, the token can be stored in Vuex or localstorage, and the corresponding token is carried every time the API is requested to obtain the response data.

The difference between Cookie, Token and Session

  • write an article here

hello, i'm suoqi~

Carefully wrote a vivid article on Cookie, Session and Token, and shared it with everyone

We can think of Cookie, Token, and Session as three good friends. They are all used to track the identity and status of users, but there are some differences and usage scenarios between them.

Cookie

  • Cookies: Cookies, Cookies; ... kind of people; (stored on the computer after browsing the web) cache files; <Scotland> light bread; pretty girls

    Ah, it’s not for you to translate~ It’s for you to introduce cookies in the computer~ (but I also learned a word)

A cookie is like your little secretary, its main function is to save user preferences and browsing history. For example, if you bought a piece of clothing online but haven't decided whether to buy it, you can put the piece of clothing into the shopping cart, and Cookie will help you remember what is in the shopping cart. When you come to this website next time, Cookie will help you display the items in the shopping cart so that you can continue shopping.

Cookie data is stored in the client's browser and will not occupy server resources

In the browser console, you can directly enter: document.Cookie to view cookies. A cookie is a string consisting of key-value pairs, for security reasons

If you can't get the httponly type, don't look for httponly for a long time and find that you can't find it

One more noun, let’s explore again?

What is httponly?

HttpOnly is a flag set in the HTTP response header that prevents certain types of client-side scripts (such as JavaScript) from accessing cookies. When the server sends a cookie with the HttpOnly flag to the client, the client's JavaScript code will not be able to access the cookie through document.cookie, which can effectively improve the security of the web application.

If the httponly attribute is set for a cookie, it cannot pass JS script

  • Read the cookie information, but you can still manually modify the cookie through the Application, so it can only prevent XSS attacks to a certain extent, not absolutely safe

  • Cookies are mainly used to track user preferences and behavior in order to provide a personalized experience. For example, save the user's login status, shopping cart information, etc. on the website.

    Ah, information such as browsing videos, browsing tb, personalized advertisements, etc. is actually recorded and pushed by the page in this way

Another topic that everyone is discussing (with different opinions) is-will our usual browsing records and other information be recorded?

  • The answer is uncertain (it is not guaranteed that it will not be recorded, and it is not guaranteed that it will be recorded)

    The cookie itself is stored on the client side, not the server side, so the server does not need to

    Cookie records are saved to the database

    But as to whether and how information such as recording personal hobbies and browsing records is recorded in the database, it depends on the specific software, website, privacy policy and data collection method..

Session

Session is like your personal file, its main function is to save the user's status and permissions. For example, after you log in on the website, the server will create a Session for you, which stores your login status and shopping cart information, etc. In this way, when you are browsing the website, the server will provide a personalized experience based on the Session, such as displaying what is in your shopping cart, or displaying the products you have recently viewed.

It can also be understood as a special map. In addition to accessing data like other maps, it also has an expiration time and a unique id to distinguish different sessions.

When the session is created, a cookie will be created at the same time key, JSESSIONIDand the cookie valueis the id of the session.

Have you encountered something you don’t understand again? What is the key of the cookie?

JSESSIONIDIt is a cookie name used to transfer session information between the client and server. When a user visits a website that requires login in a browser, the server will

Create a session in the background, generate a unique Session ID, and store it in the session on the server side. At the same time, the server will send the Session ID to the client in the form of a cookie. The commonly used cookie name is OBSESSION

  • The data information of the session is stored on the server, and the data of the session can only be accessed by the server, so it is relatively safe, but it needs to occupy the resources of the server.

  • Session is mainly used to track user status and permissions in order to provide a personalized experience. For example, what you search for, save the user's login status on the website, shopping cart information, etc.

  • There is no upper limit for Session, but for server-side performance considerations, do not store too many things in Session

Token

Token is like your ID card, its main function is for authentication and authorization. For example, when you use an APP, you need to log in to use some functions. At this time, the APP will issue you a Token (token). You need to carry this Token in each request, and the server will verify the Token. to determine your identity and permissions to ensure that you can only access content that you are authorized to access.

For example, if the user has logged in to the system, I send him a token, which contains the user id of the user. Next time the user requests to access me through Http again, just bring this token through the Http header.

But at this time, it feels no different from a session. What if someone fakes a fake attack? So the algorithm is used to sign the data, using signature + data = token, the signature is unknown, and the token cannot be forged

This token is not saved. When the user sends me this token, I will use the same algorithm and the same key to calculate the signature on the data again, and compare it with the signature in the token. If they are the same, I will Knowing that the user has already logged in, and can directly get the user id of the user, if it is not the same, the data must have been tampered with, so you know that this person is a counterfeit, and return the information without authentication

Token is a stateless authentication mechanism, which means that the server does not need to save the state of Token (this does not greatly reduce the pressure on the server~), and the front-end cannot directly access the back-end Session in the front-end and back-end separation architecture. However, in the front-end and back-end separation architecture, Session can still be used to store other state information of the application, such as shopping cart data, but it cannot be used to save the user's login state.

  • Can be saved both on the server and on the client

  • Token is a stateless authentication mechanism that can be shared among multiple servers, while Session needs to be saved on each server. Using Token can avoid problems such as session sharing and session expiration, and can also reduce the burden on the server.

  • The data in Token is stored in clear text, and can still be seen by others, so I cannot store sensitive information like passwords in it

  • Token-based authentication is stateless, and we do not store user information in the server or session.

  • In most Internet companies using Web API, it is the best way to handle authentication under Tokens multi-user

  • Isn't it annoying to be attacked! Token is usually used in scenarios such as API authentication, which can effectively avoid attacks such as cross-site request forgery (CSRF)~

Expand the Token authentication process

  • The user performs a login operation on the client side, and sends the user name and password to the server side.

  • The server generates a Token by verifying the correctness of the user name and password, and returns the Token to the client.

  • The client saves the Token locally, such as in the browser's Cookie or localStorage.

  • In subsequent requests, the client sends the Token to the server for authentication.

  • After receiving the request, the server obtains the Token from the request, and decrypts and verifies the Token.

  • If the Token verification is passed, the server will respond to the request and return the required data, otherwise it will return an error message that the authentication failed.

During the identity verification process, the server usually decrypts the Token, verifies the signature, checks whether the Token is expired, etc., to ensure the validity and security of the Token

Lifelike, easy to understand~ The key points are over!

Simply memorize some knowledge

Have you read it, and don’t understand anything? Well, helpless, just remember the difference briefly, you can’t be speechless during the interview

  • Session and Token are mechanisms for saving data on the server side, while cookies are mechanisms for saving data on the client side

    Usually, the data saved by a single cookie is within 4KB (Interviewer: I know this, I will give you an offer! Ecstatic self: Great!)

  • Session and Token are usually used for authentication and state management, while cookies are usually used to track user preferences and behaviors

  • Session and Token are usually used for storage and transmission of sensitive data, while cookies are usually used for storage and transmission of non-sensitive data.

  • Session and Token need to be managed and maintained by the server, while cookies can be managed and maintained by the client.

  • Token can be used across domains, while Session can usually only be used under the same domain name; Token can be used in a distributed system, while Session can usually only be used on a single server.

(It can be ignored) I want to expand after writing, hahaha, partners who want to explore, must want to know the number of cookies that a single site can store,

Doubts here?

The international Internet standard is that the number of cookies that each website can store shall not exceed 300, which depends on different browsers.

I found that some bloggers said that a single site can save up to 20 cookies, which is unreasonable, and there are nearly 100 likes

A series of information on the Internet is duplicated, sometimes we can't believe it easily, we have to learn to explore and verify by ourselves! Otherwise it will be misleading

This is just to explain the number of cookies, to help more partners learn to explore knowledge, and there is no malice towards the original blogger.

4. Log in to relevant interfaces

4.1 Login

  • The login information is placed in Redis

interface properties value
url /user/login
method post
request parameters username password
return parameter

controller

 /**
      *
      * @param user
      * By default in SpringMVC, the data in the request body is transmitted in JSON format. If you don't use @RequestBody annotation,
      * SpringMVC will not parse the data in the request body into a JSON object when processing the request, and it will not be able to convert the data in the request body into a User object.
      * @return
      */
     @PostMapping("/login")
     public Result<Map<String,Object>> login(@RequestBody User user){
         // Traversing through the database according to the username and password, if it exists, the information is correct, and the specific login logic is implemented in the UserServiceImpl business layer
         Map<String,Object> data = userService.login(user);
         if (data!= null){
             return Result.success(data);
         }
         // can be extended to an enumeration class by itself
         return Result.fail(20002,"The username and password are wrong");
     }

service

  @Autowired
 private RedisTemplate redisTemplate;
     /**
      * @param user
      * @return
      * @description Query based on username and password
      */
     @Override
     public Map<String, Object> login(User user) {
         // The result is not empty, generate a token, if it is empty, write the information to Redis
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(User::getUsername, user.getUsername());
         wrapper.eq(User::getPassword, user.getPassword());
         User loginUser = this.baseMapper.selectOne(wrapper);
         if (loginUser != null) {
             // Simple projects use UUID, which can be changed to a better jwt solution
             String key = "user:" + UUID.randomUUID();
             // store in redis
             // Prevent passwords from being stored in redis
             loginUser.setPassword(null);
             /*
             redisTemplate.opsForValue() is a method provided by RedisTemplate to operate string type data
             It returns a ValueOperations object,
             It can be used to operate string type data in Redis. You can use set, get, delete and other commands in Redis to operate string type data.
              */
             redisTemplate.opsForValue().set(key, loginUser, 30, TimeUnit.MINUTES);
             // return data
             Map<String, Object> data = new HashMap<>();
             data.put("token", key);
             return data;
         }
 ​
         return null;
     }
 }
  • When testing, use the post method. Since the browser sends a get request, an error will be reported.

 Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]
  • We need to use the postman tool to make post requests

Integrate redis

  • To integrate redis, you need to start the redis service

  • Here is just a simple redis function, depending on the implementation of other projects, you can also learn redis systematically

  • pom

     <!-- redis -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
  • yml

     spring:
       redis:
         host: localhost
         port: 6379

configuration class

  • In the code StringRedisSerializeris the default key and value serializer of RedisTemplate. When StringRedisSerializerserializing with , it converts the string object to a byte array and stores it in Redis. When reading data, it deserializes the byte array into a string object.

  • Jackson2JsonRedisSerializerJava objects can be serialized into JSON-formatted strings and stored in Redis. When reading data, it can deserialize JSON-formatted strings into Java objects;

 @Configuration
 public class MyRedisConfig {
     //The interface used to create a Redis connection, you don't need to care about the details of the underlying Redis connection implementation, you can directly use Redis for data storage and caching.
     @Resource
     private RedisConnectionFactory factory;
 ​
     @Bean
     public RedisTemplate redisTemplate(){
         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
          // change the serializer
         redisTemplate.setKeySerializer(new StringRedisSerializer());
         //The connection factory set to RedisTemplate allows this object to interact with the Redis server
         redisTemplate.setConnectionFactory(connectionFactory);
         // The type is uncertain so use Object
         Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
         redisTemplate.setValueSerializer(serializer);
 ​
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
         om.setTimeZone(TimeZone.getDefault());
         om.configure(MapperFeature.USE_ANNOTATIONS, false);
         om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
         om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
         om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
         serializer.setObjectMapper(om);
 ​
         return redisTemplate;
     }
 }

4.2 Get user information

interface properties value
url /user/info?token=xxx
method get
request parameters token
return parameter insert image description here
  • The roles role table we will talk about in the next section

  • avator (avatar): the address of the avatar

  • name: login username

controller

  • Return value Result<Map<String, Object>> write specific or? can be written here

 @GetMapping("/info")
     public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){
         // Obtain user information according to token
         Map<String,Object> data = userService.getUserInfo(token);
         if (data!= null){
             return Result.success(data);
         }
         // can be extended to an enumeration class by itself
         return Result.fail(20003,"The user information is invalid, please log in again");
     }

service

 @Override
     public Map<String, Object> getUserInfo(String token) {
         // Obtain user information according to token, redis
         Object obj = redisTemplate.opsForValue().get(token);
         // Serialization has been done in redisConfig, so it needs to be deserialized with the abstract class JSON to take it out and convert it into a User object (other implementations can also be used)
         if(obj!=null){
             User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class);
             Map<String,Object> data = new HashMap<>();
             data.put("name",loginUser.getUsername());
             data.put("avatar",loginUser.getAvatar());
             List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());
             //Role, a person may have multiple roles
             data.put("roles", roleList);
             return data;
 ​
         }
         return null;
     }
  • In the video project, SQL is used for multi-table joint query

UserMapper

 public interface UserMapper extends BaseMapper<User> {
     public List<String> getRoleNameByUserId(Integer userId);
 ​
 }

UserMapper.xml

 <select id="getRoleNamesByUserId" parameterType="Integer" resultType="String">
     SELECT
     b.role_name
     FROM x_user_role a,x_role b
     WHERE a.`user_id` = #{userId}
     AND a.`role_id` = b.`role_id`
 </select>

4.3 Logout

interface properties value
url /user/logout
method post
request parameters
return parameter [External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-DwV5GD4c-1675952251553) (C:\Users\dacai\AppData\Roaming\Typora\typora-user-images\ image-20230203171855151.png)]

controller

  • The previous is to save the token in Redis, just clear it

  • The token set on the front end is called x-token

 @PostMapping("/logout")
 public Result<?> logout(@RequestHeader("X-Token") String token){
     userService.logout(token);
     return Result.success("logout successful");
 }

service

 public void logout(String token) {
     redisTemplate.delete(token);
 }

There is this code in the frontend

 before: require('./mock/mock-server.js') 
  • It should be removed or commented out when the front and back ends are docked

This code is a way to use mock data in Vue projects. Mock data refers to the data returned by simulating the back-end interface during the front-end development process for front-end development and debugging. In this code, require('./mock/mock-server.js') means importing the mock-server.js file, which defines the mock data generation rules and interface interception rules. In the development environment, mock data can be used to replace the back-end interface in this way, which is convenient for front-end development and testing. In a production environment, this code should be removed to avoid unnecessary performance loss.

6. Cross-domain processing

Cross-domain means that in the browser, the domain name of the current web page is different from the domain name accessed by the current request, that is, a cross-domain request.

Take a chestnut: If the URL of the current webpage is https://www.example.com, then the same-origin policy requires that the URL that sends the request must also be https://www.example.com, otherwise it will be blocked.

  • Access-Control-Allow-Origin

The global cross-domain processing set here is not recommended to use the annotation local method

 @Configuration
 public class MyCorsConfig {
 // The maximum effective duration of the current cross-domain request, here the default is 1 day
 //    private static final long MAX_AGE = 24 * 60 * 60;
     @Bean
     public CorsFilter corsFilter() {
         //1. Add CORS configuration information
         CorsConfiguration config = new CorsConfiguration();
         //1) Allowed domains, do not write *, otherwise cookies cannot be used
         //Fill in the requested front-end server here
         config.addAllowedOrigin("http://localhost:8888");
         //2) Whether to send Cookie information
         config.setAllowCredentials(true);
         //3) Allowed request methods
         config.addAllowedMethod("*");
 ​
 //        config.setMaxAge(MAX_AGE);
         // 4) All request headers allowed
         config.addAllowedHeader("*");
         //2. Add mapping path, we intercept all requests
         UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
 // What is received is to receive parameters of the CorsConfiguration type,
         urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", config);
 ​
         //3. Return the new CorsFilter.
         return new CorsFilter(urlBasedCorsConfigurationSource);
     }
 }
 ​

CorsFilter source code is roughly as follows

 public CorsFilter(CorsConfigurationSource configSource, CorsProcessor processor) {
     Assert.notNull(configSource, "CorsConfigurationSource must not be null");
     Assert.notNull(processor, "CorsProcessor must not be null");
     this.configSource = configSource;
     this.processor = processor;
 }

Notice:

If you are using Spring Boot 2.4 and above, you also need to add the following configuration items in the application.properties or application.yml file to allow cross-domain requests to carry cookies:

 spring:
   mvc:
     cors:
       allow-credentials: true

This configuration item tells Spring Boot to allow cookies in cross-domain requests.

Tip: The scaffolding version is: 2.13.2, so it is recommended to change the element to version 2.13.2 to avoid bugs

If the sub-page is in English, it can main.jsbe changed to Chinese belowzh-CN

7. User management interface

interface illustrate
Query user list Paging query
New users
根据id查询用户
修改用户
删除用户 逻辑删除

7.1 查询用户列表

  1. controller

     @GetMapping("/list")
     public Result<?> getUserListPage(@RequestParam(value = "username", required = false) String username,
                                      @RequestParam(value = "phone", required = false) String phone,
                                      @RequestParam("pageNo") Long pageNo,
                                      @RequestParam("pageSize") Long pageSize) {
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
         wrapper.eq(username != null, User::getUsername, username);
         wrapper.eq(phone != null, User::getPhone, phone);
         Page<User> page = new Page<>(pageNo, pageSize);
         userService.page(page, wrapper);
         Map<String, Object> data = new HashMap<>();
         data.put("total", page.getTotal());
         data.put("rows", page.getRecords());
     ​
         return Result.success(data);
     }

    IService源码

      default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
             return this.getBaseMapper().selectPage(page, queryWrapper);
         }

  2. 分页拦截器配置

    复制过来的别忘了把new PaginationInnerInterceptor(DbType.MYSQL) 这里改为我们的MYSQL数据库

     @Configuration
     public class MpConfig {
         @Bean
         public MybatisPlusInterceptor mybatisPlusInterceptor() {
             MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
             interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
             return interceptor;
         }
     }

测试

对接前后端

更改api文件中进行对接后端

7.2 新增用户

密码不能是明文,需要加密处理,用BCryptPasswordEncoder,涉及登录逻辑改动

    @PostMapping
     public Result<?> addUser(@RequestBody User user){
         user.setPassword(passwordEncoder.encode(user.getPassword()));
         userService.save(user);
         return Result.success("新增用户成功");
     }

7.3 修改用户

此处不提供密码更新,大家自行扩展,可以去实现前端右上角菜单的个人信息功能

修改展示

注意这里response.data不加括号,它不是方法!这点容易忽略

 saveUser() {
       // 触发表单验证
       this.$refs.userFormRef.validate((valid) => {
         if (valid) {
           // 提交给后台
           userApi.saveUser(this.userForm).then(response => {
           // 成功提示
             this.$message({
               message: response.message,
               type: 'success'
             })
             // 关闭对话框
             this.dialogFormVisible = false
             // 刷新表格
             this.getUserList()
           })
         } else {
           console.log('error submit!!')
           return false
         }
       })
     },

   saveUser(user) {
     if (user.id == null || user.id === undefined) {
       return this.addUser(user)
     }
     return this.updateUser(user)
   }

   getUserById(id) {
     return request({
       url: `/user/'+${id}`,
       method: 'get',
       data: user
     })
   }

7.4 删除用户

利用MyBatisPlus做逻辑删除处理(MybatisPlus官网上有配置也可以复制哈)

yml(别忘记重启项目)

 mybatis-plus:
   global-config:
     db-config:
       logic-delete-field: delted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
       logic-delete-value: 1 # 逻辑已删除值(默认为 1)
       logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

Controller

   @DeleteMapping("/{id}")
     public Result<User> deleteUserById(@PathVariable("id") Integer id) {
         userService.removeById(id);
         return Result.success("删除成功");
     }

我们要学会善于查阅文档,不能局限于笔记、视频中的说明,也要结合别人的理解,善于查阅官方文档,这样才能够进一步的打通自己的任通二脉,找到自己的路~ 加油,未来可期

补充

便于大家快速查阅,这里留存一些整个类的文档

Entity

User
 @TableName("x_user")
 public class User implements Serializable {
 ​
     private static final long serialVersionUID = 1L;
 ​
     //主键字段名为id,主键生成策略为自增长。
     @TableId(value = "id", type = IdType.AUTO)
     private Integer id;
 ​
     private String username;
 ​
     private String password;
 ​
     private String email;
 ​
     private String phone;
 ​
     private Integer status;
 ​
     private String avatar;
 ​
     private Integer deleted;
 ​
     public Integer getId() {
         return id;
     }
 ​
     public void setId(Integer id) {
         this.id = id;
     }
     public String getUsername() {
         return username;
     }
 ​
     public void setUsername(String username) {
         this.username = username;
     }
     public String getPassword() {
         return password;
     }
 ​
     public void setPassword(String password) {
         this.password = password;
     }
     public String getEmail() {
         return email;
     }
 ​
     public void setEmail(String email) {
         this.email = email;
     }
     public String getPhone() {
         return phone;
     }
 ​
     public void setPhone(String phone) {
         this.phone = phone;
     }
     public Integer getStatus() {
         return status;
     }
 ​
     public void setStatus(Integer status) {
         this.status = status;
     }
     public String getAvatar() {
         return avatar;
     }
 ​
     public void setAvatar(String avatar) {
         this.avatar = avatar;
     }
     public Integer getDeleted() {
         return deleted;
     }
 ​
     public void setDeleted(Integer deleted) {
         this.deleted = deleted;
     }
 ​
     @Override
     public String toString() {
         return "User{" +
             "id=" + id +
             ", username=" + username +
             ", password=" + password +
             ", email=" + email +
             ", phone=" + phone +
             ", status=" + status +
             ", avatar=" + avatar +
             ", deleted=" + deleted +
         "}";
     }
 }
 ​
UserRole
 @TableName("x_user_role")
 public class UserRole implements Serializable {
 ​
     private static final long serialVersionUID = 1L;
 ​
     @TableId(value = "id", type = IdType.AUTO)
     private Integer id;
 ​
     private Integer userId;
 ​
     private Integer roleId;
 ​
     public Integer getId() {
         return id;
     }
 ​
     public void setId(Integer id) {
         this.id = id;
     }
     public Integer getUserId() {
         return userId;
     }
 ​
     public void setUserId(Integer userId) {
         this.userId = userId;
     }
     public Integer getRoleId() {
         return roleId;
     }
 ​
     public void setRoleId(Integer roleId) {
         this.roleId = roleId;
     }
 ​
     @Override
     public String toString() {
         return "UserRole{" +
             "id=" + id +
             ", userId=" + userId +
             ", roleId=" + roleId +
         "}";
     }
 }
 ​

Controller

UserController
 @RestController
 @RequestMapping("/user")
 public class UserController {
     @Autowired
     private IUserService userService;
     @Autowired
     private PasswordEncoder passwordEncoder;
     @GetMapping("/all")
     public Result<List<User>> getAllUser() {
         List<User> list = userService.list();
         return Result.success(list, "查询成功");
     }
 ​
     /**
      * @param user SpringMVC默认情况下,请求体中的数据是以 JSON 格式传输的。如果你不使用 @RequestBody 注解,
      *             SpringMVC 在处理请求时就不会将请求体中的数据解析成 JSON 对象,也就无法将请求体中的数据转化为 User 对象。
      * @return
      */
     @PostMapping("/login")
     public Result<Map<String, Object>> login(@RequestBody User user) {
         // 根据用户名和密码在数据库中遍历,如果存在表示信息正确,具体的登录逻辑在UserServiceImpl业务层中实现
         Map<String, Object> data = userService.login(user);
         if (data != null) {
             return Result.success(data);
         }
         // 可以自行拓展为枚举类
         return Result.fail(20002, "用户名和密码错误");
     }
 ​


     /**
      * @param token
      * @return
      * @description 将名为 "token" 的 HTTP 请求参数绑定到方法参数 token 上。
      */
     @GetMapping("/info")
     public Result<Map<String, Object>> getUserInfo(@RequestParam("token") String token) {
         // 根据token获取用户信息
         Map<String, Object> data = userService.getUserInfo(token);
         if (data != null) {
             return Result.success(data);
         }
         // 可以自行拓展为枚举类
         return Result.fail(20003, "用户信息无效,请重新登陆");
     }
 ​
     @PostMapping("logout")
     public Result<?> logout(@RequestHeader("X-Token") String token) {
         userService.logout(token);
         return Result.success();
 ​
     }
 ​
     @GetMapping("/list")
     public Result<Map<String, Object>> getUserList (@RequestParam(value = "username", required = false) String username,
                                                @RequestParam(value = "phone", required = false) String phone,
                                                @RequestParam("pageNo") Long pageNo,
                                                @RequestParam("pageSize") Long pageSize) {
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
         wrapper.eq(StringUtils.hasLength(username),User::getUsername, username);
         wrapper.eq(StringUtils.hasLength(phone),User::getPhone, phone);
         wrapper.orderByDesc(User::getId);
         Page<User> page = new Page<>(pageNo,pageSize);
         userService.page(page,wrapper);
         Map<String,Object> data = new HashMap<>();
         data.put("total",page.getTotal());
         data.put("rows",page.getRecords());
         return Result.success(data);
     }
 ​
     /**
      * 新增用户
      * @return
      */
     @PostMapping
     public Result<?> addUser(@RequestBody User user){
         user.setPassword(passwordEncoder.encode(user.getPassword()));
         userService.save(user);
         return Result.success("新增用户成功");
     }
     @PutMapping
     public Result<?> updateUser(@RequestBody User user){
         user.setPassword(null);
         userService.updateById(user);
         return Result.success("修改用户成功");
     }
     @GetMapping("/{id}")
     public Result<User> getUserById(@PathVariable("id") Integer id) {
         User user = userService.getById(id);
         return Result.success(user);
     }
     @DeleteMapping("/{id}")
     public Result<User> deleteUserById(@PathVariable("id") Integer id) {
         userService.removeById(id);
         return Result.success("删除成功");
     }
 }

Service

IUserService

IUserService

 public interface IUserService extends IService<User> {
 ​
     Map<String, Object> login(User user);
 ​
     Map<String, Object> getUserInfo(String token);
 ​
     void logout(String token);
 }
UserServiceImpl

UserServiceImpl

 @Service
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
     @Autowired
     private RedisTemplate redisTemplate;
     @Autowired
     private PasswordEncoder passwordEncoder;
     /**
      * @param user
      * @return
      * @description 根据用户名查询,加密后处理
      */
     @Override
     public Map<String, Object> login(User user) {
         // 结果不为空并且匹配传入的密码,生成token,为空,则将信息写入Redis
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(User::getUsername, user.getUsername());
         User loginUser = this.baseMapper.selectOne(wrapper);
         if (loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())) {
             // 简单项目用UUID,可以改为更好的jwt方案
             String key = "user:" + UUID.randomUUID();
             // 存入redis2
             // 防止密码存入redis中
             loginUser.setPassword(null);
 ​
 //            redisTemplate.opsForValue()是RedisTemplate提供的一个操作字符串类型数据的方法
 //            它返回一个ValueOperations对象,
 //            可以用来对Redis中的字符串类型数据进行操作可以使用Redis中的set、get、delete等操作字符串类型数据的命令。
 ​
             redisTemplate.opsForValue().set(key, loginUser, 30, TimeUnit.MINUTES);
             //返回数据
             Map<String, Object> data = new HashMap<>();
             data.put("token", key);
             return data;
         }
 ​
         return null;
     }
 //    /**
 //     * @param user
 //     * @return
 //     * @description 根据用户名和密码查询
 //     */
 //    @Override
 //    public Map<String, Object> login(User user) {
 //        // 结果不为空,生成token,为空,则将信息写入Redis
 //        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
 //        wrapper.eq(User::getUsername, user.getUsername());
 //        wrapper.eq(User::getPassword, user.getPassword());
 //        User loginUser = this.baseMapper.selectOne(wrapper);
 //        if (loginUser != null) {
 //            // 简单项目用UUID,可以改为更好的jwt方案
 //            String key = "user:" + UUID.randomUUID();
 //            // 存入redis2
 //            // 防止密码存入redis中
 //            loginUser.setPassword(null);
 //
 //            /*redisTemplate.opsForValue()是RedisTemplate提供的一个操作字符串类型数据的方法
 //            它返回一个ValueOperations对象,
 //            可以用来对Redis中的字符串类型数据进行操作可以使用Redis中的set、get、delete等操作字符串类型数据的命令。*/
 //
 //            redisTemplate.opsForValue().set(key, loginUser, 30, TimeUnit.MINUTES);
 //            //返回数据
 //            Map<String, Object> data = new HashMap<>();
 //            data.put("token", key);
 //            return data;
 //        }
 //
 //        return null;
 //    }
 ​
     @Override
     public Map<String, Object> getUserInfo(String token) {
         // 根据token获取用户信息,redis
         Object obj = redisTemplate.opsForValue().get(token);
         // 在 redisConfig中已经做了序列化,所以需要用抽象类JSON反序列化取出来,转换成User对象(也可以用其它的实现)
         if (obj != null) {
             //将一个Java对象转换为JSON字符串,然后再将JSON字符串转换回Java对象可以将数据格式标准化
             // JSON.parseObject第一个参数是要转换的JSON字符串,第二个参数是要转换成的Java对象类型。
             User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class);
             Map<String, Object> data = new HashMap<>();
             data.put("name", loginUser.getUsername());
             data.put("avatar", loginUser.getAvatar());
             List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());
 ​
             //角色,一个人可能有多个角色
             data.put("roles", roleList);
             return data;
         }
         return null;
     }
 ​
     @Override
     public void logout(String token) {
         redisTemplate.delete(token);
     }
 }

Result

Result
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class Result<T> {
     private Integer code;
     private String message;
     private T data;
 ​
     public static <T>  Result<T> success(){
         return new Result<>(20000,"success",null);
     }
 ​
     public static<T>  Result<T> success(T data){
         return new Result<>(20000,"success",data);
     }
 ​
     public static<T>  Result<T> success(T data, String message){
         return new Result<>(20000,message,data);
     }
 ​
     public static<T>  Result<T> success(String message){
         return new Result<>(20000,message,null);
     }
 ​
     public static<T>  Result<T> fail(){
         return new Result<>(20001,"fail",null);
     }
 ​
     public static<T>  Result<T> fail(Integer code){
         return new Result<>(code,"fail",null);
     }
 ​
     public static<T>  Result<T> fail(Integer code, String message){
         return new Result<>(code,message,null);
     }
 ​
     public static<T>  Result<T> fail( String message){
         return new Result<>(20001,message,null);
     }
 ​
 }
 ​

config

MpConfig
 @Configuration
 public class MpConfig {
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
         return interceptor;
     }
 }
MyCorsConfig
 @Configuration
 public class MyCorsConfig {
 //     当前跨域请求最大有效时长,这里默认1天
 //    private static final long MAX_AGE = 24 * 60 * 60;
     @Bean
     public CorsFilter corsFilter() {
         //1.添加CORS配置信息
         CorsConfiguration config = new CorsConfiguration();
         //1) 允许的域,不要写*,否则Cookie就无法使用了
         //这里填写请求的前端服务器
         config.addAllowedOrigin("http://localhost:8888");
         //2) 是否发送Cookie信息
         config.setAllowCredentials(true);
         //3) 允许的请求方式
         config.addAllowedMethod("*");
 ​
 //        config.setMaxAge(MAX_AGE);
         // 4)允许的所有的请求头
         config.addAllowedHeader("*");
         //2.添加映射路径,我们拦截一切请求
         UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
 //        接收的是接收CorsConfiguration类型的参数,
         urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", config);
 ​
         //3.返回新的CorsFilter.
         return new CorsFilter(urlBasedCorsConfigurationSource);
     }
 }
 ​
MyRedisConfig
 @Configuration
 public class MyRedisConfig {
     @Resource
     private RedisConnectionFactory factory;
 ​
     @Bean
     public RedisTemplate redisTemplate(){
         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
         redisTemplate.setConnectionFactory(factory);
         redisTemplate.setKeySerializer(new StringRedisSerializer());
 ​
         Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
         redisTemplate.setValueSerializer(serializer);
 ​
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
         om.setTimeZone(TimeZone.getDefault());
         om.configure(MapperFeature.USE_ANNOTATIONS, false);
         om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
         om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
         om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
         serializer.setObjectMapper(om);
 ​
         return redisTemplate;
     }
 }

yml

yml
 server:
   port: 9999
 ​
 spring:
   datasource:
     username: root
     password: 123456
     url: jdbc:mysql:///xdb
   redis:
     port: 6379
     host: localhost
 ​
 logging:
   level:
     com.suoqi: debug
 ​
 mybatis-plus:
   global-config:
     db-config:
       logic-delete-field: delted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
       logic-delete-value: 1 # 逻辑已删除值(默认为 1)
       logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

mapper

UserMapper

 public interface UserMapper extends BaseMapper<User> {
     public List<String> getRoleNameByUserId(Integer userId);
 }

xml

UserMapper.xml
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.suoqi.sys.mapper.UserMapper">
     <select id="getRoleNameByUserId" parameterType="Integer" resultType="String">
         SELECT b.role_name
         FROM x_user_role a,
              x_role b
         WHERE a.`user_id` = #{userId}
           AND a.`role_id` = b.`role_id`
     </select>
 </mapper>

 ​

Guess you like

Origin blog.csdn.net/m0_64880608/article/details/131485810