news 2026/9/28 5:44:41

SpringBoot+Vue招聘系统全栈实战:从权限设计到部署上线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue招聘系统全栈实战:从权限设计到部署上线

1. 毕设选题为什么要做招聘系统:一个既稳又耐打的全栈练手项目

每年到毕设季,我都能收到一堆私信,问的大多是同一个问题:市面上那么多开源项目,电商、博客、商城、后台管理系统到处都是,为什么我建议做招聘平台这类项目?尤其还是 SpringBoot + Vue 这种已经"烂大街"的组合,做出来会不会显得没新意?

先说结论:招聘系统不仅不会没新意,反而是最适合用来证明你掌握全栈开发能力的项目类型之一。原因有三。第一,它天然带有多角色概念。一个招聘平台里,有求职者、有企业招聘方、还要有平台管理员,这就意味着你的系统必须有完善的权限控制逻辑,而权限设计恰恰是面试官最爱问、课设答辩时最容易拉分的点。电商项目大多数只有买家和管理员两个角色,招聘系统天然是三端甚至四端,复杂度刚好比普通管理系统高一个台阶,但又没有高到做不完的程度。

第二,招聘系统的业务逻辑非常贴近真实企业级应用。职位发布、简历投递、收藏职位、简历筛选、面试邀请、消息通知、数据统计,每一个模块都对应真实业务场景。你在学校写的是一个毕设,但做出来的东西实际上是一个行业软件的迷你版,这套业务模型对后续找工作、写简历、进公司做需求理解都有帮助。

第三,从技术覆盖面上讲,SpringBoot + Vue 的组合能把你大学四年学过的东西全部串起来。后端你要写 RESTful API,要用 Spring Security 做认证和授权,要用 MyBatis-Plus 操作 MySQL,要用 JWT 做无状态登录;前端你要用 Vue Router 管理页面路由,用 Vuex 或 Pinia 管理状态,用 Axios 封装请求,用 Element UI 搭建后台页面。整套流程走完,Java 基础、数据库、前端、部署运维全都能过一遍手,性价比极高。

我见过很多学生一上来就想折腾微服务、分布式锁、消息队列,结果做到一半发现连单体的 CRUD 都写不利索,最后只能从网上扒代码改改交上去,答辩时一问细节就露馅。所以如果你是大三下或大四上,Java 基础还行但没完整做过一个系统的阶段,SpringBoot + Vue 的大学生就业招聘系

统管理平台是一个非常推荐的选题,既能控制开发周期,又能保证技术深度。这篇内容我会把整个项目从需求拆解、数据库设计、后端接口、前端页面到部署上线的完整链路都梳理一遍,帮你在做毕设时少踩几个坑。

2. 系统边界与角色权限设计:别把需求只写成"管理员能删帖子"

很多人的毕设死在第一步:需求文档写得稀烂,或者压根没写,直接打开 IDEA 就开始建表。建表建到一半发现字段缺东少西,返回去改实体类、改 Mapper XML,折腾几轮后代码一团乱麻。我建议所有做这类项目的同学,先花一天时间把角色、功能、页面清单梳理清楚,这一步做好了,后面写代码的速度能快一倍。

2.1 三类角色分别需要什么页面和接口

大学生就业招聘系统管理平台,核心角色就是求职者(学生)、招聘企业(企业账号)和系统管理员。有的项目还会拆出"导师"或"就业指导中心"这类角色,但作为毕设来说,三类角色已经足够撑起整个系统的复杂度,再加角色反而容易把权限逻辑写乱。

先看求职者端。求职者最核心的需求是找工作和投简历。那他的功能就应该是:注册登录、完善个人简历(基本信息、教育经历、实习经历、项目经历、技能标签)、浏览职位列表、按关键词和城市筛选职位、查看职位详情、投递简历、查看投递状态(待查看、已查看、邀面试、已拒绝)、收藏职位、查看面试通知、修改密码、退出登录。注意,投递状态这条线一定要做,这是招聘系统区别于普通信息发布平台的核心特征,你需要在后端维护一张投递记录表,记录每一次投递的流转状态。

