Files
webman-buildadmin/app/common/controller/Backend.php

435 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\controller;
use Throwable;
use app\admin\library\Auth;
use app\common\library\token\TokenExpirationException;
use app\admin\library\traits\Backend as BackendTrait;
use support\Response;
use Webman\Http\Request as WebmanRequest;
/**
* 后台控制器基类
* 迁移 Auth 鉴权、权限校验、CRUD trait
*/
class Backend extends Api
{
use BackendTrait;
/**
* 无需登录的方法
*/
protected array $noNeedLogin = [];
/**
* 无需鉴权的方法
*/
protected array $noNeedPermission = [];
/**
* 权限类实例
* @var Auth|null
*/
protected ?Auth $auth = null;
/**
* 模型类实例(子类设置)
* @var object|null
*/
protected ?object $model = null;
/**
* 新增/编辑时排除字段
*/
protected array|string $preExcludeFields = [];
/**
* 权重字段
*/
protected string $weighField = 'weigh';
/**
* 默认排序
*/
protected string|array $defaultSortField = [];
/**
* 有序保证
*/
protected string|array $orderGuarantee = [];
/**
* 快速搜索字段
*/
protected string|array $quickSearchField = 'id';
/**
* 数据限制
*/
protected bool|string|int $dataLimit = false;
/**
* 数据限制字段
*/
protected string $dataLimitField = 'admin_id';
/**
* 数据限制开启时自动填充
*/
protected bool $dataLimitFieldAutoFill = true;
/**
* 查看请求返回的主表字段
*/
protected string|array $indexField = ['*'];
/**
* 是否开启模型验证
*/
protected bool $modelValidate = true;
/**
* 是否开启模型场景验证
*/
protected bool $modelSceneValidate = false;
/**
* 关联查询方法名
*/
protected array $withJoinTable = [];
/**
* 关联查询 JOIN 方式
*/
protected string $withJoinType = 'LEFT';
/**
* 输入过滤函数名(如 clean_xssCRUD 含 editor 字段时自动设置)
*/
protected string $inputFilter = '';
/**
* 后台初始化(需在控制器方法开头调用,在 initializeApi 之后)
* @return Response|null 需直接返回时返回 Response否则 null
*/
public function initializeBackend(WebmanRequest $request): ?Response
{
$response = $this->initializeApi($request);
if ($response !== null) {
return $response;
}
// 调用子类 initializeCRUD 生成的控制器在此设置 model
$this->initialize();
$action = $this->getActionFromPath($request->path());
$needLogin = !action_in_arr($this->noNeedLogin, $action);
$needPermission = !action_in_arr($this->noNeedPermission, $action);
try {
$this->auth = Auth::instance();
$token = get_auth_token(['ba', 'token'], $request);
if ($token) {
$this->auth->init($token);
}
} catch (TokenExpirationException) {
if ($needLogin) {
return $this->error(__('Token expiration'), [], 409);
}
}
if ($needLogin) {
if (!$this->auth->isLogin()) {
return $this->error(__('Please login first'), [
'type' => Auth::NEED_LOGIN,
], 0, ['statusCode' => Auth::LOGIN_RESPONSE_CODE]);
}
if ($needPermission) {
$controllerPath = $this->getControllerPath($request);
$routePath = $controllerPath . '/' . $action;
if (!$this->auth->check($routePath)) {
return $this->error(__('You have no permission'), [], 401);
}
}
}
event_trigger('backendInit', $this->auth);
if (method_exists($this, 'initController')) {
$initResp = $this->initController($request);
if ($initResp !== null) {
return $initResp;
}
}
return null;
}
/**
* 子类可覆盖,用于初始化 model 等(替代原 initialize
* @return Response|null 需直接返回时返回 Response否则 null
*/
protected function initController(WebmanRequest $request): ?Response
{
return null;
}
public function index(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
return $this->_index();
}
public function add(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
return $this->_add();
}
public function edit(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
return $this->_edit();
}
public function del(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
return $this->_del();
}
public function sortable(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
return $this->_sortable();
}
public function select(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response instanceof Response) return $response;
return $this->_select();
}
/**
* 查询参数构建器
*/
public function queryBuilder(): array
{
if (empty($this->model) || !$this->request) {
return [[], [], 10, []];
}
$pk = $this->model->getPk();
$quickSearch = $this->request->get('quickSearch', '');
$limit = $this->request->get('limit', 10);
$limit = is_numeric($limit) ? intval($limit) : 10;
$search = $this->request->get('search', []);
$search = is_array($search) ? $search : [];
$initKey = $this->request->get('initKey', $pk);
$initValue = $this->request->get('initValue', '');
$initOperator = $this->request->get('initOperator', 'in');
$where = [];
$modelTable = strtolower($this->model->getTable());
$alias = [];
$alias[$modelTable] = parse_name(basename(str_replace('\\', '/', get_class($this->model))));
$mainTableAlias = $alias[$modelTable] . '.';
if ($quickSearch) {
$quickSearchArr = is_array($this->quickSearchField) ? $this->quickSearchField : explode(',', $this->quickSearchField);
foreach ($quickSearchArr as $k => $v) {
$quickSearchArr[$k] = str_contains($v, '.') ? $v : $mainTableAlias . $v;
}
$where[] = [implode('|', $quickSearchArr), 'LIKE', '%' . str_replace('%', '\%', $quickSearch) . '%'];
}
if ($initValue) {
$where[] = [$initKey, $initOperator, $initValue];
$limit = 999999;
}
foreach ($search as $field) {
if (!is_array($field) || !isset($field['operator']) || !isset($field['field']) || !isset($field['val'])) {
continue;
}
$field['operator'] = $this->getOperatorByAlias($field['operator']);
if (str_contains($field['field'], '.')) {
$fieldNameParts = explode('.', $field['field']);
$fieldNamePartsLastKey = array_key_last($fieldNameParts);
foreach ($fieldNameParts as $fieldNamePartsKey => $fieldNamePart) {
if ($fieldNamePartsKey !== $fieldNamePartsLastKey) {
$fieldNameParts[$fieldNamePartsKey] = parse_name($fieldNamePart);
}
}
$fieldName = implode('.', $fieldNameParts);
} else {
$fieldName = $mainTableAlias . $field['field'];
}
if (isset($field['render']) && $field['render'] == 'datetime') {
if ($field['operator'] == 'RANGE') {
$datetimeArr = explode(',', $field['val']);
if (!isset($datetimeArr[1])) {
continue;
}
$datetimeArr = array_filter(array_map('strtotime', $datetimeArr));
$where[] = [$fieldName, str_replace('RANGE', 'BETWEEN', $field['operator']), $datetimeArr];
continue;
}
$where[] = [$fieldName, '=', strtotime($field['val'])];
continue;
}
if ($field['operator'] == 'RANGE' || $field['operator'] == 'NOT RANGE') {
$arr = explode(',', $field['val']);
if (!isset($arr[0]) || $arr[0] === '') {
$operator = $field['operator'] == 'RANGE' ? '<=' : '>';
$arr = $arr[1];
} elseif (!isset($arr[1]) || $arr[1] === '') {
$operator = $field['operator'] == 'RANGE' ? '>=' : '<';
$arr = $arr[0];
} else {
$operator = str_replace('RANGE', 'BETWEEN', $field['operator']);
}
$where[] = [$fieldName, $operator, $arr];
continue;
}
switch ($field['operator']) {
case '=':
case '<>':
$where[] = [$fieldName, $field['operator'], strval($field['val'])];
break;
case 'LIKE':
case 'NOT LIKE':
$where[] = [$fieldName, $field['operator'], '%' . str_replace('%', '\%', $field['val']) . '%'];
break;
case '>':
case '>=':
case '<':
case '<=':
$where[] = [$fieldName, $field['operator'], intval($field['val'])];
break;
case 'FIND_IN_SET':
if (is_array($field['val'])) {
foreach ($field['val'] as $val) {
$where[] = [$fieldName, 'find in set', $val];
}
} else {
$where[] = [$fieldName, 'find in set', $field['val']];
}
break;
case 'IN':
case 'NOT IN':
$where[] = [$fieldName, $field['operator'], is_array($field['val']) ? $field['val'] : explode(',', $field['val'])];
break;
case 'NULL':
case 'NOT NULL':
$where[] = [$fieldName, strtolower($field['operator']), ''];
break;
}
}
$dataLimitAdminIds = $this->getDataLimitAdminIds();
if ($dataLimitAdminIds) {
$where[] = [$mainTableAlias . $this->dataLimitField, 'in', $dataLimitAdminIds];
}
return [$where, $alias, $limit, $this->queryOrderBuilder()];
}
/**
* 查询的排序参数构建器
*/
public function queryOrderBuilder(): array
{
$pk = $this->model->getPk();
$order = $this->request ? $this->request->get('order') : null;
$order = $order ?: $this->defaultSortField;
if ($order && is_string($order)) {
$order = explode(',', $order);
$order = [$order[0] => $order[1] ?? 'asc'];
}
if (!$this->orderGuarantee) {
$this->orderGuarantee = [$pk => 'desc'];
} elseif (is_string($this->orderGuarantee)) {
$orderParts = explode(',', $this->orderGuarantee);
$this->orderGuarantee = [$orderParts[0] => $orderParts[1] ?? 'asc'];
}
$orderGuaranteeKey = array_key_first($this->orderGuarantee);
if (!is_array($order) || !array_key_exists($orderGuaranteeKey, $order)) {
$order[$orderGuaranteeKey] = $this->orderGuarantee[$orderGuaranteeKey];
}
return $order;
}
/**
* 数据权限控制 - 获取有权限访问的管理员 IDs
*/
protected function getDataLimitAdminIds(): array
{
if (!$this->dataLimit || !$this->auth || $this->auth->isSuperAdmin()) {
return [];
}
$adminIds = [];
if ($this->dataLimit == 'parent') {
$parentGroups = $this->auth->getAdminChildGroups();
if ($parentGroups) {
$adminIds = $this->auth->getGroupAdmins($parentGroups);
}
} elseif (is_numeric($this->dataLimit) && $this->dataLimit > 0) {
$adminIds = $this->auth->getGroupAdmins([$this->dataLimit]);
return in_array($this->auth->id, $adminIds) ? [] : [$this->auth->id];
} elseif ($this->dataLimit == 'allAuth' || $this->dataLimit == 'allAuthAndOthers') {
$allAuthGroups = $this->auth->getAllAuthGroups($this->dataLimit);
$adminIds = $this->auth->getGroupAdmins($allAuthGroups);
}
$adminIds[] = $this->auth->id;
return array_unique($adminIds);
}
/**
* 从别名获取原始的逻辑运算符
*/
protected function getOperatorByAlias(string $operator): string
{
$alias = [
'ne' => '<>',
'eq' => '=',
'gt' => '>',
'egt' => '>=',
'lt' => '<',
'elt' => '<=',
];
return $alias[$operator] ?? $operator;
}
/**
* 从 path 解析 action
*/
protected function getActionFromPath(string $path): string
{
$parts = explode('/', trim($path, '/'));
return $parts[array_key_last($parts)] ?? '';
}
/**
* 从 Request 或路由解析控制器路径(如 auth/admin
* 优先从 $request->controllerWebman 路由匹配时设置)解析,否则从 path 解析
*/
protected function getControllerPath(WebmanRequest $request): string
{
return get_controller_path($request);
}
}