Files
hyperf_data/extend/Aether/PHP/Hyperf/AetherCrudService.php
2025-09-25 09:12:22 +08:00

324 lines
9.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace Aether;
use Aether\Contract\TreeableInterface;
use Aether\Exception\BusinessException;
use Hyperf\Database\Model\Builder;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use Throwable;
/**
* 抽象CRUD服务基类封装通用逻辑.
*/
abstract class AetherCrudService extends AetherService implements AetherCrudInterface
{
protected array $search = [];
protected array $ignoreSearchFields = [];
/**
* 通用列表查询(支持分页和树形结构).
*/
public function list(array $params = []): array
{
$model = $this->getModel();
$query = $model->newQuery();
// 通过模型配置自动应用所有搜索条件
$this->applySearch($query, $params);
// 动态应用排序
$sortConfig = $model->getSortConfig();
if ($sortConfig) {
$query->orderBy($sortConfig['field'], $sortConfig['direction']);
}
$withDeleted = filter_var($params['with_deleted'] ?? false, FILTER_VALIDATE_BOOLEAN);
if ($withDeleted) {
$query->withTrashed();
}
// 存在分页参数page或size则进行分页查询
if (isset($params['page']) || isset($params['size'])) {
$page = (int) ($params['page'] ?? 1);
$size = (int) ($params['size'] ?? 10);
// 确保分页参数合法性
$page = max(1, $page);
$size = max(1, min(100, $size)); // 限制最大页大小为100
$result = $query->paginate($size, ['*'], 'page', $page);
return [
'total' => $result->total(),
'list' => $result->items(),
];
}
// 无分页参数时返回完整数据集合
$items = $query->get()->toArray();
// 若模型支持树形结构则构建树形,否则返回普通数组
if ($model instanceof TreeableInterface) {
return $model::buildTree($items, (int) ($params['parent_id'] ?? 0));
}
return $items;
}
/**
* 通用详情查询.
*/
public function detail(int $id): object
{
return $this->getModel()->findOrFailById($id);
}
/**
* 通用创建逻辑.
* @throws BusinessException|Throwable
*/
public function create(array $data): int
{
// 数据验证(使用子类指定的验证器)
$this->getValidator()->scene('create', $data)->check();
return $this->transaction(function () use ($data) {
$model = $this->getModel()->createOne($data);
$this->logger()->info('创建资源', [
'id' => $model->id,
'code' => $data['code'] ?? $model->code,
]);
return $model->id;
});
}
/**
* 通用更新逻辑.
* @throws BusinessException|Throwable
*/
public function update(int $id, array $data): bool
{
$model = $this->getModel();
$resource = $model->findById($id);
$this->checkResourceExists($resource);
// 数据验证
$this->getValidator()->scene('update', $data)->check();
// 钩子:处理更新时的特殊逻辑(如禁止自身为父级)
$this->handleUpdateSpecialLogic($id, $data);
return $this->transaction(function () use ($id, $data) {
$result = $this->getModel()->updateById($id, $data);
$this->logger()->info('更新资源', [
'id' => $id,
'data' => $data,
]);
return $result;
});
}
/**
* 通用删除逻辑.
* @throws BusinessException|Throwable
*/
public function delete(int $id): bool
{
$model = $this->getModel();
$resource = $model->findById($id);
$this->checkResourceExists($resource);
// 钩子:删除前检查(如子级存在性)
$this->checkChildrenBeforeDelete($id);
return $this->transaction(function () use ($id) {
$result = $this->getModel()->deleteById($id);
$this->logger()->info('删除资源', ['id' => $id]);
return $result;
});
}
/**
* 根据模型的$search配置自动应用搜索条件到查询构建器.
*/
public function applySearch(Builder $query, array $params): void
{
foreach ($this->search as $field => $rule) {
// 跳过未传递的参数
if (! isset($params[$field])) {
continue;
}
$value = $params[$field];
$this->applySearchRule($query, $field, $value, $rule);
}
}
/**
* 软删除恢复.
* @throws BusinessException|Throwable
*/
public function restore(int $id): bool
{
$model = $this->getModel();
// 必须使用withTrashed()才能查询到已删除记录
$resource = $model->newQuery()->withTrashed()->find($id);
$this->checkResourceExists($resource, '恢复的资源不存在');
return $this->transaction(function () use ($id) {
$result = $this->getModel()->newQuery()->withTrashed()
->where('id', $id)->restore();
$this->logger()->info('恢复软删除资源', ['id' => $id]);
return $result;
});
}
/**
* 批量软删除.
* @throws BusinessException|Throwable
*/
public function batchDelete(array $ids): bool
{
if (empty($ids)) {
throw new BusinessException('请选择要删除的记录', 400);
}
$model = $this->getModel();
$exists = $model->whereIn('id', $ids)->exists();
if (! $exists) {
throw new BusinessException('部分记录不存在', 404);
}
return $this->transaction(function () use ($ids) {
$result = $this->getModel()->whereIn('id', $ids)->delete();
$this->logger()->info('批量软删除资源', ['ids' => $ids]);
return $result > 0;
});
}
/**
* 批量恢复软删除.
* @throws BusinessException|Throwable
*/
public function batchRestore(array $ids): bool
{
if (empty($ids)) {
throw new BusinessException('请选择要恢复的记录', 400);
}
$model = $this->getModel();
$exists = $model->newQuery()->withTrashed()
->whereIn('id', $ids)
->whereNotNull('deleted_at')
->exists();
if (! $exists) {
throw new BusinessException('部分记录不存在或未被删除', 404);
}
return $this->transaction(function () use ($ids) {
$result = $this->getModel()->newQuery()->withTrashed()
->whereIn('id', $ids)->restore();
$this->logger()->info('批量恢复资源', ['ids' => $ids]);
return $result > 0;
});
}
/**
* 永久删除(物理删除).
* @throws BusinessException|Throwable
*/
public function forceDelete(int $id): bool
{
$model = $this->getModel();
$resource = $model->newQuery()->withTrashed()->find($id);
$this->checkResourceExists($resource, '要删除的资源不存在');
return $this->transaction(function () use ($id) {
$result = $this->getModel()->newQuery()->withTrashed()
->where('id', $id)->forceDelete();
$this->logger()->info('永久删除资源', ['id' => $id]);
return $result;
});
}
protected function getSearch(): array
{
return $this->search;
}
protected function getIgnoreSearchFields(): array
{
return $this->ignoreSearchFields;
}
/**
* 获取当前服务对应的模型实例(由子类实现).
*/
abstract protected function getModel(): AetherModel;
/**
* 获取当前服务对应的验证器实例(由子类实现).
*/
abstract protected function getValidator(): AetherValidator;
/**
* 钩子方法:更新时的特殊逻辑(子类可重写).
*/
protected function handleUpdateSpecialLogic(int $id, array &$data): void
{
// 通用逻辑禁止将自身设为父级适用于有parent_id的场景
if (isset($data['parent_id']) && $data['parent_id'] == $id) {
throw new BusinessException('不能将自身设为父级', 400);
}
}
/**
* 钩子方法:删除前检查子级(子类可重写).
*/
protected function checkChildrenBeforeDelete(int $id): void
{
// 默认不检查,需要的子类重写
}
/**
* 应用单个搜索规则.
* @param mixed $value
* @param mixed $rule
*/
protected function applySearchRule(Builder $query, string $field, $value, $rule): void
{
// 处理规则格式(支持字符串简写或数组配置)
$config = is_array($rule) ? $rule : ['type' => $rule];
$type = $config['type'];
switch ($type) {
case '=': // 精确匹配
$query->where($field, $value);
break;
case 'like': // 模糊匹配
$query->where($field, 'like', "%{$value}%");
break;
case 'between': // 范围查询(支持数组或两个参数)
$values = is_array($value) ? $value : [$value, $params[$field . '_end'] ?? $value];
$query->whereBetween($field, $values);
break;
case 'callback': // 自定义回调
if (isset($config['handler']) && is_callable($config['handler'])) {
call_user_func($config['handler'], $query, $value);
}
break;
// 可扩展其他类型in、>、< 等
}
}
}