Use Beijing business date for subscription delivery paths
This commit is contained in:
parent
d8aa76e7ad
commit
0a1698b69d
@ -8,6 +8,7 @@
|
|||||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
|
|
||||||
class FreenodeController extends Controller
|
class FreenodeController extends Controller
|
||||||
{
|
{
|
||||||
@ -22,7 +23,8 @@ public function filePut(Request $oRequest)
|
|||||||
$sSubSiteCodeShort = $aData["site_code_short"];
|
$sSubSiteCodeShort = $aData["site_code_short"];
|
||||||
$aRaw = $aData["raw"];
|
$aRaw = $aData["raw"];
|
||||||
|
|
||||||
$sDate = date('Ymd');
|
// Subscription links use Beijing's calendar day, independently of app.timezone.
|
||||||
|
$sDate = CarbonImmutable::now('Asia/Shanghai')->format('Ymd');
|
||||||
$sSuffix = "nodes";
|
$sSuffix = "nodes";
|
||||||
$sRawKey = $sDate . $sSuffix;
|
$sRawKey = $sDate . $sSuffix;
|
||||||
$sUniqueToken = hash_hmac('sha256', $sRawKey, 'wocaonide');
|
$sUniqueToken = hash_hmac('sha256', $sRawKey, 'wocaonide');
|
||||||
@ -47,7 +49,7 @@ public function filePut(Request $oRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sFileSavePath = $sSubSiteCodeShort."/".$sShortToken;
|
$sFileSavePath = $sSubSiteCodeShort."/".$sShortToken;
|
||||||
$sFileSaveName = date("Ymd")."-".$sClient.$sExtension;
|
$sFileSaveName = $sDate."-".$sClient.$sExtension;
|
||||||
$sFilePathFull = $sFileSavePath."/".$sFileSaveName;
|
$sFilePathFull = $sFileSavePath."/".$sFileSaveName;
|
||||||
|
|
||||||
if (!File::isDirectory($sFileSavePath)) {
|
if (!File::isDirectory($sFileSavePath)) {
|
||||||
|
|||||||
21
docs/maintenance/FREENODE.md
Normal file
21
docs/maintenance/FREENODE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 免费节点订阅接收
|
||||||
|
|
||||||
|
Nodehub只负责接收和托管产物;上游生成、内核校验和站点分发见[master01维护正文](/Users/hellcat/Documents/GemCodes/dd.loc/docs/maintenance/FREENODE.md)。项目身份和访问规则见[GemDocs项目档案](/Users/hellcat/Documents/GemDocs/AI版/10-项目/nodehub.md)。
|
||||||
|
|
||||||
|
## 订阅日期契约
|
||||||
|
|
||||||
|
`POST /api/freenode/file/put`由`Api/v1/FreenodeController::filePut()`接收,参数仍为`site_code_short`和各格式`raw`,接口及链接算法不变。
|
||||||
|
|
||||||
|
接收时通过`CarbonImmutable::now('Asia/Shanghai')`确定一次业务日期,目录令牌和所有格式文件名均使用同一日期。应用默认时区仍为UTC,不改整个应用的日期、任务或日志。与master01按北京时间生成的当天链接保持一致;当天首次正常分发前不保证新日期文件已存在。
|
||||||
|
|
||||||
|
旧实现用UTC的`date()`,北京时间00:15、06:15两轮会写到前一天目录,直到12:15一轮才产生当天文件。2026-10-21已确认该问题;本次改动仅修复订阅接收日期,不移动历史文件、不复制旧节点伪造未来批次。
|
||||||
|
|
||||||
|
## 回归验证
|
||||||
|
|
||||||
|
`php vendor/bin/phpunit tests/Feature/FreenodeDateTest.php --do-not-cache-result`在隔离临时目录运行真实接收控制器,模拟北京时间零点前后以及00:15、06:15、12:15同步时刻,核对三种格式的日期、目录、内容以及全局UTC时区保持不变。Telegram被替换为测试对象,不访问网络或真实公开目录。
|
||||||
|
|
||||||
|
2026-10-21本机PHP 8.4.1通过5项、35断言。macOS本机使用真实PHP二进制`/usr/local/Cellar/php/8.4.1_1/bin/php`运行,避免Herd包装器导致PHPUnit隔离子进程启动失败。生产部署及验收结果另按实际记录。
|
||||||
|
|
||||||
|
## 发布与恢复
|
||||||
|
|
||||||
|
通过Git提交、服务端推送仓库,再将线上工作树更新到明确提交。提交只包括控制器、上述测试和本文;保留已有权限及未跟踪文件。部署前在服务器保存旧提交、控制器原字节/权限、工作区状态和当日订阅哈希;回退只还原本次控制器并记录代码版本,不reset整个有并行改动的工作树,不清除公开订阅目录。数据库、环境配置和系统时间不需要改动。
|
||||||
73
tests/Feature/FreenodeDateTest.php
Normal file
73
tests/Feature/FreenodeDateTest.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\v1\FreenodeController;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Mockery;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||||
|
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class FreenodeDateTest extends TestCase
|
||||||
|
{
|
||||||
|
public static function receivingTimes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'before Beijing midnight' => ['2026-10-21T15:59:59Z', '20261021'],
|
||||||
|
'Beijing midnight' => ['2026-10-21T16:00:00Z', '20261022'],
|
||||||
|
'midnight scheduled delivery' => ['2026-10-21T16:15:00Z', '20261022'],
|
||||||
|
'morning scheduled delivery' => ['2026-10-21T22:15:00Z', '20261022'],
|
||||||
|
'noon scheduled delivery' => ['2026-10-22T04:15:00Z', '20261022'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('receivingTimes')]
|
||||||
|
#[RunInSeparateProcess]
|
||||||
|
#[PreserveGlobalState(false)]
|
||||||
|
public function testReceiverWritesAllFormatsUnderBeijingDate(string $instant, string $date): void
|
||||||
|
{
|
||||||
|
$previousDirectory = getcwd();
|
||||||
|
$previousTimezone = date_default_timezone_get();
|
||||||
|
$directory = sys_get_temp_dir().'/nodehub-date-test-'.bin2hex(random_bytes(6));
|
||||||
|
mkdir($directory, 0700);
|
||||||
|
|
||||||
|
// Keep the real controller and filesystem, but never send Telegram in a test.
|
||||||
|
$telegram = Mockery::mock('alias:App\Services\TomTool\Telegram\Slave');
|
||||||
|
$telegram->shouldReceive('log')->once()->andReturnSelf();
|
||||||
|
$telegram->shouldReceive('enableDetail')->with(false)->once()->andReturnSelf();
|
||||||
|
$telegram->shouldReceive('send')->once();
|
||||||
|
|
||||||
|
try {
|
||||||
|
config(['app.timezone' => 'UTC', 'app.is_loc' => true]);
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
CarbonImmutable::setTestNow(new CarbonImmutable($instant));
|
||||||
|
chdir($directory);
|
||||||
|
$raw = ['v2ray' => 'fixture-uri', 'clash' => 'fixture-clash', 'singbox' => 'fixture-singbox'];
|
||||||
|
$request = Request::create('/api/freenode/file/put', 'POST', ['site_code_short' => 'cfn', 'raw' => $raw]);
|
||||||
|
$response = (new FreenodeController())->filePut($request);
|
||||||
|
|
||||||
|
self::assertTrue($response->getData(true)['isDone']);
|
||||||
|
$token = substr(hash_hmac('sha256', $date.'nodes', 'wocaonide'), 0, 16);
|
||||||
|
foreach ($raw as $format => $content) {
|
||||||
|
$extension = $format === 'clash' ? '.yaml' : '.txt';
|
||||||
|
self::assertSame($content, file_get_contents($directory.'/cfn/'.$token.'/'.$date.'-'.$format.$extension));
|
||||||
|
}
|
||||||
|
self::assertCount(3, glob($directory.'/cfn/*/*'));
|
||||||
|
self::assertSame('UTC', date_default_timezone_get());
|
||||||
|
self::assertSame('UTC', config('app.timezone'));
|
||||||
|
} finally {
|
||||||
|
CarbonImmutable::setTestNow();
|
||||||
|
date_default_timezone_set($previousTimezone);
|
||||||
|
chdir($previousDirectory);
|
||||||
|
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
|
||||||
|
}
|
||||||
|
rmdir($directory);
|
||||||
|
Mockery::close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user