企业端这边,核心需求是发布职位和筛选候选人。对应功能是:企业注册/登录、企业信息管理(公司名称、规模、行业、简介、Logo)、职位管理(发布职位、上下架职位、编辑职位)、收到的简历列表(也就是投递记录管理)、简历详情查看、投递状态更新(比如把某个候选人的状态改为"邀面试")、面试邀请发送、职位数据统计(比如收到的简历总数、职位浏览数,这个可以作为加分项做)。

管理员端就相对简单了,主要做的是平台治理:用户审核(企业注册后是否需要管理员审核通过才能登录)、职位审核(企业发布的职位是否需要先过审)、用户管理(禁用/启用求职者账号)、企业管理和分类管理(职位类别、城市字典这类基础数据维护)。这里我强烈建议给企业注册加一个"待审核"状态,一旦加了,管理员的存在的价值就体现出来了,答辩时你可以理直气壮地说"我的系统有完整的内容审核机制"。

2.2 角色权限怎么落到代码上

权限设计我推荐用最简单有效的方式:Spring Security + JWT,配合路径级别和方法级别的双重校验。用户登录成功后,后端根据角色返回不同的 Token 格式或在 Token 中放入角色标识。前端拿到 Token 后存在 localStorage 中,Axios 拦截器统一在请求头加Authorization: Bearer <token>,后端通过 Filter 解析 Token、拿用户信息、判断角色权限。

具体到实现层,我建议用自定义注解@PreAuthorize("hasRole('COMPANY')")之类的写法控制在 Controller 层。有的同学会去引入 Spring Security 的完整 ACL 权限模型,说实话对一个课设来说太重了,也不好讲清楚。你只需要保证:未登录用户不能访问任何业务接口,求职者接口只允许学生角色调用,企业接口只允许企业角色调用,后台管理接口只允许管理员调用,这个粒度就足够答辩了。

这里有一个容易翻车的地方:Spring Security 从 5.7 版本开始,WebSecurityConfigurerAdapter被标记为过时了。如果你用的是 SpringBoot 2.7 以上的版本,网上大量老教程的写法会报错。我自己在实际项目里推荐直接使用SecurityFilterChain的 Bean 定义方式配置过滤链,这是新版推荐做法,也避免了答辩时被问"为什么用过期写法"的尴尬。

2.3 状态机设计:投递记录是系统的心脏

招聘平台最容易做成一堆 CRUD 堆积的地方,就是把投递记录做成一张只有"已投递/未投递"两个状态的表。实际上投递是有完整生命周期的:学生投递简历后,状态为"待查看";企业查看简历后,状态变为"已查看";企业觉得合适,点击"邀请面试",状态变为"面试邀约",同时系统生成一条面试通知给用户;企业觉得不合适,状态变为"已拒绝"。

我建议你在设计数据表时给投递记录表加一个status字段,用 TINYINT 类型存数字枚举,0 待查看、1 已查看、2 面试邀约、3 已拒绝。之所以用数字不用字符串,是因为数字枚举在数据库层面更省空间、查询更快,后端做一个枚举类映射即可。这个状态流看着简单,但它是整个招聘业务的中枢,你的消息通知、用户中心列表、企业收到的简历列表全都依赖这张表的状态变化,把它做扎实了,系统就立住了一半。

3. 数据库设计:从 Table 结构到字段类型的具体落地

如果说权限设计是招聘系统的大动脉,那数据库设计就是这副骨架的基石。我见过很多学生的项目,代码写着写着发现关联查询关联不上,最后只能靠Map<String, Object>硬塞数据,根本原因就是表结构没设计好。这一节我会把招聘系统的核心表结构直接拆给你看。

3.1 核心表清单与字段设计

