后台
This commit is contained in:
526
app/common/controller/Backend.php
Normal file
526
app/common/controller/Backend.php
Normal file
@@ -0,0 +1,526 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\exception\MyBusinessException;
|
||||
use support\Request;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\Response;
|
||||
use Throwable;
|
||||
use think\Model;
|
||||
use think\facade\Event;
|
||||
use app\admin\library\Auth;
|
||||
use app\common\library\token\TokenExpirationException;
|
||||
|
||||
class Backend
|
||||
{
|
||||
/**
|
||||
* 无需登录的方法,访问本控制器的此方法,无需管理员登录
|
||||
* @var array
|
||||
*/
|
||||
protected array $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 无需鉴权的方法
|
||||
* @var array
|
||||
*/
|
||||
protected array $noNeedPermission = [];
|
||||
|
||||
/**
|
||||
* 新增/编辑时,对前端发送的字段进行排除(忽略不入库)
|
||||
* @var array|string
|
||||
*/
|
||||
protected array|string $preExcludeFields = [];
|
||||
|
||||
/**
|
||||
* 权限类实例
|
||||
* @var Auth
|
||||
*/
|
||||
protected Auth $auth;
|
||||
|
||||
/**
|
||||
* 模型类实例
|
||||
* @var object
|
||||
* @phpstan-var Model
|
||||
*/
|
||||
protected object $model;
|
||||
|
||||
/**
|
||||
* 权重字段
|
||||
* @var string
|
||||
*/
|
||||
protected string $weighField = 'weigh';
|
||||
|
||||
/**
|
||||
* 默认排序
|
||||
* @var string|array id,desc 或 ['id' => 'desc']
|
||||
*/
|
||||
protected string|array $defaultSortField = [];
|
||||
|
||||
/**
|
||||
* 有序保证
|
||||
* 查询数据时总是需要指定 ORDER BY 子句,否则 MySQL 不保证排序,即先查到哪行就输出哪行且不保证多次查询中的输出顺序
|
||||
* 将以下配置作为数据有序保证(用于无排序字段时、默认排序字段相同时继续保持数据有序),不设置将自动使用 pk 字段
|
||||
* @var string|array id,desc 或 ['id' => 'desc'](有更方便的格式,此处为了保持和 $defaultSortField 属性的配置格式一致)
|
||||
*/
|
||||
protected string|array $orderGuarantee = [];
|
||||
|
||||
/**
|
||||
* 快速搜索字段
|
||||
* @var string|array
|
||||
*/
|
||||
protected string|array $quickSearchField = 'id';
|
||||
|
||||
/**
|
||||
* 是否开启模型验证
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $modelValidate = true;
|
||||
|
||||
/**
|
||||
* 是否开启模型场景验证
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $modelSceneValidate = false;
|
||||
|
||||
/**
|
||||
* 关联查询方法名,方法应定义在模型中
|
||||
* @var array
|
||||
*/
|
||||
protected array $withJoinTable = [];
|
||||
|
||||
/**
|
||||
* 关联查询JOIN方式
|
||||
* @var string
|
||||
*/
|
||||
protected string $withJoinType = 'LEFT';
|
||||
|
||||
/**
|
||||
* 开启数据限制
|
||||
* false=关闭
|
||||
* personal=仅限个人
|
||||
* allAuth=拥有某管理员所有的权限时
|
||||
* allAuthAndOthers=拥有某管理员所有的权限并且还有其他权限时
|
||||
* parent=上级分组中的管理员可查
|
||||
* 指定分组中的管理员可查,比如 $dataLimit = 2;
|
||||
* 启用请确保数据表内存在 admin_id 字段,可以查询/编辑数据的管理员为admin_id对应的管理员+数据限制所表示的管理员们
|
||||
* @var bool|string|int
|
||||
*/
|
||||
protected bool|string|int $dataLimit = false;
|
||||
|
||||
/**
|
||||
* 数据限制字段
|
||||
* @var string
|
||||
*/
|
||||
protected string $dataLimitField = 'admin_id';
|
||||
|
||||
/**
|
||||
* 数据限制开启时自动填充字段值为当前管理员id
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $dataLimitFieldAutoFill = true;
|
||||
|
||||
/**
|
||||
* 查看请求返回的主表字段控制
|
||||
* @var string|array
|
||||
*/
|
||||
protected string|array $indexField = ['*'];
|
||||
|
||||
protected Request $request;
|
||||
/**
|
||||
* 引入traits
|
||||
* traits内实现了index、add、edit等方法
|
||||
*/
|
||||
use \app\admin\library\traits\Backend;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->request = request();
|
||||
$needLogin = !action_in_arr($this->noNeedLogin);
|
||||
|
||||
try {
|
||||
|
||||
// 初始化管理员鉴权实例
|
||||
$this->auth = Auth::instance();
|
||||
$token = request()->header('token');
|
||||
if ($token) $this->auth->init($token);
|
||||
} catch (TokenExpirationException) {
|
||||
if ($needLogin) {
|
||||
$this->error(__('Token expiration'), [], 409);
|
||||
}
|
||||
}
|
||||
|
||||
if ($needLogin) {
|
||||
if (!$this->auth->isLogin()) {
|
||||
$this->error('Please login first', [
|
||||
'type' => $this->auth::NEED_LOGIN
|
||||
], $this->auth::LOGIN_RESPONSE_CODE);
|
||||
}
|
||||
|
||||
if (!action_in_arr($this->noNeedPermission)) {
|
||||
// $routePath = ($this->app->request->controllerPath ?? '') . '/' . $this->request->action(true);
|
||||
$routePath = substr(request()->path(), 5);
|
||||
|
||||
if (!$this->auth->checkmenus($routePath, $this->auth->user_id)) {
|
||||
$this->error('You have no permission', []);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询参数构建器
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function queryBuilder(): array
|
||||
{
|
||||
if (empty($this->model)) {
|
||||
return [];
|
||||
}
|
||||
$pk = $this->model->getPk();
|
||||
$quickSearch = $this->request->get("quickSearch/s", '');
|
||||
$limit = $this->request->get("limit/d", 10);
|
||||
$search = $this->request->get("search/a", []);
|
||||
$initKey = $this->request->get("initKey/s", $pk);
|
||||
$initValue = $this->request->get("initValue", '');
|
||||
$initOperator = $this->request->get("initOperator/s", 'in');
|
||||
|
||||
$where = [];
|
||||
$modelTable = strtolower($this->model->getTable());
|
||||
$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'], (string)$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()
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
$order = $this->request->get("order/s") ?: $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)) {
|
||||
$this->orderGuarantee = explode(',', $this->orderGuarantee);
|
||||
$this->orderGuarantee = [$this->orderGuarantee[0] => $this->orderGuarantee[1] ?? 'asc'];
|
||||
}
|
||||
$orderGuaranteeKey = array_key_first($this->orderGuarantee);
|
||||
if (!array_key_exists($orderGuaranteeKey, $order)) {
|
||||
$order[$orderGuaranteeKey] = $this->orderGuarantee[$orderGuaranteeKey];
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限控制-获取有权限访问的管理员Ids
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function getDataLimitAdminIds(): array
|
||||
{
|
||||
if (!$this->dataLimit || $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从别名获取原始的逻辑运算符
|
||||
* @param string $operator 逻辑运算符别名
|
||||
* @return string 原始的逻辑运算符,无别名则原样返回
|
||||
*/
|
||||
protected function getOperatorByAlias(string $operator): string
|
||||
{
|
||||
$alias = [
|
||||
'ne' => '<>',
|
||||
'eq' => '=',
|
||||
'gt' => '>',
|
||||
'egt' => '>=',
|
||||
'lt' => '<',
|
||||
'elt' => '<=',
|
||||
];
|
||||
|
||||
return $alias[$operator] ?? $operator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
* @param string $msg 提示消息
|
||||
* @param mixed $data 返回数据
|
||||
* @param int $code 错误码
|
||||
* @param string|null $type 输出类型
|
||||
* @param array $header 发送的 header 信息
|
||||
* @param array $options Response 输出参数
|
||||
*/
|
||||
protected function success(string $msg = '', mixed $data = null, int $code = 0, string $type = null, array $header = [], array $options = []): void
|
||||
{
|
||||
$this->result($msg, $data, $code, $type, $header, $options);
|
||||
}
|
||||
|
||||
|
||||
protected function successWithData(mixed $data = null, string $msg = '', int $code = 0, string $type = null, array $header = [], array $options = []): void
|
||||
{
|
||||
$this->result($msg, $data, $code, $type, $header, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
* @param string $msg 提示消息
|
||||
* @param mixed $data 返回数据
|
||||
* @param int $code 错误码
|
||||
* @param string|null $type 输出类型
|
||||
* @param array $header 发送的 header 信息
|
||||
* @param array $options Response 输出参数
|
||||
*/
|
||||
protected function error(string $msg = '', mixed $data = null, int $code = -1, string $type = null, array $header = [], array $options = []): void
|
||||
{
|
||||
$this->result($msg, $data, $code, $type, $header, $options);
|
||||
}
|
||||
|
||||
protected function errorMsg(string $msg = '', mixed $data = null, int $code = -1, string $type = null, array $header = [], array $options = []): void
|
||||
{
|
||||
$this->result($msg, $data, $code, $type, $header, $options, 'data', 'msg');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
* @param string $msg 提示消息
|
||||
* @param mixed $data 返回数据
|
||||
* @param int $code 错误码
|
||||
* @param string|null $type 输出类型
|
||||
* @param array $header 发送的 header 信息
|
||||
* @param array $options Response 输出参数
|
||||
*/
|
||||
protected function n_success(array $data, string $msg = 'ok', int $code = 0, string $type = null, array $header = [], array $options = []): void
|
||||
{
|
||||
$this->resultApi($data, $msg, $code, $type, $header, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
* @param string $msg 提示消息
|
||||
* @param mixed $data 返回数据
|
||||
* @param int $code 错误码
|
||||
* @param string|null $type 输出类型
|
||||
* @param array $header 发送的 header 信息
|
||||
* @param array $options Response 输出参数
|
||||
*/
|
||||
protected function n_error(string $msg, array $data = [], int $code = -1, string $type = null, array $header = [], array $options = []): void
|
||||
{
|
||||
$this->resultApi($data, $msg, $code, $type, $header, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多种操作结果
|
||||
*/
|
||||
public function resultApi(array $data = [], string $msg = 'ok', int $code = 0, string $type = null, array $header = [], array $options = [])
|
||||
{
|
||||
$data['code'] = $code;
|
||||
$data['message'] = $msg;
|
||||
$data['time'] = time();
|
||||
if(isset($data['data']['records'])) {
|
||||
$data['data']['records'] = apiconvertToCamelCase($data['data']['records']);
|
||||
}
|
||||
if(isset($data['page']['list'])) {
|
||||
$data['page']['list'] = apiconvertToCamelCase($data['page']['list']);
|
||||
}
|
||||
if(isset($data['data']['list'])) {
|
||||
$data['data']['list'] = apiconvertToCamelCase($data['data']['list']);
|
||||
}
|
||||
if(isset($data['data']['userEntity'])) {
|
||||
$data['data']['userEntity'] = apiconvertToCamelCase($data['data']['userEntity']);
|
||||
}
|
||||
$result = $data;
|
||||
|
||||
throw new MyBusinessException(json_encode($result));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 返回 API 数据
|
||||
* @param string $msg 提示消息
|
||||
* @param mixed $data 返回数据
|
||||
* @param int $code 错误码
|
||||
* @param string|null $type 输出类型
|
||||
* @param array $header 发送的 header 信息
|
||||
* @param array $options Response 输出参数
|
||||
*/
|
||||
public function result(string $msg, mixed $data = null, int $code = 0, string $type = null, array $header = [], array $options = [], string $dataKey = 'data', string $msgKey = 'message')
|
||||
{
|
||||
if(isset($data['records'])) {
|
||||
$data['records'] = apiconvertToCamelCase($data['records']);
|
||||
}
|
||||
if(isset($data['page'])) {
|
||||
$data['page'] = apiconvertToCamelCase($data['page']);
|
||||
}
|
||||
if(isset($data['list'])) {
|
||||
$data['list'] = apiconvertToCamelCase($data['list']);
|
||||
}
|
||||
$result = [
|
||||
'code' => $code,
|
||||
'message' => $msg,
|
||||
'time' => time(),
|
||||
$dataKey => $data,
|
||||
];
|
||||
throw new MyBusinessException(json_encode($result));
|
||||
}
|
||||
|
||||
public function ApiDataReturn($data)
|
||||
{
|
||||
return json($data);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user