从零搭建SpringBoot3+Vue3前后端分离项目基座,中小项目可用

文章目录
  • 1. 后端项目搭建
    • 1.1 环境准备
    • 1.2 数据表准备
    • 1.3 SpringBoot3项目创建
    • 1.4 MySql环境整合,使用druid连接池
    • 1.5 整合mybatis-plus
      • 1.5.1 引入mybatis-plus
      • 1.5.2 配置代码生成器
      • 1.5.3 配置分页插件
    • 1.6 整合swagger3(knife4j)
      • 1.6.1 整合
      • 1.6.2 使用
    • 1.7 数据交互处理
      • 1.7.1 响应数据封装(公共返回数据类)
      • 1.7.2 分页查询返回数据封装
    • 1.8 全局异常处理
    • 1.9 整合JWT,生成token
    • 1.10 封装ThreadLocal 工具类
    • 1.11 MD5封装
    • 1.12登录验证拦截
    • 1.13 登录和获取当前用户信息接口处理
  • 2. 前端项目搭建
    • 2.1 环境准备
    • 2.2 创建Vue3项目
    • 2.3 项目搭建准备
    • 2.4 Element Plus 安装使用
    • 2.5 axios 安装使用
      • 2.5.1 安装
      • 2.5.2 配置(创建实例,配置请求、响应拦截器)
      • 2.5.3 配置跨域
    • 2.6 Vue Router 安装使用
    • 2.7 Pinia状态管理库
    • 2.7.1 Pinia持久化插件-persist
    • 2.8 搭建管理页面基础框架
      • 2.8.1 在src下创建api目录,次目录存放请求http方法的封装,创建user.js, 里边写封装请求方法
      • 2.8.2 登录页面
      • 2.8.3 布局页面
  • 3.项目代码

项目使用SpringBoot3+Vue3, 后端使用springboot3, mybatisPlus, druid,knife4j(swagger3),Jwt; 前端 vue3 , element-plus, axios, pinia, vue-router; 项目前后端分离, 可持续扩展, 代码放到最后

1. 后端项目搭建

1.1 环境准备

