资讯动态

企业级教学辅助平台管理系统:SpringBoot+Vue全栈开发与部署实践

发布时间:2026/10/17 14:51:23 来源:尧图企业网站定制
“企业级教学辅助平台管理系统”这个词听起来可能有点唬人但我拆开讲其实就是一套给学校、培训机构或在线教育公司用的后台管理工具。老师和教务人员不用再拿Excel登记成绩、手动排课、盯着纸质签到表发愁学生也能在网页上完成选课、查分、交作业这些操作。整个项目用SpringBoot做后端、Vue做前端、MyBatis操作MySQL数据库是标准的Java全栈架构在GitHub上这类项目的Star数一直很高也是很多毕业设计和中小型企业内部系统的理想原型。我前前后后带团队搭建过两套类似的系统一套给某高校的继续教育学院另一套给一个连锁培训机构。中间踩过很多坑比如权限模型设计得太复杂结果后期根本维护不动又比如分页查询忘记处理缓存数据库直接被打爆。这篇博文我就按实际开发顺序从架构设计到数据库建表再到前后端联调和部署上线把这一整套系统的关键细节和可行的实施方案完整梳理一遍。如果你是Java后端想转全栈或者正在找一套能用的教学管理源码做参考这篇文章应该能让你少走不少弯路。1. 项目定位与核心业务模块拆解在动手写代码之前先把“教学辅助平台”到底要管哪些事想清楚。我发现很多人一上来就画ER图结果画了十几个表做出来一堆谁都不会用的功能。真正跑得起来的系统核心业务模块就五个用户与权限、课程与排课、选课管理、成绩管理、教学互动考勤/作业/考试。先把这几个模块的边界划清楚后面每一步都会轻松很多。1.1 用户角色与权限模型设计教育类系统的主角无非三类学生、教师、教务管理员。但实际运营中还会有辅导员、院系负责人、助教这些派生角色。我建议一开始就采用RBAC基于角色的访问控制模型不要给单个用户直接分配权限那样后期调整权限时会非常痛苦。RBAC的核心思想很简单用户挂在角色上角色绑定权限菜单权限菜单对应后端接口。具体库表设计上核心是五张表sys_user用户主表存放登录账号、密码加密串、用户类型sys_role角色表sys_user_role用户-角色关联表sys_menu菜单权限表每个菜单项对应一个前端路由和一个后端接口标识sys_role_menu角色-菜单关联表菜单权限表我建议用树形结构存储用一个parent_id字段表示父子关系。前端拿到当前用户的菜单列表后动态生成左侧导航栏和可访问的路由后端拿到用户请求时通过拦截器校验该用户角色是否有对应接口的访问权限。这样一套组合拳下来权限基本不会出漏洞。很多新手容易踩的坑是把权限校验逻辑写死在业务代码里比如在Service层里写“if (user.getType() 1)”。这种做法在小项目里很灵活但业务一复杂光找这些散落的判断条件就要翻半天代码。我强烈建议把权限判断统一收敛到拦截器或注解里业务层专注处理业务不要掺杂权限判断。1.2 课程与排课的数据结构设计课程模块是教学系统的中枢神经。课程本身有基本信息比如课程名称、课程代码、学分、授课教师、上课时间、上课地点、限选人数。一个学生可以选多门课一个教师可以教多门课一门课可以被多个学生选所以课程与学生之间明显是多对多关系必须通过选课中间表来关联。课程的时间安排需要特别谨慎。我见过不少系统把上课时间做成一个字符串比如“周一 3-4节 周五 1-2节”查询课程冲突时根本没有办法计算。比较稳妥的做法是拆成多个字段week_day星期几取值1到7section_start开始节次section_end结束节次week_start开始周次如第1周week_end结束周次如第16周week_interval间隔默认1表示每周都有部分课程可以设置单双周这样查课表冲突时SQL直接做重叠判断。选课表设计成course_schedule和course_elective选课前校验该时间是否与已有课表冲突这部分逻辑虽然多一点但能明显减少线下教务争议。1.3 成绩管理如何控制好粒度成绩管理看上去很简单就是存一个分数但实际做起来有取舍。一般有两种粒度按课程维度直接给总分或者按课程下挂多个成绩项平时成绩、实验成绩、期末成绩再按比例合成总评。第二种做法更贴近学校真实需求我用的就是第二种。具体表设计是成绩主表score_record和成绩明细表score_detail。主表存学生、课程、学期、总评成绩明细表存各个成绩项的名称、分项成绩、占比权重。录入页面做成一个动态表格教务或教师可以自己配置成绩项比如某课程设置“平时30% 期中20% 期末50%”另一门课可能只设置“期末100%”。1.4 教学互动模块的统一设想考勤、作业、考试这三个功能看起来独立但它们的共同点是都围绕“课程-学生”这条线展开。我建议把它们统一挂在课程下面通过course_id关联这样天然就能查出一门课的所有考勤记录、作业记录和考试安排。考勤表attendance_record至少需要字段课程安排ID、学生ID、考勤日期、节次、考勤状态正常/迟到/缺勤/请假。教师端上课时快速勾选系统按周统计出勤率。作业表assignment和作业提交表assignment_submit需要分开设计因为一份作业对应全班学生每个学生提交情况完全不同。作业提交表记录学生提交时间、提交内容附件路径、批改分数、批改评语。这里有一个容易遗漏的点——补交通道需要给作业设置一个截止时间截止后学生不可再提交但允许教师手动开启个别学生的补交权限。2. 核心表结构落地与SQL优化细节数据库设计直接决定了系统的性能上限和开发效率。这一部分我按实际建表的顺序来梳理特别标注哪些字段容易遗漏哪些索引不加就会埋雷。2.1 数据库整体表结构总览整套系统的表数量控制在30张以内是比较合理的。核心表分成四大组系统管理组sys_user、sys_role、sys_user_role、sys_menu、sys_role_menu、sys_oper_log基础资料组student_info、teacher_info、class_info、college_info、major_info教学业务组course_info、course_schedule、course_elective、score_record、score_detail、attendance_record互动辅助组assignment、assignment_submit、exam_info、exam_question、exam_record、notice_info学生和教师的信息尽量放到独立表里不要都塞在sys_user里。sys_user只保留账号认证所需的字段比如用户名、密码、状态、最后登录时间。学生表额外存学号、班级、入学年份、培养层次教师表额外存工号、职称、所属院系。用户表和师生信息表通过user_id字段关联这样权限认证和学生资料维护的边界就分清了。2.2 选课表的去重约束与并发预防选课表是典型的高并发热点。开放选课的那几分钟如果系统设计不好可能会出现一个学生选同一门课选多次或者选课人数超过限选人数。我的做法是在创建表时给course_elective表加唯一联合索引字段组合是student_id, course_schedule_id从数据库层面防止重复选课。这个设计看似简单但很多开发者在初期建表的时候不太注意等出问题后再加索引已有数据可能已经产生重复处理起来相当被动。同时在选课Service层写入前先判断选课人数是否已达上限判断语句用SELECT COUNT加上课程状态检查。需要注意的是单纯用SELECT COUNT会有并发间隙两个请求同时查到999人限选1000人然后都执行插入就会超员。稳妥的做法是在事务中用SELECT ... FOR UPDATE锁住课程行强制串行化。这个处理在高并发场景下会有轻微的性能损耗但选课这种场景把人数算准远比比响应速度重要。2.3 MyBatis动态SQL在实际查询中的应用MyBatis在这套系统里最大的价值是动态SQL不需要在Java代码里拼接查询条件。比如教师端查看选课学生列表可能需要按学号、姓名、班级、选课状态任意组合筛选用以下这种动态SQL写法非常干净。select idselectElectiveList resultTypemap SELECT e.id, e.course_id, e.student_id, e.status, e.create_time, s.student_no, s.student_name, c.class_name FROM course_elective e LEFT JOIN student_info s ON e.student_id s.id LEFT JOIN class_info c ON s.class_id c.id where if testcourseId ! null AND e.course_id #{courseId} /if if teststudentNo ! null and studentNo ! AND s.student_no LIKE CONCAT(%, #{studentNo}, %) /if if teststudentName ! null and studentName ! AND s.student_name LIKE CONCAT(%, #{studentName}, %) /if if testclassName ! null and className ! AND c.class_name #{className} /if if teststatus ! null AND e.status #{status} /if /where ORDER BY e.create_time DESC /select这里有几个细节值得注意LIKE模糊查询用CONCAT拼接不要直接在SQL里写死%参数避免参数中包含通配符导致查询异常LEFT JOIN关联查询时如果驱动表很小MyBatis会在内存中做嵌套循环但关联表有索引的情况下性能完全够用排序字段用e.create_time而不是业务时间字段因为业务字段格式化后是字符串排序会按字典序排结果不一定是想要的时间顺序。2.4 数据库索引配置与慢查询排查这套系统最常见的慢SQL是两类一类是按学期查询成绩单另一类是按教师ID查询课程表。成绩单的查询条件通常是student_id加semester所以联合索引一定要建student_id, semester课程表查询则建在course_schedule表的teacher_id字段上。选课表中student_id也是高频查询字段同样需要索引同时course_schedule_id作为外键关联索引也建议一起带出来。我在实际压测中发现过一个问题分页用PageHelper插件时count查询会执行一条COUNT(*)语句。在数据量超过几十万后即使主查询命中了索引COUNT查询如果没有索引配合也会非常慢。解决办法是尽量让COUNT的WHERE条件走主表索引或者改用覆盖索引让COUNT查询直接在索引上完成不回表。排查慢SQL时打开MySQL慢查询日志是基本操作。如果发现某条SQL执行计划显示全表扫描而且相关字段明明有索引通常是要么字段类型不一致比如索引字段是varchar查询参数传了number要么是LIKE查询以%开头导致索引失效。3. 后端SpringBoot工程搭建与关键模块实现后端工程我习惯按照“控制器-服务-数据访问”三层来拆分但要注意不要把所有逻辑都堆到Service里那样一个Service类会膨胀到几千行。这里我把几个关键的工程配置和实现细节展开讲。3.1 SpringBoot工程结构规划与依赖管理工程结构按功能模块分包而不是按技术分层分包。也就是说不建controller包、service包、mapper包这种大杂烩而是按业务模块建包com.example.education ├── common/ // 通用工具、统一返回结果、全局异常 ├── config/ // 跨域配置、拦截器注册、数据库配置 ├── security/ // JWT认证、权限校验 ├── system/ // 用户、角色、菜单管理 ├── course/ // 课程、排课、选课 ├── score/ // 成绩管理 ├── attendance/ // 考勤管理 ├── assignment/ // 作业管理 ├── exam/ // 考试管理按模块分包的好处是后续如果拆微服务只需要把每个包底下的内容单独拎出去改造成本会小很多。Maven依赖中必加的核心依赖有spring-boot-starter-web、spring-boot-starter-security、mybatis-spring-boot-starter、mysql-connector-java、jjwtJWT生成与解析、hutool-all简化实体转换和日期操作、pagehelper-spring-boot-starter分页插件。另外必须加lombok能用注解消除的getter/setter就不要手写代码量大幅缩减。3.2 统一返回结果与全局异常处理前后端分离项目里接口返回的JSON结构必须统一。我的返回结构是标准的code、message、data三个字段。200表示成功400表示参数错误401表示未认证403表示无权限500表示服务器异常。这个结构看似简单但好处是前端封装的请求拦截器可以直接根据code统一判断不需要每个接口单独处理错误逻辑。全局异常处理是这套系统里绝对要做的不然数据库唯一约束抛出的异常会直接暴露给前端信息既难看又不安全。我额外封装了一个BizException业务异常类Service层只要new BizException(该学号已存在)全局异常处理器就会把这个message返回给前端。这样做让代码清爽很多也统一了错误信息的格式。3.3 JWT登录认证与安全配置认证方式我选的是JWT而不是传统的Session。原因是这套系统有多个端接入的需求将来如果想做小程序端或者移动端JWT天然支持跨域和跨端每次请求带上Token即可不需要维护Session共享。JWT生成的Token放在请求头Authorization字段里格式是“Bearer 一串token”。登录成功时后端的返回结构里包含token字符串和用户基本信息。前端拿到后存入本地存储每次请求由Axios拦截器自动添加请求头。Spring Security配置里我重写了两个关键部分一是自定义了一个Token过滤器用来解析请求头、加载用户信息、判断Token是否过期二是在SecurityConfig中放行登录接口、验证码接口和一些静态资源路径其余所有接口全部要求认证。密码存储不要用明文或简单MD5我用的是BCrypt加密即使用户数据库泄露密码也无法在短时间内被反推出明文。这个细节是很多毕业设计源码里做得很粗糙的地方但企业级项目这是标配。3.4 文件上传与存储方案系统涉及作业附件上传、考试附件、教师资料上传等文件上传是绕不开的环节。本地文件存储方案最直接但要注意配置虚拟路径映射把本地上传目录映射为URL访问路径。Linux环境下特别注意目录权限Tomcat默认用户对上传目录要有写权限否则会出现“FileNotFound”这种让人摸不着头脑的报错。生产环境我建议用对象存储服务把文件上传和访问都迁移到云上减少磁盘压力。但本地磁盘方案在项目初期、并发不高时完全够用不必上来就学大型平台的复杂架构。上传文件的大小限制也要在配置文件中显式设置SpringBoot默认单个文件只有1MB作业附件动辄几个MB不调会在上传时报错。配置两个参数即可spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MB4. 前端Vue应用构建与前后端联调前端是直接面对用户的部分好不好用直接影响整个系统的口碑。这里不是单纯分享一下页面长什么样而是拆解几个关键实现方式路由权限控制、Axios请求封装、主要页面组件和交互设计。4.1 Vue工程结构与路由动态注册前端工程我用的版本是Vue 3加Element Plus组件库。工程结构上强调模块化按views、components、api、store、router、utils分包。api目录下按后端模块拆文件比如course.js、score.js、user.js每个文件导出对应的接口函数。这样后端每个接口都能在api目录找到对应封装维护起来非常清晰。路由权限是前端比较容易做乱的环节。我的做法是登录成功后后端返回当前用户的菜单列表前端在全局路由守卫中根据菜单项动态注册对应的路由组件。核心思路是路由表拆分成两部分基础路由登录页、注册页、404页和动态路由业务页面。用户登录后根据菜单逐条把动态路由加到Vue Router实例中。这里有个细节要提醒——路由清空问题。刷新页面时Vue Router中的动态路由会丢失必须在全局守卫里做判断如果当前用户已存在但路由表为空则重新加载菜单并动态添加路由然后通过next({ ...to, replace: true })重新进入当前页。4.2 Axios请求拦截与错误码统一处理Axios封装的核心是请求拦截器和响应拦截器。请求拦截器里给每个请求添加Token响应拦截器里统一处理后端返回的code和HTTP状态码。401时跳转到登录页并清除本地存储的用户信息403时弹出无权限提示并跳转到403页面500时提示系统异常并建议联系管理员。下载文件的场景要单独走一个分支。后端返回的是文件流时Content-Type是application/octet-stream不能按JSON处理。我的做法是在响应拦截器里判断response.headers的content-type如果是文件流类型则直接返回blob给调用方由调用方用a标签触发下载。这个细节不处理的话下载接口会直接报错解析错误。4.3 核心页面的交互设计与组件拆分教师端的核心页面是课程管理列表。这个页面用到了典型的“表格搜索栏分页”三件套。搜索栏做学号、姓名、班级等条件组合表格展示学生信息操作列提供成绩录入、查看详情、移除选课。页面上传的查询参数要和后端接口的RequestParam精确对应字段名最好完全一致。实际开发中我用驼峰字段命名但前端请求参数和后端接收参数直接映射时要注意大小写问题MyBatis的Map接收还好但用JavaBean接收时字段名不对会直接接收不到。学生端最复杂的页面是选课中心。这个页面有筛选条件课程类型、上课校区、上课时间、课程卡片列表、选课按钮、已选课抽屉。选课操作需要二次确认弹窗提示课程时间和学分信息。选课成功后按钮状态变为“已选”同时计数器更新。整个交互的核心是状态同步我通过一个StoreVuex或Pinia维护已选课程集合任何页面修改选课状态后都同步更新Store避免出现A页面选课成功但B页面数据没刷新这种问题。4.4 跨域问题与开发环境代理配置前后端分离开发中跨域问题几乎必然遇到。最省心的处理方式是在Vue开发环境配置代理把/api前缀的请求转发到后端地址。生产环境则建议让Nginx统一处理前端静态资源和反向代理。如果出现“Access-Control-Allow-Origin”报错不要急着在前端加什么插件正确方向是去后端配置跨域支持要么写一个配置类要么让Nginx加上跨域头。后端的全局跨域配置我建议用CorsFilter过滤器的形式不要每类请求单独处理。有一点容易踩坑开发环境后端配置了跨域但前端代理恰好也在转发结果你发现一组请求成功了另一组却报跨域错误排查半天才发现是路径没有命中代理规则请求直接发到了后端8080端口而配置里CorsFilter允许的源又不包含前端地址。我的做法是前后端都允许本地开发地址保证不同路径组合都能通过。5. 从开发到部署上线的完整流程实录开发完成不代表项目能用真正上线会暴露出一堆环境相关问题。这一部分我按完整的部署流程走一遍把每一步的关键操作和容易踩的坑说透。5.1 本地环境准备与依赖版本匹配本地开发环境需要的软件版本建议先固定下来再开始否则各种兼容性会折磨人。我目前用的搭配是JDK实际上建议直接用JDK 8或者JDK 11SpringBoot 2.x搭配这两种JDK非常稳定没必要为了追新用JDK 17Maven3.8系列MySQL5.7或8.0均可注意8.0默认加密规则不同连接字符串需要带serverTimezone参数Node.js16以上前端包管理器npm或pnpm都可以项目内锁定lockfile版本初始化工程方式后端从Spring Initializr生成或直接手写一个pom.xml前端用Vue CLI或Vite创建。实际项目中我用Maven加Vue CLI架构老套但好在文档多问题好搜。MySQL连接字符串的时区问题几乎每台环境都会遇到url: jdbc:mysql://localhost:3306/edu_admin?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueserverTimezone必须显式配置否则会报“The server time zone value”异常。allowPublicKeyRetrieval在MySQL 8.0连接时会用到不配置会偶尔报Public Key Retrieval is not allowed。5.2 数据库初始化与测试数据准备数据库初始化不要全靠手工点客户端操作要把SQL脚本沉淀到项目里。项目根目录放一个sql文件夹里面至少包含三类文件schema.sql建库建表语句包括索引data.sql基础数据管理员账号、菜单权限、角色分配test_data.sql测试数据模拟学生、教师、课程、选课记录测试数据一定要提前准备没有数据联调时页面全是空表渲染效果根本看不出来接口也测不准。我通常写一个脚本批量生成几百个学生数据、几十门课程覆盖各种状态比如有已选课、有待审核、有退课和结课状态。产地数据到位后联调效率会大幅提升。初始化管理员账号时密码需要先用BCrypt加密后写入库中。可以直接在代码里写一个临时接口来生成密文插入也可以用线上BCrypt工具生成后再写SQL。直接在SQL里写明文密码是绝对不可取的万一库文件泄露系统等于裸奔。5.3 前后端联调的关键检查清单联调阶段我有一份清单每完成一个模块就按清单打勾。清单包含以下检查项登录认证正确密码能登录错误密码有提示未登录访问受保护接口返回401权限控制学生账号看不到教师管理菜单后端直接请求教师接口返回403数据完整性新增学生、编辑后更新、删除前有确认提示删除后列表正确刷新分页与搜索翻页后搜索条件还保留排序字段正确空数据时表格有友好提示文件上传与下载上传后能在对应页面预览下载文件名和内容正确错误提示后端返回400/500时前端弹出对应提示而不是白屏或控制台报错并发边界选课人数接近上限时系统提示人数已满且不会超录这份清单几乎覆盖了用户最常操作的路径按这个清单过一遍功能层面的bug能消灭掉一大半。5.4 Linux服务器部署实践服务器部署方案我直接说一套在生产上验证过多次的流程。后端部署用Maven打jar包命令是mvn clean package -DskipTests。打包后在服务器上创建/app/edu-admin目录把jar包传到该目录。使用systemd服务管理配置一个简单的服务文件。启动内存根据机器配置调整一般设置Xms256m Xmx512m就够用。生产环境记得把SpringBoot内置Tomcat的端口、数据库账号密码改成生产配置不要把开发配置打包上去。前端部署用npm run build打包生成dist目录。把dist目录内容上传到Nginx的html路径下。Nginx配置关键是location匹配规则前端路由是history模式时需要通过try_files解决刷新404的问题接口请求反向代理到后端端口。下面是一段可参考的配置注意不要直接照抄需要根据实际环境调整server { listen 80; server_name your_domain_or_ip; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }部署完成后建议至少观察一两天运行日志。Linux下用journalctl -u edu-admin -f查看后端运行日志基本能发现大部分隐蔽问题比如连接池泄漏、线程阻塞、慢SQL频发等。6. 系统上线前的安全加固与性能优化很多教学系统上线后出现问题不是功能写得不好而是安全和性能没做好准备。尤其是一旦学生集中访问选课、查成绩、交作业的高峰期很容易把系统打趴。这个环节值得你多花两三天时间。6.1 安全层面的必做清单账号安全方面除了登录密码使用BCrypt加密以外登录接口建议增加图形验证码防止机器脚本暴力破解。验证码我用的是一张会过期的图片验证码后端生成时把答案存Session或Redis校验后立即清除即使同一验证码被多次提交也无法复用。接口安全方面所有的写操作接口必须校验当前登录用户是否有权操作该资源。比如学生只能提交自己的作业不能通过修改ID来提交别人的教师只能修改自己课程的考试安排不能动其他教师的课程。这类越权漏洞在黑盒测试中不容易查出来但渗透测试一发现就是高危。操作日志方面系统管理员的敏感操作必须记录日志。我在sys_oper_log表里记录操作人、操作时间、请求URI、请求参数、操作结果后续出现问题时能快速追溯。这个设计可能平时没人注意但真出了问题没有日志就只能靠猜。6.2 前端性能与首屏加载优化前端包体积是首屏加载最大的敌人。Element Plus全量引入会把整个组件库打进去首次加载有可能到几兆甚至十几兆。我的优化方案是改成按需引入只注册项目中真正用到的组件。组件库中用到的大概有三四十个组件按需引入后包体积会大幅缩小。路由级代码分割也是必须做的优化。Vue Router配置中把每个路由映射函数改为使用动态导入这样首屏只加载当前页面路由对应的JS其他页面组件等访问到时再加载。配合Vite或Webpack的构建时拆包配置首屏加载时间能从5秒左右降到1.5秒以内效果还是比较明显的。6.3 MySQL连接池参数与Redis缓存策略数据库连接池参数别用默认值。SpringBoot内置的HikariCP默认maximumPoolSize是10但这类系统中学生集中访问时10个连接明显不够。我的配置是maximumPoolSize设置为50minimumIdle设置为10连接超时时间为30000毫秒。如果数据库服务器内存有限这个值需要回调否则启动时创建太多空闲连接也会占内存。缓存方面Redis在这套系统里主要缓存两类数据一是用户Token的黑名单退出登录后把Token加入黑名单防止被重用二是高频查询且变化不频繁的数据比如课程基础信息列表、菜单权限树。用Spring Cache的Cacheable注解在方法上直接打注解即可。需要注意缓存失效时间菜单权限树设置30分钟比较合适课程信息列表设置10分钟。给太短缓存没意义给太长用户改了数据刷新半天看不到。有一个容易踩坑的点更新了课程基本信息后缓存如果还保留旧的课程名称下拉列表里显示的还是旧值。解决办法是在更新操作所在的Service方法上加CacheEvict注解主动清除对应缓存。如果缓存key命名不统一清除时容易误删或漏删所以缓存key的规范要在项目初期约定好比如统一使用“course:list:”加查询参数的形式。7. 常见问题排查与避坑手册这里的每一条都是我在实际项目中踩过或帮别人排查过的坑按“现象-原因-解决”的方式整理供你遇到问题时直接定位。7.1 登录验证码刷新后一直提示过期或错误现象用户输入验证码后提示过期或者刷新验证码后新的验证码还是报错。原因一种是验证码保存和作用域不一致保存时用的Session校验时读的Request参数还有一种是校验逻辑在校验码移除之后再读取验证码导致永远校验不过。解决统一验证码存取逻辑校验完成后立即清除。如果是分布式环境不要用Session改用Redis设置2分钟过期时间。前端生成验证码时把时间戳参数拼到图片请求后面防止浏览器缓存旧图。7.2 前端页面能打开接口请求却返回401现象用户明明已经登录但页面上的数据请求一直返回401。原因一般是Token没有随请求发送或者Token已经过期。常见场景是Axios拦截器中获取Token时机不对等多请求并发时会取到空值。解决Token尽量存在本地存储中请求拦截器从Store中读取不要直接读本地存储因为并发时容易出状态不一致的问题。另外检查拦截器添加Token是否在请求头正确拼接“Bearer ”前缀很多后端JWT过滤器的解析逻辑对前缀大小写敏感。7.3 上传图片成功但访问时返回404现象文件上传接口返回成功下载或访问文件的URL却返回404。原因本地存储文件路径没有映射到URL。SpringBoot不会自动把磁盘目录映射为HTTP可访问路径需要额外配置资源映射。Linux上还有可能是目录没有读权限导致Nginx或Tomcat无法访问资源。解决后端增加一个WebMvcConfigurer配置类把本地上传目录映射为访问路径。示例参考如下Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }7.4 某条查询SQL越来越慢索引明明加了但没有效果现象课程表查询在数据量达到几万条后明显变慢用EXPLAIN查看执行计划发现没有走索引。原因可能是查询条件字段类型不一致或者查询条件中使用了函数包裹索引字段。比如课程表编号字段是varchar类型但查询参数传的是Long类型MySQL会隐式转换导致索引失效或者查询里写了WHERE YEAR(create_time) 2024对create_time用函数包裹索引也无法使用。解决保持字段类型与参数类型一致时间范围查询用BETWEEN或者和不要套函数。已经在运行的项目发现这类问题时改代码比改索引更有效。7.5 部署到Linux后管理员密码无法登录现象本地测试登录成功部署到服务器后管理员密码提示错误。原因大概率是初始化数据的SQL脚本在Windows环境执行时字符集编码不一致密码字段中混入了不可见字符。还有一种可能是生产数据库的密码哈希值来自不同BCrypt版本导致校验不通过。解决重新检查SQL脚本的编码格式确保UTF-8无BOM格式。重新生成管理员的BCrypt哈希值直接更新到生产数据库中。账号数据本身不多时直接重新执行初始化脚本最省事。7.6 高并发选课时超卖和多选问题现象选课开放后发现某课程选课人数超过限选人数。排查发现写入逻辑做了判断但还是穿透了。原因并发下两个请求同时执行SELECT COUNT都认为没有满员同时进行了INSERT操作。解决在事务中先锁定课程行再执行数量判断和插入操作。具体是在Service方法上开启事务先执行SELECT * FROM course_info WHERE id ? FOR UPDATE拿到锁后再执行SELECT COUNT和INSERT。MySQL的共享锁和排他锁机制比较容易理解FOR UPDATE强制并发请求串行化这样就不会超录。不过这个方案对数据库连接性能有一定压力如果不涉及超卖风险、并发量不高也可以不加锁因为大部分场景下超录问题不那么致命。8. 总结几句实在话项目做到最后回头看看技术选型其实没有太多悬念SpringBoot稳定成熟Vue前端上手快MyBatis的SQL可控性强MySQL免费且够用。这套组合能够支撑几千个学生同时使用的教学辅助平台这个结论我们实测过也经得起问。更有价值的思考点是业务上线的时机。你不需要把所有功能都做完再启动做通选课、成绩、课程管理三条主链路就足够上线试运行考勤和作业可以第二迭代加。早点让真实用户在系统上跑起来收集到的反馈才真正有意义放在办公室里空转永远测不出真实问题。我手上这两套系统就是这么一路迭代过来的第一版也又慢又难看但跑了一个学期之后根据老师和学生的使用习惯做了一轮重构用户体验变化巨大。最后多说一句的是教学类系统不只是代码问题它牵扯到用户习惯、权限边界、数据准确率甚至培训成本做这类系统持续比对方理解业务更重要。祝你把这个项目做出真正能让用户顺手的效果。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