init
This commit is contained in:
494
extend/Aether/PHP/Hyperf/AetherModel.php
Normal file
494
extend/Aether/PHP/Hyperf/AetherModel.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user