样机小程序
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* 数据转换器 Trait
|
||||
* 用于将原始数据(如模型、集合、分页等)统一转换为数组格式,
|
||||
* 并支持字段级与结果级的灵活后置加工。
|
||||
*/
|
||||
trait DataTransformer
|
||||
{
|
||||
/**
|
||||
* 核心转换方法
|
||||
*
|
||||
* @param mixed $rawData 原始数据:Model、Collection、Paginator、数组等
|
||||
* @param array $opts 可选配置:
|
||||
* - 'fieldFormatter' => callable 字段名格式化回调,如 fn($f)=>snake_case($f)
|
||||
* - 'postFormatter' => callable 结果级加工回调,接收整个数组并返回
|
||||
*
|
||||
* @return array 转换并加工后的纯 PHP 数组
|
||||
*/
|
||||
protected function transformData(
|
||||
mixed $rawData,
|
||||
?callable $postFormatter = null,
|
||||
?callable $fieldFormatter = null
|
||||
): array {
|
||||
try {
|
||||
$fieldFormatter = $fieldFormatter ?: fn($field) => $field;
|
||||
if ($rawData instanceof LengthAwarePaginator) {
|
||||
$arr = $this->formatPaginated($rawData, $postFormatter, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Model) {
|
||||
$arr = $this->formatModel($rawData, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Collection) {
|
||||
$arr = $this->formatCollection($rawData, $fieldFormatter);
|
||||
} elseif (is_array($rawData)) {
|
||||
$arr = $this->formatArray($rawData, $fieldFormatter);
|
||||
} else {
|
||||
$arr = (array)$rawData;
|
||||
}
|
||||
|
||||
if (is_callable($postFormatter)) {
|
||||
$arr = $postFormatter($arr);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'error' => true,
|
||||
'message' => $e->getMessage(),
|
||||
// 'message' => $e->getMessage()."\n".$e->getLine()."\n".$e->getFile(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化分页数据:转换每条记录
|
||||
*
|
||||
* @param LengthAwarePaginator $p
|
||||
* @param callable $fmt
|
||||
* @return array{records: array, total: int, current: int, size: int}
|
||||
*/
|
||||
protected function formatPaginated(
|
||||
LengthAwarePaginator $p,
|
||||
?callable $postFormatter,
|
||||
callable $fmt
|
||||
): array {
|
||||
$records = $p->getCollection()
|
||||
->map(fn($item) => $this->transformData($item, $postFormatter, $fmt))
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
'records' => $records,
|
||||
'total' => $p->total(),
|
||||
'current' => $p->currentPage(),
|
||||
'size' => $p->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Eloquent 模型:主表属性 + 关联递归
|
||||
*
|
||||
* @param Model $model
|
||||
* @param callable $fmt
|
||||
* @return array
|
||||
*/
|
||||
protected function formatModel(
|
||||
Model $model,
|
||||
callable $fmt
|
||||
): array {
|
||||
$out = [];
|
||||
// 1) 主表字段
|
||||
foreach ($model->getAttributes() as $key => $value) {
|
||||
$out[$fmt($key)] = $value;
|
||||
}
|
||||
// 2) 关联递归
|
||||
foreach ($model->getRelations() as $rel => $relData) {
|
||||
if ($relData instanceof Model) {
|
||||
$out[$rel] = $this->formatModel($relData, $fmt);
|
||||
} elseif ($relData instanceof Collection) {
|
||||
$out[$rel] = $relData
|
||||
->map(fn($item) => $this->transformData($item, null, $fmt))
|
||||
->toArray();
|
||||
} else {
|
||||
$out[$rel] = $relData;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化集合数据:对每个元素递归调用 transformData
|
||||
*
|
||||
* @param Collection $c
|
||||
* @param callable $fmt
|
||||
* @return array
|
||||
*/
|
||||
protected function formatCollection(
|
||||
Collection $c,
|
||||
callable $fmt
|
||||
): array {
|
||||
return $c
|
||||
->map(fn($item) => $this->transformData($item, null, $fmt))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化普通数组:递归处理可能出现的模型或集合
|
||||
*
|
||||
* @param array $arr
|
||||
* @param callable $fmt
|
||||
* @return array
|
||||
*/
|
||||
protected function formatArray(
|
||||
array $arr,
|
||||
callable $fmt
|
||||
): array {
|
||||
return array_map(function($item) use ($fmt) {
|
||||
if (is_array($item)
|
||||
|| $item instanceof Model
|
||||
|| $item instanceof Collection
|
||||
|| $item instanceof LengthAwarePaginator
|
||||
) {
|
||||
return $this->transformData($item, null, $fmt);
|
||||
}
|
||||
return $item;
|
||||
}, $arr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use support\Response;
|
||||
|
||||
trait ResponseTrait
|
||||
{
|
||||
/**
|
||||
* 返回成功响应
|
||||
*
|
||||
* @param mixed $data 响应数据
|
||||
* @param string $message 响应消息,默认“请求成功”
|
||||
* @param int $code 内部状态码,默认 0 表示成功
|
||||
* @param int $httpStatus HTTP 状态码,默认 200
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function success(
|
||||
$data = [],
|
||||
string $message = '请求成功',
|
||||
string $status = 'success',
|
||||
int $code = 200,
|
||||
int $httpStatus = 200,
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result($status, $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误响应
|
||||
*
|
||||
* @param string $message 响应错误消息,默认“请求失败”
|
||||
* @param int $code 内部状态码,默认 1 表示错误
|
||||
* @param int $httpStatus HTTP 状态码,默认 400
|
||||
* @param mixed $data 可选的错误数据
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function error(
|
||||
string $message = '请求失败',
|
||||
int $code = 400,
|
||||
int $httpStatus = 400,
|
||||
$data = [],
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('fail', $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一响应格式方法
|
||||
*
|
||||
* @param string $status 响应状态,例如 "success" 或 "fail"
|
||||
* @param int $code 内部状态码
|
||||
* @param int $httpStatus HTTP 状态码
|
||||
* @param string $message 响应消息
|
||||
* @param mixed $data 响应数据
|
||||
* @param array $header 响应头
|
||||
* @return Response
|
||||
*/
|
||||
protected function result(
|
||||
string $status,
|
||||
int $code,
|
||||
int $httpStatus,
|
||||
string $message,
|
||||
$data,
|
||||
array $header
|
||||
): Response {
|
||||
$body = [
|
||||
'status' => $status,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
];
|
||||
return response(json_encode($body, JSON_UNESCAPED_UNICODE), $httpStatus, $header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* CRUD 操作控制器 Trait
|
||||
* 提供通用的增删改查控制器方法,通过调用 service 层实现业务逻辑解耦。
|
||||
*/
|
||||
trait CrudControllerTrait
|
||||
{
|
||||
/**
|
||||
* 获取列表
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回分页数据及总数
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceList($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'indexFormat')
|
||||
? [$this, 'indexFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param int|string $id 资源 ID
|
||||
* @return mixed 返回资源详情数据
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceShow($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'showFormat')
|
||||
? [$this, 'showFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回创建成功的数据或信息
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
|
||||
$res = $this->service->serviceStore($request, $data);
|
||||
return $this->success($res, '创建成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回更新成功的数据或信息
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'update');
|
||||
$res = $this->service->serviceUpdate($request, $data);
|
||||
return $this->success([], '更新成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回删除操作结果
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
try {
|
||||
$count = $this->service->serviceDestroy($request);
|
||||
return $this->success([], "成功删除了 {$count} 条");
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 以下三个钩子方法,**在使用此 Trait 的 Controller** 中实现
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 字段名格式化:例如全部下划线
|
||||
*
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
// protected function fieldFormatter(string $field): string
|
||||
// {
|
||||
// return snake_case($field);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 字段级后置加工规则
|
||||
*
|
||||
* @return array<string,callable>
|
||||
* 键为 “点号嵌套”或 “通配符” 字段路径,
|
||||
* 值为 fn($value, $row)=>mixed
|
||||
*/
|
||||
// protected function getFieldTransforms(): array
|
||||
// {
|
||||
// return [
|
||||
// 'name' => fn($v,$row)=>strtoupper($v),
|
||||
// 'orders.*.total' => fn($v,$row)=>number_format($v,2),
|
||||
// ];
|
||||
// }
|
||||
|
||||
/**
|
||||
* 结果级后置加工:接收整个数组,可任意增删改
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function postFormat(array $data): array
|
||||
// {
|
||||
// // 例如重命名 records 为 items
|
||||
// if (isset($data['records'])) {
|
||||
// $data['items'] = $data['records'];
|
||||
// unset($data['records']);
|
||||
// }
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use app\common\traits\crud\CrudUtil;
|
||||
|
||||
/**
|
||||
* Trait CrudLogicTrait
|
||||
*
|
||||
* 提供最基础的 CRUD 和复杂查询方法,基于 CrudUtil。
|
||||
* 使用此 Trait 的类,须定义:
|
||||
* protected string $modelClass = \App\common\model\YourModel::class;
|
||||
*/
|
||||
trait CrudLogicTrait
|
||||
{
|
||||
/**
|
||||
* 分页列表
|
||||
*/
|
||||
public function logicList(array $config, int $limit, int $page)
|
||||
{
|
||||
$config['model'] = $this->modelClass;
|
||||
return CrudUtil::paginate(CrudUtil::buildQuery($config), $limit, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量列表
|
||||
*/
|
||||
public function logicListAll(array $config)
|
||||
{
|
||||
$config['model'] = $this->modelClass;
|
||||
return CrudUtil::getAll(CrudUtil::buildQuery($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单条
|
||||
*/
|
||||
public function logicShow(array $config)
|
||||
{
|
||||
return CrudUtil::findOneBy(CrudUtil::buildQuery($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建(可带 dataLimit)
|
||||
*/
|
||||
public function logicCreate(array $data, array $dataLimit = [])
|
||||
{
|
||||
return CrudUtil::create($this->modelClass, $data, $dataLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意条件更新(可带 dataLimit)
|
||||
*/
|
||||
public function logicUpdateBy(array $config, array $data)
|
||||
{
|
||||
return CrudUtil::updateBy(CrudUtil::buildQuery($config), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意条件删除(可带 dataLimit)
|
||||
*/
|
||||
public function logicDeleteBy(array $config)
|
||||
{
|
||||
return CrudUtil::deleteBy(CrudUtil::buildQuery($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 复杂查询回调
|
||||
*/
|
||||
public function logicComplex(callable $callback)
|
||||
{
|
||||
return CrudUtil::executeComplex($this->modelClass, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键查询
|
||||
*/
|
||||
public function findByPk(int $id, array $with = [])
|
||||
{
|
||||
return CrudUtil::findByPk($this->modelClass, $id, $with);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* Trait CrudServiceTrait
|
||||
*
|
||||
* 提供标准的增删改查 Service 接口,依赖 CrudLogicTrait。
|
||||
* 使用此 Trait 的 Service 类,须:
|
||||
* use CrudLogicTrait, CrudServiceTrait;
|
||||
* protected string $modelClass = \App\common\model\YourModel::class;
|
||||
*/
|
||||
trait CrudServiceTrait
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/** 具体 Service 里定义:protected string $modelClass = YourModel::class; */
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return $this->validator;
|
||||
}
|
||||
|
||||
/** 分页列表 */
|
||||
public function serviceList(Request $request, string $scene = 'index')
|
||||
{
|
||||
$result = $this->cqyProcess(ucfirst($scene), $request);
|
||||
if ($result === false) {
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$limit = (int)$request->get('size', 15);
|
||||
$page = (int)$request->get('current', 1);
|
||||
$result = $this->logicList($config, $limit, $page);
|
||||
}
|
||||
return $this->postProcess(ucfirst($scene), $result);
|
||||
}
|
||||
|
||||
/** 全量列表 */
|
||||
public function serviceListAll(Request $request, string $scene = 'default')
|
||||
{
|
||||
$result = $this->cqyProcess(ucfirst($scene), $request);
|
||||
if ($result === false) {
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$result = $this->logicListAll($config);
|
||||
}
|
||||
return $this->postProcess(ucfirst($scene), $result);
|
||||
}
|
||||
|
||||
/** 查看单条 */
|
||||
public function serviceShow(Request $request, string $scene = 'show')
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$item = $this->logicShow($config);
|
||||
return $this->postProcess(ucfirst($scene), $item);
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
public function serviceStore(Request $request, array $data = [], string $scene = 'store')
|
||||
{
|
||||
$data = $this->preProcess(ucfirst($scene), $data, $request);
|
||||
$dl = $this->getDataLimitFromRequest($request);
|
||||
$created = $this->logicCreate($data, $dl);
|
||||
return $this->postProcess(ucfirst($scene), $created);
|
||||
}
|
||||
|
||||
/** 更新 */
|
||||
public function serviceUpdate(Request $request, array $data = [], string $scene = 'update')
|
||||
{
|
||||
$data = $this->preProcess(ucfirst($scene), $data, $request);
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess(ucfirst($scene), $data);
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
public function serviceDestroy(Request $request, string $scene = 'destroy')
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$deleted = $this->logicDeleteBy($config);
|
||||
return $this->postProcess(ucfirst($scene), $deleted);
|
||||
}
|
||||
|
||||
/** ----------------- 公共辅助方法 ----------------- */
|
||||
|
||||
/**
|
||||
* 构造 CrudUtil 查询配置
|
||||
*/
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'default':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'index':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['status'] = 1;
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/** 解析 withFields 字符串 "rel:field,field;..." */
|
||||
protected function parseWithFields(string $str): array
|
||||
{
|
||||
if (!$str) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (explode(';', $str) as $pair) {
|
||||
[$rel, $flds] = explode(':', $pair);
|
||||
$out[$rel] = explode(',', $flds);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 从请求读取 dataLimitType, dataLimitField */
|
||||
protected function getDataLimitFromRequest(Request $req): array
|
||||
{
|
||||
$type = $req->get('dataLimitType');
|
||||
if (!$type) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'type' => $type,
|
||||
'field' => $req->get('dataLimitField', 'admin_id'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* $data 前置处理
|
||||
*
|
||||
* @param array $data
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
protected function preProcess(string $op, mixed $data, Request $request): mixed
|
||||
{
|
||||
$hook = "beforeService{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($data, $request)
|
||||
: $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据操作类型,调用对应的后置钩子
|
||||
*
|
||||
* @param string $op 操作:List, Show, Store, Update, Destroy
|
||||
* @param mixed $data 原始返回值
|
||||
* @return mixed 处理后的返回值
|
||||
*/
|
||||
protected function postProcess(string $op, mixed $data): mixed
|
||||
{
|
||||
$hook = "afterService{$op}";
|
||||
if (method_exists($this, $hook)) {
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
array_shift($args);
|
||||
$params = array_merge([$data], $args);
|
||||
return call_user_func_array([$this, $hook], $params);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复杂/自定义查询
|
||||
*
|
||||
* @param string $op 场景
|
||||
* @param mixed $data 原始返回值
|
||||
* @return mixed 处理后的返回值
|
||||
*/
|
||||
protected function cqyProcess(string $op, Request $request): mixed
|
||||
{
|
||||
$hook = "complexQuery{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($request)
|
||||
: false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,808 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use support\exception\BusinessException;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 构建查询工具类
|
||||
* | 方法 | 说明 |
|
||||
* | ------------------------------- | --------------------- |
|
||||
* | `buildQuery($config)` | 构建动态查询(关联加载/字段/过滤/排序) |
|
||||
* | `paginate($q,$per,$page)` | 分页返回 Paginator |
|
||||
* | `getAll($q)` | 不分页直接 `get()` |
|
||||
* | `findByPk($model,$id)` | 根据主键查询单条,找不到抛 404 |
|
||||
* | `findOneBy($model,$where)` | 根据任意条件查询单条,找不到抛 404 |
|
||||
* | `deleteByPk($model,$ids)` | 根据主键删除(可批量),返回删除条数 |
|
||||
* | `deleteBy($model,$where)` | 根据任意条件删除,返回删除条数 |
|
||||
* | `updateByPk($model,$id,$data)` | 根据主键更新,找不到抛 404 |
|
||||
* | `updateBy($model,$where,$data)` | 根据任意条件更新,返回影响行数 |
|
||||
*/
|
||||
|
||||
class CrudUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* 构建查询,支持:
|
||||
* - 本表字段过滤 where
|
||||
* - 关联加载 with
|
||||
* - 关联表字段选择 withFields
|
||||
* - 关联表字段过滤 where with dot-notation
|
||||
* - 排序 order
|
||||
* - 本表字段选择 fields
|
||||
*
|
||||
* $config = [
|
||||
* 'model' => User::class,
|
||||
* 'where' => [
|
||||
* 'status' => 1,
|
||||
* 'name' => ['like','%张%'],
|
||||
* 'roles.name' => '管理员', // 关联表字段过滤
|
||||
* 'posts.created_at'=> ['>','2025-01-01'],
|
||||
* ],
|
||||
* 'order' => ['id'=>'desc'],
|
||||
* 'fields' => ['id','name','email'],
|
||||
* 'with' => ['roles','posts'],
|
||||
* 'withFields' => [
|
||||
* 'roles' => ['id','name'], // 关联表字段选择
|
||||
* 'posts' => ['id','title','user_id'],
|
||||
* ],
|
||||
* 'dataLimit' => [
|
||||
* 'type' => 'personal|same_role|role_tree|relation', // personal:个人数据,same_role:同角色,role_tree:角色树,relation:从属关联数据
|
||||
* 'userId' => 1,
|
||||
* 'roleId' => 2,
|
||||
* 'userField' => 'user_id',
|
||||
* 'roleField' => 'role_id'
|
||||
* ]
|
||||
* ];
|
||||
*/
|
||||
public static function buildQuery(array $config): Builder
|
||||
{
|
||||
/** @var Model $model */
|
||||
$model = new $config['model'];
|
||||
$q = $model->newQuery();
|
||||
|
||||
// eager-load:支持 ['rel','rel2'] 和 ['rel'=>fields[], ...]
|
||||
if (! empty($config['with'])) {
|
||||
foreach ($config['with'] as $rel => $fields) {
|
||||
if (is_int($rel)) {
|
||||
// 普通关联
|
||||
$q->with($fields);
|
||||
} else {
|
||||
// 带字段筛选的关联
|
||||
$q->with([$rel => function ($qr) use ($fields) {
|
||||
$key = $qr->getModel()->getKeyName();
|
||||
if (! in_array($key, $fields, true)) {
|
||||
$fields[] = $key;
|
||||
}
|
||||
$qr->select($fields);
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表字段
|
||||
if (!empty($config['fields'])) {
|
||||
$q->select($config['fields']);
|
||||
}
|
||||
|
||||
// where
|
||||
if (!empty($config['where'])) {
|
||||
foreach ($config['where'] as $col => $val) {
|
||||
if (strpos($col, '.') !== false) {
|
||||
// 多层支持
|
||||
$segments = explode('.', $col);
|
||||
$leaf = array_pop($segments);
|
||||
self::applyNestedWhere($q, $segments, $leaf, $val);
|
||||
} else {
|
||||
self::applyWhere($q, $col, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// or where
|
||||
if (!empty($config['orWhere'])) {
|
||||
$q->where(function($q) use($config) {
|
||||
foreach ($config['orWhere'] as $cond) {
|
||||
[$col,$op,$val] = $cond;
|
||||
$q->orWhere($col,$op,$val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (!empty($config['order'])) {
|
||||
foreach ($config['order'] as $col => $dir) {
|
||||
$q->orderBy($col, strtolower($dir) === 'asc' ? 'asc' : 'desc');
|
||||
}
|
||||
}
|
||||
|
||||
// 数据权限过滤
|
||||
if (!empty($config['dataLimit'])) {
|
||||
self::applyDataLimit($q, $config['dataLimit']);
|
||||
}
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param int $perPage
|
||||
* @param int $page
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginate(Builder $q, int $perPage, int $page)
|
||||
{
|
||||
return $q->paginate($perPage, ['*'], 'page', $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非分页查询
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public static function getAll(Builder $q)
|
||||
{
|
||||
return $q->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行复杂查询——仅在显式调用时生效
|
||||
*
|
||||
* @param string $modelClass 模型类
|
||||
* @param callable $callback function(Builder $q) { ... }
|
||||
* @return mixed 由 callback 返回的结果
|
||||
*/
|
||||
public static function executeComplex(string $modelClass, callable $cb)
|
||||
{
|
||||
/** @var Model $model */
|
||||
$model = new $modelClass;
|
||||
return $cb($model->newQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键单条查询
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param mixed $id
|
||||
* @param array $with
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function findByPk(string $modelClass, $id, array $with = []): Model
|
||||
{
|
||||
$m = $modelClass::with($with)->find($id);
|
||||
if (!$m) {
|
||||
throw new BusinessException("记录 ID={$id} 不存在", 404);
|
||||
}
|
||||
return $m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意条件单条查询,支持 dataLimit
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function findOneBy(Builder $q): Model
|
||||
{
|
||||
$model = $q->first();
|
||||
if (!$model) {
|
||||
throw new BusinessException("记录不存在", 404);
|
||||
}
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建,支持 dataLimit
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param array $data
|
||||
* @param array $dataLimit
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function create(
|
||||
string $modelClass,
|
||||
array $data,
|
||||
array $dataLimit = []
|
||||
): Model {
|
||||
// dataLimit 检查/注入
|
||||
if (!empty($dataLimit)) {
|
||||
self::applyDataLimitOnMutate($data, $dataLimit);
|
||||
}
|
||||
return $modelClass::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键更新,支持 dataLimit
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param mixed $id
|
||||
* @param array $data
|
||||
* @param array $dataLimit
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function updateByPk(
|
||||
string $modelClass,
|
||||
$id,
|
||||
array $data,
|
||||
array $dataLimit = []
|
||||
): Model {
|
||||
/** @var Model $m */
|
||||
$m = $modelClass::find($id);
|
||||
if (!$m) {
|
||||
throw new BusinessException("记录 ID={$id} 不存在", 404);
|
||||
}
|
||||
// dataLimit 校验
|
||||
if (!empty($dataLimit)) {
|
||||
self::applyDataLimitOnMutate($m, $dataLimit, $data);
|
||||
}
|
||||
$m->fill($data)->save();
|
||||
return $m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主键条件更新(批量),支持 dataLimit
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param array $data
|
||||
* @return int 受影响行数
|
||||
*/
|
||||
public static function updateBy(Builder $q, array $data): int
|
||||
{
|
||||
return $q->update($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键删除(支持批量)
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param array $ids
|
||||
* @return int 删除条数
|
||||
*/
|
||||
public static function deleteByPk(string $modelClass, array $ids): int
|
||||
{
|
||||
return $modelClass::destroy($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主键条件删除,支持 dataLimit
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return int 删除条数
|
||||
*/
|
||||
public static function deleteBy(Builder $q): int
|
||||
{
|
||||
return $q->delete();
|
||||
}
|
||||
|
||||
|
||||
//=================== Internal Methods ===================//
|
||||
|
||||
|
||||
/**
|
||||
* 支持多层的 whereHas:$relations=['roles','permissions'],$leaf='name'
|
||||
*/
|
||||
protected static function applyNestedWhere(Builder $q, array $relations, string $leaf, $val): void
|
||||
{
|
||||
$rel = array_shift($relations);
|
||||
$q->whereHas($rel, function (Builder $qr) use ($relations, $leaf, $val) {
|
||||
if ($relations) {
|
||||
// 还有更深层,继续递归
|
||||
self::applyNestedWhere($qr, $relations, $leaf, $val);
|
||||
} else {
|
||||
// 最后一层,使用普通 applyWhere
|
||||
self::applyWhere($qr, $leaf, $val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 单字段 where 支持:
|
||||
* - 多条件数组 [ [op1,val1], [op2,val2], … ]
|
||||
* - 单条件 ['op', val]
|
||||
* - 直接值
|
||||
*
|
||||
* @param Builder $q
|
||||
* @param string $col
|
||||
* @param mixed $val
|
||||
* @return void
|
||||
*/
|
||||
protected static function applyWhere(Builder $q, string $col, $val): void
|
||||
{
|
||||
// 多条件数组
|
||||
if (is_array($val) && isset($val[0]) && is_array($val[0])) {
|
||||
foreach ($val as $pair) {
|
||||
if (is_array($pair) && count($pair) === 2) {
|
||||
self::applySingleWhere($q, $col, $pair[0], $pair[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 单条件
|
||||
elseif (is_array($val) && count($val) === 2) {
|
||||
self::applySingleWhere($q, $col, $val[0], $val[1]);
|
||||
}
|
||||
// 直接等值
|
||||
else {
|
||||
$q->where($col, $val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行条件 where
|
||||
*/
|
||||
protected static function applySingleWhere(Builder $q, string $col, string $op, $v): void
|
||||
{
|
||||
switch (strtolower($op)) {
|
||||
case 'like':
|
||||
$q->where($col, 'like', $v);
|
||||
break;
|
||||
case 'in':
|
||||
$q->whereIn($col, (array)$v);
|
||||
break;
|
||||
case 'not in':
|
||||
$q->whereNotIn($col, (array)$v);
|
||||
break;
|
||||
case 'null':
|
||||
$q->whereNull($col);
|
||||
break;
|
||||
case 'not null':
|
||||
$q->whereNotNull($col);
|
||||
break;
|
||||
case 'between':
|
||||
$q->whereBetween($col, (array)$v);
|
||||
break;
|
||||
default:
|
||||
$q->where($col, $op, $v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================== Data Limit ==================
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询(buildQuery 场景)
|
||||
*/
|
||||
protected static function applyDataLimit(Builder $q, array $dataLimit): void
|
||||
{
|
||||
$type = $dataLimit['type'] ?? 'personal';
|
||||
$userId = $dataLimit['userId'] ?? 0;
|
||||
$roleId = $dataLimit['roleId'] ?? 0;
|
||||
$userField = $dataLimit['userField'] ?? 'user_id';
|
||||
$roleField = $dataLimit['roleField'] ?? 'role_id';
|
||||
|
||||
self::applyQueryPermission($q, $type, $userId, $roleId, $userField, $roleField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到"创建/更新"操作
|
||||
*
|
||||
* - $target 可能是 array|Model 对象
|
||||
* - $newData 仅在 updateByPk 场景时传入,用以校验变更归属
|
||||
*/
|
||||
protected static function applyDataLimitOnMutate(&$target, array $dataLimit, array $newData = []): void
|
||||
{
|
||||
$type = $dataLimit['type'] ?? 'personal';
|
||||
$userId = $dataLimit['userId'] ?? 0;
|
||||
$roleId = $dataLimit['roleId'] ?? 0;
|
||||
$userField = $dataLimit['userField'] ?? 'user_id';
|
||||
$roleField = $dataLimit['roleField'] ?? 'role_id';
|
||||
|
||||
self::applyMutationPermission($target, $type, $userId, $roleId, $newData, $userField, $roleField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询
|
||||
*
|
||||
* @param Builder $query 查询构造器
|
||||
* @param string $type 权限类型:superadmin, personal, same_role, role_tree, relation
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyQueryPermission(
|
||||
Builder $query,
|
||||
string $type,
|
||||
int $currentUserId = 0,
|
||||
int $currentRoleId = 0,
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要过滤
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
// 个人权限:只能访问自己的数据
|
||||
$query->where($userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
// 同角色权限:访问同角色的数据
|
||||
$userIds = self::getSameRoleUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
// 角色树权限:访问自己角色及子角色的数据
|
||||
$userIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
// 从属关系权限:访问自己及下属的数据
|
||||
$userIds = self::getRelationUserIds($currentUserId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 默认个人权限
|
||||
$query->where($userField, $currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到创建/更新操作
|
||||
*
|
||||
* @param mixed $target 创建时为数组,更新时为Model对象
|
||||
* @param string $type 权限类型
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param array $newData 更新时的数据
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyMutationPermission(
|
||||
&$target,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
array $newData = [],
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要检查
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
self::handlePersonalPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
self::handleSameRolePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
self::handleRoleTreePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
self::handleRelationPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BusinessException("未定义的数据权限类型: $type", 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同角色的用户ID列表
|
||||
*
|
||||
* @param int $roleId 角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getSameRoleUserIds(int $roleId): array
|
||||
{
|
||||
return Db::table('user_roles')
|
||||
->where('role_id', $roleId)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树下的所有用户ID列表
|
||||
*
|
||||
* @param int $roleId 当前角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRoleTreeUserIds(int $roleId): array
|
||||
{
|
||||
// 获取角色树中的所有角色ID
|
||||
$roleIds = self::getRoleTreeIds($roleId);
|
||||
|
||||
// 获取这些角色下的所有用户ID
|
||||
return Db::table('user_roles')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从属关系下的用户ID列表(包括自己和下属)
|
||||
*
|
||||
* @param int $userId 当前用户ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRelationUserIds(int $userId): array
|
||||
{
|
||||
// 获取所有下属用户ID(递归)
|
||||
$subordinateIds = self::getSubordinateIds($userId);
|
||||
|
||||
// 返回自己和所有下属的ID
|
||||
return array_merge([$userId], $subordinateIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直接下属的用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 直接下属用户ID列表
|
||||
*/
|
||||
public static function getDirectSubordinateIds(int $userId): array
|
||||
{
|
||||
return Db::table('user')
|
||||
->where('report_to', $userId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有下属用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 所有下属用户ID列表
|
||||
*/
|
||||
private static function getSubordinateIds(int $userId): array
|
||||
{
|
||||
$subordinates = self::getDirectSubordinateIds($userId);
|
||||
$allSubordinates = $subordinates;
|
||||
|
||||
foreach ($subordinates as $subordinateId) {
|
||||
$allSubordinates = array_merge($allSubordinates, self::getSubordinateIds($subordinateId));
|
||||
}
|
||||
|
||||
return $allSubordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理个人权限
|
||||
*/
|
||||
private static function handlePersonalPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
if (is_array($target)) {
|
||||
// 创建时强制设置用户ID
|
||||
$target[$userField] = $currentUserId;
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if ($target->{$userField} !== $currentUserId) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段
|
||||
if (isset($newData[$userField]) && $newData[$userField] !== $currentUserId) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理同角色权限
|
||||
*/
|
||||
private static function handleSameRolePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取同角色用户ID列表
|
||||
$sameRoleUserIds = self::getSameRoleUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时设置归属用户并校验
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if (!in_array($target->{$userField}, $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段到无权限的用户
|
||||
if (isset($newData[$userField]) && !in_array($newData[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理角色树权限
|
||||
*/
|
||||
private static function handleRoleTreePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取角色树用户ID列表
|
||||
$roleTreeUserIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理从属关系权限
|
||||
*/
|
||||
private static function handleRelationPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
// 获取从属关系用户ID列表
|
||||
$relationUserIds = self::getRelationUserIds($currentUserId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentUserId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $relationUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树ID(当前角色及其所有子角色)
|
||||
* 从数据库中动态获取角色树关系
|
||||
*/
|
||||
private static function getRoleTreeIds(int $roleId): array
|
||||
{
|
||||
static $roleTreeCache = [];
|
||||
|
||||
if (isset($roleTreeCache[$roleId])) {
|
||||
return $roleTreeCache[$roleId];
|
||||
}
|
||||
|
||||
// 从数据库中递归获取角色树
|
||||
$ids = [$roleId];
|
||||
$queue = [$roleId];
|
||||
|
||||
while (!empty($queue)) {
|
||||
$current = array_shift($queue);
|
||||
// 查找当前角色的所有子角色
|
||||
$childRoles = Db::table('roles')
|
||||
->where('parent_id', $current)
|
||||
->where('status', 1) // 只获取启用的角色
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
foreach ($childRoles as $childId) {
|
||||
if (!in_array($childId, $ids)) {
|
||||
$ids[] = $childId;
|
||||
$queue[] = $childId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roleTreeCache[$roleId] = $ids;
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为超管
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return bool 是否为超管
|
||||
*/
|
||||
public static function isSuperAdmin(int $userId): bool
|
||||
{
|
||||
// 检查用户是否关联了超级管理员角色
|
||||
// 设定角色ID为1的是超级管理员角色
|
||||
$superAdminRoleId = 1;
|
||||
|
||||
return Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->where('role_id', $superAdminRoleId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的角色ID
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return int 角色ID
|
||||
*/
|
||||
public static function getUserIdRoleId(int $userId): int
|
||||
{
|
||||
// 从 user_roles 表中获取当前用户的角色ID
|
||||
$roleId = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->value('role_id');
|
||||
|
||||
return $roleId ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有角色ID(包括继承的角色)
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 角色ID列表
|
||||
*/
|
||||
public static function getUserAllRoleIds(int $userId): array
|
||||
{
|
||||
// 获取用户直接关联的角色
|
||||
$directRoleIds = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->pluck('role_id')
|
||||
->toArray();
|
||||
|
||||
// 获取所有继承的角色
|
||||
$allRoleIds = [];
|
||||
foreach ($directRoleIds as $roleId) {
|
||||
$allRoleIds = array_merge($allRoleIds, self::getRoleTreeIds($roleId));
|
||||
}
|
||||
|
||||
// 去重并返回
|
||||
return array_unique($allRoleIds);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user