一个完整的招聘系统,至少有八张核心表:用户表(存放所有登录账号)、求职者信息表(扩展学生资料)、企业信息表、职位表、投递记录表、收藏表、面试通知表、新闻/公告表(可选,用于前台展示就业政策类内容)。下面是每张表的核心字段和设计思路。

用户表t_user:id主键,username用户名,password密码(BCrypt 加密后存储),phone手机号,email邮箱,role角色(用 VARCHAR 存字符串,值就是 STUDENT、COMPANY、ADMIN),status状态(0 正常、1 禁用),create_time创建时间。注意用户名建议加唯一索引,登录时用用户名查表,效率高。

求职者信息表t_student_profile:id,user_id关联用户表,real_name真实姓名,gender性别,birth_date出生日期,school学校,major专业,education学历(枚举:本科、硕士、博士等),graduate_year毕业年份,phone联系方式,email,introduction个人简介,resume_file_url简历附件地址(如果是 PDF 上传的话),avatar_url头像。

企业信息表t_company:id,user_id,company_name公司全称,company_short_name公司简称,industry所属行业,scale公司规模(人数区间),city所在城市,address详细地址,introduction公司介绍,logo_urlLogo 地址,status企业状态(0 正常、1 禁用),audit_status审核状态(0 待审核、1 审核通过、2 审核驳回)。

职位表t_job:id,company_id关联企业,job_name职位名称,category_id职位分类,city工作城市,salary_min薪资下限,salary_max薪资上限,education_requirement学历要求,experience_requirement经验要求(应届生/1-3年/3-5年等),job_desc职位描述,job_requirement任职要求,status职位状态(0 上架、1 下架),audit_status审核状态(0 待管理员审核、1 通过、2 驳回),view_count浏览次数,create_time发布时间。

投递记录表t_delivery_record:id,student_profile_id求职者,job_id职位,company_id冗余企业 ID(查询方便),status投递状态(0 待查看、1 已查看、2 邀面试、3 已拒绝),create_time投递时间,update_time状态更新时间。这张表一定要给student_profile_id + job_id建联合唯一索引,防止同一个学生重复投递同一个职位。

收藏表t_favorite:id,student_profile_id,job_id,create_time。同样建联合唯一索引。

面试通知表t_interview:id,delivery_record_id关联投递记录,student_profile_id,company_id,job_id,interview_time面试时间,interview_address面试地点,message通知内容,status状态(0 未读、1 已读),create_time。

公告/新闻表t_news:id,title标题,content内容,cover_url封面图,create_time。这个表是为了让前台首页有内容可展示,同时给管理员后台的"内容管理"模块提供数据支撑,属于锦上添花型设计。

3.2 字段类型的几个实战建议

我看到不少学生的建表语句用了一堆VARCHAR(255),什么字段都往里塞,这不合理。以下几点是实际开发中非常容易踩的坑。

第一个坑是密码字段长度。BCrypt 加密后的字符串长度是 60 个字符,如果你把密码字段设为VARCHAR(32),注册时一加密就报"Data too long"。很多新手因为这个查半天查不到原因。密码字段直接给VARCHAR(100),留够余量。

第二个坑是文本类的字段。职位描述、公司介绍、个人简介、新闻内容这类字段,一定要用TEXT类型而不是VARCHAR(255)。介绍性文字随便写两三百字很常见,VARCHAR 255 根本扛不住。如果你没用 MyBatis-Plus 的自动填充,插入大文本时也要留意不要超出限制。另外 MySQL 5.7 之后支持JSON类型,如果你想把一个学生的技能标签存成一个数组,用 JSON 类型会比拆一张标签表简单很多,但缺点是关联查询麻烦,课设场景我不建议为了炫技用。

第三个坑是金额和薪资字段。薪资上下限建议用INT类型存"月薪千为单位"的数,比如salary_min = 8代表月薪 8K。不要用DECIMAL(10,2),没必要,还把类型搞复杂了。如果是做真实项目,薪资字段要考虑税前税后、年薪月薪的差异,但课设场景用千为单位最直观、最好展示。

