
每年到了一定节点就会有不少人过来问毕业设计到底做什么题目好我个人给的建议一直很明确——如果你学的是 Java 方向与其选那种纯增删改查的管理系统不如挑一个带着明确业务状态、有角色权限、还要处理文件上传的题目。今天要拆解的大学生入学审核系统就是这样一个典型项目它用了 SpringBoot 作为后端基础框架、Vue 作为前端页面框架覆盖了从学生注册填报、材料上传到审核员逐级审核、最终录取归档的完整闭环。这篇文章我打算把从选题思路、表结构设计、后端核心接口到前端页面实现、联调部署、论文结构全部按实际做项目的顺序讲一遍。适合正在为毕业设计发愁的大三大四学生也适合想通过一个完整项目快速熟悉前后端分离开发流程的自学者。首先要说明的是这类入学审核系统的本质并不是什么高深算法它的难点在于把“业务规则”翻译成“代码逻辑”。比如同一个学生申请在不同阶段能看什么、能改什么审核员退回之后学生要能重新提交但不能随便改关键信息管理员要能看到全流程记录。把这些规则理清楚代码写起来反而顺因为你每一步都知道自己在干什么。1. 项目整体设计与业务思路拆解1.1 为什么这个题目适合做毕业设计我见过太多人一开始想做什么“校园二手交易平台”“在线考试系统”说实话这类题目不是不行而是竞争太激烈而且容易被评委追问“你系统里到底有什么难点”。入学审核系统有一个天然优势它有明确的业务流转也就是状态机这个东西天然就是答辩里的亮点。另外一个重要原因是数据关系比较复杂有用户、学生档案、申请单、附件材料、审核记录、专业目录多个实体之间还有业务关联。这能支撑你在论文里写清楚数据库设计这一章画ER图、写关系模式内容非常充实。再加上文件上传、权限控制、分页筛选、统计报表这些常见技术点整个项目在“工作量”上非常能打。1.2 三类角色与核心业务闭环系统设计第一步要确定角色因为后续所有功能都是围绕角色展开的。入学审核系统一般至少有三类角色学生注册账号、填写学籍信息、上传身份证/证件照/成绩单等材料、提交申请、查看审核进度、根据退回意见修改后重新提交。审核员招生老师/院系审核人员查看待审核列表、核对材料、填写审核意见、通过或者退回、对已审核的申请进行批量操作。系统管理员管理用户账号、管理学院/专业目录、配置招生批次、查看全局统计数据、处理异常数据。核心业务流程可以概括为一句顺口溜学生注册填材料提交申请等审核审核员逐条看通过退回各归位录取结果公开后名单归档整个流程结束。这里有一个很多新手容易忽略的点审核退回之后学生的申请状态不能简单变回“待提交”否则学生改了任何信息都能再次提交。比较合理的做法是退回后状态设为“已退回修改”学生只能编辑材料并重新提交系统要记录每次提交和审核产生的历史记录保证后续有据可查。这个设计思路在答辩时非常加分。1.3 技术栈选型与理由前后端分离是现在毕业设计的主流SpringBoot Vue 也是目前最容易找到参考资料的组合。具体到项目里我推荐这一套层面技术选型选型理由后端框架SpringBoot 2.7 / 3.x生态成熟起步快内置TomcatORMMyBatis-Plus单表CRUD不用写SQL复杂查询用条件构造器权限方案JWT Spring拦截器/过滤器前后端分离下天然好用的无状态登录方案数据库MySQL 8.x数据量小经典稳定前端框架Vue 3 Vite组合式API更现代Vite启动速度比Webpack快太多UI组件库Element Plus表格、表单、上传组件开箱即用状态管理Pinia比Vuex更简洁TS友好接口请求Axios统一管理请求、拦截器处理token和错误为什么要选这套而不是用JSP单体架构原因很简单你毕设做的是“前后端分离”论文里就多了一条“前后端交互设计”的线技术含量和谈资都上来了。而且Vue的响应式开发体验确实好考试系统和后台管理类项目用Element Plus写页面效率非常高一天就能把主要页面全部糊出来。2. 核心功能拆解与数据库表设计2.1 功能模块全景图我做项目习惯先画功能清单再写代码。这个系统的功能大块可以分成四块学生端模块注册登录、个人信息维护、申请材料填报、附件上传、提交与撤回待审核前可撤回、进度查看、录取结果查看。审核端模块待办列表按批次/专业/关键词筛选、申请详情查看、审核通过/退回操作、批量审核比如同一专业材料齐全的批量通过、审核历史查询。管理端模块用户管理禁用/重置密码、学院专业管理、批次设置、录取名单导出、数据统计按专业、按材料完整度、按时间。公共模块登录鉴权、文件上传下载、字典管理、操作日志。这里我特别说一下“提交与撤回”这个功能。很多同学不理解为什么要有撤回其实这是真实业务需要学生手滑提交错了材料审核员还没开始看就应该允许学生撤回重新编辑。但一旦审核员已经开始审核就要锁住不允许撤回。这个开关本质上又是和状态机强相关的设计放在答辩上讲是很好的业务思考。2.2 数据库表结构怎么设计表结构设计直接决定你后面写代码的心情。要是表建得不好后面每个查询都在凑数据越写越痛苦。我按实际项目经验把核心表列一下并解释每张表的定位user用户表id、username、passwordBcrypt加密、rolestudent/reviewer/admin、status、create_time等。角色不建单独表因为系统没有复杂的用户角色多对多一个用户一个角色用字段就够。student_profile学生档案表user_id、姓名、性别、身份证号、毕业院校、联系方式、照片URL等。和user表一对一主要承接学生的基础信息扩展。major专业目录表id、专业名称、所属学院、招生人数、录取批次、状态。录取结果和筛选都要用到。application申请单表student_id、major_id、批次、当前状态state、提交时间、最后审核时间。这是一张核心业务表一个学生在一个批次里只有一条申请记录。attachment附件表id、application_id、文件类型证件照/身份证/成绩单/学籍证明、文件原名、存储路径、文件大小、上传时间。一张申请对应多条附件。review_record审核记录表id、application_id、审核人id、操作pass/return、意见内容、审核时间。这是不可删改的流水记录我每次都会保留。设计的时候有几个常见坑。第一不要把附件路径直接塞在申请表里一张表字段会越加越多第二审核记录和申请单一定要分开否则你写不出审核历史第三状态字段不要用int裸标建议用varchar存语义化状态比如PENDING_SUBMIT、PENDING_REVIEW、RETURNED、PASSED可读性更好查问题也不容易懵。2.3 状态机设计让业务流转清晰可查这是整个系统我觉得最值得展开的地方。入学申请的状态流转我建议设计成以下几条路径初始学生在系统中创建申请单状态PENDING_SUBMIT待提交这时所有信息可编辑。学生点击提交状态变为PENDING_REVIEW待审核信息锁死管理员/审核员可见。审核员点击“通过”状态变为PASSED已通过流程结束如果选择“退回”状态变为RETURNED已退回。学生在RETURNED状态下重新编辑并再次提交状态回到PENDING_REVIEW。批次录取阶段管理员将已通过的申请标记为ADMITTED已录取或者NOT_ADMITTED。这个状态设计好在哪每一处状态变化都有对应的操作者和时间记录你在前端页面也可以根据不同状态控制按钮显隐比如“编辑按钮只在待提交或已退回时显示”“撤回按钮只在待审核时显示”。代码上再配合枚举类做统一管理禁止到处写魔法数字后面维护起来非常舒服。3. 后端关键实现SpringBoot 接口与权限实战3.1 登录鉴权JWT 的完整落地过程这个项目里登录流程是用户提交用户名密码后端校验成功之后生成JWT返回给前端前端把token存在localStorage每次请求在请求头加Authorization: Bearer token后端用拦截器校验token并解析出用户id和角色写进ThreadLocalcontroller里直接拿出来用。我建议用JWT而不是Session核心理由是前后端分离后后端不维护Session状态水平扩展也更方便虽然毕设不涉及分布式但这个思路要写在论文里。JWT相关依赖就两个一个jjwt-api一个jjwt-impl。生成token的时候一定要加上过期时间一般一小时然后自定义一个拦截器在preHandle里放行登录接口其余接口一律校验。角色权限我用了一个很轻量的方案自定义RequireRole(reviewer)注解加拦截器里解析token角色后做比对。不需要引入Spring Security因为毕业设计重点是业务功能完整Security配置不够熟练反而把自己卡住了。当然如果你写论文想用提一嘴“实际生产中可以引入Spring Security做细粒度权限”就够了。3.2 申请列表分页多条件查询的封装审核员打开首页就是一个申请列表要支持按状态、按专业、按批次、按学号或姓名关键词筛选还要分页。这个功能就是典型的“多条件组合查询”MyBatis-Plus写起来非常爽。我实际用的方式是Controller接收一个ReviewQuery查询对象里面带上pageNum、pageSize以及可选的majorId、state、keyword等条件。Service层用LambdaQueryWrapper拼接条件关键字查询用like匹配姓名、身份证号、学号。需要注意的一点既然是审核端列表就不要把所有字段都查出来。列表接口只返回与展示相关的字段比如studentId、name、majorName、state、submitTime避免把简历附件等大字段直接查出来。详情信息单独一个接口学生点开申请时再查全量数据。分页返回结构我统一封装成ResultT里面带code、msg、data字段。data放一个分页对象page对象包含records、total、current、size。前端El-Table可以直接绑定records加pagination组件后端把total返回翻页逻辑非常顺。这里有个细节容易踩Like查询时注意模糊匹配列是哪个字段学生姓名的敏感信息记得脱敏比如中间字用星号替代展示更正规。3.3 审核操作接口一件事里做了哪些事审核员点“通过”时前端只传一个applicationId加审核意见但后端这里要做的事情远不止更新一条状态。我推荐写成一个事务方法顺序大概是这样根据applicationId查出申请单校验状态必须是PENDING_REVIEW否则直接报“当前状态不允许此操作”。校验通过后把申请单状态置为PASSED并更新lastReviewTime。插入一条review_record记录操作人、操作类型PASS、意见内容。如果专业有限额可以在此处对专业已录取人数做校验通过才允许。退回操作类似不过状态改为RETURNED。事务全都加上Transactional(rollbackFor Exception.class)防止记录插入了但状态没更新或者反过来。这段逻辑面试时完全可以拿出来讲它体现了你对数据一致性的理解比单纯CRUD值钱得多。在写这个接口时还要考虑幂等性也就是防止前端重复点击造成二次审核。我的做法是在进入事务前先用SELECT ... FOR UPDATE把申请单锁住再检查状态。如果是单机部署用乐观锁也可以就是update时把state也带进条件里更新行数为0说明已经被别人处理了。3.4 文件上传下载与路径管理材料附件是这个项目的另一个硬骨头。学生要传身份证照片、证件照、成绩单PDF等等。后端接口设计成一个通用的上传接口参数是MultipartFile加上一个文件业务类型字段比如id_card、photo、transcript。存储我选了本地磁盘存储没有接OSS理由是毕设已足够、也方便演示。配置文件里设一个upload.dir基础路径然后按yyyy/MM/dd生成子目录文件名改成UUID.原后缀避免重名和中文乱码问题路径入库域名前缀统一拼接。注意校验文件大小SpringBoot里要配spring.servlet.multipart.max-file-size10MB和max-request-size20MB否则大文件直接抛MaxUploadSizeExceededException前端还看不懂。类型校验也要做比如只接受jpg、png、pdf杜绝exe等危险文件。下载的时候用ResponseEntityResource把文件流给前端设置Content-Disposition为attachment触发下载。这里有个常见的坑路径拼接不要直接用前端传来的字符串要用url中截取相对路径防止路径穿越漏洞。安全这块在论文里写上一段算是一个加分细节。4. 前端关键实现Vue 页面与交互细节4.1 项目初始化和请求封装前端我用Vite创建Vue3项目命令是npm create vitelatest然后选vue模板。接着安装element-plus、element-plus/icons-vue、axios、pinia、vue-router。启动开发服务器之后第一步不是写页面而是把axios封装好。我在src/utils/request.js里创建axios实例设置baseURL为/api配上请求拦截器和响应拦截器。请求拦截器从localStorage里取token并设置请求头响应拦截器判断后端返回的code如果是401就让用户重新登录并跳转到登录页其它错误用Element Plus的ElMessage弹出后端返回的message。这样封装一次后面所有业务页面写请求就只需要关心数据。跨域的问题在开发阶段直接让后端配置CORSallowedOriginPatterns(*)加allowedMethods(*)。上线部署时再用Nginx做反向代理把/api转到后端端口这样前后端同源避免部署后还要处理跨域。4.2 学生填报页动态表单与上传组件学生填报页是整个前端最花心思的一页因为它的表单是分步骤的。我用Element Plus的el-steps组件做四步基本信息、联系方式、教育经历、材料上传。前三个都是普通表单校验最后一个是el-upload组件。这里我踩过最大的坑是el-upload的action属性直接填后端地址时用本地联调没问题一旦部署到服务器就要改非常麻烦。建议不要用自动上传而是用http-request自定义上传方法在该方法里调用封装好的axios上传接口并且把token带过去。同时监听文件变化上传成功后把返回的附件URL保存到表单数据里。关于文件回显需要注意后端返回的是相对路径时前端要用computed拼接完整URL。另外上传组件建议设置multiple加limit限制数量并且在上传前用before-upload校验类型和大小减少无意义的请求。4.3 审核列表页筛选、分页与审核弹窗审核员的主界面就是一张大表格加搜索筛选区。页面结构顶部是筛选表单状态下拉、专业下拉、关键词输入、查询/重置按钮中间是表格底部是分页器。表格列我一般设计为申请编号、学生姓名、申请专业、当前状态、提交时间、操作按钮。状态列用el-tag根据状态切换颜色待审核用warning黄色已通过用success绿色已退回用danger红色。操作栏在不同状态下显示不同按钮待审核显示“审核”按钮和“查看详情”按钮已通过显示“查看详情”已退回显示“查看详情”。这种以状态为驱动控制按钮的渲染逻辑前端要写清楚答辩的时候能展示你对业务的理解。审核操作我用的不是新开页或路由而是弹窗加el-descriptions展示申请详情和附件列表底部再放审核意见输入框。审核意见用el-input的textarea点击“通过”就调用通过接口点击“退回”先校验意见不能为空再调退回接口。这里注意提交成功后不要忘记刷新列表并且用ElMessage.success给用户反馈。4.4 路由与权限控制前端如何配合做权限隔离前端不能只依赖后端控制权限页面本身也要做隔离。我的做法是在vue-router配置里给每个路由加meta.roles字段比如学生填报页只允许student访问审核列表页只允许reviewer访问用户管理页只允许admin访问。在router.beforeEach全局前置守卫里先判断有没有token。没登录就重定向到登录页。已登录再判断当前路由的roles是否包含当前用户角色不包含就重定向到401页面或首页。用户信息在登录成功后就存进Pinia刷新页面时再从localStorage恢复避免刷新后状态丢掉了。这里有个小细节后端返回的用户信息里面就要带role字段前端不要自己去猜。我是在设计时就把用户表的role通过接口返回前端存到store里路由守卫直接读store。改动角色时再考虑同步更新store一般毕设不用做得太复杂。5. 常见问题排查与避坑实录匆忙把类型写成Integer前端显示状态时全是魔术数字后期改状态非常痛苦。这是我个人项目的真实体会状态按语义命名能让你少加很多班。在Vue 3项目里用TypeScript还是JavaScript毕设项目我建议JavaScript写起来快材料里也更好解释。如果你怕答辩被问TypeScript可以提一句“生产项目会用TS增强类型安全”这就算回答上了。5.1 跨域、端口与部署的连环坑开发阶段最常见的报错就是“Access to XMLHttpRequest at ... has been blocked by CORS policy”。原因很简单前端地址比如http://localhost:5173后端地址http://localhost:8080端口不同就跨域。后端加一个配置类实现WebMvcConfigurer在addCorsMappings方法里设置放行。但注意开发时前端Vite默认端口是5173如果后端配置了allowedOriginPatterns(*)带token的请求也可能因为allowCredentials而被浏览器拦截需要把allowCredentials(true)和allowedOriginPatterns(*)配合使用这个坑很多人没意识到。部署也有一串连环坑。前端打包之后是静态文件后端SpringBoot是个独立服务。最简单的部署方式是用Nginx托管前端dist目录同时把/api路径代理到后端服务。Nginx配置里location /api加proxy_pass http://127.0.0.1:8080;并设置proxy_set_header Host $host;。后端上传的文件要记得把upload.dir配置成服务器上的绝对路径不要用相对路径否则重启服务后路径容易错乱。另外别忘了后端端口要在服务器防火墙里开放不然前端代理到了但连接超时。这里推荐一套操作顺序先把后端的数据库脚本执行完再确认后端能通过java -jar启动并访问/api/health然后再部署前端。先保证后端可用再去弄前端排错会简单得多。5.2 看似简单但很致命的小问题汇总表格分页和筛选失效。原因多半是前端分页组件传的current值没有在筛选时重置回1比如你已经在第5页点查询时还在传5后端返回的自然不是第一页结果。解决方法是查询按钮点击和筛选条件变化时强制current1。日期格式无法展示。Java后端默认返回的LocalDateTime是ISO格式在前端表格里如果直接显示会带个T很难看。两种处理方式第一种在实体类字段加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)第二种前端用dayjs配合el-table的formatter转格式。我推荐后端统一格式化这样所有返回的日期都一致。上传文件中文名乱码。解决方式是后端生成存储文件名时统一用UUID加扩展名保存原文件名到数据库下载时通过ResponseHeader传回Content-Disposition里的filename*UTF-8编码。前端显示时读attachment里的name字段中文就正常了。5.3 状态流转与并发操作防止审核和提交互相打架后台管理系统虽然用户量不大但审核员可能会同时打开多个页面处理申请。假如同一个申请被两个审核员同时打开一个人点了通过另一个人再点通过就会出现状态错乱。解决方式是我在前面提过的SELECT ... FOR UPDATE行级锁加上状态校验就能避免重复操作。如果你不想用行锁也可以用乐观锁在application表加version字段更新语句SET state ?, version version 1 WHERE id ? AND version ?更新行数为0说明数据被改过直接提示“申请已被处理”。还有一个容易被忽视的场景学生提交申请和审核员审核几乎同时学生点了“提交”审核员刚好在列表里看到旧状态并点了“通过”。由于提交和审核都是基于状态机约束提交会校验“待提交”状态审核会校验“待审核”状态所以这种情况只会有一个操作成功另一个会被状态校验挡下来。5.4 论文与代码的配套写法论文不需要写得像教材但逻辑要闭环。我的建议是论文章节这样安排第一章绪论写学校的数字化办公背景、现有审核方式的问题以及系统目标。第二章需求分析三类角色、用例图、业务流程描述。第三章系统设计架构图、功能模块划分、数据库表设计ER图加表结构说明、状态机说明。第四章系统实现按学生端、审核端、管理端分模块写关键代码和截图。第五章测试写功能测试用例表、测试结果和性能结论。代码和论文对应很重要。评审老师会看你的系统有没有“审核记录”入口论文里有没有“审核记录表”如果代码做了但论文没写等于白做。反过来论文里写到的每个功能系统演示时都要能点到不要出现“系统支持统计功能”但界面上找不到按钮的情况。我习惯在论文初稿完成后对照功能清单过一遍演示脚本每项功能都提前演练确保不翻车。测试心得方面对于一个管理类系统功能测试用例表建议准备至少16条左右覆盖学生注册、材料提交、审核通过、审核退回、再次提交、管理员重置密码、跨角色访问权限等场景。还要写一条异常数据测试比如学生没传材料就提交这样说明你考虑了边界情况。6. 实操过程记录从空项目到跑通的完整顺序项目开发过程中如果你还没有一个可运行的骨架系统建议的编码顺序是先启SpringBoot后端并确认数据库连通用接口测试工具验证登录接口再创建Vue项目并封装axios最后一个个页面去对接。顺序不要乱不然就是两边来回跳排查问题时非常痛苦。我实际操作时按下面的顺序执行基本不会卡壳创建数据库admission_sys并按脚本来建表、插入管理员账号。新建SpringBoot工程配置application.yml里的数据源信息启动应用并查看日志有无报错。从后往前做先写用户登录注册、JWT工具类、拦截器。实现基础后端API专业目录CRUD、申请单查询分页、状态变更接口、附件上传下载。创建Vue项目配置Vite代理、axios封装、路由守卫。写登录页跑通凭token拉取用户信息。写学生端的填报页面做上传组件测试提交。写审核列表页测试筛选、分页、审核弹窗。写管理员端的用户管理和专业管理。最后集中测试清理测试数据录制演示截图。这样每完成一步都是在上一步基础上运行验证过的出了问题也知道是后端还是前端引入的不用把整个项目推倒重查。7. 个人经验与一些小提醒如果只看模板代码这个系统看起来“平平无奇”但你在里面认真处理了状态机、加了审核记录、配置了文件上传限制和权限区分它就和普通CRUD拉开差距了。我个人做完之后的体会是最值钱的不在于功能数量多而在于每一步业务逻辑是否闭环。你设计状态、记录日志、校验权限这些细节在论文里都能直接支撑你写出一章高质量的“系统设计”。最后再分享一个小技巧设计登录返回值时除了token、用户名、角色还可以把头像URL和真实姓名一起返回前端导航栏直接显示“欢迎你张三 [审核员]”这不仅让界面显得完整也给答辩演示添彩。后续如果你想继续扩展可以加消息通知模块比如审核结果出来后自动提醒学生那就是另一个独立的增色功能了。这些扩展点在答辩环节提到就能让题目更有延展性值得提前在PPT里埋个伏笔。