☰
SpringBoot+Vue+MyBatis实现前后端分离高校教学管理系统
2026/10/9 2:30:26 网站建设 项目流程

前后端分离的大学计算机基础网络教学系统,用SpringBoot+Vue+MyBatis+MySQL这套组合来落地,是很多计算机类专业学生和初入行的开发者在找实战项目时最常遇到的需求。这个系统要解决的是高校计算机基础课程教学管理中一堆琐碎问题:课件和视频怎么发、作业怎么收、考试怎么安排、成绩怎么统计。如果还靠U盘拷课件、Excel收作业,老师和学生都被折腾得够呛,所以一个完整的线上教学管理系统就成了刚需。下面我把整个项目的设计思路、数据库结构、前后端实现和部署流程拆开讲一遍,给想复现或者改造成课程设计的人一个能直接抄作业的参考。

这个项目的前端是Vue,后端是SpringBoot,数据库用MySQL,MyBatis负责数据访问。它最大的特点是前后端完全分离,后端只提供接口,前端只关心渲染,用户身份分为学生、教师、管理员三种,覆盖了选课、学习、作业、考试、成绩这些核心教学场景。适合的人群很广:准备做毕业设计或课程设计的学生、想上手前后端分离项目实战的后端开发者、以及学校或培训机构需要一套轻量教学管理系统的团队。

1. 动手前的整体规划:架构拆分与技术选型

1.1 前后端分离不是炫技,是这几点刚需

以前很多教学系统是用JSP加后端模板渲染的写法,页面逻辑和后端代码搅在一起。这次项目直接用前后端分离,不是跟风,而是有几个非常实际的考虑。

第一,并行开发效率高。前端和后端只需要约定好接口文档,就可以各写各的。实际开发中前端被后端页面卡死的情况很常见:后端改一个字段,前端要等重新部署才能看到效果;而前后端分离之后,后端接口只要联调一次,前端自己用Mock数据就能先跑起来。

第二,接口可以复用。教学系统往往不只是网页端在用,学校可能还会要求手机浏览器适配、校内服务号引流,甚至以后要对接教务系统。后端把接口设计成纯API,未来任何客户端都能直接复用,不需要改业务逻辑。这是我特别看重的一点,因为改业务逻辑的成本往往比新写一个接口还高。

第三,部署更灵活。前端构建出来是一堆静态文件,扔到任意Web服务器就行;后端是一个独立进程。前端崩了不影响后端数据服务,后端做版本升级也可以不停前端服务。前后端各自的发布节奏是独立的,这在团队协作里非常友好。

前后端分离的实际代价是增加了一次HTTP请求的拆分工作:跨域、Token认证、接口联调这些都需要额外处理。这也是我在第5章会重点展开部署和排错的原因。很多人觉得跨域难,其实本质就是浏览器按"协议+域名+端口"判断是不是同一个来源,不是同一个来源就拦,解决方案不外乎后端开CORS、前端走代理、生产环境用Nginx转发这几种。

1.2 技术栈选型的4个关键决定

这里先放一张我最终敲定的技术栈清单,然后逐个说明为什么这么选。

层次选型版本建议
后端框架SpringBoot2.7.x(配合JDK8)
前端框架Vue3 + Vite + Element PlusNode 16.20+
数据访问MyBatis(可用MyBatis-Plus增强)3.x
数据库MySQL8.0(5.7也可)
认证方案JWTjjwt 0.11.x
权限模型RBAC自定义注解 + 拦截器

第一个关键决定是SpringBoot版本。如果是新学,不要盲目追求最新版,我建议直接用2.7.x配合JDK8。原因很现实:SpringBoot 3.x强制要求JDK17,而且包名从javax换成了jakarta,网上大量教程还是基于老写法;另外老版本插件的兼容性更好,遇到问题更容易搜到现成答案。网上经常有人问"springboot版本太高导致启动报错怎么办",多半就是配了3.x却还在用JDK8。这个坑我建议开局就避开,别跟版本较劲。

第二个是Vue版本。Vue3配Element Plus是目前的主流,Vue2配Element UI更稳定但官方维护力度已经下降。这个项目我选Vue3,但要说清楚一点:Vue3的安装和项目创建方式与Vue2区别不小,尤其是脚手架从Vue CLI换成了Vite,配置文件从vue.config.js变成了vite.config.js,后面第4章专门讲环境配置,照着做就行。

第三个是MyBatis还是MyBatis-Plus。从学习角度,纯MyBatis可以让你搞清楚SQL映射到底是怎么回事,面试也爱问MyBatis的动态SQL、缓存机制;从开发效率角度,MyBatis-Plus的BaseMapper直接用内置方法,能省大量简单CURD代码。我的建议是:如果这是课程设计或毕业设计,直接上MyBatis-Plus,交源码时说明底层还是MyBatis;如果是为了突击面试,亲手写一份Mapper XML会更有底气,因为面试官总喜欢追问底层细节。

第四个是MySQL版本。MySQL 8.0是现在的主流,功能更强,但安装时有几个坑:初始化账号密码、时区、默认认证插件caching_sha2_password连老版本驱动会报错。这些在常见问题表里我都写了解决方案。如果本地实在装不上8.0,用5.7.44也完全能跑,只是驱动和连接串稍有不同。SpringBoot 2.7.x自带的数据库驱动兼容MySQL 5.7和8.0,主要是在连接串上加serverTimezone=Asia/Shanghai避免时区报错。

2. 功能模块与数据库设计:教学业务怎么落到表上

2.1 三类用户与权限模型怎么落地

教学系统的核心不是技术,是权限边界。学生、教师、管理员这三种角色看到的内容和能做的操作完全不同,所以权限模型要最先设计,反复琢磨都不为过。

我采用的是经典RBAC模型:用户、角色、用户角色关联。相比直接在用户表里写一个role字段,RBAC的优势是以后想加一个"助教"或者"教务秘书"角色时,不用改表结构,加一条角色记录再加关联关系就够了。说白了,RBAC就是把"用户和权限"的关系解耦,让权限可以组合和复配。

登录认证这块用的JWT。流程是:用户提交账号密码,后端校验通过后生成一个Token,Token里带上userId和roleCode,前端把Token存到localStorage,之后每次请求都在请求头里带Authorization字段。后端用一个拦截器解析Token,取到当前用户身份,再结合自定义注解做权限校验。

这里有个实际经验:Token里不要放敏感信息,只放userId、角色标识和过期时间,其他信息都能在数据库里查。过期时间建议设成2小时到24小时之间,太短频繁登录体验差,太长又有被盗用风险。如果要求更高,可以部署时加一层Redis做单点登录,但本项目为了保持完整源码的本地可运行性,用JWT自身过期机制足够了。密码存储一定要用BCrypt,别用MD5,后面代码部分会再次强调。

2.2 课程与教学资源模块的设计要点

课程模块是教学系统的信息主动脉。我把它拆成三级结构:课程表、章节表、资源表。这个三级结构是经过实际使用验证的,老师按周次上传资料,学生按章节浏览,非常符合计算机基础课的教学节奏。

课程表记录课程基本信息,比如课程名称、所属院系、授课教师、学期、课程简介和封面图。章节表挂到课程下面,常见的是按周次拆分,第1周、第2周这样排。资源表再挂到章节下面,每一条资源就是一份课件、一个视频链接或一份练习文档。

课件和文档的处理方式是:上传后存到服务器本地的一个upload目录,数据库里只保存文件路径。这样做的好处是源码跑起来简单,不需要额外配置对象存储服务。但有个隐患:这个方案只适合个人服务器或小范围使用,文件多了以后要迁移到云存储。迁移时只需要把upload目录整体搬走,数据库里的路径改成新地址即可,所以一开始设计路径时就要用相对路径,别写死绝对地址。

视频资源这里有个小知识点:很多课程视频是MP4格式,直接用HTML5的video标签就能播放。如果遇到m3u8这种切片视频,前端不安装任何浏览器插件也能处理,用hls.js这个库,一小段播放器代码就能搞定。后面第4章我会贴出关键代码,这个方案在实际项目里验证过,稳定且实现简单。

2.3 作业、考试与成绩闭环

作业与考试是这个系统最体现教学管理属性的部分,也是答辩演示时的重头戏。

作业模块的流程是:教师创建作业,设置截止时间;学生看到未完成列表,在线上传文档或填写答案;学生提交后教师在线批阅,给出评语和分数;系统再把分数汇总进成绩表。作业表里有一个status字段,区分已提交、已批阅、未提交、已逾期,列表页可以直接按状态筛选,体验比一个笼统的作业列表好很多。这个细节看起来小,但实际用起来差别很大。

考试模块分两种玩法。一种是纯客观题:题库表里存选择题,学生在前端作答,提交后后端逐题比对自动出分。另一种是包含主观题:客观题自动判,主观题需要教师手动给分。考虑到毕业设计演示效果,我建议考试部分至少做自动判分,因为答辩现场没法等教师慢慢批改主观题。自动判分还能顺便展示出系统对数据的高效处理能力,是个加分项。

成绩模块要注意数据来源的多样性:考试自动算出来的成绩、教师手工录入的作业分数、还有一些线下成绩需要支持手动添加。三种数据源统一汇总到一个成绩表,每个学生每门课能查出明细。分数范围用0到100校验,避免脏数据进入统计。

2.4 核心数据库表结构速览

为了不占篇幅,我列出最关键的表设计,这些表足够支撑前面说的全部功能。

表名用途关键字段
sys_user用户表id, username, password, real_name, status
sys_role角色表id, role_name, role_code
course课程表id, course_name, teacher_id, description, cover
course_chapter章节表id, course_id, chapter_name, sort
resource资源表id, chapter_id, resource_name, type, url
homework作业表id, course_id, title, content, deadline
homework_submit作业提交表id, homework_id, student_id, content, file_url, score, comment
exam考试表id, course_id, title, start_time, end_time, duration
exam_question题库表id, exam_id, type, question_content, score
exam_result考试成绩表id, exam_id, student_id, score

有个设计细节容易忘:所有表都建议加create_time、update_time两个字段,MyBatis-Plus支持字段自动填充,开发时不觉得,做数据排查和统计时非常有用。另外字符集统一用utf8mb4,别用utf8,因为utf8在MySQL里存不下所有表情符号和生僻字,将来导入数据很容易乱码。

这里再给一个核心建表SQL示例,方便直接跑:

