You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

定时器首次归零触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 21:09:10