Files
usedcar/app/mzl/common/controller/BaseController.php
T

375 lines
11 KiB
PHP
Raw Normal View History

2025-05-14 13:19:23 +08:00
<?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('删除失败');
}
}