前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >常见的数据列表查询:同时支持置顶、锁定位置、移动排序、分页的实现逻辑

常见的数据列表查询:同时支持置顶、锁定位置、移动排序、分页的实现逻辑

作者头像
躺平程序员老修
发布2023-09-05 16:18:51
2980
发布2023-09-05 16:18:51
举报

需求描述

  1. 假设有个操作后台,可以获取某个分类下的所有数据列表
  2. 针对当前这个分类的列表,可以进行如下操作:置顶、锁定在当前位置、拖动排序(锁定的不可改变排序、如果是置顶的,必须同为置顶的数据)
QQ截图20230330132117.png
QQ截图20230330132117.png
QQ截图20230330133238.png
QQ截图20230330133238.png

实现逻辑

每页动态算出offset,然后替换,分页就实现了锁定

逻辑: 假设每页50条数据,查出当页的lock值的数据,比如第一页就是lock为1-50的,第二页就是lock为51-100的,同时查出前面几页所有的lock数据的数量,这个数量就是当前页需要往前偏移的offset,根据这个offset获取的列表,进行当前页有lock的进行替换。这样满足任意一页的数据。

算法:

  • 当前页lock值 = (page-1)limit ~ pagelimit (即需要替换到list的数据)
  • 当前页前面所有的lock值为 1 ~ (page-1)*limit,查出数据数量n,这个n就是要偏移的值,第一页是0就不查了,少一次请求
  • 当前列表的数据list = offset((page-1)*limit - n)->limit()

示例: 第一页,查出所有lock为0的正常排序的数据列表等待替换,此时n值为0,offset为(0-0=0),需要替换的数据是所有lock值为1-50的,从上往下塞入list中。

第二页,查出所有lock值是前面的数据数量n,即lock=1-50的数据数量,n即为需要往前的偏移量,offset=(page-1)limit-n=50-n,根据这个offset查出所有lock为0的第二页正常排序数据等待替换,查出lock值为第二页的即50-100,从上往下塞入list中。lock值需要减去(page-1)limit即50, 按照这个计算后的顺序塞入list。

第三页,查出所有lock值是前面的数据数量n,即lock=1-100的数据数量,n即为需要往前的偏移量,offset=(page-1)limit-n=100-n,根据这个offset查出所有lock为0的第三页正常排序数据等待替换,查出lock值为第三页的即100-150,从上往下塞入list中。lock值需要减去(page-1)limit即100, 按照这个计算后的顺序塞入list。

示例代码

