问答功能用户提出一个问题(可以加入分类和标签),其他登录用户可以回答其提出的问题,可以选出最佳答案
提问:
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;
}
回答
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]),"回答采纳失败,请稍后再试!");
}