This commit is contained in:
Aether
2025-09-25 09:12:22 +08:00
parent f286e18e71
commit 25aa56439a
13 changed files with 171 additions and 167 deletions

View File

@@ -20,9 +20,8 @@ abstract class AetherController
#[Inject] #[Inject]
protected ResponseInterface $response; protected ResponseInterface $response;
/** /**
* 获取资源列表 (RESTFul: GET /resources) * 获取资源列表 (RESTFul: GET resources/list).
*/ */
public function index(): array 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 public function detail(int $id): array
{ {
@@ -41,7 +40,7 @@ abstract class AetherController
} }
/** /**
* 创建资源 (RESTFul: POST /resources) * 创建资源 (RESTFul: POST resources).
*/ */
public function create(): array 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 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 public function delete(int $id): array
{ {
@@ -70,8 +69,7 @@ abstract class AetherController
} }
/** /**
* 获取对应的服务类 * 获取对应的服务类.
* @return AetherCrudService
*/ */
abstract protected function getService(): AetherCrudService; abstract protected function getService(): AetherCrudService;
} }

View File

@@ -18,20 +18,10 @@ use Throwable;
*/ */
abstract class AetherCrudService extends AetherService implements AetherCrudInterface abstract class AetherCrudService extends AetherService implements AetherCrudInterface
{ {
#[Inject]
protected LoggerFactory $loggerFactory;
protected LoggerInterface $logger;
protected array $search = []; protected array $search = [];
protected array $ignoreSearchFields = []; 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 public function detail(int $id): object
{ {
$this->logger->info('获取资源详情', ['id' => $id]);
return $this->getModel()->findOrFailById($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 protected function getSearch(): array
{ {
return $this->search; return $this->search;

View File

@@ -12,8 +12,8 @@ use Hyperf\Contract\LengthAwarePaginatorInterface;
use Hyperf\Database\Model\Builder; use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Collection; use Hyperf\Database\Model\Collection;
use Hyperf\Database\Model\ModelNotFoundException; use Hyperf\Database\Model\ModelNotFoundException;
use Hyperf\DbConnection\Model\Model as HyperfModel;
use Hyperf\DbConnection\Db; use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Model\Model as HyperfModel;
use Hyperf\HttpServer\Contract\RequestInterface; use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\ModelCache\Cacheable; use Hyperf\ModelCache\Cacheable;
use Hyperf\ModelCache\CacheableInterface; use Hyperf\ModelCache\CacheableInterface;
@@ -60,13 +60,19 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
* 排序配置: * 排序配置:
* - false: 禁用排序 * - 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 public function getSortConfig(): ?array
{ {
@@ -78,19 +84,13 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
if (is_string($this->sortable)) { if (is_string($this->sortable)) {
return [ return [
'field' => $this->sortable, 'field' => $this->sortable,
'direction' => 'asc' 'direction' => 'asc',
]; ];
} }
return null; 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 * @param int $id 要删除的记录ID
* @return bool 成功删除返回true * @return bool 成功删除返回true
@@ -117,7 +117,7 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
*/ */
public static function deleteById(int $id): bool 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( throw new ModelNotFoundException(sprintf(
'找不到ID为 %d 的 %s 记录', '找不到ID为 %d 的 %s 记录',
$id, $id,
@@ -129,7 +129,6 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
/** /**
* 快捷查找. * 快捷查找.
* @param int $id
* @return Builder|Builder[]|Collection|HyperfModel * @return Builder|Builder[]|Collection|HyperfModel
* @throws Exception 当删除操作发生其他错误时抛出 * @throws Exception 当删除操作发生其他错误时抛出
* @throws ModelNotFoundException * @throws ModelNotFoundException
@@ -154,14 +153,14 @@ abstract class AetherModel extends HyperfModel implements CacheableInterface
public static function findOrFailById(int $id): static public static function findOrFailById(int $id): static
{ {
$record = static::query()->find($id); $record = static::query()->find($id);
if(is_null($record)) { if (is_null($record)) {
throw new ModelNotFoundException(sprintf( throw new ModelNotFoundException(sprintf(
'找不到ID为 %d 的 %s 记录', '找不到ID为 %d 的 %s 记录',
$id, $id,
static::class 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) { foreach ($conditions as $field => $value) {
// 跳过非字符串的字段名(防止索引数组键导致的类型错误) // 跳过非字符串的字段名(防止索引数组键导致的类型错误)
if (!is_string($field)) { if (! is_string($field)) {
continue; continue;
} }

View File

@@ -4,13 +4,14 @@ declare(strict_types=1);
namespace Aether; namespace Aether;
use function Hyperf\Support\env; use Hyperf\Context\Context;
use Hyperf\ExceptionHandler\ExceptionHandler; use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Throwable; use Throwable;
use Hyperf\Context\Context;
use function Hyperf\Support\env;
class ApiExceptionHandler extends ExceptionHandler class ApiExceptionHandler extends ExceptionHandler
{ {

View File

@@ -1,24 +1,24 @@
<?php <?php
declare(strict_types=1);
namespace Aether\Contract; namespace Aether\Contract;
/** /**
* 树形结构接口,标识模型支持树形功能 * 树形结构接口,标识模型支持树形功能.
*/ */
interface TreeableInterface interface TreeableInterface
{ {
/** /**
* 构建树形结构 * 构建树形结构.
* @param array $items 原始数据 * @param array $items 原始数据
* @param int $parentId 根节点ID * @param int $parentId 根节点ID
* @return array
*/ */
public static function buildTree(array $items, int $parentId = 0): array; public static function buildTree(array $items, int $parentId = 0): array;
/** /**
* 获取子节点ID集合 * 获取子节点ID集合.
* @param int $id 节点ID * @param int $id 节点ID
* @return array
*/ */
public function getChildIds(int $id): array; public function getChildIds(int $id): array;
} }

View File

@@ -5,14 +5,15 @@ declare(strict_types=1);
namespace Aether\Exception; namespace Aether\Exception;
use Aether\AetherValidator; use Aether\AetherValidator;
use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\Context\Context;
use Hyperf\Database\Model\ModelNotFoundException; // 引入模型未找到异常 use Hyperf\Contract\StdoutLoggerInterface; // 引入模型未找到异常
use Hyperf\Database\Model\ModelNotFoundException;
use Hyperf\ExceptionHandler\ExceptionHandler; use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Validation\ValidationException; use Hyperf\Validation\ValidationException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Throwable; use Throwable;
use Hyperf\Context\Context;
use function Hyperf\Support\env; use function Hyperf\Support\env;
class AetherExceptionHandler extends ExceptionHandler class AetherExceptionHandler extends ExceptionHandler
@@ -52,6 +53,11 @@ class AetherExceptionHandler extends ExceptionHandler
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE))); ->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 private function formatErrorResponse(Throwable $throwable, string $requestId): array
{ {
// 模型未找到异常 // 模型未找到异常
@@ -61,10 +67,10 @@ class AetherExceptionHandler extends ExceptionHandler
'message' => $throwable->getMessage() ?: '请求的资源不存在', 'message' => $throwable->getMessage() ?: '请求的资源不存在',
'data' => env('APP_ENV') === 'dev' ? [ 'data' => env('APP_ENV') === 'dev' ? [
'file' => $throwable->getFile(), 'file' => $throwable->getFile(),
'line' => $throwable->getLine() 'line' => $throwable->getLine(),
] : null, ] : null,
'request_id' => $requestId, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
@@ -85,17 +91,20 @@ class AetherExceptionHandler extends ExceptionHandler
'data' => env('APP_ENV') === 'dev' ? [ 'data' => env('APP_ENV') === 'dev' ? [
'file' => $throwable->getFile(), 'file' => $throwable->getFile(),
'line' => $throwable->getLine(), 'line' => $throwable->getLine(),
'trace' => explode("\n", $throwable->getTraceAsString()) 'trace' => explode("\n", $throwable->getTraceAsString()),
] : null, ] : null,
'request_id' => $requestId, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
private function formatValidationError(ValidationFailedException $e, string $requestId): array private function formatValidationError(ValidationFailedException $e, string $requestId): array
{ {
$validatorInstance = new class extends AetherValidator { $validatorInstance = new class extends AetherValidator {
protected function scenes(): array { return []; } protected function scenes(): array
{
return [];
}
}; };
return [ return [
@@ -104,17 +113,20 @@ class AetherExceptionHandler extends ExceptionHandler
'data' => [ 'data' => [
'errors' => $validatorInstance->formatValidationErrors($e->validator), 'errors' => $validatorInstance->formatValidationErrors($e->validator),
'scene' => $e->getScene(), '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, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
private function formatNativeValidationError(ValidationException $e, string $requestId): array private function formatNativeValidationError(ValidationException $e, string $requestId): array
{ {
$validatorInstance = new class extends AetherValidator { $validatorInstance = new class extends AetherValidator {
protected function scenes(): array { return []; } protected function scenes(): array
{
return [];
}
}; };
return [ return [
@@ -122,15 +134,10 @@ class AetherExceptionHandler extends ExceptionHandler
'message' => '参数验证失败', 'message' => '参数验证失败',
'data' => [ 'data' => [
'errors' => $validatorInstance->formatValidationErrors($e->validator), '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, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
public function isValid(Throwable $throwable): bool
{
return true;
}
} }

View File

@@ -5,13 +5,14 @@ declare(strict_types=1);
namespace Aether\Exception; namespace Aether\Exception;
use Aether\AetherValidator; use Aether\AetherValidator;
use Hyperf\Context\Context;
use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler; use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Validation\ValidationException; use Hyperf\Validation\ValidationException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Throwable; use Throwable;
use Hyperf\Context\Context;
use function Hyperf\Support\env; use function Hyperf\Support\env;
class AppExceptionHandler extends ExceptionHandler class AppExceptionHandler extends ExceptionHandler
@@ -39,8 +40,13 @@ class AppExceptionHandler extends ExceptionHandler
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE))); ->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 private function formatErrorResponse(Throwable $throwable, string $requestId): array
{ {
@@ -61,21 +67,24 @@ class AppExceptionHandler extends ExceptionHandler
'data' => env('APP_ENV') === 'dev' ? [ 'data' => env('APP_ENV') === 'dev' ? [
'file' => $throwable->getFile(), 'file' => $throwable->getFile(),
'line' => $throwable->getLine(), 'line' => $throwable->getLine(),
'trace' => explode("\n", $throwable->getTraceAsString()) 'trace' => explode("\n", $throwable->getTraceAsString()),
] : null, ] : null,
'request_id' => $requestId, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
/** /**
* 格式化自定义验证异常 * 格式化自定义验证异常.
*/ */
private function formatValidationError(ValidationFailedException $e, string $requestId): array private function formatValidationError(ValidationFailedException $e, string $requestId): array
{ {
// 复用AetherValidator的错误格式化方法 // 复用AetherValidator的错误格式化方法
$validatorInstance = new class extends AetherValidator { $validatorInstance = new class extends AetherValidator {
protected function scenes(): array { return []; } protected function scenes(): array
{
return [];
}
}; };
return [ return [
@@ -84,20 +93,23 @@ class AppExceptionHandler extends ExceptionHandler
'data' => [ 'data' => [
'errors' => $validatorInstance->formatValidationErrors($e->validator), 'errors' => $validatorInstance->formatValidationErrors($e->validator),
'scene' => $e->getScene(), // 直接从异常获取场景 '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, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
/** /**
* 格式化原生验证异常(保持格式一致) * 格式化原生验证异常(保持格式一致).
*/ */
private function formatNativeValidationError(ValidationException $e, string $requestId): array private function formatNativeValidationError(ValidationException $e, string $requestId): array
{ {
$validatorInstance = new class extends AetherValidator { $validatorInstance = new class extends AetherValidator {
protected function scenes(): array { return []; } protected function scenes(): array
{
return [];
}
}; };
return [ return [
@@ -105,15 +117,10 @@ class AppExceptionHandler extends ExceptionHandler
'message' => '参数验证失败', 'message' => '参数验证失败',
'data' => [ 'data' => [
'errors' => $validatorInstance->formatValidationErrors($e->validator), '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, 'request_id' => $requestId,
'timestamp' => time() 'timestamp' => time(),
]; ];
} }
public function isValid(Throwable $throwable): bool
{
return true;
}
} }

View File

@@ -5,25 +5,30 @@ declare(strict_types=1);
namespace Aether\Exception; namespace Aether\Exception;
use Hyperf\Server\Exception\ServerException; use Hyperf\Server\Exception\ServerException;
use Throwable;
class BusinessException extends ServerException class BusinessException extends ServerException
{ {
// 错误码常量(按业务模块划分) // 错误码常量(按业务模块划分)
public const VALIDATION_ERROR = 400; // 参数验证失败 public const VALIDATION_ERROR = 400; // 参数验证失败
public const AUTH_ERROR = 401; // 认证失败 public const AUTH_ERROR = 401; // 认证失败
public const PERMISSION_DENY = 403; // 权限不足 public const PERMISSION_DENY = 403; // 权限不足
public const RESOURCE_NOT_FOUND = 404; // 资源不存在 public const RESOURCE_NOT_FOUND = 404; // 资源不存在
public const SCENE_NOT_FOUND = 400; public const SCENE_NOT_FOUND = 400;
/** /**
* 额外错误数据(如验证详情) * 额外错误数据(如验证详情).
*/ */
protected ?array $errorData = null; protected ?array $errorData = null;
public function __construct( public function __construct(
string $message, string $message,
int $code = 500, int $code = 500,
?\Throwable $previous = null, ?Throwable $previous = null,
?array $errorData = null ?array $errorData = null
) { ) {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
@@ -31,7 +36,7 @@ class BusinessException extends ServerException
} }
/** /**
* 获取额外错误数据 * 获取额外错误数据.
*/ */
public function getErrorData(): ?array public function getErrorData(): ?array
{ {

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Aether\Exception; namespace Aether\Exception;
use Hyperf\Validation\ValidationException; use Hyperf\Validation\ValidationException;
@@ -9,7 +11,7 @@ use Psr\Http\Message\ResponseInterface;
class ValidationFailedException extends ValidationException class ValidationFailedException extends ValidationException
{ {
/** /**
* 验证场景 * 验证场景.
*/ */
protected string $scene; protected string $scene;

View File

@@ -5,13 +5,13 @@ declare(strict_types=1);
namespace Aether; namespace Aether;
use Hyperf\Context\ApplicationContext; use Hyperf\Context\ApplicationContext;
use Hyperf\Context\Context;
use Hyperf\ExceptionHandler\ExceptionHandler; use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\Rpc\Protocol; use Hyperf\Rpc\Protocol;
use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface; use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Throwable; use Throwable;
use Hyperf\Context\Context;
class RpcExceptionHandler extends ExceptionHandler class RpcExceptionHandler extends ExceptionHandler
{ {
@@ -29,7 +29,6 @@ class RpcExceptionHandler extends ExceptionHandler
]; ];
$protocol = ApplicationContext::getContainer()->get(Protocol::class); $protocol = ApplicationContext::getContainer()->get(Protocol::class);
// $response->getBody()->write($protocol->pack($data));
$response->getBody()->write($protocol->pack($data)); $response->getBody()->write($protocol->pack($data));
return $response; return $response;

View File

@@ -59,7 +59,7 @@ trait AetherEnum
/** /**
* 根据值获取枚举实例(严格模式). * 根据值获取枚举实例(严格模式).
* @param int|string $value 枚举值 * @param int|string $value 枚举值
* @return AetherEnum|NoticeStatsModel|NoticeStatusEnum 枚举实例 * @return AetherEnum 枚举实例
*/ */
public static function fromValue(int|string $value): self public static function fromValue(int|string $value): self
{ {
@@ -95,7 +95,7 @@ trait AetherEnum
/** /**
* 根据描述获取枚举实例(精确匹配). * 根据描述获取枚举实例(精确匹配).
* @param string $description 描述文本 * @param string $description 描述文本
* @return null|AetherEnum|NoticeStatsModel|NoticeStatusEnum 匹配的枚举实例无匹配时返回null * @return null|AetherEnum 匹配的枚举实例无匹配时返回null
*/ */
public static function fromDescription(string $description): ?self public static function fromDescription(string $description): ?self
{ {

View File

@@ -1,23 +1,25 @@
<?php <?php
declare(strict_types=1);
namespace Aether\Traits; namespace Aether\Traits;
use Hyperf\Database\Model\SoftDeletes; use Hyperf\Database\Model\SoftDeletes;
/** /**
* 通用软删除Trait供需要软删除的模型使用 * 通用软删除Trait供需要软删除的模型使用.
*/ */
trait AetherSoftDelete trait AetherSoftDelete
{ {
use SoftDeletes; use SoftDeletes;
/** /**
* 初始化软删除相关配置自动隐藏deleted_at字段 * 初始化软删除相关配置自动隐藏deleted_at字段.
*/ */
protected function initializeAetherSoftDeletes(): void protected function initializeAetherSoftDeletes(): void
{ {
// 自动将deleted_at添加到隐藏字段避免序列化时暴露 // 自动将deleted_at添加到隐藏字段避免序列化时暴露
if (!in_array('deleted_at', $this->hidden, true)) { if (! in_array('deleted_at', $this->hidden, true)) {
$this->hidden[] = 'deleted_at'; $this->hidden[] = 'deleted_at';
} }
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Aether\Traits; namespace Aether\Traits;
use Aether\AetherModel; use Aether\AetherModel;
@@ -13,25 +15,16 @@ trait AetherTree
{ {
parent::__construct($attributes); parent::__construct($attributes);
if (!$this instanceof AetherModel) { if (! $this instanceof AetherModel) {
throw new LogicException( throw new LogicException(
"使用AetherTree trait的类必须继承AetherModel当前类: " . get_class($this) '使用AetherTree trait的类必须继承AetherModel当前类: ' . get_class($this)
); );
} }
} }
/** /**
* 抽象方法获取父ID字段名由子类实现 * 构建树形结构.
*/ * @param mixed $items
abstract protected function getParentIdField(): string;
/**
* 抽象方法:获取排序字段名(由子类实现)
*/
abstract protected function getSortField(): string;
/**
* 构建树形结构
*/ */
public static function buildTree($items, int $parentId = 0): array public static function buildTree($items, int $parentId = 0): array
{ {
@@ -45,7 +38,7 @@ trait AetherTree
foreach ($items as $item) { foreach ($items as $item) {
if ($item[$parentField] == $parentId) { if ($item[$parentField] == $parentId) {
$children = static::buildTree($items, $item['id']); $children = static::buildTree($items, $item['id']);
if (!empty($children)) { if (! empty($children)) {
$item['children'] = $children; $item['children'] = $children;
} }
$tree[] = $item; $tree[] = $item;
@@ -57,20 +50,7 @@ trait AetherTree
} }
/** /**
* 树形节点排序 * 获取指定节点的所有子节点ID.
*/
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
*/ */
public function getChildIds(int $id): array 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 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;
}
} }