This commit is contained in:
Aether
2025-09-18 10:46:54 +08:00
commit 0920cef866
62 changed files with 13547 additions and 0 deletions

View File

@@ -0,0 +1,494 @@
<?php
declare(strict_types=1);
namespace Aether;
use Closure;
use DateTime;
use Exception;
use Hyperf\Context\ApplicationContext;
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\HttpServer\Contract\RequestInterface;
use Hyperf\ModelCache\Cacheable;
use Hyperf\ModelCache\CacheableInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
abstract class AetherModel extends HyperfModel implements CacheableInterface
{
use Cacheable;
// use AetherSoftDelete; 移除,由子类决定是否使用
/**
* 批量赋值白名单.
*/
protected array $fillable = [];
/**
* 时间戳格式.
*/
protected ?string $dateFormat = 'Y-m-d H:i:s';
/**
* 搜索器规则配置.
*/
protected array $search = [];
/**
* 获取器规则配置.
*/
protected array $append = [];
/**
* 隐藏字段.
*/
protected array $hidden = [
'created_at',
'updated_at',
'deleted_at',
// 'deleted_at' // 移除,由子类决定是否使用
];
/**
* 排序配置:
* - false: 禁用排序
* - 字符串: 排序字段(默认升序)
* - 数组: ['field' => '字段名', 'direction' => 'asc/desc']
*/
protected string|array|bool $sortable = 'sort'; // 默认按sort字段升序
/**
* 获取排序配置
* @return array|null [field, direction] 或 null禁用排序
*/
public function getSortConfig(): ?array
{
if ($this->sortable === false) {
return null;
}
// 处理字符串配置(如 'sort' 或 'create_time'
if (is_string($this->sortable)) {
return [
'field' => $this->sortable,
'direction' => 'asc'
];
}
return null;
}
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->bootBaseModel();
}
/**
* 快捷创建.
*/
public static function createOne(array $data): AetherModel|Builder|HyperfModel
{
return static::query()->create($data);
}
/**
* 快捷更新.
*/
public static function updateById(int $id, array $data): bool
{
return static::query()->where('id', $id)->update($data) > 0;
}
/**
* 快捷删除指定ID的记录
*
* @param int $id 要删除的记录ID
* @return bool 成功删除返回true
* @throws ModelNotFoundException 当记录不存在时抛出
* @throws Exception 当删除操作发生其他错误时抛出
*/
public static function deleteById(int $id): bool
{
if (!static::query()->where('id', $id)->exists()) {
throw new ModelNotFoundException(sprintf(
'找不到ID为 %d 的 %s 记录',
$id,
static::class
));
}
return static::query()->where('id', $id)->delete() > 0;
}
/**
* 快捷查找.
* @param int $id
* @return Builder|Builder[]|Collection|HyperfModel
* @throws Exception 当删除操作发生其他错误时抛出
* @throws ModelNotFoundException
*/
public static function findById(int $id): array|Builder|Collection|HyperfModel
{
$record = static::query()->find($id);
if (is_null($record)) {
throw new ModelNotFoundException(sprintf('找不到 ID 为 %d 的记录', $id));
}
return $record;
}
/**
* 快捷查找或失败.
* @param int $id 要查找的记录ID
* @return AetherModel 根据ID查找记录不存在则抛出异常
* 根据ID查找记录不存在则抛出异常
* @throws Exception 当删除操作发生其他错误时抛出
* @throws ModelNotFoundException 当记录不存在时抛出
*/
public static function findOrFailById(int $id): static
{
$record = static::query()->find($id);
if(is_null($record)) {
throw new ModelNotFoundException(sprintf(
'找不到ID为 %d 的 %s 记录',
$id,
static::class
));
}
return $record;// static::query()->findOrFail($id);
}
/**
* 获取列表.
*/
public static function getList(
array $conditions = [],
array $columns = ['*'],
array $orders = []
): Collection {
$query = static::buildQuery($conditions, $orders);
return $query->get($columns);
}
/**
* 分页查询列表.
*/
public static function getPageList(
array $conditions = [],
int $page = 1,
int $pageSize = 10,
array $columns = ['*'],
array $orderBy = []
): LengthAwarePaginatorInterface {
// 直接通过静态方法链构建查询
$query = static::query();
// 应用条件
foreach ($conditions as $field => $value) {
$query->where($field, $value);
}
// 应用排序
foreach ($orderBy as $field => $direction) {
$query->orderBy($field, $direction);
}
// 执行分页
return $query->paginate($pageSize, $columns, 'page', $page);
}
/**
* 获取器处理.
*/
public function getAttribute(string $key): mixed
{
$value = parent::getAttribute($key);
// 检查是否有自定义获取器
$getterMethod = 'get' . ucfirst($key) . 'Attr';
if (method_exists($this, $getterMethod)) {
return $this->{$getterMethod}($value);
}
// 应用获取器规则
if (isset($this->append[$key])) {
return $this->applyGetterRule($value, $this->append[$key]);
}
return $value;
}
/**
* 批量更新.
*/
public static function batchUpdate(array $conditions, array $data): int
{
return static::query()->where($conditions)->update($data);
}
/**
* 事务处理.
* @throws Throwable
*/
public static function transaction(Closure $closure): mixed
{
return Db::transaction($closure);
}
/**
* 初始化模型.
*/
protected function bootBaseModel(): void
{
// 自动注册搜索器和获取器
$this->registerSearchers();
$this->registerGetters();
}
/**
* 注册搜索器.
*/
protected function registerSearchers(): void
{
// 为模型查询添加全局作用域,自动应用搜索规则
static::addGlobalScope('auto_search', function (Builder $query) {
$searchConditions = $this->getSearchConditions();
if (empty($searchConditions)) {
return;
}
// 应用搜索条件到查询
$this->applySearch($query, $searchConditions);
});
}
/**
* 获取搜索条件.
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function getSearchConditions(): array
{
$request = ApplicationContext::getContainer()->get(RequestInterface::class);
$allParams = $request->all();
if (is_array($allParams) && count($allParams) === 1 && is_array($allParams[0])) {
$allParams = $allParams[0];
}
$allowedFields = array_keys($this->search);
return array_intersect_key($allParams, array_flip($allowedFields));
}
protected function registerGetters(): void
{
foreach ($this->append as $field => $rule) {
// 为字段注册获取器回调
$this->registerGetter($field, $rule);
}
}
/**
* 为单个字段注册获取器.
* @param string $field 字段名
* @param array|Closure|string $rule 规则(类型/带参数的类型/闭包)
*/
protected function registerGetter(string $field, array|Closure|string $rule): void
{
// 生成获取器方法名(遵循 Hyperf 模型获取器规范get{Field}Attr
$getterMethod = 'get' . ucfirst($field) . 'Attr';
// 若已存在自定义获取器方法,则不覆盖
if (method_exists($this, $getterMethod)) {
return;
}
// 动态定义获取器方法
$this->{$getterMethod} = function ($value) use ($rule) {
// 闭包规则直接执行
if ($rule instanceof Closure) {
return $rule($value, $this); // 传入当前模型实例方便关联字段处理
}
// 解析规则类型和参数
if (is_array($rule)) {
$type = $rule[0];
$params = $rule[1] ?? [];
} else {
$type = $rule;
$params = [];
}
// 应用规则(复用/扩展 applyGetterRule 方法)
return $this->applyGetterRule($value, $type, $params);
};
}
/**
* 应用获取器规则(支持参数).
* @param mixed $value 字段原始值
* @param string $type 规则类型
* @param array $params 规则参数
*/
protected function applyGetterRule(mixed $value, string $type, array $params = []): mixed
{
return match ($type) {
// 基础规则
'date' => $value instanceof DateTime ? $value->format('Y-m-d') : $value,
'datetime' => $value instanceof DateTime ? $value->format('Y-m-d H:i:s') : $value,
'timestamp' => $value instanceof DateTime ? $value->getTimestamp() : $value,
'boolean' => (bool) $value,
'json' => is_string($value) ? json_decode($value, true) : $value,
// 参数化规则
'number' => $this->formatNumber($value, $params), // 数字格式化
'truncate' => $this->truncateString($value, $params), // 字符串截断
'enum' => $this->mapEnum($value, $params), // 枚举映射
default => $value,
};
}
// 数字格式化(示例参数化实现)
protected function formatNumber($value, array $params): string
{
$precision = $params['precision'] ?? 2; // 默认保留2位小数
return number_format((float) $value, $precision);
}
// 字符串截断(示例参数化实现)
protected function truncateString($value, array $params): string
{
$length = $params['length'] ?? 20; // 默认截断到20字符
$suffix = $params['suffix'] ?? '...'; // 省略符
if (! is_string($value)) {
$value = (string) $value;
}
return mb_strlen($value) > $length
? mb_substr($value, 0, $length) . $suffix
: $value;
}
// 枚举映射(示例参数化实现)
protected function mapEnum($value, array $params): mixed
{
$map = $params['map'] ?? []; // 枚举映射表,如 [1 => '男', 2 => '女']
return $map[$value] ?? $value;
}
// 修正 buildQuery 方法,避免使用 new static()
protected static function buildQuery(array $conditions = [], array $orderBy = []): Builder
{
// 使用静态 query() 方法获取查询构建器
$query = static::query();
// 处理搜索条件
foreach ($conditions as $field => $value) {
$query->where($field, $value);
}
// 处理排序
foreach ($orderBy as $field => $direction) {
$query->orderBy($field, $direction);
}
return $query;
}
/**
* 应用搜索条件.
*/
protected function applySearch(Builder $query, array $conditions): void
{
foreach ($conditions as $field => $value) {
// 跳过非字符串的字段名(防止索引数组键导致的类型错误)
if (!is_string($field)) {
continue;
}
// 处理嵌套关系查询(如:user.name
if (str_contains($field, '.')) {
[$relation, $relationField] = explode('.', $field, 2);
$query->whereHas($relation, function ($q) use ($relationField, $value) {
$q->where($relationField, $value);
});
continue;
}
// 检查是否有自定义搜索器方法
$searchMethod = 'search' . ucfirst($field);
if (method_exists($this, $searchMethod)) {
$this->{$searchMethod}($query, $value);
continue;
}
// 应用搜索规则配置
if (isset($this->search[$field])) {
$this->applySearchRule($query, $field, $value, $this->search[$field]);
continue;
}
// 默认精确匹配(仅对$search中允许的字段生效因已通过白名单过滤
$query->where($field, $value);
}
}
/**
* 应用搜索规则.
*/
protected function applySearchRule(Builder $query, string $field, mixed $value, array|string $rule): void
{
if (is_array($rule)) {
$type = $rule[0];
$params = $rule[1] ?? [];
} else {
$type = $rule;
$params = [];
}
switch ($type) {
case 'like':
$query->where($field, 'like', "%{$value}%");
break;
case 'like_left':
$query->where($field, 'like', "%{$value}");
break;
case 'like_right':
$query->where($field, 'like', "{$value}%");
break;
case 'in':
$query->whereIn($field, (array) $value);
break;
case 'not_in':
$query->whereNotIn($field, (array) $value);
break;
case 'gt':
$query->where($field, '>', $value);
break;
case 'lt':
$query->where($field, '<', $value);
break;
case 'gte':
$query->where($field, '>=', $value);
break;
case 'lte':
$query->where($field, '<=', $value);
break;
case 'between':
$query->whereBetween($field, (array) $value);
break;
case 'null':
$query->whereNull($field);
break;
case 'not_null':
$query->whereNotNull($field);
break;
default:
$query->where($field, $type, $value);
}
}
}