springboot+vue front-end separation project (background management system)

study notes

The learning resources come from UP at station B. Up he talks in great detail, which is very useful for getting familiar with the two frameworks.

The source code of my homework is at the end of the article. Students in need are welcome to learn and use it for reference. The built-in SQL file can be used after importing and opening the springboot and vue services. Pay attention to changing your database information configuration, learn together, and make progress together! !

1. The environment configuration used:

Compiler: IDEA
Background framework: SpringBoot
Mybatis-Plus
Database: Mysql8.0
Database tool: Navicat premium
Front-end framework: Rich text editor referenced by Vue
Element UI : wangEditor

2. Project introduction

This is a background management system based on SpringBoot and Vue.
Main functions:

1. Realize CRUD of user information and display of pages.
2. Assignment of user rights, user locks with different rights can see different interface information and operations.
3. Realize the upload and download of pictures and files.
4. Realize the use of page rich text compiler and CRUD of information.
5. Cross-domain configuration, MybatisPlus configuration.
6. User login registration, interceptor.
7. Query function.
. . . .
Project display: (picture)
1. Login interface
insert image description here
2. Registration page These two pages can be switched freely
insert image description here
3. The default page and highlight display after root login
insert image description here
4. Display of several pages
insert image description here
insert image description here
5. CRUD operations that root account can perform And the user information page that can be viewed
insert image description here
Modify
insert image description here
6. Personal information modification, and logout
insert image description here
7. Ordinary user login
Here only limit the authority of the book page and limit the user information
insert image description here

3. Summary of knowledge points (code and configuration)

Configuration file:
SpringBoot:
1. Mybatis-Plus configuration file, realize paging query: MybatisPlusConfig
Reference official website: MybatisPlus

package com.wen.common;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//Spring boot方式
@Configuration
@MapperScan("com.wen.mapper")//这里所扫描的是项目中mapper文件的位置!
public class MybatisPlusConfig {

    // 旧版,官网的旧版视乎无法使用

    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

}

2. Cross-domain configuration file: CorsConfig

package com.wen.common;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    private static final long Max_AGE = 24*60*60;//连接时间
    private CorsConfiguration buildConfig(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //定义所允许的请求头,方法等。*代表所有
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setMaxAge(Max_AGE);
        return corsConfiguration;
    }
    @Bean
    public CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",buildConfig());;//允许访问后台的所有接口
        return new CorsFilter(source);
    }
}

3. Request a return class! : Result
is a key point here, it liberates my usual back-end coding thinking, thank you very much, I didn't expect get and set to be so convenient.
Put all the requests back into the unified definition, redefine and return according to the code specified by the project, to achieve the common effect of the project, very practical!

package com.wen.common;

public class Result<T> {
    private String code;
    private String msg;
    private T data;//定义泛型,用于接受数据。

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
    public Result(){

    }

    public Result(T data) {
        this.data = data;
    }
    public static Result success(){
        Result result = new Result<>();
        result.setCode("0");
        result.setMsg("成功");
        return result;
    }

    public static <T> Result<T> success(T data){
        Result<T> result = new Result<>(data);
        result.setCode("0");
        result.setMsg("成功");
        return result;
    }
    public static Result error(String code,String msg){
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

4. pom.xml configuration file

<?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.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wen</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--spring mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
<!--        mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.1</version>
        </dependency>
<!--一个后端工具库-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.7</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Vue:
1. Here, in order to solve the problem of intercepting pages of users who are not logged in, and encapsulate axios to facilitate the use of requests, a tool class /utils/ is created in Vue: request.js

import axios from 'axios'
import router from "@/router";

const request = axios.create({
    //baseUrl:'/api'
    timeout: 5000
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    // config.headers['token'] = user.token;  // 设置请求头
    //取出sessionStorage里面的用户信息
    let userJson = sessionStorage.getItem("user");
    if (!userJson){
        router.push("/login");
    }
    return config
}, error => {
    return Promise.reject(error)
});
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        let res = response.data;
        // 如果是返回的文件
        if (response.config.responseType === 'blob') {
            return res
        }
        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        return res;
    },
    error => {
        console.log('err' + error) // for debug
        return Promise.reject(error)
    }
)
export default request

2. To solve the cross-domain problem: create a new vue.config.js file under the vue file

