* * 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 * * 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()); } } }