450 lines
14 KiB
PHP
450 lines
14 KiB
PHP
<?php
|
||
/**
|
||
* Author: ykxiao
|
||
* Date: 2025/1/6
|
||
* Time: 下午4:07
|
||
* Description:
|
||
*
|
||
* (c) ykxiao <yk_9001@hotmail.com>
|
||
*
|
||
* This source file is subject to the MIT license that is bundled
|
||
* with this source code in the file LICENSE.
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Dao;
|
||
|
||
use App\Model\Model;
|
||
use App\Service\Trait\ColumnConfigTrait;
|
||
use Exception;
|
||
use Hyperf\Collection\Collection;
|
||
use Hyperf\Database\Model\Builder;
|
||
use Hyperf\Di\Annotation\Inject;
|
||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||
|
||
/**
|
||
* Author: ykxiao
|
||
* Date: 2025/2/28
|
||
* Time: 上午8:32
|
||
* Description: AbstractDao.
|
||
*
|
||
* (c) ykxiao <yk_9001@hotmail.com>
|
||
*
|
||
* This source file is subject to the MIT license that is bundled
|
||
* with this source code in the file LICENSE.
|
||
*/
|
||
abstract class AbstractDao
|
||
{
|
||
use ColumnConfigTrait;
|
||
|
||
// 支持的表达式 - 白名单操作符
|
||
protected static array $OPERATORS_THAT_SUPPORT_VALUES = [
|
||
// 比较运算符
|
||
'=', '<', '<=', '>', '>=', '<>', '!=',
|
||
|
||
// 逻辑运算符
|
||
'between', 'not between', 'in', 'not in', 'like', 'is null', 'is not null',
|
||
];
|
||
|
||
#[Inject]
|
||
protected RequestInterface $request;
|
||
|
||
private int|string $keyId;
|
||
|
||
private array $queryResult;
|
||
|
||
/**
|
||
* 获取模型
|
||
* @return Model
|
||
*/
|
||
abstract protected function getModel(): string;
|
||
|
||
/**
|
||
* 获取query selector 字段
|
||
* @return array
|
||
*/
|
||
abstract public function getFields(): array;
|
||
|
||
/**
|
||
* 构建查询条件.
|
||
* @throws Exception
|
||
*/
|
||
public function buildWhere(Builder $builder, array $params = []): Builder
|
||
{
|
||
// 根据字段查询数据库,params存在的条件进行查询
|
||
$fields = $this->getFields();
|
||
return self::daoBuildWhere($builder, $params, $fields);
|
||
}
|
||
|
||
/**
|
||
* 数据操作.
|
||
*/
|
||
public function builder(): Builder
|
||
{
|
||
return $this->getModel()::query();
|
||
}
|
||
|
||
/**
|
||
* 实例化.
|
||
*/
|
||
public function make(): AbstractDao
|
||
{
|
||
return new $this();
|
||
}
|
||
|
||
/**
|
||
* 记录查询.
|
||
* @throws Exception
|
||
*/
|
||
public function qInfo(array $params = []): Builder
|
||
{
|
||
$query = $this->builder();
|
||
$params = $params ?: $this->request->all();
|
||
if (!empty($params['ids']) && is_array($params['ids'])) {
|
||
$query->whereIn('id', $params['ids']);
|
||
} else {
|
||
$query->where('id', intval($params['id']));
|
||
}
|
||
if (!$query->exists()) {
|
||
throw new Exception('记录不存在');
|
||
}
|
||
return $query;
|
||
}
|
||
|
||
/**
|
||
* 通用创建&更新.
|
||
* @param array $data 需要新增更新的数据
|
||
* @param array $updateCondition 更新条件
|
||
* @return $this
|
||
* @throws Exception
|
||
*/
|
||
public function commonCreate(array $data = [], array $updateCondition = []): AbstractDao
|
||
{
|
||
$ignoreUpdateFields = ['id', 'company_id', 'creator_id', 'creator_name']; // 不允许更新的字段
|
||
|
||
// 1:根据条件更新或创建
|
||
if (!empty($updateCondition)) {
|
||
$updateData = array_diff_key($data, array_flip($ignoreUpdateFields));
|
||
$this->builder()->updateOrCreate($updateCondition, $updateData);
|
||
|
||
// 为保证查询条件一致,重新构造查询
|
||
$query = $this->builder()->where($updateCondition);
|
||
$records = $query->get();
|
||
|
||
$this->queryResult = $records->count() > 1
|
||
? $records->toArray()
|
||
: $records->first()?->toArray();
|
||
|
||
return $this;
|
||
}
|
||
|
||
// 2:根据主键 ID 更新
|
||
if (!empty($data['id'])) {
|
||
$this->keyId = $data['id'];
|
||
$query = $this->qInfo($data);
|
||
$query->lockForUpdate()->first(); // 获取锁
|
||
|
||
$updateData = array_diff_key($data, array_flip($ignoreUpdateFields));
|
||
|
||
$res = $query->update($updateData);
|
||
if (!$res) {
|
||
throw new Exception('数据更新失败');
|
||
}
|
||
|
||
// 更新后重新查询,避免条件变化导致查询不到
|
||
$this->queryResult = $this->builder()->where('id', $this->keyId)->first()?->toArray();
|
||
return $this;
|
||
}
|
||
|
||
// 3:创建新记录
|
||
$res = $this->builder()->create($data);
|
||
if (!$res) {
|
||
throw new Exception('数据新增失败');
|
||
}
|
||
|
||
$this->keyId = $res->getKey();
|
||
$this->queryResult = $res->toArray();
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* 返回更新&新增主键.
|
||
*/
|
||
public function getKey(): int
|
||
{
|
||
return $this->keyId;
|
||
}
|
||
|
||
/**
|
||
* 返回数据库操作结果.
|
||
*/
|
||
public function result(): array
|
||
{
|
||
return $this->queryResult;
|
||
}
|
||
|
||
/**
|
||
* 数据验证
|
||
*/
|
||
public function verifyData(Builder $builder, array $params = []): Builder
|
||
{
|
||
$map = [];
|
||
$condition = [];
|
||
if (!empty($params['id']) && is_numeric($params['id'])) {
|
||
$condition = ['id' => $params['id']];
|
||
}
|
||
foreach ($condition as $k => $v) {
|
||
$map[] = [$k, '!=', $v];
|
||
}
|
||
return clone $builder->where($map);
|
||
}
|
||
|
||
/**
|
||
* 更新状态
|
||
* @throws Exception
|
||
*/
|
||
public function updateStatus(): null|object
|
||
{
|
||
$params = $this->request->all();
|
||
$query = $this->qInfo()->lockForUpdate();
|
||
$info = $query->first();
|
||
if ($info['active_status'] == $params['status']) {
|
||
throw new Exception('状态不对,请确认');
|
||
}
|
||
$query->update(['active_status' => $params['status']]);
|
||
return $info;
|
||
}
|
||
|
||
/***************************************************************************************
|
||
* 构建查询数据条件参数.
|
||
* $params = [
|
||
* 'mail_sync_id' => 123,
|
||
* 'sn' => ['operator' => '!=', 'value' => 'ABC123'],
|
||
* 'aliid' => ['operator' => 'like', 'value' => '%example%'],
|
||
* 'serial_number' => ['operator' => '>', 'value' => 100],
|
||
* 'ali_model' => 'ModelX',
|
||
* 'model' => 'XYZ',
|
||
* 'date' => ['operator' => 'between', 'value' => ['2023-01-01', '2023-01-31']],
|
||
* 'id' => [
|
||
* ['operator' => '>=', 'value' => 10],
|
||
* ['operator' => '<=', 'value' => 100]
|
||
* ],
|
||
* 'status' => ['operator' => 'in', 'value' => ['active', 'pending']],
|
||
* ];
|
||
*************************************************************************************
|
||
* @throws Exception
|
||
*/
|
||
public static function daoBuildWhere(Builder $builder, array $params, array $fields): Builder
|
||
{
|
||
if (empty($params)) return $builder; // 如果没有参数,直接返回
|
||
// 用于存储所有 like 条件
|
||
$likeConditions = [];
|
||
|
||
foreach ($fields as $field) {
|
||
// 跳过空参数
|
||
if (!isset($params[$field]) || $params[$field] === '') {
|
||
continue;
|
||
}
|
||
|
||
$param = $params[$field];
|
||
// 如果参数是数组
|
||
if (is_array($param)) {
|
||
// 处理同一个字段的多个条件
|
||
if (isset($param[0]) && is_array($param[0])) {
|
||
self::addMultipleConditions($builder, $field, $param);
|
||
} else {
|
||
// 处理单个条件
|
||
self::handleSingleCondition($builder, $likeConditions, $field, $param);
|
||
}
|
||
} else {
|
||
// 处理字符串条件,字符串条件,强制使用绑定参数
|
||
$builder->where($field, '=', $param);
|
||
}
|
||
}
|
||
|
||
// 应用所有 like 条件
|
||
self::applyLikeConditions($builder, $likeConditions);
|
||
|
||
// 添加排序功能
|
||
if (!empty($params['sort_orders'])) {
|
||
self::applySortOrders($builder, $params['sort_orders'], $fields);
|
||
}
|
||
|
||
return $builder;
|
||
}
|
||
|
||
private static function addMultipleConditions(Builder $builder, string $field, array $conditions): void
|
||
{
|
||
// 处理同一个字段的多个条件
|
||
$builder->where(/**
|
||
* @throws Exception
|
||
*/ function ($query) use ($field, $conditions) {
|
||
foreach ($conditions as $condition) {
|
||
// 应用每个条件
|
||
self::applyCondition($query, $field, $condition);
|
||
}
|
||
});
|
||
}
|
||
|
||
private static function handleSingleCondition(Builder $builder, array &$likeConditions, string $field, array $param): void
|
||
{
|
||
// 收集 like 条件或处理其他单个条件
|
||
if (isset($param['operator']) && strtolower($param['operator']) === 'like') {
|
||
// 如果是 like 条件,将其添加到 likeConditions 数组中
|
||
$likeConditions[] = ['field' => $field, 'condition' => $param];
|
||
} else {
|
||
// 否则,直接应用条件
|
||
$builder->where(
|
||
/**
|
||
* @throws Exception
|
||
*/ function ($query) use ($field, $param) {
|
||
self::applyCondition($query, $field, $param);
|
||
});
|
||
}
|
||
}
|
||
|
||
private static function applyLikeConditions(Builder $builder, array $likeConditions): void
|
||
{
|
||
// 应用所有 like 条件
|
||
if (!empty($likeConditions)) {
|
||
$builder->where(function ($query) use ($likeConditions) {
|
||
foreach ($likeConditions as $likeCondition) {
|
||
if (empty($likeCondition['condition']['value'])) continue;
|
||
// 使用 orWhere 添加每个 like 条件
|
||
$query->orWhere($likeCondition['field'], 'like', $likeCondition['condition']['value']);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用排序功能.
|
||
* @param Builder $builder
|
||
* @param array $sortOrders
|
||
* @param array $fields
|
||
* @return void
|
||
* @throws Exception
|
||
*/
|
||
private static function applySortOrders(Builder $builder, array $sortOrders, array $fields): void
|
||
{
|
||
foreach ($sortOrders as $field => $sortOrder) {
|
||
if (!in_array($sortOrder, ['asc', 'desc'])) {
|
||
throw new Exception('排序方式不正确');
|
||
}
|
||
if (!in_array($field, $fields)) {
|
||
throw new Exception('排序字段不正确');
|
||
}
|
||
$builder->orderBy($field, $sortOrder);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用单个字段的查询条件.
|
||
* 调用方式
|
||
* (builder,'name','='|['operate'=>'bett']).
|
||
* @throws Exception
|
||
*/
|
||
private static function applyCondition(Builder &$builder, string $field, mixed $condition): void
|
||
{
|
||
if (is_array($condition) && isset($condition['operator']) && isset($condition['value'])) {
|
||
if (empty($condition['value'])) {
|
||
return;
|
||
}
|
||
|
||
$operator = strtolower($condition['operator']);
|
||
if (!in_array($operator, self::$OPERATORS_THAT_SUPPORT_VALUES)) {
|
||
throw new Exception('不支持的查询表达式: ' . $condition['operator']);
|
||
}
|
||
$builder = match ($operator) {
|
||
'between' => $builder->whereBetween($field, $condition['value']),
|
||
'not between' => $builder->whereNotBetween($field, $condition['value']),
|
||
'in' => $builder->whereIn($field, $condition['value']),
|
||
'not in' => $builder->whereNotIn($field, $condition['value']),
|
||
'is null' => $builder->whereNull($field),
|
||
'is not null' => $builder->whereNotNull($field),
|
||
'like' => $builder->orWhere($field, 'like', $condition['value']),
|
||
default => $builder->where($field, $operator, $condition['value']),
|
||
};
|
||
} else {
|
||
$builder->where($field, '=', $condition);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取字段列表(公共).
|
||
* @param Builder $builder
|
||
* @param array $params
|
||
* @param array $fields
|
||
* @param string|array $addSelect
|
||
* @return array
|
||
* @throws Exception
|
||
*/
|
||
public function selectFields(Builder $builder, array $params, array $fields = [], string|array $addSelect = ''): array
|
||
{
|
||
self::daoBuildWhere($builder, $params, $fields);
|
||
|
||
$field = $params['field'] ?? '';
|
||
if (!in_array($field, $fields)) {
|
||
throw new Exception('字段参数错误');
|
||
}
|
||
|
||
$data = $builder->select(['id', $field]);
|
||
if (!empty($addSelect)) {
|
||
$data = $data->addSelect($addSelect);
|
||
}
|
||
return $data->get()->filter(function ($item) use ($field) {
|
||
// 过滤空值但不过滤布尔值
|
||
return !empty($item[$field]) || is_bool($item[$field]);
|
||
})->unique($field)->values()->toArray();
|
||
}
|
||
|
||
/**
|
||
* 分页处理函数.
|
||
*
|
||
* 本函数用于对给定的Builder或Collection对象进行分页处理,并返回分页后的数据及其它相关信息。
|
||
*
|
||
* @param Builder|Collection $builder 可以是Eloquent Builder对象或Collection对象
|
||
* @param array $params 包含分页参数的数组,比如页码和每页数量
|
||
* @return array 返回一个包含分页数据、字段配置、过滤条件、表格设置等信息的数组
|
||
* @throws Exception
|
||
*/
|
||
public function paginate(Builder|Collection $builder, array $params = []): array
|
||
{
|
||
try {
|
||
// 从参数中获取页码和每页数量,未指定则默认值
|
||
$page = empty($params['page']) ? 1 : (int)$params['page'];
|
||
$page_size = empty($params['pageSize']) ? 100 : (int)$params['pageSize'];
|
||
// 计算跳过的记录数
|
||
$skip = ($page - 1) * $page_size;
|
||
// 获取总记录数
|
||
$total = $builder->count();
|
||
// 计算总页数,若记录数为0,则页数为0
|
||
$pageSize = $total == 0 ? 0 : (int)ceil($total / $page_size);
|
||
// 根据Builder类型获取分页后的数据
|
||
if ($builder instanceof Collection) {
|
||
// 对Collection对象进行分页
|
||
$rows = $builder->values()->slice($skip, $page_size)->values();
|
||
} else {
|
||
// 对Builder对象进行分页查询
|
||
$rows = $builder->skip($skip)->take($page_size)->orderBy('id', 'desc')->get();
|
||
}
|
||
|
||
// 获取列配置信息
|
||
$columnConfig = $this->getColumnConfig();
|
||
$tableConfig = $this->getTableConfig();
|
||
|
||
return compact(
|
||
'rows',
|
||
'page',
|
||
'total',
|
||
'pageSize',
|
||
'columnConfig',
|
||
'tableConfig'
|
||
);
|
||
} catch (Exception $e) {
|
||
throw new Exception($e->getMessage());
|
||
}
|
||
}
|
||
} |