375 lines
11 KiB
PHP
375 lines
11 KiB
PHP
<?php
|
|
|
|
namespace app\mzl\common\controller;
|
|
|
|
use support\Request;
|
|
use support\Response;
|
|
use support\exception\BusinessException;
|
|
use app\mzl\common\traits\DataTransformer;
|
|
use app\mzl\common\traits\ResponseTrait;
|
|
use app\mzl\common\utils\SimpleQueryBuilder;
|
|
|
|
abstract class BaseController
|
|
{
|
|
use ResponseTrait, DataTransformer;
|
|
|
|
/**
|
|
* 可配置的验证器(类名/实例/闭包)
|
|
*/
|
|
protected mixed $validator = null;
|
|
|
|
/**
|
|
* 子类必须实现:返回对应的 Service 实例
|
|
*
|
|
* @return mixed Service 实例
|
|
*/
|
|
abstract protected function getService();
|
|
|
|
/**
|
|
* 获取请求参数
|
|
*
|
|
* @param Request $request 请求对象
|
|
* @return array 请求参数数组
|
|
*/
|
|
protected function input(Request $request): array
|
|
{
|
|
return $request->all();
|
|
}
|
|
|
|
/**
|
|
* 获取当前用户ID
|
|
*
|
|
* @return int|null 当前用户ID,如果未登录则返回null
|
|
*/
|
|
protected function getUserId(): ?int
|
|
{
|
|
return request()->user->user_id ?? null;
|
|
}
|
|
|
|
/**
|
|
* 注入字段
|
|
* 如果数据中不存在指定字段,则注入当前用户ID
|
|
*
|
|
* @param array $data 数据数组
|
|
* @param string $field 字段名,默认为 'user_id'
|
|
* @return array 注入字段后的数据数组
|
|
*/
|
|
protected function injectAdd(array $data, string $field = 'user_id'): array
|
|
{
|
|
return !isset($data[$field]) ? $data + [$field => $this->getUserId()] : $data;
|
|
}
|
|
|
|
/**
|
|
* 参数预处理入口,按场景分发
|
|
*
|
|
* @param Request $request
|
|
* @param string $scene
|
|
* @return array
|
|
*/
|
|
protected function prepareParams(Request $request, string $scene): array
|
|
{
|
|
// 默认所有场景都注入 user_id
|
|
return $this->injectAdd($request->all());
|
|
}
|
|
|
|
/**
|
|
* 后置处理单条记录:默认不变
|
|
*
|
|
* 子类可以重写这个方法来做额外字段加工,
|
|
* 比如加 computed 字段、日期格式化、脱敏等等。
|
|
*
|
|
* @param array $item 单条记录(pruned 后的数组)
|
|
* @return array 处理后的数组
|
|
*/
|
|
protected function postProcessItem(array $item, string $scene): array
|
|
{
|
|
return $item;
|
|
}
|
|
|
|
/**
|
|
* 根据 queryScenes 配置构造 fieldsMap
|
|
*
|
|
* @param array $config 必须包含 keys: fields, relations
|
|
* @return array
|
|
*/
|
|
// protected function buildFieldsMap2(array $config): array
|
|
// {
|
|
// $relations = $config['relations'] ?? [];
|
|
// $fieldsMap = [];
|
|
|
|
// // 根层:主表字段 + 所有一级关联名
|
|
// $rootFields = $config['fields'] ?? [];
|
|
// foreach (array_keys($relations) as $path) {
|
|
// $rootFields[] = explode('.', $path, 2)[0];
|
|
// }
|
|
// $fieldsMap[''] = array_values(array_unique($rootFields));
|
|
|
|
// // 每个关联层:自己字段 + 直接子关联名
|
|
// foreach ($relations as $path => $ownFields) {
|
|
// $fieldsMap[$path] = $ownFields;
|
|
// $segments = explode('.', $path);
|
|
// $depth = count($segments);
|
|
// if ($depth > 1) {
|
|
// for ($i = 1; $i < $depth; $i++) {
|
|
// $parent = implode('.', array_slice($segments, 0, $i));
|
|
// $child = $segments[$i];
|
|
// $fieldsMap[$parent][] = $child;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // 去重每一层
|
|
// foreach ($fieldsMap as $k => $arr) {
|
|
// $fieldsMap[$k] = array_values(array_unique($arr));
|
|
// }
|
|
|
|
// return $fieldsMap;
|
|
// }
|
|
// protected function buildFieldsMap(array $config): array
|
|
// {
|
|
// $relations = $config['relations'] ?? [];
|
|
|
|
// // 1) 处理主表层字段:把 "col as alias" -> alias
|
|
// $rawFields = $config['fields'] ?? [];
|
|
// $root = [];
|
|
// foreach ($rawFields as $f) {
|
|
// if (preg_match('/\s+as\s+/i', $f)) {
|
|
// // alias 是 as 之后的部分
|
|
// [$_, $alias] = preg_split('/\s+as\s+/i', $f);
|
|
// $root[] = $alias;
|
|
// } else {
|
|
// $root[] = $f;
|
|
// }
|
|
// }
|
|
// // 再把顶级关联名也加进来
|
|
// foreach (array_keys($relations) as $path) {
|
|
// $root[] = explode('.', $path, 2)[0];
|
|
// }
|
|
// $fieldsMap = ['' => array_values(array_unique($root))];
|
|
|
|
// // 2) 每个关联路径,自身字段 & 子关联名
|
|
// foreach ($relations as $path => $rawCols) {
|
|
// $arr = [];
|
|
// // 同样把 alias 拆出来
|
|
// foreach ($rawCols as $col) {
|
|
// if (preg_match('/\s+as\s+/i', $col)) {
|
|
// [, $alias] = preg_split('/\s+as\s+/i', $col);
|
|
// $arr[] = $alias;
|
|
// } else {
|
|
// $arr[] = $col;
|
|
// }
|
|
// }
|
|
// // 再把它的直接子关联名也加上
|
|
// $segments = explode('.', $path);
|
|
// $depth = count($segments);
|
|
// if ($depth > 1) {
|
|
// for ($i = 1; $i < $depth; $i++) {
|
|
// $parent = implode('.', array_slice($segments, 0, $i));
|
|
// $child = $segments[$i];
|
|
// $fieldsMap[$parent][] = $child;
|
|
// }
|
|
// }
|
|
// $fieldsMap[$path] = array_values(array_unique($arr));
|
|
// }
|
|
|
|
// return $fieldsMap;
|
|
// }
|
|
|
|
/**
|
|
* 验证请求
|
|
*
|
|
* @param array $data 请求数据
|
|
* @param string $scene 验证场景
|
|
* @return array 验证后的数据
|
|
* @throws BusinessException 验证失败时抛出业务异常
|
|
*/
|
|
protected function validateRequest(array $data, string $scene = ''): array
|
|
{
|
|
if (!$this->validator) {
|
|
return $data;
|
|
}
|
|
$validator = is_callable($this->validator)
|
|
? call_user_func($this->validator)
|
|
: (is_string($this->validator) ? new $this->validator : $this->validator);
|
|
|
|
if ($scene && method_exists($validator, 'scene')) {
|
|
$validator->scene($scene);
|
|
}
|
|
if (!$validator->check($data)) {
|
|
throw new BusinessException($validator->getError());
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* 列表
|
|
*
|
|
* @param Request $request 请求对象
|
|
* @param string $scene 场景名称,默认为 'default'
|
|
* @param callable|null $postProcessor 额外字段处理回调函数
|
|
* @return Response 响应对象
|
|
*/
|
|
protected function listWithTransform(
|
|
array $params,
|
|
string $scene = 'default',
|
|
bool $paginate = true
|
|
): Response {
|
|
$collection = $paginate
|
|
? $this->getService()->list($params, $scene)
|
|
: $this->getService()->all($params, $scene);
|
|
|
|
$config = $this->getService()->queryScenes()[$scene]($params);
|
|
// $fieldsMap = $this->buildFieldsMap($config);
|
|
|
|
// —— 如果 fields 为空,就不做任何裁剪,直接原样输出 ——
|
|
if (empty($config['fields'])) {
|
|
$data = $this->transformData(
|
|
$collection,
|
|
fn(array $item) => $this->postProcessItem($item, $scene)
|
|
);
|
|
return $this->successResponse($data);
|
|
}
|
|
|
|
$fieldsMap = SimpleQueryBuilder::buildFieldsMap($config);
|
|
|
|
$result = $this->transformAndPrune(
|
|
$collection,
|
|
$fieldsMap,
|
|
null,
|
|
fn(array $item) => $this->postProcessItem($item, $scene)
|
|
);
|
|
|
|
return $this->successResponse($result);
|
|
}
|
|
|
|
/**
|
|
* 获取资源列表
|
|
*
|
|
* @param Request $request 请求对象
|
|
* @param string $scene 场景名称,默认为 'default'
|
|
* @return Response 响应对象
|
|
*/
|
|
public function index(Request $request, string $scene = 'default'): Response
|
|
{
|
|
return $this->listWithTransform(
|
|
$this->prepareParams($request, $scene),
|
|
$scene
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 获取所有资源(不分页)
|
|
*
|
|
* @param Request $request 请求对象
|
|
* @param string $scene 场景名称,默认为 'default'
|
|
* @return Response 响应对象
|
|
*/
|
|
public function all(Request $request, string $scene = 'default'): Response
|
|
{
|
|
return $this->listWithTransform(
|
|
$this->prepareParams($request, $scene),
|
|
$scene,
|
|
false
|
|
);
|
|
}
|
|
|
|
public function oneWithTransform(array $params, string $scene = 'default', int $id = 0): Response
|
|
{
|
|
$model = $id
|
|
? $this->getService()->getById($id, $params, $scene)
|
|
: $this->getService()->getOne($params, $scene);
|
|
|
|
if (!$model) {
|
|
return $this->errorResponse('资源不存在', 1, 404);
|
|
}
|
|
|
|
$config = $this->getService()->queryScenes()[$scene]($params);
|
|
// $fieldsMap = $this->buildFieldsMap($config);
|
|
|
|
// —— 如果 fields 为空,就不做任何裁剪,直接原样输出 ——
|
|
if (empty($config['fields'])) {
|
|
$data = $this->transformData(
|
|
$model,
|
|
fn(array $item) => $this->postProcessItem($item, $scene)
|
|
);
|
|
return $this->successResponse($data);
|
|
}
|
|
|
|
$fieldsMap = SimpleQueryBuilder::buildFieldsMap($config);
|
|
|
|
$data = $this->transformAndPrune(
|
|
$model,
|
|
$fieldsMap,
|
|
null,
|
|
fn(array $item) => $this->postProcessItem($item, $scene)
|
|
);
|
|
|
|
return $this->successResponse($data);
|
|
}
|
|
|
|
/**
|
|
* GET /resource/{id}
|
|
*/
|
|
public function show(Request $request, int $id, string $scene = 'default'): Response
|
|
{
|
|
return $this->oneWithTransform(
|
|
$this->prepareParams($request, $scene),
|
|
$scene,
|
|
$id
|
|
);
|
|
}
|
|
|
|
/**
|
|
* GET /resource/one (按任意条件取单条)
|
|
*/
|
|
public function one(Request $request, string $scene = 'default'): Response
|
|
{
|
|
return $this->oneWithTransform(
|
|
$this->prepareParams($request, $scene),
|
|
$scene
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 创建新资源
|
|
*
|
|
* @param Request $request 请求对象
|
|
* @return Response 响应对象
|
|
*/
|
|
public function store(Request $request): Response
|
|
{
|
|
$data = $this->validateRequest($request->all(), 'store');
|
|
$model = $this->getService()->create($data);
|
|
return $this->successResponse($this->transformData($model));
|
|
}
|
|
|
|
/**
|
|
* 更新现有资源
|
|
*
|
|
* @param Request $request 请求对象
|
|
* @param int $id 资源ID
|
|
* @return Response 响应对象
|
|
*/
|
|
public function update(Request $request, int $id): Response
|
|
{
|
|
$data = $this->validateRequest($request->all(), 'update');
|
|
$result = $this->getService()->update($id, $data);
|
|
return $result
|
|
? $this->successResponse(['id' => $id])
|
|
: $this->errorResponse('更新失败');
|
|
}
|
|
|
|
/**
|
|
* 删除资源
|
|
*
|
|
* @param int $id 资源ID
|
|
* @return Response 响应对象
|
|
*/
|
|
public function destroy(int $id): Response
|
|
{
|
|
$result = $this->getService()->delete($id);
|
|
return $result
|
|
? $this->successResponse(['id' => $id])
|
|
: $this->errorResponse('删除失败');
|
|
}
|
|
} |