mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-27 05:52:54 +00:00
perf: 优化设备登录
This commit is contained in:
parent
4710479b46
commit
bbe071545d
@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
52
app/Module/Lock.php
Normal file
52
app/Module/Lock.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class Lock
|
||||
{
|
||||
/**
|
||||
* 使用Redis分布式锁执行闭包
|
||||
* @param string $key 锁的key
|
||||
* @param Closure $closure 要执行的闭包函数
|
||||
* @param int $ttl 锁的过期时间(毫秒),默认10000(10秒)
|
||||
* @param int $waitTimeout 等待锁的超时时间(毫秒),0表示不等待,默认10000(10秒)
|
||||
* @return mixed 闭包函数的返回值
|
||||
* @throws Exception 如果获取锁失败或闭包执行异常
|
||||
*/
|
||||
public static function withLock(string $key, Closure $closure, int $ttl = 10000, int $waitTimeout = 10000)
|
||||
{
|
||||
$lockKey = "lock:{$key}";
|
||||
$lockValue = uniqid('', true); // 生成唯一值,用于安全释放锁
|
||||
|
||||
// 尝试获取锁,如果waitTimeout为0则直接返回false,否则等待指定时间
|
||||
$acquired = false;
|
||||
if ($waitTimeout > 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user