☰
SpringBoot+Vue大学生就业招聘系统:从数据库设计到权限控制全解析
2026/9/30 15:18:42 网站建设 项目流程

1. 为什么大学生就业招聘系统是课程设计的“黄金题目”

每年到这个时间点,后台总有人问我课程设计选什么题目。我的回答一直很明确:带角色权限的Web业务系统,首选大学生就业招聘系统。原因很简单——它不像图书管理、学生选课那种“纯增删改查”的题目一眼被人看穿,也不像秒杀系统、分布式爬虫那样需要过深的架构知识。就业招聘系统的业务链条天然很长:学生注册、简历维护、职位浏览、在线投递、企业发布职位、简历筛选、面试邀请、管理员后台审核与数据统计,这一整套流程恰好能覆盖SpringBoot + Vue全栈开发的主要知识点。

这个题目还有一层隐性价值:它贴近真实业务。招聘系统里涉及大量“多表关联查询”“状态流转”“按条件分页筛选”“文件上传(简历附件)”,这些都是企业里每天都在写的代码逻辑。课程设计做完以后,面试官问起项目,你至少有真实业务可讲,而不是背一个“基于XX的XX管理系统”这种一听就知道是模板的答案。

这篇文章我会把整个系统的设计思路、数据库结构、后端核心模块、前端页面组织、环境搭建与部署、常见坑点全部过一遍。无论你是打算从零手写,还是基于开源项目二次开发,希望这篇文章能让你少走一两个月弯路。

先给一个总览:本项目采用前后端分离架构,后端基于SpringBoot 2.x + MyBatis-Plus + MySQL,前端基于Vue 2.x + Element UI + Axios,身份认证采用JWT令牌机制。系统分三种角色:学生、企业、管理员,三个角色各有一套独立的功能面板。接下来我会按模块把设计过程完整展开。

2. 技术选型:为什么是SpringBoot + Vue而不是其他组合

2.1 后端选型理由

SpringBoot在这个题目里有绝对优势。它最大的价值在于“约定优于配置”,一个注解就能搞定的事情,在传统SSH框架里要写大量XML配置。课程设计的周期通常只有4到8周,你没有时间去和配置文件搏斗,SpringBoot能把精力集中在业务代码上。

具体到内部组件,我的建议如下:

  • 持久层框架:MyBatis-Plus,而不是纯MyBatis。MP内置了通用Mapper、分页插件、条件构造器,写单表CRUD几乎不用手写SQL。对于课程设计这种规模的项目,MP能把代码量砍掉三成以上。分页查询用Page对象配合selectPage,比手写PageHelper要直观得多。
  • 数据库:MySQL 8.0,字符集统一用utf8mb4。如果你机器上装的是5.7,问题也不大,但要注意utf8mb4_general_ci和utf8mb4_0900_ai_ci的排序规则差异,导入SQL脚本时要留意。
  • 身份认证:JWT(jjwt库)。为什么不用Session?因为前后端分离架构下,后端接口不维护会话状态,前端通过请求头携带Token来标识身份。JWT的核心逻辑是:用户登录成功后,后端签发一个包含用户ID和角色的加密Token,前端把它存到localStorage,每次请求带上。

用一个类比解释JWT:它就像游乐场的腕带——游客入场时戴上,玩每个项目时工作人员看一眼腕带颜色就知道你有没有权限,不用再回售票处查名单。服务端不需要保存登录状态,天然适合分布式部署。

  • 接口文档:SpringDoc或Knife4j。课程设计答辩时,老师十有八九会问“你的接口是怎么设计的”,你直接把Swagger页面投屏出来,把每个接口的请求参数、响应结构说清楚,这个环节基本就是满分印象。

2.2 前端选型理由

Vue在这个项目里的地位同样不可替代。Element UI组件库对国内开发者极度友好,表格、表单、分页、弹窗、日期选择器都是现成的,而且默认样式偏“后台管理风”,不用做太多美化就能落地方案评审。

前端技术栈细分如下:

  • 脚手架:Vue CLI 4.x/5.x,vue create初始化项目时选择Router + Vuex,组件语言选ES6。
  • UI组件:Element UI 2.15.x。
  • HTTP请求:Axios,统一在request.js里做封装,拦截器里带Token、统一处理错误码和HTTP状态码。
  • 路由:Vue Router,使用路由守卫做登录校验与角色鉴权。
  • 状态管理:Vuex,存储用户信息、Token、角色标识。
  • 图表:ECharts,用于管理员端的数据统计页面。