代码语言:javascript
复制
    /**
     * 分类下的问题列表
     *
     * @param QuestionType $questionType
     * @param $questionId
     * @param $questionTitle
     * @param $limit
     * @param $page
     * @param $isAdmin
     * @param $hasAnswer
     * @return array
     */
    public function getTypeQuestions(QuestionType $questionType, $questionId, $questionTitle, $limit, $page, $isAdmin, $hasAnswer = false)
    {
        // 总数
        $count = $this->getTypeQuestionsCount($questionType, $questionId, $questionTitle, $isAdmin, $hasAnswer);

        // 当前页的lock值 双边包括 即将要进行替换到列表的数据
        $currLockStart = ($page - 1) * $limit + 1;
        $currLockEnd = $page * $limit;
        $currLockList = $questionType->questionsLock($currLockStart, $currLockEnd, $isAdmin, $hasAnswer)->get()->toArray();  // 1-50  51-100

        // 偏移量值 前面所有lock值的数据数量n page=1时为0减少一次请求
        $preLockStart = 1;
        $preLockEnd = ($page - 1) * $limit;
        $n = $preLockEnd == 0 ? 0 : $questionType->questionsLock($preLockStart, $preLockEnd, $isAdmin, $hasAnswer)->count();  // 1-0 1-50 1-100 的数据条目

        // 动态计算偏移量offset
        $offset = $limit * ($page - 1) - $n;

        // 为替换之前的数据列表
        $list = $questionType->questions($questionId, $questionTitle, $isAdmin, $hasAnswer)
            ->offset($offset)
            ->limit($limit)
            ->get()->toArray();

        // 指定插入 (当搜索时不处理位置锁定)
        if (!$questionId && !$questionTitle) {
            foreach ($currLockList as $item) {
                array_splice($list, $item['lock'] - $preLockEnd - 1, 0, [$item]);
            }
        }

        // 截取
        $data['total'] = $count;
        $data['data'] = array_slice($list, 0, $limit);
        return $data;
    }

    /**
     * 获取某一lock区间的question列表数据
     *
     * @param $lockStart
     * @param $lockEnd
     * @param bool $isAdmin
     * @param bool $hasAnswer
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function questionsLock($lockStart, $lockEnd, $isAdmin = true, $hasAnswer = false)
    {
        $table = (new QuestionTypeRelation())->getTable();
        $filed = $this->handleCommonRelationField($isAdmin);

        return $this->belongsToMany(Question::class, $table, 'question_type_id', 'question_id')
            ->select($filed)
            ->with('user')
            ->where($table.'.lock', '>=', $lockStart)

            ->where('questions.audit_status', 1)
//            ->when(!$isAdmin, function ($query) {
//                return $query->where('questions.audit_status', 1);
//            })

            ->when($hasAnswer, function ($query) {
                return $query->where('questions.answer_count', '>' ,0);
            })
            ->where($table.'.lock', '<=', $lockEnd)
            // ->withTrashed()
            ->orderBy($table.'.lock');
    }

    /**
     * 通用模型 获取某个type下的question列表数据
     *
     * @param $questionId
     * @param $questionTitle
     * @param bool $isAdmin
     * @param bool $hasAnswer
     * @return \Illuminate\Database\Concerns\BuildsQueries|\Illuminate\Database\Eloquent\Relations\BelongsToMany|mixed
     */
    public function questions($questionId, $questionTitle, $isAdmin = true, $hasAnswer = false)
    {
        $table = (new QuestionTypeRelation())->getTable();
        $filed = $this->handleCommonRelationField($isAdmin);

        $dbQuery = $this->belongsToMany(Question::class, $table, 'question_type_id', 'question_id')
            ->select($filed)
            ->with('user')
            // ->withTrashed()
            ->when($questionId, function ($query) use ($questionId) {
                return $query->where('questions.id', $questionId);
            })

            ->where('questions.audit_status', 1)
//            ->when(!$isAdmin, function ($query) {
//                return $query->where('questions.audit_status', 1);
//            })

            ->when($hasAnswer, function ($query) {
                return $query->where('questions.answer_count', '>' ,0);
            })
            ->when($questionTitle, function ($query) use ($questionTitle) {
                return $query->where('questions.title', 'like', sprintf('%%%s%%', $questionTitle));
            });

        // 没有任何搜索条件的时候,只查lock=0的,动态计算limit替换数据
        if (!$questionId && !$questionTitle) {
            $dbQuery->where($table.'.lock', 0);
        }

        return $dbQuery->orderByDesc($table.'.top')
        ->orderByDesc($table.'.sort');
    }
