diff --git a/app/admin/controller/ExaminationInformation/InfoArticle.php b/app/admin/controller/ExaminationInformation/InfoArticle.php index e96a883..f2cc8de 100644 --- a/app/admin/controller/ExaminationInformation/InfoArticle.php +++ b/app/admin/controller/ExaminationInformation/InfoArticle.php @@ -52,27 +52,37 @@ class InfoArticle extends BaseController */ public function editinfoArticle(Request $request): array { - $params = $request->param(); - $this->validate($params, [ - 'info_article_type_guid|文章类型' => 'require', - 'info_article_title|文章标题' => 'require', - 'info_article_cover|文章封面' => 'require', - 'info_article_content|文章内容' => 'require', - 'info_article_order|文章排序' => 'require', - ]); - $model = ModelinfoArticle::where('info_article_guid', $params['info_article_guid'])->find(); + Db::startTrans(); + Tool::adminLockTableWrite('info_article'); + try { + $params = $request->param(); + $this->validate($params, [ + 'info_article_type_guid|文章类型' => 'require', + 'info_article_title|文章标题' => 'require', + 'info_article_cover|文章封面' => 'require', + 'info_article_content|文章内容' => 'require', + 'info_article_order|文章排序' => 'require', + ]); + $model = ModelinfoArticle::where('info_article_guid', $params['info_article_guid'])->find(); - if (!$model) throwErrorMsg("该资讯文章不存在", 1); - $model->allowField([ - 'info_article_update_user_guid', - 'info_article_title', - 'info_article_type_guid', - 'info_article_cover', - 'info_article_content', - 'info_article_order', - 'info_article_status', - ])->save($params); - return msg('编辑成功!'); + if (!$model) throwErrorMsg("该资讯文章不存在", 1); + $model->allowField([ + 'info_article_update_user_guid', + 'info_article_title', + 'info_article_type_guid', + 'info_article_cover', + 'info_article_content', + 'info_article_order', + 'info_article_status', + ])->save($params); + Db::commit(); + Tool::unlockTable(); + return msg('编辑成功!'); + } catch (\Throwable $th) { + Db::rollback(); + Tool::unlockTable(); + throw $th; + } } /** @@ -80,27 +90,35 @@ class InfoArticle extends BaseController */ public function addinfoArticle(Request $request): array { - $params = $request->param(); - $this->validate($params, [ - 'info_article_type_guid|文章类型' => 'require', - 'info_article_title|文章标题' => 'require', - 'info_article_cover|文章封面' => 'require', - 'info_article_content|文章内容' => 'require', - 'info_article_order|文章排序' => 'require' - ]); - - $model = ModelinfoArticle::create($params, [ - 'info_article_guid', - 'info_article_create_user_guid', - 'info_article_update_user_guid', - 'info_article_type_guid', - 'info_article_title', - 'info_article_cover', - 'info_article_content', - 'info_article_order', - 'info_article_status', - ]); - return msg('添加成功!'); + Db::startTrans(); + Tool::adminLockTableWrite('info_article'); + try { + $params = $request->param(); + $this->validate($params, [ + 'info_article_type_guid|文章类型' => 'require', + 'info_article_title|文章标题' => 'require', + 'info_article_cover|文章封面' => 'require', + 'info_article_content|文章内容' => 'require', + ]); + $model = ModelinfoArticle::create($params, [ + 'info_article_guid', + 'info_article_create_user_guid', + 'info_article_update_user_guid', + 'info_article_type_guid', + 'info_article_title', + 'info_article_cover', + 'info_article_content', + 'info_article_order', + 'info_article_status', + ]); + Db::commit(); + Tool::unlockTable(); + return msg('添加成功!'); + } catch (\Throwable $th) { + Db::rollback(); + Tool::unlockTable(); + throw $th; + } } /** @@ -108,16 +126,26 @@ class InfoArticle extends BaseController */ public function deleteinfoArticle(Request $request): array { - $params = $request->param(); - $this->validate($params, [ - 'info_article_guid' => 'require', - ]); - $info_article = ModelinfoArticle::where([ - 'info_article_guid' => explode(',', $params['info_article_guid']) - ])->select(); + Db::startTrans(); + Tool::adminLockTableWrite('info_article'); + try { + $params = $request->param(); + $this->validate($params, [ + 'info_article_guid' => 'require', + ]); + $info_article = ModelinfoArticle::where([ + 'info_article_guid' => explode(',', $params['info_article_guid']) + ])->select(); - $info_article->delete(); - return msg('删除成功!'); + $info_article->delete(); + Db::commit(); + Tool::unlockTable(); + return msg('删除成功!'); + } catch (\Throwable $th) { + Db::rollback(); + Tool::unlockTable(); + throw $th; + } } /** diff --git a/app/common/exception/Sort.php b/app/common/exception/Sort.php new file mode 100644 index 0000000..8f83af3 --- /dev/null +++ b/app/common/exception/Sort.php @@ -0,0 +1,203 @@ +model = new $model(); + //主键字段名初始化 + $this->pk = $this->model->db()->getPk(); + //表名初始化 + $this->table_name = $this->model->db()->getTable(); + //排序字段名初始化 + $this->initSortField(); + } + + /** + * 获取排序字段名 + */ + public function getSortField(): string + { + return $this->sort_field; + } + + /** + * 排序号新增处理 + * + * @param array $extra_wheres 关联类型 例:["xxx_type" => 1] + * @return int 返回新增所需的最新排序号 + */ + public function add(array $extra_wheres = []): int + { + $sort_field = $this->sort_field; + $max_sort = $this->model->where($extra_wheres)->order($sort_field, 'desc')->value($sort_field) ?? 0; + return ++$max_sort; + } + + /** + * 排序号互换处理(数据A <=> 数据B) + * + * @param string $guid 主键值 + * @param int $sort 新排序号 + * @param array $extra_wheres 关联类型 例:["xxx_type" => 1] + */ + public function swap(string $guid, int $sort, array $extra_wheres = []): void + { + $model = $this->model; + $table_name = $this->table_name; + $pk = $this->pk; + $sort_field = $this->sort_field; + + //数据A + $data_a = $model->where($pk, $guid)->find(); + if (!$data_a) { + throwErrorMsg('Tool::sortEditProc() : 找不到该数据原排序号', 444); + } + + //数据B + //查找当前数据所想更换的新排序号是否已有数据占用(排除自己),这个占用数据则视为数据B + $data_b = $model->where($sort_field, $sort)->where($pk, '<>', $guid)->where($extra_wheres)->find(); + + //数据B存在,开始进行互换处理 + if ($data_b) { + //【互换内容初始化】 + $update_data = []; + //检查模型是否有父祖级主键,有则也加入互换 + if ($this->isExistFathersAndAncestors()) { + $update_data[$model->parent_guid_field] = $data_a[$model->parent_guid_field]; + $update_data[$model->ancestors_guid_field] = $data_a[$model->ancestors_guid_field]; + } + //原字段排序加入互换 + $update_data = [$sort_field => $data_a[$sort_field]]; + //关联类型也加入互换 + foreach ($extra_wheres as $key => $value) { + $update_data[$key] = $data_a[$key]; + }; + + //【数据逻辑处理】 + //互换数据A与B的关联类型不相同时候,则代表为数据互换跨类型情况 + if (self::isAssociationTypeDataSame($data_a, $data_b, $extra_wheres)) { + //数据互换不同类型情况-正常互换操作 + Db::name($table_name)->where($sort_field, $sort)->where($extra_wheres)->update($update_data); + } else { + //数据互换跨类型情况-腾位操作 + $this->vacate($sort, $extra_wheres); + } + } + } + + /** + * 排序号腾位处理 + * + * @param int|null $sort 当前排序号 + * @param array $wheres 当前指定数据的额外查询条件(tp批量查询数组) 例:["xxx_type" => 1,...] + */ + public function vacate($sort, array $wheres = []): void + { + $sort_field = $this->sort_field; + $table_name = $this->table_name; + //新增数据的所属排序号已有数据占用,则腾位处理(已占用的数据及其后面所有数据一起排序号腾位+1) + if ($this->model->where($sort_field, $sort)->where($wheres)->value($sort_field) !== null) { + Db::name($table_name)->where($wheres)->where($sort_field, '>=', $sort)->inc($sort_field)->update(); + } + } + + /** + * 排序号退位处理 + * + * @param string $guid 当前主键值 + * @param array $correl_fields 关联类型字段 例:["xxx_guid",...] 代表删除排序号时会根据这些字段来关联(例:所属xxx类型、所属xxx系列)来进行排序处理 + */ + public function back(string $guid, array $correl_fields): void + { + $model = $this->model; + $sort_field = $this->sort_field; + //在所删除的数据之后的数据所属排序号将进行减位处理减位-1 + $deleted_find = $model->where($this->pk, $guid)->find(); + if ($deleted_find) { + $con = []; + foreach ($correl_fields as $correl_field) { + $con[$correl_field] = $deleted_find[$correl_field]; + } + $model->where($sort_field, '>', $deleted_find[$sort_field])->where($con)->dec($sort_field)->save(); + } + } + + /** + * 对比数据关联类型是否相同【排序号互换处理】 + * + * @param object $data_a 互换数据A + * @param object $data_b 互换数据B + * @param array $extra_wheres 关联类型数组 例:["xxx_type" => 1] + */ + private static function isAssociationTypeDataSame(object $data_a, object $data_b, array $extra_wheres): bool + { + foreach ($extra_wheres as $key => $val) { + if ($data_a[$key] != $data_b[$key]) { + return false; + } + }; + return true; + } + + /** + * 检查模型实例父祖级主键字段是否存在【排序号互换处理】 + */ + private function isExistFathersAndAncestors(): bool + { + return isset($this->model->parent_guid_field) && isset($this->model->ancestors_guid_field); + } + + /** + *初始化排序字段名 + */ + private function initSortField(): void + { + if (!isset($this->model->order_field)) { + throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field'); + } + $this->sort_field = $this->model->order_field; + } +} diff --git a/app/common/exception/Tool.php b/app/common/exception/Tool.php index 5bbae4f..3ecb948 100644 --- a/app/common/exception/Tool.php +++ b/app/common/exception/Tool.php @@ -7,6 +7,7 @@ use think\facade\Request; use think\facade\Log; use think\helper\Arr; use think\facade\Db; +use app\common\exception\Sort; use app\common\arw\adjfut\src\Exception\ErrorMsg; class Tool @@ -322,7 +323,7 @@ class Tool * @param array $extends["extraWhere"] 额外查询条件(批量混合查询格式: [ ['user_name','=','name'],... ]) * @return string|array */ - public static function getLastNextData(string $model, $sort_field_info, array $extends =[]) + public static function getLastNextData(string $model, $sort_field_info, array $extends = []) { //模型对象初始化 $model = new $model(); @@ -371,101 +372,67 @@ class Tool } /** - * 排序号更换处理 + * 后台接口锁表(排他锁/写锁/独占锁)操作 * - * 注意:使用该方法时候要启动事务!! - * @param string $model 模型层命名空间地址 + * @param string $table 被锁表名 + */ + public static function adminLockTableWrite(string $table) + { + Db::execute("LOCK TABLES {$table} WRITE,token WRITE,user WRITE"); + } + + /** + * 解除表锁 + */ + public static function unlockTable() + { + Db::execute("UNLOCK TABLES"); + } + + /** + * 排序数据修改前处理 + * + * 注意:(调用此类的方法需要进行事务与锁表操作) + * @param string $model_namespace 模型层命名空间地址 * @param string $guid 主键 * @param int $order 新排序号 * @param array $extra_wheres 关联类型 例:["xxx_type" => 1] */ - public static function sortEditProc(string $model, string $guid, int $order, array $extra_wheres = []): void + public static function sortEditProc(string $model_namespace, string $guid, int $order, array $extra_wheres = []): void { - //模型层实例化 - $model = new $model; - //表名 - $table_name = $model->db()->getTable(); - //获取当前的主键字段名 - $guld_field = $model->db()->getPk(); - //排序字段名 - if (!isset($model->order_field)) throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field'); - $order_field_name = $model->order_field; - //当前数据原信息 - $original_oreder_find = $model->where($guld_field, $guid)->find(); - if (!$original_oreder_find) throwErrorMsg('Tool::sortEditProc() : 找不到该数据原排序号', 444); - //查找当前数据所想更换的新排序号是否已有数据占用(排除自己) - //已被占用,则将它们的排序号互换 - if ($model->where($order_field_name, $order)->where($guld_field, '<>', $guid)->where($extra_wheres)->find()) { - $update_data = [$order_field_name => $original_oreder_find[$order_field_name]]; - - //检查是否有父祖级主键,有则也加入互换 - if (isset($model->parent_guid_field) && isset($model->ancestors_guid_field)) { - $update_data[$model->parent_guid_field] = $original_oreder_find[$model->parent_guid_field]; - $update_data[$model->ancestors_guid_field] = $original_oreder_find[$model->ancestors_guid_field]; - } - - //关联类型也加入互换 - foreach ($extra_wheres as $key => $value) { - $update_data[$key] = $original_oreder_find[$key]; - }; - Db::name($table_name)->where($order_field_name, $order)->where($extra_wheres)->update($update_data); - } + (new Sort($model_namespace))->swap($guid, $order, $extra_wheres); } /** - * 排序号腾位处理 + * 排序数据新增前处理 * - * 注意:使用该方法时候要启动事务!! - * @param string $model 模型层命名空间地址 - * @param int $order 当前排序号 + * 注意:(调用此类的方法需要进行事务与锁表操作) + * @param string $model_namespace 模型层命名空间地址 + * @param \think\model &$model 模型实例引用 + * @param int|null $order 当前排序号 * @param array $wheres 当前指定数据的额外查询条件(tp批量查询数组) 例:["xxx_type" => 1,...] */ - public static function sortInsertProc(string $model, int $order, array $wheres = []): void + public static function sortInsertProc(string $model_namespace, \think\model &$model, $order, array $wheres = []): void { - //模型层实例化 - $model = new $model; - //表名 - $table_name = $model->db()->getTable(); - //排序字段名 - if (!isset($model->order_field)) throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field'); - $order_field_name = $model->order_field; - //新增数据的所属排序号已有数据占用,则腾位处理(已占用的数据及其后面所有数据一起排序号腾位+1) - if ($model->where($order_field_name, $order)->where($wheres)->value($order_field_name) !== null) { - Db::name($table_name) - ->where($wheres) - ->where($order_field_name, '>=', $order) - ->inc($order_field_name) - ->update(); + $sort = new Sort($model_namespace); + if ($order) { + $sort->vacate($order, $wheres); + } else { + $model[$sort->getSortField()] = $sort->add($wheres); } } /** - * 排序号删除处理 - * 注意:使用该方法时候要启动事务!! - * @param string $model 模型层命名空间地址 - * @param array $guid 主键 + * 排序删除前处理 + * + * 注意:(调用此类的方法需要进行事务与锁表操作) + * @param string $model_namespace 模型层命名空间地址 + * @param string $guid 主键 * @param array $correl_fields 关联类型字段 例:["xxx_guid",...] 代表删除排序号时会根据这些字段来关联(例:所属xxx类型、所属xxx系列)来进行排序处理 */ - public static function sortDeleteProc(string $model, string $guid, array $correl_fields = []): void + public static function sortDeleteProc(string $model_namespace, string $guid, array $correl_fields = []): void { - //模型层实例化 - $model = new $model; - //获取当前的主键字段名 - $guld_field_name = $model->db()->getPk(); - //获取当前表排序字段名 - if (!isset($model->order_field)) throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field'); - $order_field_name = $model->order_field; - //在所删除的数据之后的数据所属排序号将进行减位处理减位-1 - if ($find = $model->where($guld_field_name, $guid)->find()) { - $con = []; - foreach ($correl_fields as $correl_field) { - $con[$correl_field] = $find[$correl_field]; - } - $model->where($order_field_name, '>', $find[$order_field_name]) - ->where($con) - ->dec($order_field_name) - ->save(); - } + (new Sort($model_namespace))->back($guid, $correl_fields); } /** diff --git a/app/common/model/ExaminationInformation/InfoArticle.php b/app/common/model/ExaminationInformation/InfoArticle.php index 8bf250a..d6f8007 100644 --- a/app/common/model/ExaminationInformation/InfoArticle.php +++ b/app/common/model/ExaminationInformation/InfoArticle.php @@ -82,10 +82,10 @@ class InfoArticle extends BaseModel { Tool::sortInsertProc( self::class, + $model, $model->info_article_order, ['info_article_type_guid' => $model->info_article_type_guid] ); - // self::checkRepeatData($model); $model->completeCreateField(); }