第四个坑是时间字段。创建时间、更新时间不要用VARCHAR,直接用DATETIME。MyBatis-Plus 有自动填充功能,你可以在实体类上配置@TableField(fill = FieldFill.INSERT)和@TableField(fill = FieldFill.INSERT_UPDATE),然后在项目里写一个MetaObjectHandler的实现类,每次插入和更新时自动填入当前时间,这样你连setCreateTime都不用写,省事且统一。

3.3 关联查询怎么设计才不跑偏

表设计完了,接下来是查询。招聘系统里最复杂的查询是"职位列表 + 公司信息 + 收藏状态"的多表关联。我见过有人直接写一条大 SQL,LEFT JOIN 三张表,WHERE 条件里又带分页又带子查询,结果查询效率极低,而且 MyBatis XML 里的 SQL 写长了特别容易出错。

我的建议是能拆就拆。职位列表页的核心查询拆成两步:第一步,按条件查职位表,分页返回职位基本信息;第二步,根据职位列表里的company_id批量查出企业信息,然后用Map在内存中做关联。这一步看似多查了一次数据库,但对性能和代码可读性都有好处。实际开发中的次数并不重要,课程设计这种并发量下,多表 JOIN 带来的维护成本远大于性能损耗。

另外,如果你用 MyBatis-Plus,我建议所有单表操作都用它自带的BaseMapper方法,多表关联才写自定义 XML。这样你不用写几百行 XML,代码干净,答辩时也好讲。

4. 后端核心接口的编写与踩坑:从登录鉴权到简历上传

数据库设计好了,接下来的硬骨头在后端。这里我重点讲三个模块:登录鉴权、职位搜索、文件上传。这三个模块是招聘系统里最容易出问题、也最体现水平的地方。

4.1 Spring Security 集成 JWT:用新版写法避开过时 API

我用的是 SpringBoot 2.7 以上版本,Security 的配置类直接注入SecurityFilterChainBean。

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth -> auth .antMatchers("/api/auth/login", "/api/auth/register", "/api/job/search", "/api/news/list").permitAll() .antMatchers("/api/student/**").hasRole("STUDENT") .antMatchers("/api/company/**").hasRole("COMPANY") .antMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint()) .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }

这里有几个关键点一定要讲清楚。

第一,permitAll()的接口是公开接口,比如登录、注册、搜索职位,但职位详情接口要不要公开?我建议公开。因为用户在未登录状态下浏览招聘网站是很正常的行为,你要是在这个接口上加了认证限制,前端写起来会非常难受(每次刷新页面都要重新登录才能看职位详情)。搜职位和看职位详情,应该允许游客访问,只有在投递和收藏时才强制要求登录。

第二,JWT 过滤器里,你解析 Token 拿到的userId怎么转成 Spring Security 能认识的Authentication对象?代码大致是:

String username = jwtTokenUtil.getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication);

拿到认证对象后,在 Controller 里用@AuthenticationPrincipal UserDetails userDetails就能直接拿当前登录用户信息,很方便。

第三,登录接口返回的数据结构。返回的 Token 不应该只是裸字符串,建议封装成对象,包含token、tokenType(固定是 Bearer)、expiresIn(过期时间,秒)、role。前端拿到后存起来,路由守卫里判断有没有 Token 和角色匹不匹配。另外登录接口里密码校验要用PasswordEncoder.matches(rawPassword, encodedPassword),千万不能把数据库里的密文拿来明文比对。

4.2 职位搜索接口:关键词匹配和条件筛选的取舍

职位搜索是前端首页最核心的交互,学生进来以后第一件事就是搜索。我建议搜索接口的参数设计为:keyword(职位名称模糊搜索,也可以匹配公司名称)、city(城市)、categoryId(职位分类)、salaryMin(薪资下限筛选)、educationRequirement(学历要求)、page和size(分页参数)。

