diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php index f3640d511..eb4cb7837 100755 --- a/app/Http/Controllers/IndexController.php +++ b/app/Http/Controllers/IndexController.php @@ -25,7 +25,6 @@ use App\Tasks\ZincSearchSyncTask; use App\Tasks\UnclaimedTaskRemindTask; use Hhxsv5\LaravelS\Swoole\Task\Task; use Laravolt\Avatar\Avatar; -use Swoole\Coroutine; /** diff --git a/app/Models/UserDevice.php b/app/Models/UserDevice.php index 0eb6ecd4e..31f7ed18c 100644 --- a/app/Models/UserDevice.php +++ b/app/Models/UserDevice.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Module\Base; use App\Module\Doo; +use App\Module\Lock; use Cache; use Carbon\Carbon; use DeviceDetector\DeviceDetector; @@ -218,13 +219,8 @@ class UserDevice extends AbstractModel self::record(); return $hash; } - // 没有记录,尝试创建一个(防止升级后所有登录都失效,保证留一个可以保持登录) // todo 后期删除 - return AbstractModel::transaction(function () use ($hash, $userid) { - if (self::whereUserid($userid)->withoutTrashed()->lockForUpdate()->exists()) { - return null; - } - return self::record() ? $hash : null; - }); + // 没有记录 + return null; } /** @@ -234,46 +230,48 @@ class UserDevice extends AbstractModel */ public static function record(string $token = null): ?self { - return AbstractModel::transaction(function () use ($token) { - if (empty($token)) { - $token = Doo::userToken(); - $userid = Doo::userId(); - $expiredAt = Doo::userExpiredAt(); - } else { - $info = Doo::tokenDecode($token); - $userid = $info['userid'] ?? 0; - $expiredAt = $info['expired_at']; - } - - $hash = md5($token); - $row = self::whereHash($hash)->lockForUpdate()->first(); - if (empty($row)) { - // 生成一个新的设备记录 - $row = self::createInstance([ - 'userid' => $userid, - 'hash' => $hash, - ]); - if (!$row->save()) { - return null; - } - // 删除多余的设备记录 - $currentDeviceCount = self::whereUserid($userid)->count(); - if ($currentDeviceCount > self::$deviceLimit) { - $rows = self::whereUserid($userid)->orderBy('id')->take($currentDeviceCount - self::$deviceLimit)->get(); - foreach ($rows as $row) { - UserDevice::forget($row); + if (empty($token)) { + $token = Doo::userToken(); + $userid = Doo::userId(); + $expiredAt = Doo::userExpiredAt(); + } else { + $info = Doo::tokenDecode($token); + $userid = $info['userid'] ?? 0; + $expiredAt = $info['expired_at']; + } + $hash = md5($token); + // + return Lock::withLock("userDeviceRecord:{$hash}", function () use ($expiredAt, $userid, $hash, $token) { + return AbstractModel::transaction(function () use ($expiredAt, $userid, $hash, $token) { + $row = self::whereHash($hash)->first(); + if (empty($row)) { + // 生成一个新的设备记录 + $row = self::createInstance([ + 'userid' => $userid, + 'hash' => $hash, + ]); + if (!$row->save()) { + return null; + } + // 删除多余的设备记录 + $currentDeviceCount = self::whereUserid($userid)->count(); + if ($currentDeviceCount > self::$deviceLimit) { + $rows = self::whereUserid($userid)->orderBy('id')->take($currentDeviceCount - self::$deviceLimit)->get(); + foreach ($rows as $row) { + UserDevice::forget($row); + } } } - } - $row->expired_at = $expiredAt; - if (Request::hasHeader('version')) { - $deviceInfo = array_merge(Base::json2array($row->detail), self::getDeviceInfo($_SERVER['HTTP_USER_AGENT'] ?? '')); - $row->detail = Base::array2json($deviceInfo); - } - $row->save(); + $row->expired_at = $expiredAt; + if (Request::hasHeader('version')) { + $deviceInfo = array_merge(Base::json2array($row->detail), self::getDeviceInfo($_SERVER['HTTP_USER_AGENT'] ?? '')); + $row->detail = Base::array2json($deviceInfo); + } + $row->save(); - Cache::put(self::ck($hash), $row->userid, now()->addHour()); - return $row; + Cache::put(self::ck($hash), $row->userid, now()->addHour()); + return $row; + }); }); } diff --git a/app/Module/Lock.php b/app/Module/Lock.php new file mode 100644 index 000000000..8951b7adf --- /dev/null +++ b/app/Module/Lock.php @@ -0,0 +1,52 @@ + 0) { + $end = microtime(true) + ($waitTimeout / 1000); + while (microtime(true) < $end) { + if (Redis::set($lockKey, $lockValue, 'PX', $ttl, 'NX')) { + $acquired = true; + break; + } + usleep(100000); // 休眠100ms后重试 + } + } else { + $acquired = Redis::set($lockKey, $lockValue, 'PX', $ttl, 'NX'); + } + + if (!$acquired) { + throw new Exception("Failed to acquire lock for key: {$key}"); + } + + try { + // 执行闭包 + return $closure(); + } finally { + // 安全释放锁(仅当锁值未变时删除) + Redis::eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", 1, $lockKey, $lockValue); + } + } +}