mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 02:12:53 +00:00
264 lines
8.8 KiB
PHP
264 lines
8.8 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use App\Module\Base;
|
||
use App\Module\Doo;
|
||
use Cache;
|
||
use Carbon\Carbon;
|
||
use DeviceDetector\DeviceDetector;
|
||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||
|
||
/**
|
||
* App\Models\UserDevice
|
||
*
|
||
* @property int $id
|
||
* @property int|null $userid 会员ID
|
||
* @property string|null $hash TOKEN MD5
|
||
* @property string|null $detail 详细信息
|
||
* @property string|null $expired_at 过期时间
|
||
* @property \Illuminate\Support\Carbon|null $created_at
|
||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice newModelQuery()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice newQuery()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice onlyTrashed()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice query()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereCreatedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereDeletedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereDetail($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereExpiredAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereHash($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereId($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereUpdatedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice whereUserid($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice withTrashed()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|UserDevice withoutTrashed()
|
||
* @mixin \Eloquent
|
||
*/
|
||
class UserDevice extends AbstractModel
|
||
{
|
||
use SoftDeletes;
|
||
|
||
protected $table = 'user_devices';
|
||
|
||
protected $appends = [
|
||
'is_current',
|
||
];
|
||
|
||
public function getDetailAttribute($value)
|
||
{
|
||
if (is_array($value)) {
|
||
return $value;
|
||
}
|
||
return Base::json2array($value);
|
||
}
|
||
|
||
public function getIsCurrentAttribute(): int
|
||
{
|
||
return $this->hash === md5(Doo::userToken()) ? 1 : 0;
|
||
}
|
||
|
||
/** ****************************************************************************** */
|
||
/** ****************************************************************************** */
|
||
/** ****************************************************************************** */
|
||
|
||
/**
|
||
* 缓存key
|
||
* @param string $hash
|
||
* @return string
|
||
*/
|
||
private static function ck(string $hash): string
|
||
{
|
||
return "user_devices:{$hash}";
|
||
}
|
||
|
||
/**
|
||
* 解析 UA 获取设备信息
|
||
* @param string $ua
|
||
* @return array
|
||
*/
|
||
private static function getDeviceInfo(string $ua): array
|
||
{
|
||
$result = [
|
||
'ip' => Base::getIp(),
|
||
'type' => '电脑',
|
||
'os' => 'Unknown',
|
||
'browser' => 'Unknown',
|
||
'version' => '',
|
||
|
||
'app_type' => '', // 客户端类型
|
||
'app_version' => '', // 客户端版本
|
||
];
|
||
|
||
if (empty($ua)) {
|
||
return $result;
|
||
}
|
||
|
||
// 使用 Device-Detector 解析 UA
|
||
$dd = new DeviceDetector($ua);
|
||
|
||
// 解析 UA 字符串
|
||
$dd->parse();
|
||
|
||
// 获取客户端信息(浏览器)
|
||
$clientInfo = $dd->getClient();
|
||
if (!empty($clientInfo)) {
|
||
$result['browser'] = $clientInfo['name'] ?? 'Unknown';
|
||
$result['version'] = $clientInfo['version'] ?? '';
|
||
}
|
||
|
||
// 获取操作系统信息
|
||
$osInfo = $dd->getOs();
|
||
if (!empty($osInfo)) {
|
||
$result['os'] = trim(($osInfo['name'] ?? '') . ' ' . ($osInfo['version'] ?? ''));
|
||
if (empty($result['os'])) {
|
||
$result['os'] = 'Unknown';
|
||
}
|
||
}
|
||
|
||
if (preg_match("/android_kuaifan_eeui/i", $ua)) {
|
||
// Android 客户端
|
||
$result['app_type'] = 'Android';
|
||
$result['app_version'] = self::getAfterVersion($ua, 'kuaifan_eeui/');
|
||
} elseif (preg_match("/ios_kuaifan_eeui/i", $ua)) {
|
||
// iOS 客户端
|
||
$result['app_type'] = 'iOS';
|
||
$result['app_version'] = self::getAfterVersion($ua, 'kuaifan_eeui/');
|
||
} elseif (preg_match("/dootask/i", $ua)) {
|
||
// DooTask 客户端
|
||
$result['app_type'] = $osInfo['name'];
|
||
$result['app_version'] = self::getAfterVersion($ua, 'dootask/');
|
||
} else {
|
||
// 其他客户端
|
||
$result['app_type'] = 'Web';
|
||
$result['app_version'] = Base::getClientVersion();
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 从 ua 的 find 之后的内容获取版本号
|
||
* @param string $ua
|
||
* @param string $find
|
||
* @return string
|
||
*/
|
||
private static function getAfterVersion(string $ua, string $find): string
|
||
{
|
||
$findPattern = preg_quote($find, '/');
|
||
if (preg_match("/{$findPattern}(.*?)(?:\s|$)/i", $ua, $matches)) {
|
||
$appInfo = $matches[1];
|
||
|
||
// 从内容中提取版本号(寻找符合x.x.x格式的部分)
|
||
if (preg_match("/(\d+\.\d+(?:\.\d+)*)/", $appInfo, $versionMatches)) {
|
||
return $versionMatches[1];
|
||
}
|
||
}
|
||
return '';
|
||
}
|
||
|
||
/** ****************************************************************************** */
|
||
/** ****************************************************************************** */
|
||
/** ****************************************************************************** */
|
||
|
||
/**
|
||
* 检查用户是否存在
|
||
* @return bool
|
||
*/
|
||
public static function check(): bool
|
||
{
|
||
$token = Doo::userToken();
|
||
$userid = Doo::userId();
|
||
|
||
$hash = md5($token);
|
||
if (Cache::has(self::ck($hash))) {
|
||
return true;
|
||
}
|
||
|
||
$row = self::whereHash($hash)->first();
|
||
if ($row) {
|
||
// 判断是否过期
|
||
if (Carbon::parse($row->expired_at)->isPast()) {
|
||
Cache::forget(self::ck($hash));
|
||
$row->delete();
|
||
return false;
|
||
}
|
||
// 更新缓存
|
||
self::record();
|
||
return true;
|
||
}
|
||
// 没有记录,尝试创建一个(防止升级后所有登录都失效,保证留一个可以保持登录) // todo 后期删除
|
||
return AbstractModel::transaction(function () use ($userid) {
|
||
if (self::whereUserid($userid)->withoutTrashed()->lockForUpdate()->exists()) {
|
||
return false;
|
||
}
|
||
return (bool)self::record();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 记录设备(添加、更新)
|
||
* @param string|null $token
|
||
* @return self|null
|
||
*/
|
||
public static function record(string $token = null): ?self
|
||
{
|
||
if (empty($token)) {
|
||
$token = Doo::userToken();
|
||
$userid = Doo::userId();
|
||
$expiredAt = Doo::userExpiredAt() ?: null;
|
||
} else {
|
||
$info = Doo::tokenDecode($token);
|
||
$userid = $info['userid'] ?? 0;
|
||
$expiredAt = $info['expired_at'] ?? null;
|
||
}
|
||
|
||
$hash = md5($token);
|
||
$row = self::updateInsert([
|
||
'userid' => $userid,
|
||
'hash' => $hash,
|
||
], [
|
||
'detail' => Base::array2json(self::getDeviceInfo($_SERVER['HTTP_USER_AGENT'] ?? '')),
|
||
'expired_at' => $expiredAt,
|
||
]);
|
||
if ($row) {
|
||
Cache::put(self::ck($hash), $row->userid, now()->addHour());
|
||
return $row;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 忘记设备(删除)
|
||
* @param string|int|null $token
|
||
* - null 表示当前登录的设备
|
||
* - string 表示指定的 token
|
||
* - int 表示指定的数据ID
|
||
* @return void
|
||
*/
|
||
public static function forget(string|int $token = null): void
|
||
{
|
||
if ($token === null) {
|
||
$token = Doo::userToken();
|
||
}
|
||
if (Base::isNumber($token)) {
|
||
$row = self::find(intval($token));
|
||
if ($row) {
|
||
Cache::forget(self::ck($row->hash));
|
||
$row->delete();
|
||
}
|
||
} elseif ($token) {
|
||
$hash = md5($token);
|
||
Cache::forget(self::ck($hash));
|
||
self::whereHash($hash)->delete();
|
||
}
|
||
}
|
||
}
|