在写 SQL 时,关键词搜索的逻辑用WHERE job_name LIKE CONCAT('%', #{keyword}, '%')来实现,同时如果你想搜公司名,可以再OR company_name LIKE ...,不过这会引入多表查询。我的做法是职位表冗余一个company_name字段,在企业修改公司信息时同步更新职位表里的冗余字段。虽然存在数据冗余,但招聘系统职位量级不大,同步更新一次成本很低,换来的是搜索接口的单表查询性能和简洁度。

另外搜索接口一定要做分页。MyBatis-Plus 的分页插件用法很简单:

Page<Job> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Job> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Job::getStatus, 0) .like(StringUtils.isNotBlank(keyword), Job::getJobName, keyword) .eq(StringUtils.isNotBlank(city), Job::getCity, city) .eq(categoryId != null, Job::getCategoryId, categoryId) .ge(salaryMin != null, Job::getSalaryMax, salaryMin) .orderByDesc(Job::getCreateTime); IPage<Job> result = jobMapper.selectPage(page, queryWrapper);

用LambdaQueryWrapper的好处是类型安全,写错了编译期就报错,不会像字符串column一样跑到数据库层才报unknown column。

4.3 简历附件上传:本地存储还是对象存储的取舍

最后说文件上传。招聘系统里,学生可能上传一个 PDF 版的简历附件,企业上传 Logo 图片。很多学生的习惯是把图片转成 Base64 塞数据库,这种做法我强烈不推荐:数据库会膨胀飞快,而且查询时把一个几十 KB 的 Base64 字符串读出来,性能极差。

正确做法是把文件存到服务器磁盘,数据库里只存一个访问路径。本地存储流程是:前端用el-upload组件选择文件,FormData 对象里 append 文件字段,用 Axios POST 到后端接口;后端接收MultipartFile,用 UUID 重新生成文件名,防止中文名乱码和重名覆盖;然后按日期建目录,比如上传的文件放在/upload/20240515/uuid.pdf,把文件写到磁盘上,再把拼接好的 URL 路径存到数据库。

需要注意两个问题。第一个是静态资源映射。SpringBoot 默认不直接暴露磁盘路径,你需要写一个配置类或配置文件把本机磁盘目录映射成 URL 访问路径。配置文件里的写法是:

spring: web: resources: static-locations: file:D:/upload/

这样http://localhost:8080/files/uuid.pdf就能直接访问到D:/upload/uuid.pdf这个文件,注意配置里的路径要和存储路径对应上。第二个是上线后文件路径会变,你把项目部署到 Linux 服务器上,绝对路径就得变成/usr/local/upload/。我的做法是写一个自定义配置项custom.upload-path,在application.yml里配置,不同环境切换只改配置不改代码。

文件类型和大小校验也别忘了,企业上传 Logo 只允许 jpg/png,简历附件只允许 pdf/docx,大小限制在 5MB 以内。用@RequestParam("file") MultipartFile file接收后先校验file.isEmpty()和file.getContentType(),不合法直接抛业务异常。SpringBoot 默认上传限制在 1MB 左右,一定要在配置里调大,不然前端选了个 3MB 的简历一传就报错,还看不到明确的错误原因,排查起来很头疼。

5. 前端 Vue 部分的关键实战:路由守卫、状态管理、组件复用

后端接口写完,前端是另一个大块。Vue 项目我建议用 Vue 2 还是 Vue 3?如果你现在才开题,直接 Vue 3 + Vite + Element Plus 是主流方向,网上资料也足够多。如果你的课程要求在 Vue 2 基础上做,也别慌,这两个版本的核心路由和状态管理思路大差不差,教材里那些Vue.use(Vuex)的写法在 Vue 3 里换成createStore就行。

5.1 路由设计:一个路由文件搞定全部页面

