From cc9d1444a309777a85d12c4f0ea4391367ec3b33 Mon Sep 17 00:00:00 2001 From: hellcat Date: Thu, 22 Oct 2026 13:35:03 +0800 Subject: [PATCH] Read article node counts from the published v2 release --- app/Services/CountrySpeedService.php | 31 +++++++++++--- docs/maintenance/FREENODE.md | 8 ++++ tests/Unit/Freenode/ArticleCountTest.php | 54 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/Freenode/ArticleCountTest.php diff --git a/app/Services/CountrySpeedService.php b/app/Services/CountrySpeedService.php index d686b7c4..f3201658 100755 --- a/app/Services/CountrySpeedService.php +++ b/app/Services/CountrySpeedService.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Services; use App\Models\FreenodePool; +use App\Services\Freenode\Pipeline; +use RuntimeException; final class CountrySpeedService { @@ -60,16 +62,35 @@ final class CountrySpeedService //// 速度 $iNodeSpeed = rand(config("freenode.speed_min"), config("freenode.speed_max")); - $oFnp = FreenodePool::whereDate('date', date("Y-m-d"))->first(); -// var_dump($oFnp->id);exit; - $aFnpContent = json_decode($oFnp->content ?? '[]', true); // 有问题吗 - $iNodeCount = count($aFnpContent["v2ray"] ?? []) ?? rand(30, 98); -// $iNodeCount = rand(config("freenode.count_min"), config("freenode.count_max")); + $iNodeCount = self::nodeCount(); $sContent = "本次更新共".$iNodeCount."个可用节点,最高速度".$iNodeSpeed."M/S。 覆盖".$sNodeZone."等多个区域。"; return $sContent; } + + public static function nodeCount(): int + { + if (Pipeline::enabled()) { + $store = Pipeline::store(); + return $store->locked(function () use ($store): int { + $release = $store->current(); + if (!$release || ($release['validated'] ?? false) !== true + || ($release['date'] ?? '') !== date('Y-m-d') + || time() - ($release['created_at'] ?? 0) > 86400) { + throw new RuntimeException('no_today_published_release_for_article'); + } + $count = $release['counts']['v2ray'] ?? null; + if (!is_int($count) || $count < 1) { + throw new RuntimeException('invalid_published_node_count'); + } + return $count; + }, false); + } + $pool = FreenodePool::whereDate('date', date('Y-m-d'))->first(); + $content = json_decode($pool->content ?? '[]', true); + return count($content['v2ray'] ?? []); + } } diff --git a/docs/maintenance/FREENODE.md b/docs/maintenance/FREENODE.md index f630e774..554d7160 100644 --- a/docs/maintenance/FREENODE.md +++ b/docs/maintenance/FREENODE.md @@ -229,3 +229,11 @@ Nodehub仍运行`507cbf7`,已在今天00:15正常生成20261022四站10份文 回退:先`systemctl disable --now gitea-archive-cache-cleaner.timer`,停止service,再按部署备份恢复/移除本次独立脚本和两个unit并`systemctl daemon-reload`;无需回退免费节点业务代码。已删除缓存可按需重新生成。部署前检查生产HEAD、并行状态及旧文件,保留安装前状态和验收记录到服务器私有备份目录;本地5项测试覆盖过期/容量淘汰、打开文件/新文件/未知文件保护、符号链接、预览与重复执行。 2026-10-22部署验收:功能提交`48dc3ba6529226fae866d440625080632c1d88a0`已推送并部署;生产5项测试通过,systemd unit校验通过。timer已enabled/active,13:04:53首轮实际定时触发成功(退出0),清理53份过期缓存、共1,000,799,388字节,缓存由约956MiB降为0;复跑预览无额外候选。根盘可用3,323,936,768字节,Gitea仍active;当前订阅manifest逐字节不变,原并行Git状态保持。安装前状态、Git增量、预览与验收回执保存在`/var/backups/master01-gitea-cache-cleaner-20261022/`。已完成服务器侧验收,未做线上页面验收;尚不将一次成功视为长期磁盘容量保障。 + +### V2文章节点数量仍读旧池的问题 + +2026-10-22用户指出cfn首页“本次更新共0个可用节点”。服务器侧HTTP读取确认首页19/20/21日三篇均显示0,cfn数据库对应slug为`fn20261019`(id356)、`fn20261020`(id358)、`fn20261021`(id360),22日文章尚不存在。master01旧`freenode_pool`最新记录为18日;V2 prepare已经不再写该表,但`CountrySpeedService::fn_b()`仍按今天日期读旧池,空值经count变0,`?? rand()`不可能生效。这是重构遗漏的文章统计调用方,与订阅文件为空无关。 + +修正`CountrySpeedService::nodeCount()`:V2从当天current已验证批次的v2ray统计取值,不取pending、不回退旧池,不存在当日有效发布或数量无效则抛异常阻止生成0节点文章。legacy沿用原池逻辑;其他随机文案保持原样,本次未实现节点联网可用性/测速。测试覆盖published优先、pending拒绝、过期日期和零值拒绝;整套23项53断言通过。 + +现存cfn文案仅计划按对应日期已发布产物修正19/20/21日三个0计数,备份正文并事务更新`sTitleSub`/`sContent`内的数字,不重发文章、不改订阅/图片/点赞/标签、不触发Telegram或IndexNow;不把今天2592条套到历史日期。部署和实际修正结果待验收。 diff --git a/tests/Unit/Freenode/ArticleCountTest.php b/tests/Unit/Freenode/ArticleCountTest.php new file mode 100644 index 00000000..3b30a6a3 --- /dev/null +++ b/tests/Unit/Freenode/ArticleCountTest.php @@ -0,0 +1,54 @@ +dir = sys_get_temp_dir().'/article-count-'.bin2hex(random_bytes(6)); + mkdir($this->dir.'/app/freenode-v2', 0700, true); + $app = new Application(dirname(__DIR__, 3)); + $app->useStoragePath($this->dir); + $app->instance('config', new Repository(['freenode' => ['engine' => 'v2']])); + } + protected function tearDown(): void + { + foreach (glob($this->dir.'/app/freenode-v2/*') as $p) unlink($p); + rmdir($this->dir.'/app/freenode-v2'); rmdir($this->dir.'/app'); rmdir($this->dir); + } + private function write(array $override = [], string $name = 'current'): void + { + $m = array_replace(['id'=>'published', 'validated'=>true, 'date'=>date('Y-m-d'), + 'created_at'=>time(), 'counts'=>['v2ray'=>2592]], $override); + file_put_contents($this->dir.'/app/freenode-v2/'.$name.'.json', json_encode($m)); + } + public function testV2ReadsPublishedCountWithoutLegacyDatabase(): void + { + $this->write(); $this->write(['counts'=>['v2ray'=>9999]], 'pending'); + self::assertSame(2592, CountrySpeedService::nodeCount()); + } + public function testPendingAloneCannotProduceAnArticleCount(): void + { + $this->write([], 'pending'); + $this->expectExceptionMessage('no_today_published_release_for_article'); + CountrySpeedService::nodeCount(); + } + public function testPreviousDayIsRejectedInsteadOfPublishingZero(): void + { + $this->write(['date'=>date('Y-m-d', time()-86400)]); + $this->expectExceptionMessage('no_today_published_release_for_article'); + CountrySpeedService::nodeCount(); + } + public function testZeroCountIsRejected(): void + { + $this->write(['counts'=>['v2ray'=>0]]); + $this->expectExceptionMessage('invalid_published_node_count'); + CountrySpeedService::nodeCount(); + } +}