新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Spring Boot+Vue的高校学生实习管理综合系统设计与实现

发布时间:2026/9/30 8:53:29来源:尧图网络
基于Spring Boot+Vue的高校学生实习管理综合系统设计与实现
每年实习季一到辅导员那边的Excel表格满天飞学生不知道去哪找岗位企业收上来的报名表半天统计不完指导老师批改周报也没有统一入口。我基于 Java 生态里的 Spring Boot配合前端 Vue把高校学生实习管理从岗位发布、学生报名、过程跟踪到成绩评定整条链路做成了线上系统也就是“高校学生实习综合服务系统”。整套内容比较适合正在做毕业设计或者帮学院做小型信息系统的同学参考里面每一步用什么、为什么、踩过哪些坑我都会讲清楚。实习管理这种系统规模不大但场景很典型。它不涉及高并发、不需要微服务但对流程状态、角色权限、数据统计的要求非常具体。把单体架构下的 Spring Boot Vue 组合吃透基本就能应付大部分校内管理系统。下面我从需求设计、后端、前端、数据库、上线排障五个方面拆开讲。1. 需求分析与整体设计先画流程再写代码1.1 实习管理到底卡在哪些环节动手写代码之前我建议先用一周时间做需求调研去教务、学工、各教研室问一圈。实习管理表面简单实际痛点很集中岗位信息发布靠群通知学生报名靠共享表格最后到底谁报了名、谁被录了没人能说清实习过程中学生要交周报但周报交在哪、老师批没批全凭口头沟通实习结束后的成绩评定更是重灾区企业打分、老师打分、周报情况、出勤情况分散在多个渠道最后汇总全靠手工。所以这个系统的核心不是“做一个网站”而是把一条业务主线跑顺企业或校内管理员发布实习岗位学生浏览并报名指导老师审核确认实习期间学生提交周报、老师批阅实习结束后按照规则计算综合成绩最后生成统计报表供学院管理使用。流程清晰了功能模块自然就出来了。1.2 技术栈选型为什么是Spring Boot Vue技术选型要回答两个问题为什么后端选 Spring Boot为什么前端选 Vue。后端用 Spring Boot核心价值在于“开箱即用”和“生态成熟”。Spring Boot 内置了 Tomcat不用再单独部署外部容器配置简化了很多一条spring-boot-starter-web依赖就能把 Web 应用跑起来事务管理、参数校验、Redis 缓存、MyBatis 集成这些都有现成方案。对做校内管理系统来说团队协作成本低维护也简单网上能查到的资料覆盖了你 90% 的常见需求。前端选 Vue是因为它学习曲线比 React 平缓中文资料多模板语法直观。Vue 的单文件组件开发方式很适合中后台系统配合 Element 系列组件库像表格、表单、弹窗、分页这些高频组件可以直接复用开发效率非常高。如果说需求侧还有别的备选比如 JSP 老方案、Freemarker 模板渲染那都不是不行但前后端分离以后接口调试、部署扩展、页面展示灵活性都会明显更好所以现在的主流选择仍然是 Spring Boot Vue。1.3 功能模块与角色权限顶层设计角色方面我分了三种学生、指导老师/学院管理员、企业账号。为了保证权限可控系统不能只靠前端页面显隐来判断后端每一个接口都要校验角色。角色核心权限主要功能学生浏览岗位、报名、提交周报、查看成绩实习大厅、我的实习、周报管理、个人中心指导老师/管理员审核岗位、审核报名、批阅周报、录入成绩、统计报表学生管理、岗位管理、报名审核、成绩管理、数据统计企业发布岗位、管理岗位、接收报名企业信息维护、岗位发布、报名名单查看顶层功能模块我建议拆成六大块基础信息管理学生、班级、专业、企业岗位发布与报名管理实习过程管理周报、打卡、沟通记录成绩评定管理统计报表中心系统管理用户、角色、菜单。每一步都是承上启下的前面没做好的后面全都会出问题。1.4 为什么放弃微服务和分布式中间件这个问题我一定要单独说。校内实习系统最常见的技术滥用就是上来就上 Spring Cloud、注册中心、分布式事务那一套。一个学院几千人、并发峰值也就几十上百微服务只会带来额外的部署成本和排障难度。我当时连 Redis 都只用来做验证码缓存和 JWT 黑名单没有强依赖。系统设计越贴近真实规模越可靠分布式方案留到真有并发和大流量再说这个阶段用 单体 合理分层 就是性价比最高的选择。2. 后端核心Spring Boot项目怎么落地2.1 骨架搭建与统一返回规范项目骨架我用的标准四层结构Controller 接收请求、Service 处理业务、Mapper 对接数据库、Entity 映射表结构。为了让接口响应格式一致我定义了一个统一的返回体ResultT包含code、msg、data三个字段。所有接口都返回这个结构前端判断 code 等于 200 才认为成功否则统一弹错误提示。这样处理的好处是错误信息、业务异常、参数校验失败都能走同一个通道前端不用为每个接口单独写判断。我还会配置一个全局异常处理器用RestControllerAdvice捕获BusinessException、参数校验异常和系统异常转换成统一格式返回。这样 Service 层只要专注业务逻辑遇到问题就抛异常不用在每个 Controller 里 try-catch。2.2 JWT登录认证与权限拦截登录模块我没有用传统的 Session 方案而是选了 JWT。原因很简单前后端分离以后Session 模式要处理跨域携带 Cookie、CSRF 防护、多端会话同步这些问题JWT 模式是无状态的客户端把 token 放在请求头里后端解析就能拿到用户身份部署多个实例也不受影响。实现上有几个关键步骤用户登录成功后用用户 ID 和角色生成 token有效期设置 24 小时前端把 token 存在 localStorage 里请求拦截器自动加到Authorization头后端写一个拦截器先校验 token 是否合法、是否过期再解析出用户信息和角色塞到当前线程上下文中。涉及权限控制的方法上加自定义注解或直接判断角色不通过就返回 403。这里的经验是JWT 的密钥不要写死在代码里要放到配置文件并通过环境变量注入token 时间也不要设太长实习生季的项目排期通常持续几个月用户一天登录一次问题不大但 token 过期以后要提供刷新接口否则用户用着用着突然被踢下线体验很差。2.3 岗位发布、报名流程与状态机设计岗位模块看起来是简单的增删改查但报名环节的状态变化很考验设计。我的状态模型是这样的岗位有“草稿、发布中、已招满、已下架”四种状态报名记录有“待审核、已通过、已驳回、已取消”四种状态。学生报名时系统要检查岗位状态必须是发布中还要检查报名人数是否已满。这里我加了一个signed_count字段在岗位表上报名通过时给这个字段加一。防止重复报名的方式是数据库层面在报名表(student_id, post_id)上建唯一索引程序层面插入前再查一次双保险。岗位已经招满时前端要能实时看到“已满”的标识这个状态由后端接口返回不能靠前端自行推断。2.4 实习成绩计算的业务逻辑成绩评定是最容易被低估的业务模块。要评分的维度有企业评价、指导教师评分、周报完成度、出勤情况这四块普遍各占不同权重。我先跟学院确认了统一规则再把规则落到代码里最终成绩 企业评分 0.4 指导老师评分 0.3 周报完成度 0.2 出勤得分 0.1每个维度都按百分制计分汇总时用BigDecimal计算避免浮点数误差。周报完成度是从已提交周报数和应提交周报数算出来的完成比例大于 80% 记 90 分60% 到 80% 记 70 分不足 60% 按比例折算。出勤得分由企业端维护的考勤记录生成。这里有个容易忽略的点成绩一旦发布学生端立即能看到所以一定要加“确认发布”这个状态。成绩录入阶段可以让老师反复修改但调成已发布以后就锁定所有变更记录都要留痕防止成绩纠纷。2.5 文件上传与静态资源处理实习申请经常要传简历、学生证照片、企业资质文件上传模块绕不开。我用的是 Spring Boot 自带的MultipartFile把文件保存到服务器本地磁盘的 upload 目录文件名用UUID 原文件名后缀重新生成避免中文文件名和重名覆盖问题。上传接口要限制文件大小和类型一般图片限 5MB 以内PDF、Word 限 10MB在配置里设置spring.servlet.multipart.max-file-size。保存到本地的文件前端要能通过 URL 访问。必须配置静态资源映射把/files/**路径映射到磁盘目录否则上传成功但图片 404这个坑非常常见。3. 前端实现Vue工程化与页面交互3.1 项目初始化和依赖安装前端工程我是用 Vite Vue 3 搭的组件库用的 Element Plus。对比 Vue 2 Element UI 的老组合Vue 3 组合式 API 对逻辑复用更友好Element Plus 的组件也比二点零时代完善很多。不过如果你所在团队都更熟 Vue 2直接用 Vue 2 Element UI 也完全没问题关键是要统一不要混着用。创建项目以后要装的依赖包括axios发请求、vue-router管理路由、pinia管理用户状态和角色信息、element-plus提供 UI 组件、dayjs处理日期格式化。项目目录建议按功能划分views放页面components放公共组件api放接口请求封装router放路由配置store放状态管理。一开始就把目录规范好后面加页面、加接口都省事。3.2 Axios封装与登录态管理所有接口请求我统一走一个request.js核心就是两个拦截器。请求拦截器里加 token响应拦截器里处理业务状态码和 HTTP 状态码。import axios from axios import { ElMessage } from element-plus import router from /router const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( res { const data res.data if (data.code ! 200) { ElMessage.error(data.msg || 请求失败) return Promise.reject(new Error(data.msg)) } return data }, err { if (err.response err.response.status 401) { localStorage.removeItem(token) localStorage.removeItem(userInfo) router.push(/login) } ElMessage.error(err.response?.data?.msg || 网络异常) return Promise.reject(err) } ) export default service这样封装以后页面里只关心业务逻辑不用重复处理 token 过期、接口报错这些通用问题。401 的情况统一跳登录页用户再重新登录即可。3.3 路由守卫和动态菜单权限前端不能只靠“页面有没有入口”来控制权限还要在路由层面做拦截。我的做法是静态路由只配置登录页、首页、公共页面动态路由根据后端返回的菜单权限生成。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token) { if (to.path /login) { next(/) } else { next() } } else { next() } })对于按钮级权限比如“发布岗位”按钮只有企业和管理员能看到我写了一个自定义指令v-perm根据当前用户的角色集合判断按钮是否渲染。这里的经验是后端返回给用户的菜单和按钮权限一定要保持单一数据源前端不要自己在 store 里写死角色清单否则后端改了权限前端还停在上一个版本。3.4 实习大厅和我的实习页面拆解重点页面有三个实习大厅、报名管理、周报详情。实习大厅是一个列表页面用来展示所有发布中的岗位筛选条件包括专业、企业名称、岗位名称。我用的核心组件是 Element Plus 的el-cardel-pagination。列表要接分页接口参数是current和size后端返回total总条数。这里有个交互细节学生点击报名后按钮要立即变灰并显示“已报名”防止连续点击产生重复提交等接口返回成功后再刷新状态。我的实习页面是学生视角的“控制台”。它展示当前学生的所有报名记录每条记录包含岗位信息、企业信息、报名状态。状态为通过的学生可以在这里进入周报管理新增本周的周报并提交。周报列表按照提交时间倒序排列老师批阅以后会显示批语状态。管理端的报名审核页面虽然数据量不大但列表查询条件一定要加好。按状态筛选是最常用的其次按批次或学院筛选。审核按钮要带二次确认弹窗防止误操作。页面好做但“操作可追溯”才是管理系统的关键。4. 数据库设计与权限模型4.1 核心表的字段设计数据库设计直接决定开发效率。我这次放了九张核心表用户表、角色表、菜单表、用户角色关联表、角色菜单关联表、企业表、岗位表、报名表、周报表外加成绩表。岗位表是最核心的一张表字段大概这样设计CREATE TABLE internship_post ( id bigint NOT NULL AUTO_INCREMENT, company_id bigint NOT NULL COMMENT 企业ID, title varchar(100) NOT NULL COMMENT 岗位名称, major_requirement varchar(255) DEFAULT NULL COMMENT 专业要求, head_count int DEFAULT 1 COMMENT 招聘人数, signed_count int DEFAULT 0 COMMENT 已报名人数, status tinyint DEFAULT 1 COMMENT 1发布中 0已下架 2已招满, duty_desc text COMMENT 岗位职责, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_company_id (company_id), KEY idx_status_time (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4报名表一定要设计成唯一索引绑定学生和岗位根本不需要在程序层纠结重复报名数据库直接拦住它CREATE TABLE internship_signup ( id bigint NOT NULL AUTO_INCREMENT, student_id bigint NOT NULL COMMENT 学生ID, post_id bigint NOT NULL COMMENT 岗位ID, status tinyint DEFAULT 0 COMMENT 0待审核 1通过 2驳回 3取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_student_post (student_id, post_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4周报表设计的时候我加了一个batch_no字段用于区分第几周。这个字段很有用。学院经常问“这周有多少学生提交了周报”一条 SQL 就能统计出来不需要在代码里循环判定。4.2 RBAC权限模型与五表关联权限设计我采用的是标准的 RBAC基于角色的访问控制模型。用户表不直接存角色而是通过用户-角色关联表和角色表连接角色再通过角色-菜单关联表和菜单表连接来控制可访问的菜单和按钮。这样做的最大优势是灵活一个学生角色以后想加上“查看企业简介”的权限只要在菜单表里给角色绑定新菜单代码不用动。后端接口做权限校验时我是在 JWT 里放用户 ID 和角色编码每进入需要权限的接口从当前上下文中取出角色编码再用RequireRole注解判断。注解方式比在每个方法里手写 if 判断要清晰很多也方便统一维护。4.3 常用查询SQL与统计口径统计报表是这类系统价值最高的部分。学院最关心的指标是实习率我的统计口径是有实习记录且状态为通过或结束的学生人数除以应实习学生总数。SQL 可以这样写SELECT d.dept_name, COUNT(s.id) AS total_student, COUNT(CASE WHEN si.status IN (1, 2) THEN 1 END) AS internship_count, ROUND( COUNT(CASE WHEN si.status IN (1, 2) THEN 1 END) / COUNT(s.id) * 100, 2 ) AS rate FROM student s LEFT JOIN dept d ON s.dept_id d.id LEFT JOIN internship_signup si ON s.id si.student_id AND si.status IN (1, 2) GROUP BY d.id这个统计的“坑”在于如果直接LEFT JOIN报名表学生会因为有多条报名记录而重复计数所以要么先子查询去重要么像我上面这样在 JOIN 条件上限定状态。统计结果对不上号十有八九都是这里出了问题。5. 上线前后的典型坑与排查思路5.1 跨域导致接口通不了一查一个准前后端分离部署最常见的第一个坑就是跨域。开发环境里Vite 默认端口是 5173后端接口在 8080直接从前端发请求会被浏览器拦截。我的做法不是在后端代码里开 CORS而是在 Vite 配置里加代理把/api前缀的请求转发到后端服务// vite.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }生产环境用 Nginx 处理同样是把/api反向代理到后端。注意代理配置里千万不要把/api前缀直接去掉会跟后端接口路径对上不接。我的习惯是后端所有接口统一加/api前缀最省心。5.2 Vue history路由刷新404前端 Vue Router 用 history 模式时有个经典现象在首页点进去没问题一刷新页面就 404或者地址栏输入二级路径直接白屏。原因是 Nginx 没有配置 vue-router 的 fallback服务器找不到对应的物理文件。解决办法是在 Nginx 的 location 里加一行location / { try_files $uri $uri/ /index.html; }如果项目部署在子路径下还需要设置router base和 Vite 的base配置否则静态资源路径全是 404。5.3 事务不生效的典型写法报名和成绩录入都是要写多张表的操作事务必须保证要么全部成功要么全部失败。Transactional 注解看着简单但踩过的坑真不少。最容易翻车的是同一个 Service 内部方法调用比如 A 方法加了事务B 方法是私有的也加了事务从 A 里调用 B事务实际上不生效因为 Spring 事务是基于 AOP 代理的私有方法内部调用不会被代理拦截。还有三种情况也常见异常被 try-catch 吃掉事务不知道要回滚方法被final修饰代理根本拦截不了数据库表用的是 MyISAM不支持事务。我的建议是事务方法里不要吞异常统一往上抛事务尽量放在 Service 入口方法回滚指定rollbackFor Exception.class。别迷信默认配置事务要显式处理。5.4 MyBatis-Plus分页不生效用了 MyBatis-Plus 之后很多人发现selectPage返回的数据还是全量列表以为分页失效。其实是因为分页插件没有配置进去。MyBatis-Plus 3.x 之后分页要注册一个拦截器Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }不注册这个拦截器selectPage只是接口形式上的“分页”实际 SQL 还是查全部。另外分页的current和size记得做参数校验size限制最多 100 条防止有人传 9999 直接拖垮数据库。5.5 时间格式和上传文件访问问题前后端时间格式不一致也是个遇到必头大的问题。Java 后端返回的LocalDateTime默认是2024-01-01T10:30:00前端直接显示很丑而且传参也容易报解析错误。我的处理方式是在后端统一配置 Jackson 的日期格式为yyyy-MM-dd HH:mm:ss前端再配合 dayjs 做展示格式化。两边都定好标准接口里就不要再折腾字符串转来转去了。文件上传常见的坑是能传但访问不到。上传成功后后端返回的是“/files/xxx.jpg”但前端拿到这个路径直接请求却提示 404。原因就是没有配置静态资源映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: System.getProperty(user.dir) /upload/); }还要注意上传目录要提前创建否则第一次上传会报FileNotFoundException生产环境不要把文件放在临时目录或项目目录下面重启后文件就丢了最好是独立的数据盘或对象存储。5.6 时间格式和上传文件访问问题还有一个隐蔽问题需要单独说把实体类直接返回给前端时如果字段中包含大文本字段比如周报内容、岗位职责JSON 序列化和网络传输都会变慢。我当时是专门写了 VO 对象列表页只返回摘要字段详情页才返回完整字段避免一次查出来几百条岗位信息全都带着大文本字段走网络。这个优化对体验提升非常明显。我个人在实际开发中的体会是高校实习管理系统这种项目最大的难点从来不是某个技术点而是业务流程实现得够不够细。把状态变化理清楚、把权限控制做严、把统计口径对准确系统就成功了一大半。Spring Boot Vue 这套组合不是为了追新而是因为从开发效率、团队熟悉度、后期维护这几个维度看它始终是这一类系统最稳的答案。最后再分享一个小经验上线之前一定要用“学生、老师、企业”三个账号各走一遍完整流程从岗位发布到成绩发布一个操作一个操作地过。很多问题都是在自己实际点一遍之后才暴露出来的比如某个按钮权限配错了、某个状态跳转没写、某条统计口径差了一条数据。流程验证通过系统才能真正交给别人用。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

grandMA2onPC与UE4灯光:DMX/Art-Net链路排查实战 2026/9/30 10:38:09

grandMA2onPC与UE4灯光:DMX/Art-Net链路排查实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
因子图优化实战:建模、消息传递与回环因子加入 2026/9/30 10:38:09

因子图优化实战:建模、消息传递与回环因子加入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
封装继承多态的工程实践:从Python项目踩坑到架构升级 2026/9/30 10:38:09

封装继承多态的工程实践:从Python项目踩坑到架构升级

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
秀米排版不踩坑:从HTML/CSS底层原理到公众号高效排版全流程 2026/9/30 10:38:09

秀米排版不踩坑:从HTML/CSS底层原理到公众号高效排版全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Lp空间与lp空间完全指南:从范数定义到完备性证明的核心笔记 2026/9/30 10:38:09

Lp空间与lp空间完全指南:从范数定义到完备性证明的核心笔记

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
FPGA纯Verilog实现PNG解码:10套工程源码与硬件加速实战 2026/9/30 10:38:01

FPGA纯Verilog实现PNG解码:10套工程源码与硬件加速实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