- spring-boot3 最低支持jdk17, 所以需要准备jdk17环境
- Idea 版本IntelliJ IDEA 2021.2及以后,版本关系参考[https://blog.csdn.net/m0_62258564/article/details/134527268](https://blog.csdn.net/m0_62258564/article/details/134527268)
- maven 版本参考以上链接
- MySql8

1.2 数据表准备

创建数据库 base_manage, 并创建表

CREATE TABLE user(
    id INT NOT NULL AUTO_INCREMENT COMMENT '主键',
    login_name VARCHAR(255) NOT NULL COMMENT '登录名(账号)',
    password VARCHAR(255) NOT NULL COMMENT '密码',
    name varchar(50) NOT NULL COMMENT '姓名',
    sex char(1) NOT NULL COMMENT '性别',
    phone VARCHAR(20) COMMENT '联系电话',
    PRIMARY KEY(id)
 )COMMENT '用户信息表';

用户表只是便于后续过程搭建操作,可根据需求修改

1.3 SpringBoot3项目创建

在这里插入图片描述
springboot版本可根据需求选择,这里选则默认的3.2.4
在这里插入图片描述
耐心等待项目构建完成, 构建完成pom文件如下,请注意,mysql驱动包<groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId>较之前版本有所改变

<?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>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.buzhisuoyun</groupId>
    <artifactId>base_manage</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>base_manage</name>
    <description>base_manage</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.4 MySql环境整合,使用druid连接池

添加 jdbc依赖,防止项目启动时找不到Bean报错

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

添加druid依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-3-starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.20</version>
</dependency>

项目使用yml格式配置文件,修改resources下application.properties
为application.yml, 并删除文件内容

配置项目端口号
server:
  port: 8099

配置数据源和druid 连接池

spring:
  # 数据源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/base_manage?serverTimezone=UTC
    username: root
    password: root

启动项目, 查看日志,端口和druid 初始化, 整合成功
在这里插入图片描述

1.5 整合mybatis-plus

1.5.1 引入mybatis-plus

官网地址: https://baomidou.com/
1、添加依赖

<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-spring-boot3-starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>

2、配置

# mybatis-plus
mybatis-plus:
  configuration:
    # sql日志, 开发调试时开启
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto

启动项目,出现mybatis-plus日志
在这里插入图片描述

1.5.2 配置代码生成器

1.引入相关依赖

<!-- mybatis-plus 代码生成器-->
<dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-generator</artifactId>
     <version>3.5.5</version>
</dependency>

<!-- mybatis-plus代码生成器模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
 </dependency>
  1. 新建utis包,并在下边创建MybatisPlusGenerator.java代码生成器配置类
    在这里插入图片描述

    package com.buzhisuoyun.base_manage.utils;

    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.io.File;
    import java.sql.Types;
    import java.util.Collections;

    public class MybatisPlusGenerator {
    static final String url = “jdbc:mysql://127.0.0.1:3306/base_manage?serverTimezone=UTC”; // 数据库地址
    static final String username = “root”; // 数据库用户名
    static final String password = “root”; // 数据库密码
    static final String authorName = “buzhisuoyun”; // 作者名
    static final String parentPackageNameResource = “com/buzhisuoyun/base_manage”; // mapper.xml路径
    static final String parentPackageNameJava = “com.buzhisuoyun.base_manage”; // java 文件父包名
    // 要生成代码对应的数据表名
    static final String tableName = “user”;

    public static void main(String[] args) {
    
        FastAutoGenerator.create(url, username, password)
                // 1.全局配置
                .globalConfig(builder -> {
                    builder.author(authorName)                // 设置作者
                            .enableSpringdoc()               // 开启 swagger 模式
                            // 获取当前工程路径并定位到项目java目录下
                            .outputDir(System.getProperty("user.dir") + "/src/main/java");            // 指定输出目录
                })
    
                // 2.数据源配置
                .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                    int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                    if (typeCode == Types.SMALLINT) {
                        // 自定义类型转换
                        return DbColumnType.INTEGER;
                    }
                    return typeRegistry.getColumnType(metaInfo);
    
                }))
    
                // 3.包名策略配置
                .packageConfig(builder -> {
                    builder.parent(parentPackageNameJava) // 设置父包名
                            .entity("entity")
                            .mapper("mapper")
                            .service("service")
                            .serviceImpl("service.impl")
                            .controller("controller")
                            //.moduleName("system") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/" + parentPackageNameResource + "/mapper")); // 设置mapperXml生成路径
                })
                // 策略配置
                .strategyConfig(builder -> {
                    builder.addInclude(tableName) // 设置需要生成的表名
                            // 覆盖已生成文件
                            .entityBuilder().enableFileOverride()
                            .mapperBuilder().enableFileOverride()
                            .serviceBuilder().enableFileOverride().formatServiceFileName("%sService");
                            //.addTablePrefix("t_", "c_"); // 设置过滤表前缀
    
                })
    
                // 配置模板
                .templateConfig(builder -> {
                    //builder.controller("");         // 不生成controller
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
    

    }

  • 修改数据库相关信息
  • 修改authorName
  • 修改parentPackageNameResource, parentPackageNameJava
  • 修改tableName, 要生成代码的数据表名称,多个表使用,分割
  • .enableSpringdoc() 可以选择,生成swagger3文档注释
    修改完成后运行main函数,生成相应代码,mapper.xml在resource下与java同路径下
    在这里插入图片描述
    实体类import io.swagger.v3.oas.annotations.media.Schema;报错,是因为swagger3依赖还未导入,下边整合swagger3后就不会报错了
1.5.3 配置分页插件

新建config包,并在下边创建配置类MybatisPlusConfig.java
在这里插入图片描述

package com.buzhisuoyun.base_manage.config;

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;

@Configuration
@MapperScan("com/buzhisuoyun/base_manage/mapper")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
        //interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
        return interceptor;
    }
}

@MapperScan(“com/buzhisuoyun/base_manage/mapper”) 扫描mapper路劲,也可在启动类配置

1.6 整合swagger3(knife4j)

1.6.1 整合

引入依赖

<!-- API 文档 knife4j -->
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.5.0</version>
</dependency>

配置:

# springdoc-openapi 配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'default'
      paths-to-match: '/**'
      packages-to-scan: com.buzhisuoyun.base_manage

# knife4j 配置
knife4j:
  # 是否启用增强
  enable: true
  # 开启生产环境屏蔽
  production: false
  # 是否认证登录
  basic:
    # basic是否开启,默认为false
    enable: true
    username: knife4j
    password: knife4j
  setting:
    language: zh_cn
    enable-version: true
    enable-swagger-models: true

在config下创建配置类

package com.buzhisuoyun.base_manage.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Knife4jConfig {
    @Bean
    public OpenAPI springShopOpenApi() {
        return new OpenAPI()
                // 接口文档标题
                .info(new Info().title("接口文档")
                        .description("api接口文档")
                        .version("1.0版本")
                );
    }
}

配置完后,启动项目,访问路径http://localhost:9999/doc.html,用户名密码就是配置文件中的,结果如图
在这里插入图片描述

1.6.2 使用

实体类使用@Schema 注解,在mybatis-plus选择的话会自动生成
在这里插入图片描述
Conntroller 层使用:

