From 63c6e12acabd0312e7de3a1a5e9306589190f8f9 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Sat, 19 Apr 2025 09:03:33 +0800 Subject: [PATCH] no message --- app/Http/Controllers/Api/UsersController.php | 81 +++ app/Models/AbstractModel.php | 11 +- app/Models/User.php | 61 +- app/Models/UserDevice.php | 263 ++++++++ composer.json | 1 + composer.lock | 634 ++++-------------- ...04_19_084905_create_user_devices_table.php | 39 ++ 7 files changed, 555 insertions(+), 535 deletions(-) create mode 100644 app/Models/UserDevice.php create mode 100644 database/migrations/2025_04_19_084905_create_user_devices_table.php diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index d2da1f448..b99f1325e 100755 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -19,6 +19,7 @@ use App\Models\UserBot; use App\Models\WebSocket; use App\Models\UmengAlias; use App\Models\UserDelete; +use App\Models\UserDevice; use App\Models\UserTransfer; use App\Models\AbstractModel; use App\Models\UserCheckinFace; @@ -266,6 +267,11 @@ class UsersController extends AbstractController return Base::retSuccess('请求成功', $captcha); } + public function logout() + { + $user = User::auth(); + } + /** * @api {get} api/users/reg/needinvite 06. 是否需要邀请码 * @@ -2425,4 +2431,79 @@ class UsersController extends AbstractController // return Base::retSuccess('success', $data); } + + /** + * @api {get} api/users/device/count 38. 获取设备数量 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup users + * @apiName device__count + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function device__count() + { + $user = User::auth(); + // + return Base::retSuccess('success', [ + 'count' => UserDevice::whereUserid($user->userid)->count() + ]); + } + + /** + * @api {get} api/users/device/list 39. 获取设备列表 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup users + * @apiName device__list + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function device__list() + { + $user = User::auth(); + // + $list = UserDevice::whereUserid($user->userid)->orderByDesc('id')->take(100)->get(); + // + return Base::retSuccess('success', [ + 'list' => $list + ]); + } + + /** + * @api {get} api/users/device/logout 40. 登出设备(删除设备) + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup users + * @apiName device__logout + * + * @apiParam {Number} id 设备id + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function device__logout() + { + $user = User::auth(); + // + $id = intval(Request::input('id')); + if (empty($id)) { + return Base::retError('参数错误'); + } + $userDevice = UserDevice::whereUserid($user->userid)->whereId($id)->first(); + if (empty($userDevice)) { + return Base::retError('设备不存在或已被删除'); + } + UserDevice::forget($userDevice->id); + // + return Base::retSuccess('删除成功'); + } } diff --git a/app/Models/AbstractModel.php b/app/Models/AbstractModel.php index 82f626217..d75e1353d 100644 --- a/app/Models/AbstractModel.php +++ b/app/Models/AbstractModel.php @@ -220,13 +220,16 @@ class AbstractModel extends Model $row = static::where($where)->first(); if (empty($row)) { $row = new static; - if ($update instanceof \Closure) { - $update = $update(); - } if ($insert instanceof \Closure) { $insert = $insert(); } - $array = array_merge($where, $insert ?: $update); + if (empty($insert)) { + if ($update instanceof \Closure) { + $update = $update(); + } + $insert = $update; + } + $array = array_merge($where, $insert); if (isset($array[$row->primaryKey])) { unset($array[$row->primaryKey]); } diff --git a/app/Models/User.php b/app/Models/User.php index bf21664e5..8b004ebdd 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -445,6 +445,7 @@ class User extends AbstractModel $user = self::authInfo(); if (!$user) { if (Base::token()) { + UserDevice::forget(); throw new ApiException('身份已失效,请重新登录', [], -1); } else { throw new ApiException('请登录后继续...', [], -1); @@ -466,31 +467,46 @@ class User extends AbstractModel private static function authInfo() { if (RequestContext::has('auth')) { + // 缓存 return RequestContext::get('auth'); } - if (Doo::userId() > 0 - && !Doo::userExpired() - && $user = self::whereUserid(Doo::userId())->whereEmail(Doo::userEmail())->whereEncrypt(Doo::userEncrypt())->first()) { - $upArray = []; - if (Base::getIp() && $user->line_ip != Base::getIp()) { - $upArray['line_ip'] = Base::getIp(); - } - if (Carbon::parse($user->line_at)->addSeconds(30)->lt(Carbon::now())) { - $upArray['line_at'] = Carbon::now(); - } - $headerLanguage = RequestContext::get('header_language'); - if (empty($user->lang) || $headerLanguage) { - if (Doo::checkLanguage($headerLanguage) && $user->lang != $headerLanguage) { - $upArray['lang'] = $headerLanguage; - } - } - if ($upArray) { - $user->updateInstance($upArray); - $user->save(); - } - return RequestContext::save('auth', $user); + if (Doo::userId() <= 0) { + // 没有登录 + return RequestContext::save('auth', false); } - return RequestContext::save('auth', false); + if (Doo::userExpired()) { + // 登录过期 + return RequestContext::save('auth', false); + } + if (!UserDevice::check()) { + // token 不存在 + return RequestContext::save('auth', false); + } + $user = self::whereUserid(Doo::userId())->whereEmail(Doo::userEmail())->whereEncrypt(Doo::userEncrypt())->first(); + if (!$user) { + // 登录信息不匹配 + return RequestContext::save('auth', false); + } + + // 更新登录信息 + $upArray = []; + if (Base::getIp() && $user->line_ip != Base::getIp()) { + $upArray['line_ip'] = Base::getIp(); + } + if (Carbon::parse($user->line_at)->addSeconds(30)->lt(Carbon::now())) { + $upArray['line_at'] = Carbon::now(); + } + $headerLanguage = RequestContext::get('header_language'); + if (empty($user->lang) || $headerLanguage) { + if (Doo::checkLanguage($headerLanguage) && $user->lang != $headerLanguage) { + $upArray['lang'] = $headerLanguage; + } + } + if ($upArray) { + $user->updateInstance($upArray); + $user->save(); + } + return RequestContext::save('auth', $user); } /** @@ -514,6 +530,7 @@ class User extends AbstractModel } else { $token = Doo::userToken(); } + UserDevice::record($token); unset($userinfo->encrypt); unset($userinfo->password); return $userinfo->token = $token; diff --git a/app/Models/UserDevice.php b/app/Models/UserDevice.php new file mode 100644 index 000000000..a3ae27015 --- /dev/null +++ b/app/Models/UserDevice.php @@ -0,0 +1,263 @@ +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(); + } + } +} diff --git a/composer.json b/composer.json index c82da0797..396557f96 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "league/html-to-markdown": "^5.1", "maatwebsite/excel": "^3.1.31", "madnest/madzipper": "^v1.1.0", + "matomo/device-detector": "^6.4", "mews/captcha": "^3.2.6", "orangehill/iseed": "^3.0.1", "overtrue/pinyin": "^4.0", diff --git a/composer.lock b/composer.lock index bf73ba809..3c0e1787a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ade0fa5c3619ee85dd7fca4cf68afbb2", + "content-hash": "c0fa92e60d19e0c371dc4d62c6555058", "packages": [ { "name": "asm89/stack-cors", @@ -861,122 +861,6 @@ ], "time": "2020-12-29T14:50:06+00:00" }, - { - "name": "elastic/transport", - "version": "v8.10.0", - "source": { - "type": "git", - "url": "https://github.com/elastic/elastic-transport-php.git", - "reference": "8be37d679637545e50b1cea9f8ee903888783021" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/elastic/elastic-transport-php/zipball/8be37d679637545e50b1cea9f8ee903888783021", - "reference": "8be37d679637545e50b1cea9f8ee903888783021", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0", - "open-telemetry/api": "^1.0", - "php": "^7.4 || ^8.0", - "php-http/discovery": "^1.14", - "php-http/httplug": "^2.3", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0 || ^2.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "nyholm/psr7": "^1.5", - "open-telemetry/sdk": "^1.0", - "php-http/mock-client": "^1.5", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^9.5", - "symfony/http-client": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Elastic\\Transport\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "HTTP transport PHP library for Elastic products", - "keywords": [ - "PSR_17", - "elastic", - "http", - "psr-18", - "psr-7", - "transport" - ], - "support": { - "issues": "https://github.com/elastic/elastic-transport-php/issues", - "source": "https://github.com/elastic/elastic-transport-php/tree/v8.10.0" - }, - "time": "2024-08-14T08:55:07+00:00" - }, - { - "name": "elasticsearch/elasticsearch", - "version": "v8.17.0", - "source": { - "type": "git", - "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "6cd0fe6a95fdb7198a2795624927b094813b3d8b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/6cd0fe6a95fdb7198a2795624927b094813b3d8b", - "reference": "6cd0fe6a95fdb7198a2795624927b094813b3d8b", - "shasum": "" - }, - "require": { - "elastic/transport": "^8.10", - "guzzlehttp/guzzle": "^7.0", - "php": "^7.4 || ^8.0", - "psr/http-client": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "ext-yaml": "*", - "ext-zip": "*", - "mockery/mockery": "^1.5", - "nyholm/psr7": "^1.5", - "php-http/message-factory": "^1.0", - "php-http/mock-client": "^1.5", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^9.5", - "psr/http-factory": "^1.0", - "symfony/finder": "~4.0", - "symfony/http-client": "^5.0|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Elastic\\Elasticsearch\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHP Client for Elasticsearch", - "keywords": [ - "client", - "elastic", - "elasticsearch", - "search" - ], - "support": { - "issues": "https://github.com/elastic/elasticsearch-php/issues", - "source": "https://github.com/elastic/elasticsearch-php/tree/v8.17.0" - }, - "time": "2024-12-18T11:00:27+00:00" - }, { "name": "ezyang/htmlpurifier", "version": "v4.18.0", @@ -3064,6 +2948,76 @@ }, "time": "2022-12-02T22:17:43+00:00" }, + { + "name": "matomo/device-detector", + "version": "6.4.5", + "source": { + "type": "git", + "url": "https://github.com/matomo-org/device-detector.git", + "reference": "270bbc41f80994e80805ac377b67324eba53c412" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/270bbc41f80994e80805ac377b67324eba53c412", + "reference": "270bbc41f80994e80805ac377b67324eba53c412", + "shasum": "" + }, + "require": { + "mustangostang/spyc": "*", + "php": "^7.2|^8.0" + }, + "replace": { + "piwik/device-detector": "self.version" + }, + "require-dev": { + "matthiasmullie/scrapbook": "^1.4.7", + "mayflower/mo4-coding-standard": "^v9.0.0", + "phpstan/phpstan": "^1.10.44", + "phpunit/phpunit": "^8.5.8", + "psr/cache": "^1.0.1", + "psr/simple-cache": "^1.0.1", + "slevomat/coding-standard": "<8.16.0", + "symfony/yaml": "^5.1.7" + }, + "suggest": { + "doctrine/cache": "Can directly be used for caching purpose", + "ext-yaml": "Necessary for using the Pecl YAML parser" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeviceDetector\\": "" + }, + "exclude-from-classmap": [ + "Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The Matomo Team", + "email": "hello@matomo.org", + "homepage": "https://matomo.org/team/" + } + ], + "description": "The Universal Device Detection library, that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, media players, mobile apps, feed readers, libraries, etc), operating systems, devices, brands and models.", + "homepage": "https://matomo.org", + "keywords": [ + "devicedetection", + "parser", + "useragent" + ], + "support": { + "forum": "https://forum.matomo.org/", + "issues": "https://github.com/matomo-org/device-detector/issues", + "source": "https://github.com/matomo-org/matomo", + "wiki": "https://dev.matomo.org/" + }, + "time": "2025-02-26T17:37:32+00:00" + }, { "name": "mews/captcha", "version": "3.3.0", @@ -3239,6 +3193,60 @@ ], "time": "2024-11-12T12:43:37+00:00" }, + { + "name": "mustangostang/spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "https://github.com/mustangostang/spyc.git", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "support": { + "issues": "https://github.com/mustangostang/spyc/issues", + "source": "https://github.com/mustangostang/spyc/tree/0.6.3" + }, + "time": "2019-09-10T13:16:29+00:00" + }, { "name": "myclabs/php-enum", "version": "1.8.5", @@ -3615,134 +3623,6 @@ }, "time": "2024-12-30T11:07:19+00:00" }, - { - "name": "open-telemetry/api", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/api.git", - "reference": "87de95d926f46262885d0d390060c095af13e2e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/87de95d926f46262885d0d390060c095af13e2e5", - "reference": "87de95d926f46262885d0d390060c095af13e2e5", - "shasum": "" - }, - "require": { - "open-telemetry/context": "^1.0", - "php": "^7.4 || ^8.0", - "psr/log": "^1.1|^2.0|^3.0", - "symfony/polyfill-php80": "^1.26", - "symfony/polyfill-php81": "^1.26", - "symfony/polyfill-php82": "^1.26" - }, - "conflict": { - "open-telemetry/sdk": "<=1.0.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - } - }, - "autoload": { - "files": [ - "Trace/functions.php" - ], - "psr-4": { - "OpenTelemetry\\API\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "API for OpenTelemetry PHP.", - "keywords": [ - "Metrics", - "api", - "apm", - "logging", - "opentelemetry", - "otel", - "tracing" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2024-02-06T01:32:25+00:00" - }, - { - "name": "open-telemetry/context", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/context.git", - "reference": "e9d254a7c89885e63fd2fde54e31e81aaaf52b7c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/e9d254a7c89885e63fd2fde54e31e81aaaf52b7c", - "reference": "e9d254a7c89885e63fd2fde54e31e81aaaf52b7c", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "symfony/polyfill-php80": "^1.26", - "symfony/polyfill-php81": "^1.26", - "symfony/polyfill-php82": "^1.26" - }, - "suggest": { - "ext-ffi": "To allow context switching in Fibers" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - } - }, - "autoload": { - "files": [ - "fiber/initialize_fiber_handler.php" - ], - "psr-4": { - "OpenTelemetry\\Context\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "Context implementation for OpenTelemetry PHP.", - "keywords": [ - "Context", - "opentelemetry", - "otel" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2024-01-13T05:50:44+00:00" - }, { "name": "opis/closure", "version": "3.6.3", @@ -4043,194 +3923,6 @@ }, "time": "2014-06-05T11:42:24+00:00" }, - { - "name": "php-http/discovery", - "version": "1.20.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/discovery.git", - "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", - "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0|^2.0", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "nyholm/psr7": "<1.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "*", - "psr/http-factory-implementation": "*", - "psr/http-message-implementation": "*" - }, - "require-dev": { - "composer/composer": "^1.0.2|^2.0", - "graham-campbell/phpspec-skip-example-extension": "^5.0", - "php-http/httplug": "^1.0 || ^2.0", - "php-http/message-factory": "^1.0", - "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", - "sebastian/comparator": "^3.0.5 || ^4.0.8", - "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" - }, - "type": "composer-plugin", - "extra": { - "class": "Http\\Discovery\\Composer\\Plugin", - "plugin-optional": true - }, - "autoload": { - "psr-4": { - "Http\\Discovery\\": "src/" - }, - "exclude-from-classmap": [ - "src/Composer/Plugin.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", - "homepage": "http://php-http.org", - "keywords": [ - "adapter", - "client", - "discovery", - "factory", - "http", - "message", - "psr17", - "psr7" - ], - "support": { - "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.20.0" - }, - "time": "2024-10-02T11:20:13+00:00" - }, - { - "name": "php-http/httplug", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/php-http/httplug.git", - "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", - "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "php-http/promise": "^1.1", - "psr/http-client": "^1.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", - "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eric GELOEN", - "email": "geloen.eric@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "HTTPlug, the HTTP client abstraction for PHP", - "homepage": "http://httplug.io", - "keywords": [ - "client", - "http" - ], - "support": { - "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.4.1" - }, - "time": "2024-09-23T11:39:58+00:00" - }, - { - "name": "php-http/promise", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/php-http/promise.git", - "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", - "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", - "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Http\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joel Wurtz", - "email": "joel.wurtz@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Promise used for asynchronous HTTP requests", - "homepage": "http://httplug.io", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.3.1" - }, - "time": "2024-03-15T13:55:21+00:00" - }, { "name": "phpoffice/common", "version": "1.0.5", @@ -7258,82 +6950,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php82", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "5d2ed36f7734637dacc025f179698031951b1692" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", - "reference": "5d2ed36f7734637dacc025f179698031951b1692", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php82\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/process", "version": "v5.4.47", diff --git a/database/migrations/2025_04_19_084905_create_user_devices_table.php b/database/migrations/2025_04_19_084905_create_user_devices_table.php new file mode 100644 index 000000000..16caa7f8d --- /dev/null +++ b/database/migrations/2025_04_19_084905_create_user_devices_table.php @@ -0,0 +1,39 @@ +bigIncrements('id'); + $table->bigInteger('userid')->index()->nullable()->default(0)->comment('会员ID'); + $table->string('hash')->index()->nullable()->default('')->comment('TOKEN MD5'); + $table->longText('detail')->nullable()->comment('详细信息'); + $table->timestamp('expired_at')->nullable()->comment('过期时间'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_devices'); + } +}