网上在线学习平台设计与实现:Spring Boot+Vue+MySQL部署与JWT改造实战
2026/9/23 10:53:16 网站建设 项目流程

简介:一份面向高校毕业设计或课程设计的Java在线学习平台完整项目,含前端页面、后端业务逻辑、MySQL数据库脚本、说明文档与LW论文文档,覆盖学生注册实名认证、教师作业发布批改、学习资源管理、个人信息管理、资源上传下载等核心功能。开发环境为JDK 1.8、MySQL 5.7,支持Eclipse与IDEA运行。资源包共2000个文件,约69.85MB,以Java源码、JSP动态页面、JS前端逻辑、CSS样式及XML配置为主,同时包含SQL数据库脚本、项目说明文档、系统截图PNG图片等,便于对照学习与二次开发。已有56人学习下载,适合对Servlet/JSP或SSM技术栈有基础、需要完整项目参考的开发者。压缩包内不仅提供可运行的前后端代码,还附带数据库设计SQL、部署说明文档和LW文档,项目结构清晰,便于快速理解在线教育平台的实现思路,也可直接用于答辩展示或毕业设计功能扩展。

1. 拿到这套“网上在线学习平台”源码后,先别急着跑:先看懂它到底能毕业还是能上线

把一个 zip 解压、导入 IDEA、启动 MySQL、看到登录页,这个流程我重复过很多次。真正的问题是:网上在线学习平台设计和实现这种课程设计/毕业设计型项目,它到底是一个能交作业的完整前后端,还是一个能扛住真实用户访问的系统。标题里写了“完整前后端 + mysql + 说明文档 + LW”,这四个关键词决定了交付物的边界,也决定了你接手后要做的第一件事——先判断这套代码值不值得你花时间跑通,还是只需要把说明文档里的截图补上就能提交。我的建议很直接:无论你是为了课程设计、求职项目经验还是想二次开发,先花十五分钟把目录结构和数据库脚本看完,再决定下一步。这十五分钟能帮你避开后面至少三小时的排错。

2. 前后端与技术栈拆解:在线学习平台最少要几张表、几个接口才算完整

拿到这套源码后,第一件事不是启动它,而是搞清楚它长什么样。解压后你通常能看到一个典型的教育管理系统目录:前端工程、后端工程、数据库脚本(.sql 文件)、说明文档和 LW(论文/设计报告)。这类项目在 Java 就业市场和高校课程设计里出现频率极高,绝大多数基于 Spring Boot + Vue + MySQL 开发,选这条技术栈的原因有三个:一是 Java 生态的招聘需求量大,二是 Spring Boot 让后端工程化门槛降低,三是前端 Vue 组件化开发速度快,能在一个学期内完成从选题到答辩的全流程。

2.1 从目录结构判断项目的完整度

不会看目录结构,就容易在启动阶段栽跟头。以常见的 Spring Boot + Vue 前后端分离项目为例,后端目录的核心逻辑是按层级划分的:controller 层负责接收 HTTP 请求,service 层处理业务逻辑,mapper 或 dao 层负责与 MySQL 交互,entity 或 pojo 层映射数据表结构。前端部分通常是一个独立的 Vue 工程,内部包含 views(页面组件)、router(路由配置)、api(接口封装)和 utils(工具类)。

打开这份源码时,检查四个关键位置:后端有没有 pom.xml,前端有没有 package.json,根目录有没有 .sql 文件,说明文档里有没有写清楚默认端口和数据库密码。这四个文件只要有缺失,你就该有心理准备——要么作者上传时漏了文件,要么它本就不是一套“完整”的源码。

2.2 数据库设计:网上在线学习平台的核心表结构

一个在线学习平台,功能再怎么包装,绕不开三类核心业务:用户管理、课程学习和考试评测。对应到数据库层面,至少需要六张表。

user(用户表)是基础,字段通常包含 id、username、password、real_name、role(区分学生、教师、管理员)、avatar 等字段;course(课程表)存储课程名称、封面图、简介、教师 id;course_video(课程视频表)或 chapter(章节表)用于挂载具体的视频资源;exam(试卷表)和 question(试题表)组成了考试模块,题目表至少要有题干、选项、正确答案、分值、所属试卷 id。如果项目里还有 banner、notice 这类扩展表,说明作者做了一些界面展示上的冗余。