招聘系统的前端页面大致分几类:门户页面(首页、职位详情、企业列表、资讯列表)、学生中心(个人中心布局,内含我的简历、我的投递、我的收藏、面试通知)、企业中心(企业信息维护、职位发布、收到的简历、面试管理)、管理后台(用户管理、企业审核、职位审核、内容管理)、通用页面(登录、注册、404)。

我建议采用嵌套路由。门户页单独一个 Layout 组件,顶栏是 Logo 和导航菜单,中间是router-view出口;学生中心和企业中心也各做一套 Layout,左侧侧边栏菜单,右侧内容区。这样三个角色各自进到自己的专属界面,体验上清清楚楚,也方便在路由层面做权限控制。

路由守卫的核心逻辑是:进入路由前检查localStorage.getItem("token")是否存在。如果目标路由需要登录且没有 Token,跳转登录页并带上redirect参数;如果已经登录且是登录页,跳回首页。对于角色控制,可以在路由的meta字段里标上roles: ['STUDENT'],然后守卫里取当前用户角色做比对,不匹配就跳转 403 页面。

router.beforeEach((to, from, next) => { const token = localStorage.getItem("token"); if (to.meta.requiresAuth && !token) { next({ path: "/login", query: { redirect: to.fullPath } }); } else if (token && to.path === "/login") { next("/"); } else if (to.meta.roles) { const userInfo = JSON.parse(localStorage.getItem("userInfo") || "{}"); if (!to.meta.roles.includes(userInfo.role)) { next("/403"); } else { next(); } } else { next(); } });

5.2 Axios 拦截器与状态管理的配合

前端接口请求统一用 Axios 封装。核心做法是:创建request.js,设置baseURL,然后在请求拦截器里把 Token 塞到请求头。

service.interceptors.request.use(config => { const token = localStorage.getItem("token"); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; });

响应拦截器有两个作用:第一个是统一处理业务错误码。比如后端返回{ code: 401, message: "登录已过期" },前端在拦截器里return Promise.reject(error)的同时,跳转到登录页并清掉本地 Token;第二个是处理后端返回的code。我建议你的后端统一返回结构为{ code: 200, message: "success", data: {...} },前端拦截器判断code === 200时直接返回response.data.data,这样调用接口的方法里不用每次都从res.data.data里取数据。

状态管理我建议用 Pinia(Vue 3)或 Vuex(Vue 2),核心只存两个内容:token和userInfo。userInfo在登录成功后从后端拿,里面有id、username、role、avatar等字段,存到localStorage里,页面刷新后还能拿到。不要每次刷新页面都重新调一次getUserInfo接口,后台管理系统这么做没问题,但门户网站刷新频繁,白白浪费时间。但是注意:用户信息有变动时(比如改了头像、企业资料审核通过),要同步更新userInfo和localStorage,两个位置都改掉才不会出现页面显示和实际不一致的问题。

5.3 核心页面的组件拆分:让一个页面代码不超过 300 行

学生端投递记录页面、企业端收到的简历列表页面,是组件复用最能体现效果的地方。以投递记录为例,学生端和企业端看到的其实是同一张投递记录表的不同视角:学生端看的是"我投了哪些职位、状态如何",企业端看的是"谁投了我们公司的职位、我要怎么处理"。两个页面共用一个DeliveryRecord展示组件,只是传进去的查询参数不同、操作按钮不同。把状态标签封装成StatusTag组件,把分页条封装成通用分页组件,把空数据提示封装成EmptyPlaceholder,这些小组件在项目里能反复用。

组件拆分的原则是:一个组件只干一件事。如果发现一个文件里又调接口又渲染表格又弹窗又处理逻辑,代码超过 300 行,就拆。不要为了炫技搞什么高阶组件、插槽嵌套八层,毕设项目代码清晰比什么都重要,答辩时老师看你的代码结构清爽,印象分直接拉高。

6. 部署上线与毕设答辩准备的四个实用建议

