Tipask一个开源社区代码设计的学习记录(二)问答功能

image.png

问答功能用户提出一个问题(可以加入分类和标签),其他登录用户可以回答其提出的问题,可以选出最佳答案

提问:

image.png
DROP TABLE IF EXISTS `community_questions`;
CREATE TABLE `community_questions`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `category_id` int(11) NOT NULL DEFAULT 0,
  `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `description` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL,
  `price` smallint(6) NOT NULL DEFAULT 0,
  `hide` tinyint(4) NOT NULL DEFAULT 0,
  `answers` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `views` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `followers` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `collections` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `comments` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `device` tinyint(4) NOT NULL DEFAULT 1,
  `status` tinyint(4) NOT NULL DEFAULT 0,
  `created_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
  `has_img` smallint(2) NULL DEFAULT 0 COMMENT '是否包含图片0无图片1有图片',
  `object_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `questions_created_at_index`(`created_at`) USING BTREE,
  INDEX `questions_updated_at_index`(`updated_at`) USING BTREE,
  INDEX `questions_user_id_index`(`user_id`) USING BTREE,
  INDEX `questions_title_index`(`title`) USING BTREE,
  INDEX `questions_category_id_index`(`category_id`) USING BTREE,
  INDEX `questions_answers_index`(`answers`) USING BTREE,
  INDEX `questions_views_index`(`views`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 57 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

提问关联了 回答表和问题邀请表

 /*问题所有回答*/
    public function answers()
    {
        return $this->hasMany('App\Models\Answer','question_id');
    }


    /*问题所有邀请*/
    public function invitations()
    {
        return $this->hasMany('App\Models\QuestionInvitation','question_id');
    }

问题同样也会根据回答数等信息提供排序推荐

/*热门问题*/
    public static function hottest($categoryId = 0,$pageSize=20)
    {
        $query = self::with('user');
        if( $categoryId > 0 ){
            $query->where('category_id','=',$categoryId);
        }
        $list = $query->where('status','>',0)->orderBy('views','DESC')->orderBy('answers','DESC')->orderBy('created_at','DESC')->paginate($pageSize);
        return $list;

    }

    /*最新问题*/
    public static function newest($categoryId=0 , $pageSize=20)
    {
        $query = self::with('user');
        if( $categoryId > 0 ){
            $query->where('category_id','=',$categoryId);
        }
        $list = $query->where('status','>',0)->orderBy('created_at','DESC')->paginate($pageSize);
        return $list;
    }

    /*未回答的*/
    public static function unAnswered($categoryId=0 , $pageSize=20)
    {
        $query = self::query();
        if( $categoryId > 0 ){
            $query->where('category_id','=',$categoryId);
        }
        $list = $query->where('status','>',0)->where('answers','=',0)->orderBy('created_at','DESC')->paginate($pageSize);
        return $list;
    }

    /*悬赏问题*/
    public static function reward($categoryId=0 , $pageSize=20)
    {
        $query = self::query();
        if( $categoryId > 0 ){
            $query->where('category_id','=',$categoryId);
        }
        $list = $query->where('status','>',0)->where('price','>',0)->orderBy('created_at','DESC')->paginate($pageSize);
        return $list;
    }

    /*最近热门问题*/
    public static function recent()
    {
        $list = Cache::remember('recent_questions',300, function() {
            return self::where('status','>',0)->where('created_at','>',Carbon::today()->subWeek())->orderBy('views','DESC')->orderBy('answers','DESC')->orderBy('created_at','DESC')->take(12)->get();
        });

        return $list;
    }

    /*是否已经邀请用户回答了*/
    public function isInvited($sendTo,$fromUserId){
        return $this->invitations()->where("send_to","=",$sendTo)->where("from_user_id","=",$fromUserId)->count();
    }


    /*问题搜索*/
    public static function search($word,$size=16)
    {
        $list = self::where('title','like',"$word%")->paginate($size);
        return $list;
    }

回答

image.png
DROP TABLE IF EXISTS `community_answers`;
CREATE TABLE `community_answers`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `question_title` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `question_id` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `user_id` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `content` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `supports` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `oppositions` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `comments` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `device` tinyint(4) NOT NULL DEFAULT 1,
  `status` tinyint(4) NOT NULL DEFAULT 0,
  `adopted_at` timestamp(0) NULL DEFAULT NULL,
  `created_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
  `object_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `answers_created_at_index`(`created_at`) USING BTREE,
  INDEX `answers_updated_at_index`(`updated_at`) USING BTREE,
  INDEX `answers_question_id_index`(`question_id`) USING BTREE,
  INDEX `answers_user_id_index`(`user_id`) USING BTREE,
  INDEX `answers_adopted_at_index`(`adopted_at`) USING BTREE,
  INDEX `answers_question_title_index`(`question_title`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 95 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

邀请问答结构

DROP TABLE IF EXISTS `community_question_invitations`;
CREATE TABLE `community_question_invitations`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `from_user_id` int(10) UNSIGNED NOT NULL DEFAULT 0,
  `user_id` int(10) UNSIGNED NOT NULL,
  `send_to` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  `question_id` int(10) UNSIGNED NOT NULL,
  `status` tinyint(4) NOT NULL DEFAULT 0,
  `created_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `question_invitations_user_id_index`(`user_id`) USING BTREE,
  INDEX `question_invitations_question_id_index`(`question_id`) USING BTREE,
  INDEX `question_invitations_send_to_index`(`send_to`) USING BTREE,
  INDEX `question_invitations_from_user_id_index`(`from_user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

创建一个问题的流程

  /**
     * 问题添加页面显示
     */
    public function create(Request $request)
    {
        $to_user_id =  $request->query('to_user_id',0);
        $toUser = User::find($to_user_id);
        $product_tags = Tag::tag_product();//产品话题
        $hot_tags = Tag::tag_hot(); //热门话题
        return view("theme::question.create")->with(compact('toUser','to_user_id','product_tags','hot_tags'));
    }

    /*创建提问*/
    public function store(Request $request, CaptchaService $captchaService)
    {
       // var_dump(preg_match('/img+/',clean($request->input('description'))));die;
        $loginUser = $request->user();
        if($request->user()->status === 0){
            return $this->error(route('website.index'),'操作失败!您的邮箱还未验证,验证后才能进行该操作!');
        }

        /*防灌水检查*/
        if( Setting()->get('question_limit_num') > 0 ){
            $questionCount = $this->counter('question_num_'. $loginUser->id);
            if( $questionCount > Setting()->get('question_limit_num')){
                return $this->showErrorMsg(route('website.index'),'你已超过每小时最大提问数'.Setting()->get('question_limit_num').',如有疑问请联系管理员!');
            }
        }

        $request->flash();
        /*如果开启验证码则需要输入验证码*/
        if( Setting()->get('code_create_question') ){
            $captchaService->setValidateRules('code_create_question',$this->validateRules);
        }

        $this->validate($request,$this->validateRules);
        $price = abs($request->input('price'));

        if($price > 0 && $request->user()->userData->coins < $price){
            return $this->error(route('ask.question.create'),'操作失败!您的金币数不足!');
        }
        //判断问答中是否包含图片
        $hasimg = 0;
        if(preg_match('/img+/',clean($request->input('description'))))
        {
            $hasimg = 1;
        }

        $data = [
            'user_id'      => $loginUser->id,
            'category_id'      => $request->input('category_id',0),
            'title'        => trim($request->input('title')),
            'description'  => clean($request->input('description')),
            'price'        => $price,
            'hide'         => intval($request->input('hide')),
            'status'       => 1,
            'has_img'      => $hasimg,
        ];

        $question = Question::create($data);
        /*判断问题是否添加成功*/
        if($question){
            if (!empty($request->user())) {
                $ip = $request->getClientIp();
                $user_id = MongoUsers::get_id_by_csp_user($request->user());
                Log::info('question_5_'.$user_id);
                Credits::updateUserCredit($user_id,'sendfeed', '', 5,'',$ip);
            }
            /*悬赏提问*/
            if($question->price > 0){
                $this->credit($question->user_id,'ask',-$question->price,0,$question->id,$question->title);
            }

            /*添加标签*/
            $tagString = trim($request->input('tags'));
            Tag::multiSave($tagString,$question);


            //记录动态
            $this->doing($question->user_id,'ask',get_class($question),$question->id,$question->title,$question->description);


            /*邀请作答逻辑处理*/
            $to_user_id = $request->input('to_user_id',0);
            $this->notify($question->user_id,$to_user_id,'invite_answer',$question->title,$question->id);

            $this->invite($question->id,$to_user_id,$request);

            /*用户提问数+1*/
            $loginUser->userData()->increment('questions');
            UserTag::multiIncrement($loginUser->id,$question->tags()->get(),'questions');
            $this->credit($request->user()->id,'ask',Setting()->get('coins_ask'),Setting()->get('credits_ask'),$question->id,$question->title);
            if($question->status == 1 ){
                $message = '发起提问成功! '.get_credit_message(Setting()->get('credits_ask'),Setting()->get('coins_ask'));
            }else{
                $message = '问题发布成功!为了确保问答的质量,我们会对您的提问内容进行审核。请耐心等待......';
            }

            $this->counter( 'question_num_'. $question->user_id , 1 , 60 );

            return $this->success(route('ask.question.detail',['question_id'=>$question->id]),$message);


        }

        return  $this->error(route('website.index'),"问题创建失败,请稍后再试");

    }

邀请问答流程

 /*邀请回答*/
    public function invite($question_id,$to_user_id,Request $request){

        $loginUser = $request->user();

        if($loginUser->id == $to_user_id){
            return $this->ajaxError(50009,'不用邀请自己,您可以直接回答 :)');
        }

        $question = Question::find($question_id);
        if(!$question){
            return $this->ajaxError(50001,'notFound');
        }

        if( $this->counter('question_invite_num_'.$loginUser->id) > config('tipask.user_invite_limit') ){
            return $this->ajaxError(50007,'超出每天最大邀请次数');
        }


        $toUser = User::find(intval($to_user_id));
        if(!$toUser){
            return $this->ajaxError(50005,'被邀请用户不存在');
        }

        if(!$toUser->allowedEmailNotify('invite_answer')){
            return $this->ajaxError(50006,'邀请人设置为不允许被邀请回答');
        }

        /*是否已邀请,不能重复邀请*/
        if($question->isInvited($toUser->email,$loginUser->id)){
            return $this->ajaxError(50008,'该用户已被邀请,不能重复邀请');
        }

        $invitation = QuestionInvitation::create([
            'from_user_id'=> $loginUser->id,
            'question_id'=> $question->id,
            'user_id'=> $toUser->id,
            'send_to'=> $toUser->email
        ]);

        if($invitation){
            $this->counter('question_invite_num_'.$loginUser->id, 1);
            $subject = $loginUser->name."在「".Setting()->get('website_name')."」向您发起了回答邀请";
            $message = "我在 ".Setting()->get('website_name')." 上遇到了问题「".$question->title."」 → ".route("ask.question.detail",['question_id'=>$question->id]).",希望您能帮我解答 ";
            $this->sendEmail($invitation->send_to,$subject,$message);
            return $this->ajaxSuccess('success');
        }

        return $this->ajaxError(10008,'邀请失败,请稍后再试');
    }

回答流程

 /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request, CaptchaService $captchaService)
    {
        $loginUser = $request->user();
        if($loginUser->status === 0){
            return $this->error(route('website.index'),'操作失败!您的邮箱还未验证,验证后才能进行该操作!');
        }

        /*防灌水检查*/
        if( Setting()->get('answer_limit_num') > 0 ){
            $questionCount = $this->counter('answer_num_'. $loginUser->id,1);
            if( $questionCount > Setting()->get('answer_limit_num')){
                return $this->showErrorMsg(route('website.index'),'你已超过每小时回答限制数'.Setting()->get('answer_limit_num').',请稍后再进行该操作,如有疑问请联系管理员!');
            }
        }

        $question_id = $request->input('question_id');
        $question = Question::find($question_id);

        if(empty($question)){
            abort(404);
        }
        $loginUser = $request->user();
        $request->flash();
        /*普通用户修改需要输入验证码*/
        if( Setting()->get('code_create_answer') ){
            $captchaService->setValidateRules('code_create_answer', $this->validateRules);
        }
        //$validator=$this->validate($request,$this->validateRules);
        $validator = Validator::make($request->all(), $this->validateRules, $this->message);
        if($validator->passes()) {
            $answerContent = clean($request->input('content'));
            $data = [
                'user_id' => $loginUser->id,
                'question_id' => $question_id,
                'question_title' => $question->title,
                'content' => $answerContent,
                'status' => 1,
            ];
            $answer = Answer::create($data);
            if ($answer) {

                $ip = $request->getClientIp();
                $user_id = MongoUsers::get_id_by_csp_user($request->user());
                Log::info('answer_5_'.$user_id);
                Credits::updateUserCredit($user_id,'comment', $question_id, 5,'',$ip);

                /*用户回答数+1*/
                $loginUser->userData()->increment('answers');

                /*问题回答数+1*/
                $question->increment('answers');

                UserTag::multiIncrement($loginUser->id, $question->tags()->get(), 'answers');

                /*记录动态*/
                $this->doing($answer->user_id, 'answer', get_class($question), $question->id, $question->title, $answer->content);

                /*记录通知*/
                $this->notify($answer->user_id, $question->user_id, 'answer', $question->title, $answer->id, $answer->content, 'question', $question->id);

                /*回答后通知关注问题*/
                if (intval($request->input('followed'))) {
                    $attention = Attention::where("user_id", '=', $request->user()->id)->where('source_type', '=', get_class($question))->where('source_id', '=', $question->id)->count();
                    if ($attention === 0) {
                        $data = [
                            'user_id' => $request->user()->id,
                            'source_id' => $question->id,
                            'source_type' => get_class($question),
                            'subject' => $question->title,
                        ];
                        Attention::create($data);

                        $question->increment('followers');
                    }
                }


                /*修改问题邀请表的回答状态*/
                QuestionInvitation::where('question_id', '=', $question->id)->where('user_id', '=', $request->user()->id)->update(['status' => 1]);

                $this->counter('answer_num_' . $answer->user_id, 1, 60);

                /*记录积分*/
                if ($answer->status == 1 && $this->credit($request->user()->id, 'answer', Setting()->get('coins_answer'), Setting()->get('credits_answer'), $question->id, $question->title)) {
                    $message = '回答成功! ' . get_credit_message(Setting()->get('credits_answer'), Setting()->get('coins_answer'));
                    //return $this->success(route('ask.question.detail', ['question_id' => $answer->question_id]), $message);
                    return $this->ajaxSuccess($answer);
                }
            }
           // return redirect(route('ask.question.detail', ['id' => $question_id]));
            return $this->ajaxSuccess($answer);
        }
        else
        {
            return $this->ajaxError($validator);
        }
    }

采纳回答流程

public function adopt($id,Request $request)
    {
        $answer = Answer::findOrFail($id);

        if(($request->user()->id !== $answer->question->user_id) && !$request->user()->is('admin')  ){
            abort(403);
        }

        /*防止重复采纳*/
        if($answer->adopted_at>0){
            return $this->error(route('ask.question.detail',['question_id'=>$answer->question_id]),'该回答已被采纳,不能重复采纳');
        }


        DB::beginTransaction();
        try{

            $answer->adopted_at = Carbon::now();
            $answer->save();

            $answer->question->status = 2;
            $answer->question->save();

            $answer->user->userData->increment('adoptions');

            /*悬赏处理*/
            $this->credit($answer->user_id,'answer_adopted',($answer->question->price+Setting()->get('coins_adopted',0)),Setting()->get('credits_adopted'),$answer->question->id,$answer->question->title);

            UserTag::multiIncrement($request->user()->id,$answer->question->tags()->get(),'adoptions');
            $this->notify($request->user()->id,$answer->user_id,'adopt_answer',$answer->question_title,$answer->question_id);
            DB::commit();
            /*发送邮件通知*/
            if($answer->user->allowedEmailNotify('adopt_answer')){
                $emailSubject = '您对于问题「'.$answer->question_title.'」的回答被采纳了!';
                $emailContent = "您对于问题「".$answer->question_title."」的回答被采纳了!
点击此链接查看详情 → ".route('ask.question.detail',['question_id'=>$answer->question_id]); $this->sendEmail($answer->user->email,$emailSubject,$emailContent); } return $this->success(route('ask.question.detail',['question_id'=>$answer->question_id]),"回答采纳成功!".get_credit_message(Setting()->get('credits_adopted'),Setting()->get('coins_adopted'))); }catch (\Exception $e) { echo $e->getMessage(); DB::rollBack(); } return $this->error(route('ask.question.detail',['question_id'=>$answer->question_id]),"回答采纳失败,请稍后再试!"); }

你可能感兴趣的:(Tipask一个开源社区代码设计的学习记录(二)问答功能)