@Tag(name = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
/**
     * 用户列表分页
     * @param pageSize 每页显示的条数
     * @param currentPage  要查询的页
     * @param name  用户姓名
     * @return  Result<PageResultBean<List<User>>>
     */
    @GetMapping("/pageList")
    @Operation(summary = "用户列表分页查询")
    @Parameters({
            @Parameter(name = "Authorization", in = ParameterIn.HEADER, required = true, description = "token"),
            @Parameter(name = "pageSize", required = true, description = "每页显示的条数"),
            @Parameter(name = "currentPage", required = true, description = "要查询的页"),
            @Parameter(name = "name", description = "用户姓名", required = false)
    })
    public Result<PageResultBean<User>> pageList(@RequestParam int pageSize, @RequestParam int currentPage, @Nullable @RequestParam String name) {
        IPage<User> page = userService.pageList(pageSize, currentPage, name);
        if (page == null) {
            return Result.error("查询失败");
        }
        //PageResultBean<User> pageResultBean = new PageResultBean<User>(page.getTotal(), page.getRecords());
        return Result.success(PageResultBean.getInstance(page.getTotal(), page.getRecords()));

    }
}

在这里插入图片描述

1.7 数据交互处理

1.7.1 响应数据封装(公共返回数据类)

封装返回数据封装类,放到common包下:
在这里插入图片描述

package com.buzhisuoyun.base_manage.common;

import io.swagger.v3.oas.annotations.media.Schema;

public class Result<T> {
    @Schema(description = "业务状态码 0:成功  1: 失败")
    private int code;        // 业务状态码 0:成功  1: 失败
    @Schema(description = "提示信息")
    private String message;  // 提示信息
    @Schema(description = "返回数据")
    private T data;          // 响应数据

    public Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    // 操作成功返回响应结果(带响应数据)
    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    public static <E> Result<E> success() {
        return new Result<>(0, "操作成功", null);
    }

    public static <E> Result<E> error(String message) {
        return new Result<>(1, message, null);
    }

