Merge remote-tracking branch 'origin/php8' into php8

This commit is contained in:
xiaomlove
2024-11-18 22:21:27 +08:00
62 changed files with 864 additions and 242 deletions

View File

@@ -6,6 +6,7 @@ use App\Events\NewsCreated;
use App\Events\TorrentCreated;
use App\Events\TorrentDeleted;
use App\Events\TorrentUpdated;
use App\Events\UserCreated;
use App\Events\UserDestroyed;
use App\Events\UserDisabled;
use App\Events\UserEnabled;
@@ -31,15 +32,18 @@ class FireEvent extends Command
*
* @var string
*/
protected $description = 'Fire a event, options: --name, --idKey --idKeyOld';
protected $description = 'Fire an event, options: --name, --idKey --idKeyOld';
protected array $eventMaps = [
"torrent_created" => ['event' => TorrentCreated::class, 'model' => Torrent::class],
"torrent_updated" => ['event' => TorrentUpdated::class, 'model' => Torrent::class],
"torrent_deleted" => ['event' => TorrentDeleted::class, 'model' => Torrent::class],
"user_created" => ['event' => UserCreated::class, 'model' => User::class],
"user_destroyed" => ['event' => UserDestroyed::class, 'model' => User::class],
"user_disabled" => ['event' => UserDisabled::class, 'model' => User::class],
"user_enabled" => ['event' => UserEnabled::class, 'model' => User::class],
"news_created" => ['event' => NewsCreated::class, 'model' => News::class],
];
@@ -69,6 +73,7 @@ class FireEvent extends Command
}
$result = call_user_func_array([$eventName, "dispatch"], $params);
$log .= ", success call dispatch, result: " . var_export($result, true);
publish_model_event($name, $model->id);
} else {
$log .= ", invalid argument to call, it should be instance of: " . Model::class;
}

View File

@@ -40,10 +40,12 @@ use App\Repositories\UserRepository;
use Carbon\Carbon;
use Filament\Notifications\Notification;
use GeoIp2\Database\Reader;
use GuzzleHttp\Client;
use Illuminate\Console\Command;
use Illuminate\Encryption\Encrypter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@@ -59,6 +61,7 @@ use NexusPlugin\Permission\Models\Role;
use NexusPlugin\PostLike\PostLikeRepository;
use NexusPlugin\StickyPromotion\Models\StickyPromotion;
use NexusPlugin\StickyPromotion\Models\StickyPromotionParticipator;
use NexusPlugin\Tracker\TrackerRepository;
use NexusPlugin\Work\Models\RoleWork;
use NexusPlugin\Work\WorkRepository;
use PhpIP\IP;
@@ -98,7 +101,8 @@ class Test extends Command
*/
public function handle()
{
CleanupRepository::checkQueueFailedJobs();
$result = \Nexus\Plugin\Plugin::listEnabled();
dd($result);
}
}

View File

@@ -4,6 +4,9 @@ namespace App\Console;
use App\Jobs\CheckCleanup;
use App\Jobs\CheckQueueFailedJobs;
use App\Jobs\MaintainPluginState;
use App\Jobs\ManagePlugin;
use App\Utils\ThirdPartyJob;
use Carbon\Carbon;
use Illuminate\Console\Scheduling\Event;
use Illuminate\Console\Scheduling\Schedule;
@@ -41,6 +44,8 @@ class Kernel extends ConsoleKernel
$schedule->command('meilisearch:import')->weeklyOn(1, "03:00")->withoutOverlapping();
$schedule->command('torrent:load_pieces_hash')->dailyAt("01:00")->withoutOverlapping();
$schedule->job(new CheckQueueFailedJobs())->everySixHours()->withoutOverlapping();
$schedule->job(new ThirdPartyJob())->everyMinute()->withoutOverlapping();
$schedule->job(new MaintainPluginState())->everyMinute()->withoutOverlapping();
$this->registerScheduleCleanup($schedule);
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ?Model $model = null;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@@ -1,7 +1,32 @@
<?php
namespace App\Filament\Pages;
use App\Filament\Widgets\AccountInfo;
use App\Filament\Widgets\LatestTorrents;
use App\Filament\Widgets\LatestUsers;
use App\Filament\Widgets\SystemInfo;
use App\Filament\Widgets\TorrentStat;
use App\Filament\Widgets\TorrentTrend;
use App\Filament\Widgets\UserClassStat;
use App\Filament\Widgets\UserStat;
use App\Filament\Widgets\UserTrend;
class Dashboard extends \Filament\Pages\Dashboard
{
protected ?string $maxContentWidth = 'full';
protected function getWidgets(): array
{
return [
AccountInfo::class,
LatestUsers::class,
LatestTorrents::class,
UserTrend::class,
TorrentTrend::class,
UserStat::class,
UserClassStat::class,
TorrentStat::class,
SystemInfo::class,
];
}
}