CREATE TABLE `sys_user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(100) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `role_code` VARCHAR(20) DEFAULT NULL COMMENT '角色编码', `status` TINYINT DEFAULT 1 COMMENT '1启用 0禁用', `create_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `course` ( `id` INT NOT NULL AUTO_INCREMENT, `course_name` VARCHAR(100) NOT NULL, `teacher_id` INT NOT NULL, `description` TEXT, `cover` VARCHAR(255) DEFAULT NULL, `create_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里我把用户和角色的关联简化成了role_code字段,工程上这叫冗余设计。正规项目我会建一张关联表,课程设计里为了简化复现,用一个角色编码字段也能满足需求,各有取舍。

3. 后端SpringBoot项目:从分层到核心接口实现

3.1 项目分层与配置文件

一个清晰的项目结构能救命。我习惯这样分包:

com.example.teachingsystem ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── Result.java │ ├── JwtUtils.java │ └── GlobalExceptionHandler.java └── TeachApplication.java

controller只做参数接收和结果返回,service处理业务逻辑,mapper做数据库交互,entity对应表结构,dto是接口入参,vo是出参。很多初学者把业务逻辑写在controller里,越写越乱。这个分层习惯一旦养成,后面接手任何SpringBoot项目都不会迷路。

application.yml配置里有几个重点:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/teach_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 servlet: multipart: max-file-size: 200MB max-request-size: 200MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.teachingsystem.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-256-bit-secret-key-please-change expire-hours: 24

password这里只是示意,正式项目要用环境变量注入,绝对不能把真实密码提交到代码仓库里。map-underscore-to-camel-case这个配置特别重要,没开启的话数据库的course_name字段映射不到实体的courseName属性上,查询结果全是null,这种问题很难排查。log-impl配成StdOutImpl可以在控制台打印SQL,调试阶段非常有用,开发完建议关掉,否则日志刷屏严重。

SpringBoot默认内嵌的是Tomcat,想换成Undertow或者Jetty也可以,改一下starter依赖即可,这个属于可选配置,一般项目用默认就好。启动时控制台会打印一个SpringBoot样式的Banner,想换成自定义图案的话可以找在线Banner生成工具,改完只是启动日志花样变了,不影响功能,纯属个人趣味。

3.2 统一响应、异常处理与JWT认证

前后端交互最怕格式不统一。我定义一个Result类:

public class Result<T> { private int code; private String message; private T data; // 提供 success / error 静态方法 }

所有接口不管成功失败,都返回这个结构。前端axios拦截器里只看code,code为200就取data,其他情况统一弹错误提示。这个约定在联调阶段能省非常多沟通成本,后端改了返回格式,前端也不用跟着改。

异常处理用@RestControllerAdvice加全局异常类捕获,业务上发现参数不对或者权限不足就抛自定义异常,统一转成Result结构返回。这样就不会出现后端一报错就返回一堆英文堆栈、前端一脸懵的情况。全局异常处理器里还要捕获数据库唯一键冲突、字段过长这类常见异常,转成用户能看懂的中文提示。

JWT认证实现成两步。第一步是登录控制器,根据用户名查出用户、用BCrypt校验密码,然后调用JwtUtils生成Token。第二步是写一个拦截器,注册进WebConfig,拦截所有接口(放行登录接口)。拦截器里从Authorization头解析Token,解析成功就把userId和角色信息放到请求上下文里,方便后端业务取出当前操作人。

这里有一个我实际踩过的坑:拦截器校验Token通过后,不要只校验"是不是登录用户",还要校验"是不是有权限做这件事"。举例:一个学生拿着自己的合法Token去调教师才允许的删除课程接口,如果不做第二层校验,就会出严重的安全事故。简单做法是自定义一个@RequireRole("teacher")注解,用拦截器或AOP在进入接口前检查角色,代码侵入很小。

3.3 MyBatis实战:动态SQL、缓存与SQL打印

MyBatis是这套系统里技术含量最高也最容易被面试官追问的部分。

第一要掌握的是动态SQL。典型场景是课程列表的模糊搜索,可能有课程名、教师、学期三个条件。用<where>标签配合<if>标签,能根据参数自动拼接查询条件,避免用字符串拼接SQL导致SQL注入。这里注意一个细节:在XML文件里如果用到小于号<、大于号>,必须写成&lt;和&gt;,否则解析会报错,这就是网上说的"MyBatis等于/小于号怎么写"一类问题的由来。

<select id="selectCourseList" resultType="com.example.teachingsystem.vo.CourseVO"> SELECT * FROM course <where> <if test="keyword != null and keyword != ''"> AND course_name LIKE CONCAT('%', #{keyword}, '%') </if> <if test="teacherId != null"> AND teacher_id = #{teacherId} </if> </where> </select>

第二是缓存。MyBatis有一级缓存和二级缓存。一级缓存默认开启,是SqlSession级别的;二级缓存需要手动开启,是Mapper级别的。这里有一个大坑:在同一个事务里第一次查询某条课程记录,然后其他会话修改了这条记录,一级缓存可能返回旧数据。对实时性要求高的接口,比如查询考试剩余时间,就不要依赖缓存。面试里问到MyBatis缓存,记住一句核心总结:一级缓存是会话范围,二级缓存是命名空间范围,还要说清楚缓存失效的场景,这个问题才算答完整。

第三是SQL打印。在application.yml里配置log-impl为StdOutImpl后,控制台会输出完整的SQL语句和查询参数。很多调半天不知道SQL出了什么问题的场景,一看日志就明白了。比如动态SQL条件没生效,打印出来的SQL一眼就能定位。

3.4 两个核心接口代码解读

登录接口,我简化后大概是这样的:

@PostMapping("/login") public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) { SysUser user = userService.findByUsername(loginDTO.getUsername()); if (user == null || !BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) { throw new RuntimeException("账号或密码错误"); } String token = jwtUtils.createToken(user.getId(), user.getRoleCode()); return Result.success(new LoginVO(token, user.getRoleCode(), user.getRealName())); }

这里用BCrypt而不是MD5,原因很简单:MD5撞库太容易,BCrypt自带随机盐,即使两个用户密码相同,加密结果也不同,攻击者拿到数据库也没法批量还原。这一点在课程设计文档里可以作为安全亮点写进去。

再一个是分页查询课程列表,用PageHelper插件:

public PageResult<CourseVO> listCourses(int pageNum, int pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); List<CourseVO> list = courseMapper.selectCourseList(keyword); PageInfo<CourseVO> pageInfo = new PageInfo<>(list); return new PageResult<>(pageInfo.getTotal(), pageInfo.getList()); }

PageHelper的本质是在执行SQL之前拦截,自动拼接limit语句。用它的关键点:PageHelper.startPage后面必须紧跟第一条Mapper查询,中间不能有任何其他SQL操作,否则分页会失效,这是非常典型的坑。比如有人习惯在查询前先查一下当前用户信息,分页条件就被消费掉了,查出来的列表根本没有分页效果。

4. 前端Vue项目:环境搭建、路由与接口联调

4.1 Node与Vue环境搭建

前端工程化第一步是环境。网上大量"vue安装及环境配置"的求助帖,核心原因往往是Node版本不对。我给出经过验证的版本组合:Node 16.20.x或18.x,搭配npm。Node版本太低,Vite 5起不来;版本太高又可能遇到部分原生依赖编译问题。用nvm管理Node版本是推荐做法,一套命令来回切换,能在不同项目间使用不同Node。

项目创建我用Vite:

npm create vite@latest teach-front -- --template vue cd teach-front npm install npm run dev

如果网络不好,npm install卡半天,可以切换镜像源:

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

这里有个小坑:Vite默认启动端口是5173,后端接口是8080,直接访问会跨域。解决方式推荐用Vite的代理,而不是在后端开启CORS了事。在vite.config.js里配置:

export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

这样前端请求地址写成/api/login,Vite会自动把请求转发到后端8080,浏览器层面没有跨域问题。生产环境部署时再在Nginx里做同样的转发。注意,Vue2时代常用vue.config.js加devServer.proxy,Vue3换用Vite后配置项变了,拿着Vue2的教程配Vue3项目会踩空。

4.2 路由、状态管理与登录守卫

前端路由用Vue Router 4,页面组织分三块:登录页、公共主页、以角色区分的业务区域。这里最值得讲清楚的是路由守卫。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next({ path: '/login' }); } else { next(); } });

思路很简单:需要登录才能访问的页面,如果没有Token就强行跳走;如果已经有Token还去登录页,直接放回首页。再进一步可以配合路由meta里的角色信息,比如teacher路由配置meta:{ requiresAuth: true, role: 'teacher' },在守卫里拿出来判断角色,防止学生直接改URL进入教师管理页。这种URL级权限是前端的第一道防线,但真正的权限控制后端必须再做一次,这个我在第3章强调过。

Vue3的状态管理推荐Pinia,比Vuex轻量很多。用户登录成功后,把用户信息和Token同时存到Pinia和localStorage。Pinia负责当前会话的响应式状态,localStorage负责刷新后恢复。刷新时在App启动阶段从localStorage读一次并更新Pinia,这叫初始化同步,不做这一步,一刷新页面用户就"丢失"了。

路由传参是容易混淆的点。query方式参数会出现在URL中,比如/detail?courseId=1,刷新页面参数还在,适合不敏感、可分享的链接;params方式在Vue Router 4里要配合动态路由配置,比如/detail/:id,刷新后参数也不会丢,但取参方式不同。我建议对课程详情、作业详情这类资源跳转统一用动态路由加params,参数语义更清晰,分享链接也不会带一堆query串。

4.3 axios封装与前后端联调

接口请求统一封装是前端代码不爆炸的前提。在src/utils/request.js里做:

import axios from 'axios'; import router from '../router'; const request = axios.create({ baseURL: '/api', timeout: 10000 }); request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = token; } return config; }); request.interceptors.response.use( response => { const res = response.data; if (res.code === 200) { return res; } if (res.code === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(res); }, error => { return Promise.reject(error); } ); export default request;

这里把baseURL设置成'/api',正好对应Vite代理的路径。后端接口也统一加/api前缀,联调时前后端只需要对这个前缀达成一致,其余字段格式看同一个接口文档就行。401表示Token过期,前端统一踢回登录页,这是大部分系统都会用的策略。整个封装完成之后,业务页面里就不用关心Token和错误弹窗了,代码清爽很多。

4.4 页面组件与视频播放的实现要点

页面编码过程中,有三个地方值得单独说。

第一个是表格列里的操作按钮。课程列表每行都有"编辑""删除""查看"按钮,如果每个按钮写成一行独立代码,代码会非常冗余。Vue的插槽语法在这里很好用:el-table的列里定义插槽,插槽里放按钮,按钮触发各自处理函数。这样同一套操作模板可以复用在作业列表、学生列表里,维护时只改一处。这就是Vue插槽机制最常见的实际应用场景。

第二个是视频播放。教学系统的课程资源里,视频是最常见的形式。MP4格式用原生video标签解决。m3u8这类切片视频用hls.js,一个很简单的封装:

<video id="player" controls></video> <script> import Hls from 'hls.js'; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(document.getElementById('player')); } </script>

hls.js是纯前端解析,不需要额外安装浏览器插件。这个方案在做课程视频模块时反复验证过,稳定且实现简单。要注意的是视频请求路径要能通到后端资源目录,建议后端单独做一个/resource/视频的映射接口,前端拿到的就是可直接播放的URL。

第三是考试页面。考试是交互逻辑最复杂的页面,用倒计时组件加题目切换组件实现。倒计时用定时器每秒减一,到零自动提交。这里有个坑:如果直接依赖每秒减一,学生切到别的浏览器标签页,后台标签页的定时器会被浏览器降频甚至暂停,导致倒计时变慢。稳妥做法是进入页面时记录开始时间戳,倒计时用当前时间减去开始时间计算,而不是傻傻地每秒减一。这个小细节能在演示时避免一场事故。

5. 部署全流程与常见问题排查

5.1 本地部署五步走

拿到源码之后,按下面顺序操作基本不会错。

第一步,准备数据库。先在MySQL里建一个teach_db数据库,注意字符集选utf8mb4,再通过source命令或者数据库客户端工具运行项目里的teach_db.sql文件,把表和初始数据都导入。初始数据我会预置一个管理员账号、两个教师账号、三个学生账号,方便直接登录测试。如果你在Windows上装MySQL时发现安装器没有"Develop"这类选项,不用慌,只要能装上MySQL Server并启动服务就能用,选项名称因版本而异。

第二步,配置后端。打开application.yml,改成自己本地的MySQL账号密码。在开发工具里直接运行TeachApplication启动,控制台看到"Tomcat started on port(s): 8080"说明后端起来了。后端启动时默认会打印一个SpringBoot样式的Banner,这个就是网上那些"springboot banner在线制作"相关的功能,实际只影响启动日志观感,不影响项目功能。

第三步,启动前端。在前端目录执行npm install安装依赖,然后npm run dev,看到Vite提示localhost:5173就能访问页面。如果端口被占用,检查是不是开了多个前端实例,或者把5173改成其他端口。

第四步,联调验证。用预置账号登录,走一遍浏览课程、进入详情、播放视频、提交作业、查看成绩的流程。这一步能发现接口访问不到的配置问题,比如代理失效、Token没带上等。

第五步,如果要打包上线。后端执行mvn clean package打成jar包,用java -jar运行;前端执行npm run build生成dist目录,里面是纯静态文件,扔给Nginx或者放到后端项目的static目录下都可以。

5.2 部署中的高频报错速查表

我把运行过程中经常出现的报错整理成表,方便直接对照:

报错现象大概率原因解决方案
数据库连不上,Access denied账号密码错误或授权没开检查application.yml,重新授权账号
Public Key Retrieval is not allowedMySQL8驱动连库未授权连接串加allowPublicKeyRetrieval=true
Invalid bound statement (not found)Mapper XML未扫描或namespace错误检查@MapperScan和XML的namespace
Failed to load tsconfig '@vue/tsconfig/...'Vue3 TS项目模板配置缺失重装依赖,检查tsconfig扩展路径
npm安装node-sass失败Node版本过高或镜像源问题改用dart-sass,设置npmmirror源
Vite显示页面但接口404代理没生效或后端没启动检查vite.config.js代理和后端端口
前端页面空白,控制台CORS报错生产环境跨域Nginx配置proxy_pass,或后端开启CorsFilter
SpringBoot启动失败,找不到主类JDK版本不匹配确认JDK8或统一JDK17
上传课件超过大小限制默认限流1MB在配置里调大multipart限制
页面加载慢,CPU占用高数据库连接未释放检查连接池配置,生产用HikariCP默认值即可

这里补充两点MySQL的实战经验。第一,如果之前装过MySQL后来又卸载了,重装时容易初始化失败,大概率是残留的数据目录和注册表服务没清理干净,卸载后要把数据目录一并删掉再装。第二,MySQL 8.0默认认证插件是caching_sha2_password,旧版驱动会出现连不上或者Public Key Retrieval的报错,连接串加参数解决。这两个问题在Windows上装MySQL时非常高频,提前知道能省很多时间。

5.3 服务器部署与数据备份的实用建议

真正上线到服务器,和本地跑通完全是两码事。我建议按下面这几点做。

第一,前端用Nginx托管,同时做反向代理:

server { listen 80; server_name your-domain.com; location / { root /www/teach-front/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } }

这套配置同时解决了两个问题:前端路由的history模式在刷新时容易404,try_files把请求统一指向index.html;接口请求通过/api前缀转发到后端,避免跨域。如果你用了Vue的history路由又没有配try_files,刷新页面经常出现白屏或404,问题就出在这里。

第二,数据库一定要定时备份。教学系统里成绩数据极其重要,用mysqldump写个定时脚本,每天凌晨备份一次:

mysqldump -uroot -p123456 teach_db > /backup/teach_db_$(date +%Y%m%d).sql

第三,日志文件按天滚动。SpringBoot默认只输出到控制台,部署时用logging配置把日志写到文件,保留最近30天,出现问题时能快速排查。不要把日志和代码放在同一目录,不然日志涨满磁盘会影响服务运行。

最后分享一点我个人反复验证过的体会。做这种前后端分离的教学系统,核心不是把某个接口写得多花哨,而是把用户、课程、作业、成绩这条业务链路理顺。我第一次接触类似需求的时候,先画了三张图:一张权限图、一张课程资源层级图、一张成绩数据流转图,然后才开始写代码。事实证明这比直接上手编码省了至少一半返工时间。如果你打算拿这个项目做课程设计或毕业设计,强烈建议也先做这个动作。部署过程中如果遇到上面没覆盖到的问题,把启动日志和控制台报错完整贴出来定位,绝大多数的坑都能在SpringBoot和Vue这套成熟技术栈的社区问答里找到答案。这套组合最大的优势就是生态成熟,别人踩过的坑基本都有前人总结,关键是你得学会看日志,而不是把报错整段截图就完事。耐心按日志一层层排查,问题总能解决。

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

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

立即咨询