SpringBoot+Vue 招聘系统实战:解决简历投递高并发重复提交问题(附源码)

最近在维护公司内部小型招聘ATS系统,线上频繁收到测试反馈:用户疯狂点击投递按钮,会生成多条重复投递记录。尤其是校招高峰期,大量用户集中投递岗位,不仅产生脏数据,还会导致数据库压力骤增、接口响应卡顿。

网上很多教程只讲基础CRUD,完全没覆盖招聘系统的核心痛点------投递接口的幂等性与防抖拦截。折腾了大半天,整合了前端防抖+后端幂等校验+Redis锁的完整方案,亲测有效,分享给做同类项目的小伙伴。

一、问题复盘:为什么普通接口扛不住简历投递场景?

先说说原始代码的问题,也是大部分新手写招聘系统的通病:

前端无任何防抖限制,用户连续点击投递按钮,会短时间内触发多次接口请求;后端仅做了简单的参数校验,没有幂等判断。只要请求到达接口,就会直接insert投递记录。

招聘业务有个强规则:同一个用户,同一个岗位,仅允许投递一次。重复投递不仅业务逻辑错误,还会造成HR后台数据冗余,筛选简历时出现大量重复条目,极大影响办公效率。

单纯靠数据库唯一索引可以解决重复数据问题,但会抛出异常,前端体验极差,而且高并发下数据库锁竞争会拖慢整体接口速度,治标不治本。

二、完整解决方案:前后端双层拦截

1. Vue前端:按钮防抖+投递状态锁定

核心思路:点击投递按钮后,立即禁用按钮,请求结束(成功/失败)后再解锁,杜绝人为连续点击。相比定时器防抖,状态锁定更适配表单提交场景,不会出现临界点击漏洞。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| javascript // 核心防抖逻辑 export default { data() { return { submitLoading: false, // 提交加载状态 jobId: '', // 岗位ID userId: '' // 当前登录用户ID } }, methods: { // 简历投递提交 async submitDelivery() { if (this.submitLoading) return this.submitLoading = true try { const res = await this.api.job.delivery({ jobId: this.jobId, userId: this.userId }) if (res.code === 200) { this.message.success('投递成功!') } } catch (err) { this.$message.error(err.msg || '投递失败,请重试') } finally { // 无论成功失败,都解锁按钮 this.submitLoading = false } } } } |

同时在页面加载时,提前调用接口校验用户是否已投递该岗位,直接隐藏投递按钮,从源头避免无效请求,优化用户体验。

2. 后端SpringBoot:Redis幂等锁兜底

前端拦截只能解决普通用户误操作,无法防止恶意刷接口、爬虫批量请求。所以后端必须做幂等兜底,这是企业级项目的必备逻辑。

设计规则:以 用户ID+岗位ID 作为唯一key,设置过期时间(适配招聘投递周期),请求进来先判断key是否存在,存在则直接返回"已投递",不存在则新增投递记录并写入缓存。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Service public class JobDeliveryServiceImpl implements JobDeliveryService { @Autowired private RedisTemplate<String, Object> redisTemplate; // 缓存key前缀 private static final String DELIVERY_KEY = "job:delivery:"; // 缓存过期时间:30天(岗位投递有效期) private static final long EXPIRE_TIME = 30 * 24 * 60 * 60; @Override public Result delivery(Long userId, Long jobId) { // 拼接唯一key String key = DELIVERY_KEY + userId + ":" + jobId; // 幂等判断:已存在则直接返回 if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) { return Result.fail("您已投递过该岗位,无需重复投递"); } // 数据库新增投递记录 JobDelivery delivery = new JobDelivery(); delivery.setUserId(userId); delivery.setJobId(jobId); delivery.setDeliveryTime(LocalDateTime.now()); save(delivery); // 写入缓存,设置过期时间 redisTemplate.opsForValue().set(key, "1", EXPIRE_TIME, TimeUnit.SECONDS); return Result.success("投递成功"); } } |

三、补充优化:高并发场景适配

校招高峰期会出现瞬时大量投递请求,单纯的幂等锁还不够稳定,我额外做了两点优化:

  1. 接口限流:基于Redis实现单用户每秒请求限制,防止恶意刷接口导致系统过载;

  2. 异步记录日志:投递日志、操作记录通过异步线程处理,不阻塞主业务流程,提升接口响应速度。

四、踩坑总结

  1. 招聘系统的重复投递问题,绝对不能只依赖数据库唯一索引,高并发下极易触发数据库异常,影响用户体验;

  2. 前后端双层拦截是最优解:前端优化交互,后端保障数据一致性;

  3. 缓存过期时间要贴合业务场景,不能设置永久有效,避免岗位过期后缓存残留导致无法投递。

这套方案落地后,线上再也没有出现过重复投递数据,接口稳定性提升非常明显。做招聘系统、求职平台的小伙伴可以直接照搬,有问题评论区交流~

相关推荐
会编程的土豆7 天前
12306 春运抢票:数据一致性怎么保证
redis·消息队列·go·高并发
智码看视界11 天前
Day 45 | Sentinel流量治理:从限流到熔断降级再到系统自适应保护
sentinel·高并发·熔断降级·流量控制·微服务治理·热点参数限流
Rain的Java大神实战圈1 个月前
订单超时如何自动取消
消息队列·rabbitmq·高并发·架构设计
Rain的Java大神实战圈2 个月前
高并发下如何防止商品超卖少卖
经验分享·redis·高并发·架构设计·电商·面试经验
SRETalk2 个月前
高并发通知系统设计笔记:如何安全发送百万级/亿级推送?
高并发
睡不醒男孩0308232 个月前
云原生运维实战:高并发架构下的云原生可观测性、韧性降级与自动化干预体系
数据库·kubernetes·高并发·prometheus·devops·sre·缓存调优
星辰_mya2 个月前
限流、漏斗桶和令牌桶的区别
java·开发语言·面试·架构·高并发
东南门吹雪2 个月前
JAVA TCP socket编程框架
java·高并发·socket·tcp·nio
YYYing.2 个月前
【C++大型项目之高性能服务器框架 (一) 】一切物语的开始:日志系统&配置系统篇
服务器·高并发·高性能·c/c++·后端框架