no message

This commit is contained in:
hellcat 2026-06-16 18:15:15 +08:00
parent 8d680d9698
commit e551f8a0f9
6 changed files with 253 additions and 72 deletions

View File

@ -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() {

View File

@ -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'] . ' - 工单被回复',

View File

@ -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();

View File

@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Nasa\V1;
use Illuminate\Http\Request;
use App\Controllers\Api\Nasa\V1\Base\ListController;
final class EnvConfigController extends ListController
{
protected string $sModelName = 'Config';
protected array $aFilterFields = [ // 允许where的字段不设视为全允许
// 's_name',
// 'i_status'
];
protected string $sPageName = "config";
protected array $aListFields = [ // list展示的字段和名字映射
'id' => 'id',
'item' => '配置项',
'value' => '配置值',
'class' => '配置类别',
'is_public' => '是否为公共参数',
'type' => '配置值类型',
'default' => '默认值',
'mark' => '备注',
];
// protected array $aValueStyle = [ // 特定文字渲染样式
// '关闭' => "<span class='text-danger'>关闭</span>",
// '开启' => "<span class='text-success'>开启</span>"
// ];
// 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 '父级ID0代表顶级导航',
// `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='系统功能导航路由表';

View File

@ -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',
];

View File

@ -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,
// ]
// );
}
}