| 
									
										
										
										
											2021-11-18 17:57:04 +08:00
										 |  |  |  | <?php | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | namespace app\service; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | use Exception; | 
					
						
							|  |  |  |  | use think\db\exception\DataNotFoundException; | 
					
						
							|  |  |  |  | use think\db\exception\DbException; | 
					
						
							|  |  |  |  | use think\db\exception\ModelNotFoundException; | 
					
						
							|  |  |  |  | use think\Model; | 
					
						
							|  |  |  |  | use think\facade\Db; | 
					
						
							|  |  |  |  | use think\Collection; | 
					
						
							|  |  |  |  | use think\facade\Env; | 
					
						
							|  |  |  |  | use think\facade\Log; | 
					
						
							|  |  |  |  | use think\Paginator; | 
					
						
							|  |  |  |  | use app\exception\RepositoryException; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /** | 
					
						
							|  |  |  |  |  * 领域模型-仓储 | 
					
						
							|  |  |  |  |  * | 
					
						
							|  |  |  |  |  * Class Repository | 
					
						
							|  |  |  |  |  * @package app\service | 
					
						
							|  |  |  |  |  */ | 
					
						
							|  |  |  |  | class Repository | 
					
						
							|  |  |  |  | { | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * @var array 已加载对象列表 | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     private static $objects = []; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * @var Model 模型对象 | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     protected $model; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // 布尔值数字关系
 | 
					
						
							|  |  |  |  |     public const BOOL_FALSE = 0; | 
					
						
							|  |  |  |  |     public const BOOL_TRUE  = 1; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 获取当前子对象实列(单例形式返回) | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  Model|null  $model  模型对象。未指定则自动获取 | 
					
						
							|  |  |  |  |      * @return mixed | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public static function getInstance(Model $model = null) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         $class = get_called_class(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if (isset(self::$objects[$class]) && self::$objects[$class] !== null) { | 
					
						
							|  |  |  |  |             return self::$objects[$class]; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         $obj = new $class(); | 
					
						
							|  |  |  |  |         if ($model) { | 
					
						
							|  |  |  |  |             $obj->model = $model; | 
					
						
							|  |  |  |  |         } elseif (strpos($class, '\\repository') > 0) { | 
					
						
							|  |  |  |  |             $model = str_replace('\\repository', '\\model', $class); | 
					
						
							|  |  |  |  |             //去掉末尾Repository app\model\AccountRepository  =》 app\model\Account
 | 
					
						
							|  |  |  |  |             $model = substr($model, 0, strlen($model) - strlen('Repository')); | 
					
						
							|  |  |  |  |             if (class_exists($model)) { | 
					
						
							|  |  |  |  |                 $obj->model = new $model; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         self::$objects[$class] = $obj; | 
					
						
							|  |  |  |  |         return $obj; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * @param  callable  $callback | 
					
						
							|  |  |  |  |      * @param  mixed  $failReturn | 
					
						
							|  |  |  |  |      * @param  bool  $transaction | 
					
						
							|  |  |  |  |      * @return mixed | 
					
						
							|  |  |  |  |      * @throws RepositoryException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     protected function access(callable $callback, $failReturn, bool $transaction = false) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         $exception = null; | 
					
						
							|  |  |  |  |         try { | 
					
						
							|  |  |  |  |             if ($transaction) { | 
					
						
							|  |  |  |  |                 Db::startTrans(); | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             $r = $callback(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if ($transaction) { | 
					
						
							|  |  |  |  |                 Db::commit(); | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if ($r) { | 
					
						
							|  |  |  |  |                 return $r; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if ($failReturn instanceof Exception) { | 
					
						
							|  |  |  |  |                 return null; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             return $failReturn; | 
					
						
							|  |  |  |  |         } catch (Exception $e) { | 
					
						
							|  |  |  |  |             if ($transaction) { | 
					
						
							|  |  |  |  |                 Db::rollback(); | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if ($e instanceof RepositoryException) { | 
					
						
							|  |  |  |  |                 throw $e; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             $name = 'Domain - Repository - 未知错误'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             $traces = $e->getTrace(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             foreach ($traces as $i => $trace) { | 
					
						
							|  |  |  |  |                 if (!empty($trace['class']) && $trace['class'] === Repository::class && $trace['function'] === 'access') { | 
					
						
							|  |  |  |  |                     $trace = $traces[$i - 2] ?? null; | 
					
						
							|  |  |  |  |                     break; | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (!empty($trace) && !empty($trace['file'][1]) && !empty($trace['line'])) { | 
					
						
							|  |  |  |  |                 $parts = explode('application\\', $trace['file']); | 
					
						
							|  |  |  |  |                 if (!empty($parts[1])) { | 
					
						
							|  |  |  |  |                     $name = $parts[1].':'.$trace['line']; | 
					
						
							|  |  |  |  |                 } else { | 
					
						
							|  |  |  |  |                     $name = $trace['file'].':'.$trace['line']; | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             $exception = $e; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             $line = '['.$name.'] '.$e->getMessage(); | 
					
						
							|  |  |  |  |             Log::error($line); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-24 14:41:24 +08:00
										 |  |  |  |             throw new RepositoryException('Repository异常'.$line.(Env::get('app_debug') ? ': '.$line : ''), 5009); | 
					
						
							| 
									
										
										
										
											2021-11-18 17:57:04 +08:00
										 |  |  |  |         } finally { | 
					
						
							|  |  |  |  |             if ($exception && $failReturn instanceof RepositoryException) { | 
					
						
							|  |  |  |  |                 if (Env::get('app_debug')) { | 
					
						
							|  |  |  |  |                     $failReturn = new RepositoryException($failReturn->getMessage().': '.$exception->getMessage(), | 
					
						
							|  |  |  |  |                         $failReturn->getCode()); | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |                 throw $failReturn; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 获取当前model对象 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @return Model | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function getModel(): Model | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->model; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 根据条件查询列表 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  array  $where  查询条件 | 
					
						
							|  |  |  |  |      * @param  array  $fields  查询字段 []表示全部 | 
					
						
							|  |  |  |  |      * @param  int  $page  默认第一页 0不限制 | 
					
						
							|  |  |  |  |      * @param  int  $limit  限制条数 0不限制 | 
					
						
							|  |  |  |  |      * @param  callable|null  $callback 更为复杂的条件 使用闭包查询 | 
					
						
							|  |  |  |  |      * @return array | 
					
						
							|  |  |  |  |      * @throws RepositoryException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function findList(array $where = [], array $fields = [], int $page = 1, int $limit = 0, callable $callback = null, array $order = []): ?array | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         $failData = [ | 
					
						
							|  |  |  |  |             'total'   => 0, | 
					
						
							|  |  |  |  |             'current' => $page, | 
					
						
							|  |  |  |  |             'size'    => $limit, | 
					
						
							|  |  |  |  |             'list'    => new Collection(), | 
					
						
							|  |  |  |  |         ]; | 
					
						
							|  |  |  |  |         return $this->access(function () use ($where, $fields, $page, $limit, $callback, $order) { | 
					
						
							|  |  |  |  |             return $this->model->findList($where, $fields, $page, $limit, $callback, $order); | 
					
						
							|  |  |  |  |         }, $failData); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 根据条件查询列表[带分页 适用于后台] | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  array  $data  查询数据 | 
					
						
							|  |  |  |  |      * @param  array  $pageParams  分页参数 | 
					
						
							|  |  |  |  |      * @param  callable|null  $callback 复杂查询条件  使用闭包查询 | 
					
						
							|  |  |  |  |      * @return Paginator | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function findListWithPaginate(array $data = [], array $pageParams = [], callable $callback = null): Paginator | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->model->findListWithPaginate($data, $pageParams, $callback); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 根据主键 ID 查询 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  int  $id  ID | 
					
						
							|  |  |  |  |      * @param  array  $fields  要返回的字段,默认全部 | 
					
						
							|  |  |  |  |      * @return Mixed | 
					
						
							|  |  |  |  |      * @throws RepositoryException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function findById(int $id, array $fields = [], callable $callback = null) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->access(function () use ($id, $fields, $callback) { | 
					
						
							|  |  |  |  |             return $this->model->findById($id, $fields, $callback); | 
					
						
							|  |  |  |  |         }, null); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * @param  array  $where | 
					
						
							|  |  |  |  |      * @param  array  $fields | 
					
						
							|  |  |  |  |      * @return array|Model|null | 
					
						
							|  |  |  |  |      * @throws DataNotFoundException | 
					
						
							|  |  |  |  |      * @throws DbException | 
					
						
							|  |  |  |  |      * @throws ModelNotFoundException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function findOneByWhere(array $where, array $fields = []) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->model->field($fields)->where($where)->find(); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 创建 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  array  $data  数据 | 
					
						
							|  |  |  |  |      * @return Model | 
					
						
							|  |  |  |  |      * @throws RepositoryException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function create(array $data): Model | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->access(function () use ($data) { | 
					
						
							|  |  |  |  |             return $this->model->create($data); | 
					
						
							|  |  |  |  |         }, new RepositoryException('创建失败')); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 更新 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  array  $data  数据 | 
					
						
							|  |  |  |  |      * @param  array  $where  条件 | 
					
						
							|  |  |  |  |      * @return bool|Exception | 
					
						
							|  |  |  |  |      * @throws RepositoryException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function update(array $data, array $where): bool | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->access(function () use ($data, $where) { | 
					
						
							|  |  |  |  |             return $this->model->where($where)->find()->save($data); | 
					
						
							|  |  |  |  |         }, new RepositoryException('更新失败')); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 删除 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  array  $where  删除条件 | 
					
						
							|  |  |  |  |      * @param  bool  $softDelete  是否软删除 默认false | 
					
						
							|  |  |  |  |      * @param  string  $softDeleteTime  删除时间 softDelete=true时有效 | 
					
						
							|  |  |  |  |      * @param  string  $softDeleteField  软删除字段 softDelete=true时有效 | 
					
						
							|  |  |  |  |      * @return bool | 
					
						
							|  |  |  |  |      * @throws RepositoryException | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function delete(array $where, bool $softDelete = false, string $softDeleteTime = '', string $softDeleteField = 'deleted_at'): bool | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->access(function () use ($where, $softDelete, $softDeleteField, $softDeleteTime) { | 
					
						
							|  |  |  |  |             // 注意:如果model中引入了软删除trait,$softDelete又设置false 将无法正确删除
 | 
					
						
							|  |  |  |  |             return $this->model->where($where) | 
					
						
							|  |  |  |  |                 ->when($softDelete, function ($q) use ($softDeleteField, $softDeleteTime) { | 
					
						
							|  |  |  |  |                     $softDeleteTime = $softDeleteTime ?: date('Y-m-d H:i:s'); | 
					
						
							|  |  |  |  |                     $q->useSoftDelete($softDeleteField, $softDeleteTime); | 
					
						
							|  |  |  |  |                 })->delete(); | 
					
						
							|  |  |  |  |         }, false); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 排序 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  int  $id 排序ID | 
					
						
							|  |  |  |  |      * @param  string  $type 排序类型 向上、向下 | 
					
						
							|  |  |  |  |      * @param  int  $num 移动位数 | 
					
						
							|  |  |  |  |      * @param  string  $listType 列表的排序类型  降序|升序 | 
					
						
							|  |  |  |  |      * @param  array  $where 额外条件 格式如: | 
					
						
							|  |  |  |  |      *                                  $map[] = ['name','like','think']; | 
					
						
							|  |  |  |  |      *                                  $map[] = ['status','=',1]; | 
					
						
							|  |  |  |  |      * @return array | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public function sort(int $id,string $type,int $num,string $listType, array $where = []): array | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return $this->model->sort($id, $type, $num, $listType, $where); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     /** | 
					
						
							|  |  |  |  |      * 日志记录 | 
					
						
							|  |  |  |  |      * | 
					
						
							|  |  |  |  |      * @param  string  $msg | 
					
						
							|  |  |  |  |      * @param  Exception|null  $e | 
					
						
							|  |  |  |  |      * @param  string  $level | 
					
						
							|  |  |  |  |      * @param  string  $channel | 
					
						
							|  |  |  |  |      */ | 
					
						
							|  |  |  |  |     public static function log(string $msg, Exception $e = null, string $level = 'error', string $channel = 'file') | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         if ($e != null) { | 
					
						
							|  |  |  |  |             $msg = sprintf("[%s]%s:%s %s", $msg, $e->getFile(), $e->getLine(), $e->getMessage()); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |             $msg = sprintf("%s", $msg); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |         Log::channel($channel)->$level($msg); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | } |