SpringBoot+Vue课程管理系统源码(含数据库脚本、管理员账号及完整部署文档)

📅 2026/7/12 14:00:09 👁️ 阅读次数 📝 编程学习
SpringBoot+Vue课程管理系统源码(含数据库脚本、管理员账号及完整部署文档)

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

简介:提供一套可直接运行的课程管理全栈项目,后端用SpringBoot开发,前端基于Vue.js搭配Element UI实现响应式界面,前后端完全分离。包含完整的后端服务模块(RainngCourseBE)、前端工程(RainngCourseFE)、MySQL建库建表SQL脚本、详细启动说明文档以及技术截图PDF。默认管理员账号为admin,密码123456,导入SQL后即可启动后端服务和前端项目。功能覆盖课程信息增删改查、教师与学生基础信息维护、班级排课逻辑、用户角色权限控制等典型教务场景。代码结构清晰,无冗余文件,保留.gitignore和.idea等开发配置但不影响打包部署;node_modules未上传,需npm install自行安装依赖。适合Java或Vue初学者实战练习,也适合作为高校计算机类专业毕业设计参考模板,支持快速二次开发与功能扩展。

1. 项目概述:这不是一个“Demo”,而是一套能真正跑起来的教务管理骨架

我带过三届毕业设计,每年都有学生卡在“系统搭不起来”这一步——不是不会写代码,而是被环境配置、前后端联调、数据库初始化这些琐事拖垮了信心。直到去年我把这套课程管理系统从零重构了一遍,才真正理解什么叫“开箱即用”。它不是那种只有一张登录页、点进去就404的“教学演示项目”,而是一个从数据库建表到角色权限落地、从班级排课逻辑到前端响应式渲染,全部走通闭环的真实业务骨架。核心关键词——课程管理、Vue前端、SpringBoot后端、Element UI、数据库脚本——每一个都不是摆设:课程增删改查支持富文本描述和封面图上传;教师信息管理带职称、入职年份、所授课程关联;学生档案包含学号、专业、班级归属及选课记录;排课模块不是简单的时间段填空,而是基于教室容量、教师空闲时段、课程周学时约束的三层校验逻辑。默认账号admin/123456不是为了偷懒,而是经过完整权限测试后的最小可行凭证——你导入SQL后启动后端(mvn spring-boot:run),再执行npm run serve,浏览器打开localhost:8080,就能看到左侧菜单栏里“课程管理”“教师管理”“排课计划”“权限设置”四个主模块全部可点击、可操作、有数据反馈。它不追求炫酷动画或微服务架构,但每个接口都带Swagger文档,每张表都有字段注释,每个Vue组件都按功能拆分(比如CourseList.vue只负责列表展示与搜索,CourseForm.vue专注表单验证与提交),这种结构对初学者极其友好:你想学后端,就盯着RainngCourseBE/src/main/java/com/rainng/course/controller下的Controller层看请求怎么进来、Service层怎么处理业务、Mapper层怎么映射SQL;你想练前端,就打开RainngCourseFE/src/views/course目录,观察Element UI的el-table如何绑定分页数据、el-dialog怎么控制表单弹窗、el-date-picker怎样格式化学期起止时间。更关键的是,它没把开发环境当累赘——.idea目录保留了Maven依赖索引和编码配置,node_modules虽未上传,但package.json里所有依赖版本都锁定(包括vue@2.7.16、element-ui@2.15.14、axios@1.6.7),你执行npm install绝不会出现“版本冲突导致表格错位”的尴尬。这不是一个让你截图交差的玩具,而是一块能让你亲手打磨出毕业设计成品的坯料。

2. 整体架构设计与技术选型逻辑:为什么是这套组合,而不是其他方案?

2.1 前后端分离的底层动因:不是跟风,而是为降低学习门槛

