ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于SpringBoot+Vue+MySQL的大学生网络教学系统全流程实战

基于SpringBoot+Vue+MySQL的大学生网络教学系统全流程实战 站在一个刚带完一届毕业设计的过来人角度我太明白你们拿到“基于SpringBootVueMySQL的大学生计算机基础网络教学系统”这类题目时的状态了题目看得懂框架名字都听过但真要动手写代码脑子里全是“从哪开始”的问号。这篇文章我就把这一整套流程完整走一遍从需求拆解、数据库设计到后端接口、前端页面最后到部署和答辩准备每一步都附上我实际踩过的坑和解决办法你可以直接照着干。这个系统本质上就是一个简化版在线学习平台核心用户有三类学生、教师、管理员。学生要能看课件、看视频、做作业、参加考试教师要能管课程、传课件、布置作业批改作业、录题组卷管理员负责用户管理、数据统计、公告维护。至于为什么选SpringBootVueMySQL这个组合原因很现实市场认可度高、资料多、招聘要求里常见而且这三个技术配合模板化程度高一个人完全Hold得住。它是绝大多数计算机专业学生能在半年内独立完成并且讲得清楚的项目所以别纠结“太简单”还是“太难”做到位就是好毕设。1. 项目整体规划与需求拆解1.1 系统边界与功能模块划分拿到题目先别急着写代码第一步是明确“做什么、不做什么”。我的建议是画一张功能树把整个系统切成学生端、教师端、管理端三个子模块每个端再做二级功能拆分。以“计算机基础网络教学系统”为例我的功能清单是这样的学生端课程浏览与选课、学习资料下载、视频课程在线播放、在线作业提交、在线考试答题、成绩查询、通知公告查看教师端课程管理增删改查、课件与视频上传、作业布置与批改、题库管理、试卷生成与发布、成绩统计导出管理端用户管理学生、教师账号维护、课程分类管理、公告管理、系统数据概览这里有个小技巧毕设题目里的“网络教学系统”听着很大但你不必真的做成慕课网那种规模。把“在线考试作业资源管理”三条线打通再配合视频播放、文件上传这些亮点功能答辩时完全够讲。1.2 技术选型理由与架构思路很多人选型只会写“用了SpringBoot”但答辩老师一定会问“为什么这么选”。我的回答思路是这样的SpringBoot简化Spring配置内置Tomcat能快速构建RESTful API和MyBatis-Plus配合写CRUD非常省代码Vue组件化开发数据双向绑定让页面交互不用频繁操作DOM配合Element UI能做到两天搭完管理后台。我建议用Vue 2 Vue CLI或者Vue 3 Vite都行但如果你照着网上教程自学Vue 2能搜到的资料数量远超Vue 3稳妥起见我用的Vue 2MySQL主流关系型数据库安装维护资料多事务支持可靠教学系统的结构化数据用户、成绩、课程用关系模型再合适不过架构上就是标准的前后端分离前端Vue工程通过Axios请求后端接口后端SpringBoot处理业务并返回JSONMySQL负责数据持久化。文件上传我额外用了一个本地磁盘目录存储课件和视频没有上OSS是因为穷学生没有稳定的云服务预算后面我会详细说这个方案的利弊。2. 数据库设计与开发环境搭建2.1 核心数据表设计数据库设计是毕业设计里最能体现“功底”的部分答辩老师最喜欢盯着表关系问。我最终设计了9张核心表先列几个重要的表名用途关键字段sys_user用户表学生/教师共用一个表user_id, username, password, role, real_namecourse课程表course_id, course_name, teacher_id, category, cover_url, introcourse_chapter课程章节表chapter_id, course_id, chapter_name, chapter_orderresource课件/视频资源表resource_id, chapter_id, resource_name, resource_type, resource_urlhomework作业表homework_id, course_id, title, deadline, contenthomework_submit作业提交表submit_id, homework_id, student_id, submit_content, submit_time, grade, commentexam考试表exam_id, course_id, title, start_time, end_time, durationexam_question题目表question_id, course_id, question_type, question_content, options, answer, scoreexam_record考试记录表record_id, exam_id, student_id, score, submit_time, answers_json这里必须说一个我当初踩过的坑用户表千万别设计成学生表、教师表两张表。你发现后期加一个“管理员”时要动的地方翻倍而用role字段区分角色代码里一个判断就搞定了。2.2 物理外键与逻辑外键的选择我还被答辩老师问住过一次“你的外键怎么没有索引”后来我才意识到MyBatis-Plus分页查询时JOIN操作对没有索引的外键字段性能影响很明显。我的解决办法是所有关联字段如course表的teacher_id都加上普通索引但不使用数据库物理外键约束而是在Service层做逻辑判断。好处是写测试数据方便、删数据不报外键错误坏处是要自己保证数据一致性控制好代码逻辑就行。这个也是业内很常见的做法可以大大减少开发期“莫名其妙外键冲突”的问题。2.3 MySQL安装配置与Navicat连接排查MySQL环境配置这一关就能卡掉一半人。下载安装包建议选MySQL 5.7或8.0注意安装时选“Server only”避免装一堆用不到的组件。root账户密码一定要记好建议设置成简单但自己能背下来的组合比如root/123456毕设项目不涉及生产安全可以适当降低密码复杂度。我遇到过一个非常典型的启动问题执行mysql -uroot -p报错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock这种80%的情况是MySQL服务没有启动。Linux系统下执行systemctl start mysqldMac下用brew services start mysqlWindows下检查“服务”管理器里MySQL是否启动。另外Navicat连接本地报错“Host localhost is not allowed to connect”那就是root账户的host权限限制执行一条SQL就可以解决ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;2.4 开发环境依赖版本选择再说一下开发工具版本组合这个也是容易折腾人的组件版本建议JDK1.8或11SpringBoot2.5.x千万不要一上来用3.x有很多兼容性差异MySQL5.7或8.0Node.js14.x到16.xVue CLI4.x或5.xMyBatis-Plus3.5.xSpringBoot版本过高会带来很多麻烦比如javax.servlet变成jakarta.servlet导致代码提示报错、第三方组件未适配等。所以建议锁死SpringBoot 2.5.x版本资源最多规避问题最稳妥。3. 后端SpringBoot核心实现3.1 工程项目结构与分层思路后端我习惯用的包结构如下src/main/java/com/example/teaching/ ├── controller/ // 控制层接收请求、返回结果 ├── service/ // 业务层核心逻辑实现 │ └── impl/ ├── mapper/ // MyBatis-Plus数据访问层 ├── entity/ // 数据库实体类 ├── dto/ // 前端请求参数封装 ├── vo/ // 前端响应结果封装 ├── config/ // 配置类CORS、拦截器、文件资源映射等 ├── common/ // 返回结果封装、全局异常、常量 └── util/ // 工具类JWT、文件处理分层目的在于职责分离controller层保持轻薄尽量只做参数接收和响应包装业务判断放service层dao层只用MyBatis-Plus的BaseMapper自带CRUD。这样答辩时你讲代码结构会非常清晰。3.2 JWT认证与拦截器设计教学系统的接口不能裸奔用户必须登录后才能访问对应资源。我用的方案是JWTJSON Web Token HandlerInterceptor。JWT生成思路String token Jwts.builder() .setSubject(String.valueOf(user.getUserId())) .claim(username, user.getUsername()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 120)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();后端写一个AuthInterceptor在preHandle阶段从请求头取token解析成功就把用户ID放到ThreadLocal或请求attribute里放行解析失败直接返回401状态码。记得在WebMvcConfig中注册拦截器并排除登录、注册接口和静态资源路径。这里有个常见坑前端测试接口时如果请求头不是“Authorization”两个字而是“token”后端拦截器就会一直提示未登录。解决方案是前后端约定一致建议使用“Authorization”并带上“Bearer ”前缀代码里再统一处理。3.3 统一响应结果与全局异常处理毕设代码最容易显得业余的地方就是接口返回值五花八门。有的返回Map有的直接返回String前端判断得晕头转向。我的做法是写一个统一的Result对象Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(success); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } }同时用RestControllerAdvice实现全局异常处理器业务异常统一返回Result.error。这样前端Axios拦截器只需要判断code即可。3.4 MyBatis-Plus分页插件使用细节列表页几乎都要分页MyBatis-Plus的分页插件用起来极其顺手但也有坑。首先必须配置分页插件不配置的话分页查询不生效Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后业务里这样用PageCourse page new Page(current, size); LambdaQueryWrapperCourse wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Course::getCourseName, keyword); PageCourse result courseMapper.selectPage(page, wrapper); return new PageResult(result.getRecords(), result.getTotal());细节在于模糊查询时一定要判断keyword不为空否则会生成where course_name like %%查全表还影响性能。总记录数这里用selectPage会自动查询count不用担心。3.5 XSS过滤器与上传文件安全处理热词里有“SpringBoot项目全局过滤器处理上传pdf文件时xss攻击”这块确实是容易被忽略的安全点。我的项目里做了两件事第一写了一个XssFilter拦截所有请求对JSON字符串中的特殊字符做转义。实现方式是用OncePerRequestFilter包一层自定义HttpServletRequestWrapper重写getParameter和getInputStream用HtmlUtils.htmlEscape处理尖括号等字符。第二上传PDF或Office文件时要校验文件后缀和Content-Type防止有人传个伪装成PDF的jsp或html脚本。参考代码如下String ext filename.substring(filename.lastIndexOf(.) 1).toLowerCase(); if (!Arrays.asList(pdf, doc, docx, ppt, pptx, mp4).contains(ext)) { throw new BusinessException(不支持的文件类型); }3.6 文件存储方案网上教学系统资源文件多我最初想用MinIO做对象存储。MinIO的Docker部署并不难几行命令就能拉起来docker run -p 9000:9000 -p 9001:9001 --name minio \ -e MINIO_ROOT_USERadmin -e MINIO_ROOT_PASSWORDadmin123 \ -v /data/minio:/data \ minio/minio server /data --console-address :9001SpringBoot集成时引入minio依赖然后写一个MinioUtil封装上传、下载、删除、生成临时链接等方法。但实际开发过程中我发现毕设服务器内存不够时MinIO每次启动占内存不小而且答辩现场演示极容易因为网络问题打开不了临时链接。最终我采用了本地磁盘存储方案配置一个自定义上传目录比如D:/upload/设置静态资源映射后端存储相对路径访问时通过映射URL读取文件。这个方案部署简单答辩不会因为依赖服务出问题而翻车。实现方式Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: fileUploadPath /); } }前端上传文件走后端接口返回文件URL再保存到数据库资源表里。视频播放接口也是走的/files/xxx.mp4路径支持HTTP Range请求Vue里的video标签可以直接播放。4. 前端Vue核心实现4.1 环境安装与项目初始化前端这块热词里“vue安装及环境配置”“vue安装依赖”都是高频搜索。先说环境安装Node.js去官网下载LTS版本一路默认安装。然后安装Vue CLInpm install -g vue/cli创建项目vue create teaching-frontend创建时选Manually select features勾选Router、Vuex、AxiosAxios要自己再装。然后安装UI组件库npm install element-ui --save npm install axios --save依赖安装失败的常见解决方案是把npm源换成国内镜像npm config set registry https://registry.npmmirror.com再删除node_modules目录重新执行npm install。遇到过node-sass安装报错的同学建议直接不要用scss语法或者采用dart-sass替代毕设样式用普通css和Element UI完全够。4.2 路由设计与动态权限控制Vue Router是前端的核心骨架。我的路由分三类静态路由登录页、首页、学生路由、教师路由。由于项目没有复杂到用后端动态下发菜单我直接在路由的meta里写入角色信息然后在全局守卫里判断router.beforeEach((to, from, next) { const token sessionStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } const role sessionStorage.getItem(role) if (to.meta.roles !to.meta.roles.includes(role)) { next(/403) return } next() })当时我踩过的一个坑是路由模式使用history后项目部署到服务器刷新页面404需要Nginx配置try_files解决第6节会讲到。4.3 Axios请求封装Axios如果每次请求都写一遍请求头代码又乱又容易漏。我封装了一个request.jsimport axios from axios import { Message } from element-ui import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token sessionStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code 200) { return res.data } Message.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) }, error { if (error.response error.response.status 401) { sessionStorage.clear() router.push(/login) } Message.error(网络异常请稍后重试) return Promise.reject(error) } ) export default requestbaseURL设置为/api前端开发时通过vue.config.js的proxy把/api代理到后端8080端口这样解决跨域问题。生产环境交给Nginx统一转发前端代码里不需要写死后端IP。4.4 视频播放与M3U8格式问题热词里“vue播放m3u8”出现频率很高说明很多人在视频点播上被卡住了。教学系统资源里如果有HLS切片视频m3u8后缀直接使用Video标签无法播放。我当时用的方案是video.js videojs-contrib-hls插件npm install video.js videojs-contrib-hls --save页面里这样引入import Video from video.js import video.js/dist/video-js.css import videojs-contrib-hls mounted() { this.player Video(this.$refs.videoPlayer, { sources: [{ src: this.videoUrl, type: application/x-mpegURL }], controls: true, autoplay: false }) }, beforeDestroy() { if (this.player) { this.player.dispose() } }但如果你是播放服务器本地MP4文件直接用video标签加controls再设定src地址即可无需任何插件。这点得先分清。4.5 Element UI表单校验与管理端页面快速搭建管理系统大量页面无非就是“表格搜索弹窗表单”。Element UI的el-table配合el-pagination加上el-form的rules校验能应付80%的页面需求。表单校验注意两点数字输入用:rules中的type: number上传文件的组件用before-upload钩子先做拦截。管理端的菜单结构建议使用el-menu的router模式每个菜单项index直接写路由地址省去手写点击跳转逻辑。4.6 Vue DevTools调试技巧调试前端时Vue DevTools是神器。装的时候要确认和Vue版本匹配Vue 2对应Vue DevTools 6.x版本Vue 3对应Vue DevTools 7.x以上版本。在Chrome扩展商店直接装是最省事的方式如果商店打不开就下载crx文件手动拖入扩展页面打开时记得勾选“允许访问文件网址”。实际调试中我用得最多的是Vuex面板查看状态变更、Components面板动态修改data中的值而不刷新页面。5. 前后端联调与部署上线5.1 联调阶段的Mock数据切换前后端并行开发时前端经常因为后端接口没写好而卡住。我的做法是前端写了一个mock目录用axios的适配器模式在开发早期切换mock数据后端接口好了以后只需要改一行代码切换回真实请求。具体实现是判断请求路径是否在mock配置列表中如果在则返回本地数据if (process.env.VUE_APP_USE_MOCK true) { require(./mock/index) }这里的拦截放axios拦截器之前mock数据返回结构保证和真实接口一致所以页面代码完全不用改。5.2 SpringBoot后端打包后端打包很简单但要注意跳过测试mvn clean package -DskipTests打包后target目录下会生成teaching-system.jar。本地直接运行java -jar teaching-system.jar如果jar包运行后端口8080被占用用以下命令查看占用进程netstat -ano | findstr 8080 # Windows lsof -i :8080 # Mac/Linux然后结束对应进程或者在后端配置文件修改server.port。生产环境我建议部署到Linux服务器上配合nohup后台启动nohup java -jar /opt/teaching/teaching-system.jar --server.port8090 /opt/teaching/log.log 21 5.3 Nginx配置Vue静态资源与反向代理前端打包npm run build生成dist目录里面是纯静态文件。Nginx配置如下server { listen 80; server_name localhost; root /opt/teaching/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /files/ { proxy_pass http://127.0.0.1:8090/files/; } }这里最关键的坑就是try_files那行。如果不加刷新某个路由页面时Nginx会返回404因为Vue是单页应用路由切换全靠前端后端没有对应的文件路径。try_files $uri $uri/ /index.html的作用就是找不到对应文件时把请求交给index.html处理。5.4 跨域问题与CORS配置前后端联调时最常见的报错是“CORS policy: No Access-Control-Allow-Origin header”。出现这个情况时笔者首先检查浏览器发出的请求URL是不是跨域的。前端开发环境请求路径/api/xxx经过proxy转发一般不会跨域如果配置没生效可以检查vue.config.js是否放在了项目根目录。后端也要配置CORS两种方式一种是写配置类实现WebMvcConfigurer的addCorsMappings另一种是用CrossOrigin注解。全局放法的配置更省心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); } }5.5 答辩演示环境准备答辩当天最怕的就是网络波动、服务器宕机、演示页面打不开。我的三条建议备份全部代码到U盘和网盘数据库导出一份SQL文件本地搭建一份完整环境万一服务器挂了马上切换演示设计好演示脚本先跑通学生端核心流程登录—选课—看视频—做作业—查成绩再演示教师端发作业—改作业—发布考试最后展示管理员数据统计演示的时候也不要猴急点这点那一步一步按脚本走给老师留出看清每个页面和操作结果的时间。每次操作前说一句“我接下来演示什么功能”答辩效果会明显更好。6. 常见问题排查与答辩重点梳理6.1 开发期高频报错速查表我把带毕设过程中学生遇到最多的坑整理成了一张表报错/现象根本原因解决方案Port 8080 was already in use端口被占用换端口或kill进程java.sql.SQLException: Access denied for user rootlocalhost数据库密码配置错误检查application.yml中数据库账号密码Table xxx doesnt exist数据库没有导入SQL或表名字段大小写不一致执行导入脚本MySQL表名和实体类名保持一致Invalid bound statement (not found)Mapper方法没有对应XML检查Mapper接口与XML的namespace和idCannot find module node-sass依赖安装失败或切换Node版本删除node_modules重新安装换dart-sassCannot read property xxx of undefined前端拿到空数据检查后端返回结构是否套了两层dataFailed to resolve component路由懒加载路径写错检查import路径大小写404 Not Foundwhen点击按钮后端接口路径错误核对前后端URL用控制台网络请求看具体404路径net::ERR_CONNECTION_REFUSEDLinux服务器防火墙没放行或云服务器安全组未开放端口放行对应端口配置安全组规则中文消息显示乱码数据库字符集/请求编码问题设置MySQL的utf8mb4后端设置CharacterEncodingFilterunknown database teachingNavicat连接库名拼错库不存在先用root连接创建对应数据库MySQL 8.0 caching_sha2_password连接失败旧版JDBC驱动不支持新加密规则升级mysql-connector-java版本或用mysql_native_passwordNo serializer found for class ...JSON序列化实体时包含没有getter的属性给实体类字段加Data注解或者用JsonIgnore排除不需要序列化的字段修改了代码但页面没变化浏览器缓存了打包后的js强制刷新CtrlShiftR前后端打包发布时刷新缓存的Nginx配置npm install很慢或报错npm源太慢切换为镜像源后重装MySQLERROR 1045 (28000): Access denied for user ... (using password: YES)密码错误或root账号host限制检查密码输入状态确认host为%或localhost6.2 MyBatis-Plus分页插件不生效分页插件配置了但还是全量返回发现请求total一直是0大概率是因为没有注入MybatisPlusInterceptor。很多教程只写了selectPage方法但没有把PaginationInnerInterceptor注册到Spring容器导致分页参数没被插件解析。加了上一节代码里的Bean配置基本能解决。另外还要注意不要同时引入PageHelper两个分页插件会冲突。6.3 时间字段处理的坑后端返回日期格式是2024-05-01T12:00:00.00000:00这种带T的格式前端展示非常丑。解决方案是加一个配置类Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder - builder .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))) .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); } }如果MySQL的datetime字段存进去比本地时间差8小时八成是时区配置问题。在JDBC连接URL上加上serverTimezoneAsia/Shanghai即可。6.4 答辩必问问题准备清单回答技术问题有自己的框架几句话把自己做的事情和为什么这么做讲清楚。下面几个问题几乎是必问的为什么选择前后端分离开发我的答法职责清晰前后端可以并行开发部署灵活而且这也是目前企业主流的开发模式。数据库如何保证数据完整性答设置了主外键逻辑关联在Service层对关键操作做了事务管理比如学生提交考试试卷时一次性写入考试记录和明细部分约束通过代码校验。JWT和Session的区别答JWT无状态不需要在服务端存储会话信息适合前后端分离和分布式部署Session需要依赖服务端存储会有会话粘滞问题。密码是如何存储的答用MD5加盐或BCrypt加密数据库不存明文。建议代码里用BCryptPasswordEncoder答辩时回答“使用加盐哈希加密”会显得更专业。某个复杂查询例如考试排行榜怎么实现的答先用SQL写子查询/多表JOIN分页查询后用Java在内存中做排序必要时用Redis缓存热点数据。6.5 毕业设计加分项建议如果你的进度较快可以在系统上追加几个“小而亮”的功能点来提升分数在线编辑器做题在试卷详情页嵌CodeMirror或Monaco Editor模拟程序设计类课程的代码题相似度检测作业提交后调用一个简单的文本相似度算法如余弦相似度展示重复率数据可视化使用ECharts在管理端展示学生成绩分布、课程选课人数趋势验证码登录集成kaptcha或easy-captcha提升登录模块完成度登录错误次数限制防止暴力破解连续错误5次锁定10分钟这些功能每一个都不难但写进毕业设计说明书里工作量数据会漂亮很多。7. 写在最后的一些实在建议这一套从需求到数据库从后端到前端从部署到答辩的完整流程是我带多人做完这个题目之后总结出的最省时间路径。按照这个顺序走前两周搭建好前后端骨架和数据库表第三四周完成核心业务逻辑第五周做资源管理和视频播放第六周联调起所有功能第七周写文档、做测试。节奏安排妥当不会每天迷茫自己该干什么。过程中我自己的体会是不要憋大招不要等“我把所有知识学懂了再动手”而是先跑通最小闭环也就是“学生能够登录—选课—看到课件”这一个小流程。哪怕页面做得残缺难看哪怕数据库只有四张表只要你把链路跑通了后面每一个功能都是往里填。代码写着写着自然就会了。最后再分享一个小技巧每天写代码前花十分钟想清楚今天的输出物是什么收工前跑一遍现有的功能和记录日志。这样你攒下来的不只是代码还有每一处调试的记录。答辩评审老师问“你在做这个项目的过程中遇到什么困难时”你随手就能讲出一两个真实案例和解决过程这比背一百条八股文都管用。毕业设计这件事做到比做好更重要做完整比做惊艳更重要。希望你顺利通关。
返回列表