判断这套数据库设计是否合格的标准很简单:业务上“学生选课”这个动作有没有对应的表结构支撑。如果只有 user 表和 course 表,中间缺少选课关系表(如 course_ref 或 user_course),那么课程学习功能就是假的,只是把课程列出来让学生看个列表而已。

2.3 后端接口设计:Controller-Service-Mapper 的分工逻辑

这套代码的接口设计通常遵循 RESTful 风格或半 RESTful 风格,例如/api/user/login/api/course/list/api/exam/submit。Controller 只负责接收参数和返回结果,Service 层承载业务规则,Mapper 层执行 SQL。这种层层递进的分层模式,是我在接手任何 Java 项目时都会先确认的事:如果你的代码里 Controller 直接写 SQL,那么这个项目基本没有太多框架规范可言。

在启动这套项目之前,把后端 application.properties 或 application.yml 里的配置项过一遍:端口号(默认 8080)、数据库连接 url、用户名、密码、MyBatis 的 mapper-locations 路径、日志级别。重点检查数据库 url 中的时区参数,serverTimezone=Asia/Shanghai必须写,否则连接 MySQL 8.0 时会报错。

3. 数据库导入与后端启动:用 MySQL 8.0 跑通 Spring Boot 的详细步骤

网上在线学习平台这类项目的核心是数据库,后端只有和数据库连上才有意义。很多初学者翻车的地方不在代码本身,而是 MySQL 没装对、SQL 文件没导入或者配置文件里的密码没改成自己的。这一章我把每一步写清楚,你照着执行就能把后端跑起来。

3.1 环境准备:JDK、Maven 和 MySQL 版本对齐

先确认机器的 Java 环境。如果是 JDK 8,后端代码大概率用的就是 Spring Boot 2.x;如果是 JDK 17,那代码可能基于 Spring Boot 3.x。查看 pom.xml 里 spring-boot-starter-parent 的版本号,如果是 2.7.x,就用 JDK 8 或 JDK 11;如果是 3.x,就必须 JDK 17。版本对不上,项目启动时会出现org/springframework/boot/springframework相关的NoClassDefFoundError或者编译直接失败。

MySQL 的版本选择上,包里的 sql 文件若包含ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,MySQL 5.7 和 8.0 都能正常导入。我一般推荐直接装 MySQL 8.0,因为 8.0 的 utf8mb4 默认字符集更完整,存储 emoji 表情不会乱码。注意:如果你用的是 MySQL 8.0,驱动必须用com.mysql.cj.jdbc.Driver,而不是 5.7 时代的com.mysql.jdbc.Driver;配置文件里加不加serverTimezone,启动时的区别会非常明显。

3.2 导入 SQL 文件:命令行导入比图形工具更直观

MySQL 装好后,先创建数据库再导入数据。打开命令行,执行:

mysql -u root -p

输入密码后,依次执行以下语句:

CREATE DATABASE IF NOT EXISTS online_learning DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE online_learning; SOURCE D:/path/to/online_learning.sql;

逻辑说明:第一步创建数据库,指定字符集为 utf8mb4,避免导入中文时出现乱码;第二步切换当前库;第三步执行 SQL 文件里的建表语句和初始数据。SOURCE 后面跟的是你本机 sql 文件的绝对路径,路径中避免出现中文和空格。

验证导入是否成功的办法是查一下表数量:

SHOW TABLES;

如果输出的表和你之前在 2.2 节预判的几张表能对上,说明 sql 文件基本正常。如果提示Unknown databaseTable already exists,检查是不是之前导过同名库,直接 DROP 掉重来一遍即可。

注意:SQL 文件中如果包含删除数据库的语句,比如DROP DATABASE IF EXISTS online_learning;,执行前仔细看清楚,别把自己的数据误删了。

3.3 修改后端配置文件并启动

打开后端工程的src/main/resources/application.yml(或 application.properties),把数据源部分改成你自己的配置:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/online_learning?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true

参数说明:

  • 3306是 MySQL 默认端口,如果你安装时改过端口,这里要对应修改。
  • serverTimezone=Asia/Shanghai解决 mysql 8.0 的时区问题,不加会报The server time zone value'�й���ʱ��' is unrecognized
  • map-underscore-to-camel-case设置为 true,让数据库的user_name能自动映射到实体的userName字段,省去手动写 ResultMap 的麻烦。

启动方式有两种。第一种用 IDEA 打开后端项目,等 Maven 把依赖下载完,直接运行主类里带@SpringBootApplication注解的 main 方法。第二种用命令行:

mvn spring-boot:run

看到Started Application in x.xxx seconds的日志,并且没有抛异常,后端就启动成功了。用浏览器访问http://localhost:8080/api/user/list之类的接口,如果返回 JSON 数据,说明后端和 MySQL 已经正常连通。

3.4 后端启动失败时怎么定位问题

我给你一套自己的排查顺序:先看控制台最下面三行,是数据库连接失败、端口被占用还是 mapper 文件解析报错。数据库连接失败,检查密码、用户名、URL 的端口号三个位置是否有误;端口被占用,把 8080 改掉就行;MyBatis 相关报错,大概率是 mapper 文件里的 namespace 或 resultType 与实体类路径不一致。

这套排查顺序能解决八成启动失败的问题,剩下的两成通常和 Maven 依赖下载不完整或代码本身缺漏有关,需要打开报错堆栈逐行分析。

4. 前端跑通与前后端联调:Vue 项目的启动、代理配置和接口调通验证

后端接口已经通了,但只靠 Postman 或浏览器访问接口,你只能得到一串 JSON。要让整套网上在线学习平台真正“能用”,还得把前端启动起来,并且让前端页面能请求到后端数据。

4.1 前端依赖安装:npm install 之后才能启动

前端项目目录下通常能看到package.json,这就是 Vue 工程的家底。打开看 dependencies 部分:

"dependencies": { "axios": "^0.24.0", "element-ui": "^2.15.6", "vue": "^2.6.11", "vue-router": "^3.4.2" }

参数说明:

  • vue版本是 2.x 还是 3.x,决定了写法的兼容性。如果是 Vue 2,那配套的 UI 库基本是 Element UI;Vue 3 对应的则是 Element Plus。
  • axios是前端发起 HTTP 请求的库,注意看它的版本,不同大版本 API 基本一致。
  • vue-router的 3.x 对应 Vue 2,4.x 对应 Vue 3,装错版本会出现Cannot read property 'prototype' of undefined这类报错。

确认了依赖之后,在项目根目录执行:

npm install

这一步是把 package.json 里声明的依赖包全部下载到 node_modules 目录。如果网络状况不理想,可以用淘宝镜像源:

npm config set registry https://registry.npmmirror.com npm install

npm install 过程可能持续几分钟,看到added xxx packages in xx s且没有红色报错就算安装完成。如果出现node-sass相关的编译错误,后面第 5 章会专门现象级讲解。

提示:npm install 第一次慢是正常的,耐心等;如果中断,删除 node_modules 目录重新 install 往往比续跑靠谱。

4.2 配置开发代理:解决跨域问题的标准姿势

前端跑在 8081 或 3000 端口,后端跑在 8080 端口,直接发请求会被浏览器跨域策略拦截。解决这个问题最干净的方式不是在写后端的人那边加@CrossOrigin或配置 CorsFilter,而是让前端开发服务器把请求代理给后端。

在 Vue 项目的根目录下,找到vue.config.js(如果没有这个文件就新建一个):

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };

参数说明:

  • port: 8081指前端开发服务器监听在 8081 端口。
  • proxy['/api']表示,凡是以/api开头的请求,都转发到target指向的后端地址。
  • changeOrigin: true会让后端收到的请求头 Host 变成 target 的地址,有些项目后端配置严谨的话,这个参数必须为 true。

配置完成后,启动前端:

npm run dev

看到App running at: http://localhost:8081的输出说明前端已经跑起来了。浏览器打开这个地址,会默认跳转到登录页。

4.3 联调验证:从登录请求到数据渲染

前端的 API 封装文件里(通常是src/api/login.jssrc/api/user.js),会看到用 axios 发送的请求:

import request from '@/utils/request' export function login(data) { return request({ url: '/api/user/login', method: 'post', data: data }) }

这里的/api/user/login并不会直接请求到 8080 端口,而是先发给前端开发服务器(8081),再由 devServer 的 proxy 配置转发到http://localhost:8080/api/user/login。验证联调是否成功,有两条路径:一是看浏览器开发者工具 Network 面板,登录请求返回 200 并且有 JSON 响应体;二是看 IDEA 控制台,后端打印出请求日志。如果 Network 里请求的状态码是 302 或 404,优先检查 proxy 的 target 是否写对、后端端口是否真的在 8080。

4.4 一份联调自检清单

联调过程容易扯皮的问题,我这里直接列给你,照着检查就行:

登录接口返回 JSON 但页面提示账号密码错误,检查前端是否对密码做了 MD5 加密而后端没有——这类加密格式不对称的问题在两套独立代码里很常见。课程列表渲染不出来,检查浏览器 Console 是 404 还是 500;404 大概率是接口路径写错,500 大概率是后端 SQL 或数据库字段不匹配。页面完全空白且 Console 无报错,排查src/main.js是否正确 import 了 Vue 实例和路由配置。

5. 项目踩坑与故障排查:五个常见问题,从报错到解决

把 Java 后端和 Vue 前端跑通这件事,本地环境不同会遇到不同的报错。我按出现频率排序,把自己遇到过并且亲手解决的五个问题完整记录下来,每一条都是现象、原因、解决三步走,照着排查基本能解决九成的问题。

5.1 MySQL 8.0 驱动报错 ClassNotFound

现象:启动后端时控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,或者链接数据库时提示Public Key Retrieval is not allowed

原因:pom.xml 里引入的 MySQL 依赖版本是 5.x,driver-class-name 写的还是旧的com.mysql.jdbc.Driver。MySQL 8.0 把驱动类改名了,旧驱动已经不兼容。而Public Key Retrieval is not allowed是因为 MySQL 8.0 默认使用 caching_sha2_password 认证方式,JDBC 连接时没有显式允许公钥检索。

解决:把 pom.xml 里的依赖改成 8.0.x,并且配置文件中的 driver-class-name 改成com.mysql.cj.jdbc.Driver。同时在数据源的 URL 后追加allowPublicKeyRetrieval=trueuseSSL=false

url: jdbc:mysql://localhost:3306/online_learning?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false

这条解决了就能连上,再不行就把 MySQL 用户的认证插件改回 mysql_native_password:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;

5.2 数据库时间差 8 小时的问题

现象:前端页面展示的课程创建时间比实际时间少 8 个小时。

原因:MySQL 连接字符串里没有指定 serverTimezone,JDBC 驱动会取 JVM 默认时区,而你的 JVM 和 MySQL 服务器时区不一致,导致时间在转换过程中被偏移。

解决:在 datasource 的 URL 上显式加上serverTimezone=Asia/Shanghai。如果项目里用了 Jackson 做 JSON 序列化,再检查 application.yml 里有没有配:

spring: jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss

这两个配置是配套的,只改一个可能会出现数据库存对了、前端展示又偏移回来的情况。

5.3 前端请求接口跨域失败

现象:浏览器 Console 出现Access to XMLHttpRequest at 'http://localhost:8080/api/...' from origin 'http://localhost:8081' has been blocked by CORS policy

原因:前后端分离环境下,前端调试用的 8081 端口请求 8080 端口属于跨域。解决方式有两种,后端加全局 CORS 配置,或者前端 devServer 配 proxy。我更推荐在后端加 CORS 配置,这样部署到服务器后依然有效。写法:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

参数说明:

  • allowedOriginPatterns("*")放行所有来源,注意allowCredentials(true)时不能直接用allowedOrigins("*"),否则会报异常。
  • 配置位置放在主启动类能扫描到的包路径下,或者直接放在启动类同级,确保被 Spring Boot 自动装配。

5.4 Vue 项目 npm install 时报 node-sass 编译错误

现象:npm install 时出现gyp ERR! build error或者Module build failed: Error: Node Sass version 7.0.0 is incompatible with ^4.0.0

原因:node-sass 是一个依赖于编译步骤的库,Node 版本升级后,node-sass 的预编译二进制文件找不到对应版本,只能现场编译,而现场编译依赖 Python 和 C++ 编译环境。很多课程设计项目为了省事直接弃坑换成 sass(Dart Sass)了,但没有同步调整代码里的引入方式。

解决:先删掉 node_modules 和 package-lock.json,然后执行:

npm uninstall node-sass npm install -D sass

代码里原来的@import "xxx"写法保持一致,不用改。这是最快的方案。如果用 NVM 管理 Node 版本,也可以直接把 Node 降到项目适用的版本(比如 Vue 2 项目用 Node 14 或 16),再执行 npm install,不过我更推荐直接换 sass,一劳永逸。

5.5 图片上传后访问 404

现象:教师头像、课程封面上传成功后,前端访问图片地址返回 404。

原因:Spring Boot 默认不会把本地磁盘目录映射为静态资源路径。项目里上传文件保存到了类似 D:/upload/ 的目录,但访问路径是/files/xxx.jpg,后端没有写对应的映射处理器。

