Initial commit
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
<?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('删除失败');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user