从零搭建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.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/base_manage?serverTimezone=UTCusername: rootpassword: 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.StdOutImplglobal-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 {/*** 添加分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbTypereturn 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.htmltags-sorter: alphaoperations-sorter: alphaapi-docs:path: /v3/api-docsgroup-configs:- group: 'default'paths-to-match: '/**'packages-to-scan: com.buzhisuoyun.base_manage# knife4j 配置
knife4j:# 是否启用增强enable: true# 开启生产环境屏蔽production: false# 是否认证登录basic:# basic是否开启,默认为falseenable: trueusername: knife4jpassword: knife4jsetting:language: zh_cnenable-version: trueenable-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 {@Beanpublic 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 {@Autowiredprivate 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;}@Overridepublic 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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取tokenString token = request.getHeader("Authorization");// 验证tokentry {Map<String, Object> claims = JwtUtil.parseToken(token);// 存储业务信息到线程变量ThreadLocalUtil.set(claims);// 放行拦截return true;} catch (Exception e) {// 登录信息异常或未登录,http响应状态码为401response.setStatus(401);// 拦截请求return false;}}@Overridepublic 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 {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic 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) => {// 请求前回调// 添加tokenconst tokenStore = useTokenStore()// 判断有无tokenif (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) returnconsole.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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大文件上传服务-后端V1V2

文章目录 大文件上传概述:minio分布式文件存储使用的一些技术校验MD5的逻辑 uploadV1 版本 1uploadv2 版本 2 大文件上传概述: 之前项目做了一个文件上传的功能,最近看到有面试会具体的问这个上传功能的细节&#xff0c;把之前做的项目拿过来总结一下&#xff0c;自己写的一个…

【机器学习】鲁棒(健壮)回归-Theil-Sen估计(Theil-Sen Estimator)

Theil-Sen估计 Theil-Sen估计是一种用于线性回归的非参数方法&#xff0c;其优点是对离群点具有鲁棒性。它通过计算数据点之间所有可能斜率的中位数来估计回归线的斜率&#xff0c;随后使用这些斜率估算截距。 核心思想 斜率估计&#xff1a; 对于给定的一组数据点 &#xff0…

配置Kubernetes从节点与集群Calico网络

在上一篇博客中&#xff0c;我们成功安装并初始化了Kubernetes的主节点&#xff0c;并且看到了集群初始化成功的标志信息。接下来&#xff0c;我们将继续安装从节点&#xff08;worker nodes&#xff09;&#xff0c;以构建一个完整的Kubernetes集群。 步骤回顾 在上一步中&a…

【数据库】MySQL数据库SQL语句汇总

目录 1.SQL 通用语法 2.SQL 分类 2.1.DDL 2.2.DML 2.3.DQL 2.4.DCL 3.DDL 3.1.数据库操作 3.1.1.查询 3.1.2.创建 3.1.3.删除 3.1.4.使用 3.2.表操作 3.2.1.查询 3.2.2.创建 3.2.3.数据类型 3.2.3.1.数值类型 3.2.3.2.字符串类型 3.2.3.3.日期时间类型 3.2…

做跨境电商服务器用什么宽带好?

做跨境电商服务器用什么宽带好&#xff1f;做跨境电商服务器&#xff0c;推荐选择光纤宽带或高性能的5G网络。光纤宽带高速稳定&#xff0c;适合处理大量数据和实时交互&#xff1b;5G网络则提供超高速移动连接&#xff0c;适合需要灵活性和移动性的卖家。具体选择需根据业务规…

光谱相机的光谱分辨率可以达到多少?

多光谱相机 多光谱相机的光谱分辨率相对较低&#xff0c;波段数一般在 10 到 20 个左右&#xff0c;光谱分辨率通常在几十纳米到几百纳米之间&#xff0c;如常见的多光谱相机光谱分辨率为 100nm 左右。 高光谱相机 一般的高光谱相机光谱分辨率可达 2.5nm 到 10nm 左右&#x…

Python毕业设计选题:基于django+vue的智能租房系统的设计与实现

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 租客注册 添加租客界面 租客管理 房屋类型管理 房屋信息管理 系统管理 摘要 本文首…

[Qualcomm]Qualcomm MDM9607 SDK代码下载操作说明

登录Qualcomm CreatePoing Qualcomm CreatePointhttps://createpoint.qti.qua

PE文件:节表-添加节

在所有节的空白区域都不够存放我们想要添加的数据时&#xff0c;这个时候可以通过添加节来扩展我们可操作的空间去存储新的数据&#xff08;如导入表、代码或资源&#xff09;。 过程步骤 1.判断是否有足够的空间添加节表 PE文件的节表紧跟在PE头之后&#xff0c;每个节表的…

图论的起点——七桥问题

普瑞格尔河从古堡哥尼斯堡市中心流过&#xff0c;河中有小岛两座&#xff0c;筑有7座古桥&#xff0c;哥尼斯堡人杰地灵&#xff0c;市民普遍爱好数学。1736年&#xff0c;该市一名市民向大数学家Euler提出如下的所谓“七桥问题”&#xff1a; 从家里出发&#xff0c;7座桥每桥…

Fabric区块链网络搭建:保姆级图文详解

目录 前言1、项目环境部署1.1 基础开发环境1.2 网络部署 2、后台环境2.1、环境配置2.2、运行springboot项目 3、PC端3.1、安装依赖3.2、修改区块链网络连接地址3.3、启动项目 前言 亲爱的家人们&#xff0c;创作很不容易&#xff0c;若对您有帮助的话&#xff0c;请点赞收藏加…

02JavaWeb——JavaScript-Vue(项目实战)

一、JavaScript html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习 JavaScript&#xff0c;这门语言会让我们的页面能够和用户进行交互。 1.1 介绍 通过JS/js效果演示提供资料进行效果演…

Windows 蓝牙驱动开发-蓝牙设备栈

蓝牙设备栈 蓝牙驱动程序堆栈包含 Microsoft 为蓝牙协议提供支持的核心部分。 有了这个堆栈&#xff0c;已启用蓝牙的设备可以彼此定位并建立连接。 在此类连接中&#xff0c;设备可以通过各种应用程序交换数据并彼此交互。 下图显示了蓝牙驱动程序堆栈中的模块&#xff0c;以…

GPU 硬件原理架构(一)

这张费米管线架构图能看懂了&#xff0c;整个GPU的架构基本就熟了。市面上有很多GPU厂家&#xff0c;他们产品的架构各不相同&#xff0c;但是核心往往差不多&#xff0c;整明白一了个基本上就可以触类旁通了。下面这张图信息量很大&#xff0c;可以结合博客GPU 英伟达GPU架构回…

CSS布局与响应式

学习链接 Grid网格布局 前端五大主流网页布局 flex布局看这一篇就够了 grid布局看这一篇就够了 用六个案例学会响应式布局 伸缩盒响应式页面布局实战 实现响应式布局的五种方式 - csdn 如何完成响应式布局&#xff0c;有几种方法&#xff1f;看这个就够了 响应式布局总…

大疆最新款无人机发布,可照亮百米之外目标

近日&#xff0c;DJI 大疆发布全新小型智能多光旗舰 DJI Matrice 4 系列&#xff0c;包含 Matrice 4T 和 Matrice 4E 两款机型。DJI Matrice 4E 价格为27888 元起&#xff0c;DJI Matrice 4T价格为38888元起。 图片来源&#xff1a;大疆官网 DJI Matrice 4E DJI Matrice 4T D…

Nmap入门

- 在已有的参数上加上哄骗或是使用文件 nmap -iL data.txt ------- nmap -PS -PA -O -ttl 200 -F -D dcay1, dcay2,dcay3... -vv -P 3306 1.1.1.0/24 -oN data.txtNmap端口的6个状态 open 应用程序正在该端口接收TCP或UDP报文 closed 关闭的端口对于Nmap也是可访问的(它接受…

ubuntu18.04开发环境下samba服务器的搭建

嵌入式linux的发展很快&#xff0c;最近准备在一个新项目上采用新一代的linux核心板&#xff0c;发现linux内核的版本已经更新到5.4以上甚至6.0以上&#xff1b;之前常用的linux内核版本是2.6.4&#xff0c;虽然在某些项目上还能用但是明显跟不上时代的步伐了&#xff0c;所以要…

计算机网络速成

前言&#xff1a;最近在做一些动态的crypto&#xff0c;但是配置总搞不好&#xff0c;正好也有学web的想法&#xff0c;就先学学web再回去做密码&#xff0c;速成视频推荐b站建模老哥 目录 计算机网络概述网络的范围分级电路交换网络&#xff08;电路交换&#xff09;报文交换网…

【React】静态组件动态组件

目录 静态组件动态组件创建一个构造函数(类)使用 class 实现组件**使用 function 实现类组件** 静态组件 函数组件是静态组件&#xff1a; 组件第一次渲染完毕后&#xff0c;无法基于内部的某些操作让组件更新「无法实现自更新」&#xff1b;但是&#xff0c;如果调用它的父组…