资讯动态

高校实习管理系统实战:SpringBoot+Vue+MySQL从需求到部署全复盘

发布时间:2026/10/16 4:54:29 来源:尧图企业网站定制
高校实习管理系统这类项目我在不同阶段接触过好几版。最早是给某高校信息中心做的课程设计级别Demo后来逐步补上多角色权限、实习流程审批和成绩归档才意识到这类系统真正的难点根本不在CRUD而在角色边界、状态流转和业务数据的一致性上。正好最近又整理了一套SpringBoot后端加Vue前端配合MySQL的实现可以完整跑起来这里从需求拆解到落地细节做一次复盘希望能给正在做类似管理系统、或者想拿一套可运行项目做二次开发的朋友一些参考。1. 实习管理系统的需求画像从业务痛点倒推功能边界1.1 这类系统最容易被低估的部分很多开发者拿到实习管理系统这个题目第一反应是学生填表、老师审批、导出表格听起来简单写起来也确实不难。但实际在高校场景里跑一圈就会发现真正的痛点集中在三块。第一块是角色极其分散。至少涉及学生、院系管理员、学校管理员、指导教师、实习单位联系人五类角色而每类角色能看到的数据范围和操作权限完全不同。学生只能看自己的实习申请和成绩指导教师能看到名下学生的进度院系管理员要处理整个学院的审批流学校管理员要汇总全校数据。权限模型如果没在一开始设计好后面就是无休止的if else.第二块是实习业务流程不是一条直线。学生提交申请后可能要经过指导教师初审、院系审核、单位确认实习过程中要提交周报或者签到记录结束之后要上传实习报告再由教师评分、院系归档。每一个节点都有状态每一个状态变更都牵扯到通知、记录和权限变化。状态机设计得松一点后面就会到处是脏数据。第三块是数据归档和统计。毕业后巡查、专业认证、教学质量评估都会要求调取实习数据所以系统不光要管过程还要管结果包括成绩分布、实习单位类型、实习时长、专业匹配度等信息能不能快速导出直接影响管理老师愿不愿意用这套系统。1.2 用例梳理与核心流程我在梳理需求的时候习惯先把所有用户故事写出来再合并成功能模块而不是直接开数据库表。这个项目最后收敛成六个核心模块。学生端实习申请提交、申请状态查看、周报填写、实习报告上传、成绩查看。教师端学生申请审核、周报批阅、实习评分、所带学生列表管理。院系管理员本学院实习数据总览、教师分配、申请复审、数据导出。学校管理员系统参数配置、学院数据汇总、实习基地管理、账号管理。实习单位接收学生信息、确认实习接收、反馈学生实习表现。公共部分登录认证、个人中心、消息通知、密码修改。流程上最核心的一条主线是申请-审核-开始实习-过程记录-结束实习-成绩评定-归档这也是我在设计数据库时优先保障的状态链。1.3 功能边界怎么划定有一个常见的错误是把系统做得太宽比如加入考勤打卡、工资结算甚至定位签到。高校实习管理系统本质上是信息管理系统不是过程监控平台。实习单位千差万别很多小微单位根本没有规范打卡条件定位签到只会制造虚假数据。所以我把范围控制在信息流转资料归档成绩管理上把考勤这类需求做成可选的周报填写而不是强制的打卡。另外一个容易被忽略的边界是通知渠道。系统内部通知已经够用对接短信网关或者企业微信机器人会在部署环境上增加大量不确定性而且学生的手机号变更频繁短信到达率其实并不理想。所以我只保留了站内信和邮件通知两种方式邮件用最简单的JavaMailSender配一个公共邮箱即可。2. 技术底座的选择逻辑SpringBootVueMySQL的搭配依据2.1 后端为什么锁定SpringBoot生态选SpringBoot不是因为它新而是因为它在高校和中小团队里几乎零门槛。Java语言在高校信息中心普遍有维护基础SpringBoot的自动配置又大幅压缩了SSM时代繁琐的XML配置。更重要的是SpringSecurity和MyBatis-Plus这两个生态组件可以无缝嵌进去权限和数据库操作都能站在成熟方案上做而不是从零造轮子。具体到这个项目我用的技术栈是SpringBoot 2.7.x配合MyBatis-Plus 3.5.xMySQL 8.0前端Vue 2.6加Element-UI。为什么不直接用SpringBoot 3.x因为这个项目针对的是拿来就能跑的场景很多高校机房或者个人电脑上的JDK还停留在1.8SpringBoot 3强行要求JDK 17会直接把一部分人挡在门外。所以在技术选型上我宁可稍微保守一点也不给运行环境添乱。2.2 前端为什么选Vue而不是React或者JSP选择Vue的原因很实际Element-UI的中后台组件库能覆盖管理系统绝大多数界面需求表格、表单、弹窗、树形控件都有现成方案。Vue的双向绑定让表单类页面开发效率非常高而且Vue在国内高校的项目传承中非常普遍接手的人多。如果选JSP或者Thymeleaf做服务端渲染前后端会强耦合权限控制和接口复用都会麻烦很多。如果选React当然也可以但团队学习成本、组件生态的匹配度反而不如Vue来得直接。Vue在这个场景里是最小阻力路径。2.3 数据库建模的几个关键设计数据库是第一份文档建模阶段想清楚后面少走一半弯路。这项目里核心表我大致分为四组。用户与权限sys_user、sys_role、sys_user_role、sys_menu、sys_role_menu标准RBAC五表。实习业务主表internship_application实习申请、internship_report实习报告、internship_weekly周报、internship_score成绩。基础数据college学院、major专业、class_info班级、company实习单位、teacher_student指导教师与学生关系。流程记录audit_log审批记录、notice通知。字段设计上有几点值得展开。第一所有业务表都带create_time、update_time、deleted三个字段前两个是审计需要deleted用于逻辑删除。这类管理系统通常不要求物理删除数据一旦误删成绩归档材料很难找回逻辑删除能留一条后悔路。第二实习申请表的status字段用整数类型0表示待审核1表示教师通过2表示院系通过3表示单位确认4表示实习中5表示已结束待评分6表示已完成-1表示被驳回。这个状态编号贯穿整个后端逻辑和前端展示前端会根据不同状态渲染不同颜色的标签和操作按钮。第三成绩表不直接存一个总分而是拆成考勤分、过程分、报告分、单位评分几个维度最后用视图或者查询时计算总评。这样做的好处是管理端可以调整权重而不是为了一个总分改动整张表结构。CREATE TABLE internship_application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT 学生用户ID, company_id BIGINT COMMENT 实习单位ID, company_name VARCHAR(200) COMMENT 单位名称兜底字段, position_name VARCHAR(100) COMMENT 实习岗位, start_date DATE, end_date DATE, status TINYINT DEFAULT 0 COMMENT 0待审核 1教师通过 2院系通过 3单位确认 4实习中 5待评分 6已完成 -1驳回, reject_reason VARCHAR(500), create_time DATETIME, update_time DATETIME, deleted TINYINT DEFAULT 0 );第四公司表单独建但申请表里也冗余了一个company_name。原因是很多学生填的实习单位在系统里不存在如果强制先建单位再申请会卡住流程。这里采用选择已有单位手动输入兜底的模式审核老师后续再统一维护单位信息这样的设计更贴合实际。3. 后端核心实现权限模型、状态流转与文件处理的细节3.1 RBAC权限模型是全局的地基权限模型我用的是经典RBAC用户关联角色角色关联菜单和按钮权限SpringSecurity做认证和授权。接口层的控制通过自定义注解处理比如PreAuthorize(hasRole(TEACHER))直接挂在Controller方法上。有一个设计细节值得一提菜单表和按钮权限统一放在sys_menu表里用menu_type字段区分。目录类型是0菜单类型是1按钮类型是2。前端登录后拿到的是完整菜单树和按钮权限标识集合路由根据菜单树动态生成按钮根据权限标识做v-if控制。这样新加一个按钮权限就不用改前端代码只要后端在菜单表里配一下刷新就生效。学生、教师、院系管理员、学校管理员、实习单位这五类角色在接口层面我做了严格隔离。学生端接口统一以/student开头教师端以/teacher开头院系管理员以/dept开头学校管理员以/admin开头单位端以/company开头。这个命名习惯看起来很简单但在维护时价值极大看到路径就知道当前请求属于哪个角色域权限异常排查起来很快。3.2 状态机的实现没有想象中复杂但细节多实习申请的主状态流转是最容易写乱的地方。我最初用的是大量if-else嵌套后来重构为一张状态流转配置表每次操作前校验当前状态是否允许执行目标操作。举个具体例子学生提交申请后状态是0指导教师在状态0下可以审核为1或者驳回为-1。如果状态已经是-1学生重新编辑提交后状态又回到0。看起来简单的逻辑如果不做状态校验学生会重复提交教师端会出现重复审批的问题。我在实现时写了一个简单的状态校验方法private static final MapInteger, ListInteger ALLOWED_NEXT_STATUS new HashMap(); static { ALLOWED_NEXT_STATUS.put(0, Arrays.asList(1, -1)); ALLOWED_NEXT_STATUS.put(1, Arrays.asList(2, -1)); ALLOWED_NEXT_STATUS.put(2, Arrays.asList(3, -1)); ALLOWED_NEXT_STATUS.put(3, Arrays.asList(4, -1)); ALLOWED_NEXT_STATUS.put(4, Arrays.asList(5, -1)); ALLOWED_NEXT_STATUS.put(5, Arrays.asList(6, -1)); }在Service层统一封装一个transition方法先查当前状态再判断目标状态是否在允许列表里最后更新状态并写入audit_log。这套写法简历里叫状态机实际上就是一张Map加一个校验方法但效果非常明显所有状态变更行为都被收口到一个入口不会再出现某个Controller里直接update status的漏网操作。3.3 成绩导入导出和文件上传的真实需求成绩录入分两种方式教师逐条录入或者通过Excel批量导入。逐条录入走普通表单批量导入用EasyExcel解析上传的xlsx文件。批量导入有几个隐藏的坑。第一个是模板问题我提供了标准模板下载要求模板中包含学号、成绩分项等固定列头第二个是数据校验不能直接信任Excel里的内容学号对应哪个学生、分数是否在0到100之间、是否重复导入这些都要校验。第3个是错误提示解析完必须返回具体的错误行号和错误原因不然管理老师根本不知道哪里出了问题。文件上传这里我用的是本地磁盘存储配置文件里指定一个upload.path。有人可能会问为什么不用OSS或者MinIO理由很简单这类系统大多数部署在学校内部服务器不一定有公网对象存储条件本地存储加Nginx静态映射是最稳的方案。上传的文件名一律用UUID重命名原始文件名存到数据库避免中文文件名和路径遍历问题。spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MB upload: path: /data/internship/files/对应的Nginx静态映射配置片段如下location /files/ { alias /data/internship/files/; expires 7d; add_header Cache-Control public; }上传成功后接口返回给前端的不是磁盘路径而是一个/files开头拼接文件名的虚拟路径。前端直接拿这个路径拼接访问域名就能预览或者下载文件逻辑简单也不用额外写下载接口。3.4 消息通知的数据表设计站内消息我用了一张notice表字段包括receiver_id、title、content、is_read、create_time。发送时机包括申请状态变更、周报被退回、成绩公布、系统公告四类。这里我踩过一个坑最初设计是业务操作时同步插入消息记录结果在高并发导入成绩时批量插入消息导致接口超时。后来改成异步发送SpringBoot里直接用Async注解加一个线程池配置消息写入和主业务解耦不影响主流程响应速度。同时消息表加了receiver_id索引否则数据量大了之后查询会很慢。4. 前端实战动态路由、业务组件与接口联调的翻车现场4.1 动态路由和按钮级权限的实现前端登录后拿到两个东西用户的角色列表以及该角色可见的菜单树。Vue Router先只注册login和layout两个基础路由其余业务路由全部走动态添加。具体做法是在路由守卫里判断用户信息是否存在不存在就跳登录页存在但动态路由没添加过就调用后端菜单接口生成路由表。动态路由的坑在于刷新页面时Vue Router会重置需要重新根据用户信息加载路由。这个问题我调试了很久才意识到是因为没有在Store里持久化路由添加状态。解决方案是在刷新时从后端重新拉取菜单数据并且用window.location.reload解决addRoute后路由表未同步的问题虽然不够优雅但稳定可靠。按钮级权限我用了自定义指令v-permission指令内部检查当前用户权限标识列表是否包含传入的值不包含就把DOM元素移除。这样页面上不同角色看到的操作按钮完全不同学生看不到审核按钮教师看不到全校统计入口。4.2 核心页面的交互设计思路实习申请页面是最复杂的一个表单页。学生需要填实习单位、岗位、起止时间、实习类型、实习内容描述还可能上传实习安全承诺书。我用了一步一组的步骤条而不是一个长表单避免学生一次性面对太多字段产生压力。分步校验通过后再统一提交用户体验和代码可维护性都更好。审核页面是教师和高频使用的页面。列表用Table组件展示待审核学生支持按状态筛选、按班级筛选、按关键字搜索。操作列提供通过和驳回两个按钮驳回时必须填写原因。这里有一个设计细节驳回原因采用弹窗表单强制填写而不是直接在输入框里自由输入避免了教师只点驳回不写理由的情况学生在申请状态里能看到驳回理由方便修改后重新提交。成绩管理页面则是一张紧凑表格加一个批量导入按钮。表格按学生维度展示各项分数支持行内编辑教师在页面上改完直接保存。因为成绩是学生比较敏感的数据保存时加了二次确认弹窗并且在后端重新校验成绩范围。4.3 前后端联调时的典型问题联调是最花时间的环节。我整理了三个最常见的坑。第一个是跨域问题。前端开发服务器在localhost:8080后端在localhost:9000必然产生跨域。开发环境我在Vue的vue.config.js里配置了devServer代理生产环境则靠Nginx反向代理解决。一个容易忽视的点是SpringSecurity开启后跨域预检请求OPTIONS会被拦截后端必须放行OPTIONS请求或者正确配置CorsFilter。第二个是日期格式。Java后端LocalDateTime默认序列化格式是带T的ISO格式前端Element-UI的日期组件格式不一致导致提交表单时后端解析报错。统一方案是在后端配置Jackson的日期格式前端用标准yyyy-MM-dd或者yyyy-MM-dd HH:mm:ss传输两边对齐后这类问题彻底消失。spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第三个是分页参数命名。MyBatis-Plus默认分页参数是current和sizeElement-UI的分页组件默认事件返回的是pageNum和pageSize如果不做转换后端永远收不到正确的页码。这个不是技术难题但第一次联调时经常因为忽略它浪费一个下午。4.4 为什么前端状态管理选择Vuex而不是Pinia项目管理后台的状态不算太复杂全局状态主要存用户信息、菜单权限、按钮权限。Vuex足够用而且Vue 2项目里Pinia的支持不够顺手。我利用Vuex的模块化拆成user模块和app模块user模块存用户详细信息app模块存侧边栏折叠状态和动态路由是否已加载的标记。有一个不常被提到的小优化动态路由的添加结果用Vuex的state标记而不是本地存储。因为本地存储可以被用户手动清除但不能触发路由重新加载用Vuex管理配合路由守卫的判断逻辑上更自洽。5. 让源码真正可直接运行环境准备、初始化与启动细节5.1 环境清单与版本搭配可直接运行这四个字是这个项目最大的目标也是我最花心思的地方。整理出了一套经过验证的版本搭配照着装就行不需要来回试错。组件推荐版本备注JDK1.8对应SpringBoot 2.7.xMaven3.6.x使用阿里云镜像加速依赖下载MySQL8.0至少5.7SQL脚本兼容5.7和8.0Node.js14.x或16.xVue 2.6项目不要用Node 18以上Nginx1.20以上生产环境部署用前端项目直接用Vue CLI脚手架生成Node版本太新可能导致node-sass编译失败所以我换成了dart-sass的sass包解除对node-sass原生模块的依赖这样Node版本兼容性会好很多。5.2 数据库初始化的正确姿势SQL脚本我按照建库、建表、插入初始数据三步走。初始数据里面包含了默认管理员账号、五个角色的示例用户、菜单权限数据、示例学院和专业、示例实习单位。导入时的一个重要细节是MySQL 8.0默认字符集已经是utf8mb4但5.7的某些配置可能不是建议建库时显式指定字符集。CREATE DATABASE IF NOT EXISTS internship_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;数据脚本里有一条超级管理员账号密码通过BCrypt加密存储初始密码统一为123456登录后强制修改。如果直接把明文密码写进数据库系统一旦暴露在公网就是严重的安全问题。有人会嫌BCrypt校验麻烦但这是SpringSecurity的标准做派也符合基本的账号安全要求。5.3 前后端各自的启动步骤后端的启动流程比较简单导入项目到IDE等待Maven下载依赖修改application.yml里的数据库连接信息运行主类Application。这里有一个被很多人忽略的问题默认配置文件里数据库密码是写在application.yml里的如果多人协作或者代码上传到公开仓库密码就泄露了。正确做法是本地放application-local.yml提交到仓库的application.yml只放占位符通过启动参数指定激活profile。虽然这个项目本来就是为了学习但好的习惯还是应该从一开始就养成。前端的启动流程是安装依赖、启动开发服务器npm install npm run serve依赖安装阶段最容易出问题的是网络。我推荐使用npmmirror镜像源安装依赖安装速度会快很多。如果安装过程中报node-sass相关的错误直接删除node_modules和package-lock.json再重装一次通常能解决。npm config set registry https://registry.npmmirror.com npm install npm run serve开发服务器启动后浏览器访问localhost:8080Vue开发服务器会通过代理转发后端请求所有接口都能正常工作。5.4 启动失败的常见原因排查我遇到过启动失败的情况中90%可以归为下面几类。第一类是端口被占用。后端默认跑在9000端口前端代理目标也是9000。如果本机其他服务占用了9000后端会直接启动失败日志会提示端口被占用换个端口或者kill掉占用进程即可。第二类是数据库连接失败。Caused by: Communications link failure基本就是MySQL没启动、账号密码错误、或者数据库地址端口不对。Caused by: Unknown database则说明数据库没创建。这类问题看日志很容易定位但新手经常会忽略最后的Caused by信息只看第一行异常就不知所措。第三类是Redis连接失败。这个项目里我用Redis做验证码缓存和Token黑名单如果本机没安装Redis就会启动报错。最简单的处理是在application.yml里把Redis相关配置注释掉同时把验证码逻辑改成本地内存存储。但更规范的做法还是装一个RedisWindows下可以用Redis的官方Windows版本Linux下直接apt-get或者yum安装。5.5 生产部署的简化路径本地跑通之后部署到服务器有两种常见方式。第一种是纯前后端分离部署。后端打包成jar服务器上用java -jar命令启动Nginx托管前端静态文件并反向代理后端接口。这种方式不需要额外资源配置简单适合校内小规模使用。第二种是Docker Compose一键部署。把后端、前端Nginx、MySQL、Redis四个容器编排在一起一条docker-compose up -d命令全部起来。Docker方式对服务器环境要求更低换机器迁移也更省心。Docker部署时有几个细节数据库数据卷要挂载出来否则容器重建数据就没了后端配置里的数据库地址不能用localhost要用docker-compose里的服务名前端Nginx配置代理后端容器名而不是IP地址。6. 跑起来之后更要重视的安全加固、性能取舍与二次开发方向6.1 安全方面至少要做这几件事管理系统跑起来容易安全防护才是长期的事。我至少要提醒做四件事。第一修改默认密码。系统内置了默认账号部署后第一件事就是改管理员密码并且要求强密码策略。数据库中密码用BCrypt加密存储这一点从项目初始就保证了。第二接口层统一加操作日志。用AOP切面拦截所有写操作记录操作人、操作时间、操作接口、请求参数。出了数据问题可以追溯到人而不只是甩锅给系统。第三限制上传文件类型。文件上传接口除了后缀校验还要校验文件头magic number避免有人把一个伪装成jpg的木马传上来。同时上传目录禁止执行权限服务器配置里对upload目录的PHP或JSP解析必须关闭。第四防止SQL注入。MyBatis-Plus的参数绑定机制天然规避了大部分注入风险但如果你用字符串拼接SQL风险就会回来。这个项目里所有Mapper方法都使用参数绑定没有一处字符串拼接SQL。6.2 大数据量场景下的取舍实习系统如果供一个万人规模的学校使用核心表的记录数会快速增长。这个时候有几个地方值得提前优化。学生表加了院系、专业、班级的联合索引实习申请表加了student_id和status联合索引。最常用的查询是某个教师名下的待审核列表和某个学生在某个时间段的申请记录对应的索引一定要建上。批量导入成绩如果一次导入上千条逐条insert会很慢。我用MyBatis-Plus的saveBatch方法会生成批量插入SQL性能提升非常明显。同时把导入操作放在异步线程池里执行导入完成后发送站内信通知操作人。MyBatis-Plus的分页插件默认会执行count查询数据量大了以后count会变慢。如果业务上不需要精确总数应该把分页查询改成不执行count返回一个比较大的页码数即可。6.3 从基础版本继续扩展的方向这套系统的价值不只是跑起来更重要的是它是一个可扩展的业务骨架。基于现状可以往几个方向走。第一对接统一身份认证。很多高校已经有统一身份认证平台学生和教师用校园账号直接登录不用再注册一套账号。实现思路是后端预留一个SSO接口前端加一个校园统一认证登录按钮重定向到认证中心回调时用票据换取本地Token。第二增加数据可视化大屏。按照学院、专业、实习单位类型、实习状态等维度展示统计图表用于学校管理层的驾驶舱页面。前端用ECharts做图表后端加聚合统计接口即可。第三对接企业微信或钉钉推送。很多教师习惯在手机端看消息站内信对于他们来说不够及时。可以通过企业微信应用消息或者钉钉工作通知推送审核待办提醒这类对接都有官方API文档扩展难度不高。最后聊一点个人感受。我在整理这套系统的过程中最大的体会是可直接运行背后需要付出的努力远比写功能代码要多。环境兼容性、数据初始化、配置项说明、部署文档每一项都要反复验证才能真正做到让另一个开发者拿过去就能跑起来。如果你拿到源码不要急着改功能先在默认配置下完整跑通一遍申请、审核、评分、导出的全流程理解状态是怎么流转的再动手改需求。很多人的二次开发翻车不是代码能力不够而是还没理解原设计的约束就开始删改结果把状态链弄断了。希望这份复盘能帮你少走几步弯路不管是课程设计、毕业论文还是实际项目的起步阶段都能有一个靠谱的底座。

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

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

免费获取报价 →
↑