fn_b/app/Services/OneYearArticleLikes.php

60 lines
2.1 KiB
PHP

<?php
namespace App\Services;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;
final class OneYearArticleLikes
{
/** Each article owns its progress; retries and overlapping workers cannot pay twice. */
public function sync(): int
{
$total = 0;
DB::table('articles')->where('iStatus', 1)
->whereColumn('iYearLikeTarget', '>', 'iYearLikeAdded')
->select('id')->chunkById(100, function ($articles) use (&$total) {
foreach ($articles as $article) {
$total += $this->syncArticle($article->id);
}
});
return $total;
}
public function syncArticle(int $id): int
{
return DB::transaction(function () use ($id) {
$article = DB::table('articles')->where('id', $id)->lockForUpdate()->first();
if (! $article || (int) $article->iStatus !== 1
|| $article->iYearLikeTarget <= $article->iYearLikeAdded) {
return 0;
}
$now = CarbonImmutable::now();
if (! $article->sYearLikeStartedAt) {
DB::table('articles')->where('id', $id)->update(['sYearLikeStartedAt' => $now]);
return 0;
}
$start = CarbonImmutable::parse($article->sYearLikeStartedAt);
$end = $start->addYearNoOverflow();
$duration = $end->getTimestamp() - $start->getTimestamp();
$elapsed = max(0, min($duration, $now->getTimestamp() - $start->getTimestamp()));
$due = intdiv((int) $article->iYearLikeTarget * $elapsed, $duration);
$add = max(0, $due - (int) $article->iYearLikeAdded);
if ($add > 0) {
// Query-builder arithmetic preserves real/other automatic likes and content timestamps.
DB::table('articles')->where('id', $id)->update([
'iLikeCount' => DB::raw('iLikeCount + '.$add),
'iYearLikeAdded' => DB::raw('iYearLikeAdded + '.$add),
'sLastLikedAt' => $now,
]);
}
return $add;
}, 3);
}
}