60 lines
2.1 KiB
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);
|
|
}
|
|
}
|