解决:在 Spring Boot 里加一个静态资源配置:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Value("${file.upload-dir}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceLocations("file:" + uploadDir); } }

参数说明:

  • /files/**表示 URL 中以 /files/ 开头的请求会被转发到本地目录。
  • file:前缀不能省略,它告诉 Spring Boot 这是一个文件系统路径而不是 classpath 路径。
  • uploadDir在 application.yml 里配置为D:/upload/,注意末尾要有/

这个配置在很多项目里是缺失的,解决起来不难,但不知道这个机制的话会白白耗费一两个小时。

6. 在脚手架基础上升级:把传统 Session 登录改造成 JWT 认证的实战技巧

跑通整套源码只是第一步,如果你想把这个网上在线学习平台写进简历,或者想在答辩时讲出亮点,最值得做的一处改造就是把登录模块的技术债还掉。这类课程设计项目里,后端登录控制器的实现千篇一律:登录成功往 Session 里塞一个用户对象,再用拦截器检查 Session 是否为 null。但对于前后端分离项目,Session 方案并不理想,移动端和后端分离时无法跨域共享 Session,扩展集群时 Session 同步也要额外配置。用 JWT(JSON Web Token)替换 Session 登录,是面试官最容易认可的改造方向,也是实际企业级项目的主流做法。

6.1 JWT 登录的核心思路与依赖引入

JWT 的思路是:用户登录成功后,服务端生成一个带签名的 token 字符串返回给前端;前端把 token 存在 localStorage 或 Vuex 里,后续请求在请求头加上Authorization: token;后端通过拦截器解析 token,从 token 里取用户 id 和角色,不再依赖 Session。

首先在 pom.xml 引入 JJWT 依赖:

<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>

逻辑说明:jjwt-api 是编译期依赖的 API,jjwt-impl 和 jjwt-jackson 是运行期的实现和 JSON 解析器,scope 设置为 runtime 即可。这里选择的 0.11.5 是常用稳定版本,如果你的项目 Spring Boot 版本较旧,这个依赖依然兼容。

6.2 实现 JWT 工具类和登录改造

先写一个 JwtUtil 类,负责生成和解析 token:

@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(Integer userId, String role) { Date now = new Date(); return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(now) .setExpiration(new Date(now.getTime() + expiration)) .signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .compact(); } public Integer getUserIdFromToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) .getBody(); return Integer.parseInt(claims.getSubject()); } public Boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }

参数说明:

  • secret是签名密钥,长度至少 32 字节,配置在 application.yml 中。
  • expiration是 token 有效期,比如 86400000 就是 24 小时。
  • setSubject存入用户 id,claim("role")额外放入用户角色,因为学习平台里学生和教师看到的接口权限不同。

然后在登录接口里,验证密码正确后不再往 Session 存对象,改成返回 token:

@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { User user = userService.login(loginDTO.getUsername(), loginDTO.getPassword()); if (user == null) { return Result.error("账号或密码错误"); } String token = jwtUtil.generateToken(user.getId(), user.getRole()); return Result.success(token); }

6.3 拦截器解析 token 与前端携带请求头

新增一个 JwtInterceptor 拦截器:

@Component public class JwtInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); if (jwtUtil.validateToken(token)) { Integer userId = jwtUtil.getUserIdFromToken(token); request.setAttribute("userId", userId); return true; } } response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } }

注册拦截器到 WebMvcConfig:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private JwtInterceptor jwtInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register"); } }

注意,拦截器排除了登录和注册接口,其余/api/**下的所有请求都必须携带有效 token。前端 axios 请求的封装也要对应修改:

request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config })

到这一步,你的改造就完成了。可以在浏览器里验证:登录后 localStorage 能拿到 token,刷新页面状态依然保持,后端接口请求头里带着 Authorization,未登录时访问接口返回 401 而不是 302 重定向。做完这些,整个网上在线学习平台的设计和实现就从交作业级别提升到了贴近企业项目的程度。

最后说一点我的切身习惯:拿到任何一份新代码,我先改造成本最低、收益最高的安全模块,再谈业务功能的二次开发。第一次改造 JWT 时我也踩过密钥长度不足和 token 过期未处理的坑,后来形成了一套自己的固定写法才稳定下来。希望这篇能帮你把项目跑通的同时,把登录认证这条线做扎实,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询