diff --git a/app/routes.php b/app/routes.php index 4bc466218..1246f947c 100755 --- a/app/routes.php +++ b/app/routes.php @@ -9,6 +9,7 @@ use App\Middleware\GemboxToken; use App\Middleware\TgHookAdmin; use App\Middleware\TgAdmin; use App\Middleware\User; +use App\Middleware\VerifyNasaToken; use Slim\Routing\RouteCollectorProxy; return static function (Slim\App $app): void { @@ -441,4 +442,52 @@ return static function (Slim\App $app): void { // $group->post('/user/online_ip', App\Controllers\Api\NodeApiV1Controller::class . ':addUserOnlineIp'); // $group->post('/user/detect_log', App\Controllers\Api\NodeApiV1Controller::class . ':addUserDetectLog'); //})->add(new NodeApi()); + + $app->group('/api/nasa/v1', function (RouteCollectorProxy $group): void { + $group->get('/node', App\Controllers\Api\Nasa\V1\NodeController::class . ':index'); + $group->get('/node/detail/{id}', App\Controllers\Api\Nasa\V1\NodeController::class . ':detail'); + $group->put('/node/detail/{id}', App\Controllers\Api\Nasa\V1\NodeController::class . ':detailSave'); + $group->delete('/node/detail/{id}', App\Controllers\Api\Nasa\V1\NodeController::class . ':delete'); + $group->get('/node/add', App\Controllers\Api\Nasa\V1\NodeController::class . ':add'); + $group->put('/node/add', App\Controllers\Api\Nasa\V1\NodeController::class . ':addSave'); + $group->get('/node/clone/{id}', App\Controllers\Api\Nasa\V1\NodeController::class . ':clone'); + $group->put('/node/clone/', App\Controllers\Api\Nasa\V1\NodeController::class . ':cloneSave'); + $group->get('/node/hit/{id}/{field}', App\Controllers\Api\Nasa\V1\NodeController::class . ':hit'); + })->add(new VerifyNasaToken()); + + +// Route::prefix('setting')->group(function() { +// Route::get('nav', [NasaV1SettingNavController::class, 'index']); +// Route::get('nav/detail/{id}', [NasaV1SettingNavController::class, 'detail']); +// Route::put('nav/detail/{id}', [NasaV1SettingNavController::class, 'detailSave']); +// Route::delete('nav/delete/{id}', [NasaV1SettingNavController::class, 'delete']); +// Route::get('nav/add', [NasaV1SettingNavController::class, 'add']); +// Route::put('nav/add', [NasaV1SettingNavController::class, 'addSave']); +// Route::get('nav/clone/{id}', [NasaV1SettingNavController::class, 'clone']); +// Route::put('nav/clone', [NasaV1SettingNavController::class, 'cloneSave']); +// Route::get('nav/hit/{id}/{field}', [NasaV1SettingNavController::class, 'hit']); +// }); + +// $app->group('/api/nasa/v1', function (RouteCollectorProxy $group): void { +// $group->get('/node', App\Controllers\Nasa\V1\NodeController::class . ':index'); +// })->add(new VerifyNasaToken()); + +// Route::prefix('nasa/v1')->group(function () { +// +// Route::prefix('node')->group(function() { +// Route::get('/', [App\Controllers\Nasa\V1\NodeController::class . ':index'); +// Route::get('/detail/{id}', [NasaV1SettingNavController::class, 'detail']); +// Route::put('/detail/{id}', [NasaV1SettingNavController::class, 'detailSave']); +// Route::delete('/delete/{id}', [NasaV1SettingNavController::class, 'delete']); +// Route::get('/add', [NasaV1SettingNavController::class, 'add']); +// Route::put('/add', [NasaV1SettingNavController::class, 'addSave']); +// Route::get('/clone/{id}', [NasaV1SettingNavController::class, 'clone']); +// Route::put('/clone', [NasaV1SettingNavController::class, 'cloneSave']); +// Route::get('/hit/{id}/{field}', [NasaV1SettingNavController::class, 'hit']); +// }); +// +// })->add(new VerifyNasaToken()); + + +// $app->get('/api/huodong/findbydate/{date}', App\Controllers\Nasa\V1\NodeController::class . ':index'); }; diff --git a/public/index.php b/public/index.php index 484b59f13..627629a0f 100755 --- a/public/index.php +++ b/public/index.php @@ -21,13 +21,26 @@ use GuzzleHttp\Psr7\ServerRequest; use Slim\Factory\AppFactory; use Slim\Http\Factory\DecoratedResponseFactory; -function tomd($d, $type = 0) +function t($x) { - echo "
";
- var_dump($d);
+ tomd($x);
+}
+
+function tt($x)
+{
+ tomd($x, 1);
+}
+
+function tomd($aData, $iType = 0) {
- if ($type) {
- exit;
+ if ($_ENV["is_loc"] == true) {
+ echo "";
+ var_dump($aData);
+
+ if ($iType == 1) {
+ http_response_code(500);
+ exit;
+ }
}
}
diff --git a/src/Controllers/Api/Nasa/V1/Base/ListController.php b/src/Controllers/Api/Nasa/V1/Base/ListController.php
new file mode 100755
index 000000000..dd14c91c1
--- /dev/null
+++ b/src/Controllers/Api/Nasa/V1/Base/ListController.php
@@ -0,0 +1,464 @@
+ [],
+ 'cc' => [],
+ 'ob' => [],
+ ];
+
+ protected $oModel;
+ protected string $sPageName = '';
+ protected string $sModelName = '';
+ protected array $aFilterFields = []; // 允许where的字段
+// protected array $aArticleFields = []; // 文章字段
+ protected array $aFieldTypes = [];
+ protected array $aLenthFields = []; // 限制长度的字段
+ protected array $aSelectFields = []; // 默认作为select的字段
+ protected array $aListFields = []; // list展示的字段和名字映射
+ protected array $aDateFields = []; // date字段
+ protected array $aJsonFields = []; // json字段
+ protected array $aValueStyle = []; // 特定文字渲染样式
+ protected int $iLimitMax = 100; // 默认limit限制
+ protected int $iLimit = 20; // 默认显示行数
+ protected array $aCcExtFields = []; // 自定义条件字段追加在listFields后
+ protected array $aCcFields = [];
+ protected int $iOrderByMax = 3;
+ protected array $aOrderBy = [
+ [
+ "sField" => "id",
+ "sSort" => "desc"
+ ]
+ ];
+
+ public function __construct()
+ {
+ if ($this->sModelName) {
+ $sFullNamespace = "App\Models\\{$this->sModelName}";
+ if (! class_exists($sFullNamespace)) {
+ throw new InvalidArgumentException("【系统架构错误】: 找不到领域模型 {$sFullNamespace}");
+ }
+
+ $this->oModel = new $sFullNamespace();
+ $this->aCcFields = array_merge($this->aListFields, $this->aCcExtFields);
+ }
+ }
+
+ public function index(Request $oRequest, Response $oResponse, $args)
+ {
+ if (! $this->oModel) {
+ return $oResponse->withJson(['code' => 500, 'msg' => '未绑定核心领域模型'], 500);
+ }
+
+ $oQuery = $this->oModel->newQuery();
+
+ $aParams = $oRequest->getParams();
+
+ foreach ($aParams as $k => $v) {
+
+ if (!empty($this->aFilterFields)) {
+ if (!in_array($k, $this->aFilterFields)) {
+ continue;
+ }
+ }
+
+ // w__name__eq=1
+ $aK = explode("__", $k);
+ $sType = $aK[0] ?? '';
+ $sField = $aK[1] ?? '';
+ $sOperator = $aK[2] ?? $this->sOperatorDefault;
+ $sSqlOperator = $this->mapOperator($sOperator);
+
+ if (!isset($this->aFilterParams[$sType])) {
+ continue;
+ }
+
+ if (!$sType || !$sField) {
+ continue;
+ }
+
+ $this->aFilterParams[$k] = $v;
+
+ if ($sType === 'ob') {
+ $this->aFilterParams[$sType][] = [
+ 'sField' => $sField,
+ 'sSort' => $v ?? $this->sSortDefault
+ ];
+ continue;
+ }
+
+ if ($sType === 'c') { // 预制条件,字典式
+ $this->aFilterParams[$sType][$sField] = [
+ 'sOperator' => $sOperator,
+ 'sValue' => $v
+ ];
+ }
+
+ if ($sType === 'cc') { // 自定义条件,集合式
+ $this->aFilterParams[$sType][] = [
+ 'sField' => $sField,
+ 'sOperator' => $sOperator,
+ 'sValue' => $v
+ ];
+ }
+
+ if (blank($v)) continue; // 空v可以back但不加入sql查询
+
+ $sSqlValue = $v;
+ if ($sSqlOperator === 'like') {
+ $sSqlValue = "%{$v}%";
+ }
+
+ if (in_array($sField, $this->aDateFields)) {
+ $oQuery->whereDate($sField, $sSqlOperator, $sSqlValue);
+ } else {
+ $oQuery->where($sField, $sSqlOperator, $sSqlValue);
+ }
+
+ }
+
+ $iLimit = (int) $oRequest->getParam('limit', $this->iLimit);
+ $iLimit = max(1, $iLimit);
+ $iLimit = min($this->iLimitMax, $iLimit);
+
+ if (!empty($this->aFilterParams['ob'])) {
+ $this->aOrderBy = $this->aFilterParams['ob'];
+ }
+
+ foreach ($this->aOrderBy as $aOrderBy) {
+ $oQuery->orderBy($aOrderBy['sField'], $aOrderBy['sSort']);
+ }
+
+ $oPaginatedData = $oQuery->paginate($iLimit, array_keys($this->aListFields));
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [
+ 'cList' => $oPaginatedData->items(),
+ 'aListField' => $this->getListFields(),
+ 'aFieldTypes' => $this->aFieldTypes,
+ 'aMap' => $this->oModel->getMapAll(),
+ 'aValueStyle' => $this->aValueStyle,
+ 'aSelectField' => $this->aSelectFields,
+ 'aFilterParam' => $this->aFilterParams,
+ 'aCcField' => $this->aCcFields,
+ 'sCcLast' => $this->getCcLast(),
+ 'sPageName' => $this->sPageName,
+ 'aPager' => [
+ 'aOrderBySchema'=> $this->getOrderBySchema(),
+ 'aOrderBy' => $this->getOrderBy(),
+ 'iTotal' => $oPaginatedData->total(),
+ 'iLimit' => min($this->iLimitMax, $iLimit),
+ 'iCurrentPage' => $oPaginatedData->currentPage(),
+ 'iLastPage' => $oPaginatedData->lastPage(),
+ 'iPerPage' => $oPaginatedData->perPage(),
+ 'iPrevPage' => max(1, $oPaginatedData->currentPage() - 1),
+ 'iNextPage' => min($oPaginatedData->lastPage(), $oPaginatedData->currentPage() + 1)
+ ]
+ ]
+ ], 200);
+
+ }
+
+ private function mapOperator($sOperator)
+ {
+ $sSqlOperator = match ($sOperator) {
+ 'like' => 'like',
+ 'eq' => '=',
+ 'ne' => '!=',
+ 'gt' => '>',
+ 'gte' => '>=',
+ 'lt' => '<',
+ 'lte' => '<=',
+ default => '',
+ };
+
+ return $sSqlOperator;
+ }
+
+ private function getOrderBySchema()
+ {
+ $iOrderByCount = count($this->aOrderBy);
+ $iDiff = $this->iOrderByMax - $iOrderByCount;
+ $a = $this->aOrderBy;
+ for ($i=0; $i<$iDiff; $i++) {
+ $a[] = [];
+ }
+ return $a;
+ }
+
+ private function getOrderBy()
+ {
+ return $this->aOrderBy;
+ }
+
+ public function getListFields()
+ {
+ $a = [];
+
+ foreach ($this->aListFields as $k => $v) {
+ if ($v) {
+ $a[$k] = $v;
+ } else {
+ $a[$k] = $k;
+ }
+ }
+
+ return $a;
+ }
+
+ private function getCcLast()
+ {
+ $aCcFieldsTmp = $this->aCcFields;
+
+ foreach ($this->aFilterParams['cc'] as $ACC) {
+ unset($aCcFieldsTmp[$ACC['sField']]);
+ }
+
+ $s = head(array_keys($aCcFieldsTmp));
+
+ return $s;
+ }
+
+ public function add(Request $oRequest, Response $oResponse, $args): \Illuminate\Http\JsonResponse
+ {
+ $sTableName = $this->oModel->getTable();
+ $aRawFields = Schema::getColumnListing($sTableName);
+
+ $sClassName = get_class($this->oModel);
+
+ $aKickFields = [];
+
+ if (defined($sClassName . '::UPDATED_AT')) {
+ $aKickFields[] = constant($sClassName . '::UPDATED_AT');
+ }
+
+ if (defined($sClassName . '::CREATED_AT')) {
+ $aKickFields[] = constant($sClassName . '::CREATED_AT');
+ }
+
+ $aKickFields[] = "id";
+
+ $aRawFieldsClear = [];
+ foreach ($aRawFields as $aRawField) {
+ if (in_array($aRawField, $aKickFields)) {
+ continue;
+ }
+ $aRawFieldsClear[] = $aRawField;
+ }
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [
+ 'cList' => $aRawFieldsClear,
+ 'sPageName' => $this->sPageName
+ ]
+ ], 200);
+ }
+
+ public function clone(Request $oRequest, Response $oResponse, $args)
+ {
+ $iId = $args['id'];
+ $oModelResult = $this->oModel->findOrFail($iId);
+ $cList = $oModelResult->getRawOriginal();
+
+ $sTableName = $this->oModel->getTable();
+ $aStatus = DB::select("SHOW TABLE STATUS LIKE '{$sTableName}'");
+ $iNextId = $aStatus[0]->Auto_increment ?? 1;
+
+ $cList['id'] = $iNextId;
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [
+ 'cList' => $cList,
+ 'aMap' => $this->oModel->getMapAll(),
+ 'aListField' => $this->getListFields(),
+ 'sPageName' => $this->sPageName
+ ]
+ ], 200);
+ }
+
+ public function cloneSave(Request $oRequest, Response $oResponse, $args)
+ {
+ $iId = $args['id'];
+
+ $aParams = $oRequest->getParams();
+
+ foreach ($aParams as $k => $v) {
+ $this->oModal->{$k} = $v;
+ }
+ $this->oModel->create();
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [],
+ 'sMsg' => "保存成功:id ".$this->oModel->id
+ ], 200);
+ }
+
+ public function detail(Request $oRequest, Response $oResponse, $args)
+ {
+ $iId = $args['id'];
+ $oModelResult = $this->oModel->findOrFail($iId);
+ $cList = $oModelResult->getRawOriginal();
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [
+ 'cList' => $cList,
+ 'aMap' => $this->oModel->getMapAll(),
+ 'aListField' => $this->getListFields(),
+ 'sPageName' => $this->sPageName
+ ]
+ ], 200);
+ }
+
+ public function hit(Request $oRequest, Response $oResponse, $args)
+ {
+ $iId = $args['id'];
+ $sField = $args['field'];
+ $sHitType = $this->aFieldTypes[$sField] ?? '';
+
+ $sHit = $this->oModel->find($iId)?->{$sField};
+
+ if ($sHitType) {
+ if ($sHitType == 'json') {
+ $aHit = json_decode($sHit);
+ $sHit = json_encode($aHit, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ }
+ }
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [
+ 'sHit' => $sHit
+ ]
+ ], 200);
+
+ }
+
+ public function delete(Request $oRequest, Response $oResponse, $args)
+ {
+ $iId = $args['id'];
+
+ $this->oModel->findOrFail($iId)->delete();
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [],
+ 'sMsg' => '删除成功'
+ ], 200);
+ }
+
+ public function addSave(Request $oRequest, Response $oResponse, $args)
+ {
+ $aParams = $oRequest->getParams();
+
+ foreach ($aParams as $k => $v) {
+ $this->oModel->{$k} = $v;
+ }
+
+ if (!$this->oModel->save()) {
+ return $oResponse->withJson(['code' => 500, 'msg' => '保存失败?'], 500);
+ }
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => [],
+ 'sMsg' => '保存成功'
+ ], 200);
+ }
+
+ public function detailSave(Request $oRequest, Response $oResponse, $args)
+ {
+ $iId = $args['id'];
+
+ $oThisModel = $this->oModel->find($iId);
+
+ $aParams = $oRequest->getParams();
+
+ unset($aParams['id'], $aParams['_method']);
+
+ foreach ($aParams as $k => &$v) {
+ if (empty($v) && empty($oThisModel->{$k})) {
+ continue;
+ }
+ if (empty($v) && $v !== 0 && $v !== '0') {
+ $v = '';
+ }
+ if ($oThisModel->{$k} != $v) {
+ $oThisModel->{$k} = $v;
+ }
+ }
+
+ $oThisModel->save();
+
+ $aChangeField = $oThisModel->getChanges();
+
+ /// 排除update_at
+ $sClassName = get_class($this->oModel);
+ if (
+ defined($sClassName . '::UPDATED_AT')
+ && ($sUpdateColumn = constant($sClassName . '::UPDATED_AT'))
+ && !isset($aParams[$sUpdateColumn])
+ && isset($aChangeField[$sUpdateColumn])
+ ) {
+ unset($aChangeField[$sUpdateColumn]);
+ }
+
+ /// 构造msg
+ $sMsg = "";
+ $sJiantou = " -> ";
+ foreach ($aChangeField as $k => $v) {
+
+ if (mb_strlen($v, 'UTF-8') > 60 ) {
+ $sJiantou = '';
+// $v = mb_substr($v, 0, 60, 'UTF-8');
+ $v = '';
+ }
+
+ if (!isset($aParams[$k])) {
+ $sMsg .= "⚠️ ".$k . $sJiantou . $v . "
";
+ } else {
+ $sMsg .= "✅ ".$k . $sJiantou . $v . "
";
+ unset($aParams[$k]);
+ }
+ }
+ foreach ($aParams as $k => $v) {
+
+ if (mb_strlen($v, 'UTF-8') > 60 ) {
+ $sJiantou = '';
+// $v = mb_substr($v, 0, 60, 'UTF-8');
+ $v = '';
+ }
+
+ $sMsg .= "❌ ".$k . $sJiantou . $v . "
";
+ }
+ $sMsg = rtrim($sMsg, "
");
+
+ return $oResponse->withJson([
+ 'iCode' => 200,
+ 'aData' => $aChangeField,
+ 'sMsg' => $sMsg
+ ], 200);
+ }
+
+}
diff --git a/src/Controllers/Api/Nasa/V1/NodeController.php b/src/Controllers/Api/Nasa/V1/NodeController.php
new file mode 100755
index 000000000..401d92672
--- /dev/null
+++ b/src/Controllers/Api/Nasa/V1/NodeController.php
@@ -0,0 +1,129 @@
+ 'id',
+ 'name' => 'name',
+ 'type' => '启用',
+ 'server' => 'server',
+ 'custom_config' => 'custom_config',
+ 'sort' => 'sort',
+ 'traffic_rate' => '倍率',
+ 'node_class' => 'node_class',
+ 'node_speedlimit' => '限速',
+ 'node_bandwidth' => 'node_bandwidth',
+ 'node_bandwidth_limit' => 'node_bandwidth_limit',
+ 'bandwidthlimit_resetday'=> 'bandwidthlimit_resetday',
+ 'node_heartbeat' => 'node_heartbeat',
+ 'online_user' => 'online_user',
+ 'ipv4' => 'ipv4',
+ 'ipv6' => 'ipv6',
+ 'node_group' => 'node_group',
+ 'online' => 'online',
+ 'gfw_block' => 'gfw_block',
+ 'order_group' => 'order_group',
+ 'zhuanfa_code' => 'zhuanfa_code',
+ 'duck_code' => 'duck_code',
+ 'duck_group' => 'duck_group',
+ 'memo' => 'memo',
+ 'is_shadow' => 'is_shadow',
+ 'traffic_rate_front' => 'traffic_rate_front',
+ 'memo_front' => 'memo_front',
+ 'fly' => 'fly',
+ 'pid' => 'pid',
+ 'prev_day_width' => 'prev_day_width',
+ 'country_code' => 'country_code',
+ 'country_emoji' => 'country_emoji',
+ ];
+
+ protected array $aValueStyle = [ // 特定文字渲染样式
+ '关闭' => "关闭",
+ '开启' => "开启"
+ ];
+
+ protected array $aFieldTypes = [
+ 'custom_config' => 'json',
+ 'prev_day_width' => 'byte',
+ 'node_bandwidth' => 'byte',
+ 'node_bandwidth_limit' => 'byte'
+ ];
+
+ protected array $aOrderBy = [
+ [
+ "sField" => "type",
+ "sSort" => "desc",
+ ],
+ [
+ "sField" => "is_shadow",
+ "sSort" => "asc",
+ ]
+ ];
+
+ protected array $aJsonFields = [ // json字段
+
+ ];
+
+ protected array $aDateFields = [ // date字段
+// 'dtUpdatedAt'
+ ];
+
+ protected array $aLenthFields = [ // 限制长度的字段
+// "xx" => 100,
+ ];
+
+ protected array $aSelectFields = [ // 作为默认select的字段
+// 'iStatus',
+// 'sComponentType',
+ ];
+
+// protected array $aValueFields = [ // 值映射
+// 'sComponentType' => [
+// 'TOP_BAR' => '顶级菜单',
+// 'SIDEBAR_MENU' => '次级菜单',
+// 'INNER_PAGE_TAB' => '页内菜单',
+// ]
+// ];
+
+ protected array $aCcExtFields = [
+// 'dtCreatedAt' => '创建时间'
+ ];
+
+// protected array $aLimit = [3, 15, 40, 100]; // list长度,第一个为默认
+
+}
+
+
+
+
+//CREATE TABLE `nasa_navigation` (
+// `iId` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+// `iParentId` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '父级ID(0代表顶级导航)',
+// `sName` varchar(64) NOT NULL COMMENT '导航菜单名称',
+// `sSlug` varchar(128) NOT NULL DEFAULT '' COMMENT 'Laravel 路由别名(Route Name)',
+// `sIcon` varchar(64) NOT NULL DEFAULT '' COMMENT '图标 Class/Svg 标识',
+// `sGroup` varchar(128) NOT NULL DEFAULT '',
+// `sComponentType` varchar(32) NOT NULL DEFAULT 'SIDEBAR_MENU' COMMENT '渲染组件类型:TOP_BAR, SIDEBAR_MENU, INNER_PAGE_TAB',
+// `iSort` int(11) NOT NULL DEFAULT 0 COMMENT '排序权重(数值越小越靠前)',
+// `iStatus` tinyint(4) NOT NULL DEFAULT 1 COMMENT '是否可见:1-显示, 0-隐藏',
+// `sPermissionSlug` varchar(64) DEFAULT NULL COMMENT '权限标识(用于中间件拦截校验)',
+// `dtCreatedAt` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间',
+// `dtUpdatedAt` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
+// PRIMARY KEY (`iId`),
+// KEY `idx_status_parent_sort` (`iStatus`,`iParentId`,`iSort`),
+// KEY `idx_permission` (`sPermissionSlug`)
+//) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统功能导航路由表';
diff --git a/src/Middleware/VerifyNasaToken.php b/src/Middleware/VerifyNasaToken.php
new file mode 100644
index 000000000..d3cb6047f
--- /dev/null
+++ b/src/Middleware/VerifyNasaToken.php
@@ -0,0 +1,55 @@
+getHeaderLine('X-Nasa-Timestamp');
+ $sClientToken = $request->getHeaderLine('X-Nasa-Token');
+
+ // 2. 钢铁防御线一:缺少安全头直接熔断,直接沿用你模板里的 ->withJson 丝滑连招!
+ if (!$iRequestTime || !$sClientToken) {
+ return AppFactory::determineResponseFactory()->createResponse(403)->withJson([
+ 'ret' => 0,
+ 'msg' => 'Security Headers Missing.',
+ ]);
+ }
+
+ // 3. 时效性防御:前后端时间差超过 60 秒判定为过期
+ if (abs(time() - $iRequestTime) > 60) {
+ return AppFactory::determineResponseFactory()->createResponse(403)->withJson([
+ 'ret' => 0,
+ 'msg' => 'Request Expired.',
+ ]);
+ }
+
+ // 4. 验签防御:从环境变量掏出密钥进行像素级对撞
+ $sLocalSecret = getenv('nasa_secret') ?: ($_ENV['nasa_secret'] ?? '');
+ $sExpectedToken = md5("timestamp={$iRequestTime}&secret={$sLocalSecret}");
+
+ if ($sClientToken !== $sExpectedToken) {
+ return AppFactory::determineResponseFactory()->createResponse(403)->withJson([
+ 'ret' => 0,
+ 'msg' => 'Invalid Token.',
+ ]);
+ }
+
+ // 5. 所有的关卡都过了,安全放行,通电!
+ return $handler->handle($request);
+ }
+}
diff --git a/src/Models/Node.php b/src/Models/Node.php
index b4bf080fc..34aae89b4 100755
--- a/src/Models/Node.php
+++ b/src/Models/Node.php
@@ -191,4 +191,9 @@ final class Node extends Model
return $oNow->diffInDays($oTargetDate);
}
+ public function getMapAll() {
+
+ return [];
+ }
+
}