最后一个部分是很多学生忽略、但真正拉开差距的地方。做完系统不是终点,能跑在公网上让人看到、能在答辩时把项目讲清楚,才算真正完成。

6.1 本地 IDEA 跑通项目的完整顺序

先从后端开始。把项目导入 IDEA,确认 Maven 依赖都下载完成。然后本地装一个 MySQL 5.7 或 8.0,新建数据库,导入sql脚本。改application.yml里的数据库地址、账号、密码,把 Redis 相关配置干掉(如果你没用 Redis 的话)。启动类跑起来,控制台出现 Spring Boot 启动成功的日志后,先用 Postman 调一下登录接口,确认能正常返回 Token,再继续测下面的接口。

前端这边,npm install装依赖。有时候会出现 node-sass 安装失败的问题,这是因为 node-sass 需要从 GitHub 下载二进制文件,国内网络环境容易失败。解决方案有两种:一是用 sass 替换 node-sass,安装命令是npm install sass -D;二是配一下镜像源,npm config set registry https://registry.npmmirror.com,大概率能解决。装完依赖后npm run serve启动,浏览器打开本地地址,如果接口通、页面能渲染、登录注册能走通,那就说明整个链路 OK 了。

这里提醒一个非常常见的启动失败原因:后端端口占用。SpringBoot 默认端口 8080,如果你本地装了 Nginx 或者其他项目占用了这个端口,启动会报Port already in use。改端口别硬改成 8081 就完事,前端 Axios 里的baseURL也得同步改,两个地方改了才能通。

6.2 打包部署到云服务器:前后端分离项目的标准姿势

如果你想把项目部署到公网,需要一台云服务器。后端打成 jar 包,前端npm run build生成 dist 静态文件。部署方式推荐用 Nginx 做反向代理:Nginx 托管前端静态文件,同时把/api开头的请求反向代理到后端端口。

Nginx 配置的关键部分是 location 块:

server { listen 80; server_name your-domain.com; root /usr/local/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

注意proxy_pass后面的 URL 末尾加不加斜杠是有讲究的:proxy_pass http://127.0.0.1:8080;不加,则/api/login会原样转发给后端;如果加成了http://127.0.0.1:8080/;,会把/api前缀吃掉变成/login,这是新手最容易踩的坑,没有之一。

后端的 jar 包启动用nohup java -jar app.jar > app.log 2>&1 &,日志输出到文件方便排查。部署完成后用curl http://127.0.0.1:8080/api/auth/login先自测一下,通了再通过域名访问前端页面,逐步排查,不要一上来就打开浏览器然后一脸懵。

6.3 答辩前要能讲清楚的三件事

第一个是项目架构图。不要求你用专业绘图工具画得多精美,但一定要能准确描述:用户通过浏览器访问 Vue 前端,前端通过 HTTP 请求调用 SpringBoot 后端接口,后端通过 MyBatis 操作 MySQL 数据库,文件走本地磁盘存储,整个系统的认证由 JWT + Spring Security 完成。这三层(前端、后端、数据库)的调用关系,是答辩必问的。

第二个是核心业务逻辑。投递简历的完整流程、面试通知怎么从企业端发到学生端、职位审核的流转逻辑,这三条链路你要能一口气讲清楚,讲的时候结合状态字段提醒自己:这个操作会改哪些表、更新哪些状态。把这条线串起来讲,老师基本就能确认这个项目是你自己写的。

第三个是你自己做的功能亮点。比如我给项目加了职位浏览量统计,用一个view_count字段记录每次点击职位详情时的请求数;给企业端加了岗位数据统计图,用 ECharts 展示一个月内收到的简历趋势。这些小功能不复杂,但是能体现你考虑业务的完整度,比干巴巴的 CRUD 强很多。

6.4 学习这个项目的正确姿势,别变成纯抄源码

我知道很多同学拿到项目源码的第一个习惯是:直接打开后端启动类,点运行,看页面,然后就没然后了。这种学法是学不到东西的,答辩也过不了关。

正确姿势是这样:第一遍,打开数据库脚本,把每张表看一遍,搞懂字段含义;第二遍,打开项目结构,把 Controller、Service、Mapper 三层对应关系捋顺;第三遍,挑一个核心接口比如"投递简历",从 Controller 入口一路追到 Mapper SQL,把整个链路读出来,在纸上画一下这个接口改了哪些表;第四遍,自己动手改一个小功能,比如给职位搜索加一个"薪资区间筛选",或者给企业端加一个"一键导出面邀名单"的 Excel 功能。等你亲手改动过一个模块,再讲这个项目时就和纯背代码完全不一样了。

说到底,SpringBoot + Vue 这个技术组合本身没有门槛,真正拉开差距的是你对业务的理解深度和代码的工程整洁度。招聘系统作为毕设题材,业务模型完整、扩展空间大、答辩素材丰富,认真做下来,你学到的不仅是一套代码,而是一整套从需求到部署的思维方法。这套方法,才是毕业设计真正应该带走的东西。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:44:03

云计算工程师成长路线:从Linux基础到云架构师四层进阶指南

这几年私信里被问得最多的一个问题&#xff0c;不是“K8s怎么学”&#xff0c;也不是“云原生是个啥”&#xff0c;而是“云计算工程师有没有一条可以照着走的成长路线”。问的人里有刚毕业的学生&#xff0c;有做传统运维想转行的&#xff0c;也有已经在云厂商子公司里干了一年…

作者头像 李华
网站建设 2026/9/28 5:43:08

批量重置文件夹时间:一键修复备份迁移后的文件时间戳

昨天收拾一个攒了三年的设计素材库&#xff0c;几千个文件&#xff0c;打开一看修改时间一排排全是“今天上午”“昨天下午”&#xff0c;我第一反应是同步工具发疯了&#xff0c;仔细检查后发现就是前阵子换硬盘做整盘迁移&#xff0c;把每个文件的创建时间和修改时间都刷到了…

作者头像 李华
网站建设 2026/9/28 5:42:33

ETF双动量轮动策略:从回测54%年化到实盘避坑指南

看到“年化54%”这几个字&#xff0c;绝大多数人的第一反应要么是“骗子”&#xff0c;要么是“快把代码发我”。我以前也这样&#xff0c;直到自己把动量轮动的逻辑、回测、实盘链路完整走了一遍之后才明白&#xff1a;真正值钱的不是那几行Python量化交易策略代码&#xff0c…

作者头像 李华
网站建设 2026/9/28 5:42:19

NodeJS大学生二手交易平台全栈开发实战:从数据库到小程序

这两年“大学生二手交易平台”几乎成了计算机毕业设计的常青树&#xff0c;十个人里能有三个选它。我经手过的相关课题也不少&#xff0c;NodeJS版本算是其中综合性价比最高的一档。它不像Java那套体系那么重&#xff0c;又比纯Python Flask多一些工程上的完整感&#xff0c;而…

作者头像 李华
网站建设 2026/9/28 5:41:57

Linux命令场景化手册:从基础到故障排查实战

先把话放这儿&#xff1a;如果你只是把 Linux 命令当成“背单词”&#xff0c;那你大概率会在三个月后忘掉一半&#xff0c;半年后彻底回到图形界面。这套《Linux 网络操作系统常用命令手册》不是又一份命令清单&#xff0c;而是把命令按“运维场景 排查链路”重新组织过的实操…

作者头像 李华
网站建设 2026/9/28 5:41:55

Windows 11部署全新安装:最干净最快速的重装系统方案

1. 为什么说部署全新安装是“最干净”的重装方式在写《Windows 11 从入门到精通》读书笔记的过程中&#xff0c;2.1.4这一小节给我留下的印象最深。它讲的是“通过部署进行全新安装”&#xff0c;说白了就是我们常说的“重装系统”——但注意&#xff0c;它不等于你在设置里点的…

作者头像 李华