    public int getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

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

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", message='" + message + ''' +
                ", data=" + data +
                '}';
    }
}
1.7.2 分页查询返回数据封装

封装分页查询数据,放到common包下:
在这里插入图片描述

package com.buzhisuoyun.base_manage.common;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

public class PageResultBean<T> {
    @Schema(description = "数据总条数")
    private Long total;
    @Schema(description = "当前页数据集合")
    private List<T> items;

    public PageResultBean() {
    }

    public PageResultBean(Long total, List<T> items) {
        this.total = total;
        this.items = items;
    }

    public static <E> PageResultBean<E> getInstance(Long total, List<E> items) {
        return new PageResultBean<>(total, items);
    }

    public Long getTotal() {
        return total;
    }

    public void setTotal(Long total) {
        this.total = total;
    }

    public List<T> getItems() {
        return items;
    }

    public void setItems(List<T> items) {
        this.items = items;
    }
}

1.8 全局异常处理

创建xception包,在包下放全局异常处理类GlobalExceptionHandler.java,类名放置 位置可以随意,为便于分类整理, 同类包放到一个包下
xception包
使用springboot @RestControllerAdvice 注解配置

package com.buzhisuoyun.base_manage.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    public Result<String> handlerException(Exception e) {
        logger.warn(e.getMessage());
        return Result.error(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败");
    }
}

1.9 整合JWT,生成token

引入依赖

 <!-- jwt -->
 <dependency>
     <groupId>com.auth0</groupId>
     <artifactId>java-jwt</artifactId>
     <version>4.4.0</version>
 </dependency>

封装工具类, 放到utils包下:

package com.buzhisuoyun.base_manage.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtil {
    private static final String KEY = "buzhisuoyun";   // 密钥

    // 接收数据,生成token并返回
    public static String getToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60))       // 失效时间1小时
                .sign(Algorithm.HMAC256(KEY));
    }

    // 接收token,验证并返回数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }
}

1.10 封装ThreadLocal 工具类

因项目使用前后端分离,使用ThreadLocal 线程变量存储用户登录信息,替代session

package com.buzhisuoyun.base_manage.utils;

public class ThreadLocalUtil {
    // 提供ThreadLocal 对象
    private static final ThreadLocal<Object> THREAD_LOCAL = new ThreadLocal<>();

    // 获取存储值
    public static <T> T get() {
        return (T) THREAD_LOCAL.get();
    }

    // 存储值
    public static void set(Object value) {
        THREAD_LOCAL.set(value);
    }

    // 清除THREAD_LOCAL 防止内存泄漏
    public static void remove() {
        THREAD_LOCAL.remove();
    }
}

1.11 MD5封装

package com.buzhisuoyun.base_manage.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

    // 测试
    public static void main(String[] args) {
        System.out.println(Md5Util.getMD5String("admin"));
    }

}

1.12登录验证拦截

1、创建包interceptors,在包下配置登录拦截器

package com.buzhisuoyun.base_manage.interceptors;


import com.buzhisuoyun.base_manage.utils.JwtUtil;
import com.buzhisuoyun.base_manage.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取token
        String token = request.getHeader("Authorization");
        // 验证token
        try {
            Map<String, Object> claims = JwtUtil.parseToken(token);
            // 存储业务信息到线程变量
            ThreadLocalUtil.set(claims);
            // 放行拦截
            return true;
        } catch (Exception e) {
            // 登录信息异常或未登录,http响应状态码为401
            response.setStatus(401);
            // 拦截请求
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清除ThreadLocal业务数据
        ThreadLocalUtil.remove();
    }
}

2、在config包下创建WebConfig配置类,注册登录拦截器

package com.buzhisuoyun.base_manage.config;

import com.buzhisuoyun.base_manage.interceptors.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 配置放行资源
        // 无需拦截的接口集合
        List<String> ignorePath = new ArrayList<>();
        // knife4j(swagger)
        ignorePath.add("/swagger-resources/**");
        ignorePath.add("/doc.html");
        ignorePath.add("/v3/**");
        ignorePath.add("/webjars/**");
        ignorePath.add("/static/**");
        ignorePath.add("/templates/**");
        ignorePath.add("/error");
        // 登录页面
        ignorePath.add("/user/login");
        registry.addInterceptor(loginInterceptor).excludePathPatterns(ignorePath);
    }
}

1.13 登录和获取当前用户信息接口处理

	@PostMapping("/login")
    @Operation(summary = "用户登录")
    @Parameters({
            @Parameter(name = "loginName", description = "登录名", required = true, schema = @Schema(type = "sting")),
            @Parameter(name = "password", description = "密码", required = true, schema = @Schema(type = "sting"))
    })
    public Result<String> login(@RequestBody User user) {
        if (user == null || "".equals(user.getLoginName()) || user.getLoginName() == null || "".equals(user.getPassword()) || user.getPassword() == null) {
            return Result.error("用户名密码不能为空");
        }
        // 检验用户名是否存在
        User eruser = userService.findByLoginName(user.getLoginName());
        if (eruser == null) {
            return Result.error("用户名不存在");
        }
        // 检验用户密码是否正确
        if (Md5Util.getMD5String(user.getPassword()).equals(eruser.getPassword())) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", eruser.getId());
            claims.put("name", eruser.getName());
            claims.put("loginName", eruser.getLoginName());
            String token = JwtUtil.getToken(claims);
            return Result.success(token);
        }
        return Result.error("密码错误");
    }



/**
     * 获取当前登录用户信息
     * @return  User
     */
    @GetMapping("/currentUser")
    @Operation(summary = "获取当前登录用户信息")
    @Parameter(name = "Authorization", in = ParameterIn.HEADER, required = true, description = "token")
    public Result<User> getCurrentUser() {
        Map<String, Object> userSession = ThreadLocalUtil.get();
        int id = (int) userSession.get("id");
        User user = userService.getUserById(id);
        if (user != null) {
            return Result.success(user);
        }
        return Result.error("用户状态异常");
    }

因为是做简单后端项目搭建,所以token存在一定问题,如在修改用户密码后必须强制重新登录或采用redis缓存token,同时验证浏览器token和用户token,或采用第三方认证中心解决等
项目工程结构如图
在这里插入图片描述

2. 前端项目搭建

2.1 环境准备

- node安装
- vscode安装

2.2 创建Vue3项目

在将要存放vue3项目的路径打开cmd, 使用以下命令创建项目

npm init vue@latest

在这里插入图片描述
此时项目创建完成,vscode打开项目目录,在资源目录空白右键,打开终端
在这里插入图片描述
执行命令 npm install安装依赖,等待安装完成后执行 npm run dev 运行项目
在这里插入图片描述
访问路径可访问项目
在这里插入图片描述
在终端ctrl c 可停止运行项目
项目描述如图,图引自黑马开源教程PPT
在这里插入图片描述

2.3 项目搭建准备

说明:项目中使用组合式API
删掉components下所有文件,删除App.vue文件不需要的东西,最后如下所示

<script setup>

</script>

<template>
    <router-view></router-view>
</template>

<style scoped>

</style>

2.4 Element Plus 安装使用

  • 安装 npm install element-plus --save

  • 引入:在main.js中引入Element Plus(参照官方文档)https://element.eleme.cn/#/zh-CN/component/quickstart

    import { createApp } from ‘vue’
    import ElementPlus from ‘element-plus’
    import ‘element-plus/dist/index.css’
    import App from ‘./App.vue’

    const app = createApp(App)
    app.use(ElementPlus)
    app.mount(‘#app’)

  • 使用: 访问Element官方文档,复制组件代码,修改调整

2.5 axios 安装使用

2.5.1 安装
npm install axios
2.5.2 配置(创建实例,配置请求、响应拦截器)

在src目录下新建 utils, 并在utils 下创建request.js 进行axios配置
在这里插入图片描述

// 请求配置

import axios from "axios";

// 定义公共前缀,创建请求实例
// const baseUrl = "http://localhost:8080";
const baseURL = '/api/';
const instance = axios.create({baseURL})


import { ElMessage } from "element-plus"
import { useTokenStore } from "@/stores/token.js"
// 配置请求拦截器
instance.interceptors.request.use(
    (config) => {
        // 请求前回调
        // 添加token
        const tokenStore = useTokenStore()
        // 判断有无token
        if (tokenStore.token) {
            config.headers.Authorization = tokenStore.token
        }
        return config
    },
    (err) => {
        // 请求错误的回调
        Promise.reject(err)
    }
)


import router from "@/router";
// 添加响应拦截器
instance.interceptors.response.use(
    result => {
        // 判断业务状态码
        if (result.data.code === 0) {
            return result.data;
        }
        // 操作失败
        ElMessage.error(result.data.message ? result.data.message : '服务异常')
        // 异步操作的状态转换为失败
        return Promise.reject(result.data)
    },
    err => {
        // 判断响应状态码, 401为未登录,提示登录并跳转到登录页面
        if (err.response.status === 401) {
            ElMessage.error('请先登录')
            router.push('/login')
        } else {
            ElMessage.error('服务异常')
        }
        // 异步操作的状态转换为失败
        return Promise.reject(err)  
    }
)

export default instance
2.5.3 配置跨域

在vite.config.js 中defineConfig配置代理,实现跨域

server: {
    proxy: {
        '/api': {   // 获取路径中包含了/api的请求
            target: 'http://localhost:9999',        // 服务端地址
            changeOrigin: true, // 修改源
            rewrite:(path) => path.replace(/^/api/, '')   // api 替换为 ''
        }
    }
  }

在这里插入图片描述

2.6 Vue Router 安装使用

  • 安装 npm install vue-router@4

  • 在src/router/index.js中创建路由器,并导出

    // 导入vue-router
    import {createRouter, createWebHistory} from ‘vue-router’

    // 导入组件
    import LoginVue from ‘@/views/Login.vue’
    import LayoutVue from ‘@/views/Layout.vue’
    import UserList from ‘@/views/user/UserList.vue’
    import EditPassword from ‘@/views/user/EditPassword.vue’
    import DisplayUser from ‘@/views/user/DisplayUser.vue’

    // 定义路由关系
    const routes = [
    {path: ‘/login’, component: LoginVue},
    {
    path: ‘/’, component: LayoutVue, redirect: ‘’, children: [
    {path: ‘/user/userlist’, name: “/user/userlist”, component: UserList, meta: {
    title: “用户列表”
    },},
    {path: ‘/user/editpassword’, name: “/user/editpassword”, component: EditPassword, meta: {
    title: “修改密码”
    }
    },
    {path: ‘/user/displayuser’, name: “/user/displayuser”, component: DisplayUser, meta: {
    title: “个人信息”
    }}
    ]
    }
    ]

    // 创建路由器
    const router = createRouter({
    history: createWebHistory(),
    routes: routes
    })

    export default router

  • 在vue应用实例中使用vue-router

    在main.js 中

    import router from ‘@/router’
    app.use(router)

  • 声明router-view标签,展示组件内容

    在app.vue 中

2.7 Pinia状态管理库

  • 安装 npm install pinia

2.7.1 Pinia持久化插件-persist

  • 安装persist npm install pinia-persistedstate-plugin

  • 在pinia中使用persist

    main.js

    import {createPersistedState} from’pinia-persistedstate-plugin’ const persist = createPersistedState()
    pinia.use(persist)

  • 定义状态Store时指定持久化配置参数
    在这里插入图片描述

  • 在src/stores/下定义token.js和userInfo.js 用来存储token和用户相关信息

    token.js

    // 定义 store
    import { defineStore } from “pinia”
    import {ref} from ‘vue’
    /*
    第一个参数:名字,唯一性
    第二个参数:函数,函数的内部可以定义状态的所有内容

    返回值: 函数
    

    */
    export const useTokenStore = defineStore(‘token’, () => {
    // 响应式变量
    const token = ref(‘’)

    // 修改token值函数
    const setToken = (newToken) => {
        token.value = newToken
    }
    
    // 移除token值函数
    const removeToke = () => {
        token.value = ''
    }
    
    return {
        token, setToken, removeToke
    }
    

    },
    {
    persist: true // 持久化存储
    }
    )

    userInfo.js

    import { defineStore } from “pinia”
    import {ref} from ‘vue’

    const useUserInfoStore = defineStore(‘userInfo’, () => {
    const info = ref({})

    const setInfo = (newInfo) => {
        info.value = newInfo
    }
    
    const removeInfo = () => {
        info.value = {}
    }
    
    return {info, setInfo, removeInfo}
    

    },
    {
    persist: true
    }
    )

    export default useUserInfoStore;

  • 在组件中使用store 示例

    import {userLoginService} from ‘@/api/user.js’
    import {useTokenStore} from ‘@/stores/token.js’
    import {useRouter} from ‘vue-router’
    const router = useRouter()
    const tokenStore = useTokenStore();
    const login = async ()=>{
    // 校验表单
    if (!ruleFormRef.value) return
    console.log(“校验”)
    await ruleFormRef.value.validate(async (valid) => {
    if (valid) {
    console.log(“校验成功”)
    // 调用接口,完成登录
    let result = await userLoginService(registerData.value);
    /* if(result.code===0){
    alert(result.msg? result.msg : ‘登录成功’)
    }else{
    alert(‘登录失败’)
    } */
    //alert(result.msg? result.msg : ‘登录成功’)
    ElMessage.success(result.msg ? result.msg : ‘登录成功’)
    //token存储到pinia中
    tokenStore.setToken(result.data)
    //跳转到首页 路由完成跳转
    router.push(‘/’)
    } else {
    console.log(“校验失败”)
    }
    })
    }

    // 配置请求拦截器
    instance.interceptors.request.use(
    (config) => {
    // 请求前回调
    // 添加token
    const tokenStore = useTokenStore()
    // 判断有无token
    if (tokenStore.token) {
    config.headers.Authorization = tokenStore.token
    }
    return config
    },
    (err) => {
    // 请求错误的回调
    Promise.reject(err)
    }
    )

2.8 搭建管理页面基础框架

2.8.1 在src下创建api目录,次目录存放请求http方法的封装,创建user.js, 里边写封装请求方法
import request from "@/utils/request.js"

// 登录接口调用函数
export const userLoginService = (loginData) => {
    return request.post('/user/login', loginData)
}

// 获取当前登录用户信息
export const currentUserService = () => {
    return request.get('/user/currentUser')
}

// 获取所有用户信息
export const allUserService = () => {
    return request.get('/user/userList')
}

// 分页查询
export const pageListService = (pageParam) => {
    return request.get('/user/pageList', {params: pageParam})
}

// 新增用户
export const addUserService = (addData) => {
    return request.post('/user/add', addData)
}

// 根据id获取用户信息
export const getUserById = (id) => {
    return request.get('/user/getuserById', {params: id})
}

// 修改用户信息
export const updateUserService = (data) => {
    return request.put('/user/update', data)
}

// 删除用户
export const deleteByIdService = (id) => {
    console.log("deleteRequestid:", id)
    return request.delete('/user/delete/' + id)
}
2.8.2 登录页面
  • 安装 npm install sass -D

在src下创建views目录,用于存放vue页面组件
在这里插入图片描述
登录 Login.vue

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
//定义数据模型
const registerData = ref({
    loginName: 'admin',
    password:'admin',
    rePassword: ''
})

// 定义表单组件的引用
const ruleFormRef = ref(null)

//定义表单校验规则
const rules = ref({
    loginName: [
        { required: true, message: '请输入用户名', trigger: 'blur' },
        { min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' }
    ],
    password: [
        { required: true, message: '请输入密码', trigger: 'blur' },
        { min: 5, max: 16, 2: '长度为5~16位非空字符', trigger: 'blur' }
    ]
})

//绑定数据,复用注册表单的数据模型
//表单数据校验
//登录函数
import {userLoginService} from '@/api/user.js'
import {useTokenStore} from '@/stores/token.js'
import {useRouter} from 'vue-router'
const router = useRouter()
const tokenStore = useTokenStore();
const login = async ()=>{
    // 校验表单
    if (!ruleFormRef.value) return
    console.log("校验")
    await ruleFormRef.value.validate(async (valid) => {
        if (valid) {
            console.log("校验成功")
            // 调用接口,完成登录
            let result = await userLoginService(registerData.value);
            /* if(result.code===0){
                alert(result.msg? result.msg : '登录成功')
            }else{
                alert('登录失败')
            } */
            //alert(result.msg? result.msg : '登录成功')
            ElMessage.success(result.msg ? result.msg : '登录成功')
            //token存储到pinia中
            tokenStore.setToken(result.data)
            //跳转到首页 路由完成跳转
            router.push('/')
        } else {
            console.log("校验失败")
        }
    })
}

//定义函数,清空数据模型的数据
const clearRegisterData = ()=>{
    registerData.value={
        loginName: '',
        password:'',
        rePassword:''
    }
}
</script>

<template>
    <el-row class="login-page">
        <el-col :span="12" class="bg"></el-col>
        <el-col :span="6" :offset="3" class="form">
            <!-- 登录表单 -->
            <el-form ref="ruleFormRef" :model=registerData size="large" autocomplete="off" :rules="rules">
                <el-form-item>
                    <h1>登录</h1>
                </el-form-item>
                <el-form-item prop="loginName">
                    <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.loginName"></el-input>
                </el-form-item>
                <el-form-item prop="password">
                    <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
                </el-form-item>
                <el-form-item class="flex">
                    <div class="flex">
                        <el-checkbox>记住我</el-checkbox>
                        <!-- <el-link type="primary" :underline="false">忘记密码?</el-link> -->
                    </div>
                </el-form-item>
                <!-- 登录按钮 -->
                <el-form-item>
                    <el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button>
                </el-form-item>
            </el-form>
        </el-col>
    </el-row>
</template>

<style lang="scss" scoped>
/* 样式 */
.login-page {
    height: 100vh;
    background-color: #fff;

    .bg {
        background: url('@/assets/login_bg.jpg') no-repeat center / cover;
        border-radius: 0 20px 20px 0;
    }

    .form {
        display: flex;
        flex-direction: column;
        justify-content: center;
        user-select: none;

        .title {
            margin: 0 auto;
        }

        .button {
            width: 100%;
        }

        .flex {
            width: 100%;
            display: flex;
            justify-content: space-between;
        }
    }
}
</style>
2.8.3 布局页面
  • Layout .vue

项目结构:
在这里插入图片描述

3.项目代码

https://download.csdn.net/download/qq_51355375/89085020

此项目不包含动态路由,菜单权限部分, 如需要可参考 : https://blog.csdn.net/qq_51355375/article/details/139722876

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/944523.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

支持最新 mysql9的workbench8.0.39 中文汉化教程来了

之前在 B 站上发布了 mysql8 workbench 汉化教程&#xff0c;一年多来帮助很多初学者解决了不熟悉英文的烦恼。 汉化视频可以访问&#xff1a; 2024最新版mysql8.0.39中文版mysql workbench汉化 中文升级 旧版汉化报错解决_哔哩哔哩_bilibili MySql Workbench汉化_哔哩哔哩_…

JavaWeb期末复习

目录 学习通题目Web技术与应用&#xff08;2024秋&#xff09;第二章 绪论课前测试一.选择题二.填空题 课后巩固 第三章 HTML基础课前测试一.单选题二.填空题 课后巩固一.单选题二.填空题 第四章 CSS课前测试二.单选题三.填空题 4.5 浮动与清理一.单选题 课后巩固二.单选题 第五…

云手机与Temu矩阵:跨境电商运营新引擎

云手机与 Temu 矩阵结合的基础 云手机技术原理 云手机基于先进的 ARM 虚拟化技术&#xff0c;在服务器端运行 APP。通过在服务器上利用容器虚拟化软件技术&#xff0c;能够虚拟出多个独立的手机操作系统实例&#xff0c;每个实例等同于一部单独的手机&#xff0c;可独立运行各…

【每日学点鸿蒙知识】Text填充父控件、Native接收数组、js逻辑不执行问题、UIAbility上下文问题、页面跳转路由栈

1、HarmonyOS 如何使Text组件填充满父组件&#xff1f; build() {Row() {Row() {Text(this.str).constraintSize({ maxWidth: 100%, minHeight: "30vp" }).backgroundColor(Color.Gray).fontSize(24vp)}.key(row1).constraintSize({ maxWidth: 100%}).backgroundCol…

Sqoop的使用

每个人的生活都是一个世界&#xff0c;即使最平凡的人也要为他那个世界的存在而战斗。 ——《平凡的世界》 目录 一、sqoop简介 1.1 导入流程 1.2 导出流程 二、使用sqoop 2.1 sqoop的常用参数 2.2 连接参数列表 2.3 操作hive表参数 2.4 其它参数 三、sqoop应用 - 导入…

基于Java+Springboot+Vue开发的旅游景区管理系统,实习作品

项目简介 该项目是基于JavaSpringbootVue开发的旅游景区管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Java的旅…

十二月第五周python

第一个程序&#xff0c;熟悉转换器&#xff0c;把加法计算器变成exe# // 1,制作加法计算器&#xff0c; # 输入两个数字得到相加结果并输出aint(input("输入数字&#xff1a;"))#int()是把输入的内容转换成整数&#xff0c; bint(input("输入数字&#xff1a;&…

【视觉惯性SLAM:十一、ORB-SLAM2:跟踪线程】

跟踪线程是ORB-SLAM2的核心之一&#xff0c;其主要任务是实时跟踪相机的位姿变化和场景的变化&#xff0c;以维持地图的更新和相机轨迹的估计。ORB-SLAM2的跟踪线程通过多种方式&#xff08;参考关键帧跟踪、恒速模型跟踪、重定位跟踪、局部地图跟踪&#xff09;处理跟踪丢失、…

在字节5年被优化,太难了。。。

先简单说下&#xff0c;涵哥是某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。还差几个月也7年了吧&#xff0c;算是在这行的资深划水员。7年的时间也让涵哥从…

阿里云-将旧服务器数据与配置完全迁移至新服务器

文章目录 一&#xff1a;创建镜像二&#xff1a;将创建好的镜像复制到新服务器所在的目标地域&#xff08;如果新服务器与镜像在同一地域就不用进行这一操作&#xff09;三&#xff1a;将镜像配置到新服务器上四&#xff1a;导出安全组&#xff08;如果新服务器与旧服务器使用同…

Spark SQL DML语句

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 Spark本地模式安装_spark3.2.2本地模式安装-CSDN博客 DML&#xff08;Data Manipulation Language&#xff0c;数据操作语言&#xff09;操作主要用来对…

路过石岩浪心古村

周末常去的七彩城堡儿童乐园附近经常有老房子&#xff0c;没想到老房子最多的地方还是浪心古村。而且越看越有历史。 见到一座写着《序西书室》的房子&#xff0c;我最开始以为是一个古代的学校。但是查了百度更加不知道什么意思了哈。‌“序西书室”‌是指《文心雕龙》中的一个…

运行Zr.Admin项目(后端)

1.下载Zr.Admin代码压缩包 https://codeload.github.com/izhaorui/Zr.Admin.NET/zip/refs/heads/main 2.打开项目 我这里装的是VS2022社区版 进入根目录&#xff0c;双击ZRAdmin.sln打开项目 3.安装.net7运行时 我当时下载的代码版本是.net7的 点击安装 点击安装&#xff0…

Wordly Wise 3000 国际背单词01 介绍 + 测词汇量

&#x1f4da; Wordly Wise 3000 国际背单词01 介绍 测词汇量 &#x1f31f; 大家好&#xff01;我们正式启动背Wordly Wise 3000单词&#xff0c;旨在利用国际资源和科学的学练方法&#xff0c;帮助大家更得效地坚持学练单词。我们将通过图文和Video等多种形式与大家分享经验…

HarmonyOS Next 实现登录注册页面(ARKTS) 并使用Springboot作为后端提供接口

1. HarmonyOS next ArkTS ArkTS围绕应用开发在 TypeScript &#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集 ArkTS在TS的基础上扩展了struct和很多的装饰器以达到描述UI和状态管理的目的 以下代码是一个基于…

3.微服务灰度发布落地实践(组件灰度增强)

文章目录 前言调用链示图dubbo服务之间的的调链cloud 服务之间的调用链 网关servlet容器: 标签续传1.定义插件2.实现灰度增强拦截 线程池: 标签续传1.拦截Runnable或Callable,接口增强实现标签续传;Callable 插件定义Runnable 插件定义拦载Callabl或Runnable构造(可共用)拦载ru…

不修改内核镜像的情况下,使用内核模块实现“及时”的调度时间片超时事件上报

一、背景 之前的博客 不修改内核镜像的情况下&#xff0c;使用内核模块实现高效监控调度时延-CSDN博客 里&#xff0c;我们讲了不修改内核镜像高效监控每次的调度时延的方法。这篇博客里&#xff0c;我们对于调度时间片也做这么一个不修改内核镜像的改进。关于调度时间片过长的…

机器视觉中的单线程、多线程与跨线程:原理与应用解析

在机器视觉应用中&#xff0c;程序的运行效率直接影响到系统的实时性和稳定性。随着任务复杂度的提高&#xff0c;单线程处理往往无法满足高性能需求&#xff0c;多线程技术因此被广泛应用。此外&#xff0c;跨线程操作&#xff08;如在多线程中更新界面或共享资源&#xff09;…

喜报 | 擎创科技入围上海市优秀信创解决方案

近日&#xff0c;由上海市经信委组织的“2024年上海市优秀信创解决方案”征集遴选活动圆满落幕&#xff0c;擎创科技凭借实践经验优秀的《擎创夏洛克智能预警与应急处置解决方案》成功入选“2024年上海市优秀信创解决方案”名单。 为激发创新活力&#xff0c;发挥标杆作用&…

linux-21 目录管理(一)mkdir命令,创建空目录

对linux而言&#xff0c;对一个系统管理来讲&#xff0c;最关键的还是文件管理。那所以我们接下来就来看看如何实现文件管理。当然&#xff0c;在文件管理之前&#xff0c;我们说过&#xff0c;文件通常都放在目录下&#xff0c;对吧&#xff1f;所以先了解目录&#xff0c;可能…