在高校和企业的对接链条里“找实习”这件事的痛点一直很明显学生简历投递渠道分散、企业收简历靠邮箱堆、学院老师难以跟踪学生实习进度。我之前做过几个管理类的全栈项目这一次想试着做一个更垂直的“基于vuenodejs的大学生实习招聘系统”把学生端、企业端和管理端全部打通让实习流程在线上闭环跑起来。这套系统我实测下来整体体验比传统邮件投递的方式顺畅太多而且Vue负责界面交互、Node.js负责业务逻辑的组合对于单人开发或者小组协作来说性价比非常高。这篇文章我会从数据库设计、接口规划、核心功能实现、权限控制到部署上线的完整链路把我踩过的坑和验证过的做法都整理出来适合正在做毕设、或者想入门全栈开发的朋友直接参考。1. 系统整体设计与技术选型思路1.1 为什么选VueNode.js这套组合先聊一下技术选型。当前招聘类系统的主流做法无非两种一种是传统的服务端渲染方案比如Java的Spring Boot搭配Thymeleaf或者PHP的Laravel直接输出页面另一种就是前后端分离方案Vue或React负责所有交互页面后端只提供JSON接口。我最终选择了Vue 2.x Node.js的Express框架。原因有三点。第一Vue在国内开发者中的普及率极高中文文档完善遇到问题基本都能搜到解决方案第二Node.js的JavaScript语言栈让我可以前后端统一用一门语言不需要频繁切换上下文开发效率明显高很多第三实习招聘系统的核心业务是“信息聚合审批流转”并发量不会太高Node.js的事件驱动模型完全能扛住且部署成本比Java低不少。这个系统我拆成了三个角色端学生端、企业端和管理端。整体架构是典型的前后端分离结构前端用Vue Router做路由管理、Vuex做全局状态存储后端根据业务模块划分路由统一返回JSON格式数据。服务器通过CORS配置允许前端跨域请求登录认证采用JWT令牌机制。1.2 功能模块拆解在设计功能之前我先梳理了用户的核心需求和操作路径学生注册登录、完善个人简历、浏览实习职位、投递简历、查看投递状态、收藏职位企业注册登录、发布实习岗位、查看收到的简历、筛选候选人、更新招聘状态管理员审核企业注册信息、审核职位发布内容、管理学生与企业的账号状态、数据统计从操作路径来看学生找实习的核心动作是“搜职位、看详情、投简历、等反馈”企业的核心动作是“发职位、收简历、筛人”管理员的动作集中在“审核和治理”。所以系统的核心表结构是围绕“用户—简历—职位—投递记录”这条主链路来设计的。我画了一下数据流向学生在前端编辑简历提交到后端后端存入数据库企业发布职位后职位数据进入职位表学生点击投递时系统创建一条投递记录同时往企业端的消息列表推送一条新动态企业登录后在“收到的简历”模块里看到学生的主页和简历附件可以进行“通过/拒绝”的操作状态更新后学生端同步看到。这套流程把整个招聘闭环串了起来所有环节都能在线上完成。2. 数据库设计与核心表结构2.1 用户表与角色设计数据库采用的是MySQL 8.0。因为涉及角色权限我没有把用户和角色混在一张表里而是设计了user表加role字段区分。三个角色用数字标识1代表学生2代表企业3代表管理员。这样设计的好处是权限中间层非常轻量不需要单独建role_permission表。用户表核心字段字段名类型说明idint(11)主键自增usernamevarchar(50)登录账号唯一索引passwordvarchar(255)bcrypt加密后的密码roletinyint(1)1学生 / 2企业 / 3管理员emailvarchar(100)邮箱用于找回密码phonevarchar(20)手机号statustinyint(1)账号状态0禁用 / 1正常create_timedatetime注册时间企业在注册时还需要额外填写公司名称、统一社会信用代码、营业执照图片等信息这些单独放在company表里和user表通过user_id字段关联。管理员审核企业账号时就是检查company表中的审核状态字段。2.2 简历表与职位表设计学生简历的存储我纠结了一下最终选择了“主表JSON扩展字段”的混合方案。主表存储基础信息比如姓名、性别、学校、专业、学历、毕业年份、联系电话、个人简介工作经历和教育经历这种数组结构字段直接存在JSON类型的字段里。MySQL 8.0对JSON类型的支持已经很成熟查询时也可以直接使用JSON_EXTRACT函数做筛选比单独建子表加联查要省事很多。职位表的字段相对固定所属企业ID、职位名称、职位类别、招聘人数、薪资范围、工作地点、学历要求、职位描述、任职要求加上发布时间和截止时间。这里值得注意的是薪资范围我拆成了salary_min和salary_max两个整数段方便学生端做价格区间筛选也方便后台做统计。如果用字符串存储“8k-12k”这种文本筛选时就需要字符串转换性能和数据整洁性都会受影响。职位表还加了hot_flag字段用来标记热门职位。后台管理员或者企业端在发布职位时可以手动置顶某些急需岗位。列表页查询时先按hot_flag倒序再按发布时间倒序这样就能让紧急招聘的岗位优先展示。2.3 投递记录与收藏功能的设计细节投递记录表是整个系统的核心业务表字段包括学生ID、职位ID、投递时间、当前状态、企业反馈备注。状态我用了数字表示1已投递、2已查看、3已通过、4已拒绝。为什么不用字符串状态因为数字状态在做统计查询时效率更高而且状态流转的逻辑判断用数字更清晰前端映射成对应文字即可。收藏功能则单独建了一张favorite表字段只有id、student_id、position_id、create_time再加一个联合唯一索引防止重复收藏。小功能表结构一定要精简到极致这是我在之前的项目里被教训过的。为了方便学生投递时快速匹配简历我还在resume表里增加了default_flag字段。一个学生可以维护多份简历但默认只有一份投递时自动带出默认简历。这个体验细节很多人忽略但在实际使用中非常加分。3. 核心功能模块的实现细节3.1 登录注册与JWT身份认证认证这块我用了JWT方案配合bcrypt做密码加密。JWT相比传统的session方案最大的优势就是无状态前端存储Token每次请求在Authorization头里带上后端通过中间件解析校验不需要在服务端维护session非常适合前后端分离的场景。登录接口的处理流程大概是前端调login接口传账号密码后端查user表用bcrypt.compareSync校验密码是否匹配。匹配成功后生成一个包含userId和role的JWT Token设置过期时间为7天。返回值里除了Token还带上用户的基础信息和角色类型这样前端可以决定跳转到哪个控制台页面。为了防止Token泄露带来的风险我在前端封装了axios请求拦截器每次请求自动在header中加入Authorization: Bearer token响应拦截器里统一处理401错误发现Token过期就清空本地存储并跳转登录页。这个机制测试下来可以节省大量重复代码强烈建议照做。3.2 招聘职位的发布与浏览企业发布职位的内容较多包括基础信息和详细描述两部分。表单前端分了两个step展示其实提交时一次性传给后端。后端校验必填项后执行insert操作。这里要注意的一个细节是发布时需要从Token里解析出userId再通过company表查出企业ID职位表里的company_id不应该让前端传否则就有越权的风险。学生端浏览职位列表时我实现了按关键词搜索、按城市筛选、按职位类别筛选、按薪资区间筛选。这里用了MySQL的多条件组合查询。为了防止SQL注入所有筛选条件都走参数化查询严禁拼接字符串。实测在十万级数据量下加了联合索引的组合查询响应依然在100毫秒以内完全满足体验要求。职位详情页展示完整信息之外我还做了一个“推荐相似职位”的模块。实现逻辑很简单根据当前职位的职位类别去查同类别的其他职位排除自身取最新4条。虽然推荐算法很简单但在实际体验中非常提升站内留存。3.3 简历管理与文件上传简历模块我做了两部分可在线编辑的结构化简历以及附件简历上传。结构化简历的好处是方便企业快速筛选附件简历则保留学生的原始排版。文件上传我用的是multer中间件上传目录设在服务器磁盘的uploads文件夹里。这里要特别提醒的是上传文件一定要做类型和大小限制。我只允许pdf、doc、docx、jpg、png这几种类型文件大小限制在5MB以内。无论在哪个项目里不限制用户上传文件类型的系统都是等着被上传木马的。另外上传后的文件名我用时间戳加随机数重新命名保留原始文件的扩展名避免文件名冲突也防止中文文件名乱码。数据库只存相对路径不存完整URL。这样以后迁服务器时只需要把整个uploads目录带走就行。3.4 投递流程与状态流转投递是整个系统的核心操作。我在设计时加了一个比常规系统更严格的校验投递前检查学生是否已填写简历。如果简历表中没有该学生的记录后端直接返回提示“请先完善个人简历”从流程层面引导学生先完善资料提高投递有效性。投递状态机是这套系统里逻辑最复杂的部分。当学生投递时创建一条记录状态为1已投递。企业打开这条投递时状态自动从1变成2已查看。企业操作“通过”则状态变为3“拒绝”则变为4。学生端展示状态进度条时根据数字映射到对应步骤简洁直观。在企业端增加一个简单的筛选查看已投递、未查看、已通过、已拒绝四类。这对每日收到几十份简历的企业来说处理效率比邮箱收简历高太多。我在某公司的试用反馈里听到最多的评价也是这一点。3.5 企业端筛选与后台管理企业筛选简历时系统提供了几个快速筛选维度学历要求、专业关键词、学校层次。这些筛选本质上都是对resume表做多条件查询加上简历与投递记录表的关联。为了让查询效率更高我在投递记录表里冗余了student_name和school_name字段虽然违背了严格的第三范式但在数据库读多写少的场景下反而更实用。管理后台的功能相对简单但很重要。包括企业注册审核、职位上线审核、用户禁用/启用。企业注册时的审核是防控平台内容风险的第一道门槛没有这一步平台上会出现大量垃圾企业账号。职位审核则把关招聘信息的真实性。管理员列表页用分页拉取数据配合简单的关键词搜索即可不需要复杂的统计图表。但我在后台做了三个简单统计卡片注册学生数、注册企业数、当前有效职位数方便管理员快速感知平台规模。4. 前后端联调中的高频问题4.1 跨域配置与axios封装开发环境下前端跑在8080端口后端跑在3000端口跨域问题必然存在。我在后端使用了cors中间件配置允许的来源为本地开发服务地址。注意allowedHeaders里要加上Authorization否则前端传JWT Token时会被浏览器拦截。前端axios的封装我总结了一套通用模板基础URL用环境变量区分开发和生产请求拦截器加Token响应拦截器统一处理业务码1000代表成功其他代表业务异常统一弹出错误消息401专门跳登录页。这套模板放到任何Vue项目中都能直接复用实际联调时可以少踩很多坑。4.2 接口返回格式统一前后端联调时最影响效率的问题就是接口返回格式不统一。我强烈建议后端在项目一开始就约定统一的返回结构。我的约定是{code: 1000, message: success, data: {...}}。不管成功失败都遵循这个结构。前端在响应拦截器里判断code直接拿到data处理业务代码会清晰很多。管理后台的分页接口返回结构中除了当前页数据列表还要返回总数total。前端表格组件需要总数来计算总页数。如果只返数组不返总数分页组件就无法正确工作。这个细节也常常被遗漏。4.3 权限控制的常见漏洞在实现权限控制时最常见的漏洞就是只控制路由不控制接口。有些开发者在前端根据角色隐藏菜单觉得用户就进不去了。但只要有浏览器开发者工具用户可以随意构造请求直接调后端接口拿数据。所以我坚持所有敏感接口都必须做后端鉴权。每个需要登录的接口我都会在路由处理中加入authMiddleware解析Token并挂载到req对象上。管理员的接口额外加adminMiddleware判断req.user.role是否为3。双重中间件叠加的方式可以灵活控制权限层级。比如学生和企业共用的投递接口只需要authMiddleware而只有管理员能访问的用户列表接口必须authMiddleware加adminMiddleware双重验证。5. 安全加固与性能优化实践5.1 密码加密与防暴力破解这是很多初学者会犯的严重错误密码明文存数据库。我之前看到过不少教程里直接insert用户提交的密码毫无加密措施这是极其危险的。一旦数据库泄露用户的所有账号都暴露了。我用的bcrypt哈希密码每次校验时用bcrypt.compareSync比对。虽然bcrypt的计算稍微慢一点但在登录场景下这个开销完全可以接受换来的安全收益是巨大的。登录防暴力破解方面我实现了连续失败5次后锁定账号15分钟的策略。实现逻辑是在内存维护一个失败计数对象记录每个用户名的连续失败次数达到阈值就锁定并记录锁定到期时间。虽然这个方案在服务器重启后会丢失计数但对付脚本扫描已经足够。要正式做的话可以升级为Redis方案但学习项目中内存方案已经够了。5.2 SQL注入与XSS攻击的防御后端SQL注入的防御核心就一条铁律所有查询全部使用参数化查询或预处理语句绝不直接拼接用户输入。代码审查时我会把这条作为第一检查项。XSS防御主要做两件事第一前端提交的内容后端统一做标签转义把script等关键字转成HTML实体第二前端渲染用户提交的内容时用Vue的插值表达式{{}}而不是v-html指令。如果确实需要富文本展示比如职位描述那就建议引入成熟的富文本编辑器组件它内部会做白名单过滤比手动处理更可靠。5.3 列表查询的索引优化数据库超过一定数据量之后索引的作用就体现出来了。这个系统里最重要的索引设计职位表在publish_time、city、category、hot_flag上建了联合索引投递记录表在student_id和position_id上分别建立索引。原因是学生端列表页最常见的查询组合是“城市类别时间排序”企业端投递列表最多的查询是“按学生查投递记录”。有一次我遇到一个查询耗时超过2秒的接口定位发现是投递记录表关联简历表时的N1查询问题。后来通过关联查询一次性取出所需数据把多次循环查询改为一次多表操作响应时间直接降到了50毫秒。如果发现首页越来越慢记得优先检查是否存在N1查询这几乎是列表性能瓶劲的最高发原因。6. 部署上线与运维避坑6.1 服务器环境准备与Node部署部署环境我用的是一台2核4G的云服务器系统是Ubuntu。上线前需要安装Node.js、MySQL、Nginx这几个核心软件。Node.js版本我推荐用长期支持版避免新版本带来的模块兼容问题。MySQL需要初始化数据库并导入建表SQL同时创建专门的数据库用户配置好权限。项目本身部署方式选择的是用PM2做进程守护。PM2可以监听Node进程状态崩溃后自动重启这在生产环境是必须的。启动时通过环境变量区分开发与生产环境比如NODE_ENVproduction后端代码里用process.env.NODE_ENV读取判断。数据库连接池的配置也做了调整生产环境的连接池上限比开发环境调大一些避免多人使用时报连接数不足。6.2 Nginx反向代理配置要点Nginx在前端项目的部署中承担了两个职责托管前端静态文件和反向代理后端接口。前端在Vue项目执行npm run build后生成dist目录把dist目录内容上传到服务器指定路径Nginx配置中将其设置为站点根目录即可。反向代理部分是重点。前端发起的接口请求统一以/api前缀开头Nginx将/api开头的请求转发到本地的3000端口。这样生产环境只有一个80端口暴露后端服务不直接对外既安全又便于维护。前端环境的接口请求地址则统一配置为相对路径/api而不是写死服务器IP或者域名这样切环境时只需在后端配置的地方修改代理规则即可。6.3 线上环境排查与常见故障处理上线后遇到过三个典型的坑。第一个是服务器时间与本地时间不一致导致JWT在校验过期时间时直接判定过期。排查方法是通过日志发现每次登录成功后紧接着就返回401检查服务器时间才发现差了整整8个小时。解决方式是同步服务器时间标准并在JWT校验时统一使用时间戳。第二个坑是静态资源路径问题。Vue打包后的文件默认使用绝对路径上传到子目录时会找不到JS和CSS文件。解决方法是把Vue配置里的publicPath改为./相对路径这样资源就会从当前路径加载任意子目录部署都没问题。第三个坑是数据库连接数耗尽。上线初期出现过数据库连接数达到上限导致部分请求失败。原因是一个查询逻辑中手写了连接而未释放后来规范了连接释放逻辑同时在代码中统一使用连接池管理问题不再出现。还有一个运维教训上线前一定要做好数据库自动备份。我用的是mysqldump加crontab定时任务每天凌晨自动备份到服务器本地并定期下载到本地。数据是系统里最珍贵的资产绝对不能裸奔。7. 踩过的几个关键坑与避坑建议第一个大坑是简历附件上传路径与前端显示路径不一致。开发时使用本地绝对路径部署后路径前缀变化导致图片和附件全部无法显示。后面统一为上传时返回相对路径前端显示时由后端根据配置组装完整URL。这样服务器迁移时只需改后端配置中的一个基础URL变量即可。第二个是富文本内容的样式问题。职位详细描述用富文本编辑器编辑时会带有一堆内置的class样式。前端Vue组件中如果样式作用域隔离会导致这些class无法生效渲染出来的内容排版全乱。解决方法是引入一个全局样式重置区将编辑器生成的默认样式放在非作用域样式中保证富文本内容的正常渲染。第三个是文件上传体积限制没有生效的迷惑时刻。明明nginx配置了client_max_body_size 10m文件稍微大一点还是报413错误。翻了一圈才发现是Node层也默认限制请求体大小。需要同时在后端代码中配置express.json({limit: 10mb})以及对应文件上传中间件的限制参数双重限制都放行才能真正生效。这个我调了很久才彻底解决。第四个是关于Token过期时用户正在填写表单的数据丢失问题。JWT的过期时间一般设置得比较长但还是会存在用户填了很久的信息突然过期的情况。我的处理办法是在401响应后先用前端临时存储把页面数据暂存跳转登录页登录成功后再触发恢复操作。这个体验优化虽然小众但实际给到用户的反馈特别好。8. 实际使用效果与再扩展空间目前这个系统在模拟环境里跑了两个多月注册了大约几百个学生账号和几十个企业账号累计发布了上百个实习岗位。后台统计显示通过系统成功流转的投递数量到了三位数级别状态流转正常无重大bug。企业端反馈最好的是投递列表的筛选功能不用再一封封翻邮件学生端反馈最好的是在线简历投递的便捷性以及微信提醒投递状态变化的通知功能。这套系统基于vuenodejs的设计方案比较通用也很容易继续扩展。如果接着做的话有几个方向可以完善接入WebSocket实时通知投递状态变化时即时推送给学生而不是靠刷新页面查看加入企业评分与学生实习评价体系形成数据沉淀开发小程序端让学生可以随时随地浏览和投递职位毕竟找实习的场景大部分时间发生在手机上。对于想练手全栈的同学来说基于这套核心框架去扩展自己的业务功能是个很好的切入点。我个人在实际开发中的体会是核心业务逻辑一定要用最简单的方案实现技术选型要选自己最熟悉、社区资料最多的方案不要一开始就想设计一个完美无缺的高大上系统。先把主流程跑通把数据库设计和权限控制做扎实剩下的事情都只是叠加功能而已。哪怕后面发现了设计短板只要表和权限这两层底子打得够稳改进成本都非常可控。做全栈项目最重要的是先跑通闭环先有全链路再谈优化。
阅读完成 · 觉得有帮助?