需要强调一点:构建工具不建议现在去折腾Vite。虽然Vite启动速度快,但Vue CLI在兼容性、插件生态、资料丰富度上依然最适合课程设计场景。等你工作后用Vite不迟,课设阶段求稳。

2.3 数据库设计思路:五张核心表 + 两张辅助表

数据库设计是整个项目最先要做好的环节,直接决定后端的开发效率。我按业务模块把表结构拆解如下。

2.3.1 用户表(sys_user)

所有角色共用一张用户表,用role字段区分三种身份。这样做的好处是登录逻辑只需要查一张表,前端路由守卫也只需要根据一个字段跳转不同面板。设计字段如下:

  • id:主键,自增。
  • username:登录账号,唯一索引。
  • password:密码,存储BCrypt加密后的密文。
  • role:角色标识,0代表学生、1代表企业、2代表管理员。
  • status:账号状态,0代表正常、1代表禁用。
  • create_time:注册时间。

需要注意:企业用户的账号,建议关联企业信息表,而不是把企业名称直接存在用户表里。这样后续扩展企业认证、企业详情修改都不会动到用户表的逻辑。

2.3.2 学生信息表(student_info)

学生用户的扩展信息:姓名、性别、出生日期、学历、毕业院校、专业、联系电话、邮箱、期望职位、期望城市、个人简介、简历附件路径。

这张表通过user_id和用户表关联,一对一关系。简历附件路径只存相对路径,前端展示时通过接口拼接完整URL下载。

2.3.3 企业信息表(company_info)

企业用户的扩展信息:企业名称、统一社会信用代码、所在城市、详细地址、联系人、联系电话、邮箱、企业简介、企业Logo路径、营业执照路径、审核状态。

这里要单独强调audit_status字段。企业注册后不能直接发布职位,需要管理员在后台审核通过后才激活发布权限。这就是我前面说的“业务链条长”的体现——课程设计的业务逻辑里,一定要有状态流转,否则和图书管理没有区别。

审核状态的流转方向:0(待审核)→ 1(审核通过)或 2(审核拒绝)。审核拒绝时要填写拒绝原因,企业端能看到。

2.3.4 职位表(job_info)

职位表是系统的核心业务表,字段如下:

  • id、company_id(关联企业信息表)、job_name(职位名称)、job_type(职位类别)、salary_min、salary_max(薪资范围)、city(工作城市)、education_requirement(学历要求)、experience_requirement(经验要求)、job_desc(职位描述)、publish_time(发布时间)、status(职位状态:0招聘中、1已下线)。

职位表没有直接关联用户表,而是通过企业信息表间接关联,这在多表联查时需要多写一次JOIN。有些课程设计为了省事会把company_id换成user_id,我不建议这么做,因为你一旦做了企业信息审核,company_id才是业务上的正确外键。

2.3.5 投递记录表(delivery_record)

这是系统的“订单表”,也是流量最密集的一张表。字段:id、student_id(关联学生信息表)、job_id(关联职位表)、status(投递状态:0待查看、1已查看、2邀约面试、3不合适)、delivery_time(投递时间)、interview_time(面试时间,可空)、interview_location(面试地点,可空)、remark(企业备注)。

为什么单独建这张表而不是在学生表里加一个“投递过的职位”字段?因为投递是一个多对多关系,而且每一步状态流转都需要记录时间和操作人,必须用独立的关系表来承载。这也是我判断一个课程设计同学到底懂不懂业务的标准之一——关系表的设计能力,比单表CRUD能力值钱得多。

2.3.6 辅助表

收藏表(favorite_job):student_id+job_id+create_time,学生收藏职位用的。管理员公告表(notice):标题、内容、发布时间、发布人,管理员发通知用的。

至此,一个完整系统的表结构就勾勒出来了。七张表的关系清晰,业务不冗余,扩展空间也有——想加“面试评价”就加一张表,想加“站内信”就加一张表,都不会破坏现有结构。

3. 后端核心模块拆解:从登录鉴权到简历投递的状态机

后端工程建议按以下包结构组织:

com.example.recruitment ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── result(统一返回体) │ ├── exception(全局异常) │ └── constant └── utils(JWT工具、文件上传工具)

