mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-03 14:10:57 +08:00
Refactor IP History
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
139
app/Filament/Resources/System/IpLogs/IpLogResource.php
Normal file
139
app/Filament/Resources/System/IpLogs/IpLogResource.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\IpLogs;
|
||||
|
||||
use App\Filament\Resources\System\IpLogs\Pages\ManageIpLogs;
|
||||
use App\Models\IpLog;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use UnitEnum;
|
||||
|
||||
class IpLogResource extends Resource
|
||||
{
|
||||
protected static ?string $model = IpLog::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
protected static string | UnitEnum | null $navigationGroup = 'System';
|
||||
|
||||
public static function getLabel(): ?string
|
||||
{
|
||||
return __('ip-log.label');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->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('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Filament/Resources/System/IpLogs/Pages/ManageIpLogs.php
Normal file
20
app/Filament/Resources/System/IpLogs/Pages/ManageIpLogs.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\IpLogs\Pages;
|
||||
|
||||
use App\Filament\PageListSingle;
|
||||
use App\Filament\Resources\System\IpLogs\IpLogResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageIpLogs extends PageListSingle
|
||||
{
|
||||
protected static string $resource = IpLogResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Http;
|
||||
|
||||
use App\Http\Middleware\Filament;
|
||||
use App\Http\Middleware\Locale;
|
||||
use App\Http\Middleware\LogUserIp;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
@@ -24,7 +25,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
// \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\BootNexus::class,
|
||||
Locale::class,
|
||||
LogUserIp::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,6 @@ class Kernel extends HttpKernel
|
||||
'api' => [
|
||||
'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,
|
||||
];
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\User;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\UnauthorizedException;
|
||||
|
||||
class Admin
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
/** @var CheckUserStatus $user */
|
||||
$user = $request->user();
|
||||
if (!$user || !$user->canAccessAdmin()) {
|
||||
do_log("denied!");
|
||||
throw new UnauthorizedException('Unauthorized!');
|
||||
}
|
||||
do_log("allow!");
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Repositories\IpLogRepository;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Nexus\Nexus;
|
||||
|
||||
26
app/Http/Middleware/LogUserIp.php
Normal file
26
app/Http/Middleware/LogUserIp.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Repositories\IpLogRepository;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class LogUserIp
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$response = $next($request);
|
||||
$user = $request->user();
|
||||
if ($user) {
|
||||
IpLogRepository::saveToCache($user->id);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\User;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\UnauthorizedException;
|
||||
|
||||
class Permission
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
/** @var CheckUserStatus $user */
|
||||
$user = $request->user();
|
||||
if (!$user || (nexus()->isPlatformAdmin() && !$user->canAccessAdmin())) {
|
||||
do_log("denied!");
|
||||
throw new UnauthorizedException('Unauthorized!');
|
||||
}
|
||||
do_log("allow!");
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\UnauthorizedException;
|
||||
|
||||
class Platform
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$platform = nexus()->getPlatform();
|
||||
if (empty($platform)) {
|
||||
throw new \InvalidArgumentException("Require platform header.");
|
||||
}
|
||||
if (!nexus()->isPlatformValid()) {
|
||||
throw new \InvalidArgumentException("Invalid platform: " . $platform);
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
31
app/Jobs/SaveIpLogCacheToDB.php
Normal file
31
app/Jobs/SaveIpLogCacheToDB.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Repositories\IpLogRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class SaveIpLogCacheToDB implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
IpLogRepository::saveToDB();
|
||||
do_log("done");
|
||||
}
|
||||
}
|
||||
40
app/Models/IpLog.php
Normal file
40
app/Models/IpLog.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
|
||||
class IpLog extends NexusModel
|
||||
{
|
||||
protected $table = 'iplog';
|
||||
|
||||
protected $fillable = ['ip', 'userid', 'access', 'uri', 'count'];
|
||||
|
||||
protected function ipLocation(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn (mixed $value, array $attributes) => $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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'));
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
87
app/Repositories/IpLogRepository.php
Normal file
87
app/Repositories/IpLogRepository.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\IpLog;
|
||||
use Carbon\Carbon;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class IpLogRepository extends BaseRepository
|
||||
{
|
||||
const CACHE_KEY_PREFIX = 'nexus_ip_logs';
|
||||
|
||||
private const CACHE_TIME = 72 * 3600;
|
||||
|
||||
public static function saveToCache($userId, $uri = null, $ipArr = null): void
|
||||
{
|
||||
if (!is_numeric($userId) || $userId <= 0) {
|
||||
do_log("invalid userId: $userId", "error");
|
||||
return;
|
||||
}
|
||||
$redis = NexusDB::redis();
|
||||
if (is_null($uri)) {
|
||||
$parsed_uri = parse_url($_SERVER['REQUEST_URI']);
|
||||
$uri = $parsed_uri['path'];
|
||||
}
|
||||
if (is_null($ipArr)) {
|
||||
$ipArr = [getip()];
|
||||
}
|
||||
$key = sprintf("%s:%s", self::CACHE_KEY_PREFIX, date('Y-m-d-H'));
|
||||
foreach ($ipArr as $ip) {
|
||||
$field = sprintf("%s|%s|%s", $userId, $ip, $uri);
|
||||
$result = $redis->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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('iplog', function (Blueprint $table) {
|
||||
$table->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']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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 "<div align=\"center\" style=\"margin-top: 10px\" id=\"\">".$footerad[0]."</div>";
|
||||
}
|
||||
print("<div style=\"margin-top: 10px; margin-bottom: 30px;\" align=\"center\">");
|
||||
if ($CURUSER && count($USERUPDATESET)){
|
||||
sql_query("UPDATE users SET " . join(",", $USERUPDATESET) . " WHERE id = ".$CURUSER['id']);
|
||||
if ($CURUSER) {
|
||||
if (count($USERUPDATESET)) {
|
||||
sql_query("UPDATE users SET " . join(",", $USERUPDATESET) . " WHERE id = ".$CURUSER['id']);
|
||||
}
|
||||
}
|
||||
// Variables for End Time
|
||||
$tend = microtime(true);
|
||||
@@ -6099,9 +6103,9 @@ function calculate_seed_bonus($uid, $torrentIdArr = null): array
|
||||
$torrentIdArr = [-1];
|
||||
}
|
||||
$idStr = implode(',', \Illuminate\Support\Arr::wrap($torrentIdArr));
|
||||
$sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, 'NO_PEER_ID' as peerID, '' as last_action from torrents WHERE id in ($idStr) and size >= $minSize";
|
||||
$sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, 'NO_PEER_ID' as peerID, '' as last_action, '' as ip from torrents WHERE id in ($idStr) and size >= $minSize";
|
||||
} else {
|
||||
$sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, peers.id as peerID, peers.last_action from torrents LEFT JOIN peers ON peers.torrent = torrents.id WHERE peers.userid = $uid AND peers.seeder ='yes' and torrents.size > $minSize group by peers.torrent, peers.peer_id";
|
||||
$sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, peers.id as peerID, peers.last_action, peers.ip from torrents LEFT JOIN peers ON peers.torrent = torrents.id WHERE peers.userid = $uid AND peers.seeder ='yes' and torrents.size > $minSize group by peers.torrent, peers.peer_id";
|
||||
}
|
||||
$tagGrouped = [];
|
||||
$torrentResult = \Nexus\Database\NexusDB::select($sql);
|
||||
@@ -6120,11 +6124,15 @@ function calculate_seed_bonus($uid, $torrentIdArr = null): array
|
||||
$medalAdditionalFactor = floatval($userMedalResult[0]['factor'] ?? 0);
|
||||
do_log("$logPrefix, sql: $sql, count: " . count($torrentResult) . ", officialTag: $officialTag, officialAdditionalFactor: $officialAdditionalFactor, zeroBonusTag: $zeroBonusTag, zeroBonusFactor: $zeroBonusFactor, medalAdditionalFactor: $medalAdditionalFactor");
|
||||
$last_action = "";
|
||||
$ip_arr = [];
|
||||
foreach ($torrentResult as $torrent)
|
||||
{
|
||||
if ($torrent['last_action'] > $last_action) {
|
||||
$last_action = $torrent['last_action'];
|
||||
}
|
||||
if (!empty($torrent['ip']) && !isset($ip_arr[$torrent['ip']])) {
|
||||
$ip_arr[$torrent['ip']] = $torrent['ip'];
|
||||
}
|
||||
$size = bcadd($size, $torrent['size']);
|
||||
$weeks_alive = ($timenow - strtotime($torrent['added'])) / $sectoweek;
|
||||
$gb_size = $gb_size_raw = $torrent['size'] / 1073741824;
|
||||
@@ -6155,11 +6163,12 @@ function calculate_seed_bonus($uid, $torrentIdArr = null): array
|
||||
$medal_bonus = $valuetwo * atan($A / $l_bonus);
|
||||
$result = compact(
|
||||
'seed_points','seed_bonus', 'A', 'count', 'torrent_peer_count', 'size', 'last_action',
|
||||
'official_bonus', 'official_a', 'official_torrent_peer_count', 'official_size', 'medal_bonus'
|
||||
'official_bonus', 'official_a', 'official_torrent_peer_count', 'official_size', 'medal_bonus',
|
||||
);
|
||||
$result['donor_times'] = $donortimes_bonus;
|
||||
$result['official_additional_factor'] = $officialAdditionalFactor;
|
||||
$result['medal_additional_factor'] = $medalAdditionalFactor;
|
||||
$result['ip_arr'] = array_keys($ip_arr);
|
||||
do_log("$logPrefix, result: " . json_encode($result));
|
||||
return $result;
|
||||
}
|
||||
@@ -6514,6 +6523,9 @@ function torrent_name_for_admin(\App\Models\Torrent|null $torrent, $withTags = f
|
||||
|
||||
function username_for_admin(int $id)
|
||||
{
|
||||
if (empty($id)) {
|
||||
return '';
|
||||
}
|
||||
return new HtmlString(get_username($id, false, true, true, true));
|
||||
}
|
||||
|
||||
|
||||
@@ -53,13 +53,7 @@ function benc_resp($d)
|
||||
function benc_resp_raw($x) {
|
||||
header("Content-Type: text/plain; charset=utf-8");
|
||||
header("Pragma: no-cache");
|
||||
|
||||
if (isset($_SERVER["HTTP_ACCEPT_ENCODING"]) && $_SERVER["HTTP_ACCEPT_ENCODING"] == "gzip" && function_exists('gzencode')) {
|
||||
header("Content-Encoding: gzip");
|
||||
echo gzencode($x, 9, FORCE_GZIP);
|
||||
}
|
||||
else
|
||||
echo $x;
|
||||
echo $x;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -731,7 +731,7 @@ class Install
|
||||
$redis = NexusDB::redis();
|
||||
$result = $redis->info();
|
||||
$version = $result['redis_version'];
|
||||
$minVersion = '2.6.12';
|
||||
$minVersion = '4.0.0';
|
||||
$match = version_compare($version, $minVersion, '>=');
|
||||
return compact('version', 'match', 'minVersion');
|
||||
}
|
||||
|
||||
@@ -657,6 +657,7 @@ if(count($USERUPDATESET) && $userid)
|
||||
$lockKey = sprintf("record_batch_lock:%s:%s", $userid, $torrentid);
|
||||
if ($redis->set($lockKey, TIMENOW, ['nx', 'ex' => $autoclean_interval_one])) {
|
||||
\App\Repositories\CleanupRepository::recordBatch($redis, $userid, $torrentid);
|
||||
\App\Repositories\IpLogRepository::saveToCache($userid, null, [$ip]);
|
||||
}
|
||||
if (\App\Repositories\RequireSeedTorrentRepository::shouldRecordUser($redis, $userid, $torrentid)) {
|
||||
if (!isset($snatchInfo)) {
|
||||
|
||||
@@ -62,10 +62,11 @@ if (!empty($_REQUEST['downhash'])) {
|
||||
}
|
||||
}
|
||||
//User may choose to download torrent from RSS. So log ip changes when downloading torrents.
|
||||
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'] . "')");
|
||||
//}
|
||||
\App\Repositories\IpLogRepository::saveToCache($CURUSER['id']);
|
||||
//User may choose to download torrent from RSS. So update his last_access and ip when downloading torrents.
|
||||
sql_query("UPDATE users SET last_access = ".sqlesc(date("Y-m-d H:i:s")).", ip = ".sqlesc($CURUSER['ip'])." WHERE id = ".sqlesc($CURUSER['id']));
|
||||
|
||||
|
||||
@@ -6,6 +6,33 @@ dbconn_announce();
|
||||
|
||||
// BLOCK ACCESS WITH WEB BROWSERS AND CHEATS!
|
||||
block_browser();
|
||||
$passkey = $_GET['passkey'] ?? '';
|
||||
if (empty($passkey)) {
|
||||
err('require passkey');
|
||||
}
|
||||
$redis = $Cache->getRedis();
|
||||
$passkeyInvalidKey = "passkey_invalid";
|
||||
// check passkey
|
||||
if (!$az = $Cache->get_value('user_passkey_'.$passkey.'_content')){
|
||||
$res = sql_query("SELECT id, username, downloadpos, enabled, uploaded, downloaded, class, parked, clientselect, showclienterror, passkey, donor, donoruntil, seedbonus, tracker_url_id FROM users WHERE passkey=". sqlesc($passkey)." LIMIT 1");
|
||||
$az = mysql_fetch_array($res);
|
||||
do_log("[check passkey], currentUser: " . nexus_json_encode($az));
|
||||
$Cache->cache_value('user_passkey_'.$passkey.'_content', $az, 3600);
|
||||
}
|
||||
if (!$az) {
|
||||
$redis->set("$passkeyInvalidKey:$passkey", TIMENOW, ['ex' => 24*3600]);
|
||||
err("Invalid passkey! Re-download the .torrent from $BASEURL");
|
||||
}
|
||||
if ($az["enabled"] == "no")
|
||||
err("Your account is disabled!", 300);
|
||||
elseif ($az["parked"] == "yes")
|
||||
err("Your account is parked! (Read the FAQ)", 300);
|
||||
elseif ($az["downloadpos"] == "no")
|
||||
err("Your downloading privileges have been disabled! (Read the rules)", 300);
|
||||
|
||||
$userid = intval($az['id'] ?? 0);
|
||||
unset($GLOBALS['CURUSER']);
|
||||
$CURUSER = $GLOBALS["CURUSER"] = $az;
|
||||
|
||||
preg_match_all('/info_hash=([^&]*)/i', $_SERVER["QUERY_STRING"], $info_hash_array);
|
||||
$fields = "info_hash, times_completed, seeders, leechers";
|
||||
|
||||
@@ -310,7 +310,7 @@ elseif ($action == 'tweaksettings') // tweak settings
|
||||
print ($notice);
|
||||
print ("<form method='post' action='".$_SERVER["SCRIPT_NAME"]."'><input type='hidden' name='action' value='savesettings_tweak' />");
|
||||
yesorno($lang_settings['row_save_user_location'], 'where', $TWEAK["where"], $lang_settings['text_save_user_location_note']);
|
||||
yesorno($lang_settings['row_log_user_ips'], 'iplog1', $TWEAK["iplog1"], $lang_settings['text_store_user_ips_note']);
|
||||
// yesorno($lang_settings['row_log_user_ips'], 'iplog1', $TWEAK["iplog1"], $lang_settings['text_store_user_ips_note']);
|
||||
tr($lang_settings['row_kps_enabled'],"<input type='radio' id='bonusenable' name='bonus'" . ($TWEAK["bonus"] == "enable" ? " checked='checked'" : "") . " value='enable' /> <label for='bonusenable'>".$lang_settings['text_enabled']."</label> <input type='radio' id='bonusdisablesave' name='bonus'" . ($TWEAK["bonus"] == "disablesave" ? " checked='checked'" : "") . " value='disablesave' /> <label for='bonusdisablesave'>".$lang_settings['text_disabled_but_save']."</label> <input type='radio' id='bonusdisable' name='bonus'" . ($TWEAK["bonus"] == "disable" ? " checked='checked'" : "") . " value='disable' /> <label for='bonusdisable'>".$lang_settings['text_disabled_no_save']."</label> <br />".$lang_settings['text_kps_note'], 1);
|
||||
yesorno($lang_settings['row_enable_location'], 'enablelocation', $TWEAK["enablelocation"], $lang_settings['text_enable_location_note']);
|
||||
yesorno($lang_settings['row_enable_tooltip'], 'enabletooltip', $TWEAK["enabletooltip"], $lang_settings['text_enable_tooltip_note']);
|
||||
|
||||
12
resources/lang/en/ip-log.php
Normal file
12
resources/lang/en/ip-log.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'label' => 'IP History',
|
||||
'access' => 'Access time',
|
||||
'access_begin' => 'Access time begin',
|
||||
'access_end' => 'Access time end',
|
||||
'access_tooltip' => 'The actual time was within one hour prior to this.',
|
||||
'uri' => 'URI',
|
||||
'count' => 'Count',
|
||||
'ip_location' => 'Location',
|
||||
];
|
||||
12
resources/lang/zh_CN/ip-log.php
Normal file
12
resources/lang/zh_CN/ip-log.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'label' => 'IP 历史',
|
||||
'access' => '访问时间',
|
||||
'access_begin' => '访问时间开始',
|
||||
'access_end' => '访问时间结束',
|
||||
'access_tooltip' => '实际时间在此之前 1 小时内',
|
||||
'uri' => '访问路径',
|
||||
'count' => '访问次数',
|
||||
'ip_location' => '地理位置',
|
||||
];
|
||||
12
resources/lang/zh_TW/ip-log.php
Normal file
12
resources/lang/zh_TW/ip-log.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'label' => 'IP 歷史',
|
||||
'access' => '訪問時間',
|
||||
'access_begin' => '訪問時間開始',
|
||||
'access_end' => '訪問時間結束',
|
||||
'access_tooltip' => '實際時間在此之前 1 小時內',
|
||||
'uri' => '訪問路徑',
|
||||
'count' => '訪問次數',
|
||||
'ip_location' => '地理位置',
|
||||
];
|
||||
Reference in New Issue
Block a user