代码语言:javascript
复制
    /**
     * 置顶 取消置顶 移除
     *
     * @param QuestionType $questionType
     * @param $params
     * @return string
     */
    public function questionsAction(QuestionType $questionType, $params)
    {
        $message = '';
        switch ($params['action']) {
            case 'del':
                QuestionTypeRelation::where(['question_id' => $params['question_id'], 'question_type_id' => $questionType->id])->delete();
                $message = '移除成功';
                OperationLog::writeFeedLog(13, 50, $params['question_id']);
                break;
            case 'top':
                $item = QuestionTypeRelation::where(['question_id' => $params['question_id'], 'question_type_id' => $questionType->id])->first();
                if ($item) {
                    if ($item->top == 0) {
                        $res = QuestionTypeRelation::where('question_type_id', $questionType->id)->orderByDesc('top')->first();
                        $top = $res ? $res->top + 1 : 1;
                        $item->top = $top;
                        $item->save() && OperationLog::writeFeedLog(13, 12, $params['question_id']);
                        $message = '置顶成功';
                    } else {
                        $item->top = 0;
                        $item->save() && OperationLog::writeFeedLog(13, 13, $params['question_id']);
                        $message = '已取消置顶';
                    }
                }
                break;
            default:
                break;
        }
        return $message;
    }

    /**
     * 锁定 取消锁定
     *
     * @param QuestionType $questionType
     * @param $params
     * @return string
     */
    public function questionsLock(QuestionType $questionType, $params)
    {
        $message = '';
        $item = QuestionTypeRelation::where(['question_id' => $params['question_id'], 'question_type_id' => $questionType->id])->first();
        if ($item) {
            if ($params['index'] == 0) {
                $item->lock = $params['index'];
                $item->save() && OperationLog::writeFeedLog(13, 49, $params['question_id']);
                $message = '已取消锁定';
            } else {
                // 避免锁定值重复导致数据丢失 避免重复锁定同一个值来减少请求
                if ($params['index'] == $item->lock) {
                    $message = '锁定成功';
                } else {
                    $count = QuestionTypeRelation::where(['lock' => $params['index'], 'question_type_id' => $questionType->id])->count();
                    if ($count) {
                        $message = '当前位置已有锁定数据,请先取消锁定后再进行设置';
                    } else {
                        $item->lock = $params['index'];
                        $item->save() && OperationLog::writeFeedLog(13, 48, $params['question_id']);
                        $message = '锁定成功';
                    }
                }
            }
        }
        return $message;
    }

    /**
     * @param QuestionType $questionType
     * @param $params
     * @return string
     */
    public function questionsSort(QuestionType $questionType, $params)
    {
        $message = '';
        $currItem = QuestionTypeRelation::where(['question_id' => $params['curr_question_id'], 'question_type_id' => $questionType->id])->first();
        $targetItem = QuestionTypeRelation::where(['question_id' => $params['target_question_id'], 'question_type_id' => $questionType->id])->first();

        if ($currItem && $targetItem) {
            switch ($params['is_top']) {
                case 1:
                    // 必须同为置顶数据
                    if ($currItem->top && $targetItem->top) {
                        $message = $this->dragSort($questionType->id, $params['action'], $currItem, $targetItem, true);
                    } else {
                        $message = '必须同为置顶数据';
                    }
                    break;
                case 0:
                    // 必须都没有置顶
                    if (!$currItem->top && !$targetItem->top) {
                        $message = $this->dragSort($questionType->id, $params['action'], $currItem, $targetItem, false);
                    } else {
                        $message = '必须同为非置顶数据';
                    }
                    break;
                default:
                    break;
            }
        }
        return $message;
    }

    /**
     * @param $questionTypeId
     * @param $action
     * @param QuestionTypeRelation $currItem
     * @param QuestionTypeRelation $targetItem
     * @param false $isTop
     * @return string
     */
    private function dragSort($questionTypeId, $action, QuestionTypeRelation $currItem, QuestionTypeRelation $targetItem, $isTop = false)
    {
        if ($isTop) {
            $filed = 'top';
        } else {
            $filed = 'sort';
        }

        $message = '';
        $oldCurrSort = (int) $currItem->$filed;
        $oldTargetSort = (int) $targetItem->$filed;

        switch ($action) {
            case 'up':
                // 移动到谁上面
                if ($oldCurrSort >= $oldTargetSort) {
                    $message = '上移参数错误,无法将上方数据上移置下方数据前,请尝试下移动作';
                } else {
                    QuestionTypeRelation::where('question_type_id', $questionTypeId)
                        ->where($filed, '>', $oldCurrSort)
                        ->where($filed, '<=', $oldTargetSort)
                        ->where($filed, '!=', 0)
                        ->update([$filed => DB::raw($filed . ' - 1')]);

                    $currItem->$filed = $oldTargetSort;
                    $currItem->save() && OperationLog::writeFeedLog(13, 11, $currItem->question_id);
                    $message = '调序成功';
                }
                break;
            case 'down':
                // 移动到谁下面
                if ($oldCurrSort <= $oldTargetSort) {
                    $message = '下移参数错误,无法将下方数据下移置上方数据后,请尝试上移动作';
                } else {
                    QuestionTypeRelation::where('question_type_id', $questionTypeId)
                        ->where($filed, '<', $oldCurrSort)
                        ->where($filed, '>=', $oldTargetSort)
                        ->where($filed, '!=', 0)
                        ->update([$filed => DB::raw($filed . ' + 1')]);

                    $currItem->$filed = $oldTargetSort;
                    $currItem->save() && OperationLog::writeFeedLog(13, 11, $currItem->question_id);
                    $message = '调序成功';
                }
                break;
            default:
                break;
        }
        return $message;
    }

    /**
     * 更新问题分类关系
     * 维护分类关系的更新,原本的sort不变,新增的才用id,防止拖动排序后的sort被重新赋值成ID
     *
     * @param Question $question
     * @param $questionType
     */
    private function handleEditQuestionRelation(Question $question, $questionType)
    {
        $sync = [];
        $relations = QuestionTypeRelation::where('question_id', $question->id)->get();
        $currTypeIds = $relations->pluck('question_type_id')->toArray();

        $intersects = array_intersect($currTypeIds, $questionType);
        $diffs = array_diff($questionType, $currTypeIds);
        foreach ($intersects as $intersect) {
            foreach ($relations as $relation) {
                if ($relation->question_type_id == $intersect) {
                    $sync[$intersect] = ['sort' => $relation->sort];
                }
            }
        }
        foreach ($diffs as $diff) {
            $sync[$diff] = ['sort' => $question->id];
        }
        $question->questionTypes()->sync($sync);
    }

    /**
     * 问题审核操作日志
     *
     * @param $questionId
     * @param $auditStatus
     */
    private function handleQuestionAuditLogs($questionId, $auditStatus)
    {
        // 添加日志
        $opAction = 0;
        switch ($auditStatus) {
            case 0:
                $opAction = 43;
                break;
            case 1:
                $opAction = 5;
                break;
            case 2:
                $opAction = 6;
                break;
        }
        OperationLog::writeFeedLog(10, $opAction, $questionId);
    }

    /**
     * 审核操作系统通知
     *
     * @param Question $question
     * @param $auditStatus
     */
    private function handleQuestionAuditNotice(Question $question, $auditStatus)
    {
        // 系统通知
        $notice_type = Notice::NOTICE_SYSTEM_QUESTION_AUDIT;
        $toUser = '';
        switch ($auditStatus) {
            case 0:
                break;
            case 1:
                $toUser = '【你于' . (new Times())->utcChangeToPrc($question->created_at) . '提出的标题为“' . $question->title . '”的问题已通过审核】';
                break;
            case 2:
                $toUser = '【你于' . (new Times())->utcChangeToPrc($question->created_at) . '提出的标题为“' . $question->title . '”的问题审核不通过,已删除】';
                break;
        }
        if (!empty($toUser)) {
            (new Notice())->system($question->user_id, $toUser, $notice_type);
        }
    }

    /**
     * 根据id或标题查找问题
     *
     * @param array $params
     * @return mixed
     */
    public function query($params)
    {
        if (!empty($params['light'])) {
            $columns = ['id', 'user_id', 'summary'];
        } else {
            $columns = ['id', 'user_id', 'title', 'summary', 'images', 'video', 'created_at'];
        }

        $questions = Question::query()
            ->with(['user'])
            ->when(!empty($params['id']), function ($query) use ($params) {
                $id = array_values(
                    array_filter(
                        explode(',', $params['id'])
                    )
                );
                return $query->whereIn('id', $id);
            })
            ->when(!empty($params['title']), function ($query) use ($params) {
                return $query->where('title', 'like', sprintf('%%%s%%', $params['title']));
            })
            ->where('audit_status', Question::AUDIT_STATUS_PASS)
            ->get($columns)
            ->toArray();

        return $questions ?: [];
    }

    /**
     * 查询简单版
     *
     * @param array  $ids
     * @return mixed
     */
    public function querySimple($ids)
    {
        $questions = Question::query()
            ->with(['user'])
            ->whereIn('id', $ids)
            ->where('audit_status', Question::AUDIT_STATUS_PASS)
            ->get(['id', 'user_id', 'images', 'video'])
            ->toArray();

        return $questions ?: [];
    }

    /**
     * 处理最佳答案
     *
     * @param $response
     * @return mixed
     */
    public static function handleBestAnswer($response)
    {
        $questionIds = [];
        foreach ($response['data'] as $item) {
            $questionIds[] = $item['id'];
        }

        if (count($questionIds)) {
            $allAnswers = self::getBestAnswer($questionIds);
            foreach ($response['data'] as &$res) {
                $res['best_answer'] = null;
                foreach ($allAnswers as $allAnswer) {
                    if ($res['id'] == $allAnswer['question_id']) {
                        $res['best_answer'] = $allAnswer;
                    }
                }
            }
        }
        return $response;
    }

    /**
     * @param $response
     * @return array
     */
    public static function handleFlycardFormat($response)
    {
        $res = [];
        $res['total'] = $response['total'];
        $appId = Question::APPID_QUESTION;
        foreach ($response['data'] as $datum) {
            $temp['appid'] = $appId;
            $temp['title'] = $datum['title'];
            $temp['extra']['question'] = [
                'id' => $datum['id'],
                'content' => $datum['summary'],
                'images' => $datum['images'],
                'video' => $datum['video'],
                'user' => $datum['user']
            ];

            if ($datum['best_answer']) {
                $temp['extra']['answer'] = [
                    'id' => $datum['best_answer']['id'],
                    'content' => $datum['best_answer']['summary'],
                    'images' => $datum['best_answer']['images'],
                    'video' => $datum['best_answer']['video'],
                    'user' => $datum['best_answer']['user']
                ];
            }
            $res['data'][] = $temp;
        }
        return $res;
    }

    /**
     * pgc接口
     * 获取当前question的额外数据 [question_id, answer_id, answer_count, best_answer_like_count]
     *
     * @param $questionId
     * @param $memberId
     * @return array
     */
    public static function getPgcQuestionData($questionId, $memberId)
    {
        $res = [];
        $question = Question::find($questionId);
        if ($question) {
            $answerCount = $question->answer_count;
            $bestAnswer = self::getBestAnswer([$questionId]);
            if ($bestAnswer->count()) {
                // 查答案点赞状态
                $hasLiked = false;
                if ($memberId) {
                    $user = UserModel::where('member_id', $memberId)->first('id');
                    $hasLiked = Answer::find($bestAnswer[0]->id)->liked($user->id);
                }
                $res = [
                    'question_id' => $questionId,
                    'answer_id' => $bestAnswer[0]->id,
                    'answer_count' => $answerCount,
                    'best_answer_like_count' => $bestAnswer[0]->like_count,
                    'has_liked' => $hasLiked,
                ];
            }
        }
        return $res;
    }

    /**
     * 封装通用获取最佳答案数据
     *
     * @param $questionIds
     * @return array|\Illuminate\Http\Resources\Json\AnonymousResourceCollection
     */
    private static function getBestAnswer(array $questionIds)
    {
        $allAnswers = [];
        $topAnswer = Answer::query()
            ->select('id', 'question_id')
            ->when($questionIds, function ($query) use ($questionIds) {
                return $query->whereIn('question_id', $questionIds);
            })
            ->where('audit_status', Answer::AUDIT_STATUS_PASS)
            ->where('sort', 1)
            ->pluck('id', 'question_id')->toArray();

        // 有置顶答案的问题ID、答案ID
        $topQuestionIds = array_keys($topAnswer);
        $topAnswerIds = array_values($topAnswer);

        // 没有置顶的问题的答案ID,以及相同排序的时候最大id值
        $normalQuestionIds = array_diff($questionIds, $topQuestionIds);
        $normalAnswerIds = [];
        if (count($normalQuestionIds)) {
            $idStr = implode(',', $normalQuestionIds);
            $sql = '
                    SELECT
                     a1.id,
                     a1.question_id,
                     a2.max_id
                    FROM
                        answers a1
                    JOIN (
                        SELECT
                            question_id,
                            max(view_count + like_count) AS hot_count,max(id) as max_id
                        FROM
                            answers
                        WHERE
                            question_id IN (' . $idStr . ')
                        AND
                            audit_status = 1
                        AND
                            deleted_at IS NULL
                        GROUP BY
                            question_id
                    ) a2 ON a1.question_id = a2.question_id
                    AND (
                        a1.view_count + a1.like_count
                    ) = a2.hot_count

                    WHERE
                        a1.question_id IN (' . $idStr . ')
                    AND
                        audit_status = 1
                    AND
                        deleted_at IS NULL
                    GROUP BY
                      a1.question_id
';
            $normalAnswer = DB::select($sql);

            // 当热度值一样的时候,会有问题,需要判断max_id的hot_count是否等于id的hot_count,相等则取max_id
            // 概率很小,建议省点资源,不然还需要每个不同的ID查询两次
            foreach ($normalAnswer as $normal) {
                $normalAnswerIds[] = $normal->id;
            }
        }
        $allAnswerIds = array_merge($normalAnswerIds, $topAnswerIds);
        $allAnswers = Answer::with('user')
            ->select(['id', 'question_id', 'user_id', 'images', 'video', 'summary', 'audit_status', 'like_count', 'view_count', 'comment_count'])
            ->whereIn('id', $allAnswerIds)
            ->get();

        $allAnswers = AnswerResource::collection($allAnswers);

        return $allAnswers;
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求描述
  • 实现逻辑
  • 示例代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档