统一返回体设计为Result<T>,包含code、message、data三个字段。所有接口统一返回这个结构,前端Axios拦截器里根据code判断业务成败,HTTP状态码只负责传输层。

3.1 登录鉴权与权限拦截

登录接口设计为POST /api/auth/login,接收username和password,后端校验通过后生成JWT返回给前端。JWT生成时要写入用户ID和角色,过期时间建议设2小时,答辩演示时如果过期了重新登录即可。

String token = Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim("role", user.getRole()) .claim("username", user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact();

前端拿到Token后,Axios请求拦截器在请求头加Authorization: Bearer <token>。后端配置一个JwtInterceptor拦截器,校验/api/**路径下除登录接口外的所有请求。角色权限校验我用的是自定义注解@RequireRole("student")配合拦截器实现,这样在每个Controller方法上标注角色即可。

这里有个关键点容易踩坑:跨域配置和拦截器的执行顺序问题。如果你在SpringBoot里同时配置了CORS跨域和JWT拦截器,要确保拦截器放行OPTIONS预检请求,否则前端请求会直接挂掉。我的做法是在拦截器里直接判断:

if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; }

3.2 学生端核心接口设计

学生端的接口围绕“简历 + 职位 + 投递”三个核心对象展开:

  • GET /api/student/profile:获取自己的简历信息。
  • PUT /api/student/profile:更新简历信息,包含基本信息、教育经历、期望职位等。
  • POST /api/student/resume/upload:上传简历附件(PDF),用MultipartFile接收后存到服务器指定目录。
  • GET /api/job/list:分页查询职位列表,支持按关键词、城市、职位类别、薪资范围筛选。
  • GET /api/job/detail/{id}:职位详情。
  • POST /api/delivery:投递职位。投递前先查询是否已经投递过,防止重复投递。
  • GET /api/student/delivery/list:我的投递记录,状态变化实时展示。
  • POST /api/student/favorite:收藏/取消收藏。

投递逻辑里有一个状态机,这是整个后端最值得展开讲的业务点。投递记录的状态流转如下:

0(待查看) → 1(已查看) → 2(邀约面试) ↘ 3(不合适)

这个状态流的核心约束是:学生不能修改状态,只能查看;企业端操作状态;管理员可以看全量。后端代码里的核心实现就是DeliveryStatusEnum枚举类定义状态常量,Service层在更新状态前先校验当前状态是否合法,不做非法流转。

3.3 企业端核心接口设计

企业注册后默认是待审核状态,只有audit_status = 1时才能使用职位发布接口。企业端接口如下:

  • POST /api/company/register:注册企业账号,同时插入企业信息表和用户表,事务控制。
  • GET /api/company/info:查看企业信息。
  • PUT /api/company/info:更新企业信息。
  • POST /api/company/job:发布职位。
  • PUT /api/company/job/{id}:编辑职位,只能操作自己公司发布的职位。
  • PUT /api/company/job/{id}/offline:职位下线。
  • GET /api/company/job/list:本公司职位列表。
  • GET /api/company/delivery/list:收到的投递列表,按岗位筛选、按状态筛选。
  • PUT /api/company/delivery/{id}/status:更新投递状态(查看、邀约面试、不合适)。

这里有一个隐藏的权限控制细节:企业在操作投递记录时,必须校验这条投递记录对应的职位是不是本企业的。不能只根据delivery_record_id去更新状态,否则一个企业可以通过遍历ID操作其他企业的投递记录。正确的做法是先查出投递记录关联的job_id,再查出这个job_id对应的company_id,和当前登录用户的company_id比对,不一致直接拒绝。这是真实的权限校验逻辑,也是答辩时老师喜欢追问的点。

3.4 管理员端接口设计

管理员的功能面板:用户管理(列表、禁用/启用)、企业审核(通过/拒绝)、职位管理(下架违规职位)、数据统计(每日注册量、投递量、职位发布量)。

数据统计这部分用ECharts展示时,后端接口返回的数据格式要和图表要求对齐。比如统计最近七天的投递量:

// 返回结构:{ dateList: ["2024-05-01", ...], countList: [3, 5, 0, ...] }

这个接口的实现思路是:先查询最近七天的日期列表,再对每天单独统计投递记录数量。不要试图一条SQL把所有数据查出来再填充空值,代码可读性和性能都会变差。

3.5 文件上传模块

简历附件的上传和访问是高频考点。我的实现方式:

@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { // 校验文件类型:仅支持pdf、doc、docx // 校验文件大小:不超过5MB String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFileName = UUID.randomUUID().toString().replace("-", "") + ext; String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date()); String dirPath = "D:/upload/" + datePath + "/"; File dir = new File(dirPath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dirPath + newFileName)); return Result.success("/upload/" + datePath + "/" + newFileName); }

访问上传文件时,配置一个资源映射:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:D:/upload/"); } }

这里最容易踩的坑是:文件相对路径和映射路径不一致。如果你把文件存在D:/upload/20240501/xxx.pdf,但数据库里存的是/upload/20240501/xxx.pdf,访问请求到达后端时,映射处理器会把它转成本地路径D:/upload/20240501/xxx.pdf。路径少一个层级、多一个反斜杠,都会导致404。建议存路径时统一用相对路径格式,映射时统一处理盘符。

4. 前端页面组织与关键交互流程

4.1 前端工程结构

src ├── api(接口定义) │ ├── student.js │ ├── company.js │ ├── admin.js │ └── auth.js ├── assets ├── components(通用组件) ├── router(路由配置) ├── store(Vuex) ├── views │ ├── login │ ├── student(学生端页面) │ ├── company(企业端页面) │ ├── admin(管理员端页面) │ └── common(公开页面) └── utils ├── request.js(Axios封装) └── auth.js(Token存取)

页面拆分建议按角色建目录,每个角色下的页面才3-5个,清晰不臃肿。学生端:首页职位列表、职位详情、个人简历、投递记录、我的收藏。企业端:企业信息、职位管理、投递管理。管理员端:用户管理、企业审核、职位管理、数据统计。

4.2 路由守卫实现角色跳转

路由守卫是这个项目前端最值得写的逻辑。它解决的核心问题是:用户登录后,系统怎么知道该跳转到哪个角色面板。

router.beforeEach((to, from, next) => { const token = getToken(); if (!token && to.path !== '/login') { next('/login'); } else if (token && to.path === '/login') { next('/'); } else { const role = store.state.user.role; // 根据路由meta.roles判断当前角色是否允许访问 if (to.meta.roles && !to.meta.roles.includes(role)) { next('/403'); } else { next(); } } });

这里有个细节:角色信息什么时候存入Vuex?登录接口返回的数据里带上role字段,前端登录成功后先存Token再存角色,然后根据角色跳转:

const roleMap = { 0: '/student', 1: '/company', 2: '/admin' }; window.location.href = roleMap[res.data.role];

不推荐在路由守卫里根据角色动态拼接路由表,课程设计阶段用静态路由 + 角色meta控制就够了,简单稳定,不容易出幺蛾子。

4.3 职位列表页:搜索筛选 + 分页的实现要点

职位列表页是整个系统最核心的展示页面,它的交互完整链路是:用户选择筛选条件 → 点击查询 → 前端组装查询参数 → Axios GET请求携带参数 → 后端用MyBatis-Plus条件构造器查询 → 返回分页数据 → 前端渲染表格和分页组件。

前端搜索条件表单绑定一个queryParams对象:

data() { return { queryParams: { pageNum: 1, pageSize: 10, keyword: '', city: '', jobType: '', salaryMin: '', salaryMax: '' } }; }, methods: { loadData() { getJobList(this.queryParams).then(res => { this.tableData = res.data.records; this.total = res.data.total; }); } }

后端查询逻辑用LambdaQueryWrapper实现:

LambdaQueryWrapper<JobInfo> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(keyword)) { wrapper.like(JobInfo::getJobName, keyword); } if (StringUtils.isNotBlank(city)) { wrapper.eq(JobInfo::getCity, city); } Page<JobInfo> page = jobInfoMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);

需要注意一个细节:keyword要做模糊查询(like),city和jobType做精确查询(eq),这两个不能搞混。否则搜索“北京”会把“北京东路”之类的地名也匹配出来。薪资范围筛选同理,建议前端用两个独立的数字字段传给后端,后端做salaryMin >= ?和salaryMax <= ?的范围条件。

4.4 Element UI表格操作列中的权限控制

职位列表和管理列表的操作列,是前端最容易写乱的地方。以企业端职位管理为例,每行有“编辑”“下线”“查看投递”三个操作按钮。如果是“已下线”状态,就只显示“编辑”和“重新上线”,不再显示“下线”。

这个逻辑用v-if按状态渲染即可:

<el-table-column label="操作" width="220"> <template slot-scope="scope"> <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button> <el-button v-if="scope.row.status === 0" size="mini" type="danger" @click="handleOffline(scope.row)">下线</el-button> <el-button v-else size="mini" type="success" @click="handleOnline(scope.row)">重新上线</el-button> <el-button size="mini" type="primary" @click="handleDelivery(scope.row)">投递记录</el-button> </template> </el-table-column>

不要在一个按钮里写一堆条件表达式,宁可多拆两个按钮,代码可读性要紧。

4.5 简历投递流程的完整交互

学生查看职位详情 → 点击“投递简历”按钮 → 前端先校验是否已登录 → 请求后端投递接口 → 后端校验是否已投递 → 返回结果 → 前端提示“投递成功”或“已经投递过该职位”。

投递成功后需要在按钮状态上体现出来:用disabled属性加已投递文字,避免学生重复操作。这里的前端状态建议从后端响应中获取,不要本地存一个集合,因为刷新页面后本地状态会丢失。

5. 数据库脚本编写技巧与初始化数据准备

课程设计交付的SQL脚本要具备“一键建库”的能力,而不是让老师手动一条条执行。脚本里要有这几部分:

  1. 创建数据库和指定字符集。
  2. 创建所有表结构,包含主键、索引、默认值、注释。
  3. 插入初始化数据,至少包括一个管理员账号、两个学生账号、一个企业账号。
  4. 插入几组演示数据:3-5个职位、若干条投递记录、企业审核记录。

有一个很重要的细节:在CREATE TABLE语句前加DROP TABLE IF EXISTS,这样脚本可以重复执行,老师在检查时反复导入不会报错。

密码字段注意不要存明文。比如管理员账号admin密码统一初始化为123456的BCrypt密文。用BCrypt加密后每次生成的密文都不一样,直接复制现有项目中已经生成的密文放入SQL脚本即可。用户体验上,初始密码就是123456,答辩时直接告诉老师,演示速度会快很多。

插入时间字段建议用NOW(),不要手写具体时间,否则过一段时间再看数据就都是“很久以前”了。

5.1 核心SQL示例:投递记录的联表查询

投递记录列表接口是查询逻辑最复杂的一块,需要联三张表:投递记录表 + 职位表 + 学生信息表。企业端查看某职位收到的投递时:

SELECT dr.id AS delivery_id, dr.status AS delivery_status, dr.delivery_time, si.name AS student_name, si.education AS student_education, si.major AS student_major, si.phone AS student_phone, ji.job_name FROM delivery_record dr LEFT JOIN student_info si ON dr.student_id = si.id LEFT JOIN job_info ji ON dr.job_id = ji.id WHERE dr.job_id = #{jobId} ORDER BY dr.delivery_time DESC

这里用LEFT JOIN而不是INNER JOIN,是为了防止简历不完整的学生记录被过滤掉。学生端查看自己的投递记录时,只要联职位表和企业信息表:

SELECT dr.id AS delivery_id, dr.status AS delivery_status, dr.delivery_time, ji.job_name, ji.salary_min, ji.salary_max, ci.company_name, ci.city FROM delivery_record dr LEFT JOIN job_info ji ON dr.job_id = ji.id LEFT JOIN company_info ci ON ji.company_id = ci.id WHERE dr.student_id = #{studentId} ORDER BY dr.delivery_time DESC

这两条SQL基本就是整个系统查询的核心骨架。课堂上讲三表联查时你可能听着很简单,但真正写业务时你会发现联表方向的选择、关联字段的取舍、是否需要带筛选条件才是关键。

6. 踩坑实录:我在开发与答辩中遇到的问题

6.1 跨域配置导致的登录失败

前端和后端如果跑在不同的端口上(前端8080、后端8081),Axios请求必然遇到跨域问题。我在SpringBoot中配置了CORS:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

前提是springboot版本2.4以上用allowedOriginPatterns代替allowedOrigins,否则allowCredentials(true)和allowedOrigins("*")会冲突报错。这是SpringBoot新版本的已知变更,网上很多老教程还在用旧写法,直接复制就报错。

6.2 JWT过期后前端跳转逻辑

JWT有效期我们设了2小时,如果用户一直开着页面不操作,Token过期后请求返回401。Axios响应拦截器里要统一处理:

service.interceptors.response.use( response => { return response; }, error => { if (error.response.status === 401) { removeToken(); router.push('/login'); } return Promise.reject(error); } );

这里有个体验上的坑:不要等到点击某个功能按钮时才跳登录页,应该做一个轻量的定时器,在Token快过期前主动刷新,或者在401时静默跳转并提示“登录已过期,请重新登录”,不要让用户以为系统崩了。

6.3 文件上传路径泄漏与访问404

开发环境的文件路径和部署环境的文件路径大概率不同。如果在代码里硬编码D:/upload,部署到Linux服务器上就找不到文件。我的建议:在application.yml里配置一个自定义属性:

file: upload-dir: D:/upload

然后在WebMvcConfig中读取配置:

@Value("${file.upload-dir}") private String uploadDir;

部署时只需要修改配置文件,不用改代码。这个习惯在职场上也是基本的部署规范。

6.4 投递记录状态更新的越权操作

这个就是我在前面讲的权限细节。一个典型的场景:企业A发布了一个Java开发岗,投递记录ID是100;企业B登录后,如果能直接调用更新接口把投递记录状态改成“面试邀约”,那就出现越权了。

修复方案的核心是校验归属权:

DeliveryRecord dr = deliveryRecordMapper.selectById(id); JobInfo jobInfo = jobInfoMapper.selectById(dr.getJobId()); if (!jobInfo.getCompanyId().equals(currentCompanyId)) { return Result.error("无权操作该投递记录"); }

答辩时把这个逻辑讲清楚,老师对你的评分会明显高于只会背CRUD的同学。

6.5 前端组件库按需引入与打包体积

Element UI全量引入会导致打包体积偏大,加载速度变慢。答辩演示时如果网络不好,首屏加载可能要卡好几秒。建议开启按需引入:

import { Button, Table, TableColumn, Pagination, Form, FormItem, Input, Select, Option, Dialog, Message, Upload } from 'element-ui'; Vue.use(Button); Vue.use(Table); // ...

不过课程设计阶段,全量引入其实问题也不大,毕竟本地跑Demo。但如果你对性能优化有追求,按需引入是一个很好的加分点,可以写进文档的“项目优化与展望”章节。

7. 部署运行的完整步骤:从环境准备到跑通Demo

7.1 本机环境版本建议

组件版本建议
JDK1.8(稳定版),如果代码里用了新特性就选17
Maven3.6.3及以上
MySQL5.7或8.0
Node.js14.x或16.x(Vue CLI支持较好)
Vue CLI4.x或5.x
IDEIntelliJ IDEA + VS Code

后端JDK 8在SpringBoot 2.7上是完全兼容的,不要图新上SpringBoot 3.x,那需要JDK 17起步,且部分旧教程里的javax包要改成jakarta,排查起来很费时间。课程设计求稳,SpringBoot 2.7.x + JDK 8是黄金组合。

7.2 后端启动步骤

  1. 用IDEA导入后端工程,等待Maven下载依赖。
  2. 修改application.yml中的数据库连接信息(用户名、密码)。
  3. 执行SQL脚本建库、建表、插入初始化数据。
  4. 启动SpringBoot应用,检查控制台输出。
  5. 访问http://localhost:8081/swagger-ui.html查看接口文档。

7.3 前端启动步骤

  1. 用VS Code打开前端工程。
  2. 在终端执行npm install安装依赖。这一步如果网络不好会卡很久,可以把npm源换成国内镜像:
npm config set registry https://registry.npmmirror.com
  1. 修改src/utils/request.js里的baseURL,改成http://localhost:8081。
  2. 执行npm run serve启动开发服务器。
  3. 浏览器访问http://localhost:8080,用初始账号登录测试。

7.4 常见启动失败排查

现象原因解决方案
后端启动报数据库连接失败数据库密码错误、库不存在检查application.yml、重新执行SQL脚本
前端npm install报错Node版本过高/过低切换Node 14或16版本
前端访问接口404baseURL配置错误确认前端请求路径与后端Controller对应
登录接口报跨域CORS配置缺失或版本冲突检查CorsConfig,注意SpringBoot版本对应写法
上传文件访问404映射路径配置错误检查WebMvcConfig中addResourceHandlers

8. 课程设计文档写作:结构、要点与避坑

课程设计文档通常包含:需求分析、总体设计、数据库设计、详细设计、系统测试、总结。这里我只提炼最关键的几点。

需求分析部分,画用例图是必须的。三种角色的用例要分清楚,例如学生用例:注册登录、维护简历、浏览职位、投递简历、查看投递状态、收藏职位。企业用例:注册登录、企业信息维护、职位发布与下线、查看投递、更新投递状态、邀请面试。管理员用例:用户管理、企业审核、职位审核、数据统计。

数据库设计部分,除了ER图,每个表的字段设计最好用表格列出字段名、类型、约束、说明。比如:

字段类型约束说明
idbigint主键,自增主键ID
usernamevarchar(50)唯一,非空登录账号
passwordvarchar(255)非空登录密码(BCrypt加密)
roletinyint非空,默认00学生、1企业、2管理员
statustinyint非空,默认00正常、1禁用
create_timedatetime非空注册时间

测试部分不要只写“系统运行正常”。每个模块至少写一个测试用例:输入、操作步骤、预期输出、实际结果。例如“学生投递职位已投递过”这个场景:第一次投递返回成功,第二次投递返回“已投递过”,这是一个很能体现业务逻辑的测试案例。

文档写作这部分我要多提醒一句:不要从网上直接抄模板的内容,老师每年审几百份文档,看一眼就知道是不是抄的。你按照自己项目的实际开发过程写,技术选型部分写自己的思考过程,让老师看到你确实做了这个项目,比堆字数有用得多。

9. 答辩常见追问与应答思路

答辩环节老师喜欢追问的点,集中在权限控制、状态流转、设计取舍这几个方向。我例举几个高频问题:

问:为什么用户表不区分三张表,而是用role字段?

答:区分角色表和共用一张表各有优劣。共用一张表的优势是登录逻辑简单,只需要查一次数据库,而且权限模型清晰;如果拆成三张表,登录时要先判断走哪张表,逻辑更复杂。考虑到课程设计规模不大,共用一张用户表是合理的。但扩展性上,如果后续每个角色的字段差异很大,拆表会更合适。

问:JWT和Session有什么区别?

答:Session是服务端保存登录状态,客户端只保存SessionId,服务端重启后Session丢失;JWT是客户端保存完整Token,服务端无状态,通过签名验签确认身份。JWT适合前后端分离和分布式部署场景,但Token本身无法撤销,服务端无法主动踢人下线,这是它的局限。

问:为什么会想到用状态机来管理投递流程?

答:投递记录的状态不是随意跳转的,从待查看到已查看、邀约面试、不合适,每一步都有业务含义,不能从“待查看”直接跳到“不合适”,也不能从“邀约面试”回到“待查看”。用状态机来约束,能保证业务数据的准确性,也方便后续扩展——比如增加“已入职”状态。

问:多表联查的SQL,索引怎么设计?

答:比如delivery_record表上,student_id和job_id都是高频查询字段,应该分别建索引。job_info上的company_id也建议建索引,联表查询时能明显加快速度。

问:项目的难点和创新点是什么?

答:难点在于三种角色的权限控制、投递状态的流转管理、文件上传与访问的安全控制。创新点体现在:企业注册后需要管理员审核才能发布职位,职位状态上下线管理,投递状态由企业端推进,形成完整的业务闭环。这些其实不算“创新”,但体现了对业务完整性的思考,老师就会觉得你动了脑子。

每次答辩前,建议自己把项目的流程完整走一遍:注册学生账号、维护简历、浏览职位、投递、企业登录查看投递并更新状态、学生查看状态、管理员审核企业、数据统计。把主流程走顺畅,比背一百页文档都管用。

我在带学生做课设和帮读者复现项目的过程里,最深的一个体会是:课程设计和真实项目之间没有隔着一堵墙,差别只在于你有没有把每个模块的真实业务想清楚。大学生就业招聘系统这个题目选得好,是因为它刚好戳中了“用户-数据-状态-权限”这四件事的交叉点,而这四件事恰恰是后端开发的底层逻辑。

如果你正准备做这个题目,先具体化你的业务场景,再把数据库表建好,然后从登录模块开始往后写。遇到一个模块就把它吃透一个模块,不要图快直接贴copy别人代码。等整个系统跑通、文档写完、自己能在不加提示的情况下说清楚每个表的用途和每个接口的逻辑,这个课设就是你的真实能力证明了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询