前阵子接了个全栈项目,要做一套学生就业信息管理系统,技术栈指定了 Node.js + Vue。这类系统在高校里属于典型的信息化管理需求——就业指导中心每天要面对大量学生简历、企业招聘信息、投递记录和统计报表,光靠 Excel 和邮件来回倒腾,效率低还容易丢数据,所以需要一个统一平台把这些数据全部管理起来。系统本身的业务逻辑不算复杂,但把前端框架、后端接口、数据库设计、部署运维这套全流程走下来,对技术积累是全面的检验。这篇文章把我从设计思路、环境搭建、数据库设计、前后端联调,到最终部署上线踩过的坑全部整理出来,给正在做类似毕业设计或全栈项目的朋友一份可以直接参考的实操经验。
先说一下这个系统最后做出来是什么样。整个项目分为三个角色端:学生端可以维护个人简历、浏览招聘信息、投递职位、查看投递进度;企业端可以注册入驻、发布职位、查看收到的简历;管理员端负责审核企业资质、管理用户和职位、发布公告,以及查看统计报表。前后端采用完全分离的架构,后端是 Node.js + Express + MySQL,前端是 Vue 3 + Vue Router + Element Plus,通过 RESTful API 通信。看完这篇文章,你不仅能复现这套系统,还能理解为什么我要在每一步做那样的技术取舍。
1. 项目定位与整体设计思路
1.1 学生就业信息管理系统到底在解决什么问题
先别急着谈技术,这个系统的核心是业务问题。很多学校现在的就业管理方式非常原始:就业信息发在班级QQ群里,简历通过邮箱收,投递状态靠学生自己问,统计就业率时再让辅导员一个个催。这种模式下,学生容易错过招聘节点,企业也愁找不到匹配的人,学校更没法学数据层面掌握就业情况。
所以我做这个系统时,第一件事不是写代码,而是把角色和流程梳理清楚。系统里有三类用户,每类用户关心的东西完全不一样。学生关心的是"有什么岗位适合我"、"投了没被查看";企业关心的是"发布的职位有没有人投"、"来的简历跟岗位匹配不匹配";管理员关心的是"企业入驻是否合规"、"整体就业情况如何"。三套诉求如果混在一个页面里,体验一定崩。所以要拆成三个端口,每个端口一套独立的页面和接口权限。
这也顺带决定了整个项目的边界:需要用户注册登录、需要角色区分、需要职位发布与检索、需要简历投递流程、需要简单的统计看板。把这些功能范围圈定好,后面设计和写代码才不会跑偏。很多新手做系统爱堆功能,但真实的项目要做减法。这个系统里我特意砍掉了在线面试、薪酬对比、论坛社区这些"锦上添花"的功能,因为就业信息管理系统现阶段最核心的诉求就是把信息流跑通,把状态记录清楚,其他都可以等二期。
1.2 为什么选 Node.js + Vue 这个技术组合
关于技术选型,我挨个说说理由。后端选 Node.js 主要看中三点:第一,JavaScript 一门语言通吃前后端,你写业务逻辑、处理 JSON 数据时的心智负担会小很多,不用在多种语言之间来回切换;第二,Express 框架非常轻,中间件机制直观,适合快速搭建 RESTful API;第三,异步非阻塞 I/O 在应对高并发读请求上有天然优势,像职位列表、投递状态查询这类高频读接口,Node.js 的并发表现完全够用。
前端选 Vue 的理由更简单。Vue 的响应式数据和组件化开发模式非常适合这种"页面多、交互逻辑相似"的后台管理系统。比如学生信息页、企业信息页、职位卡片列表,拆成组件后到处复用,开发效率提升非常明显。Vue 3 的组合式 API 对逻辑组织也更友好,一个业务功能相关的响应式变量和方法放一起,维护起来不会乱。搭配 Element Plus 组件库,表格、表单、弹窗、分页这些后台系统最常用的组件开箱即用,省下了大量调 UI 的时间。
数据存储这块选了 MySQL,原因是系统里大多是结构化的关系数据——用户、职位、投递记录天然适合用表来存,而且 MySQL 的生态成熟,部署和备份方案都很稳定。有人会问为什么不用 MongoDB,对这类强关系、强事务的场景,比如一笔投递操作需要同时更新投递表和职位状态,关系型数据库的事务保障会让你少操很多心。
2. 开发环境搭建与工具链配置
2.1 Node.js 安装与环境变量配置的坑
这个环节看着简单,其实翻车率极高,尤其是对刚接触 Node.js 的同学。我建议直接去官网下载 LTS 版本,不要追最新版,LTS 版本意味着长时间维护、稳定可靠,和项目里依赖包兼容性也最好。安装包默认会帮你勾选 "Add to PATH",推荐保留这个选项,否则装完还要手动配环境变量。
不过这里有个细节要重点提醒:尽量不要把 Node.js 安装到带中文或空格的路径下。我看到很多人装到了D:\Program Files\这种路径,后续在使用很多命令行工具时容易出幺蛾子。我之前就遇到过因为路径空格导致某个 Node 的原生模块编译失败,排查了半天才定位到是路径问题。如果确实已经装错了,最简单的办法就是卸载重装到D:\nodejs这种纯英文路径,千万不要将就。
安装完成后,打开终端输入node -v和npm -v,能正常输出版本号就说明成功了。如果提示"无法识别 node 命令",大概率是 PATH 没配好,系统找不到可执行文件。手动配置环境变量的做法是:在系统变量里新建NODE_HOME,指向 Node.js 安装目录,然后把%NODE_HOME%追加到 PATH 变量里。改完记得重新打开终端窗口,环境变量不会在已打开的窗口里自动生效。
顺带多说一句,我推荐安装一个 nvm(Node Version Manager)。做多个项目时经常遇到版本冲突——老项目需要 Node 12,新项目要 Node 20,用 nvm 切换就在一条命令之间。这是当年切到全栈开发后最后悔没有早点养成的工具习惯。
2.2 npm 脚本执行策略问题:一个高频踩坑点
第一次用 Windows PowerShell 执行npm install的人,大概率会撞上这个经典报错:npm : 无法加载文件 ...\npm.ps1,因为在此系统上禁止运行脚本。这个问题本质不是 npm 坏了,而是 PowerShell 的脚本执行策略默认是 Restricted,禁止运行 .ps1 脚本文件。
解决方法很简单,在 PowerShell 里执行下面这一行,把执行策略改成当前用户级别允许运行本地脚本:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser这里解释一下RemoteSigned的含义:本地创建的脚本可以运行,从网上下载的脚本必须有可信签名才能运行。这个策略对日常开发来说足够安全,不明来源的脚本不会在你不知情的情况下被执行。执行后它会弹出确认提示,输入Y回车。
如果公司电脑有组策略限制,改执行策略被拒绝,那还有一个更省事的方案:直接用 Git Bash 或者 CMD 执行 npm 命令,这两个终端不受 PowerShell 执行策略影响。我个人的习惯是 Windows 下统一用 Git Bash,既能跑 npm 命令,又能用 Linux 风格的命令语法,舒适度提升不止一个档次。
另外,npm 安装依赖慢的问题也值得操心一下。国内直连 npm 官方源经常慢到怀疑人生,一条提速方案是把默认 registry 切换到国内镜像源:
npm config set registry https://registry.npmmirror.com切换之后用npm config get registry检查一下生效没有。以后装包速度会快非常多。
2.3 前端项目脚手架与目录结构
Vue 项目我推荐用 Vite 来初始化,相比 Vue CLI,Vite 的冷启动速度是真的快,开发体验好不少。用下面的命令就能快速创建一个 Vue 3 项目:
npm create vite@latest student-employment-frontend -- --template vue安装依赖并启动开发服务器:
cd student-employment-frontend npm install npm run devVite 的默认脚手架生成的项目结构比较精简,我会手动做一层目录规划,把业务模块拆分清楚,因为后面项目一大,目录乱就全乱了。我的习惯是在src下建这些目录:
api目录统一放接口请求封装,每个模块一个文件,比如user.js、job.js、application.jsviews目录按角色拆页面文件夹,比如student/、company/、admin/router目录放路由配置文件store目录放 Pinia 状态管理components目录放通用组件,比如JobCard.vue、PageTable.vue
这样规划的一个直接好处是:后期加功能时,代码应该放哪里不用想,跟着目录名走就行。很多同学习惯把业务逻辑全堆在页面组件里,一个文件上千行,后面几乎没法维护。组件拆分的原则是"同一代码块在两个及以上页面复用就提取",比如职位列表卡片、用户表格这种,就必须做成通用组件。
3. 数据库设计与核心模块拆分
3.1 功能模块划分与角色权限
数据库设计之前,先把系统的模块边界画清楚。我的设计是四大模块:
第一个是用户与权限模块,包含用户注册、登录、角色校验。角色我用了最简单的方式——在用户表里加一个role字段,值有student、company、admin三种。网上很多教程推荐用独立的角色表和权限表,可以做细粒度权限控制,但对这个项目而言有点过度设计。三个角色的权限差异可以用中间件轻松控制,单独建几张关联表反而是给自己找麻烦。
第二个是学生信息模块。学生登录后要维护扩展信息,比如真实姓名、专业、学历、毕业年份、联系方式、个人简历等。这些字段和用户表是 1 对 1 的关系,单独拆一张学生信息表,和用户表通过user_id关联。为什么不直接存在用户表?因为学生字段较多且只对学生角色有意义,拆开可以让用户表保持精简。
第三个是企业与职位模块。企业账号注册后需要填写企业名称、行业类型、规模、简介、地址,管理员审核通过后才能发布职位。职位表存企业 ID、职位名称、招聘类型(校招/社招)、薪资范围、学历要求、工作地点、职位描述、发布时间、状态等字段。
第四个是投递与统计模块。学生投递职位会生成一条投递记录,记录里包含学生 ID、职位 ID、投递状态(待查看/已查看/已通知/不合适)、投递时间。统计模块就可以基于这张表做各种聚合查询,比如某企业的职位投递量、某学校的就业率统计。
角色与模块的对应关系如下表:
| 角色 | 可用模块 | 关键权限说明 |
|---|---|---|
| 学生 | 个人信息、简历、职位检索、投递 | 投递操作只能对学生角色开放 |
| 企业 | 企业资料、职位管理、投递列表 | 只能操作本企业的数据和职位 |
| 管理员 | 用户管理、企业审核、公告、统计 | 所有数据可看可管 |
3.2 表结构设计要点
接下来是重头戏,数据库表结构设计。这一步如果设计不好,后面写接口时会发现自己不断在拼接 SQL 和跟表关系打架。我的核心表是六张,分别说明每张表的用途和关键字段。
用户表users字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint 主键自增 | 用户唯一标识 |
| username | varchar(50) 唯一 | 登录名 |
| password | varchar(100) | 加密后的密码 |
| role | varchar(20) | student/company/admin |
| status | tinyint | 1 可用,0 禁用 |
| created_at | datetime | 注册时间 |
密码字段必须强调一点:不要明文存储。我用了bcryptjs对密码做哈希,登录时比对哈希值。这样做的好处是即使数据库泄露,用户的密码原文也不会直接暴露。这个习惯在任何项目中都应该坚持。
学生信息表student_profiles通过user_id关联users表,字段包含真实姓名、性别、专业、学院、学历、毕业年份、手机号、邮箱、个人简介、简历文件路径。简历我会以文件 URL 的形式存路径,而不是直接存文件本身,文件上传到服务器指定目录,数据库只留相对路径。
企业信息表company_profiles包含企业名称、统一社会信用代码、所属行业、企业规模、官网地址、企业简介、所在城市、审核状态。审核状态这个字段很关键,企业注册后默认为待审核,管理员后台审核通过后才能发布职位。
职位表jobs字段比较多,包括职位标题、所属企业 ID、招聘类型、薪资下限、薪资上限、学历要求、工作地点、职位描述、截止时间、状态。薪资用两个整数而不是一个字符串,是为了后续能按薪资区间筛选,如果存成 "10-20k" 这种字符串,排序和筛选都会很难受。
投递记录表applications有四个核心字段:学生 ID、职位 ID、状态、投递时间。为了防重复投递,我在学生 ID 和职位 ID 上建立了唯一索引,保证同一个人不能对同一职位投两次。这个设计是从业务角度出发的,因为重复投递对企业和学生都没有意义。
所有表都加上了created_at和updated_at字段,这是基本操作,定时任务或统计时非常有用。
4. 后端接口开发与核心逻辑实现
4.1 Express 服务搭建与路由组织
后端我用 Express 搭建,项目结构比前端还讲究"模块化"三个字。一个典型的 Express 项目目录长这样:
server/ ├── app.js ├── routes/ │ ├── userRoutes.js │ ├── jobRoutes.js │ ├── applicationRoutes.js │ └── statsRoutes.js ├── controllers/ ├── middleware/ │ ├── auth.js │ └── errorHandler.js ├── config/ └── models/express项目初始化很简单,核心代码就一行:
const express = require('express'); const app = express(); app.use(express.json());但真正写项目时一定要把路由拆到独立文件夹里,而不是全部堆在app.js。比如用户相关的接口统一挂载在/api/user前缀下,职位相关的挂在/api/job下,代码结构一眼就懂。路由文件里只做路由匹配和参数校验,具体业务逻辑放到控制器里,这种 MVC 分层思想在 Node 后端同样适用。
连接 MySQL 我用了mysql2这个包,因为它支持 Promise,配合async/await写起来非常舒服。和数据库相关的公共逻辑封装成一个db.js模块,导出连接池,其他业务模块直接调用。这里有个关键点:一定要用连接池而不是每次查询都新建连接,否则并发稍微上来,数据库瞬间就会被拖垮。
const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: 'localhost', user: 'root', password: '...', database: 'student_employment', waitForConnections: true, connectionLimit: 10, }); module.exports = pool;关于 API 响应格式,我建议在全项目统一一个包裹格式:{ code: 0, message: 'success', data: ... }。前端拿到响应后先判断code是否为 0,再做业务处理。统一格式带来的好处是明显且长期的——前端拦截器可以统一处理错误提示,不用每个接口单独写一套判断逻辑。
4.2 登录认证与权限控制:JWT 的落地
登录认证部分我选了 JWT(JSON Web Token)方案,而非传统的 Session。原因在于前后端分离架构下,JWT 天然适合:后端不存储会话状态,token 由客户端保存并在每次请求时携带,服务器无状态化,部署和横向扩展会简单很多。
流程是:用户登录成功后,后端用jsonwebtoken库签发一个 token,里面包含用户 ID 和角色信息,设置过期时间比如 24 小时。前端将 token 存在 localStorage 里,然后在 axios 请求拦截器里把 token 加到请求头的Authorization字段上,格式是Bearer <token>。
// 登录成功签发 token const token = jwt.sign( { userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' } );后端写一个authMiddleware来统一校验 token:
const authMiddleware = (req, res, next) => { const authHeader = req.headers.authorization || ''; const token = authHeader.split(' ')[1]; if (!token) return res.status(401).json({ code: 401, message: '未登录' }); try { const payload = jwt.verify(token, process.env.JWT_SECRET); req.user = payload; next(); } catch (e) { return res.status(401).json({ code: 401, message: '登录已过期' }); } };在需要角色控制的接口上,再包一层requireRole中间件,判断当前用户角色是否在允许列表里。比如职位发布接口只有企业角色能调,管理员接口只有 admin 能访问。中间件组合式的写法让权限控制非常灵活,新增接口时按需叠加中间件就行。
这里提醒一点:JWT 无状态是有代价的,无法在服务端主动吊销某个 token。如果遇到安全事件需要踢人下线,只能等 token 自然过期。对这个系统来说,页面操作时如果返回 401,前端会自动清掉本地 token 并跳转到登录页,这样流程上基本够用。
4.3 就业信息推荐的核心实现
系统里有一个让我花时间最多的功能,是"针对学生的职位推荐"。我不打算引入什么复杂的机器学习模型,因为数据量完全撑不起来,更容易解释也实用的方案是基于关键词匹配的评分推荐。
思路很简单:每个职位都有标题和描述,每个学生档案里有专业、个人简介。从这些字段中提取关键词,计算职位内容和学生信息之间的匹配度打分。比如一个计算机专业的学生看到职位描述里出现 "Java"、"Vue"、"Node.js" 这些词,命中数量多,分数就高,推荐排前。
具体做法是用一个简单的分词匹配器:把职位描述拆成小写单词数组,把学生专业和简介里的关键词也拆成数组,然后统计共同词的个数,再结合职位发布时间做时间衰减。最终评分公式简化成score = 匹配词数量 * 0.6 + 学历匹配加分 * 0.4。学历匹配加分是职位要求的学历不高于学生学历时加 10 分。
这个功能虽然逻辑简单,但实际效果已经足够好,学生登录后发现推荐栏里的职位确实比全部列表更精准,这也说明一个问题——很多"看起来很智能"的功能,用朴素但合理的规则实现,性价比是最高的。
5. Vue 前端开发与前后端联调
5.1 Vue Router 路由配置与登录守卫
前端路由我用 Vue Router 4,配置方式上有个很实用的设计:路由拆成静态两步走。第一步是公共路由,比如登录页、注册页,所有人可访问;第二步是带认证的业务路由,比如学生中心、企业管理台、管理员列表页,这些页面必须在登录状态下才能访问。
登录守卫是通过路由的beforeEach钩子实现的:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }); } else { next(); } });to.meta.requiresAuth是在路由配置里给每个页面标记的元信息。这样设计的好处是,哪些页面需要登录一看路由表就清楚,新加页面时只要在 meta 里声明requiresAuth: true就能自动加上守卫。
深入一点说,真正严谨的权限控制一定要前后端同时做。前端守卫只能防君子——用户打开页面组件之前就被拦截,体验好;但后端接口的权限校验才是最终防线,不然懂点技术的人完全可以绕过前端直接调用接口拿数据。这也是我上面设计requireRole中间件的原因,两边配合才叫完整的权限闭环。
5.2 Axios 二次封装与跨域处理
前后端分离开发时,跨域问题是最容易劝退新手的坎。开发环境下我用 Vite 的代理解决跨域,配置在vite.config.js里:
server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, } } }这样前端请求/api/user/login时,Vite 开发服务器会自动把请求代理到后端http://localhost:3000,浏览器的同源限制就被绕过了。生产环境这个问题由 Nginx 反向代理来解决,可以在 Nginx 配置里把/api前缀的请求转发到后端服务。所以跨域问题的根本解法是:不要在前端代码里写死后端地址,而是用相对路径 + 代理层转发。
请求封装这里要单独说。我在src/api/request.js里创建了一个 axios 实例,做两层拦截器:请求拦截器自动带上 token,响应拦截器统一处理错误码和 401。前端每个页面里就不会出现裸的axios.get了,都走封装好的request.get('/job/list'),代码可读性和可维护性都好不少。
Element Plus 的负责展示这一层省了我非常多事。比如后台管理页的用户表格,用el-table加几个属性就是分页、排序全都有,搜索表单用el-form快速搭,弹窗确认用el-message-box,整体界面做出来整洁又专业。前端界面这块,日常开发真的不需要从零写 CSS,用成熟组件库是省力且正确的选择。
5.3 页面模块开发与组件复用
页面开发走中,我的经验是"先搭公共布局,再填业务页面"。后台系统的布局套路基本固定:左边侧边栏菜单、顶部用户信息、中间内容区。这部分我直接封装成一个Layout.vue公共组件,所有业务页面都渲染在内容区<router-view />里,菜单项根据角色动态渲染——学生看到的是"职位大厅、我的投递",企业看到的是"职位管理、简历收件箱",管理员看到的是"用户管理、企业审核、数据统计"。
组件复用上最有价值的一个案例是职位卡片组件JobCard.vue。它在三个地方都用到了:学生的职位列表中、企业的已发布职位里、管理员的职位管理页。设计时我把"职位数据"和"操作按钮"作为插槽暴露出去,展示逻辑保持统一,操作逻辑由使用方定制。这样职位卡片的样式改了,三处页面同步更新,不用处处去调。
状态管理用了 Pinia。主要存两个全局状态:当前登录用户的基本信息和侧边栏折叠状态。有些同学会把所有接口返回的数据都放进 store,这其实没必要。只有被多个组件共享、且需要保持一致性的数据才值得进全局 store,其他数据放在各自页面组件里管理就好了,过度使用全局状态反而让数据流变得神秘而不便。
6. 部署上线与常见问题排查实录
6.1 前端打包与 Nginx 部署
开发完成后,部署阶段又是一轮新的挑战。前端需要先打包出静态文件,命令是npm run build,产物在dist目录。把这dist目录里的文件放到服务器上的 Nginx 静态目录里,比如/var/www/student-employment-frontend/。
Nginx 配置里除了静态文件托管,还必须要配置/api路径的反向代理,把接口请求转发到 Node.js 服务的监听端口:
server { listen 80; server_name your-domain.com; root /var/www/student-employment-frontend; index index.html; location /api { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }这一行try_files $uri $uri/ /index.html;是必不可少的。SPA 单页应用的路由是前端动态管理的,直接访问https://xxx.com/student/profile时,服务器上并没有这个物理文件,如果不把请求回退到index.html,刷新就白屏、报 404。这个坑当年愣是让我排查了一个小时,几乎每个部署 Vue 项目的人都会撞上,记住了就不会难受。
6.2 后端服务部署与进程守护
Node.js 后端部署时,不能直接在服务器上挂一个node app.js就完事。首先这家伙是前台进程,SSH 窗口一关进程就没了。其次进程一旦崩溃不会自动重启,服务就停了。我用了 PM2 来做进程守护:
npm install -g pm2 pm2 start app.js --name student-employment-server pm2 savePM2 稳定提供了开机自启、日志收集、内存监控等能力,对单机部署运维来说完全够用。日常看日志用pm2 logs student-employment-server,看状态用pm2 status,服务崩了 PM2 几秒内就能自动拉起。
环境变量这块也要注意,比如数据库密码和 JWT_SECRET,不要把真实值写死在代码里。我在服务器上创建了一个.env文件存放这些敏感配置,代码里用process.env.DB_PASSWORD读取。这样做的好处是,即使代码上传到 GitHub 被公开,敏感信息也不会泄露。
6.3 高频问题排查速查表
整个项目从开发到上线跑下来,我记录了一堆踩过的坑,挑出现频率最高的做成一个排查速查表,希望能帮你少走些弯路:
| 错误现象 | 根本原因 | 解决方案 |
|---|---|---|
| 启动后端口被占用 | 前一个进程没关干净 | lsof -i:3000找到进程 PID 后杀掉,或者换端口启动 |
| 前端请求接口返回 404 | Nginx 没配置/api代理,或后端没启动 | 检查 Nginxlocation /api配置,用curl http://127.0.0.1:3000/api/...先本地测试 |
| 请求返回 502 Bad Gateway | 后端服务崩了或挂了 | pm2 status查看进程状态,pm2 logs看具体报错 |
| 登录后刷新页面跳回登录页 | 刷新后没有重新校验 token,或 token 过期被拦截 | 前端启动时读取本地 token,调用/api/user/info拉取用户信息并恢复登录态 |
| 中文乱码 | 数据库连接配置少了字符集参数 | 建连接池时添加charset: 'utf8mb4' |
| 数据库连接数耗尽 | 每次查询都新建连接没复用 | 使用连接池mysql2.createPool,避免逐条 query 新建连接 |
| npm 安装依赖后启动报缺少模块 | package.json 里依赖版本不一致 | 删除node_modules和package-lock.json后重新npm install |
| 接口数据返回后表格不渲染 | 数据结构不匹配或响应格式不统一 | 对照后端响应包裹结构{ code, message, data }和前端字段名 |
这上面每一个问题我都实际遇到过,而且说实话,它们不是写项目能力的问题,是新人对工具链和运行机制不熟悉导致的。把这套排查表走一遍,很多莫名其妙的 Bug 都能稳住心神找到原因。
6.4 项目体验优化与安全加固要点
系统功能跑通之后,不要急着收工。还有一批"不做不行"的体验与安全层面的优化点,这里单独列一下:
安全层面,密码存储必须用bcryptjs加盐哈希,登录接口要做好基础的频率限制,防止被暴力尝试。所有删除类的敏感操作必须有角色校验,不能让普通学生直接调接口删职位。文件上传做一个类型白名单校验,只允许上传 JPG、PNG、PDF,并且重命名为随机文件名,避免用户可执行文件上传导致的安全问题。
体验层面,用户操作后必须要有反馈,不管是提交成功还是失败都要有明确的提示。比如投递职位时,如果用户没有完善简历,系统应先提示"请先完善简历"而不是直接投递,投递成功后再跳转到投递记录页。职位列表使用分页而不是一次性全量返回,页面上加上搜索关键词和筛选条件,方便学生快速找到目标职位。
性能层面,职位列表接口避免直接查全表再排序,充分利用 MySQL 的索引。推荐列表的评分计算不要每次请求都重新跑一遍,可以加一层简单缓存,比如职位数据一小时更新一次。这些优化对这个体量的系统不是必须的,但养成了习惯,做大型项目时受益无穷。
最后再分享一个和这个项目相关的小心得。如果你是要把这套系统写成毕业设计论文,最好把思路从"我完成了系统"调整为"我解决了什么问题"。论文的章节结构可以跟着"背景与意义、需求分析、系统设计、系统实现、系统测试"这个脉络走,但重点放在需求分析里的用例梳理、系统设计里的模块与数据库设计、系统实现里的核心代码片段解释。代码和细节不是靠粘贴大段源码凑数,而是靠清晰的逻辑和自己的语言讲明白为什么这样做。做完系统再回头看论文,会发现写起来顺手很多——因为它是真实从实践里生长出来的,每一个章节都有实际对应物的。
这个系统做完之后我的体会是,全栈项目的核心不是某一门技术学得多深,而是把每层技术串起来的能力。你开发环境配置明白了、路由守卫会写了、Express 中间件玩转了、Nginx 配置懂了,独立完成一个小型系统不成问题。下一步可以在这个系统上继续扩展点个人感兴趣的功能,比如简历自动解析、职位推荐算法优化、或是把统计报表做成可视化图表,每一步扩展都会让你对整个技术栈的理解更深一层。