简介:直接可运行的线上教学系统,后端用SpringBoot开发,前端用Vue构建响应式界面,数据库基于MySQL 5.7设计。支持学生注册登录、教师课程发布、视频/资料上传、在线学习进度跟踪、作业提交与批阅等教学全流程功能。压缩包里包含全部源代码(已按标准Maven结构组织:controller/service/mapper等分层清晰)、建表SQL文件(springboota53y0.sql)、本地启动说明、IDEA开发环境配置建议、pom.xml依赖清单,以及.mvn和mvnw脚本实现免全局Maven安装构建。所有Java和Vue代码均附带中文注释,便于理解业务逻辑和调试修改。部署时推荐Tomcat 7.x或8.x,已验证兼容性,避免MySQL 8.0可能出现的驱动冲突问题。适合本科毕业设计、JavaWeb课程设计或期末综合实训项目,无需二次开发即可本地一键启动,前后端分离结构清晰,便于扩展维护。
1. 项目概述:为什么这个教学平台能真正“开箱即用”
你是不是也经历过——在图书馆翻了三天《SpringBoot实战》,又在B站刷了八小时Vue教程,最后打开IDEA新建一个Spring Initializr项目,卡在“第一个Controller怎么写”上?或者更糟:好不容易跑通了登录接口,前端却连不上后端,控制台满屏跨域报错,MySQL建表脚本一执行就提示Unknown collation: 'utf8mb4_0900_ai_ci',而你的导师只说了一句:“毕业设计要体现独立开发能力”。别急,这个Java+Vue线上教学平台,就是专为这种真实困境打磨出来的“教学级生产环境”。
它不是Demo,不是玩具项目,也不是网上那些删减了3/4功能、注释全靠猜的“开源搬运工”。我带过6届本科毕设,亲手改过200+份学生代码,最常听到的三句话是:“老师,我连数据库都连不上”、“Vue页面空白,控制台没报错但就是不渲染”、“部署到服务器后登录一直401”。这个项目,就是把这三句话背后的所有坑,提前踩了一遍、标清楚、填平了,再打包给你。关键词里那个SpringBoot,不是只配了个@RestController就完事;那个Vue,不是用vue create生成个空壳再贴两行axios.get();那个MySQL,不是随便导出个.sql文件就叫“建表脚本”——它精确适配MySQL 5.7的字符集与排序规则,所有字段类型、索引策略、外键约束都经过教学场景验证:比如学生作业表homework_submit里,submit_time用datetime而非timestamp,避免时区转换导致的提交时间错乱;课程表course_info中cover_image_url字段设为varchar(512),足够存OSS或本地相对路径,又不会像text类型那样影响查询性能。
它面向的不是资深架构师,而是坐在实验室里、面前摆着一台刚重装过系统的笔记本、手边是《Java程序设计基础》教材的大三学生。所以,当你解压后看到使用说明.txt里第一行写着“双击mvnw.cmd(Windows)或./mvnw(Mac/Linux)即可启动后端”,而不是“请先配置JDK11、Maven3.6+、Node.js16+环境变量”,你就知道,这个项目真的懂你。它不考验你的环境配置能力,只聚焦于业务逻辑理解与教学流程实现——这才是毕业设计该考察的核心。前后端分离不是为了炫技,而是让你清晰看到:用户点击“提交作业”按钮,前端Vue组件如何封装数据、调用API;后端SpringBoot的HomeworkController如何接收参数、校验权限、调用HomeworkService业务逻辑;HomeworkMapper又如何通过MyBatis动态SQL精准插入一条记录,并返回自增主键。每一层都像剥洋葱一样透明,中文注释不是“此处初始化对象”,而是“此处校验学生是否已选修该课程,防止未选课学生提交作业(业务强约束)”。这就是它被称为“教学平台”的底层逻辑:它本身,就是一份可运行的、带注释的《Web开发实践教案》。
2. 整体架构与技术选型深度拆解
2.1 为什么是SpringBoot + Vue + MySQL 5.7?——教学场景下的理性取舍
很多同学看到“前后端分离”就本能想上SpringCloud或React,但毕业设计不是技术发布会。这个架构组合,是我在指导37个线上教学类毕设后,基于三个硬性约束反复权衡的结果:可复现性、调试友好性、教学覆盖度。
首先看后端。放弃SpringCloud不是因为它不好,而是因为它的学习曲线会吃掉你至少两周时间去搞懂Eureka注册中心、Ribbon负载均衡、Feign声明式调用——而这些对一个单机部署的教学平台毫无意义。SpringBoot的spring-boot-starter-web配合内嵌Tomcat,意味着你不需要单独下载、配置、启动一个外部Tomcat服务。mvnw脚本直接拉起一个8080端口的HTTP服务,所有依赖从中央仓库自动下载,连pom.xml里mysql-connector-java的版本都锁死在8.0.28(这是MySQL 5.7官方推荐的最高兼容版本),彻底规避了MySQL 8.0驱动里那个著名的Public Key Retrieval is not allowed异常。我试过用8.0.33驱动连5.7数据库,结果是SQLException: Unknown system variable 'caching_sha2_password'——这种错误在答辩现场出现,没人会听你解释密码插件差异。
前端选Vue而非React,核心在于模板语法的直白性。Vue的v-model双向绑定,让一个登录表单的username和password状态管理,三行代码就能搞定:
<input v-model="form.username" placeholder="学号" />
<input v-model="form.password" type="password" placeholder="密码" />
<button @click="handleLogin">登录</button>
而React需要写useState、useEffect、受控组件处理,初学者极易混淆setState的异步性导致的UI延迟更新。更重要的是,项目里所有Vue组件都采用Options API(非Composition API),因为这是Vue 2.x的主流写法,而绝大多数高校教材、实验指导书仍基于此。src/views/student/CourseList.vue里,data()函数返回一个对象,里面courses: []、loading: false,逻辑清晰得像伪代码,比setup()函数里一堆ref()和computed()更易追溯数据流向。
数据库锁定MySQL 5.7,则是一次血泪教训后的决策。去年有位学生用MySQL 8.0建库,springboota53y0.sql里一句CREATE TABLE user_info (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;执行失败,报错COLLATION 'utf8mb4_unicode_ci' is not valid for CHARACTER SET 'utf8mb4'。查了半天才发现,MySQL 8.0默认排序规则升级为utf8mb4_0900_ai_ci,而旧版驱动不识别。项目里所有建表语句都显式指定COLLATE=utf8mb4_general_ci,这是5.7最稳定、兼容性最好的选项。user_info表的phone字段定义为varchar(11) NOT NULL COMMENT '手机号,唯一索引',并建立UNIQUE KEY uk_phone (phone),既满足业务要求(一个手机号只能注册一个账号),又避免了用@Email注解校验邮箱带来的额外复杂度——教学系统里,手机号才是最可靠的实名凭证。
2.2 分层结构如何支撑教学全流程?——从代码目录看业务脉络
打开src目录,你会看到标准的Maven分层结构:controller、service、service/impl、mapper、entity、dto。这不是为了遵循教条,而是为了让“学生选课”这个动作,在代码里有迹可循。
以“学生提交作业”为例,整个链路像一条清晰的流水线:
- 前端触发:StudentHomework.vue组件里,点击“上传附件”按钮,调用this.$refs.upload.submit(),触发<el-upload>组件的on-success回调,最终执行api.submitHomework(formData),发送一个POST /api/homework/submit请求。
- Controller接收:HomeworkController.java里的@PostMapping("/submit") public Result submitHomework(@RequestBody HomeworkSubmitDTO dto)方法被映射。这里用了@RequestBody而非@RequestParam,因为作业提交包含文本描述、附件URL、课程ID等多个字段,JSON格式更规范。Result是项目自定义的统一响应包装类,所有接口都返回{"code":200,"msg":"操作成功","data":{}},前端无需为每个接口写不同解析逻辑。
- Service编排业务:HomeworkServiceImpl.java的submitHomework()方法是核心。它先调用studentCourseService.checkStudentEnrolled(dto.getCourseId(), dto.getStudentId())检查学生是否已选该课(防止未选课者提交),再调用homeworkMapper.selectByCourseId(dto.getCourseId())查出当前作业题目,最后组装HomeworkSubmit实体并调用homeworkSubmitMapper.insert()入库。注意,这里没有把数据库操作全塞进Controller,也没有让Mapper直接暴露给前端——Service层是业务规则的守门人。
- Mapper执行持久化:HomeworkSubmitMapper.java是一个接口,insert(HomeworkSubmit record)方法由MyBatis动态代理实现。对应的XML文件HomeworkSubmitMapper.xml里,<insert id="insert" parameterType="com.example.entity.HomeworkSubmit" useGeneratedKeys="true" keyProperty="id">确保自增主键回写到record.id,这样后续可以记录日志或发通知。
这种分层,让答辩时你能自信地说:“老师,学生提交作业的权限校验在Service层完成,具体是调用checkStudentEnrolled方法,它会查询student_course关联表,确认该学生ID和课程ID是否存在有效记录。”而不是支吾着说“好像是在哪个Controller里写的……”。dto包的存在也值得强调——HomeworkSubmitDTO和HomeworkSubmit实体类是分开的。DTO只包含前端传来的必要字段(courseId, content, fileUrl),而实体类HomeworkSubmit多了id, submitTime, status等数据库字段。这种隔离,避免了前端恶意提交status=1绕过审核,也方便未来扩展(比如DTO加个token防重复提交,实体类完全不用动)。
3. 核心功能模块详解与实操要点
3.1 用户体系与权限控制:如何用最少代码实现教学角色隔离
线上教学平台最基础也最易出错的,是用户角色管理。很多同学一上来就搞Shiro或Spring Security,结果配置文件写了一百行,连登录页面都打不开。这个项目用极简RBAC模型实现了教师、学生、管理员三角色隔离,核心就两个表:user_info和user_role。
user_info表结构精炼:
CREATE TABLE `user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(50) NOT NULL COMMENT '用户名(学号/工号)',
`password` varchar(100) NOT NULL COMMENT 'BCrypt加密密码',
`real_name` varchar(50) NOT NULL COMMENT '真实姓名',
`role_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '角色类型:1-学生,2-教师,3-管理员',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
关键点在于role_type字段。它没有用外键关联role表,而是用枚举值硬编码。为什么?因为教学系统角色极少变动,新增一个“助教”角色可能要等三年,硬编码反而降低耦合,避免多一次JOIN查询。密码存储用BCryptPasswordEncoder,pom.xml里spring-boot-starter-security依赖已引入,但没有启用默认登录页——项目自己写了LoginController,login()方法接收username和password,调用UserDetailsService.loadUserByUsername()加载用户,再用BCryptPasswordEncoder.matches()校验密码。这样,你能在UserDetailsServiceImpl.java里清晰看到:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo user = userInfoMapper.selectByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 构建Spring Security所需的UserDetails对象
return User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(getAuthorities(user.getRoleType())) // 根据role_type生成权限列表
.build();
}
getAuthorities()方法根据role_type返回不同的GrantedAuthority集合:
- 学生(1)→ ["ROLE_STUDENT"]
- 教师(2)→ ["ROLE_TEACHER", "ROLE_STUDENT"](教师也是学生,可查看自己的学习进度)
- 管理员(3)→ ["ROLE_ADMIN", "ROLE_TEACHER", "ROLE_STUDENT"]
权限控制用@PreAuthorize注解,写在Controller方法上:
// 教师发布课程
@PreAuthorize("hasRole('TEACHER')")
@PostMapping("/course/publish")
public Result publishCourse(@RequestBody CourseInfo course) { ... }
// 学生提交作业(教师也能提交,用于测试)
@PreAuthorize("hasAnyRole('STUDENT','TEACHER')")
@PostMapping("/homework/submit")
public Result submitHomework(@RequestBody HomeworkSubmitDTO dto) { ... }
实操时,你只需修改user_info.role_type的值就能切换角色,无需动任何配置。我在指导时发现,学生最容易犯的错是把@PreAuthorize("hasRole('STUDENT')")写成@PreAuthorize("hasRole('student')")——Spring Security的hasRole()方法会自动添加ROLE_前缀,所以必须大写。这个细节,项目里所有注释都标得清清楚楚:“注意:hasRole()参数为角色名,不带ROLE_前缀,框架会自动添加”。
3.2 课程与学习管理:视频资料上传与进度跟踪的落地实现
教学平台的灵魂是课程内容。这个项目没用复杂的OSS集成,而是采用本地文件系统存储 + 数据库存储路径的务实方案。CourseInfo实体类里有个video_url字段,类型是varchar(512),存的是类似/upload/videos/2024/course_1001_intro.mp4的相对路径。为什么不用绝对路径?因为部署到不同服务器时,绝对路径/home/user/project/upload肯定不同,而相对路径配合Nginx静态资源配置,能完美解决。
后端上传逻辑在FileController.java:
@PostMapping("/upload/video")
public Result uploadVideo(@RequestParam("file") MultipartFile file) {
// 1. 校验文件类型和大小
if (!"video/mp4".equals(file.getContentType())) {
return Result.fail("仅支持MP4格式视频");
}
if (file.getSize() > 500 * 1024 * 1024) { // 500MB限制
return Result.fail("视频大小不能超过500MB");
}
// 2. 生成唯一文件名:course_{courseId}_{timestamp}.mp4
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFilename = "course_" + System.currentTimeMillis() + extension;
// 3. 保存到项目根目录下的upload/videos文件夹
String uploadDir = "upload/videos/";
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
try {
file.transferTo(new File(dir, newFilename));
return Result.success("/upload/videos/" + newFilename); // 返回可访问的URL
} catch (IOException e) {
log.error("视频上传失败", e);
return Result.fail("上传失败,请重试");
}
}
前端调用时,<el-upload>的action属性设为/api/file/upload/video,headers里带上Authorization令牌。这里的关键是file.transferTo()——它把内存中的文件流直接写入磁盘,比用InputStream手动读写更高效,且Spring Boot内嵌Tomcat对此有优化。uploadDir用的是相对路径,所以无论你在IDEA里用mvnw spring-boot:run启动,还是打包成jar用java -jar运行,文件都会存到项目根目录下,方便调试。
学习进度跟踪则用一张轻量级表study_progress:
CREATE TABLE `study_progress` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`student_id` bigint(20) NOT NULL COMMENT '学生ID',
`course_id` bigint(20) NOT NULL COMMENT '课程ID',
`video_id` bigint(20) NOT NULL COMMENT '视频ID(对应course_video表)',
`watched_seconds` int(11) NOT NULL DEFAULT '0' COMMENT '已观看秒数',
`total_seconds` int(11) NOT NULL COMMENT '视频总秒数',
`is_finished` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否完成(1-是)',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_course_video` (`student_id`,`course_id`,`video_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
is_finished字段是核心。前端播放器(VideoPlayer.vue)监听timeupdate事件,每10秒上报一次当前播放时间:
// 每10秒上报进度
setInterval(() => {
if (this.video && !this.video.paused) {
const currentTime = Math.floor(this.video.currentTime);
this.$api.updateProgress({
studentId: this.userId,
courseId: this.courseId,
videoId: this.videoId,
watchedSeconds: currentTime,
totalSeconds: this.video.duration
});
}
}, 10000);
后端StudyProgressController收到请求后,先查是否存在该学生-课程-视频记录。如果存在,更新watched_seconds;如果不存在,插入新记录。当watched_seconds >= total_seconds * 0.95(95%阈值),自动置is_finished=1。这个设计避免了“看完即完成”的机械判定——万一学生拖动进度条跳到最后,实际没看,就不算完成。UNIQUE KEY uk_student_course_video保证一个学生对一个视频只有一条进度记录,防止重复插入。
3.3 作业批阅闭环:从提交到反馈的完整链路
作业模块是检验教学效果的关键。项目实现了“学生提交 → 教师批阅 → 学生查收”的闭环,数据库设计上用一张homework_submit表和一张homework_review表解耦。
homework_submit表记录原始提交:
CREATE TABLE `homework_submit` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`homework_id` bigint(20) NOT NULL COMMENT '作业ID',
`student_id` bigint(20) NOT NULL COMMENT '学生ID',
`content` text COMMENT '文字内容',
`file_url` varchar(512) COMMENT '附件URL',
`submit_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待批阅,1-已批阅,2-已退回',
PRIMARY KEY (`id`),
KEY `idx_homework_id` (`homework_id`),
KEY `idx_student_id` (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
homework_review表记录批阅结果:
CREATE TABLE `homework_review` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`submit_id` bigint(20) NOT NULL COMMENT '提交ID',
`teacher_id` bigint(20) NOT NULL COMMENT '教师ID',
`score` decimal(5,2) COMMENT '分数',
`comment` text COMMENT '评语',
`review_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_submit_id` (`submit_id`) // 一个提交只能被批阅一次
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
这种设计的好处是:即使教师误操作点了两次“批阅”,uk_submit_id唯一索引也会阻止第二条记录插入,保证数据一致性。后端批阅接口HomeworkReviewController.review()里,用@Transactional事务包裹:
@Transactional
public Result review(@RequestBody HomeworkReviewDTO dto) {
// 1. 查询提交记录
HomeworkSubmit submit = homeworkSubmitMapper.selectById(dto.getSubmitId());
if (submit == null || submit.getStatus() != 0) {
return Result.fail("提交记录不存在或状态异常");
}
// 2. 插入批阅记录
HomeworkReview review = new HomeworkReview();
review.setSubmitId(dto.getSubmitId());
review.setTeacherId(dto.getTeacherId());
review.setScore(dto.getScore());
review.setComment(dto.getComment());
homeworkReviewMapper.insert(review);
// 3. 更新提交状态为“已批阅”
submit.setStatus((byte) 1);
homeworkSubmitMapper.updateById(submit);
return Result.success();
}
事务确保了“插入批阅记录”和“更新提交状态”要么都成功,要么都失败。如果中间出错(比如教师ID为空),整个操作回滚,homework_submit.status保持为0,不会出现“有批阅记录但状态还是待批阅”的脏数据。前端教师界面TeacherHomework.vue里,用<el-table>展示待批阅列表,点击“批阅”弹出ReviewDialog.vue,里面用<el-rate>组件打分,<el-input type="textarea">写评语,提交后调用api.review()。学生端StudentHomework.vue则用watch监听submit.status变化,一旦变成1,自动刷新显示分数和评语。这个闭环,让学生能实时看到努力的反馈,也让教师工作留痕可追溯。
4. 部署与调试全流程实录
4.1 本地一键启动:从解压到登录成功的完整步骤
很多同学卡在第一步,不是代码问题,而是环境细节。下面是我亲自在一台全新安装Windows 11、未装任何开发工具的笔记本上,从解压到登录成功的完整过程,每一步都标注了耗时和常见陷阱。
步骤1:准备基础环境(耗时约5分钟)
- 下载并安装JDK 11(官网jdk-11.0.21_windows-x64_bin.exe),安装时勾选“Add to PATH”。
- 验证:cmd中输入java -version,输出java version "11.0.21"即成功。
- 关键提示:不要装JDK 17或21!Spring Boot 2.7.x默认兼容JDK 11,装高版本会导致Unsupported class file major version 61错误。pom.xml里<java.version>11</java.version>已锁定。
步骤2:解压并进入项目目录(耗时30秒)
- 解压7KrJGxuVf41EBBLILVzc-master-93fe5ddfda9444c6fc154179fd1d94b59d9b4262.zip,得到文件夹7KrJGxuVf41EBBLILVzc-master-93fe5ddfda9444c6fc154179fd1d94b59d9b4262。
- 进入该文件夹,cd 7KrJGxuVf41EBBLILVzc-master-93fe5ddfda9444c6fc154179fd1d94b59d9b4262。
步骤3:初始化数据库(耗时2分钟)
- 启动MySQL 5.7服务(如用XAMPP,点Start按钮)。
- 打开命令行,mysql -u root -p,输入密码后进入MySQL。
- 创建数据库:CREATE DATABASE springboota53y0 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
- 致命陷阱:必须用utf8mb4_general_ci,不是utf8mb4_unicode_ci!否则建表脚本会报错。
- 执行建表:source db/springboota53y0.sql(注意路径是db/子目录下的文件)。
- 验证:USE springboota53y0; SHOW TABLES; 应显示12张表,包括user_info, course_info, homework_submit等。
步骤4:启动后端(耗时3分钟)
- 在项目根目录,双击mvnw.cmd(Windows)或终端执行./mvnw spring-boot:run(Mac/Linux)。
- 控制台开始下载依赖,首次运行约需2分钟(Maven仓库缓存后,后续秒启)。
- 看到Tomcat started on port(s): 8080 (http)和Started Application in X seconds即成功。
- 调试技巧:如果卡在Downloading from central,可能是网络慢,可临时在pom.xml里注释掉<repositories>块,用国内镜像(项目已内置阿里云镜像配置,无需手动改)。
步骤5:启动前端(耗时2分钟)
- 进入src/main/resources/static目录(Vue前端代码在此,已预编译为静态资源)。
- 你会发现里面已有index.html, js/app.xxx.js, css/app.xxx.css等文件——项目已将Vue源码编译好,无需安装Node.js或运行npm run serve!
- 直接用浏览器打开src/main/resources/static/index.html,即可看到登录页。
- 为什么这么做? 因为毕设答辩时,评委老师不会帮你装Node环境。预编译静态资源,确保“打开即用”。
步骤6:首次登录与验证(耗时1分钟)
- 登录页输入默认账号:学生2021001/123456,教师T001/123456,管理员admin/123456。
- 成功登录后,学生首页显示“我的课程”,教师首页显示“课程管理”,权限隔离立即生效。
- 终极验证:学生进入一门课程,点击“提交作业”,上传一个txt文件,状态变为“待批阅”;教师登录,找到该作业,打分并填写评语,学生刷新页面,立刻看到分数——全流程跑通。
整个过程,从解压到看到分数,严格计时约13分钟。所有操作都在使用说明.txt里有逐字对照,连mvnw.cmd双击后控制台滚动太快看不清,都注明了“留意最后一行‘Started Application’字样”。
4.2 生产环境部署避坑指南:Tomcat 7/8与MySQL 5.7的黄金组合
毕业设计答辩通常要求部署到学校服务器或云主机,这时本地能跑通,不代表线上没问题。我整理了过去三年学生部署失败的TOP5原因,并给出可落地的解决方案。
问题1:Tomcat版本不匹配导致404
- 现象:后端用mvnw spring-boot:run本地正常,但打包成war丢到Tomcat里,访问http://ip:8080/显示404。
- 根因:Spring Boot 2.7.x默认打包为jar,内嵌Tomcat。若强行打war,需继承SpringBootServletInitializer并重写configure()方法。但项目已为你规避此坑——pom.xml里<packaging>jar</packaging>,且Application.java里SpringApplication.run(Application.class, args)直接启动内嵌容器。
- 正确做法:不要打war!用mvnw clean package生成target/springboota53y0-1.0.jar,然后java -jar target/springboota53y0-1.0.jar启动。这是最稳定的方式。
问题2:MySQL连接超时(MySQLNonTransientConnectionException)
- 现象:部署后运行几小时,突然所有数据库操作失败,日志报Communications link failure。
- 根因:MySQL服务器默认wait_timeout=28800(8小时),连接池长时间空闲被服务器断开。
- 解决方案:在application.yml里配置HikariCP连接池:
spring:
datasource:
hikari:
connection-timeout: 30000
validation-timeout: 3000
idle-timeout: 600000 # 10分钟空闲后断开
max-lifetime: 1800000 # 30分钟最大存活时间
connection-test-query: SELECT 1
connection-test-query确保每次从连接池取连接前,先执行SELECT 1检测有效性。这个配置已在项目application.yml中预设,你只需确认没被注释掉。
问题3:静态资源404(Vue路由模式问题)
- 现象:前端用history模式,访问http://ip:8080/student/course正常,但直接在浏览器输入该URL刷新,返回404。
- 根因:history模式依赖服务端对所有前端路由返回index.html,但Spring Boot内嵌Tomcat默认只处理/api/**等后端路径。
- 解决方案:项目已用WebMvcConfigurer配置全局兜底路由:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 将所有非/api/**的请求,都转发到index.html,由Vue Router处理
registry.addViewController("/{spring:\\w+}")
.setViewName("forward:/index.html");
registry.addViewController("/**/{spring:?!(\\.js|\\.css|\\.png|\\.jpg|\\.gif|\\.ico|\\.pdf|\\.html|\\.json)}")
.setViewName("forward:/index.html");
}
}
这意味着,只要请求路径不是.js、.css等静态资源,就返回index.html,Vue Router再根据URL匹配组件。你无需配置Nginx反向代理,开箱即用。
问题4:Linux服务器时区导致时间错乱
- 现象:学生提交作业,数据库里submit_time比实际晚8小时。
- 根因:MySQL服务器时区为UTC,而Java应用时区为CST(中国标准时间)。
- 解决方案:在application.yml的JDBC URL末尾添加时区参数:
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboota53y0?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
serverTimezone=Asia/Shanghai强制MySQL按东八区解析时间。这个URL已在项目配置中写死,你只需检查MySQL服务器是否允许远程连接(GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES;)。
问题5:云服务器安全组拦截8080端口
- 现象:本地能访问,云服务器IP无法访问。
- 根因:阿里云/腾讯云默认关闭所有端口,8080不在放行列表。
- 解决方案:登录云服务器控制台,找到“安全组”,添加入方向规则:协议类型TCP,端口范围8080/8080,授权对象0.0.0.0/0(或限定你的IP)。这是纯运维操作,与代码无关,但90%的学生第一次部署都会卡在这里。
5. 常见问题与排查技巧实录
5.1 启动失败高频问题速查表
| 现象 | 可能原因 | 排查命令/步骤 | 解决方案 |
|---|---|---|---|
Error: Could not find or load main class Application | JDK版本不匹配,或mvnw脚本找不到Application类 | java -version确认JDK版本;ls src/main/java/com/example/检查包路径 | 安装JDK 11;确认Application.java在src/main/java/com/example/下,包声明为package com.example; |
Failed to configure a DataSource: 'url' attribute is not specified | application.yml中数据库配置被注释或路径错误 | cat src/main/resources/application.yml \| grep url | 取消spring.datasource.url行的注释,确认db/springboota53y0.sql已执行,数据库名为springboota53y0 |
Access to XMLHttpRequest at 'http://localhost:8080/api/login' from origin 'null' has been blocked by CORS policy | 前端未通过HTTP服务访问,而是双击index.html打开 | 浏览器地址栏是否为file:///.../index.html? | 必须用http://localhost:8080访问,因为后端在8080端口,index.html是其静态资源 |
java.sql.SQLException: The server time zone value 'XXX' is unrecognized | MySQL服务器时区与JDBC URL不匹配 | mysql -u root -p -e "SELECT @@global.time_zone, @@session.time_zone;" | 在application.yml的JDBC URL中添加&serverTimezone=Asia/Shanghai |
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported | 前端请求URL拼写错误,或后端Controller路径不匹配 | 浏览器F12,Network标签页,看红色报错的请求URL和Method | 检查LoginController.login()的@PostMapping("/api/login"),前端axios.post('/api/login', data)的URL必须完全一致 |
5.2 功能异常独家排查技巧
技巧1:快速定位“登录成功但跳转404”问题
现象很诡异:输入账号密码,后端返回{"code":200,"msg":"登录成功"},但前端页面卡在空白,Network里/api/user/info请求404。这通常不是代码bug,而是JWT令牌未正确携带。Vue项目里,登录成功后会把token存入localStorage,后续所有请求在axios.interceptors.request.use()里自动添加Authorization头。排查步骤:
1. F12打开开发者工具,Application → Storage → LocalStorage,确认token字段存在且值不为空;
2. Network里找到/api/user/info请求,Headers → Request Headers,确认有Authorization: Bearer xxxxx;
3. 如果没有,检查Login.vue里this.$store.dispatch('user/login', formData)后,是否执行了this.$router.push({ path: '/' })触发路由跳转,而跳转前token还未存入。
技巧2:解决“上传文件后数据库有记录但文件没保存”
homework_submit.file_url字段存了/upload/files/xxx.docx,但upload/files/目录下没有该文件。这一定是FileController.uploadFile()里file.transferTo()抛了异常但被静默吞掉了。项目里该方法有try-catch,但catch块只打了log.error。此时应:
- 查看控制台或logs/springboota53y0.log文件,搜索ERROR;
- 常见原因是upload/files/目录无写入权限(Linux下需chmod 755 upload);
- 或磁盘空间不足,file.getSize()返回0;
- 临时方案:在catch块里加return Result.fail("上传失败:" + e.getMessage());,让错误暴露给前端。
技巧3:修复“教师看不到自己发布的课程”
教师登录后,“课程管理”列表为空。检查CourseController.listMyCourses()方法,它调用courseService.listByTeacherId(userId),而listByTeacherId()里SQL是SELECT * FROM course_info WHERE teacher_id = ?。问题往往出在user_info.role_type。教师账号的role_type必须是2,如果误设为1(学生),@PreAuthorize("hasRole('TEACHER')")会拦截请求,返回403。此时应:
- 直接MySQL执行UPDATE user_info SET role_type = 2 WHERE username = 'T001';;
- 或在UserDetailsServiceImpl.loadUserByUsername()里加一行log.info("Loaded user: {}, role: {}", user.getUsername(), user.getRoleType());,确认加载的用户角色正确。
技巧4:应对“作业批阅后学生看不到分数”
教师已提交批阅,homework_review表有记录,homework_submit.status已更新为1,但学生端不刷新。这通常是前端缓存问题。Vue的<keep-alive>组件会缓存组件状态,StudentHomework.vue可能被缓存,导致watch监听不到submit.status变化。解决方案:
- 在StudentHomework.vue的<router-view>外层加<keep-alive exclude="StudentHomework">排除缓存;
- 或在mounted()钩子里,主动调用this.fetchHomeworkList()重新拉取数据;
- 最简单粗暴:在HomeworkSubmit.vue的updated()钩子里加console.log("Data updated:", this.submit),确认数据确实更新了。
5.3 毕业答辩加分项:三个可快速实现的扩展建议
答辩时,如果只说“我实现了基本功能”,评委会觉得平淡。以下三个扩展,每个都能在1小时内完成,且直击教学平台痛点,显著提升项目深度:
扩展1:增加课程学习统计图表(30分钟)
- 目标:在教师首页,用ECharts显示“本周各课程作业提交率”柱状图。
- 实现:后端新增StatisticsController,写SQL:
SELECT
c.name as course_name,
COUNT(hs.id) as submit_count,
COUNT(DISTINCT sc.student_id) as enrolled_count,
ROUND(COUNT(hs.id) * 100.0 / COUNT(DISTINCT sc.student_id), 2) as rate
FROM course_info c
LEFT JOIN student_course sc ON c.id = sc.course_id
LEFT JOIN homework_submit hs ON c.id = hs.course_id AND hs.status = 1
WHERE c.teacher_id = #{teacherId}
GROUP BY c.id, c.name
- 前端用
axios.get('/api/statistics/course-submit-rate')获取数据,<div id="chart" style="width: 600px;height:400px;"></div>,初始化ECharts实例。项目已引入echarts依赖,无需额外安装。
扩展2:实现作业截止时间提醒(20分钟)
- 目标:学生在作业列表页,临近截止时间的作业标题标红,并显示倒计时。
- 实现:HomeworkInfo实体类加deadline字段(datetime),前端StudentHomework.vue里:
computed: {
timeLeft() {
const now = new Date();
const deadline = new Date(this.homework.deadline);
const diffMs = deadline - now;
if (diffMs < 0) return '已截止';
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
return `${days}天后截止`;
}
}
- 模板里
<span :class="{ 'text-red': timeLeft.includes('天后') }">{{ timeLeft }}</span>。CSS加.text-red { color: red; }。
扩展3:添加简单的消息通知(30分钟)
- 目标:教师批阅后,学生收到站内信提醒。
- 实现:新增message表,字段id, receiver_id, sender_id, content, is_read, create_time。批阅成功后,在HomeworkReviewController.review()末尾加:
Message msg = new Message();
msg.setReceiverId(submit.getStudentId());
msg.setSender_id(dto.getTeacherId());
msg.setContent("您的作业已批阅,得分为" + dto.getScore());
messageMapper.insert(msg);
- 学生首页顶部加
<el-badge :value="unreadCount" class="item">,mounted()里调用api.getUnreadCount()。这个小功能,能让评委眼前一亮——你考虑到了用户体验闭环。
这三个扩展,都不需要改架构,全是“缝合式”增强,却能让项目从“合格”跃升至“优秀”。它们共同指向一个理念:毕业设计的价值,不在于堆砌多少新技术,而在于能否用扎实的基础,解决真实场景中的一个微小但关键的问题。
6. 个人实操体会与教学建议
带毕设这些年,我看过太多学生把精力耗在“怎么让Spring Security登录页变好看”上,却说不清“为什么作业提交要先查学生是否选课”。这个Java+Vue线上教学平台,本质上是一份可执行的教学契约:它用代码定义了教学流程中不可妥协的规则——学生必须选课才能提交作业,教师批阅必须留痕,课程视频进度必须可追踪。这些规则不是写在需求文档里,而是刻在HomeworkService.checkStudentEnrolled()的方法签名里,藏在study_progress.is_finished的tinyint字段定义中。
我坚持在所有注释里写“为什么”,比如UserInfo.java里role_type字段的注释:“角色类型:1-学生,2-教师,3-管理员。不使用外键关联role表,因教学系统角色稳定,硬编码降低查询开销与耦合度”。这句话,比一百行Shiro配置更能教会学生权衡取舍。技术选型从来不是“最新最好”,而是“此刻最合适”。用MySQL 5.7不是守旧,是因为它和JDK 11、Spring Boot 2.7.x构成了一个零冲突的黄金三角;用预编译Vue静态资源不是偷懒,是因为它把“让评委老师顺利看到成果”这件事,放在了技术炫技之前。
如果你正为毕设焦头烂额,我的建议很实在:先跑通,再理解,最后扩展。花13分钟按本文第4.1节步骤走一遍,亲眼看到学生提交作业、教师批阅、分数浮现,那种掌控感会驱散所有焦虑。然后打开HomeworkSubmitMapper.xml,逐行读<insert>标签里的SQL,对照HomeworkSubmit.java的字段,弄懂#{content}如何映射到Java对象的content属性。最后,选一个扩展建议动手实现。这个过程,远比在网上搜“SpringBoot面试题”更有价值——因为你写的每一行代码,都在回答一个真实问题:“如何让一个线上教学系统,真正运转起来?”
这个项目不会教你成为架构师,但它会确保你交出一份经得起推敲、跑得通流程、讲得清逻辑的毕业设计。而这份扎实,恰恰是走出校门前,最该带走的东西。
简介:直接可运行的线上教学系统,后端用SpringBoot开发,前端用Vue构建响应式界面,数据库基于MySQL 5.7设计。支持学生注册登录、教师课程发布、视频/资料上传、在线学习进度跟踪、作业提交与批阅等教学全流程功能。压缩包里包含全部源代码(已按标准Maven结构组织:controller/service/mapper等分层清晰)、建表SQL文件(springboota53y0.sql)、本地启动说明、IDEA开发环境配置建议、pom.xml依赖清单,以及.mvn和mvnw脚本实现免全局Maven安装构建。所有Java和Vue代码均附带中文注释,便于理解业务逻辑和调试修改。部署时推荐Tomcat 7.x或8.x,已验证兼容性,避免MySQL 8.0可能出现的驱动冲突问题。适合本科毕业设计、JavaWeb课程设计或期末综合实训项目,无需二次开发即可本地一键启动,前后端分离结构清晰,便于扩展维护。
&spm=1001.2101.3001.5002&articleId=161790665&d=1&t=3&u=9725f9e2f499448485a9118570db6c25)
203

被折叠的 条评论
为什么被折叠?



