尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

SpringBoot+Vue高校实习管理系统:从设计到部署的完整实战指南

发布时间:2026/9/26 20:26:21

资讯中心
01
ARTICLE

SpringBoot+Vue高校实习管理系统:从设计到部署的完整实战指南

SpringBoot+Vue高校实习管理系统:从设计到部署的完整实战指南
1. 为什么高校实习管理系统选型SpringBootVue而不是其他组合每年到毕业季前后总有人拿着高校实习管理系统这类题目来找我说是在做课程设计、毕业设计或者帮学校信息中心跑腿。问了一圈选型基本就两种要么是SSH这种上古组合要么就是前后端不分离的JSPServlet。结果项目做完能跑但交到下一届学弟学妹手里谁都改不动。我这边最近整理了一套基于SpringBootVueMyBatisMySQL的高校实习管理系统源码标题写的是2025最新其实核心价值不是最新而是这套组合在高校场景里刚好卡在一个非常舒服的位置SpringBoot负责把后端从繁琐的配置里解放出来Vue负责把前端页面的交互效率提上去MyBatis负责让SQL不被封装到失去灵魂MySQL则是绝大多数高校机房和服务器都跑得动的轻量数据库。如果你正在纠结选型我建议你先想清楚这四件事——这套系统谁来用、数据量有多大、部署环境是什么样、后续有没有人接手维护。高校实习管理系统的用户无非四类学生、指导教师、企业负责人、院系管理员。学生要报名实习岗位、提交周报、上传实习报告老师要审核、打分、查看进度企业要发布岗位、确认学生到岗管理员要管理基础数据、分配指导关系、导出统计报表。这种多角色、多状态的业务模型非常适合用前后端分离来做因为不同角色的页面形态差异很大前端按角色拆分视图比后端拼模板要灵活得多。数据量方面一个学院一届实习生也就几百到一两千人产生的申请记录、周报、成绩评定加在一起撑死几十万条。MySQL完全够用不需要上Oracle也不需要搞分库分表。要是谁给你说这个规模需要微服务架构那是纯粹忽悠你。部署环境就更现实了。高校信息中心给的服务器配置普遍不高内存4G、8G都很常见你不可能在上面跑一堆微服务组件。单机部署一个SpringBoot Jar 一个Nginx静态资源加一个MySQL实例是最稳妥也最好维护的方案。再说接手问题。这套技术栈是目前Java岗位面试和工作中最主流的配备学弟学妹接手的时候网上资料多、遇到问题搜得到不会出现代码跑不起来又没人能教的情况。SSH那套现在就别考虑了Struts和Hibernate的生态基本上已经半僵化招聘市场上能看到的老项目越来越少。这套系统我是在SpringBoot 2.7.x Vue 2 Element UI 的基础上做的用Maven做依赖管理MySQL 8.0做数据库。为什么没用Vue 3不是Vue 3不行而是Element UI对Vue 2的支持更成熟很多高校里现有的教学资源和部署经验都集中在Vue 2上交接成本低。你想升级到Vue 3也没问题后面我会单独说迁移思路。2. 实习管理系统的模块划分与数据库设计要点2.1 权限模型设计四类角色共用一张用户表实习管理系统的核心数据模型第一张表必须是用户表。很多新手在这里容易犯错给每种角色各建一张表学生表、教师表、企业表、管理员表各来一套结果做登录的时候要在四张表里轮询做授权的时候更是原地爆炸。我这里统一建一张sys_user表通过role_type字段来区分角色用status字段控制账号启用状态CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(32) NOT NULL COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT 密码(MD5盐), real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, role_type TINYINT NOT NULL COMMENT 角色类型 1学生 2教师 3企业 4管理员, phone VARCHAR(20) DEFAULT NULL, email VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, status TINYINT DEFAULT 1 COMMENT 状态 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_username (username), KEY idx_role_type (role_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;密码加密使用MD5加盐虽然现在更推荐BCrypt但在课程设计级别的项目里MD5加盐已经是及格线。如果你做的是真实部署的系统建议直接用Spring Security自带的BCryptPasswordEncoder代码改动不大但安全等级完全不一样。角色单独放在一张sys_role表里做关联也可以但实习管理系统就四种角色用字段标识比多表关联更直观查询效率也高。兼顾扩展性的话可以在用户表里预留dept_id之类的字段后面要接学校统一身份认证时可以做映射。2.2 核心业务表从岗位发布到成绩评定的完整链路这套系统的主线逻辑非常清晰企业发布岗位 → 学生浏览并申请 → 指导教师审核 → 学生到岗实习 → 提交周报和报告 → 企业和老师评定成绩。围绕这条主线一共需要7张核心业务表internship_post实习岗位表字段包括企业ID、岗位名称、工作地点、招聘人数、已报名人数、薪资待遇、岗位要求、状态招聘中/已截止internship_apply实习申请表字段包括学生ID、岗位ID、申请时间、状态待审核/已通过/已拒绝/已撤销internship_record实习记录表学生申请通过后生成关联指导教师ID、企业联系人、实习开始和结束时间internship_weekly周报表学生每周提交包含本周工作内容、遇到的问题、下周计划internship_report实习报告表实习结束后提交包含报告内容、提交时间evaluation成绩评定表包含教师评分、企业评分、总分、评语notice通知公告表管理员发布全体用户可见岗位表里的已报名人数字段有人会质疑说这个值应该实时从申请表里统计不应该冗余存储。理论上确实是这样但实战场合下学生在岗位列表页要反复看到已报名人数/招聘人数这个比例如果每次响应都走一次COUNT聚合查询数据库压力会明显上升。折中方案是列表页读取冗余字段学生提交申请或撤销申请时用一个事务同时更新申请表和岗位表的人数。2.3 状态字段用int还是varchar我踩过的坑业务表里最关键的字段就是状态status。我最早做这类系统时习惯用varchar存中文状态值比如待审核、已通过查数据的时候一眼能看懂但问题很快暴露前端下拉筛选要传中文后端判断逻辑要中文equals数据库排序时中文按拼音还是按编码又乱套重构时改状态名还要同步改数据。后来我统一改成TINYINT用注释把状态枚举解释清楚。比如申请表里的status1待审核2已通过3已拒绝4已撤销后端代码里对应写一个枚举类前端也维护一套常量映射。这样数据的可读性靠枚举类注释保证代码逻辑判断走整数筛选排序都不折腾。还有一个细节所有业务表都要保留create_time和update_time两个时间字段可以不用外键约束但这两个字段必须有。项目里经常要按时间排序要查某个时间段内的申请记录没有这两个字段的话后补的代价非常大。2.4 数据库初始化数据让系统跑起来就能演示拿到源码后第一次启动最尴尬的事情是打开页面发现列表是空的不知道怎么演示。所以我在初始化SQL脚本里预置了完整的演示数据4个角色各两个账号、10个企业用户、20个学生用户、5个教师用户、每个企业发布3到5个岗位再加上一条完整的申请流程记录——从学生提交申请到教师审核通过再到企业确认到岗最后成绩评定保证你登录任何一个账号都能看到有业务内容的页面。演示数据里我特别处理了密码统一初始化为123456加密后写入SQL脚本登录后可以直接改密码。账号会在项目文档里列出一张表格方便你快速查找。3. SpringBootMyBatis后端落地的关键代码与设计逻辑3.1 为什么业务层选择Service接口Impl实现而不是只有Service类你打开这套源码的backend目录会看到包里每个模块都是controller、service、service.impl、mapper四层结构。有人觉得课程设计级别的东西不需要接口一个Service类够用了我原来也这么想直到有一次重构时发现没有接口意味着所有调用点都得跟着改实现类的签名而ServiceImpl接口的隔离让替换实现、写单元测试mock都方便很多。比如实习审核这个动作我定义了这样的接口public interface InternshipApplyService { // 学生提交实习申请 ResultDTO submitApply(InternshipApplyDTO dto); // 教师审核申请 ResultDTO reviewApply(Long applyId, Integer approveStatus, String reviewComment); // 撤销申请 ResultDTO cancelApply(Long applyId, Long studentId); // 分页查询申请列表 PageResultInternshipApplyVO pageApply(ApplyQuery query); }实现类里重点关注审核申请的Transactional逻辑更新申请表状态的同时要判断是否扣减岗位表的剩余名额。这里一旦失败必须整体回滚否则会出现申请状态是已通过但岗位名额没变的脏数据。事务这块我多说一句。SpringBoot里加Transactional很简单但它只对RuntimeException生效如果你抛的是受检异常事务是不会自动回滚的。我习惯在实现类里把异常统一包装成自定义的BusinessException继承RuntimeException然后再用全局异常处理器RestControllerAdvice统一捕获返回这样Controller层完全没有try-catch干净得多。3.2 ResultDTO统一返回结构前后端分离项目里最忌讳的就是每个接口返回的JSON结构都不一样。这个项目里我定义了一个统一返回体public class ResultDTOT implements Serializable { private Integer code; private String message; private T data; public static T ResultDTOT success(T data) { ResultDTOT result new ResultDTO(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultDTOT error(String message) { ResultDTOT result new ResultDTO(); result.setCode(500); result.setMessage(message); return result; } }前端Axios拦截器统一判断code非200直接弹出错误提示不需要每个页面单独处理异常分支。这个约定看着简单但很多项目改到后期发现返回结构五花八门就是因为一开始没定这个规矩。3.3 MyBatis动态SQL多条件查询的正确姿势实习管理系统的列表页普遍带筛选条件比如岗位列表按企业名称、岗位类型、地点筛选申请列表按状态筛选。MyBatis里处理这种多条件查询要靠where标签和if标签配合避免SQL拼接出错select idpagePost resultTypecom.example.vo.InternshipPostVO SELECT p.*, c.company_name FROM internship_post p LEFT JOIN company_info c ON p.company_id c.id where if testquery.keyword ! null and query.keyword ! AND (p.post_name LIKE CONCAT(%, #{query.keyword}, %) OR c.company_name LIKE CONCAT(%, #{query.keyword}, %)) /if if testquery.postType ! null and query.postType ! 0 AND p.post_type #{query.postType} /if if testquery.location ! null and query.location ! AND p.location #{query.location} /if if testquery.status ! null AND p.status #{query.status} /if /where ORDER BY p.create_time DESC /selectwhere标签会自动去掉第一个AND前缀避免出现WHERE AND的语法错误。我见过不少新手直接写WHERE 11也能跑但看着很不专业还是用where更优雅。LIKE拼接这里我用了CONCAT(%, #{keyword}, %)而不是%${keyword}%前者走预编译能避免SQL注入后者是字符串拼接等于白给攻击者留后门。这个区别在面试里也是高频考点你如果自己讲不清楚这一段说明还没吃透MyBatis。3.4 分页插件PageHelper的正确用法和坑列表页要分页很多同学选择手动写LIMIT #{offset}, #{pageSize}还要单独写一条COUNT查询。这套源码里我直接用PageHelper插件因为MyBatis官方生态里它最主流。PageHelper的用法很简单在Mapper查询前调用PageHelper.startPage(pageNum, pageSize)public PageResultInternshipApplyVO pageApply(ApplyQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListInternshipApplyVO list internApplyMapper.selectApplyPage(query); PageInfoInternshipApplyVO pageInfo new PageInfo(list); return new PageResult(pageInfo.getTotal(), pageInfo.getList()); }使用时有几个坑必须注意第一个坑——分页参数和查询之间不能有别的查询。PageHelper.startPage()只对接下来执行的第一个Mapper查询生效如果你在中间做了其他数据库操作分页会作用到错误的SQL上返回的数据会莫名其妙变少或者错乱。所以我严格要求在Service方法里startPage之后立刻调用目标查询。第二个坑——COUNT查询的性能。默认的count SQL是SELECT COUNT(*) FROM (...)套一层子查询数据量一大会慢。PageHelper支持自定义count查询给Mapper方法加Select(SELECT COUNT(*) FROM internship_apply WHERE ...)就能优化。第三个坑——XML里的多表关联分页。分页插件自动生成的count语句可能因为SQL复杂出现解析异常。遇到这种情况先在数据库里单独跑一遍count看能不能定位问题。一般解决方式就是手写count别所有查询都依赖插件。3.5 MyBatis缓存默认配置下别随便开二级缓存MyBatis的缓存分为一级缓存和二级缓存。一级缓存是SqlSession级别的默认开启同一个会话里执行相同SQL会直接返回缓存结果。但在SpringBoot整合的场景下Service层每次Mapper调用都在独立会话中一级缓存基本形同虚设。二级缓存是Mapper级别的需要手动开启而且开启后所有用户查同一张表都会命中同一份缓存。实习管理系统里用户权限差异极大——学生查自己的申请老师查学生的申请企业查自己的岗位管理员查全部数据。如果开二级缓存A学生申请了新岗位B学生登录后看到的数据可能还是旧的因为缓存里存的还是之前的查询结果。虽然MyBatis的二级缓存会在执行增删改时自动清空但当数据没有经过对应Mapper修改时比如管理员直接改库缓存不会同步失效。我的结论是这套系统不开二级缓存。业务量根本到不了需要缓存兜底的程度连Redis都没必要上。保持最简单的查询链路代码可读性最好也不会出现玄学数据问题。4. Vue前端与后端对接的实战细节4.1 Vue项目结构和路由设计前端部分我用的Vue 2 Element UI Vue Router Axios项目结构如下frontend/ ├── public/ ├── src/ │ ├── api/ # 接口请求封装 │ ├── assets/ # 静态资源 │ ├── components/ # 公共组件 │ ├── router/ # 路由配置 │ ├── store/ # 状态管理(Vuex) │ ├── views/ # 页面组件 │ │ ├── student/ # 学生端页面 │ │ ├── teacher/ # 教师端页面 │ │ ├── company/ # 企业端页面 │ │ └── admin/ # 管理端页面 │ ├── utils/ # 工具函数 │ ├── App.vue │ └── main.js └── package.json路由设计是前端最关键的环节我在router/index.js里用动态路由和路由守卫控制页面访问权限const routes [ { path: /login, component: Login }, { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: Dashboard, meta: { title: 首页看板 } }, // 学生端 { path: student/post-list, component: StudentPostList, meta: { role: [1] } }, { path: student/my-apply, component: StudentMyApply, meta: { role: [1] } }, { path: student/weekly, component: StudentWeekly, meta: { role: [1] } }, // 教师端 { path: teacher/review, component: TeacherReview, meta: { role: [2] } }, // 企业端 { path: company/post-manage, component: CompanyPostManage, meta: { role: [3] } }, // 管理端 { path: admin/user-manage, component: AdminUserManage, meta: { role: [4] } }, ] }, ]路由守卫里做两件事第一没登录一律跳转登录页第二登录后根据用户角色判断当前路由是否在meta.role允许范围内不在就跳到无权限提示页。4.2 Axios封装与Token自动附加前后端分离项目必然要做登录鉴权。后端每次接口请求都需要校验Token一般做法是登录成功后后端返回一个Token前端存到LocalStorage后续请求在请求头里带上Authorization字段。我在utils/request.js里做的一次封装import axios from axios import router from /router import { Message } from element-ui import { getToken, removeToken } from /utils/auth const service axios.create({ baseURL: /api, // 开发环境走代理生产环境走Nginx timeout: 10000 }) // 请求拦截器 service.interceptors.request.use(config { const token getToken() if (token) { config.headers[Authorization] token } return config }, error Promise.reject(error)) // 响应拦截器 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data // 直接返回业务数据页面代码少套一层 }, error { if (error.response error.response.status 401) { removeToken() router.push(/login) Message.error(登录已过期请重新登录) } else { Message.error(网络请求异常) } return Promise.reject(error) } ) export default service这个封装的好处是让页面里的接口调用非常干净比如申请岗位只要写import { submitApply } from /api/apply submitApply({ postId: this.postId }).then(() { this.$message.success(申请成功) this.loadList() })不需要每个页面重复处理Token和错误提示。这个习惯一定要养成不然几十个页面每个都写一遍拦截逻辑后期维护会让你崩溃。4.3 跨域问题本地开发和生产环境的区别前后端分离后跨域问题几乎是必经之路。开发环境下前端跑在localhost:8080Vue默认端口后端跑在localhost:8081浏览器会拦截跨域请求解决方案有两种。方案一后端配置跨域简单粗暴。SpringBoot里写一个配置类Configuration public class CorsConfig { Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addCorsMappings(Registry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }; } }方案二前端Vue开发服务器配置代理推荐。在vue.config.js里配置module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true, pathRewrite: { ^/api: } } } } }这种方式的好处是前端代码里请求路径统一写成/api/xxx生产环境让Nginx也把/api转发到后端开发和生产环境前端代码完全不用改。我推荐你用代理方案。4.4 Element UI表格分页组件的搭配学生申请列表、企业岗位列表、管理员用户列表这些页面结构高度相似我在源码里写了一个通用的分页混入避免每个页面重复写分页逻辑export default { data() { return { pageNum: 1, pageSize: 10, total: 0, loading: false, list: [] } }, methods: { handleSizeChange(val) { this.pageSize val this.loadList() }, handleCurrentChange(val) { this.pageNum val this.loadList() } } }页面里引入混入后只需要实现loadList方法做接口请求把返回的list和total赋值即可。Element UI的el-table配el-pagination用current-change和size-change两个事件驱动重新加载。这里要注意el-table一定要给row-key否则表格刷新时容易出现渲染错乱。5. 最容易翻车的地方时间字段、ID精度、异常处理排查实录5.1 LocalDateTime序列化后前端显示格式不对后端用了MyBatis 3.5之后数据库的datetime字段可以直接映射到Java的LocalDateTime不再需要手动类型转换。但SpringBoot默认的Jackson序列化LocalDateTime默认输出的是数组格式比如[2025, 1, 12, 14, 30, 0]前端拿到手根本没法直接展示。这个坑一踩一个准。解决方式是在配置文件里加spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8但注意这个配置只对java.util.Date生效对LocalDateTime无效。要解决LocalDateTime需要在项目里定义JacksonObjectMapper或者使用JsonFormat注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private LocalDateTime createTime;我建议一次性配一个全局配置类在Jackson2ObjectMapperBuilderCustomizer里注册LocalDateTimeSerializer和LocalDateTimeDeserializer这样所有实体的时间字段都会自动格式化不用一个个加注解。5.2 雪花ID传到前端精度丢失如果你的主键策略用的是雪花算法生成的Long类型ID在纯前端展示时由于JavaScript的Number类型存在精度上限2^53 - 1超过这个精度的数字会被截断导致后端返回的ID和前端的ID不一致。我原来在做一个管理后台时列表里点击编辑结果ID串了排了很久才发现是这个原因。解决方案是对参与前端传输的Long类型字段统一做序列化处理转成字符串输出JsonSerialize(using ToStringSerializer.class) private Long id;或者全局配置Jackson处理。这个坑在分布式ID场景下非常典型虽然高校实习管理系统用自增ID就够但你自己扩展项目时可能用到先记住这个坑。5.3 登录接口没有校验验证码这是很多课程设计项目的通病。没有验证码意味着可以被暴力破解学生用户密码本来就是弱口令一旦被刷出账号整个系统的数据都暴露了。在登录页面加VerifyCode组件后端生成图片验证码并存Session登录时校验这个功能不难不建议省略。5.4 MySQL连接时区问题MySQL 8.0的驱动对时区要求比较苛刻JDBC URL里如果不加serverTimezone参数会报Cannot get a connection, physical connection is not valid之类的错误。完整的连接配置如下url: jdbc:mysql://localhost:3306/internship_db?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruezeroDateTimeBehaviorconvertToNull很重要如果表里有0000-00-00 00:00:00这样的空时间值不加这个参数查询会直接报错加了以后会自动转成null。5.5 一个典型的排查链路企业端发布岗位后列表不显示有一次测试同学反馈企业用户登录后发布了一个新岗位页面提示发布成功但回到岗位列表却看不到这条记录。我排查的步骤是先看数据库岗位表里有没有这条记录。查了之后发现数据存在说明后端写库正常。然后看前端列表接口请求发现列表请求带了status1的筛选条件而新发布的岗位默认状态是0即未上架。管理员还没审核通过所以企业在列表里看不到。这里暴露了一个业务逻辑设计问题企业发布的岗位要先进入待审核状态管理员审核通过后才能在公共岗位列表里展示。而企业自己的管理端应该能看到所有状态的岗位。前端代码里企业端列表页复用了公共岗位查询接口导致状态过滤逻辑混在一起。修复方式是把企业端列表查询和后端Service方法拆开企业端用selectPostManagePage返回自己发布的所有岗位公共端用selectPublishedPostPage只返回审核通过且招聘中状态的岗位。这个案例说明列表接口的职责边界一定要清晰复用的时候要特别注意过滤条件是否会误伤业务需求。6. 源码使用指南从环境准备到部署上线的完整路径6.1 本地启动的核心环境配置拿到源码后本地启动的步骤是这样的前置环境我列一张表方便对照组件推荐版本说明JDK1.88或11均可不要高于17否则部分配置要调整Maven3.6依赖管理工具MySQL8.05.7也能跑但建议8.0Node.js14Vue 2构建需要建议16浏览器Chrome/Edge无特殊要求第一步创建数据库并导入初始化脚本。用MySQL客户端执行SQL脚本文件脚本里包含库表结构、初始数据和演示账号。第二步修改后端配置文件里的数据库账号密码启动SpringBoot应用。如果不出意外后端应该在localhost:8081跑起来访问/swagger-ui.html能看到接口文档。第三步前端安装依赖后启动npm installnpm run dev。前端默认端口8080自动代理到8081。6.2 Maven打包和前端build部署阶段后端先执行mvn clean package -DskipTests生成可执行的Jar包。如果要指定环境配置文件用--spring.profiles.activeprod参数启动。前端执行npm run build生成dist目录。然后把dist目录里的静态文件放到Nginx的html目录下在Nginx配置里写反向代理规则server { listen 80; server_name yourdomain.com; # 静态资源 location / { root /opt/internship/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } # API反向代理 location /api/ { proxy_pass http://127.0.0.1:8081/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files $uri $uri/ /index.html这块是Vue路由History模式必需的配置不加的话页面刷新就会出现404。如果你用Hash模式则可以不配但URL里会多一个#号观感差一些。6.3 性能优化几百人的并发场景需要做什么高校实习管理系统的并发量其实不高峰值也就是实习申请季那几天同时在线一百人左右已经很夸张了。这个量级下MySQL单机和SpringBoot单进程完全扛得住不需要额外引入缓存中间件。真正需要做的性能优化是以下几点数据库连接池配到合理值。SpringBoot默认的HikariCP连接池够用最小空闲连接设5最大连接池设20避免空闲连接过多占用资源。MySQL慢查询日志开启定位耗时超过1秒的SQL。大部分慢查询来自多表关联和LIKE模糊查询可以通过索引优化解决。比如岗位表的company_id字段、申请表的student_id和post_id字段都要建索引。我的初始化脚本里已经把这些索引加上了你可以在正式使用前用EXPLAIN检查一遍查询计划。JVM参数做一次调优。部署时指定合适的堆内存比如服务器4G内存给JVM 1G到1.5G的堆空间java -Xms1024m -Xmx1536m -jar internship-system.jar --spring.profiles.activeprod6.4 从Vue 2迁移到Vue 3的评估如果你拿到源码后想升级Vue 3我的建议是核心业务逻辑代码后端接口、数据库表结构完全不需要动只要替换前端部分。Vue 3的Composition API和Element Plus的组合在组件复用上确实比Vue 2 Element UI更舒服。但要注意Element Plus把部分组件API改了比如$listeners合并到了$attrsv-model绑定方式也变了。如果你想短期快速迁移可以保留Options API写法大部分代码平移后修一遍兼容性即可。如果你对Vue 3已经很熟我甚至建议直接把前端用Vite TypeScript重写反正后端就那几十个接口重写一遍工作量也不大两三天就能搞定。7. 我给这套源码额外加的东西开发时最容易被忽略的细节7.1 接口统一返回日志每个接口请求和响应我在后端加了一个日志切面用AOP记录请求路径、参数、耗时和返回码。上线之后排查问题非常有用不然用户报出一个操作失败你连是哪个接口出的问题都不知道。日志配置在logback-spring.xml里按天拆分控制台和文件同时输出。7.2 请假和考勤功能怎么扩展很多学校除了实习管理和成绩评定还要求统计学生的出勤情况。当前版本没有做考勤模块但我在实习记录表里预留了leave_start_time和leave_end_time两个字段学生请假只需要填入时间范围教师在审核周报时可以看到请假记录这个扩展用时大概半天就能做完。7.3 数据导出的实现思路成绩评定完以后管理员要把成绩报表导出成Excel交给教务处。项目里我用了EasyExcel这个库写了一个通用的导出工具只需要传入实体Class和数据列表就能生成带表头的Excel。接口是/admin/evaluation/export点击前端按钮自动下载。这个功能在课程设计和真实项目答辩里都是加分项建议你自己复现一遍不要只看代码。7.4 演示数据中的完整闭环示例我在初始化数据里专门准备了一个已经走完整个流程的学生案例从报名岗位、提交周报、上传实习报告到教师和企业分别打分最后管理员导出成绩。这个数据在答辩演示的时候特别好用你只需要用对应账号登录把这个学生的整个实习过程从头到尾点一遍评分逻辑、状态流转、权限控制一次性展示完毕评委会觉得这套系统完成度非常高。最后再分享一个实际经验。每年都会有人上来就问能不能直接帮我改一个数据库名字或者是怎么把Logo换成我们学校的。我的态度是拿源码学习没问题但至少先把我上面说的这些表结构、接口分页、Token鉴权、跨域配置这些核心概念过一遍再动手去改。这套系统的价值不是给你一个能交差的成品而是让你明白一个前后端分离的管理系统是怎么从零到一搭建起来的。遇到问题的时候先查日志、先定位到具体接口再谈修改学到的东西才是你自己的。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。