View File

@@ -66,6 +66,8 @@ class TorrentStateResource extends Resource
Tables\Actions\EditAction::make()->after(function () {
do_log("cache_del: global_promotion_state");
NexusDB::cache_del(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
do_log("publish_model_event: global_promotion_state_updated");
publish_model_event("global_promotion_state_updated", 0);
}),
// Tables\Actions\DeleteAction::make(),
])

View File

@@ -117,9 +117,4 @@ class ViewExamUser extends ViewRecord
Actions\DeleteAction::make(),
];
}
private function getProgress()
{
}
}

View File

@@ -12,7 +12,7 @@ class TrustProxies extends Middleware
*
* @var array<int, string>|string|null
*/
protected $proxies;
protected $proxies = ['*'];
/**
* The headers that should be used to detect proxies.

68
app/Jobs/BuyTorrent.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
namespace App\Jobs;
use App\Models\TorrentBuyLog;
use App\Repositories\BonusRepository;
use App\Repositories\TorrentRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class BuyTorrent implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $userId;
public int $torrentId;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(int $userId, int $torrentId)
{
$this->userId = $userId;
$this->torrentId = $torrentId;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$logPrefix = sprintf("user: %s, torrent: %s", $this->userId, $this->torrentId);
$torrentRep = new TorrentRepository();
$userId = $this->userId;
$torrentId = $this->torrentId;
$hasBuy = TorrentBuyLog::query()
->where("uid", $userId)
->where("torrent_id", $torrentId)
->exists()
;
if ($hasBuy) {
//标记购买成功
do_log("$logPrefix, already bought");
$torrentRep->addBuySuccessCache($userId, $torrentId);
return;
}
try {
$bonusRep = new BonusRepository();
$bonusRep->consumeToBuyTorrent($this->userId, $this->torrentId);
//标记购买成功
do_log("$logPrefix, buy torrent success");
$torrentRep->addBuySuccessCache($userId, $torrentId);
} catch (\Throwable $throwable) {
//标记购买失败,缓存 3600 秒,这个时间内不能再次购买
do_log("$logPrefix, buy torrent fail: " . $throwable->getMessage(), "error");
$torrentRep->addBuyFailCache($userId, $torrentId);
}
}
}

View File

@@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Nexus\Database\NexusDB;
use Nexus\Nexus;
@@ -55,6 +56,16 @@ class CalculateUserSeedBonus implements ShouldQueue
public $timeout = 3600;
/**
* 获取任务时,应该通过的中间件。
*
* @return array
*/
public function middleware()
{
return [new WithoutOverlapping($this->idRedisKey)];
}
/**
* Execute the job.
*
@@ -63,7 +74,11 @@ class CalculateUserSeedBonus implements ShouldQueue
public function handle()
{
$beginTimestamp = time();
$logPrefix = sprintf("[CLEANUP_CLI_CALCULATE_SEED_BONUS_HANDLE_JOB], commonRequestId: %s, beginUid: %s, endUid: %s", $this->requestId, $this->beginUid, $this->endUid);
$logPrefix = sprintf(
"[CLEANUP_CLI_CALCULATE_SEED_BONUS_HANDLE_JOB], commonRequestId: %s, beginUid: %s, endUid: %s, idStr: %s, idRedisKey: %s",
$this->requestId, $this->beginUid, $this->endUid, $this->idStr, $this->idRedisKey
);
do_log("$logPrefix, job start ...");
$haremAdditionFactor = Setting::get('bonus.harem_addition');
$officialAdditionFactor = Setting::get('bonus.official_addition');
$donortimes_bonus = Setting::get('bonus.donortimes');
@@ -84,6 +99,8 @@ class CalculateUserSeedBonus implements ShouldQueue
$logFile = getLogFile("seed-bonus-points");
do_log("$logPrefix, [GET_UID_REAL], count: " . count($results) . ", logFile: $logFile");
$fd = fopen($logFile, 'a');
$seedPointsUpdates = $seedPointsPerHourUpdates = $seedBonusUpdates = [];
$logStr = "";
foreach ($results as $userInfo)
{
$uid = $userInfo['id'];
@@ -92,7 +109,7 @@ class CalculateUserSeedBonus implements ShouldQueue
$bonusLog = "[CLEANUP_CLI_CALCULATE_SEED_BONUS_HANDLE_USER], user: $uid, seedBonusResult: " . nexus_json_encode($seedBonusResult);
$all_bonus = $seedBonusResult['seed_bonus'];
$bonusLog .= ", all_bonus: $all_bonus";
if ($isDonor) {
if ($isDonor && $donortimes_bonus != 0) {
$all_bonus = $all_bonus * $donortimes_bonus;
$bonusLog .= ", isDonor, donortimes_bonus: $donortimes_bonus, all_bonus: $all_bonus";
}
@@ -112,13 +129,17 @@ class CalculateUserSeedBonus implements ShouldQueue
$all_bonus += $medalAddition;
$bonusLog .= ", medalAdditionFactor: {$seedBonusResult['medal_additional_factor']}, medalBonus: {$seedBonusResult['medal_bonus']}, medalAddition: $medalAddition, all_bonus: $all_bonus";
}
do_log($bonusLog);
$dividend = 3600 / $autoclean_interval_one;
$all_bonus = $all_bonus / $dividend;
$seed_points = $seedBonusResult['seed_points'] / $dividend;
$updatedAt = now()->toDateTimeString();
$sql = "update users set seed_points = ifnull(seed_points, 0) + $seed_points, seed_points_per_hour = {$seedBonusResult['seed_points']}, seedbonus = seedbonus + $all_bonus, seed_points_updated_at = '$updatedAt' where id = $uid limit 1";
do_log("$bonusLog, query: $sql");
NexusDB::statement($sql);
// $updatedAt = now()->toDateTimeString();
// $sql = "update users set seed_points = ifnull(seed_points, 0) + $seed_points, seed_points_per_hour = {$seedBonusResult['seed_points']}, seedbonus = seedbonus + $all_bonus, seed_points_updated_at = '$updatedAt' where id = $uid limit 1";
// do_log("$bonusLog, query: $sql");
// NexusDB::statement($sql);
$seedPointsUpdates[] = sprintf("when %d then ifnull(seed_points, 0) + %f", $uid, $seed_points);
$seedPointsPerHourUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['seed_points']);
$seedBonusUpdates[] = sprintf("when %d then seedbonus + %f", $uid, $all_bonus);
if ($fd) {
$log = sprintf(
'%s|%s|%s|%s|%s|%s|%s|%s',
@@ -126,16 +147,28 @@ class CalculateUserSeedBonus implements ShouldQueue
$userInfo['seed_points'], number_format($seed_points, 1, '.', ''), number_format($userInfo['seed_points'] + $seed_points, 1, '.', ''),
$userInfo['seedbonus'], number_format($all_bonus, 1, '.', ''), number_format($userInfo['seedbonus'] + $all_bonus, 1, '.', '')
);
fwrite($fd, $log . PHP_EOL);
// fwrite($fd, $log . PHP_EOL);
$logStr .= $log . PHP_EOL;
} else {
do_log("logFile: $logFile is not writeable!", 'error');
}
}
$nowStr = now()->toDateTimeString();
$sql = sprintf(
"update users set seed_points = case id %s end, seed_points_per_hour = case id %s end, seedbonus = case id %s end, seed_points_updated_at = '%s' where id in (%s)",
implode(" ", $seedPointsUpdates), implode(" ", $seedPointsPerHourUpdates), implode(" ", $seedBonusUpdates), $nowStr, $idStr
);
$result = NexusDB::statement($sql);
if ($delIdRedisKey) {
NexusDB::cache_del($this->idRedisKey);
}
fwrite($fd, $logStr);
$costTime = time() - $beginTimestamp;
do_log("$logPrefix, [DONE], cost time: $costTime seconds");
do_log(sprintf(
"$logPrefix, [DONE], update user count: %s, result: %s, cost time: %s seconds",
count($seedPointsUpdates), var_export($result, true), $costTime
));
do_log("$logPrefix, sql: $sql", "debug");
}
/**

View File

@@ -16,6 +16,10 @@ class LoadTorrentBoughtUsers implements ShouldQueue
private int $torrentId;
public $tries = 1;
public $timeout = 1800;
/**
* Create a new job instance.
*

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Nexus\Database\NexusDB;
use Nexus\Plugin\Plugin;
class MaintainPluginState implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$enabled = Plugin::listEnabled();
$key = "nexus_plugin_enabled";
NexusDB::redis()->del($key);
$nowStr = now()->toDateTimeString();
foreach ($enabled as $name => $value) {
NexusDB::redis()->hSet($key, $name, $nowStr);
}
do_log("$key: " . nexus_json_encode($enabled));
}
}

View File

@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Nexus\Database\NexusDB;
@@ -55,6 +56,16 @@ class UpdateTorrentSeedersEtc implements ShouldQueue
public $timeout = 1800;
/**
* 获取任务时,应该通过的中间件。
*
* @return array
*/
public function middleware()
{
return [new WithoutOverlapping($this->idRedisKey)];
}
/**
* Execute the job.
*
@@ -63,7 +74,11 @@ class UpdateTorrentSeedersEtc implements ShouldQueue
public function handle()
{
$beginTimestamp = time();
$logPrefix = sprintf("[CLEANUP_CLI_UPDATE_TORRENT_SEEDERS_ETC_HANDLE_JOB], commonRequestId: %s, beginTorrentId: %s, endTorrentId: %s", $this->requestId, $this->beginTorrentId, $this->endTorrentId);
$logPrefix = sprintf(
"[CLEANUP_CLI_UPDATE_TORRENT_SEEDERS_ETC_HANDLE_JOB], commonRequestId: %s, beginTorrentId: %s, endTorrentId: %s, idStr: %s, idRedisKey: %s",
$this->requestId, $this->beginTorrentId, $this->endTorrentId, $this->idStr, $this->idRedisKey
);
do_log("$logPrefix, job start ...");
$idStr = $this->idStr;
$delIdRedisKey = false;
@@ -76,44 +91,51 @@ class UpdateTorrentSeedersEtc implements ShouldQueue
return;
}
$torrentIdArr = explode(",", $idStr);
foreach ($torrentIdArr as $torrentId) {
if ($torrentId <= 0) {
continue;
}
$peerResult = NexusDB::table('peers')
->where('torrent', $torrentId)
->selectRaw("count(*) as count, seeder")
->groupBy('seeder')
->get()
;
$commentResult = NexusDB::table('comments')
->where('torrent',$torrentId)
->selectRaw("count(*) as count")
->first()
;
$update = [
'comments' => $commentResult && $commentResult->count !== null ? $commentResult->count : 0,
'seeders' => 0,
'leechers' => 0,
];
foreach ($peerResult as $item) {
if ($item->seeder == 'yes') {
$update['seeders'] = $item->count;
} elseif ($item->seeder == 'no') {
$update['leechers'] = $item->count;
}
}
NexusDB::table('torrents')->where('id', $torrentId)->update($update);
do_log("[CLEANUP_CLI_UPDATE_TORRENT_SEEDERS_ETC_HANDLE_TORRENT], [SUCCESS]: $torrentId => " . json_encode($update));
//批量取,简单化
$torrents = array();
// $res = sql_query("SELECT torrent, seeder, COUNT(*) AS c FROM peers GROUP BY torrent, seeder where torrent in ($idStr)");
$res = NexusDB::table("peers")
->selectRaw("torrent, seeder, COUNT(*) AS c")
->whereRaw("torrent in ($idStr)")
->groupBy(['torrent', 'seeder'])
->get();
foreach ($res as $row) {
if ($row->seeder == "yes")
$key = "seeders";
else
$key = "leechers";
$torrents[$row->torrent][$key] = $row->c;
}
// $res = sql_query("SELECT torrent, COUNT(*) AS c FROM comments GROUP BY torrent where torrent in ($idStr)");
$res = NexusDB::table("comments")
->selectRaw("torrent, COUNT(*) AS c")
->whereRaw("torrent in ($idStr)")
->groupBy(['torrent'])
->get();
foreach ($res as $row) {
$torrents[$row->torrent]["comments"] = $row->c;
}
$seedersUpdates = $leechersUpdates = $commentsUpdates = [];
foreach ($torrentIdArr as $id) {
$seedersUpdates[] = sprintf("when %d then %d", $id, $torrents[$id]["seeders"] ?? 0);
$leechersUpdates[] = sprintf("when %d then %d", $id, $torrents[$id]["leechers"] ?? 0);
$commentsUpdates[] = sprintf("when %d then %d", $id, $torrents[$id]["comments"] ?? 0);
}
$sql = sprintf(
"update torrents set seeders = case id %s end, leechers = case id %s end, comments = case id %s end where id in (%s)",
implode(" ", $seedersUpdates), implode(" ", $leechersUpdates), implode(" ", $commentsUpdates), $idStr
);
$result = NexusDB::statement($sql);
if ($delIdRedisKey) {
NexusDB::cache_del($this->idRedisKey);
}
$costTime = time() - $beginTimestamp;
do_log(sprintf(
"$logPrefix, [DONE], update torrent count: %s, cost time: %s seconds",
count($torrentIdArr), $costTime
"$logPrefix, [DONE], update torrent count: %s, result: %s, cost time: %s seconds",
count($torrentIdArr), var_export($result, true), $costTime
));
do_log("$logPrefix, sql: $sql", "debug");
}
/**

View File

@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Nexus\Database\NexusDB;
@@ -55,6 +56,16 @@ class UpdateUserSeedingLeechingTime implements ShouldQueue
public $timeout = 3600;
/**
* 获取任务时,应该通过的中间件。
*
* @return array
*/
public function middleware()
{
return [new WithoutOverlapping($this->idRedisKey)];
}
/**
* Execute the job.
*
@@ -63,9 +74,12 @@ class UpdateUserSeedingLeechingTime implements ShouldQueue
public function handle()
{
$beginTimestamp = time();
$logPrefix = sprintf("[CLEANUP_CLI_UPDATE_SEEDING_LEECHING_TIME_HANDLE_JOB], commonRequestId: %s, beginUid: %s, endUid: %s", $this->requestId, $this->beginUid, $this->endUid);
$logPrefix = sprintf(
"[CLEANUP_CLI_UPDATE_SEEDING_LEECHING_TIME_HANDLE_JOB], commonRequestId: %s, beginUid: %s, endUid: %s, idStr: %s, idRedisKey: %s",
$this->requestId, $this->beginUid, $this->endUid, $this->idStr, $this->idRedisKey,
);
do_log("$logPrefix, job start ...");
$count = 0;
$idStr = $this->idStr;
$delIdRedisKey = false;
if (empty($idStr) && !empty($this->idRedisKey)) {
@@ -76,33 +90,35 @@ class UpdateUserSeedingLeechingTime implements ShouldQueue
do_log("$logPrefix, no idStr or idRedisKey", "error");
return;
}
$uidArr = explode(",", $idStr);
foreach ($uidArr as $uid) {
if ($uid <= 0) {
continue;
}
$sumInfo = NexusDB::table('snatched')
->selectRaw('sum(seedtime) as seedtime_sum, sum(leechtime) as leechtime_sum')
->where('userid', $uid)
->first();
if ($sumInfo && $sumInfo->seedtime_sum !== null) {
$update = [
'seedtime' => $sumInfo->seedtime_sum ?? 0,
'leechtime' => $sumInfo->leechtime_sum ?? 0,
'seed_time_updated_at' => Carbon::now()->toDateTimeString(),
];
NexusDB::table('users')
->where('id', $uid)
->update($update);
do_log("[CLEANUP_CLI_UPDATE_SEEDING_LEECHING_TIME_HANDLE_USER], [SUCCESS]: $uid => " . json_encode($update));
$count++;
}
//批量取,简单化
// $res = sql_query("select userid, sum(seedtime) as seedtime_sum, sum(leechtime) as leechtime_sum from snatched group by userid where userid in ($idStr)");
$res = NexusDB::table("snatched")
->selectRaw("userid, sum(seedtime) as seedtime_sum, sum(leechtime) as leechtime_sum")
->whereRaw("userid in ($idStr)")
->groupBy("userid")
->get();
$seedtimeUpdates = $leechTimeUpdates = [];
$nowStr = now()->toDateTimeString();
$count = 0;
foreach ($res as $row) {
$count++;
$seedtimeUpdates = sprintf("when %d then %d", $row->userid, $row->seedtime_sum ?? 0);
$leechTimeUpdates = sprintf("when %d then %d", $row->userid, $row->leechtime_sum ?? 0);
}
$sql = sprintf(
"update users set seedtime = case id %s end, leechtime = case id %s end, seed_time_updated_at = '%s' where id in (%s)",
implode(" ", $seedtimeUpdates), implode(" ", $leechTimeUpdates), $nowStr, $idStr
);
$result = NexusDB::statement($sql);
if ($delIdRedisKey) {
NexusDB::cache_del($this->idRedisKey);
}
$costTime = time() - $beginTimestamp;
do_log("$logPrefix, [DONE], user total count: " . count($uidArr) . ", success update count: $count, cost time: $costTime seconds");
do_log(sprintf(
"$logPrefix, [DONE], update user count: %s, result: %s, cost time: %s seconds",
$count, var_export($result, true), $costTime
));
do_log("$logPrefix, sql: $sql", "debug");
}
/**

View File

@@ -184,7 +184,8 @@ class User extends Authenticatable implements FilamentUser, HasName
protected $fillable = [
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'modcomment', 'enabled', 'status',
'leechwarn', 'leechwarnuntil', 'page', 'class', 'uploaded', 'downloaded', 'clientselect', 'showclienterror', 'last_home',
'seedbonus', 'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card', 'seed_points_per_hour'
'seedbonus', 'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
'seed_points_per_hour', 'passkey',
];
/**

View File

@@ -6,6 +6,7 @@ use App\Http\Middleware\Locale;
use Carbon\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -37,8 +38,11 @@ class AppServiceProvider extends ServiceProvider
{
global $plugin;
$plugin->start();
// JsonResource::withoutWrapping();
DB::connection(config('database.default'))->enableQueryLog();
$forceScheme = strtolower(env('FORCE_SCHEME'));
if (env('APP_ENV') == "production" && in_array($forceScheme, ['https', 'http'])) {
URL::forceScheme($forceScheme);
}
Filament::serving(function () {
Filament::registerNavigationGroups([

View File

@@ -169,7 +169,10 @@ for k, v in pairs(batchList) do
local isBatchKeyNew = false
if batchKey == false then
batchKey = v .. ":" .. ARGV[4]
redis.call("SET", v, batchKey, "EX", ARGV[5])
redis.call("SET", v, batchKey)
if (k > 1) then
redis.call("EXPIRE", v, ARGV[5])
end
isBatchKeyNew = true
end
local hashKey

View File

@@ -9,6 +9,7 @@ use App\Models\User;
use Carbon\Carbon;
use Filament\Facades\Filament;
use Illuminate\Support\Facades\DB;
use Nexus\Database\NexusDB;
class DashboardRepository extends BaseRepository
{
@@ -51,18 +52,26 @@ class DashboardRepository extends BaseRepository
'text' => nexus_trans("dashboard.system_info.$name"),
'value' => DB::select(DB::raw('select version() as info'))[0]->info,
];
$name = 'os';
// $name = 'os';
// $result[$name] = [
// 'name' => $name,
// 'text' => nexus_trans("dashboard.system_info.$name"),
// 'value' => PHP_OS,
// ];
$name = 'redis_version';
$result[$name] = [
'name' => $name,
'text' => nexus_trans("dashboard.system_info.$name"),
'value' => PHP_OS,
'value' => NexusDB::redis()->info()['redis_version'],
];
$name = 'server_software';
$result[$name] = [
'name' => $name,
'text' => nexus_trans("dashboard.system_info.$name"),
'value' => $_SERVER['SERVER_SOFTWARE'] ?? '',
];
$name = 'load_average';
$result[$name] = [
'name' => $name,

View File

@@ -1327,32 +1327,4 @@ class ExamRepository extends BaseRepository
return $result;
}
public function fixIndexUploadTorrentCount()
{
$page = 1;
$size = 2000;
$examUserTable = (new ExamUser())->getTable();
$examProgressTable = (new ExamProgress())->getTable();
while (true) {
$offset = ($page - 1)*$size;
$list = NexusDB::table($examProgressTable)
->select("$examProgressTable.*, $examUserTable.created_at as exam_created_at")
->join($examUserTable, "$examProgressTable.exam_user_id", "=", "$examUserTable.id", "left")
->where("$examUserTable.status", ExamUser::STATUS_NORMAL)
->where("$examProgressTable.index", Exam::INDEX_UPLOAD_TORRENT_COUNT)
->limit($size)
->offset($offset)
->get()
;
if ($list->count() == 0) {
do_log("page: $page, offset: $offset, no more data...");
return;
}
foreach ($list as $item) {
}
}
}
}

View File

@@ -34,6 +34,7 @@ class SeedBoxRepository extends BaseRepository
$params = $this->formatParams($params);
$seedBoxRecord = SeedBoxRecord::query()->create($params);
$this->clearCache();
publish_model_event("seed_box_record_created", $seedBoxRecord->id);
return $seedBoxRecord;
}
@@ -85,6 +86,7 @@ class SeedBoxRepository extends BaseRepository
$params = $this->formatParams($params);
$model->update($params);
$this->clearCache();
publish_model_event("seed_box_record_updated", $id);
return $model;
}

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Repositories;
use App\Http\Middleware\Locale;
use App\Models\Invite;
use App\Models\Message;
use App\Models\News;
@@ -503,4 +504,25 @@ class ToolRepository extends BaseRepository
}
}
}
public function sendAlarmEmail(string $subjectTransKey, array $subjectTransContext, string $msgTransKey, array $msgTransContext): void
{
$receiverUid = get_setting("system.alarm_email_receiver");
if (empty($receiverUid)) {
$locale = Locale::getDefault();
$subject = nexus_trans($subjectTransKey, $subjectTransContext, $locale);
$msg = nexus_trans($msgTransKey, $msgTransContext, $locale);
do_log(sprintf("%s - %s", $subject, $msg), "error");
} else {
$receiverUidArr = preg_split("/[\r\n\s,]+/", $receiverUid);
$users = User::query()->whereIn("id", $receiverUidArr)->get(User::$commonFields);
foreach ($users as $user) {
$locale = $user->locale;
$subject = nexus_trans($subjectTransKey, $subjectTransContext, $locale);
$msg = nexus_trans($msgTransKey, $msgTransContext, $locale);
$result = $this->sendMail($user->email, $subject, $msg);
do_log(sprintf("send msg: %s result: %s", $msg, var_export($result, true)), $result ? "info" : "error");
}
}
}
}

View File

@@ -36,13 +36,23 @@ use Illuminate\Support\Str;
use Nexus\Database\NexusDB;
use Nexus\Imdb\Imdb;
use Rhilip\Bencode\Bencode;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class TorrentRepository extends BaseRepository
{
const BOUGHT_USER_CACHE_KEY_PREFIX = "torrent_purchasers:";
const BOUGHT_USER_CACHE_KEY_PREFIX = "torrent_purchasers";
const BUY_FAIL_CACHE_KEY_PREFIX = "torrent_purchase_fails";
const PIECES_HASH_CACHE_KEY = "torrent_pieces_hash";
const BUY_STATUS_SUCCESS = 0;
const BUY_STATUS_NOT_YET = -1;
const BUY_STATUS_UNKNOWN = -2;
/**
* fetch torrent list
*
@@ -334,13 +344,23 @@ class TorrentRepository extends BaseRepository
public function encryptDownHash($id, $user): string
{
$key = $this->getEncryptDownHashKey($user);
return (new Hashids($key))->encode($id);
$payload = [
'id' => $id,
'exp' => time() + 3600
];
return JWT::encode($payload, $key, 'HS256');
}
public function decryptDownHash($downHash, $user)
{
$key = $this->getEncryptDownHashKey($user);
return (new Hashids($key))->decode($downHash);
try {
$decoded = JWT::decode($downHash, new Key($key, 'HS256'));
return [$decoded->id];
} catch (\Exception $e) {
do_log("Invalid down hash: $downHash, " . $e->getMessage(), "error");
return '';
}
}
private function getEncryptDownHashKey($user)
@@ -752,15 +772,94 @@ HTML;
return $total;
}
public function addBoughtUserToCache($torrentId, $uid)
/**
* 购买成功缓存,保存为 hash一个种子一个 hash永久有效
* @param $uid
* @param $torrentId
* @return void
* @throws \RedisException
*/
public function addBuySuccessCache($uid, $torrentId): void
{
NexusDB::redis()->hSet($this->getBoughtUserCacheKey($torrentId), $uid, 1);
}
private function getBoughtUserCacheKey($torrentId): string
public function hasBuySuccessCache($uid, $torrentId): bool
{
return self::BOUGHT_USER_CACHE_KEY_PREFIX . $torrentId;
return NexusDB::redis()->hGet($this->getBoughtUserCacheKey($torrentId), $uid) == 1;
}
/**
* 获取购买种子的缓存状态
*
* @param $uid
* @param $torrentId
* @return int
*/
public function getBuyStatus($uid, $torrentId): int
{
//查询是否已经购买
if ($this->hasBuySuccessCache($uid, $torrentId)) {
return self::BUY_STATUS_SUCCESS;
}
//是否购买失败过
$buyFailCount = $this->getBuyFailCache($uid, $torrentId);
if ($buyFailCount > 0) {
//根据失败次数,禁用下载权限并做提示等
return $buyFailCount;
}
//不是成功或失败,直接返回未知
return self::BUY_STATUS_UNKNOWN;
}
/**
* 添加购买失败缓存, 结果累加
* @param $uid
* @param $torrentId
* @return void
* @throws \RedisException
*/
public function addBuyFailCache($uid, $torrentId): void
{
$key = $this->getBuyFailCacheKey($uid, $torrentId);
$result = NexusDB::redis()->incr($key);
if ($result == 1) {
NexusDB::redis()->expire($key, 3600);
}
}
/**
* 获取失败缓存 ,结果是失败的次数
*
* @param $uid
* @param $torrentId
* @return int
* @throws \RedisException
*/
public function getBuyFailCache($uid, $torrentId): int
{
return intval(NexusDB::redis()->get($this->getBuyFailCacheKey($uid, $torrentId)));
}
/**
* 购买成功缓存 key
* @param $torrentId
* @return string
*/
public function getBoughtUserCacheKey($torrentId): string
{
return sprintf("%s:%s", self::BOUGHT_USER_CACHE_KEY_PREFIX, $torrentId);
}
/**
* 购买失败缓存 key
* @param int $userId
* @param int $torrentId
* @return string
*/
public function getBuyFailCacheKey(int $userId, int $torrentId): string
{
return sprintf("%s:%s:%s", self::BUY_FAIL_CACHE_KEY_PREFIX, $userId, $torrentId);
}
public function addPiecesHashCache(int $torrentId, string $piecesHash): bool|int|\Redis

