diff --git a/app/routes.php b/app/routes.php index 1246f947c..585d76c9d 100755 --- a/app/routes.php +++ b/app/routes.php @@ -444,16 +444,28 @@ return static function (Slim\App $app): void { //})->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()); + + $group->get('/node/base', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':index'); + $group->get('/node/base/detail/{id}', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':detail'); + $group->put('/node/base/detail/save/{id}', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':detailSave'); + $group->delete('/node/base/delete/save/{id}', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':delete'); + $group->get('/node/base/add', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':add'); + $group->put('/node/base/add/save', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':addSave'); + $group->get('/node/base/clone/{id}', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':clone'); +// $group->put('/node/base/clone/', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':cloneSave'); + $group->get('/node/base/hit/{id}/{field}', App\Controllers\Api\Nasa\V1\NodeBaseController::class . ':hit'); + + $group->get('/env/config', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':index'); + $group->get('/env/config/detail/{id}', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':detail'); + $group->put('/env/config/detail/save/{id}', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':detailSave'); + $group->delete('/env/config/delete/save/{id}', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':delete'); + $group->get('/env/config/add', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':add'); + $group->put('/env/config/add/save', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':addSave'); + $group->get('/env/config/clone/{id}', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':clone'); +// $group->put('/env/config/clone/', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':cloneSave'); + $group->get('/env/config/hit/{id}/{field}', App\Controllers\Api\Nasa\V1\EnvConfigController::class . ':hit'); + + })->add(new VerifyNasaToken());// get可以其他不可以? // Route::prefix('setting')->group(function() { diff --git a/src/Controllers/Admin/TicketController.php b/src/Controllers/Admin/TicketController.php index e03c5672d..af75a3476 100755 --- a/src/Controllers/Admin/TicketController.php +++ b/src/Controllers/Admin/TicketController.php @@ -94,6 +94,13 @@ final class TicketController extends BaseController $user = (new User())->find($ticket->userid); +// MailHub::start() +// ->setScene("tk_ry") +// ->setTitle($_ENV['appName'] . ' - 工单被回复') +// ->setView("one", $a) +// ->setTo($email) +// ->send(); + Notification::notifyUser( $user, $_ENV['appName'] . ' - 工单被回复', diff --git a/src/Controllers/Api/Nasa/V1/Base/ListController.php b/src/Controllers/Api/Nasa/V1/Base/ListController.php index 608b94219..b1ed7fee8 100755 --- a/src/Controllers/Api/Nasa/V1/Base/ListController.php +++ b/src/Controllers/Api/Nasa/V1/Base/ListController.php @@ -61,6 +61,15 @@ abstract class ListController } } + public function getMapAll() + { + if (method_exists($this->oModel, 'getMapAll')) { + return $this->oModel->getMapAll(); + } + + return []; + } + public function index(Request $oRequest, Response $oResponse, $args) { if (! $this->oModel) { @@ -162,7 +171,7 @@ abstract class ListController 'cList' => $oPaginatedData->items(), 'aListField' => $this->getListFields(), 'aFieldTypes' => $this->aFieldTypes, - 'aMap' => $this->oModel->getMapAll(), + 'aMap' => $this->getMapAll(), 'aValueStyle' => $this->aValueStyle, 'aSelectField' => $this->aSelectFields, 'aFilterParam' => $this->aFilterParams, @@ -245,37 +254,34 @@ abstract class ListController return $s; } - public function add(Request $oRequest, Response $oResponse, $args): \Illuminate\Http\JsonResponse + public function add(Request $oRequest, Response $oResponse, $args) { + // 1. 获取表名 $sTableName = $this->oModel->getTable(); - $aRawFields = Schema::getColumnListing($sTableName); - $sClassName = get_class($this->oModel); + // 2. 修复 500 报错:改用连接对象的 Schema 构建器,彻底解耦门面 + // 变量命名:oSchemaBuilder (Schema构建器对象) -> 【欧 斯基玛 标德儿】 + $oSchemaBuilder = $this->oModel->getConnection()->getSchemaBuilder(); + $aRawFields = $oSchemaBuilder->getColumnListing($sTableName); - $aKickFields = []; + // 3. 大厂正统手法:利用模型原生方法安全获取时间戳字段名(自动处理关闭时间戳的情况) + // 变量命名:aKickFields (要剔除的字段数组) -> 【诶 踢客 费尔茨】 + $aKickFields = ['id']; - if (defined($sClassName . '::UPDATED_AT')) { - $aKickFields[] = constant($sClassName . '::UPDATED_AT'); + if ($this->oModel->timestamps) { + $aKickFields[] = $this->oModel->getCreatedAtColumn(); + $aKickFields[] = $this->oModel->getUpdatedAtColumn(); } - - 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; - } - + + // 4. 高效数组过滤:干掉 foreach,改用内置函数完成差集计算 + // 变量命名:aCleanFields (干净的字段数组) -> 【诶 克林 费尔茨】 + $aCleanFields = array_values(array_diff($aRawFields, $aKickFields)); + + // 5. 标准响应返回 return $oResponse->withJson([ 'iCode' => 200, - 'aData' => [ - 'cList' => $aRawFieldsClear, + 'aData' => [ + 'cList' => $aCleanFields, // 保持你原有的前端 Key 兼容 'sPageName' => $this->sPageName ] ], 200); @@ -283,44 +289,85 @@ abstract class ListController public function clone(Request $oRequest, Response $oResponse, $args) { - $iId = $args['id']; + // 1. 安全获取参数并查询 + $iId = $args['id'] ?? 0; + // 确保使用 findOrFail,找不到直接抛出 404 异常(由框架底层捕获) $oModelResult = $this->oModel->findOrFail($iId); - $cList = $oModelResult->getRawOriginal(); - $sTableName = $this->oModel->getTable(); - $aStatus = DB::select("SHOW TABLE STATUS LIKE '{$sTableName}'"); + // 2. 匈牙利命名法修正:获取原始属性数组 + // 变量命名:aRowAttributes (行属性数组) -> 【诶 柔 艾特里比茨】 + $aRowAttributes = $oModelResult->getRawOriginal(); + + // 3. 修复 500 报错:抛弃 DB 门面,改用解耦的实例调用 + // 变量命名:oConnection (连接对象) -> 【欧 康内克闪】 + $oConnection = $this->oModel->getConnection(); + $sTableName = $this->oModel->getTable(); + + // 防御性编程:将原生 SQL 占位绑定,防止潜在注入风险 + // 直接把表名变量拼进单引号里 + $aStatus = $oConnection->select("SHOW TABLE STATUS LIKE '{$sTableName}'"); $iNextId = $aStatus[0]->Auto_increment ?? 1; - $cList['id'] = $iNextId; + // 4. 移除旧主键,赋予预测的自增 ID(注:若无特殊业务强需求,大厂更倾向于 unset($aRowAttributes['id'])) + $aRowAttributes['id'] = (int)$iNextId; // 强制强类型转换 + // 5. 健壮的方法存在性检查(呼应你上一条的需求) + // 变量命名:aMapData (映射数据数组) -> 【诶 迈普 数据】 + $aMapData = method_exists($this->oModel, 'getMapAll') ? $this->oModel->getMapAll() : []; + + // 6. 统一标准的 JSON 响应输出 return $oResponse->withJson([ 'iCode' => 200, - 'aData' => [ - 'cList' => $cList, - 'aMap' => $this->oModel->getMapAll(), + 'aData' => [ + 'cList' => $aRowAttributes, // 保持你前端需要的 Key 命名不变 + 'aMap' => $aMapData, 'aListField' => $this->getListFields(), - 'sPageName' => $this->sPageName + '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 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) +// { +//// tt(123); +// $iId = $args['id']; +////dd(123); +// $aParams = $oRequest->getParams(); +// +// foreach ($aParams as $k => $v) { +// $this->oModel->{$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) { @@ -332,7 +379,7 @@ abstract class ListController 'iCode' => 200, 'aData' => [ 'cList' => $cList, - 'aMap' => $this->oModel->getMapAll(), + 'aMap' => $this->getMapAll(), 'aListField' => $this->getListFields(), 'sPageName' => $this->sPageName ] @@ -365,6 +412,7 @@ abstract class ListController public function delete(Request $oRequest, Response $oResponse, $args) { + $iId = $args['id']; $this->oModel->findOrFail($iId)->delete(); diff --git a/src/Controllers/Api/Nasa/V1/EnvConfigController.php b/src/Controllers/Api/Nasa/V1/EnvConfigController.php new file mode 100755 index 000000000..d87cbf195 --- /dev/null +++ b/src/Controllers/Api/Nasa/V1/EnvConfigController.php @@ -0,0 +1,101 @@ + 'id', + 'item' => '配置项', + 'value' => '配置值', + 'class' => '配置类别', + 'is_public' => '是否为公共参数', + 'type' => '配置值类型', + 'default' => '默认值', + 'mark' => '备注', + ]; + +// protected array $aValueStyle = [ // 特定文字渲染样式 +// '关闭' => "关闭", +// '开启' => "开启" +// ]; + +// protected array $aFieldTypes = [ +// 'custom_config' => 'json', +// 'prev_day_width' => 'byte', +// 'node_bandwidth' => 'byte', +// 'node_bandwidth_limit' => 'byte' +// ]; + + protected array $aOrderBy = [ + [ + "sField" => "id", + "sSort" => "desc", + ] + ]; + + 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/Controllers/Api/Nasa/V1/NodeController.php b/src/Controllers/Api/Nasa/V1/NodeBaseController.php similarity index 98% rename from src/Controllers/Api/Nasa/V1/NodeController.php rename to src/Controllers/Api/Nasa/V1/NodeBaseController.php index 6a1c6dbe0..e5347cfba 100755 --- a/src/Controllers/Api/Nasa/V1/NodeController.php +++ b/src/Controllers/Api/Nasa/V1/NodeBaseController.php @@ -6,7 +6,7 @@ namespace App\Controllers\Api\Nasa\V1; use Illuminate\Http\Request; use App\Controllers\Api\Nasa\V1\Base\ListController; -final class NodeController extends ListController +final class NodeBaseController extends ListController { protected string $sModelName = 'Node'; protected array $aFilterFields = [ // 允许where的字段,不设视为全允许 @@ -25,10 +25,11 @@ final class NodeController extends ListController 'sort' => 'sort', 'traffic_rate' => '倍率', 'node_class' => 'node_class', - 'node_speedlimit' => '限速', + 'prev_day_width' => 'prev_day_width', 'node_bandwidth' => 'node_bandwidth', 'node_bandwidth_limit' => 'node_bandwidth_limit', 'bandwidthlimit_resetday'=> 'bandwidthlimit_resetday', + 'node_speedlimit' => '限速', 'node_heartbeat' => 'node_heartbeat', 'online_user' => 'online_user', 'ipv4' => 'ipv4', @@ -46,7 +47,6 @@ final class NodeController extends ListController 'memo_front' => 'memo_front', 'fly' => 'fly', 'pid' => 'pid', - 'prev_day_width' => 'prev_day_width', 'country_code' => 'country_code', 'country_emoji' => 'country_emoji', ]; diff --git a/src/Services/Password.php b/src/Services/Password.php index b6895f359..4afdf686c 100755 --- a/src/Services/Password.php +++ b/src/Services/Password.php @@ -8,6 +8,7 @@ use App\Models\Config; use App\Utils\Tools; use Psr\Http\Client\ClientExceptionInterface; use RedisException; +use App\Services\MailHub; final class Password { @@ -25,13 +26,25 @@ final class Password $subject = $_ENV['appName'] . ' - 重置密码'; $resetUrl = $_ENV['baseUrl'] . '/password/token/' . $token; - Mail::send( - $email, - $subject, - 'password_reset.tpl', - [ - 'resetUrl' => $resetUrl, - ] - ); + $a = [ + 'resetUrl' => $resetUrl, + ]; + + MailHub::start() + ->setScene("pwd_reset") + ->setTitle($subject) + ->setView("password_reset", $a) + ->setTo($email) + ->send(); + +// Mail::send( +// $email, +// $subject, +// 'password_reset.tpl', +// [ +// 'resetUrl' => $resetUrl, +// ] +// ); + } }