优化
This commit is contained in:
@@ -20,9 +20,8 @@ abstract class AetherController
|
||||
#[Inject]
|
||||
protected ResponseInterface $response;
|
||||
|
||||
|
||||
/**
|
||||
* 获取资源列表 (RESTFul: GET /resources)
|
||||
* 获取资源列表 (RESTFul: GET resources/list).
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
@@ -32,7 +31,7 @@ abstract class AetherController
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个资源 (RESTFul: GET /resources/{id})
|
||||
* 获取单个资源 (RESTFul: GET resources/{id}).
|
||||
*/
|
||||
public function detail(int $id): array
|
||||
{
|
||||
@@ -41,7 +40,7 @@ abstract class AetherController
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建资源 (RESTFul: POST /resources)
|
||||
* 创建资源 (RESTFul: POST resources).
|
||||
*/
|
||||
public function create(): array
|
||||
{
|
||||
@@ -51,7 +50,7 @@ abstract class AetherController
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新资源 (RESTFul: PUT /resources/{id})
|
||||
* 更新资源 (RESTFul: PUT resources/{id}).
|
||||
*/
|
||||
public function update(int $id): array
|
||||
{
|
||||
@@ -61,7 +60,7 @@ abstract class AetherController
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除资源 (RESTFul: DELETE /resources/{id})
|
||||
* 删除资源 (RESTFul: DELETE resources/{id}).
|
||||
*/
|
||||
public function delete(int $id): array
|
||||
{
|
||||
@@ -70,8 +69,7 @@ abstract class AetherController
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应的服务类
|
||||
* @return AetherCrudService
|
||||
* 获取对应的服务类.
|
||||
*/
|
||||
abstract protected function getService(): AetherCrudService;
|
||||
}
|
||||
|
||||
@@ -18,20 +18,10 @@ use Throwable;
|
||||
*/
|
||||
abstract class AetherCrudService extends AetherService implements AetherCrudInterface
|
||||
{
|
||||
#[Inject]
|
||||
protected LoggerFactory $loggerFactory;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
protected array $search = [];
|
||||
|
||||
protected array $ignoreSearchFields = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logger = $this->loggerFactory->get($this->getLoggerName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用列表查询(支持分页和树形结构).
|
||||
*/
|
||||
@@ -85,7 +75,6 @@ abstract class AetherCrudService extends AetherService implements AetherCrudInte
|
||||
*/
|
||||
public function detail(int $id): object
|
||||
{
|
||||
$this->logger->info('获取资源详情', ['id' => $id]);
|
||||
return $this->getModel()->findOrFailById($id);
|
||||
}
|
||||
|
||||
@@ -262,14 +251,6 @@ abstract class AetherCrudService extends AetherService implements AetherCrudInte
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志名称.
|
||||
*/
|
||||
protected function getLoggerName(): string
|
||||
{
|
||||
return strtolower((new ReflectionClass($this))->getShortName());
|
||||
}
|
||||
|
||||
protected function getSearch(): array
|
||||
{
|
||||
return $this->search;
|
||||
|
||||
@@ -12,8 +12,8 @@ use Hyperf\Contract\LengthAwarePaginatorInterface;
|
||||
use Hyperf\Database\Model\Builder;
|
||||
use Hyperf\Database\Model\Collection;
|
||||
use Hyperf\Database\Model\ModelNotFoundException;
|
||||
use Hyperf\DbConnection\Model\Model as HyperfModel;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\DbConnection\Model\Model as HyperfModel;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\ModelCache\Cacheable;
|
||||
use Hyperf\ModelCache\CacheableInterface;
|
||||
@@ -60,13 +60,19 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
* 排序配置:
|
||||
* - false: 禁用排序
|
||||
* - 字符串: 排序字段(默认升序)
|
||||
* - 数组: ['field' => '字段名', 'direction' => 'asc/desc']
|
||||
* - 数组: ['field' => '字段名', 'direction' => 'asc/desc'].
|
||||
*/
|
||||
protected string|array|bool $sortable = 'sort'; // 默认按sort字段升序
|
||||
protected array|bool|string $sortable = 'sort'; // 默认按sort字段升序
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->bootBaseModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排序配置
|
||||
* @return array|null [field, direction] 或 null(禁用排序)
|
||||
* 获取排序配置.
|
||||
* @return null|array [field, direction] 或 null(禁用排序)
|
||||
*/
|
||||
public function getSortConfig(): ?array
|
||||
{
|
||||
@@ -78,19 +84,13 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
if (is_string($this->sortable)) {
|
||||
return [
|
||||
'field' => $this->sortable,
|
||||
'direction' => 'asc'
|
||||
'direction' => 'asc',
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->bootBaseModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷创建.
|
||||
*/
|
||||
@@ -108,7 +108,7 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷删除指定ID的记录
|
||||
* 快捷删除指定ID的记录.
|
||||
*
|
||||
* @param int $id 要删除的记录ID
|
||||
* @return bool 成功删除返回true
|
||||
@@ -117,7 +117,7 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
*/
|
||||
public static function deleteById(int $id): bool
|
||||
{
|
||||
if (!static::query()->where('id', $id)->exists()) {
|
||||
if (! static::query()->where('id', $id)->exists()) {
|
||||
throw new ModelNotFoundException(sprintf(
|
||||
'找不到ID为 %d 的 %s 记录',
|
||||
$id,
|
||||
@@ -129,7 +129,6 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
|
||||
/**
|
||||
* 快捷查找.
|
||||
* @param int $id
|
||||
* @return Builder|Builder[]|Collection|HyperfModel
|
||||
* @throws Exception 当删除操作发生其他错误时抛出
|
||||
* @throws ModelNotFoundException
|
||||
@@ -154,14 +153,14 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
public static function findOrFailById(int $id): static
|
||||
{
|
||||
$record = static::query()->find($id);
|
||||
if(is_null($record)) {
|
||||
if (is_null($record)) {
|
||||
throw new ModelNotFoundException(sprintf(
|
||||
'找不到ID为 %d 的 %s 记录',
|
||||
$id,
|
||||
static::class
|
||||
));
|
||||
}
|
||||
return $record;// static::query()->findOrFail($id);
|
||||
return $record; // static::query()->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,7 +405,7 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
|
||||
{
|
||||
foreach ($conditions as $field => $value) {
|
||||
// 跳过非字符串的字段名(防止索引数组键导致的类型错误)
|
||||
if (!is_string($field)) {
|
||||
if (! is_string($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Aether;
|
||||
|
||||
use function Hyperf\Support\env;
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
use Hyperf\Context\Context;
|
||||
|
||||
use function Hyperf\Support\env;
|
||||
|
||||
class ApiExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aether\Contract;
|
||||
|
||||
/**
|
||||
* 树形结构接口,标识模型支持树形功能
|
||||
* 树形结构接口,标识模型支持树形功能.
|
||||
*/
|
||||
interface TreeableInterface
|
||||
{
|
||||
/**
|
||||
* 构建树形结构
|
||||
* 构建树形结构.
|
||||
* @param array $items 原始数据
|
||||
* @param int $parentId 根节点ID
|
||||
* @return array
|
||||
*/
|
||||
public static function buildTree(array $items, int $parentId = 0): array;
|
||||
|
||||
/**
|
||||
* 获取子节点ID集合
|
||||
* 获取子节点ID集合.
|
||||
* @param int $id 节点ID
|
||||
* @return array
|
||||
*/
|
||||
public function getChildIds(int $id): array;
|
||||
}
|
||||
@@ -5,14 +5,15 @@ declare(strict_types=1);
|
||||
namespace Aether\Exception;
|
||||
|
||||
use Aether\AetherValidator;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Database\Model\ModelNotFoundException; // 引入模型未找到异常
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\Contract\StdoutLoggerInterface; // 引入模型未找到异常
|
||||
use Hyperf\Database\Model\ModelNotFoundException;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\Validation\ValidationException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
use Hyperf\Context\Context;
|
||||
|
||||
use function Hyperf\Support\env;
|
||||
|
||||
class AetherExceptionHandler extends ExceptionHandler
|
||||
@@ -52,6 +53,11 @@ class AetherExceptionHandler extends ExceptionHandler
|
||||
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function formatErrorResponse(Throwable $throwable, string $requestId): array
|
||||
{
|
||||
// 模型未找到异常
|
||||
@@ -61,10 +67,10 @@ class AetherExceptionHandler extends ExceptionHandler
|
||||
'message' => $throwable->getMessage() ?: '请求的资源不存在',
|
||||
'data' => env('APP_ENV') === 'dev' ? [
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine()
|
||||
'line' => $throwable->getLine(),
|
||||
] : null,
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -85,17 +91,20 @@ class AetherExceptionHandler extends ExceptionHandler
|
||||
'data' => env('APP_ENV') === 'dev' ? [
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
'trace' => explode("\n", $throwable->getTraceAsString())
|
||||
'trace' => explode("\n", $throwable->getTraceAsString()),
|
||||
] : null,
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatValidationError(ValidationFailedException $e, string $requestId): array
|
||||
{
|
||||
$validatorInstance = new class extends AetherValidator {
|
||||
protected function scenes(): array { return []; }
|
||||
protected function scenes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -104,17 +113,20 @@ class AetherExceptionHandler extends ExceptionHandler
|
||||
'data' => [
|
||||
'errors' => $validatorInstance->formatValidationErrors($e->validator),
|
||||
'scene' => $e->getScene(),
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null,
|
||||
],
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatNativeValidationError(ValidationException $e, string $requestId): array
|
||||
{
|
||||
$validatorInstance = new class extends AetherValidator {
|
||||
protected function scenes(): array { return []; }
|
||||
protected function scenes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -122,15 +134,10 @@ class AetherExceptionHandler extends ExceptionHandler
|
||||
'message' => '参数验证失败',
|
||||
'data' => [
|
||||
'errors' => $validatorInstance->formatValidationErrors($e->validator),
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null,
|
||||
],
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@ declare(strict_types=1);
|
||||
namespace Aether\Exception;
|
||||
|
||||
use Aether\AetherValidator;
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\Validation\ValidationException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
use Hyperf\Context\Context;
|
||||
|
||||
use function Hyperf\Support\env;
|
||||
|
||||
class AppExceptionHandler extends ExceptionHandler
|
||||
@@ -39,8 +40,13 @@ class AppExceptionHandler extends ExceptionHandler
|
||||
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一错误响应格式
|
||||
* 统一错误响应格式.
|
||||
*/
|
||||
private function formatErrorResponse(Throwable $throwable, string $requestId): array
|
||||
{
|
||||
@@ -61,21 +67,24 @@ class AppExceptionHandler extends ExceptionHandler
|
||||
'data' => env('APP_ENV') === 'dev' ? [
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
'trace' => explode("\n", $throwable->getTraceAsString())
|
||||
'trace' => explode("\n", $throwable->getTraceAsString()),
|
||||
] : null,
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化自定义验证异常
|
||||
* 格式化自定义验证异常.
|
||||
*/
|
||||
private function formatValidationError(ValidationFailedException $e, string $requestId): array
|
||||
{
|
||||
// 复用AetherValidator的错误格式化方法
|
||||
$validatorInstance = new class extends AetherValidator {
|
||||
protected function scenes(): array { return []; }
|
||||
protected function scenes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -84,20 +93,23 @@ class AppExceptionHandler extends ExceptionHandler
|
||||
'data' => [
|
||||
'errors' => $validatorInstance->formatValidationErrors($e->validator),
|
||||
'scene' => $e->getScene(), // 直接从异常获取场景
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null,
|
||||
],
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化原生验证异常(保持格式一致)
|
||||
* 格式化原生验证异常(保持格式一致).
|
||||
*/
|
||||
private function formatNativeValidationError(ValidationException $e, string $requestId): array
|
||||
{
|
||||
$validatorInstance = new class extends AetherValidator {
|
||||
protected function scenes(): array { return []; }
|
||||
protected function scenes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -105,15 +117,10 @@ class AppExceptionHandler extends ExceptionHandler
|
||||
'message' => '参数验证失败',
|
||||
'data' => [
|
||||
'errors' => $validatorInstance->formatValidationErrors($e->validator),
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null
|
||||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null,
|
||||
],
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => time()
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5,25 +5,30 @@ declare(strict_types=1);
|
||||
namespace Aether\Exception;
|
||||
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
use Throwable;
|
||||
|
||||
class BusinessException extends ServerException
|
||||
{
|
||||
// 错误码常量(按业务模块划分)
|
||||
public const VALIDATION_ERROR = 400; // 参数验证失败
|
||||
|
||||
public const AUTH_ERROR = 401; // 认证失败
|
||||
|
||||
public const PERMISSION_DENY = 403; // 权限不足
|
||||
|
||||
public const RESOURCE_NOT_FOUND = 404; // 资源不存在
|
||||
|
||||
public const SCENE_NOT_FOUND = 400;
|
||||
|
||||
/**
|
||||
* 额外错误数据(如验证详情)
|
||||
* 额外错误数据(如验证详情).
|
||||
*/
|
||||
protected ?array $errorData = null;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
int $code = 500,
|
||||
?\Throwable $previous = null,
|
||||
?Throwable $previous = null,
|
||||
?array $errorData = null
|
||||
) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
@@ -31,7 +36,7 @@ class BusinessException extends ServerException
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取额外错误数据
|
||||
* 获取额外错误数据.
|
||||
*/
|
||||
public function getErrorData(): ?array
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aether\Exception;
|
||||
|
||||
use Hyperf\Validation\ValidationException;
|
||||
@@ -9,7 +11,7 @@ use Psr\Http\Message\ResponseInterface;
|
||||
class ValidationFailedException extends ValidationException
|
||||
{
|
||||
/**
|
||||
* 验证场景
|
||||
* 验证场景.
|
||||
*/
|
||||
protected string $scene;
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ declare(strict_types=1);
|
||||
namespace Aether;
|
||||
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\Rpc\Protocol;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
use Hyperf\Context\Context;
|
||||
|
||||
class RpcExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
@@ -29,7 +29,6 @@ class RpcExceptionHandler extends ExceptionHandler
|
||||
];
|
||||
|
||||
$protocol = ApplicationContext::getContainer()->get(Protocol::class);
|
||||
// $response->getBody()->write($protocol->pack($data));
|
||||
$response->getBody()->write($protocol->pack($data));
|
||||
|
||||
return $response;
|
||||
|
||||
@@ -59,7 +59,7 @@ trait AetherEnum
|
||||
/**
|
||||
* 根据值获取枚举实例(严格模式).
|
||||
* @param int|string $value 枚举值
|
||||
* @return AetherEnum|NoticeStatsModel|NoticeStatusEnum 枚举实例
|
||||
* @return AetherEnum 枚举实例
|
||||
*/
|
||||
public static function fromValue(int|string $value): self
|
||||
{
|
||||
@@ -95,7 +95,7 @@ trait AetherEnum
|
||||
/**
|
||||
* 根据描述获取枚举实例(精确匹配).
|
||||
* @param string $description 描述文本
|
||||
* @return null|AetherEnum|NoticeStatsModel|NoticeStatusEnum 匹配的枚举实例,无匹配时返回null
|
||||
* @return null|AetherEnum 匹配的枚举实例,无匹配时返回null
|
||||
*/
|
||||
public static function fromDescription(string $description): ?self
|
||||
{
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aether\Traits;
|
||||
|
||||
use Hyperf\Database\Model\SoftDeletes;
|
||||
|
||||
/**
|
||||
* 通用软删除Trait,供需要软删除的模型使用
|
||||
* 通用软删除Trait,供需要软删除的模型使用.
|
||||
*/
|
||||
trait AetherSoftDelete
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 初始化软删除相关配置(自动隐藏deleted_at字段)
|
||||
* 初始化软删除相关配置(自动隐藏deleted_at字段).
|
||||
*/
|
||||
protected function initializeAetherSoftDeletes(): void
|
||||
{
|
||||
// 自动将deleted_at添加到隐藏字段,避免序列化时暴露
|
||||
if (!in_array('deleted_at', $this->hidden, true)) {
|
||||
if (! in_array('deleted_at', $this->hidden, true)) {
|
||||
$this->hidden[] = 'deleted_at';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aether\Traits;
|
||||
|
||||
use Aether\AetherModel;
|
||||
@@ -13,25 +15,16 @@ trait AetherTree
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
|
||||
if (!$this instanceof AetherModel) {
|
||||
if (! $this instanceof AetherModel) {
|
||||
throw new LogicException(
|
||||
"使用AetherTree trait的类必须继承AetherModel,当前类: " . get_class($this)
|
||||
'使用AetherTree trait的类必须继承AetherModel,当前类: ' . get_class($this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽象方法:获取父ID字段名(由子类实现)
|
||||
*/
|
||||
abstract protected function getParentIdField(): string;
|
||||
|
||||
/**
|
||||
* 抽象方法:获取排序字段名(由子类实现)
|
||||
*/
|
||||
abstract protected function getSortField(): string;
|
||||
|
||||
/**
|
||||
* 构建树形结构
|
||||
* 构建树形结构.
|
||||
* @param mixed $items
|
||||
*/
|
||||
public static function buildTree($items, int $parentId = 0): array
|
||||
{
|
||||
@@ -45,7 +38,7 @@ trait AetherTree
|
||||
foreach ($items as $item) {
|
||||
if ($item[$parentField] == $parentId) {
|
||||
$children = static::buildTree($items, $item['id']);
|
||||
if (!empty($children)) {
|
||||
if (! empty($children)) {
|
||||
$item['children'] = $children;
|
||||
}
|
||||
$tree[] = $item;
|
||||
@@ -57,20 +50,7 @@ trait AetherTree
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形节点排序
|
||||
*/
|
||||
protected function sortTreeItems(array &$items, string $sortField): void
|
||||
{
|
||||
usort($items, function ($a, $b) use ($sortField) {
|
||||
$direction = $this->treeSortDirection ?? 'asc';
|
||||
return $direction === 'desc'
|
||||
? $b[$sortField] <=> $a[$sortField]
|
||||
: $a[$sortField] <=> $b[$sortField];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定节点的所有子节点ID
|
||||
* 获取指定节点的所有子节点ID.
|
||||
*/
|
||||
public function getChildIds(int $id): array
|
||||
{
|
||||
@@ -84,7 +64,57 @@ trait AetherTree
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集子节点ID
|
||||
* 获取节点的完整路径.
|
||||
*/
|
||||
public function getPath(int $id): array
|
||||
{
|
||||
$parentField = $this->getParentIdField();
|
||||
// 安全调用newQuery()
|
||||
$node = $this->newQuery()->find($id);
|
||||
if (! $node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$path = [$node->toArray()];
|
||||
$parentId = $node[$parentField];
|
||||
|
||||
while ($parentId > 0) {
|
||||
$parent = $this->newQuery()->find($parentId);
|
||||
if (! $parent) {
|
||||
break;
|
||||
}
|
||||
array_unshift($path, $parent->toArray());
|
||||
$parentId = $parent[$parentField];
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽象方法:获取父ID字段名(由子类实现).
|
||||
*/
|
||||
abstract protected function getParentIdField(): string;
|
||||
|
||||
/**
|
||||
* 抽象方法:获取排序字段名(由子类实现).
|
||||
*/
|
||||
abstract protected function getSortField(): string;
|
||||
|
||||
/**
|
||||
* 树形节点排序.
|
||||
*/
|
||||
protected function sortTreeItems(array &$items, string $sortField): void
|
||||
{
|
||||
usort($items, function ($a, $b) use ($sortField) {
|
||||
$direction = $this->treeSortDirection ?? 'asc';
|
||||
return $direction === 'desc'
|
||||
? $b[$sortField] <=> $a[$sortField]
|
||||
: $a[$sortField] <=> $b[$sortField];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集子节点ID.
|
||||
*/
|
||||
private function collectChildIds(array $items, int $parentId, string $parentField, array &$ids): void
|
||||
{
|
||||
@@ -95,31 +125,4 @@ trait AetherTree
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的完整路径
|
||||
*/
|
||||
public function getPath(int $id): array
|
||||
{
|
||||
$parentField = $this->getParentIdField();
|
||||
// 安全调用newQuery()
|
||||
$node = $this->newQuery()->find($id);
|
||||
if (!$node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$path = [$node->toArray()];
|
||||
$parentId = $node[$parentField];
|
||||
|
||||
while ($parentId > 0) {
|
||||
$parent = $this->newQuery()->find($parentId);
|
||||
if (!$parent) {
|
||||
break;
|
||||
}
|
||||
array_unshift($path, $parent->toArray());
|
||||
$parentId = $parent[$parentField];
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user