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 $keywords, string $resultType = 'view') { $term = trim(strip_tags($keywords)); abort_if($term === '' || mb_strlen($term) > 100, 404); // Live DB authorization avoids stale external-index documents and cross-site results. $literal = '%'.str_replace(['!','%','_'], ['!!','!%','!_'], $term).'%'; $articles = Articles::publicVisible()->with('oCategory.oParent') ->where(fn($q) => $q->whereRaw("sTitle LIKE ? ESCAPE '!'", [$literal])->orWhereRaw("sContent LIKE ? ESCAPE '!'", [$literal])) ->orderByDesc('iIsTop')->orderByDesc('created_at')->orderByDesc('id')->paginate(config('seo.page_size')); return ['oArticles'=>$articles, 'sKeywords'=>$term]; } public function getTagArticles(string $slug, string $resultType = 'view') { $tag = Tags::where('sSlug', $slug)->firstOrFail(); return ['oTag'=>$tag, 'oArticles'=>$tag->oArticles()->publicVisible()->with('oCategory.oParent') ->orderByDesc('iIsTop')->orderByDesc('created_at')->orderByDesc('id')->paginate(config('seo.page_size'))]; } public function getLatestFreeNodeSlug(): ?string { foreach (Articles::publicVisible()->whereHas('oCategory', fn($q)=>$q->where('sSlug','freenode')) ->orderByDesc('created_at')->orderByDesc('id')->cursor() as $article) { if (ResourcePublication::usable($article)) return $article->sSlug; } return null; } public function getCategoryArticles(string $slug, string $resultType = 'view') { $category = Categories::where('sSlug', $slug)->where('iStatus',1)->with('oParent')->firstOrFail(); abort_if($category->iParentId && (!$category->oParent || !$category->oParent->iStatus),404); $ids = [$category->id]; if ($category->iShowChildren) $ids = array_merge($ids, Categories::where('iParentId',$category->id)->where('iStatus',1)->pluck('id')->all()); $articles = Articles::publicVisible()->whereIn('iCategoryId',$ids)->with('oCategory.oParent') ->orderByDesc('iIsTop')->orderByDesc('created_at')->orderByDesc('id')->paginate(config('seo.page_size')); return ['oCategory'=>$category,'oArticles'=>$articles,'iArticleCount'=>$articles->total()]; } public function getArticle(string $slug, string $ip, string $fingerprint) { $article = Articles::publicVisible()->where('sSlug',$slug)->with(['oCategory.oParent','oTags'])->firstOrFail(); $article->bIsLiked = ArticleLikes::where('iArticleId',$article->id) ->where(fn($q)=>$q->where('sIp',$ip)->orWhere('sFingerprint',$fingerprint))->exists(); return ['oArticle'=>$article]; } }