View File

@@ -132,7 +132,8 @@ class UserRepository extends BaseRepository
'stylesheet' => $setting['defstylesheet'],
'added' => now()->toDateTimeString(),
'status' => User::STATUS_CONFIRMED,
'class' => $class
'class' => $class,
'passkey' => md5($username.date("Y-m-d H:i:s").$passhash)
];
$user = new User($data);
if (!empty($params['id'])) {
@@ -143,7 +144,7 @@ class UserRepository extends BaseRepository
$user->id = $params['id'];
}
$user->save();
fire_event("user_created", $user);
return $user;
}

85
app/Utils/MsgAlert.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
namespace App\Utils;
use Nexus\Database\NexusDB;
final class MsgAlert {
private static ?self $instance = null;
private static array $alerts = [];
private string $redisKeyPrefix = "nexus_alerts";
private function __construct()
{
$redis = NexusDB::redis();
$result = $redis->lRange($this->getListKey(), 0, 10);
if (!empty($result)) {
$nowTimestamp = time();
$valid = [];
foreach ($result as $item) {
$arr = json_decode($item, true);
if (is_array($arr) && $arr['deadline'] > $nowTimestamp) {
$valid[$arr['name']] = $arr;
} else {
$redis->lRem($this->getListKey(), $item, 0);
}
}
self::$alerts = $valid;
}
}
private function __clone()
{
}
public static function getInstance(): MsgAlert
{
if (isset(self::$instance)) {
return self::$instance;
}
return self::$instance = new self;
}
public function add(string $name, int $deadline, string $text, string $url = "", string $color = "red"): void
{
if (!isset(self::$alerts[$name])) {
$params = compact('name', 'deadline', 'text', 'url', 'color');
self::$alerts[$name] = $params;
NexusDB::redis()->rPush($this->getListKey(), json_encode($params));
}
}
private function getListKey(): string
{
return sprintf("%s:%s", $this->redisKeyPrefix, get_user_id());
}
public static function render(): void
{
$nowTimestamp = time();
foreach (self::$alerts as $item) {
if ($item['deadline'] > $nowTimestamp) {
msgalert($item['url'] ?: '', $item['text'], $item['color'] ?: 'red');
}
}
}
public function remove($name): void
{
foreach (self::$alerts as $item) {
if ($item['name'] = $name) {
unset(self::$alerts[$name]);
NexusDB::redis()->lRem($this->getListKey(), json_encode($item));
}
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Utils;
use App\Jobs\BuyTorrent;
use http\Exception\InvalidArgumentException;
use Illuminate\Support\Facades\Queue;
use Nexus\Database\NexusDB;
use Nexus\Database\NexusLock;
final class ThirdPartyJob {
private static string $queueKey = "nexus_third_party_job";
private static int $size = 20;
const JOB_BUY_TORRENT = "buyTorrent";
public function __invoke(): void
{
$lockName = convertNamespaceToSnake(__METHOD__);
$lock = new NexusLock($lockName, 600);
if (!$lock->get()) {
do_log("can not get lock: $lockName, return ...");
return;
}
$list = NexusDB::redis()->lRange(self::$queueKey, 0, self::$size);
$successCount = 0;
foreach ($list as $item) {
$data = json_decode($item, true);
if (!empty($data['name'])) {
$successCount++;
match ($data['name']) {
self::JOB_BUY_TORRENT => self::enqueueJobBuyTorrent($data),
default => throw new InvalidArgumentException("invalid name: {$data['name']}")
};
} else {
do_log(sprintf("%s no name, skip", $item), "error");
}
NexusDB::redis()->lRem(self::$queueKey, $item);
}
do_log(sprintf("success dispatch %s jobs", $successCount));
$lock->release();
}
public static function addBuyTorrent(int $userId, int $torrentId): void
{
$key = sprintf("%s:%s_%s", self::$queueKey, $userId, $torrentId);
if (NexusDB::redis()->set($key, now()->toDateTimeString(), ['nx', 'ex' => 3600])) {
$value = [
'name' => self::JOB_BUY_TORRENT,
'userId' => $userId,
'torrentId' => $torrentId,
];
NexusDB::redis()->rPush(self::$queueKey, json_encode($value));
do_log("success addBuyTorrent: $key", "debug");
} else {
do_log("no need to addBuyTorrent: $key", "debug");
}
}
private static function enqueueJobBuyTorrent(array $params): void
{
if (!empty($params['userId']) && !empty($params['torrentId'])) {
$job = new BuyTorrent($params['userId'], $params['torrentId']);
Queue::push($job);
} else {
do_log("no userId or torrentId: " . json_encode($params), "error");
}
}
}