// 跨域配置
module.exports = {
    devServer: {                //记住,别写错了devServer//设置本地默认端口  选填
        port: 9876,//设置的本项目端口
        proxy: {                 //设置代理,必须填
            '/api': {              //设置拦截器  拦截器格式   斜杠+拦截器名字,名字可以自己定
                target: 'http://localhost:9090/',     //代理的目标地址
                changeOrigin: true,              //是否设置同源,输入是的
                pathRewrite: {                   //路径重写
                    '/api': ''                     //选择忽略拦截器里面的单词
                }
            }
        }
    }
}

Summary of other knowledge points:
SpringBoot backend file upload and download Controller: FileController

package com.wen.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.wen.common.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;

@RestController
@RequestMapping("/files")
public class FileController {
    @Value("${server.port}")
    private String port;

    private static final String ip = "http://localhost";

    /**
     * 上传接口
     * @param file
     * @return
     */

    @PostMapping("/upload")
    public Result<?> upload(MultipartFile file){
        String originalFilename = file.getOriginalFilename();//获取源文件的名称
//        定义文件的唯一标识(前缀)
        String flag = IdUtil.fastSimpleUUID();

        String rootFilePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"+flag+"_"+originalFilename;//获取文件上传的路径
        try {
            FileUtil.writeBytes(file.getBytes(),rootFilePath);//把文件写入该路径
        } catch (IOException e) {
            e.printStackTrace();
        }
        String url = ip+":"+port+"/files/"+flag;
        return Result.success(url);//返回结果url
    }

    /**
     * 下载接口
     * @param flag
     * @param response
     */
    @GetMapping("/{flag}")
    public void getFiles(@PathVariable String flag, HttpServletResponse response){
        OutputStream os;//新建一个输出对象
        String basePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/";//文件路径
        List<String> fileNames = FileUtil.listFileNames((basePath));//获取所有的文件名称
        String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse("");//找到根参数一致的文件

        try {
            if (StrUtil.isNotEmpty(fileName)){
                response.addHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));
                response.setContentType("application/octet-stream");
                byte[] bytes = FileUtil.readBytes(basePath + fileName);//通过文件路径读取文字节流
                os = response.getOutputStream();//通过输出流返回文件
                os.write(bytes);
                os.flush();
                os.close();
            }
        }catch (Exception e){
            System.out.println("文件下载失败");
        }
    }
    /**
     * 富文本上传接口
     * @param file
     * @return
     */

    @PostMapping("editor/upload")
    public JSON editorUpload(MultipartFile file){
        String originalFilename = file.getOriginalFilename();//获取源文件的名称
//        定义文件的唯一标识(前缀)
        String flag = IdUtil.fastSimpleUUID();

        String rootFilePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"+flag+"_"+originalFilename;//获取文件上传的路径
        try {
            FileUtil.writeBytes(file.getBytes(),rootFilePath);//把文件写入该路径
        } catch (IOException e) {
            e.printStackTrace();
        }
        String url = ip+":"+port+"/files/"+flag;
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("errno",0);
        JSONArray arr = new JSONArray();
        JSONObject data = new JSONObject();
        arr.add(data);
        data.set("url",url);
        jsonObject.set("data",arr);
        return jsonObject;//返回结果url
    }
}

Summarize:

  • @Value: Obtain the data specified in the configuration file (the server.port here exists in the application.yaml file in the project file), and store it in the variable defined below.
  • MultipartFile: The class used to receive uploaded files, recommended articles , which contains many usages of this class, very detailed.
  • IdUtil.fastSimpleUUID(): The method in hutool is used to generate a uniquely identified UUID, which is added in front of the uploaded image for unique distinction, avoiding the problem of overwriting after uploading the same file name.
  • System.getProperty("user.dir"): Get the root directory of the current project, which is the springboot-vue-demo directory in this project.insert image description here
  • HttpServletResponse: The response to the http request. (Learning key points, I am not very familiar with it, strengthen study!)
  • response.addHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));: Add the corresponding header to define the name of the downloaded file.
  • response.setContentType(“application/octet-stream”);: Define the format of file download, binary stream.

About Mybatis-Plus: In short, it is very convenient, and the development combined with lombok greatly simplifies the entity definition of the back end and the operation problems related to the database.
In Spring Boot:

  • SpringBoot introduces MybatisPlus through maven

    com.baomidou mybatis-plus-boot-starter mybatis-plus-latest-version//Remember to change it to the version number here, so it cannot be imported!
  • The configuration can be used only through the @MapperScan annotation

    @MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")//Here is the file path where mapper is stored in the project.

  • Annotations that need to be used: official website
    For example: using @TableName("user") on the entity is to correspond the entity to the corresponding table in the database, and @TableId is the primary key in the database.

  • Define the interface, that is, the mapper layer or the service layer: Inherit BaseMapper<corresponding entity name> to use the methods in BaseMapper, including various CRUD operations. If there is a XXmapper.xml file that defines itself, it will use the xml file The corresponding CRUD methods. All interfaces in the official

    public interface BookMapper extends BaseMapper {
    }

