每年到了毕业季,都能在校园里看到同一幕场景:学生在各种微信群里被动接收零散的就业信息,企业HR一边抱怨收不到合适的简历,一边在多个平台重复发布岗位。作为一个做过几年Java开发、又回来带过几届毕设的人,我可以直接说,这种"信息管理系统"类型的题目,在毕设选题库里永远霸占一席之地——不是因为题目陈旧,而是因为它能完整覆盖一个业务系统的所有核心环节。
"毕业就业信息管理系统的设计与实现"这个题目,我在不同届学生里见过十几次,做得好的和做得潦草的,答辩表现和分数差距非常大。这篇就完全围绕这个题目来拆:它到底是做什么的、该用什么技术栈、数据库怎么设计、哪些功能是答辩加分项、哪些细节一旦踩坑就会浪费你一两周时间。题目标题里带了编号(11752),一般是指学校毕设选题库里的题目编号,不影响系统本身的业务定位,直接理解为"面向高校就业办、应届毕业生和招聘企业三方的信息管理平台"就好。
写这篇的定位很明确:给正在做这个题目、或者打算换皮做类似"XX信息管理系统"的同学,一份可以直接参考的完整实操笔记。内容会涵盖需求拆解、技术选型、数据库设计、核心功能实现、前后端对接和部署避坑,全部基于我实际带项目时走过的路子。你不需要完全照抄,但其中的设计思路和踩坑经验,在你写自己的系统时一定用得上。
1. 先把这个题目想清楚:它到底在解决什么业务问题
1.1 系统服务的三个角色和他们的核心诉求
一个合格的毕设答辩,老师问的第一件事一定是"你为什么要做这个系统"。如果你回答说"因为题目库里选了它",那这个开场基本就输了。所以做之前必须先把业务逻辑想透。
这套系统的业务背景一点也不复杂:学校就业办需要掌握毕业生的就业去向,学生需要获取招聘信息和投递简历,企业需要发布岗位和筛选候选人。在没有统一平台之前,这三方的信息流通非常低效——就业办靠辅导员层层转发通知,学生靠同学口口相传,企业靠进校宣讲会收纸质简历。所以这套系统的核心价值,就是让三方在同一个平台上完成各自的工作,让信息从"离散的线下传播"变成"统一的在线流转"。
1.2 功能模块的拆法:按角色划分永远比按功能划分清晰
我见过不少学生的系统设计文档,功能列表写得天花乱坠,什么"系统管理""信息发布""统计报表"全列出来,看起来功能很多,实际一团浆糊。正确的做法是先把角色厘清,再定每个角色能干什么、不能干什么。
这套系统的角色划分基本是固定的三员模型:
- 学生(毕业生):注册登录、完善个人简历、浏览企业发布的职位、投递简历/收藏职位、接收面试通知、查看就业政策与公告。
- 企业(招聘单位):注册登录(部分设计会加入管理员审核环节)、维护企业信息、发布/下架职位、浏览收到的简历投递、发送面试通知。
- 管理员(就业办/系统管理员):学生与企业账号的审核与管理、职位信息的审核与违规处理、公告与就业政策的发布、就业数据的统计查看、系统基础参数配置。
把角色和功能对应起来之后,"学生"不用看到"职位审核"这种管理端功能,"企业"也不用看到"就业统计",权限边界自然清晰。前端做菜单控制、后端做接口鉴权、数据库做数据范围隔离,三层都基于这个模型展开,不会乱。
1.3 为什么说这个题目是"安全牌"但也是"分数分水岭"
我说这个题目是安全牌,是因为它的业务逻辑清晰,没有复杂的算法,没有高并发,没有支付和第三方对接,一个学生独立完成没有任何技术瓶颈。但恰恰是因为题目常见,老师答辩时的心态往往是"看你有没有做出新意"。
同样是这个题目,有人只做增删改查,答辩五分钟就结束了;有人会加一个投递状态的可视化跟踪,把从"投递"到"待面试"再到"已录用"的流转做得清清楚楚;还有人会把就业统计做成图表,按学院、专业、学历统计就业率。后两种就是明显的加分项。这些功能在技术上都不难,但体现的是你对业务的理解程度——这才是毕设真正的考察点。
2. 技术栈选型决策实录:不追新但也不能太老
2.1 核心框架:为什么锁定Spring Boot 2.7.x
关注过Spring Boot生态的人应该知道,Spring Boot 3.x 已经发布有一段时间了,它基于JDK 17,性能更好,也带来了很多新特性。但我在带毕设时,给学生的建议基本都是:除非你非常确定自己的环境全部兼容,否则选Spring Boot 2.7.x。
原因很实际。第一,学校的实验室机器、学生自己的电脑,绝大多数安装的是JDK 1.8,而Spring Boot 2.7.x是支持JDK 1.8的最后一个大版本线,无需折腾环境。第二,网上能找到的资料、踩坑帖子、CSDN博客,90%以上都是基于Spring Boot 2.x写的,真出了问题搜一下就有答案。第三,有些看起来和Spring Boot无关的小坑,其实都是版本不兼容引起的——比如某个依赖版本过高,直接报一个特别奇怪、网上怎么搜都搜不到的异常,最后发现是Spring Boot版本和某个starter版本的兼容性问题。
如果你已经是Spring Boot 3.x甚至更高版本,也没有必要降级重写,只要注意JDK版本对应上就行。但稳妥起见,这篇笔记里的所有思路都是基于Spring Boot 2.7.x + JDK 1.8这套组合,这也是最多毕设项目的落地配置。
2.2 持久层和数据库:MyBatis Plus与MySQL的组合逻辑
持久层框架是另一个需要决断的技术选型。纯MyBatis和Spring Data JPA各有拥趸,但在毕设这个场景里,我最推荐的是MyBatis Plus——理由也很直白:单表操作完全不用写SQL,代码量少一半,而且自带分页插件。
实际做这个系统的时候,你会发现大部分业务查询是单表操作:学生查职位列表、企业查自己发布的职位列表、管理员查用户列表,这些都是典型的"只要拼条件就能查"的活,用MyBatis Plus的LambdaQueryWrapper直接搞定。真正需要手写SQL的场景集中在两块:多表关联查询(比如查职位详情时要带出企业信息)和统计类SQL(比如按专业统计就业人数)。这两块用注解@Select写在Mapper接口里就行,不需要多余的XML配置文件。
数据库选MySQL就没什么悬念了,8.0版本即可,注意安装时的字符集选utf8mb4而不是utf8,否则存不了Emoji表情。数据库设计工具可以用Navicat或者免费的DBeaver,直接在图形界面里建表导表都方便。
2.3 前端方案:Vue + Element UI是毕设的舒适区
前端这一层我不推荐搞得太复杂,什么微前端、TypeScript、Tailwind CSS,对毕设来说都是给自己找麻烦。最稳妥的组合是Vue 2.7 + Element UI + Axios + Vue Router,这套组合成熟到几乎每个前端问题都能搜到现成的解决方案。
有人会问,Vue 3都出来这么久了,为什么还要用Vue 2?这个问题我得说得实在一点:Vue 2的Element UI组件库真的太好用了,表格、表单、分页、对话框这些后台管理系统的必备组件全部现成,样式也统一,不用花时间调CSS。Vue 3虽然可以用Element Plus,但资料相对少一些,毕设这个时间节点上,稳妥优先。如果你自己有Vue 3的基础,用Element Plus也完全没问题,核心的组件使用逻辑是一样的。
2.4 周边组件:JWT、Redis、接口文档工具的必要性
还有一些周边组件,这里逐个说清楚,避免你什么都往项目里塞,也避免该用的没用到。
JWT(JSON Web Token):做登录鉴权的最佳选择。无状态、不需要在服务端存Session,前端每次请求把Token放在Header里传递即可。Spring Boot里有现成的jjwt库,几行代码就能完成生成和解析,适合毕设使用。
Redis:有些论文里会写"使用Redis做缓存提升系统性能",但在毕设这个数据量级别下,Redis的价值更多是体现在简历上而不是实际意义上。如果你要加Redis,我会建议用它存验证码或者Token黑名单,这样结合了实际业务,又不显得生硬。不加也完全不影响系统完成度。
接口文档工具:后端接口写完之后,可以集成knife4j或者Swagger自动生成接口文档。这对前后端联调特别重要,前端同学(或者你自己做前端时)可以直接在文档页面上试调接口,不用对着Postman一遍一遍手动填参数。
统一响应体:后端接口建议统一返回格式,比如Result ,包含code、message、data三个字段。这个看似不起眼,但对后续前端统一处理请求结果、统一处理错误提示非常有帮助,绝对不是白费的架构。
3. 数据库设计:就业系统最核心的几张表该这么建
3.1 用户体系设计:统一用户表还是三张表分开
数据库设计是一套系统最见功力的部分,也是答辩时老师拿起来就问你"为什么这么建"的部分。先说最基础的用户表。
有的设计会把学生、企业、管理员分别建三张独立的表,字段各管各的。我做过对比,不推荐这种方案。更合理的做法是一张user表做账号体系,存储username、password、role等字段,然后通过扩展表去存不同角色的个性化信息:student_profile存学生的学号、姓名、专业、学历等,company表存企业名称、行业、规模等。这样做的好处有三点:
- 登录逻辑只对着用户表做,不同角色走同一套登录接口,只不过登录成功后返回的菜单和战场权限不同;
- 权限管理简单——一个role字段就能确定用户角色,不需要在代码里写死角色类型;
- 扩展方便,以后如果加一个"教师"角色,只要在role字段里加枚举值,再新增一张教师信息表即可,改动很小。
3.2 业务表详解:职位、简历、投递、收藏、面试通知
用户表之后,就是几张核心业务表。我先直接给出表名和关键字段,再在同节下文解释设计意图。
job(职位表):id、company_id(发布企业)、title(职位名称)、category(职位类别)、salary_min、salary_max(薪资区间)、city(工作城市)、education_required(学历要求)、description(职位描述)、status(0下架 1招聘中 2待审核)、create_time。
resume(简历表):id、student_id(关联学生)、major(专业)、education(学历)、phone、email、expect_position(期望职位)、work_experience(实习/工作经历)、project_experience(项目经历)、skill_tags(技能标签)、file_path(附件简历路径)、update_time。
application(投递记录表):id、student_id、job_id、status(0已投递 1企业已查看 2通过初筛 3已发面试 4已拒绝 5已录用)、create_time、update_time。
favorite(职位收藏表):id、student_id、job_id、create_time,业务上应用户id和职位id做唯一约束,防止重复收藏。
interview(面试通知表):id、application_id、company_id、student_id、interview_time(面试时间)、location(面试地点/线上会议链接)、note(备注说明)、status(0未读 1已读)、create_time。
这5张表是系统的核心骨架。每一张表的设计都隐含了一个关键业务规则:投递记录表用status字段控制流程,而不是用多张状态表去记录;面试通知表外关联的是application_id而不是直接关联student_id和job_id,因为一次面试必然对应一次投递。这种细节在答辩时能体现你对业务是真正想清楚了的。
3.3 围统计辅助表:公告、就业统计与数据字典
再补两张辅助性质的表:
announcement(公告表):id、title、content、type(0系统公告 1就业政策 2招聘会信息)、publisher_id、create_time。这张表功能简单,就是管理员发布内容、用户浏览内容,前端配合一个富文本编辑器上传HTML即可,注意做一下XSS过滤。
就业统计:很多同学的就业统计是写死在代码里的SQL,每次统计去count一下。如果你是做毕业就信息管理系统,更规范的姿势是专门建一张employment_stats表(学生id、就业状态、就业单位、就业城市、就业时间),在投递流程走到"已录用"或者管理员手动维护时更新。这样最后做"按专业统计就业率"这类报表时,直接一条SQL联查学生表和就业统计表,逻辑清晰又高效。
另外提醒一句:所有表都加上id主键(自增或雪花ID均可)和create_time字段,这类公共字段是行业习惯,评审老师看了也挑不出毛病。
4. 后端核心功能实战拆解:从登录鉴权到投递闭环
4.1 登录认证设计:Spring Boot拦截器 + JWT
登录模块是每个系统都绕不开的入口。直接用最成熟的方案:接口层用JWT实现无状态认证,处理器配置一个自定义拦截器校验Token。
实现逻辑大概是这样的:
- 用户提交用户名密码,接口查出用户并校验密码(密码存储建议用BCrypt加密,不要用明文);
- 校验通过后,用jjwt库生成Token,通过统一响应体返回给前端;
- 前端把Token存储在localStorage(或内存),请求时在拦截器中把它写入请求头;
- 后端定义一个HandlerInterceptor,重写preHandle方法,取出请求头中的Token并解析;解析失败返回401状态码;解析成功把用户信息放进ThreadLocal或HttpServletRequest的属性中,供Controller直接取用;
- 在WebMvcConfigurer里注册这个拦截器,并配置好需要放行的路径——登录注册接口、静态资源路径、Swagger文档路径等。
这里有一个很容易踩的坑:Interceptor放行路径配错,导致前端所有的请求都被拦截。最常见的原因是放行时写的是"/login",但前端实际请求的URL是"/api/user/login",对不上,就配了个寂寞。这种问题排查方式也很简单,在preHandle方法里打印请求路径,对比实际请求与拦截器配置,一般一眼就能看出来。
4.2 职位发布与筛选查询:分页和条件组合的通用解法
职位模块是这个系统业务量最大的部分。后端接口至少要有两个核心接口:
- 企业端:发布职位、修改职位、上下架职位。
- 学生端:分页条件查询职位列表、职位详情。
学生端的分页条件查询是重点。职位列表的筛选条件通常有:关键词(职位名称模糊匹配)、城市、职位类别、学历要求、薪资范围。使用MyBatis Plus的分页插件,配合LambdaQueryWrapper构造条件,代码量很少:
LambdaQueryWrapper<Job> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(keyword), Job::getTitle, keyword) .eq(StringUtils.hasText(city), Job::getCity, city) .eq(education != null, Job::getEducationRequired, education) .ge(minSalary != null, Job::getSalaryMax, minSalary) .eq(Job::getStatus, 1) // 只查询招聘中的职位 .orderByDesc(Job::getCreateTime); Page<Job> page = jobMapper.selectPage(new Page<>(current, size), wrapper);这里有个小设计点:薪资筛选的粒度问题。很多毕设在这个地方直接糊弄过去,筛选条件里写"最低薪资不低于x",代码实现却是ge(Job::getSalaryMax, minSalary)还是ge(Job::getSalaryMin, minSalary)都没想清楚。我的建议是:薪资区间用了min/max两个字段,筛选用salary_max >= 传入的期望最低薪资去查,保证查出来的职位不亏待学生——这个逻辑面试官追问起来,你要答得出来为什么。
分页插件在MyBatis Plus中需要配置一个PaginationInnerInterceptor,同样在配置类里注册即可。注意版本对应,老版本配置方法跟新版有些差异,这个后面在部署避坑部分会细说。
4.3 投递状态机的实现与边界处理
投递模块是我认为这个系统最能做出亮点的地方,也是最考察逻辑能力的模块。
先搞清楚状态流转:学生投递简历后,生成一条application记录,状态为"已投递";企业查看该投递后,状态变为"企业已查看";企业决定发起面试,创建interview记录的同时把投递状态改为"已发面试";如果学生被拒绝,状态变为"已拒绝";面试通过被录用,状态变为"已录用"。
实现时最忌讳的就是在代码里直接写死状态数字,比如if (status == 3)。正确做法是在一个常量类或者枚举类里定义所有状态:
public class ApplicationStatus { public static final int SUBMITTED = 0; // 已投递 public static final int VIEWED = 1; // 企业已查看 public static final int SCREENED = 2; // 通过初筛 public static final int INTERVIEWED = 3; // 已发面试 public static final int REJECTED = 4; // 已拒绝 public static final int HIRED = 5; // 已录用 }还有一个边界条件别忽略:学生重复投递同一家企业同一个职位。如果业务上允许重复投递,状态管理会乱套;如果不允许,需要在投递接口里先查一遍是否已经存在记录。我建议在application表上加一个联合唯一索引(student_id + job_id),这样即使代码里漏了判断,数据库层面也会挡住重复投递,算是一道兜底防线。
面试通知的创建逻辑也要同步设计好。当企业把投递状态改为"已发面试"时,事务需要同时做两件事:更新投递状态 + 插入面试通知记录。这两步必须放在同一个事务方法里,否则就会出现"面试通知没发出去但投递状态已经变了"的脏数据问题。在Service层方法上加@Transactional注解,这个坑就能避开。
4.4 就业统计的数据来源:不要临时去count,要建立统计口径
就业统计模块是这个系统区别于普通CRUD的一个亮点,同时也是很多同学做得最敷衍的部分。
先从业务出发想清楚:就业办领导想看什么数据?按学院/专业/学历统计的就业人数和就业率、分月的就业变化趋势、就业去向分布(国企/民企/外企/升学/自由职业)、热门就业城市排名。这些如果每次都在前端请求时临时跑SQL去聚合,一是慢,二是口径不统一(比如"定向就业"和"灵活就业"算不算就业,在不同统计图里应该有统一定义)。
所以我建议在设计时单独建一张student_employment表,由系统在业务流转中自动维护:当某个学生的投递状态变为"已录用"或者管理员手动录入就业信息时,就向这张表插入或更新一条记录。这样就业统计的SQL查询就变成"查这张表"而不是"去投递记录里各种统计",简单、准确、高效。
统计接口可以用如下的SQL思路去聚合:
-- 按专业统计就业人数 SELECT sp.major, COUNT(*) AS employed_count FROM student_employment se LEFT JOIN student_profile sp ON se.student_id = sp.student_id GROUP BY sp.major;前端拿到聚合结果后,用ECharts画成柱状图或饼图,效果非常直观。这套设计还能为答辩加不少分,因为从业务层面的口径统一,到技术层面的数据沉淀,都能体现出你的系统不是一堆接口的堆砌,而是"有设计"的。
5. 前端页面与交互:Vue项目里的几个关键落地细节
5.1 路由守卫与动态菜单:三个角色如何共用一个前端
前端项目我建议直接使用Vue CLI来创建,它和Vue 2.7组合很顺畅。创建完成之后,把Element UI、Axios、Vue Router、Vuex(或Pinia)都装好,就可以开始动手了。
用户登录成功后,后端返回的数据除了Token之外,最好把用户基本信息(id、用户名、角色)也一并返回给前端。前端拿到角色之后,需要做三件事:
- 把用户信息存入Vuex或本地存储,供后续页面读取;
- 根据角色动态生成左侧菜单——学生的菜单是"职位浏览、我的投递、我的收藏、就业信息",企业的菜单是"职位管理、简历管理、面试通知",管理员的菜单是"用户管理、职位审核、公告管理、就业统计";
- 在Vue Router的路由配置中,对每个页面设置路由meta信息,比如
meta: { roles: ['student'] },然后在全局前置守卫里加一层角色判断。
动态菜单的实现方式建议:把菜单栏的数据和路由表的数据分开。路由表一次性全量注册(因为页面组件无非就那些,注册了也不会真的被加载),但菜单栏只渲染当前角色有权限的那部分,再叠加路由守卫做二次拦截。这种做法逻辑清楚、实现成本低。
5.2 Axios封装:拦截器携带Token与统一错误处理
前端调用后端接口,一个统一的Axios实例和拦截器是必不可少的。否则在每个页面里都写一遍"请求头里塞Token、响应码为401时跳回登录页",不仅代码冗余,还容易漏。
在request.js里做如下封装:
import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器:携带 Token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => { return Promise.reject(error) }) // 响应拦截器:统一处理错误码 service.interceptors.response.use(response => { const res = response.data if (res.code !== 200) { Message.error(res.message || '请求失败') if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { Message.error('网络异常,请稍后重试') return Promise.reject(error) }) export default service这个封装看着简单,但它是前后端联调的基石。你之后在任何一个页面里调用接口,只需要引入这个request实例,然后写业务代码就行,不用再关心鉴权和错误提示。
5.3 页面组件设计要点:列表页与表单页的三种复用技巧
整个管理系统里有大量"列表页 + 弹窗表单"的页面模式,比如职位列表加发布职位的弹窗、用户列表加审核弹窗、公告列表加编辑弹窗。这类页面写多了你会发现很多套路可以复用,总结下来有三个技巧:
第一,表格列字段统一定义。每个列表页定义一个columns数组或直接在el-table-column里配置prop,字段名跟后端返回的驼峰字段保持一致,减少转换工作量。
第二,分页组件公共化。封装一个分页组件,接收total和page/size参数,切换页码时触发事件,每个列表页复用,避免每个页面复制粘贴一段几乎一样的分页逻辑。
第三,弹窗表单的校验规则。Element UI的el-form自带校验规则,必填项、邮箱格式、手机号格式都可以通过rules配置,这样能大幅减少后端的无效请求。例如发布职位时职位名称为必填、薪资区间不能为负数,这类校验在前端先拦截了,后端接口的输入就干净很多。
在写列表页数据加载的时候,还要注意请求竞态的问题。所谓竞态,就是当用户快速切换筛选条件时,之前发出的慢请求可能在快请求之后返回,导致页面展示的是旧条件的数据。解决办法不复杂,在请求函数里用一个requestSeq计数器,每次新的请求发出时seq自增,响应回来后判断seq是否等于当前记录的值,不等就不更新数据。这个问题在毕设答辩演示时尤其容易发生——演示时手速一快,页面数据就乱了,现场很尴尬。
6. 测试、踩坑与部署经验:从本跑到线上的最后一公里
6.1 联调阶段最常见的5个问题与排查方法
整个功能写完,进入前后端联调阶段之后,你会发现好多问题非常琐碎但特别消耗时间。我这里把最常见的问题清单和排查方法整理出来,你遇到的时候可以照着查:
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 前端请求跨域报错 | 后端没配置跨域 | Spring Boot 添加CorsConfig即可;若用了网关或Nginx,在对应配置层放开跨域 |
| 后端拿不到请求体中的数据 | 前端没设置Content-Type为application/json | Axios发送数据时确保header正确设置 |
| 日期类型数据前后端差8小时 | 时区不一致 | 后端在配置文件中连接URL加上 serverTimezone=Asia/Shanghai,Jackson格式化加时区设置 |
| 上传的文件在服务器上找不到 | 本地路径与部署路径不一致 | 将上传路径配置到application.yml的配置项,用配置中心或环境变量覆盖 |
| 分页数据不准确 | 分页参数传递不一致 | 前端确认pageSize、current字段名与后端Page对象字段一致,或在后端用@RequestParam显式指定 |
6.2 一个典型的IDEA配置坑:JDK版本引发的编译连锁问题
毕设期间和JDK版本做斗争几乎是人人的必修课。常见于两种场景:一是用IDEA创建Spring Boot项目时默认选了JDK 17,导入后第一行就报错;二是项目写到一半,发现Maven下载依赖特别慢,或者干脆下载不下来。
如果你用JDK 1.8去创建Spring Boot 3.x项目,IDEA会直接不让你创建成功,这时需要把Spring Initializr的Server URL改成阿里云的镜像地址,然后选Spring Boot 2.7.x,SDK选1.8。
依赖下载慢的问题,在Maven的settings.xml中配置阿里云镜像即可:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>这步做完,下载速度能肉眼可见地提升。不要在这上面死磕等待,白白浪费很多时间。
6.3 Docker部署与云服务器上线的完整路径
系统写完,快答辩了,下一步就是把项目部署到云服务器上,让评委老师可以线上访问。这一步如果没做过,确实需要一点时间去摸索,但你完全可以用Docker搞定,减少环境差异带来的问题。
后端项目的Dockerfile可以这样写:
FROM maven:3.8.6-jdk-8 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM openjdk:8-jdk-alpine WORKDIR /app COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]前端项目在云服务器上,最轻的方案是先把Vue项目构建成静态文件(npm run build),然后放到Nginx的html目录下,配置一个反向代理,把/api前缀的请求转发到后端的8080端口。这份Nginx配置写出来是这样的:
server { listen 80; server_name your_domain_or_ip; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }整个过程走通之后,你会发现毕设部署到线上没有想象中那么神秘。后面面试聊起项目,可以顺带讲一句"项目用Docker部署到了云服务器,后端做了容器化处理",这比在简历上写三行技术栈更有说服力。
6.4 答辩准备:别让细节暴露你的系统质量问题
部署完成之后,离答辩也就不远了。最后这段时间,建议你按下面的清单过一遍系统,把细节质量拉满:
- 每个表单的必填校验是否有提示,日期选择器是否限制了过去的时间;
- 有无权限漏洞:普通学生调接口访问管理员的统计接口,后端是否拦截住了;
- 把系统里面所有的测试数据清理一遍,尤其是企业发布的一些内容乱码职位,别让评委看到你的库里飘着"测试职位1""asdf职位2"这种数据;
- 准备一处被System.out.println轰炸过的代码,或一段被SQL拼接的旧写法,能讲清楚"后来改成了什么方案、为什么改"——这证明你是真的做过,而不是背概念。
到最后答辩演示的时候,我个人的体会是:把"投递状态流转"和"就业统计报表"这两个功能放在演示的中后段,因为它们是系统的业务主线,最能体现逻辑完整度。另外开头不要花太多时间讲背景意义,直接在系统里走一遍"学生注册-完善简历-投递-企业查看-发面试通知-录用"的完整流程,流程走得顺,再加数据统计页面收尾,整套演示其实就是真实使用场景的再现,评审自然信服。