From 815163b8e41f86c61515168f20ec9c9027c35661 Mon Sep 17 00:00:00 2001 From: hellcat Date: Sun, 14 Jun 2026 18:16:25 +0800 Subject: [PATCH] no message --- app/routes.php | 49 ++ public/index.php | 23 +- .../Api/Nasa/V1/Base/ListController.php | 464 ++++++++++++++++++ .../Api/Nasa/V1/NodeController.php | 129 +++++ src/Middleware/VerifyNasaToken.php | 55 +++ src/Models/Node.php | 5 + 6 files changed, 720 insertions(+), 5 deletions(-) create mode 100755 src/Controllers/Api/Nasa/V1/Base/ListController.php create mode 100755 src/Controllers/Api/Nasa/V1/NodeController.php create mode 100644 src/Middleware/VerifyNasaToken.php 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 []; + } + }