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(); } } }