From ba8715a3f98d1986e9169dbb8cd5008fcc85cc45 Mon Sep 17 00:00:00 2001 From: xiaomlove <1939737565@qq.com> Date: Tue, 14 Oct 2025 14:54:44 +0700 Subject: [PATCH] Refactor IP History --- README-EN.md | 2 +- README.md | 2 +- app/Console/Kernel.php | 3 +- .../Resources/System/IpLogs/IpLogResource.php | 139 ++++++++++++++++++ .../System/IpLogs/Pages/ManageIpLogs.php | 20 +++ app/Http/Kernel.php | 6 +- app/Http/Middleware/Admin.php | 30 ---- app/Http/Middleware/BootNexus.php | 1 + app/Http/Middleware/LogUserIp.php | 26 ++++ app/Http/Middleware/Permission.php | 30 ---- app/Http/Middleware/Platform.php | 29 ---- app/Jobs/CalculateUserSeedBonus.php | 4 +- app/Jobs/SaveIpLogCacheToDB.php | 31 ++++ app/Models/IpLog.php | 40 +++++ app/Models/NexusModel.php | 8 + app/Models/Setting.php | 2 +- app/Models/TrackerUrl.php | 2 +- app/Providers/RouteServiceProvider.php | 4 +- app/Repositories/CleanupRepository.php | 2 +- app/Repositories/IpLogRepository.php | 87 +++++++++++ .../RequireSeedTorrentRepository.php | 2 +- app/Repositories/TorrentRepository.php | 2 - app/Utils/ApiQueryBuilder.php | 8 +- ...add_uri_and_count_field_to_iplog_table.php | 30 ++++ include/functions.php | 30 ++-- include/functions_announce.php | 8 +- nexus/Install/Install.php | 2 +- public/announce.php | 1 + public/download.php | 9 +- public/scrape.php | 27 ++++ public/settings.php | 2 +- resources/lang/en/ip-log.php | 12 ++ resources/lang/zh_CN/ip-log.php | 12 ++ resources/lang/zh_TW/ip-log.php | 12 ++ 34 files changed, 494 insertions(+), 131 deletions(-) create mode 100644 app/Filament/Resources/System/IpLogs/IpLogResource.php create mode 100644 app/Filament/Resources/System/IpLogs/Pages/ManageIpLogs.php delete mode 100644 app/Http/Middleware/Admin.php create mode 100644 app/Http/Middleware/LogUserIp.php delete mode 100644 app/Http/Middleware/Permission.php delete mode 100644 app/Http/Middleware/Platform.php create mode 100644 app/Jobs/SaveIpLogCacheToDB.php create mode 100644 app/Models/IpLog.php create mode 100644 app/Repositories/IpLogRepository.php create mode 100644 database/migrations/2025_10_12_052151_add_uri_and_count_field_to_iplog_table.php create mode 100644 resources/lang/en/ip-log.php create mode 100644 resources/lang/zh_CN/ip-log.php create mode 100644 resources/lang/zh_TW/ip-log.php diff --git a/README-EN.md b/README-EN.md index e9a38687..03282a5b 100644 --- a/README-EN.md +++ b/README-EN.md @@ -42,7 +42,7 @@ Welcome to participate in internationalization work, click [here](https://github ## System Requirements - PHP: 8.2|8.3|8.4, must have extensions: bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3 - Mysql: 5.7 latest version or above -- Redis:2.6.12 or above +- Redis:4.0.0 or above - Others: supervisor, rsync ## Quick Start diff --git a/README.md b/README.md index 61a5211c..4d6e8335 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ ## 系统要求 - PHP: 8.2|8.3|8.4,必须扩展:bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3 - Mysql: 5.7 最新版或以上版本 -- Redis:2.6.12 或以上版本 +- Redis:4.0.0 或以上版本 - 其他:supervisor, rsync ## 快速开始 diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 1f2d1ab2..277ad9e6 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -6,6 +6,7 @@ use App\Jobs\CheckCleanup; use App\Jobs\CheckQueueFailedJobs; use App\Jobs\MaintainPluginState; use App\Jobs\ManagePlugin; +use App\Jobs\SaveIpLogCacheToDB; use App\Jobs\UpdateIsSeedBoxFromUserRecordsCache; use App\Utils\ThirdPartyJob; use Carbon\Carbon; @@ -48,10 +49,10 @@ class Kernel extends ConsoleKernel $schedule->command('meilisearch:import')->weeklyOn(1, "03:00"); $schedule->command('torrent:load_pieces_hash')->dailyAt("01:00"); $schedule->job(new CheckQueueFailedJobs())->everySixHours(); -// $schedule->job(new ThirdPartyJob())->everyMinute(); $schedule->job(new MaintainPluginState())->everyMinute(); $schedule->job(new UpdateIsSeedBoxFromUserRecordsCache())->everySixHours(); $schedule->job(new CheckCleanup())->everyFifteenMinutes(); + $schedule->job(new SaveIpLogCacheToDB())->hourly(); } diff --git a/app/Filament/Resources/System/IpLogs/IpLogResource.php b/app/Filament/Resources/System/IpLogs/IpLogResource.php new file mode 100644 index 00000000..0f807b0c --- /dev/null +++ b/app/Filament/Resources/System/IpLogs/IpLogResource.php @@ -0,0 +1,139 @@ +components([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('userid') + ->label('UID') + , + TextColumn::make('usernameForAdmin') + ->label(__('label.username')) + , + TextColumn::make('ip') + ->label('IP') + , + TextColumn::make('ipLocation') + ->label(__('ip-log.ip_location')) + , + TextColumn::make('uri') + ->label(__('ip-log.uri')) + , + TextColumn::make('count') + ->label(__('ip-log.count')) + , + TextColumn::make('access') + ->label(__('ip-log.access')) + ->tooltip(__('ip-log.access_tooltip')) + , + ]) + ->defaultSort('id', 'desc') + ->filters([ + Filter::make('uid') + ->schema([ + TextInput::make('uid')->label('UID'), + ]) + ->query(function (Builder $query, array $data) { + return $query + ->when( + $data['uid'], + fn (Builder $query, $value): Builder => $query->where('userid', $value), + ); + }), + Filter::make('ip') + ->schema([ + TextInput::make('ip')->label('IP'), + ]) + ->query(function (Builder $query, array $data) { + return $query + ->when( + $data['ip'], + fn (Builder $query, $value): Builder => $query->where('ip', $value), + ); + }), + Filter::make('access_begin') + ->schema([ + DateTimePicker::make('access_begin')->label(__('ip-log.access_begin')), + ]) + ->query(function (Builder $query, array $data) { + return $query + ->when( + $data['access_begin'], + fn (Builder $query, $value): Builder => $query->where('access', '>=', $value), + ); + }), + Filter::make('access_end') + ->schema([ + DateTimePicker::make('access_end')->label(__('ip-log.access_end')), + ]) + ->query(function (Builder $query, array $data) { + return $query + ->when( + $data['access_end'], + fn (Builder $query, $value): Builder => $query->where('access', '<=', $value), + ); + }), + ]) + ->recordActions([ +// ViewAction::make(), +// EditAction::make(), +// DeleteAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ +// DeleteBulkAction::make(), + ]), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => ManageIpLogs::route('/'), + ]; + } +} diff --git a/app/Filament/Resources/System/IpLogs/Pages/ManageIpLogs.php b/app/Filament/Resources/System/IpLogs/Pages/ManageIpLogs.php new file mode 100644 index 00000000..f96a9de5 --- /dev/null +++ b/app/Filament/Resources/System/IpLogs/Pages/ManageIpLogs.php @@ -0,0 +1,20 @@ + [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, -// \App\Http\Middleware\Platform::class, ], 'filament' => [ \Illuminate\Session\Middleware\StartSession::class, @@ -73,8 +73,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'permission' => \App\Http\Middleware\Permission::class, - 'admin' => \App\Http\Middleware\Admin::class, 'locale' => \App\Http\Middleware\Locale::class, 'checkUserStatus' => \App\Http\Middleware\CheckUserStatus::class, ]; diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php deleted file mode 100644 index 422efa94..00000000 --- a/app/Http/Middleware/Admin.php +++ /dev/null @@ -1,30 +0,0 @@ -user(); - if (!$user || !$user->canAccessAdmin()) { - do_log("denied!"); - throw new UnauthorizedException('Unauthorized!'); - } - do_log("allow!"); - return $next($request); - } -} diff --git a/app/Http/Middleware/BootNexus.php b/app/Http/Middleware/BootNexus.php index 42710b07..3d4dfe5b 100644 --- a/app/Http/Middleware/BootNexus.php +++ b/app/Http/Middleware/BootNexus.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Repositories\IpLogRepository; use Closure; use Illuminate\Http\Request; use Nexus\Nexus; diff --git a/app/Http/Middleware/LogUserIp.php b/app/Http/Middleware/LogUserIp.php new file mode 100644 index 00000000..de77f20a --- /dev/null +++ b/app/Http/Middleware/LogUserIp.php @@ -0,0 +1,26 @@ +user(); + if ($user) { + IpLogRepository::saveToCache($user->id); + } + return $response; + } +} diff --git a/app/Http/Middleware/Permission.php b/app/Http/Middleware/Permission.php deleted file mode 100644 index e6c6a7fc..00000000 --- a/app/Http/Middleware/Permission.php +++ /dev/null @@ -1,30 +0,0 @@ -user(); - if (!$user || (nexus()->isPlatformAdmin() && !$user->canAccessAdmin())) { - do_log("denied!"); - throw new UnauthorizedException('Unauthorized!'); - } - do_log("allow!"); - return $next($request); - } -} diff --git a/app/Http/Middleware/Platform.php b/app/Http/Middleware/Platform.php deleted file mode 100644 index 43417cf3..00000000 --- a/app/Http/Middleware/Platform.php +++ /dev/null @@ -1,29 +0,0 @@ -getPlatform(); - if (empty($platform)) { - throw new \InvalidArgumentException("Require platform header."); - } - if (!nexus()->isPlatformValid()) { - throw new \InvalidArgumentException("Invalid platform: " . $platform); - } - return $next($request); - } -} diff --git a/app/Jobs/CalculateUserSeedBonus.php b/app/Jobs/CalculateUserSeedBonus.php index b26d1fcd..afdf998b 100644 --- a/app/Jobs/CalculateUserSeedBonus.php +++ b/app/Jobs/CalculateUserSeedBonus.php @@ -3,8 +3,10 @@ namespace App\Jobs; use App\Models\BonusLogs; +use App\Models\IpLog; use App\Models\Setting; use App\Models\User; +use App\Repositories\IpLogRepository; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; @@ -238,7 +240,7 @@ class CalculateUserSeedBonus implements ShouldQueue $client = app(\ClickHouseDB\Client::class); $fields = ['business_type', 'uid', 'old_total_value', 'value', 'new_total_value', 'comment', 'created_at']; $client->insert("bonus_logs", $bonusLogInsert, $fields); - do_log("insertIntoClickHouseBulk done, created_at: {$bonusLogInsert[0]['created_at']}"); + do_log("insertIntoClickHouseBulk done, created_at: {$bonusLogInsert[0]['created_at']}, count: " . count($bonusLogInsert)); } catch (\Exception $e) { do_log($e->getMessage(), 'error'); } diff --git a/app/Jobs/SaveIpLogCacheToDB.php b/app/Jobs/SaveIpLogCacheToDB.php new file mode 100644 index 00000000..bb7a140b --- /dev/null +++ b/app/Jobs/SaveIpLogCacheToDB.php @@ -0,0 +1,31 @@ + $this->getIpLocation($attributes['ip']) + ); + } + + private function getIpLocation(string $ip) + { + $result = get_ip_location_from_geoip($ip); + $out = $result['name']; + $suffix = []; + if (!empty($result['city_en'])) { + $suffix[] = $result['city_en']; + } + if (!empty($result['country_en'])) { + $suffix[] = $result['country_en']; + } + if (!empty($result['continent_en'])) { + $suffix[] = $result['continent_en']; + } + if (!empty($suffix)) { + $out .= " " . implode(', ', $suffix); + } + return $out; + } +} diff --git a/app/Models/NexusModel.php b/app/Models/NexusModel.php index 7ab1fce9..3786dd73 100644 --- a/app/Models/NexusModel.php +++ b/app/Models/NexusModel.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Nexus\Database\NexusDB; @@ -16,6 +17,13 @@ class NexusModel extends Model protected $connection = NexusDB::ELOQUENT_CONNECTION_NAME; + protected function usernameForAdmin(): Attribute + { + return Attribute::make( + get: fn (mixed $value, array $attributes) => username_for_admin($attributes['uid'] ?? $attributes['userid'] ?? $attributes['user_id']) + ); + } + /** * * @param \DateTimeInterface $date diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 4ac33ee4..1490cfdb 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -108,7 +108,7 @@ class Setting extends NexusModel { $redis = NexusDB::redis(); $key = self::USER_TOKEN_PERMISSION_ALLOWED_CACHE_KRY; - $redis->del($key); + $redis->unlink($key); //must not use cache if (empty($allowed)) { $allowed = self::getFromDb("permission.user_token_allowed"); diff --git a/app/Models/TrackerUrl.php b/app/Models/TrackerUrl.php index 8d9a5f0c..0992bf39 100644 --- a/app/Models/TrackerUrl.php +++ b/app/Models/TrackerUrl.php @@ -35,7 +35,7 @@ class TrackerUrl extends NexusModel { //添加 id 与 URL 映射 $redis = NexusDB::redis(); - $redis->del(self::TRACKER_URL_CACHE_KEY); + $redis->unlink(self::TRACKER_URL_CACHE_KEY); $list = self::listAll(); $first = $list->first(); $hasDefault = false; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 00399302..5dccda00 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -40,11 +40,11 @@ class RouteServiceProvider extends ServiceProvider $this->routes(function () { Route::prefix('api/v1') - ->middleware('api') + ->middleware(['api', 'locale']) ->namespace($this->namespace) ->group(base_path('routes/api.php')); - Route::middleware('web') + Route::middleware(['web', 'locale']) ->namespace($this->namespace) ->group(base_path('routes/web.php')); diff --git a/app/Repositories/CleanupRepository.php b/app/Repositories/CleanupRepository.php index 3222c11b..3f7b075a 100644 --- a/app/Repositories/CleanupRepository.php +++ b/app/Repositories/CleanupRepository.php @@ -132,7 +132,7 @@ class CleanupRepository extends BaseRepository //remove this batch if ($batchKey != self::USER_SEED_BONUS_BATCH_KEY) { - $redis->del($batch); + $redis->unlink($batch); } $endTimestamp = time(); do_log(sprintf("$logPrefix, [DONE], batch: $batch, count: $count, cost time: %d seconds", $endTimestamp - $beginTimestamp)); diff --git a/app/Repositories/IpLogRepository.php b/app/Repositories/IpLogRepository.php new file mode 100644 index 00000000..b89d8d9f --- /dev/null +++ b/app/Repositories/IpLogRepository.php @@ -0,0 +1,87 @@ +hincrby($key, $field, 1); + do_log("success hincrby $key $field, result: $result", "debug"); + if ($result === 1) { + $redis->expire($key, self::CACHE_TIME); + } + } + } + + public static function saveToDB(): void + { + $beginTimestamp = microtime(true); + $redis = NexusDB::redis(); + $begin = Carbon::now()->subSeconds(self::CACHE_TIME); + $end = Carbon::now()->subHours(1); + $interval =\DateInterval::createFromDateString("1 hour"); + $period = new \DatePeriod($begin->clone(), $interval, $end); + $size = 2000; + do_log(sprintf("begin: %s, end: %s, size: %s", $begin->toDateTimeString(), $end->toDateTimeString(), $size)); + $redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY); + foreach ($period as $dt) { + $key = sprintf("%s:%s", self::CACHE_KEY_PREFIX, $dt->format('Y-m-d-H')); + if (!$redis->exists($key)) { + do_log("key: $key not found", "debug"); + continue; + } + if ($redis->hlen($key) == 0) { + do_log("key: $key length = 0", "debug"); + $redis->unlink($key); + } + do_log("handing key: $key"); + //遍历hash + $it = NULL; + while($arr_keys = $redis->hScan($key, $it, "*", $size)) { + $insert = []; + foreach ($arr_keys as $field => $value) { + list($userId, $ip, $uri) = explode("|", $field); + $insert[] = [ + 'userid' => $userId, + 'ip' => $ip, + 'uri' => $uri, + 'access' => date("Y-m-d H:i:s"), + 'count' => intval($value), + ]; + } + if (!empty($insert)) { + IpLog::query()->insert($insert); + } + do_log("key: $key, it: $it, count: " . count($insert)); + } + $redis->unlink($key); + do_log("handle key: $key done!"); + } + do_log(sprintf("all done! cost time: %.3f sec.", microtime(true) - $beginTimestamp)); + } + +} diff --git a/app/Repositories/RequireSeedTorrentRepository.php b/app/Repositories/RequireSeedTorrentRepository.php index f51539eb..0401bfd8 100644 --- a/app/Repositories/RequireSeedTorrentRepository.php +++ b/app/Repositories/RequireSeedTorrentRepository.php @@ -104,7 +104,7 @@ class RequireSeedTorrentRepository extends BaseRepository //remove torrent from list $redis->hDel(self::getTorrentCacheKey(), $torrent->id); //remove all users under torrent - $redis->del(self::getTorrentUserCacheKey($torrent->id)); + $redis->unlink(self::getTorrentUserCacheKey($torrent->id)); } RequireSeedTorrent::query()->whereIn('torrent_id', $idArr)->delete(); UserRequireSeedTorrent::query()->whereIn('torrent_id', $idArr)->delete(); diff --git a/app/Repositories/TorrentRepository.php b/app/Repositories/TorrentRepository.php index 7c6abc79..c446bb24 100644 --- a/app/Repositories/TorrentRepository.php +++ b/app/Repositories/TorrentRepository.php @@ -221,9 +221,7 @@ class TorrentRepository extends BaseRepository } if ($apiQueryBuilder->hasIncludeField('description') && $apiQueryBuilder->hasInclude('extra')) { - do_log("before format_description of torrent: {$torrent->id}"); $descriptionArr = format_description($torrent->extra->descr ?? ''); - do_log("after format_description of torrent: {$torrent->id}"); $torrent->description = $descriptionArr; $torrent->images = get_image_from_description($descriptionArr); } diff --git a/app/Utils/ApiQueryBuilder.php b/app/Utils/ApiQueryBuilder.php index 9759296d..22fb96eb 100644 --- a/app/Utils/ApiQueryBuilder.php +++ b/app/Utils/ApiQueryBuilder.php @@ -101,10 +101,10 @@ class ApiQueryBuilder { $includeCounts = explode(',', $this->request->query(self::PARAM_NAME_INCLUDE_COUNTS, '')); $valid = array_intersect($this->allowedIncludeCounts, $includeCounts); - do_log(sprintf( - "includeCounts: %s, allow: %s, valid: %s", - json_encode($includeCounts), json_encode($this->allowedIncludeCounts), json_encode($valid) - )); +// do_log(sprintf( +// "includeCounts: %s, allow: %s, valid: %s", +// json_encode($includeCounts), json_encode($this->allowedIncludeCounts), json_encode($valid) +// )); $this->query->withCount($valid); } diff --git a/database/migrations/2025_10_12_052151_add_uri_and_count_field_to_iplog_table.php b/database/migrations/2025_10_12_052151_add_uri_and_count_field_to_iplog_table.php new file mode 100644 index 00000000..fe334610 --- /dev/null +++ b/database/migrations/2025_10_12_052151_add_uri_and_count_field_to_iplog_table.php @@ -0,0 +1,30 @@ +string('uri')->nullable(); + $table->integer('count')->default(0); + $table->index('ip'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('iplog', function (Blueprint $table) { + $table->dropColumn(['uri', 'count']); + }); + } +}; diff --git a/include/functions.php b/include/functions.php index 27641fae..75c3174c 100644 --- a/include/functions.php +++ b/include/functions.php @@ -2498,10 +2498,12 @@ function stdhead($title = "", $msgalert = true, $script = "", $place = "") $tstart = getmicrotime(); // Start time //Insert old ip into iplog if ($CURUSER){ - if ($iplog1 == "yes") { - if (($oldip != $CURUSER["ip"]) && $CURUSER["ip"]) - sql_query("INSERT INTO iplog (ip, userid, access) VALUES (" . sqlesc($CURUSER['ip']) . ", " . $CURUSER['id'] . ", '" . $CURUSER['last_access'] . "')"); - } +// if ($iplog1 == "yes") { +// if (($oldip != $CURUSER["ip"]) && $CURUSER["ip"]) +// sql_query("INSERT INTO iplog (ip, userid, access) VALUES (" . sqlesc($CURUSER['ip']) . ", " . $CURUSER['id'] . ", '" . $CURUSER['last_access'] . "')"); +// } + //record always + \App\Repositories\IpLogRepository::saveToCache($CURUSER['id']); $USERUPDATESET[] = "last_access = ".sqlesc(date("Y-m-d H:i:s")); $USERUPDATESET[] = "ip = ".sqlesc($CURUSER['ip']); } @@ -2966,8 +2968,10 @@ function stdfoot() { echo "