定时器首次归零触发Ajax请求重复扣币问题排查
问题原因分析
看起来你的问题出在多个定时器同时运行,导致首次触发时get_question()被调用了两次,进而让控制器执行了两次扣金币操作。
具体来看你的JavaScript代码:
- 你在
$(document).ready()中调用了match_timer()启动定时器 - 在
countDown函数中,当n===0时,你设置timeout=true,紧接着的if(timeout===true)会立即调用match_timer()重启定时器 - 这里的潜在问题是:如果
match_timer()被意外触发了两次(比如页面加载时DOMContentLoaded事件重复触发,或者其他代码也调用了该函数),就会同时存在两个独立的定时器,它们会在同一时间归零,各自调用一次get_question(),最终导致金币被扣两次。
另外,你的timeout全局变量逻辑也有漏洞:在countDown的同一个执行周期内,设置timeout=true后立即调用match_timer(),虽然重置了timeout=false,但这种同步调用可能让新、旧定时器的逻辑产生冲突,增加重复触发的风险。
修复方案
我们可以通过以下两种方式解决这个问题:
方案1:确保只有一个定时器在运行
将定时器变量改为全局变量,启动新定时器前检查是否已有活跃的定时器,避免重复创建:
var activeTimer = null; // 全局变量存储当前活跃的定时器 var timeout=false; $(document).ready(function () { startMatchTimer(); }) function startMatchTimer() { if (activeTimer !== null) return; // 如果已有定时器,直接返回 var n = 5; activeTimer = setInterval(countDown, 1000); function countDown() { n--; document.getElementById('match-timer').innerHTML=n.toString(); if (n === 0) { clearInterval(activeTimer); activeTimer = null; // 重置定时器状态 get_question(); // 延迟1秒重启定时器,避免同步调用导致的冲突 setTimeout(() => { timeout=false; startMatchTimer(); }, 1000); } } } function get_question() { $("#question").empty(); $.ajax({ url:'http://127.0.0.1:8000/get_another_question/', method: 'POST', data:{ "_token":"{{csrf_token()}}", }, success:function (data) { document.getElementById('question').innerHTML=data['title']; $("#coin_balance").html(data['newCoinBalance']); } }); }
方案2:优化timeout逻辑,避免同步重启定时器
把重启定时器的逻辑从countDown的主流程中分离出来,确保get_question()只在定时器归零的时机被调用一次:
var timeout=false; $(document).ready(function () { match_timer(); }) function match_timer() { var n = 5; var tm = setInterval(countDown, 1000); function countDown() { n--; document.getElementById('match-timer').innerHTML=n.toString(); if (n === 0) { clearInterval(tm); get_question(); // 重置timeout并在1秒后重启定时器,避免同步执行的冲突 setTimeout(() => { timeout=false; match_timer(); }, 1000); timeout=true; } } } // get_question函数保持不变
额外建议
为了避免后端重复扣币的风险,你可以在Laravel控制器中添加幂等性校验,比如扣币前再次检查用户余额,或者使用Laravel内置的方法更安全地更新数据:
public function getAnotherMatchQuestion() { $user = auth()->user(); // 扣币前再次验证余额,避免并发请求导致的重复扣减 if ($user->coin_balance < 1) { return response()->json(['error' => 'Insufficient coins'], 400); } $user->decrement('coin_balance', 1); // 使用Laravel的decrement方法更安全 $question_id=mt_rand(1,15612); $getMatchQuestion=QuizQuestion::find($question_id)->only(['question']); $data=[ 'title' => $getMatchQuestion['question'], 'newCoinBalance' => $user->coin_balance ]; return response()->json($data); }
内容的提问来源于stack exchange,提问作者samm13
相关产品推荐
相关产品推荐

