diff --git a/app/Http/Controllers/FishingController.php b/app/Http/Controllers/FishingController.php index 15fdeb9..8e30994 100644 --- a/app/Http/Controllers/FishingController.php +++ b/app/Http/Controllers/FishingController.php @@ -92,8 +92,13 @@ class FishingController extends Controller $waitTime = rand($waitMin, $waitMax); $token = Str::random(32); $tokenKey = "fishing:token:{$user->id}"; - // token 有效期 = 等待时间 + 10秒点击窗口 + 5秒缓冲 - Redis::setex($tokenKey, $waitTime + 15, $token); + // token 有效期 = 等待时间 + 8秒点击窗口 + 5秒缓冲 + // 同时把 cast 时间戳和 wait_time 一起存入,供 reel 做服务端时间校验 + Redis::setex($tokenKey, $waitTime + 13, json_encode([ + 'token' => $token, + 'cast_at' => time(), + 'wait_time' => $waitTime, + ])); // 5. 生成随机浮漂坐标(百分比,避开边缘) $bobberX = rand(15, 85); // 左右 15%~85% @@ -130,15 +135,33 @@ class FishingController extends Controller return response()->json(['status' => 'error', 'message' => '请先登录'], 401); } - // 1. 验证 token(防止脚本绕过浮漂直接收竿) + // 1. 验证 token + 服务端时间校验(防止前端篡改 wait_time 跳过等待) $tokenKey = "fishing:token:{$user->id}"; - $storedToken = Redis::get($tokenKey); + $storedJson = Redis::get($tokenKey); $clientToken = $request->input('token', ''); - if (! $storedToken || $storedToken !== $clientToken) { + if (! $storedJson) { return response()->json([ 'status' => 'error', - 'message' => '鱼儿跑了!浮漂已超时或令牌无效,请重新抛竿。', + 'message' => '鱼儿跑了!浮漂已超时,请重新抛竿。', + ], 422); + } + + $stored = json_decode($storedJson, true); + // 校验 token 一致性 + if (($stored['token'] ?? '') !== $clientToken) { + return response()->json([ + 'status' => 'error', + 'message' => '令牌无效,请重新抛竿。', + ], 422); + } + // 校验服务端时间:距抛竿必须已过 wait_time 秒(允许 ±1s 误差) + $elapsed = time() - (int) ($stored['cast_at'] ?? 0); + $required = (int) ($stored['wait_time'] ?? 0); + if ($elapsed < $required - 1) { + return response()->json([ + 'status' => 'error', + 'message' => '鱼还没上钩,别急!', ], 422); }