1. 项目概述
1.1 核心需求解析
高校专业实习管理,是很多软件工程、计算机类专业学生毕业设计里绕不开的一个题目。表面上看它是一个"教务管理系统"的变种,但实际上它踩中了真实业务场景里最痛的几个点:学生找实习、学校管过程、老师做评价、企业要反馈。这套系统的核心价值不在于"录入数据",而在于把一段长达数月、分散在多方的实习过程,用一套流程串起来,让每个角色都能随时知道"现在进行到哪一步了"。
从技术角度讲,这类系统的后端几乎默认选择SpringBoot。原因很简单:SpringBoot对中小企业级Web应用的覆盖度太高了,内置Tomcat、自动配置、生态成熟,搭配一套Vue3前端就能组成完整的毕业设计技术栈。我见过大量同类项目的前后端交互设计,几乎所有学生的选型逻辑都是一样的:后端SpringBoot处理业务逻辑和数据持久化,前端Vue3做页面渲染和状态管理,中间用Restful API通信。
如果你正准备做这个题目,这篇文章会完整拆解系统的设计思路、核心功能模块的落地方式、实操过程中的关键代码细节,以及毕业后真正被面试官问到的那些SpringBoot原理点。从搭项目骨架到部署上线,一条线走完。
1.2 业务流程全景
实习管理系统的业务复杂度,很多人一开始是低估的。你以为它只是一个CRUD系统,写几个Controller就能交差,但实际上它包含了一条完整的状态流转链路:
学生登录系统查看企业发布的实习岗位,在线提交申请,企业导师或学院管理员审核通过后,学生进入实习阶段。实习期间学生需要定期提交周报,记录工作内容,企业导师给出阶段性评价,学校指导教师不定期查看实习进度。实习结束后,学生提交实习总结报告,企业导师给出综合评价,学院根据过程数据和最终评价自动汇总成绩。整个流程下来,至少涉及五个角色的状态交互。
这种业务模型的本质是一个工作流系统。开发时最忌讳的就是把所有状态判断堆在Service层里写if-else,一旦实习流程出现"撤回申请""重新审核""延期实习"这类分支操作,代码会迅速腐化。所以设计时要在数据库层面就把状态字段、操作记录、时间线分开存储,每一条状态变更都留痕。这也是后期答辩时最出彩的一个设计亮点。
2. 系统功能模块设计
2.1 用户角色与权限体系
整个系统的角色划分,直接决定权限模块的复杂度。我建议按六种角色来设计:学生、企业导师、学院教师、教学秘书、系统管理员、超级管理员。其中学院教师和教学秘书的权限要仔细区分,教师只能看到自己指导的学生,教学秘书可以看到整个专业的实习数据。
SpringBoot侧实现权限控制时,我推荐使用Sa-Token而不是Shiro或Spring Security。理由有三点:第一,Sa-Token的API设计非常直观,StpUtil.login()、StpUtil.checkLogin()一行代码完成会话管理,对毕业设计这种时间紧张的项目非常友好;第二,它内置了权限校验注解@SaCheckPermission,可以直接标注在Controller方法上,省去写拦截器的麻烦;第三,它支持Redis集成,分布式场景下也能平滑过渡。
权限数据模型上,我采用了RBAC设计,五张表搞定:用户表、角色表、权限表、用户角色关联表、角色权限关联表。这里有一个容易被忽略的细节——不要只在前端做菜单级别的权限控制,后端Controller层的每个接口都必须做细粒度权限校验。我之前帮人排查过一个系统,前端隐藏了"删除岗位"按钮,但接口没做权限校验,直接调用API就能删数据,这是非常严重的安全漏洞。
2.2 核心功能模块拆解
整个系统按业务边界拆成六大模块:实习岗位管理、实习申请审批、实习过程监管、周报与指导记录、考核评价管理、数据统计导出。下面详细拆解每个模块的设计要点。
实习岗位管理模块承担的是企业信息维护和岗位发布功能。企业信息建议单独建表,一个企业可以发布多个岗位。岗位表的核心字段除了岗位名称、工作地点、薪资待遇、岗位要求之外,还要加上招聘状态和实习周期。这个模块的难点在于"岗位过期自动下架"的逻辑,我采用的方式是定时任务每天扫描一次,将截止日期小于当前日期的岗位批量置为"已截止"状态。
实习申请审批模块是整个系统状态流转最复杂的地方。学生提交申请后,申请记录表会生成一条数据,状态为"待审核"。企业导师审核通过后,状态变为"已通过",同时系统自动生成一条实习记录,关联到学生和岗位。这个过程必须用数据库事务保证原子性——审核通过的同时必须创建实习记录,任何一个操作失败都要回滚。我在Service方法上标记@Transactional,并且合理设置了事务的传播行为。
实习过程监管模块解决的是"学生到底有没有认真实习"这个问题。我的方案是让学生提交周报和实习打卡记录。周报表包含实习记录ID、周次、工作内容、心得体会、遇到的问题及解决方案;打卡记录表则记录打卡时间、定位信息、打卡备注。打卡功能如果要做定位,前端使用高德地图API获取经纬度,后端计算与实习企业地址的距离,超出500米则标记为异常打卡。
考核评价模块的评分逻辑需要仔细设计。我采用的是百分制加权评分模型:企业导师评价占60%,学院指导教师评价占40%。企业导师从工作态度(20分)、专业能力(30分)、团队协作(20分)、出勤情况(30分)四个维度打分;学院教师则根据周报质量(50分)、实习总结报告(50分)给出成绩。最终系统自动计算综合得分,并映射为五级制成绩(优秀、良好、中等、及格、不及格)。
数据统计导出模块是答辩时的亮点模块。实习状态分布、专业方向就业率、企业满意度、实习成绩分布这四类统计图几乎必做,前端使用ECharts渲染,后端通过SQL聚合查询返回统计数据。导出功能使用EasyExcel,支持实习名单、考核成绩、实习报告摘要的批量导出。很多人在做导出时踩过坑——千万不要在前端循环调接口逐条导出,一定要后端生成文件返回URL,前端用window.open或a标签下载。
3. 技术选型与项目搭建
3.1 SpringBoot版本选择的经验之谈
SpringBoot版本选择,是这类项目第一个容易踩坑的地方。"springboot版本太高"这个热词背后全是血泪教训。
我的建议是:不要追新。以当前生态来看,SpringBoot 2.7.x 是毕业设计项目最稳妥的选择。为什么不用3.x?因为SpringBoot 3.x 基于Jakarta EE 9规范,包名从javax.*变成了jakarta.*,很多网上能搜到的老教程直接失效。而且3.x版本最低要求JDK 17,如果你本机是JDK 8,强行用3.x会直接启动失败。校园里机房、实验室的机器,JDK版本普遍还是8或11,选SpringBoot 2.7.x加JDK 8是完全无痛的组合。
另一个要注意的点是SpringBoot与SpringCloud版本对应关系。如果项目只是单体应用,不要引入SpringCloud相关组件,徒增复杂度。我见过有人毕设里引入了Nacos、OpenFeign、Gateway,结果项目启动慢、配置复杂、问题排查困难,最后答辩时讲都不好讲清楚。单体应用就老老实实用SpringBoot自带的能力,把业务做扎实比堆技术栈有意义。
3.2 项目结构搭建
我推荐的分层结构如下:
com.example.internship ├── config // 配置类:跨域、拦截器、文件上传、定时任务 ├── controller // 控制层:接收请求、参数校验、返回统一响应 ├── service // 业务层:核心业务逻辑、事务控制 │ └── impl // Service实现类 ├── mapper // 数据访问层:MyBatis-Plus的Mapper接口 ├── entity // 实体类:与数据库表结构对应 ├── dto // 数据传输对象:接收前端参数 ├── vo // 视图对象:返回前端数据 ├── common // 通用类:统一返回结果、异常处理、常量、枚举 ├── utils // 工具类:JWT工具、Excel工具、日期工具 └── aspect // 切面类:操作日志记录Controller只做参数接收和响应封装,不写业务逻辑,Service层做实际业务处理,Mapper层只负责数据库交互。这个分层习惯从毕设养成,到了工作中会受益无穷。你去看SpringBoot相关的面试题,十道里有八道都在问"说说你对分层架构的理解",没有实际项目经验支撑很难答出深度。
对于数据访问层,推荐MyBatis-Plus,而不是原生MyBatis。原因不用多说——单表CRUD的代码量差了一个数量级,BaseMapper内置的selectById、insert、updateById直接省掉大量重复mapper XML。复杂查询用LambdaQueryWrapper构造条件,比如查询某个专业所有已通过审核的实习记录,一行代码搞定。
// 查询条件构造示例 LambdaQueryWrapper<InternshipRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(InternshipRecord::getStudentId, studentId) .eq(InternshipRecord::getStatus, "已通过") .orderByDesc(InternshipRecord::getCreateTime); List<InternshipRecord> records = internshipRecordMapper.selectList(wrapper);3.3 数据库表结构设计
数据库设计是整个系统的地基。我设计时遵循以下核心表:用户表(user)、角色表(role)、实习岗位表(internship_position)、企业信息表(company)、实习申请表(internship_application)、实习记录表(internship_record)、周报表(weekly_report)、打卡记录表(attendance_record)、考核评价表(evaluation)、通知公告表(notice)。再加一张操作日志表,记录关键操作行为。
有两张表的细节需要特别关注。第一张是实习记录表,它是连接申请、过程、考核的枢纽,建议字段包括:主键ID、学生ID、岗位ID、企业导师ID、学院教师ID、开始时间、结束时间、当前状态、综合成绩。第二张是考核评价表,建议设计为一条实习记录对应一条总评记录,同时关联两张子表存储企业导师评价明细和学院教师评价明细,这样查询总评时只需一次关联查询。
字段设计的几个实战建议:所有金额字段用DECIMAL(10,2),不用FLOAT/DOUBLE,避免精度丢失;所有时间字段统一用DATETIME,不用TIMESTAMP,规避2038年问题;业务状态字段用TINYINT存数字枚举,配合统一枚举类做映射;学生学号和企业统一社会信用代码一定要加唯一索引,避免重复录入。这些细节在面试时讲出来,比你背十道面试题管用得多。
4. 核心功能落地实操
4.1 SpringBoot整合MyBatis-Plus与数据访问
数据访问层是整套系统的地基工程,SpringBoot整合MyBatis-Plus的步骤看起来简单,但配置细节直接决定后期开发效率。
第一步在pom.xml引入依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>第二步在application.yml配置数据源和MyBatis-Plus相关参数。我通常会在配置里加上map-underscore-to-camel-case: true,让数据库下划线字段自动映射为Java驼峰属性;逻辑删除配置logic-delete-field: deleted配合实体类的@TableLogic注解,实现软删除;分页插件单独放在config类中注册。
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页插件一定要配置,不然Page对象不会自动拼接LIMIT语句。我当时做学生列表分页时,忘记注册这个Bean,查出来的数据永远是全量的,排查了很久才发现是分页插件没有生效。这个配置做到配置类里以后,所有分页查询都会自动生效,前端传current和size两个参数即可。
实体类通过@TableName注解关联数据库表,@TableId(type = IdType.AUTO)设置主键自增策略,@TableField处理字段映射。还有一个小技巧:项目开启MyBatis-Plus的SQL日志打印功能,在application.yml里配置:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl开发阶段开着日志,控制台能直接看到拼接的SQL语句,排查问题效率提升一大截。上线前记得关掉或调整日志级别,避免性能损耗和SQL语句泄漏。
4.2 实习申请流程的状态机设计
实习申请模块是整个系统里业务逻辑最密集的地方。我强烈建议用状态机思想设计,而不是随手写if-else。
定义一个枚举类存所有申请状态:
public enum ApplyStatus { PENDING(0, "待审核"), APPROVED(1, "已通过"), REJECTED(2, "已驳回"), WITHDRAWN(3, "已撤回"), CANCELED(4, "已取消"); private final Integer code; private final String desc; ApplyStatus(Integer code, String desc) { this.code = code; this.desc = desc; } // getter方法 }当学生提交申请时,状态为待审核;企业导师审核通过,状态变为已通过,同时创建实习记录;审核驳回,状态变为已驳回,需要填写驳回原因;学生在待审核状态下可以撤回申请,状态变为已撤回;若实习记录已生成但学生放弃实习,状态变为已取消,同时实习记录做作废处理。
状态流转的合法性校验一定要写。比如已驳回的申请不能再走审核通过的逻辑,已撤回的记录不能重复提交。我的实现方式是在Service层写一个状态机校验方法,接收当前状态和期望操作,返回是否允许流转,不允许则直接抛出业务异常,由全局异常处理器统一捕获返回前端。
实习时长计算也是一个实用细节。系统根据岗位发布的实习周期和企业导师确认的起止时间,自动计算学生实习天数。这块建议在生成实习记录时就算好存到数据库里,而不是每次查询时实时计算,因为后续周报和考核模块都会用到这个字段,实时计算会带来重复逻辑和性能损耗。
4.3 文件上传与在线预览
实习管理系统中,学生需要上传实习承诺书、实习报告等附件,企业可能需要上传营业执照扫描件。文件上传是必不可少的功能。
SpringBoot配置上传大小限制是第一步。默认配置1MB上传限制,绝对要把spring.servlet.multipart.max-file-size和max-request-size调整到合理值。我的配置是单个文件10MB,整个请求50MB。上传文件保存路径一定不要放在项目内部的resources下,因为项目重新打包部署后文件会丢失。推荐配置外部路径,比如Linux环境下的/www/upload/。
上传接口的设计,我建议返回文件ID和访问路径两个关键信息。文件ID用于业务表和附件表关联,访问路径用于前端回显和下载。文件存储到本地服务器,文件名不要用用户上传的原始文件名称,而应该用UUID重命名,避免出现中文乱码和特殊字符问题,也避免文件名冲突。
在线预览实习报告PDF文件,有一种在毕业设计里很好用的方案是使用pdf.js在前端实现预览。后端提供一个文件流接口,前端通过PDF.js加载并渲染。如果是图片文件,直接返回文件流给img标签的src即可预览。Office文件预览最麻烦,简单的做法是引导用户下载后用本地办公软件打开,复杂的做法是接入OnlyOffice或KKFile在线预览服务,但这类服务部署成本高,毕业设计不推荐。
@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { throw new BusinessException("上传文件不能为空"); } // 校验文件类型,只允许图片、PDF、Office文档 String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); List<String> allowSuffixList = Arrays.asList(".jpg", ".png", ".pdf", ".docx", ".xlsx"); if (!allowSuffixList.contains(suffix.toLowerCase())) { throw new BusinessException("不支持的文件类型"); } // 使用UUID生成新文件名 String newFileName = UUID.randomUUID().toString().replace("-", "") + suffix; // 按月份分目录存储,避免单个目录文件过多 String monthPath = new SimpleDateFormat("yyyyMM").format(new Date()); String dirPath = uploadPath + "/" + monthPath; File dir = new File(dirPath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, newFileName)); return Result.success("/preview/" + monthPath + "/" + newFileName); }4.4 定时任务与消息通知
系统里的"岗位自动下架""周报提交提醒""实习即将到期提醒"都依赖定时任务。SpringBoot实现定时任务很简单,启动类加@EnableScheduling注解,然后在方法上标注@Scheduled(cron = "0 0 2 * * ?"),就能每天凌晨两点执行一次扫描逻辑。
我用一个定时任务类处理三类提醒:一是检查岗位截止日期,将已过期岗位置为下架状态;二是给超过三天未提交周报的学生发送站内信和邮件提醒;三是实习结束前一周给学院教师发送审核提醒。执行频率别太频繁,一般每天一次即可,如果学校对实时性要求高,可以把过期岗位检查改为每小时一次。
消息通知用两个渠道就够了:系统站内信 + 邮件通知。站内信表设计很简单:ID、接收用户ID、标题、内容、是否已读、创建时间。邮件通知使用SpringBoot的spring-boot-starter-mail依赖,QQ邮箱或126邮箱的SMTP服务足够。发邮件前一定要确认SMTP授权码配置正确,很多人在这一步卡住,报javax.mail.AuthenticationFailedException,基本就是授权码填成了邮箱密码。
我踩过的另一个坑是定时任务方法内部出现了异常,整个任务线程被中断,后续任务不再执行。解决方法是每个任务内部用try-catch包裹独立逻辑,甚至给每个任务单独设置异常处理,确保一个任务失败不影响其他任务。
5. 常见问题与排查实录
5.1 SpringBoot版本太高导致的依赖冲突
"SpringBoot版本太高"这个热词,在实战中是真的会发生。SpringBoot 3.x发布后,很多同学直接用了最新版,然后发现之前用的mybatis-plus-boot-starter版本过低,启动报ClassNotFoundException: javax.servlet.Filter。
原因是SpringBoot 3.x迁移到了Jakarta命名空间,旧包里的javax.servlet相关类全部失效。查了下MyBatis-Plus官方,需要3.5.4以上版本才适配SpringBoot 3.x。如果你在pom里看到类似下面的报错,先别慌,版本对齐是解决路径:
*************************** APPLICATION FAILED TO START *************************** Description: An attempt was made to call a method that does not exist. The attempt was made from the following location: ... Caused by: java.lang.NoClassDefFoundError: javax/servlet/Filter我的建议方案是:直接降级到SpringBoot 2.7.x,同时保持JDK 8。我不止一次在项目维护中遇到过这种版本牵连的连环问题,对于以业务落地为核心的毕业设计项目,稳定压倒一切。如果你已经用SpringBoot 3写了不少代码,另一个方案是把MyBatis-Plus升级到3.5.5+,并且把代码里所有javax包替换为jakarta,但这项工作很容易遗漏,不如降级来得干脆。
5.2 多角色登录态管理与跨域联调
前端Vue3和后端SpringBoot通常不在同一台服务器上开发调试,跨域问题一定会在联调阶段爆发。SpringBoot侧最简单的处理方式是新增一个配置类,允许所有跨域请求:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(Registry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意allowCredentials(true)和allowedOriginPatterns("*")需要配合使用,如果只用allowedOrigins("*")会冲突报错。OPTIONS预检请求也要放行,否则前端调用跨域接口时会看到"请求被CORS策略阻止"的错误。
多角色登录态管理,我采用的是Token方案。用户登录成功后,后端生成一个JWT Token,返回给前端存储到localStorage或pinia。前端在请求拦截器中把Token塞进Authorization请求头,后端写一个拦截器统一解析Token并校验用户身份。
一个值得留意的细节:不同角色的登录入口可以共用同一个登录接口,后端根据用户输入的账号去用户表查询,拿到用户信息和关联的角色ID,生成Token时把角色和权限点全部写进Token的claims里。这样后续做权限判断时,直接从Token拿角色比对,不需要频繁查数据库。我在面试中问候选人"如何实现用户角色权限的动态刷新",大部分人回答都是"改数据库重新登录",但实际项目中更优雅的做法是在Redis里存储权限版本号,每次请求时比对版本,版本变化就重新加载权限。
5.3 部署中的真实问题清单
部署是很多毕业设计项目最轻视、最后出洋相的一环。很多人的项目在自己电脑上跑得好好的,一部署到服务器就各种启动失败。
我把部署中遇到的典型问题整理成一份速查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 项目打包后启动报找不到主类 | Maven打包插件未配置 | pom.xml中增加spring-boot-maven-plugin并设置mainClass |
| 端口被占用或外网无法访问 | 服务器安全组未放行端口 | 检查宝塔面板/云安全组规则,放行8080或自定义端口 |
| 数据库连接报Access denied | 生产环境数据库密码与本地不同 | 使用配置文件外部化,用--spring.datasource.password=xxx启动参数覆盖 |
| 上传的图片刷新后丢失 | 文件路径使用相对路径 | 改为绝对路径,并配置静态资源映射访问外部目录 |
| 前端页面请求后端404 | 前端未配置反向代理 | Nginx中配置/api路径转发到后端服务端口 |
如果你用宝塔面板部署,强烈推荐直接上Docker容器。宝塔的Docker管理器可以一键创建SpringBoot容器,环境隔离、迁移方便,以后换服务器直接把镜像打包带过去,不用重新配置环境。我的部署习惯是:MySQL、Redis、后端服务各起一个容器,再在宿主机配置Nginx反向代理。
Docker部署时有个SpringBoot项目常见的坑:容器内时间默认是UTC时区,而数据库存的是北京时间,查出来的时间总是差8小时。在启动容器时加-e TZ=Asia/Shanghai参数即可解决。另外容器内存限制要预留充足,SpringBoot应用默认JVM堆内存可能吃掉宿主机大半内存,用-e JAVA_OPTS="-Xmx512m -Xms256m"显式限制堆大小,避免服务器资源耗尽。
6. 深入理解SpringBoot自动配置原理
6.1 为什么SpringBoot能"自动配置"
做完整个项目,你会对SpringBoot的自动配置机制产生天然的疑问:为什么在配置类里写一个DataSource的Bean,加上application.yml里的连接信息,项目就能自动建好数据库连接池?答案藏在spring-boot-autoconfigure包中。
SpringBoot的自动配置核心机制是@EnableAutoConfiguration注解,它通过SpringFactoriesLoader机制加载META-INF/spring.factories文件中声明的所有自动配置类。这些配置类上都会加@ConditionalOnClass、@ConditionalOnMissingBean等条件注解。比如DataSourceAutoConfiguration只有在classpath存在javax.sql.DataSource类时才生效,RedisAutoConfiguration只有在classpath存在RedisConnectionFactory时才生效。
结合自动配置机制回答"SpringBoot的starter是什么"这类面试题时,你的答案会特别有说服力:starter本质上是一个Maven依赖组合包,它把一组相关依赖和自动配置类打包在一起。引入spring-boot-starter-web后,不仅有了SpringMVC相关jar包,还自动配置好了DispatcherServlet、内置Tomcat、JSON序列化器等全套Web能力。你如果再讲一句"这就是SpringBoot约定大于配置理念的体现",面试官基本就能给你加分。
6.2 自定义自动配置的场景
项目中有一个很实际的场景适合自定义自动配置:操作日志记录。每个Controller的写操作都想记录操作者、操作时间、操作内容,与其在每个方法里手写日志代码,不如用自定义注解加AOP切面统一处理。更进一步,你还可以把这个切面逻辑封装成一个自定义starter,放到公司内部仓库供多个项目复用。
自定义一个starter需要做的事情很清晰:写自动配置类,标注@Configuration和@ConditionalOnClass相关条件;在META-INF/spring.factories文件注册自动配置类;在POM中声明spring-boot-autoconfigure-processor依赖生成配置元数据。
我在项目中实践过把"文件上传服务"封装为独立模块:定义一个FileStorageAutoConfiguration,当项目配置了file.storage.local-path时,自动装配本地存储实现类;配置了file.storage.oss-endpoint时,自动装配OSS存储实现类。切换到不同的云存储供应商时,只需调整配置文件即可,业务代码一行都不用改。这套思想来自SpringBoot的Profile机制和条件装配设计,学透之后再看各种框架的扩展机制,会有一种豁然开朗的感觉。
7. 项目锦上添花的方向
如果你时间充裕,以下四个方向可以给项目增加真实亮点。
一是引入Redis缓存热点数据。比如岗位列表是访问频率最高的接口,每次查询都要打数据库。可以做一个简单的缓存策略:岗位列表首次查询后存入Redis,设置10分钟过期时间;有新岗位发布时主动清除缓存,下次查询重新加载。这个改造只需要一个缓存工具类加几行代码,但答辩时讲"缓存击穿、缓存穿透"的应对方案,会让项目的技术含金量上一个台阶。
二是SpringBoot整合WebSocket实现消息实时推送。企业导师审核学生的实习申请后,学生页面可以像聊天软件一样实时收到审核结果通知,不必手动刷新页面。WebSocket的接入难度不大,引入spring-boot-starter-websocket依赖,实现一个WebSocketConfigurer配置类,再开发一个消息推送服务类即可。需要注意的是WebSocket会话的管理和资源释放,避免连接泄漏。
三是Excel批量导入企业实习岗位信息。使用EasyExcel做一个模板文件,企业用户在系统里下载模板、填写岗位信息、上传导入,后端批量解析并校验数据合法性。这个功能在教学秘书批量导入学生信息、批量归档实习成果时也很实用。EasyExcel的API上手快,文档齐全,做导入导出的体验远好于Apache POI。批量导入时记得查询数据库是否存在重复数据,通过唯一索引做幂等控制。
四是引入SpringBoot Admin做运行监控。它将项目的运行状态可视化展示,包括内存使用、线程池情况、请求耗时、健康状态等。只要引入依赖并做简单配置,就能在浏览器里看到项目的实时运行指标。这个功能在自己本地调试和上线后排查问题时都有用。答辩时展示监控页面,可以直观地向评委证明你的项目已经考虑了运维侧的能力。
8. 个人经验总结
我在实际开发这个项目时,花在业务梳理上的时间比写代码多得多。很多人拿到这种题目就急着上手敲代码,结果做到一半发现角色权限没想清楚、流程状态有漏洞,又回头改表结构,反复折腾浪费了大把时间。我的建议是开发前先把业务流程图和数据表结构画清楚,用纸笔或在线工具花两个晚上把整个流程走一遍,之后编码就是单纯的体力活。
最后再分享一个小技巧:你在答辩前,把项目里用到的每个注解都翻一遍源码注释。面试官最喜欢问的就是"你为什么在这个地方用@Transactional,不加的话会怎样""@RestController和@Controller有什么区别",这些细节都是加分项。系统做成什么样是能力的下限,讲清楚为什么这么做才是能力的上限。