About wangEditor:
Hahaha, the problem of creating editors repeatedly has been solved!
The error during learning is as follows:
Problem description: Since the editor node can only be generated after the pop-up window is created, that is, it can only be obtained. In the project, the original code will cause new and modified pop-up windows to repeatedly create the editor.
insert image description here
Solution:

let editor;
method:{
creatDom(){
      editor = new E('#div1');//富文本编辑器创建,获取节点
      // 配置 server 接口地址
      editor.config.uploadImgServer = 'http://localhost:9090/files/editor/upload';
      editor.config.uploadFileName = 'file';//设置文件上传的名字
      editor.create();//创建。
    },
    //这里是新增弹窗
    add(){
      this.dialogVisible = true;
      this.form = {};
      //由于只有在弹窗启动之后,div节点才会被创建,那么创建富文本编辑器也只能在其之后。
      this.$nextTick(()=>{
        if (editor==null){
          this.creatDom();
        }else {
          editor.destroy();//这里做了一次判断,判断编辑器是否被创建,如果创建了就先销毁。
          this.creatDom();
        }
      });
    },
    //这里是修改弹窗
    handleEdit(row){
      this.form = JSON.parse((JSON.stringify(row)));
      this.dialogVisible = true;
      this.$nextTick(()=>{
        if (editor==null){
          this.creatDom();
          editor.txt.html(row.content);
        }else {
          editor.destroy();//这里做了一次判断,判断编辑器是否被创建,如果创建了就先销毁。
          this.creatDom();
          editor.txt.html(row.content);
        }
      });
    },
}

About Vue:
Use sessionStorage to obtain the information of the logged-in user, to determine whether the user is logged in, and to obtain information such as the user's authority, nickname, age, etc. for the display of the page. If the user is not logged in, it will be redirected to the login interface.
The required code is as follows: not used on the same page

sessionStorage.removeItem("user");//登录界面加载前先清楚已有的用户信息(若有)
sessionStorage.setItem("user",JSON.stringify(res.data));//缓存用户信息

let userStr =sessionStorage.getItem("user") || '{}';//从sessionStorage中获取用户信息
this.user = JSON.parse(userStr);//将JSON转为JavaScript对象,再赋值给user对象

Routing for Vue:

import { createRouter, createWebHistory } from 'vue-router'
import Layout from '../layout/Layout.vue'

const routes = [
  {
    path: '/',
    name: 'Layout',
    component: Layout,
    redirect: "/news",
    children: [
      {
        path: 'user',
        name: 'User',
        component: () => import("@/views/User"),
      },
      {
        path: '/book',
        name: 'Book',
        component: ()=>import("@/views/Book")
      },
      {
        path: '/person',
        name: 'Person',
        component: ()=>import("@/views/Person")
      },
      {
        path: '/news',
        name: 'News',
        component: ()=>import("@/views/News")
      },
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: ()=>import("@/views/Login")
  },
  {
    path: '/register',
    name: 'Register',
    component: ()=>import("@/views/Register")
  },

]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

Among them: the main route is /, redirected to the /news page when visiting, there are many sub-routes under the main route, and the array objects in children, that is, the same page, different sub-pages.

Project source code : Github warehouse

Talk is cheap, show me the code! —— Xinhuo Studio!

at last

I know that most junior and middle-level Java engineers want to improve their skills, and they often try to grow by themselves or enroll in classes. However, the tuition fees of nearly 10,000 yuan for training institutions are really stressful. The effect of self-study is inefficient and long, and it is easy to hit the ceiling and stagnate in technology!

Therefore, I collected and sorted out a " Complete Set of Learning Materials for Java Development " and gave it to everyone. The original intention is also very simple, that is, I hope to help friends who want to learn and improve themselves but don't know where to start, and at the same time reduce everyone's burden.

The editor has encrypted: aHR0cHM6Ly9kb2NzLnFxLmNvbS9kb2MvRFVrVm9aSGxQZUVsTlkwUnc==For security reasons, we have encoded the website through base64, and you can decode the URL through base64.

Guess you like

Origin blog.csdn.net/m0_67390963/article/details/126774149