0) { // 随机把剩下的点赞塞进后 6 天的坑位里 for ($i = 0; $i < $iRemaining; $i++) { $iRandomDay = mt_rand(1, 6); $aPlan[$iRandomDay]++; } } // 返回匈牙利命名法下的 json 字符串 return json_encode($aPlan); } /** * 第二步:执行点赞任务 (建议每小时跑一次) * 只负责把今天该加的赞加进去 */ public function executeLikeTask(Article $oArticle): void { if (empty($oArticle->sLikePlan)) return; $aPlan = json_decode($oArticle->sLikePlan, true); $iDaysPassed = $oArticle->created_at->diffInDays(now()); // 如果已经超过7天,或者计划里没这一天,直接撤退 if ($iDaysPassed > 6 || !isset($aPlan[$iDaysPassed])) { return; } $iShouldHaveAdded = $aPlan[$iDaysPassed]; // 重点:为了不让点赞在凌晨一瞬间加上去,我们可以配合小时数 // 比如现在是 14 点,我们应该加到今天总量的 14/24 $iHour = (int) date('H'); $iCurrentTarget = (int) floor($iShouldHaveAdded * ($iHour / 24)); // 这里需要一个逻辑记录今天已经加了多少,或者直接根据小时计算增量 // 简单处理:我们每天分 24 次把 $iShouldHaveAdded 加完 // 为保简洁,这里展示核心逻辑更新: if ($iShouldHaveAdded > 0) { // 算出每小时平均加几个,剩下的随机掉落 $iAddToday = $this->calculateHourlyIncrement($iShouldHaveAdded, $iHour); // 这里更新 i7like 字段 // 注意:i7like 应该是这篇文章总共通过这个逻辑加了多少赞 // ... 具体的数据库 Update 逻辑 } } /** * 365天自动点赞:高标准随机触发 */ public function applyYearlyGrowth(Article $oArticle): void { $iYearlyTarget = 10; // 哪怕一年只要 10 个 // 计算今天中奖的概率:10 / 365 // 我们放大一万倍来摇号 $iProbability = (int)(($iYearlyTarget / 365) * 10000); $iMagicNumber = mt_rand(1, 10000); if ($iMagicNumber <= $iProbability) { // 中奖了,加 1 个赞 $oArticle->increment('i365like'); } } public function like(int $iArticleId, string $sIp, string $sFingerprint): array { // 1. 检查是否已经点过 $bExists = ArticleLikes::where('iArticleId', $iArticleId) ->where(function ($q) use ($sIp, $sFingerprint) { $q->where('sIp', $sIp) ->orWhere('sFingerprint', $sFingerprint); })->exists(); if ($bExists) { return ['status' => false, 'code' => 200, 'msg' => '感谢支持,点过赞了。']; } // 2. 事务扣杀 try { // 定义一个变量来接收最新的点赞数 $iNewLikeCount = 0; \DB::transaction(function () use ($iArticleId, $sIp, $sFingerprint, &$iNewLikeCount) { ArticleLikes::create([ 'iArticleId' => $iArticleId, 'sIp' => $sIp, 'sFingerprint' => $sFingerprint ]); // 执行增加并直接获取最新的模型实例 $oArticle = Articles::where('id', $iArticleId); $oArticle->increment('iLikeCount', 1, [ 'sLastLikedAt' => now(), // 这里的 now() 是 Laravel 提供的 Carbon 实例 ]); $oArticle->sLastLikedAt = time(); // $oArticle->save(); // 不对吗 // 拿到自增后的最新数字 $iNewLikeCount = $oArticle->value('iLikeCount'); }); CacheService::updateOrderLikes(); return [ 'status' => true, 'code' => 200, 'msg' => '感谢支持!', 'iLikeCount' => $iNewLikeCount // 药水哥绝杀:把热腾腾的数字发回去 ]; } catch (\Exception $e) { // tt($e->getMessage()); \Log::error("点赞失败: " . $e->getMessage()); return ['status' => false, 'code' => 500, 'msg' => '系统打了个盹,稍后再试。']; } } public function search(string $sKeywords, string $sResultType) { if (empty($sKeywords)) { return []; } $sKeywords = strip_tags(trim($sKeywords)); $oArticles = Articles::search($sKeywords) ->options([ 'attributesToHighlight' => ['sTitle', 'sContent', 'sCategoryName'], // 1. 把 attributesToSnippet 改成 attributesToCrop 'attributesToCrop' => ['sContent'], // 2. 设置裁剪后的长度(单位是字符,按需调整) 'cropLength' => 100, 'highlightPreTag' => '', 'highlightPostTag' => '', ]) ->orderByDesc('iIsTop') ->paginate(config('app.page_limit')); $oArticles->getCollection()->transform(function ($oArticle) { $aMeta = $oArticle->scoutMetadata(); $aFormatted = $aMeta['_formatted'] ?? []; if (!empty($aFormatted)) { $oArticle->sTitle = $aFormatted['sTitle'] ?? $oArticle->sTitle; $oArticle->sContent = $aFormatted['sContent'] ?? $oArticle->sContent; if (isset($aFormatted['sCategoryName'])) { $oArticle->sCategoryName = $aFormatted['sCategoryName']; $oArticle->setRelation('oCategory', (object)[ 'sName' => $aFormatted['sCategoryName'], 'sSlug' => $oArticle->sCategorySlug ?? '' ]); } } return $oArticle; }); if ($sResultType === "ajax") { $sHtml = ''; foreach ($oArticles as $oArticle) { $sHtml .= view('components.article-item', ['oArticle' => $oArticle])->render(); } $xResult = $sHtml; } else { $xResult = [ 'oArticles' => $oArticles, ]; } return $xResult; } public function getTagArticles(string $sSlug, string $sResultType) { $oTags = Tags::where('sSlug', $sSlug)->firstOrFail(); $oArticles = $oTags->oArticles() ->where('iStatus', 1) // 只拿已发布的 ->with(['oCategory']) // 预加载关联,拒绝 N+1 ->orderByDesc('iIsTop') // 置顶优先 ->orderByDesc('created_at') // 最新优先 ->paginate(config('app.page_limit')); // 必须分页,否则数据多了会爆内存 if ($sResultType === "ajax") { $sHtml = ''; foreach ($oArticles as $oArticle) { $sHtml .= view('components.article-item', ['oArticle' => $oArticle])->render(); } $xResult = $sHtml; } else { $xResult = [ 'oArticles' => $oArticles, ]; } return $xResult; } public function getCategoryArticles(string $sSlug, string $sResultType) { $oCategory = Categories::where('sSlug', $sSlug)->firstOrFail(); if ($oCategory->iShowChildren) { $aCategoryIds = Categories::where('iParentId', $oCategory->id) ->pluck('id') ->push($oCategory->id) ->toArray(); } else { $aCategoryIds = [$oCategory->id]; } $oArticles = Articles::whereIn('iCategoryId', $aCategoryIds) ->with(['oUser', 'oCategory']) ->orderBy('iIsTop', 'desc') ->orderBy('created_at', 'desc') ->simplePaginate(config('app.page_limit')); if ($sResultType === "ajax") { $sHtml = ''; foreach ($oArticles as $oArticle) { $sHtml .= view('components.article-item', ['oArticle' => $oArticle])->render(); } $aResult = $sHtml; } else { $aResult = [ 'oCategory' => $oCategory, 'oArticles' => $oArticles, ]; } return $aResult; } public function getArticle(string $sArticleSlug, string $sIp, string $sFingerprint) { $oArticle = Articles::where('sSlug', $sArticleSlug) ->where('iStatus', 1) ->with(['oCategory.oParent', 'oTags']) ->firstOrFail(); $oArticle->bIsLiked = ArticleLikes::where('iArticleId', $oArticle->id) ->where(function($q) use ($sIp, $sFingerprint) { $q->where('sIp', $sIp) ->orWhere('sFingerprint', $sFingerprint); })->exists(); // return $oArticle; return [ 'oArticle' => $oArticle ]; } }