525 lines
17 KiB
PHP
Executable File
525 lines
17 KiB
PHP
Executable File
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Controllers\Api\Nasa\V1\Base;
|
||
|
||
//use Illuminate\Http\Request;
|
||
use Illuminate\Http\JsonResponse;
|
||
use InvalidArgumentException;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Slim\Http\Response;
|
||
//use Slim\Http\ServerRequest;
|
||
use Slim\Http\ServerRequest as Request;
|
||
|
||
abstract class ListController
|
||
{
|
||
private string $sOperatorDefault = 'eq';
|
||
private string $sSortDefault = 'asc';
|
||
|
||
private array $aFilterParams = [
|
||
'c' => [],
|
||
'cc' => [],
|
||
'ob' => [],
|
||
];
|
||
|
||
protected $oModel;
|
||
protected string $sPageName = '';
|
||
protected string $sModelName = '';
|
||
protected array $aFilterFields = []; // 允许where的字段
|
||
// protected array $aArticleFields = []; // 文章字段
|
||
protected array $aFieldTypes = [];
|
||
protected array $aValueFields = [];
|
||
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 = 300; // 默认limit限制
|
||
protected int $iLimit = 30; // 默认显示行数
|
||
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 getMapAll()
|
||
{
|
||
$a = [];
|
||
if (method_exists($this->oModel, 'getMapAll')) {
|
||
$a = $this->oModel->getMapAll();
|
||
}
|
||
|
||
$b = array_merge($a, $this->aValueFields);
|
||
|
||
return $b;
|
||
}
|
||
|
||
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();
|
||
$iLimit = $aParams['limit'] ?? $this->iLimit;
|
||
$iPage = $aParams['page'] ?? 1;
|
||
|
||
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 = 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));
|
||
$oPaginatedData = $oQuery->paginate(
|
||
$iLimit,
|
||
array_keys($this->aListFields),
|
||
'page',
|
||
$iPage
|
||
);
|
||
|
||
return $oResponse->withJson([
|
||
'iCode' => 200,
|
||
'aData' => [
|
||
'cList' => $oPaginatedData->items(),
|
||
'aListField' => $this->getListFields(),
|
||
'aFieldTypes' => $this->aFieldTypes,
|
||
'aMap' => $this->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)
|
||
{
|
||
// 1. 获取表名
|
||
$sTableName = $this->oModel->getTable();
|
||
|
||
// 2. 修复 500 报错:改用连接对象的 Schema 构建器,彻底解耦门面
|
||
// 变量命名:oSchemaBuilder (Schema构建器对象) -> 【欧 斯基玛 标德儿】
|
||
$oSchemaBuilder = $this->oModel->getConnection()->getSchemaBuilder();
|
||
$aRawFields = $oSchemaBuilder->getColumnListing($sTableName);
|
||
|
||
// 3. 大厂正统手法:利用模型原生方法安全获取时间戳字段名(自动处理关闭时间戳的情况)
|
||
// 变量命名:aKickFields (要剔除的字段数组) -> 【诶 踢客 费尔茨】
|
||
$aKickFields = ['id'];
|
||
|
||
if ($this->oModel->timestamps) {
|
||
$aKickFields[] = $this->oModel->getCreatedAtColumn();
|
||
$aKickFields[] = $this->oModel->getUpdatedAtColumn();
|
||
}
|
||
|
||
// 4. 高效数组过滤:干掉 foreach,改用内置函数完成差集计算
|
||
// 变量命名:aCleanFields (干净的字段数组) -> 【诶 克林 费尔茨】
|
||
$aCleanFields = array_values(array_diff($aRawFields, $aKickFields));
|
||
|
||
// 5. 标准响应返回
|
||
return $oResponse->withJson([
|
||
'iCode' => 200,
|
||
'aData' => [
|
||
'cList' => $aCleanFields, // 保持你原有的前端 Key 兼容
|
||
'sPageName' => $this->sPageName
|
||
]
|
||
], 200);
|
||
}
|
||
|
||
public function clone(Request $oRequest, Response $oResponse, $args)
|
||
{
|
||
// 1. 安全获取参数并查询
|
||
$iId = $args['id'] ?? 0;
|
||
// 确保使用 findOrFail,找不到直接抛出 404 异常(由框架底层捕获)
|
||
$oModelResult = $this->oModel->findOrFail($iId);
|
||
|
||
// 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;
|
||
|
||
// 4. 移除旧主键,赋予预测的自增 ID(注:若无特殊业务强需求,大厂更倾向于 unset($aRowAttributes['id']))
|
||
$aRowAttributes['id'] = (int)$iNextId; // 强制强类型转换
|
||
|
||
// 5. 健壮的方法存在性检查(呼应你上一条的需求)
|
||
// 变量命名:aMapData (映射数据数组) -> 【诶 迈普 数据】
|
||
$aMapData = $this->getMapAll();
|
||
|
||
// 6. 统一标准的 JSON 响应输出
|
||
return $oResponse->withJson([
|
||
'iCode' => 200,
|
||
'aData' => [
|
||
'cList' => $aRowAttributes, // 保持你前端需要的 Key 命名不变
|
||
'aMap' => $aMapData,
|
||
'aListField' => $this->getListFields(),
|
||
'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)
|
||
// {
|
||
//// 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)
|
||
{
|
||
$iId = $args['id'];
|
||
$oModelResult = $this->oModel->findOrFail($iId);
|
||
$cList = $oModelResult->getRawOriginal();
|
||
|
||
return $oResponse->withJson([
|
||
'iCode' => 200,
|
||
'aData' => [
|
||
'cList' => $cList,
|
||
'aMap' => $this->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 . "<br><br>";
|
||
} else {
|
||
$sMsg .= "✅ ".$k . $sJiantou . $v . "<br><br>";
|
||
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 . "<br><br>";
|
||
}
|
||
$sMsg = rtrim($sMsg, "<br><br>");
|
||
|
||
return $oResponse->withJson([
|
||
'iCode' => 200,
|
||
'aData' => $aChangeField,
|
||
'sMsg' => $sMsg
|
||
], 200);
|
||
}
|
||
|
||
}
|