很多初学者一上来就想搞“SpringBoot+Vue全家桶”,结果卡在跨域、代理配置、打包路径上三天。这套系统坚持前后端完全分离,根本原因在于把“让代码跑起来”这个最脆弱的环节彻底解耦。后端只暴露RESTful API(如GET /api/course?page=1&size=10),前端通过axios统一拦截请求头、处理token、捕获401跳转登录页——这意味着你完全可以先不启动前端,用Postman直接调后端接口,验证课程数据能否正确返回JSON;也可以先不碰Java,只改前端路由和组件,用Mock.js模拟后端响应,把界面交互逻辑跑通。这种分离不是技术炫技,而是把“环境问题”和“业务问题”物理隔离:当你发现课程列表空白时,第一反应是查浏览器Network面板看API是否返回200,而不是怀疑pom.xml里某个starter版本不对。我刻意没采用Nginx反向代理做开发期联调,因为那会引入新的配置变量(location规则、proxy_pass路径),初学者容易陷入“到底是前端路径写错还是Nginx转发失败”的死循环。取而代之的是Vue CLI的devServer代理机制,在vue.config.js里写:

devServer: { proxy: { '/api': { target: 'http://localhost:8081', // SpringBoot默认端口 changeOrigin: true, pathRewrite: { '^/api': '' } } } }

这段代码的精妙之处在于:前端发请求时写/api/course,开发服务器自动把/api前缀去掉,转发到http://localhost:8081/course,且changeOrigin: true解决了浏览器同源策略报错。你不需要懂HTTP协议细节,只要记住“前端所有接口地址加/api前缀,后端Controller的@RequestMapping别写/api就行”。

2.2 SpringBoot后端选型:为什么不用Spring Cloud或MyBatis-Plus?

看到RainngCourseBE目录下只有spring-boot-starter-webspring-boot-starter-data-jpaspring-boot-starter-security三个核心starter,可能有人疑惑:现在都2024年了,为啥不用MyBatis-Plus的CRUD封装?答案很实在——避免抽象层遮蔽SQL本质。初学者如果直接用courseMapper.selectPage(),很容易忽略分页查询背后的LIMIT ? OFFSET ?原理,更不会意识到COUNT(*)统计总数时若没加索引会导致全表扫描。这套系统坚持手写JPQL(Java Persistence Query Language):

@Query("SELECT c FROM Course c WHERE c.name LIKE %?1% AND c.status = ?2") Page<Course> findByNameAndStatus(String name, Integer status, Pageable pageable);

好处是:你一眼能看出查询条件对应SQL的WHERE子句,Pageable参数明确告诉你分页由JPA自动生成LIMIT/OFFSET,而COUNT语句也由框架自动生成。更重要的是,所有实体类(Course.javaTeacher.java)都用@Table(name = "t_course")显式指定表名,字段用@Column(name = "course_name")标注列名,杜绝了“驼峰转下划线”这种隐式转换带来的调试困惑。至于没上Spring Cloud,是因为单体架构已足够承载教务系统负载——全校5000师生,课程表并发访问峰值也就几百QPS,强行拆微服务反而增加注册中心、网关、熔断等学习成本。Security模块也没用OAuth2,而是基于Session的传统认证:UsernamePasswordAuthenticationFilter拦截登录请求,UserDetailsService加载用户角色,@PreAuthorize("hasRole('ADMIN')")注解控制接口权限。这种方案的好处是,你能在SecurityConfig.java里清晰看到http.authorizeRequests()链式配置,理解“/login”放行、“/api/”需要认证、“/api/admin/”需ADMIN角色的具体含义,而不是被OAuth2的Authorization Code Flow绕晕。

2.3 Vue前端与Element UI的务实选择:放弃Composition API,拥抱Options API

RainngCourseFE用的是Vue 2.7(兼容Vue 3 Composition API语法糖但保持Options API主体),而非Vue 3 + Vite。原因很朴素:高校教材和主流教程仍以Vue 2为蓝本,学生查资料时不会因语法差异卡壳。比如课程列表组件CourseList.vue,它的data选项定义状态:

data() { return { tableData: [], pagination: { currentPage: 1, pageSize: 10, total: 0 }, loading: false } }

methods里写获取数据方法:

async fetchCourses() { this.loading = true; try { const res = await this.$http.get('/api/course', { params: { page: this.pagination.currentPage, size: this.pagination.pageSize } }); this.tableData = res.data.content; this.pagination.total = res.data.totalElements; } finally { this.loading = false; } }

这种写法的好处是:生命周期钩子(mounted)、计算属性(computed)、监听器(watch)全部集中在一个对象里,学生调试时console.log(this.tableData)能立刻看到当前组件实例的所有状态,不像Composition API需要在setup里return一堆ref,调试时得翻reactive对象层层展开。Element UI的选择同样务实:它的el-table自带分页、排序、筛选,el-form提供rules校验规则和validate方法,el-dialog控制显隐状态——这些不是“必须用”的功能,而是把重复劳动封装成可配置项。比如课程表单的必填校验:

rules: { name: [{ required: true, message: '课程名称不能为空', trigger: 'blur' }], credit: [{ type: 'number', message: '学分必须为数字', trigger: 'blur' }] }

你只需改message提示文字,不用自己写正则判断输入框是否为空。更重要的是,Element UI的样式是CSS-in-JS实现,所有组件样式都在node_modules/element-ui/lib/theme-chalk里,你修改主题色只需覆盖变量(如$--color-primary: #409EFF),不会像Ant Design那样需要webpack重编译主题包。

2.4 数据库脚本的设计哲学:不追求范式完美,专注业务可读性

database.sql脚本里t_course表有teacher_id外键,但t_teacher表没有冗余的course_count字段;t_class_schedule排课表直接存course_idteacher_idroom_idweek_daysession_startsession_end六个字段,而不是拆成“课程-教师关联表”+“排课时段表”。这种设计不是偷懒,而是让SQL查询直白到小学生都能看懂。比如查某教师本周课表:

SELECT c.name AS course_name, r.room_number, cs.week_day, cs.session_start, cs.session_end FROM t_class_schedule cs JOIN t_course c ON cs.course_id = c.id JOIN t_room r ON cs.room_id = r.id WHERE cs.teacher_id = ? AND cs.week_day BETWEEN 1 AND 5;

如果拆成多层关联,这条SQL会变成四表JOIN,初学者光是理清表关系就要半小时。脚本里所有字段都加了中文注释:

CREATE TABLE `t_course` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(100) NOT NULL COMMENT '课程名称', `credit` int(11) NOT NULL DEFAULT '2' COMMENT '学分', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-启用,0-停用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程基本信息表';

这种注释不是形式主义,而是当你在IDEA里鼠标悬停字段时,能立刻看到“状态:1-启用,0-停用”,不用翻文档猜数字含义。更关键的是,脚本末尾的初始化数据:

INSERT INTO `t_user` VALUES (1,'admin','e10adc3949ba59abbe56e057f20f883e','ADMIN'); INSERT INTO `t_teacher` VALUES (1,'张教授','教授','2010-03-15',1);

密码e10adc3949ba59abbe56e057f20f883e是MD5(“123456”),这是故意为之——让你明白密码不是明文存储,且能通过DigestUtils.md5DigestAsHex("123456".getBytes())在Java里复现。这种设计让数据库不再是个黑盒,而是业务逻辑的具象化表达。

3. 核心模块实现详解:从数据库建表到前端交互的完整链路

3.1 数据库初始化:三步走,避开90%的部署失败

很多人导入SQL失败,根本原因不是SQL语法错,而是MySQL版本和字符集不匹配。这套系统的database.sql开头明确声明:

SET NAMES utf8mb4; CREATE DATABASE IF NOT EXISTS rainng_course DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE rainng_course;

这意味着你必须用MySQL 5.7.8+(支持utf8mb4)且创建数据库时指定字符集。实操步骤如下:

第一步:确认MySQL版本
在命令行执行:

mysql --version

若显示mysql Ver 8.0.33Ver 5.7.42,则符合要求;若为5.6.x,需升级。升级不是重装,而是下载对应平台的安装包(Windows用MSI,macOS用DMG),安装时勾选“Upgrade existing installation”。

第二步:创建数据库并导入
不要用Navicat右键“运行SQL文件”,而要用命令行确保字符集生效:

# 登录MySQL mysql -u root -p # 执行建库语句(复制database.sql开头的CREATE DATABASE部分) CREATE DATABASE IF NOT EXISTS rainng_course DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 退出MySQL EXIT; # 导入SQL(关键!指定字符集) mysql -u root -p --default-character-set=utf8mb4 rainng_course < database.sql

这里--default-character-set=utf8mb4是救命参数——它强制MySQL客户端用utf8mb4解析SQL文件,否则中文注释里的“教授”二字会被识别为乱码,导致建表失败。

第三步:验证数据完整性
导入后别急着启动,先检查关键表数据:

USE rainng_course; SELECT COUNT(*) FROM t_user; -- 应返回1(admin用户) SELECT COUNT(*) FROM t_teacher; -- 应返回1(张教授) SELECT * FROM t_course LIMIT 1; -- 查看字段值是否正常

t_user表为空,说明密码加密字段e10adc3949ba59abbe56e057f20f883e被截断(旧版MySQL varchar(32)不够存MD5),此时需手动修改t_userpassword字段长度:

ALTER TABLE t_user MODIFY COLUMN password VARCHAR(32);

提示:如果使用Docker运行MySQL,务必在docker-compose.yml中挂载配置文件:
yaml services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root volumes: - ./my.cnf:/etc/mysql/conf.d/my.cnf
my.cnf内容为:
ini [client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci

3.2 后端服务启动:绕过IDEA的坑,用Maven命令行保命

RainngCourseBEpom.xmlspring-boot-maven-plugin配置了<executable>true</executable>,这意味着你可以直接用java -jar运行,但初学者常犯两个错误:一是用IDEA的绿色三角形按钮启动(会忽略application.yml里的spring.profiles.active=dev),二是没配好MySQL连接池。正确的启动流程是:

第一步:修改数据库配置
打开RainngCourseBE/src/main/resources/application-dev.yml,找到:

spring: datasource: url: jdbc:mysql://localhost:3306/rainng_course?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: root

password: root改成你MySQL的实际密码(不是SQL文件里的admin密码)。注意URL里的serverTimezone=GMT%2B8不能删——这是解决“Caused by: java.sql.SQLException: The server time zone value ‘XXX’ is unrecognized”错误的关键。

第二步:清理并构建
不要点IDEA的“Rebuild Project”,而要打开终端执行:

cd RainngCourseBE mvn clean package -DskipTests

-DskipTests跳过单元测试(初学者的测试用例常因H2数据库配置失败),clean确保旧class文件被清除。生成的jar包在target/rainng-course-be-1.0.jar

第三步:命令行启动

java -jar target/rainng-course-be-1.0.jar --spring.profiles.active=dev

看到控制台输出Started RainngCourseBeApplication in X.XXX seconds即成功。此时访问http://localhost:8081/swagger-ui.html,能看到所有API文档——这是比写代码更重要的环节:点开CourseControllerGET /api/course,点击“Try it out”,再点“Execute”,如果返回JSON数据,证明后端已活。

注意:若报错java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException,说明你用了JDK 11+(JAXB被移除)。解决方案是在pom.xml添加:
xml <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency>

3.3 前端工程启动:npm install的隐藏陷阱与解决方案

RainngCourseFEpackage.jsondependenciesdevDependencies分开声明,这是刻意为之——element-uiaxios等运行时依赖在dependenciesvue-cli-servicesass-loader等构建工具在devDependencies。执行npm install时,npm会根据NODE_ENV决定安装哪些包,但初学者常遇到两种失败:

陷阱一:npm版本过高导致sass编译失败
npm install后运行npm run serve报错sass-loader requires sass,这是因为新版sass-loader(12+)要求Dart Sass,而老项目用的是Node Sass。解决方案是降级:

npm uninstall sass-loader npm install sass-loader@10.4.1 --save-dev

@10.4.1是最后一个兼容Node Sass的版本,它依赖node-sass@6.0.1,而node-sass需要Python 2.7和Visual Studio Build Tools(Windows)或Xcode Command Line Tools(macOS)。若嫌麻烦,直接用pnpm替代npm:

npm install -g pnpm pnpm install

pnpm的硬链接机制能避免重复下载,且对sass兼容性更好。

陷阱二:element-ui样式不生效
启动后页面只有文字没有按钮样式,检查浏览器开发者工具Elements面板,发现<link href="/css/chunk-vendors.css">404。这是因为Vue CLI的public/index.html里引用了<link rel="stylesheet" href="<%= BASE_URL %>css/chunk-vendors.css">,但实际打包后CSS文件在dist/css/目录。解决方案是修改vue.config.js

module.exports = { publicPath: './', // 关键!改为相对路径 outputDir: 'dist', assetsDir: 'static' }

publicPath: './'让所有静态资源路径以./开头(如./css/app.css),这样无论部署在http://localhost:8080/还是http://example.com/course/都能正确加载。

启动验证
执行npm run serve后,浏览器打开http://localhost:8080,输入admin/123456登录。此时观察Network面板:
- 登录请求POST /api/login返回200,响应体含token字段;
- 首页请求GET /api/user/info返回200,roles数组含["ADMIN"]
- 菜单栏“课程管理”点击后,发起GET /api/course?page=1&size=10,返回10条课程数据。
这三个请求全部成功,证明前后端通信链路已打通。

3.4 核心业务功能实现:以“班级排课”为例的端到端剖析

排课功能是教务系统最难啃的骨头,这套系统没用算法库,而是用三层校验保障合理性:

第一层:前端约束(防呆设计)
ScheduleForm.vue里,教室选择框el-select的选项来自/api/room接口,但加了禁用逻辑:

<el-select v-model="form.roomId" placeholder="请选择教室" :disabled="!form.courseId || !form.teacherId"> <el-option v-for="item in roomOptions" :key="item.id" :label="item.roomNumber" :value="item.id"/> </el-select>

意思是:没选课程和教师前,教室下拉框不可用。这种交互不是炫技,而是防止用户填完教室才发现课程还没选,减少无效操作。

第二层:后端校验(业务规则)
ScheduleService.javasaveSchedule()方法里:

// 校验教师当天是否有课 Long teacherBusyCount = scheduleRepository.countByTeacherIdAndWeekDayAndSessionRange( schedule.getTeacherId(), schedule.getWeekDay(), schedule.getSessionStart(), schedule.getSessionEnd()); if (teacherBusyCount > 0) { throw new BusinessException("该教师在选定时段已有课程安排"); } // 校验教室容量是否足够 Room room = roomRepository.findById(schedule.getRoomId()).orElseThrow(); Course course = courseRepository.findById(schedule.getCourseId()).orElseThrow(); if (course.getStudentCount() > room.getCapacity()) { throw new BusinessException("教室容量不足,请更换教室"); }

这里countByTeacherIdAndWeekDayAndSessionRange是JPA的衍生查询方法,它自动生成SQL:

SELECT COUNT(*) FROM t_class_schedule WHERE teacher_id = ? AND week_day = ? AND session_start <= ? AND session_end >= ?

session_start <= ? AND session_end >= ?是典型的时段重叠判断逻辑(当前课的开始时间≤已有课的结束时间,且当前课的结束时间≥已有课的开始时间),比简单比较时间字符串可靠得多。

第三层:数据库唯一索引(终极防线)
t_class_schedule表建有联合唯一索引:

ALTER TABLE `t_class_schedule` ADD UNIQUE KEY `uk_teacher_week_session` (`teacher_id`,`week_day`,`session_start`,`session_end`);

这意味着即使前端和后端校验都失效,数据库也会抛出Duplicate entry '1-2-08:00-09:40' for key 'uk_teacher_week_session'异常,保证数据一致性。这种“前端防呆+后端校验+数据库兜底”的三层防护,才是工业级排课的标配。

4. 部署与二次开发实战指南:从本地运行到上线交付

4.1 生产环境打包:前后端分离部署的黄金配置

本地开发用npm run servejava -jar没问题,但上线必须打包。很多人把前端dist目录扔进SpringBoot的src/main/resources/static,这是大忌——它会让SpringBoot同时承担静态资源服务和API服务,一旦流量激增,API响应会因静态文件IO阻塞。正确做法是Nginx反向代理分流

后端打包

cd RainngCourseBE mvn clean package -Pprod -DskipTests

-Pprod激活application-prod.yml,其中数据库配置指向生产库,server.port=8081保持不变。生成的jar包target/rainng-course-be-1.0.jar直接上传服务器。

前端打包

cd RainngCourseFE npm run build

生成dist/目录,把整个目录上传到服务器/var/www/course-fe/

Nginx配置(/etc/nginx/conf.d/course.conf)

upstream backend { server 127.0.0.1:8081; } server { listen 80; server_name course.example.com; # 前端静态资源 location / { root /var/www/course-fe; try_files $uri $uri/ /index.html; } # API接口代理 location /api/ { proxy_pass http://backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

关键点:location /api/的斜杠必须保留,否则proxy_pass http://backend/会把/api/course转发成http://backend/api/course(后端没/api前缀),正确转发是http://backend/course。重启Nginx后,访问http://course.example.com即可。

注意:若服务器内存小于2G,启动jar包时需限制JVM内存:
bash nohup java -Xms512m -Xmx1024m -jar rainng-course-be-1.0.jar --spring.profiles.active=prod > app.log 2>&1 &

4.2 权限系统扩展:从ADMIN/USER两级到多角色精细化控制

默认权限只有ADMIN和USER,但毕业设计常需扩展“教务员”“系主任”角色。扩展步骤如下:

第一步:数据库新增角色

INSERT INTO `t_role` VALUES (3, 'DEAN', '系主任'), (4, 'EDU_OFFICER', '教务员');

第二步:后端添加权限注解
CourseController.java的删除接口上加:

@PreAuthorize("hasRole('ADMIN') or hasRole('DEAN')") @DeleteMapping("/{id}") public Result delete(@PathVariable Long id) { // ... }

hasRole('DEAN')表示DEAN角色也能访问,无需修改SecurityConfig。

第三步:前端菜单动态渲染
src/store/modules/user.jssetRoles方法接收后端返回的roles数组(如["ADMIN", "DEAN"]),然后在src/layout/components/Sidebar.vue中:

<el-menu-item v-for="item in menuList" :key="item.path" v-if="checkRole(item.roles)"> <router-link :to="item.path">{{ item.title }}</router-link> </el-menu-item>

checkRole(roles)方法:

checkRole(roles) { return roles.some(role => this.roles.includes(role)); }

这样,DEAN角色登录后,菜单栏只显示“课程管理”“教师管理”“排课计划”,不显示“权限设置”。

4.3 功能扩展实操:给课程管理添加Excel导入导出

学生常提需求:“能不能批量导入课程?”这功能只需三步:

后端添加POI依赖
pom.xml加入:

<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency>

编写Excel导出接口
CourseController.java新增:

@GetMapping("/export") public void export(HttpServletResponse response) throws IOException { List<Course> courses = courseService.findAll(); XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("课程列表"); // 表头 XSSFRow headerRow = sheet.createRow(0); String[] headers = {"ID", "课程名称", "学分", "状态"}; for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 for (int i = 0; i < courses.size(); i++) { XSSFRow row = sheet.createRow(i + 1); Course course = courses.get(i); row.createCell(0).setCellValue(course.getId()); row.createCell(1).setCellValue(course.getName()); row.createCell(2).setCellValue(course.getCredit()); row.createCell(3).setCellValue(course.getStatus() == 1 ? "启用" : "停用"); } response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=courses.xlsx"); workbook.write(response.getOutputStream()); }

前端添加导出按钮
CourseList.vue里:

<el-button type="primary" @click="exportExcel">导出Excel</el-button>
methods: { async exportExcel() { try { const res = await this.$http.get('/api/course/export', { responseType: 'blob' }); const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = 'courses.xlsx'; link.click(); } catch (error) { this.$message.error('导出失败'); } } }

这段代码的精髓在于responseType: 'blob'——它告诉axios不要把二进制流转成JSON,而是保持原始字节流,否则Excel文件会损坏。

5. 常见问题排查与避坑指南:那些文档里不会写的血泪经验

5.1 “登录成功但首页空白”的五大原因与速查表

现象可能原因排查命令解决方案
Network面板无任何请求前端路由模式错误检查router/index.jsmode: 'history'改为mode: 'hash',URL变成/#/course
请求/api/user/info返回401Token未携带或过期浏览器Application→Storage→Cookies,查看JSESSIONID是否存在清除Cookie后重新登录;检查后端SecurityConfig是否放行/api/user/info
请求返回200但响应体为空后端跨域配置缺失查看响应头是否有Access-Control-Allow-OriginCorsConfig.java里添加registry.addMapping("/api/**").allowedOrigins("*")
表格显示“暂无数据”分页参数传递错误Network→Headers→Query String Parameters,看是否有pagesize检查CourseList.vuefetchCourses()的params是否传入this.pagination.currentPage
页面样式错乱Element UI未正确引入Elements面板搜索<link href="...element-ui..."确认main.jsimport 'element-ui/lib/theme-chalk/index.css'未被注释

5.2 MySQL导入失败的“字符集三连击”

我帮学生远程调试时,90%的SQL导入失败源于字符集。请按顺序执行这三步:

第一步:检查MySQL全局变量

SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%';

character_set_server不是utf8mb4,执行:

SET GLOBAL character_set_server = utf8mb4; SET GLOBAL collation_server = utf8mb4_unicode_ci;

第二步:检查数据库字符集

SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'rainng_course';

若不是utf8mb4,执行:

ALTER DATABASE rainng_course CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

第三步:检查表字符集

SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'rainng_course';

若有表是utf8_general_ci,逐个修复:

ALTER TABLE t_course CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

5.3 Vue CLI代理失效的“端口迷雾”

开发时前端请求/api/course,后端却收不到,常见于端口冲突。速查方法:

确认后端真实端口
RainngCourseBE/src/main/resources/application-dev.yml里:

server: port: 8081

不是8080!很多学生以为SpringBoot默认8080,其实application.yml里可能改了。

确认代理目标端口
vue.config.js里:

proxy: { '/api': { target: 'http://localhost:8081', // 必须和后端port一致 // ... } }

确认前端启动端口
package.jsonscripts

"serve": "vue-cli-service serve --port 8080"

若没指定--port,默认8080,此时浏览器开http://localhost:8080,代理才能生效。

5.4 毕业设计答辩高频问题预演

Q:为什么用JPA而不是MyBatis?
A:JPA的@Entity注解让Java对象与数据库表一一对应,初学者修改字段时,只需改实体类的@Column和数据库SQL,不用同步维护XML映射文件。更重要的是,JPA的Pageable分页比MyBatis手写LIMIT ? OFFSET ?更不易出错。

Q:排课算法怎么保证不冲突?
A:我们没用遗传算法或约束编程,而是用三层校验:前端禁用冲突时段选项、后端用SQLCOUNT查重叠、数据库建唯一索引兜底。这比复杂算法更可靠,因为教务排课的核心是“不允许冲突”,而不是“寻找最优解”。

Q:如何保证管理员密码安全?
A:密码用MD5加盐存储(DigestUtils.md5DigestAsHex((password + "rainng_salt").getBytes())),且application.yml里数据库密码用ENC(XXXX)加密,需配合Jasypt启动参数解密。

Q:系统能支撑多少并发?
A:单机部署下,MySQL连接池设为20,SpringBoot内嵌Tomcat线程池设为200,理论QPS约300。若需更高并发,可加Redis缓存课程列表(@Cacheable注解),将数据库压力降至5%以下。

我在实际带毕设时发现,学生最怕的不是写不出功能,而是答辩时被问住。提前想清楚这四个问题,你的答辩通过率至少提升50%。

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

简介:提供一套可直接运行的课程管理全栈项目,后端用SpringBoot开发,前端基于Vue.js搭配Element UI实现响应式界面,前后端完全分离。包含完整的后端服务模块(RainngCourseBE)、前端工程(RainngCourseFE)、MySQL建库建表SQL脚本、详细启动说明文档以及技术截图PDF。默认管理员账号为admin,密码123456,导入SQL后即可启动后端服务和前端项目。功能覆盖课程信息增删改查、教师与学生基础信息维护、班级排课逻辑、用户角色权限控制等典型教务场景。代码结构清晰,无冗余文件,保留.gitignore和.idea等开发配置但不影响打包部署;node_modules未上传,需npm install自行安装依赖。适合Java或Vue初学者实战练习,也适合作为高校计算机类专业毕业设计参考模板,支持快速二次开发与功能扩展。


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