maliang il y a 4 ans
Parent
commit
e41dfdd65c
100 fichiers modifiés avec 9386 ajouts et 0 suppressions
  1. 19 0
      .editorconfig
  2. 6 0
      .gitignore
  3. 1 0
      .htaccess
  4. 77 0
      app/Console/Commands/AdoptAnswer.php
  5. 42 0
      app/Console/Kernel.php
  6. 60 0
      app/Exceptions/Handler.php
  7. 85 0
      app/Http/Controllers/Account/AttentionController.php
  8. 164 0
      app/Http/Controllers/Account/AuthenticationController.php
  9. 61 0
      app/Http/Controllers/Account/CollectionController.php
  10. 35 0
      app/Http/Controllers/Account/DoingsController.php
  11. 101 0
      app/Http/Controllers/Account/DraftController.php
  12. 90 0
      app/Http/Controllers/Account/EmailController.php
  13. 209 0
      app/Http/Controllers/Account/MessageController.php
  14. 43 0
      app/Http/Controllers/Account/NotificationController.php
  15. 174 0
      app/Http/Controllers/Account/OauthController.php
  16. 32 0
      app/Http/Controllers/Account/PasswordController.php
  17. 257 0
      app/Http/Controllers/Account/ProfileController.php
  18. 89 0
      app/Http/Controllers/Account/QuestionInvitationController.php
  19. 69 0
      app/Http/Controllers/Account/ReportController.php
  20. 103 0
      app/Http/Controllers/Account/SearchController.php
  21. 157 0
      app/Http/Controllers/Account/SpaceController.php
  22. 95 0
      app/Http/Controllers/Account/SupportController.php
  23. 55 0
      app/Http/Controllers/Account/TopController.php
  24. 305 0
      app/Http/Controllers/Account/UserController.php
  25. 91 0
      app/Http/Controllers/Admin/AccountController.php
  26. 40 0
      app/Http/Controllers/Admin/AdminController.php
  27. 122 0
      app/Http/Controllers/Admin/AnswerController.php
  28. 142 0
      app/Http/Controllers/Admin/ArticleController.php
  29. 214 0
      app/Http/Controllers/Admin/AuthenticationController.php
  30. 130 0
      app/Http/Controllers/Admin/BanIpController.php
  31. 134 0
      app/Http/Controllers/Admin/CategoryController.php
  32. 133 0
      app/Http/Controllers/Admin/CommentController.php
  33. 79 0
      app/Http/Controllers/Admin/CreditController.php
  34. 49 0
      app/Http/Controllers/Admin/DraftController.php
  35. 130 0
      app/Http/Controllers/Admin/ExchangeController.php
  36. 107 0
      app/Http/Controllers/Admin/FriendshipLinkController.php
  37. 132 0
      app/Http/Controllers/Admin/GoodsController.php
  38. 162 0
      app/Http/Controllers/Admin/IndexController.php
  39. 123 0
      app/Http/Controllers/Admin/NoticeController.php
  40. 105 0
      app/Http/Controllers/Admin/OperationController.php
  41. 93 0
      app/Http/Controllers/Admin/PermissionController.php
  42. 128 0
      app/Http/Controllers/Admin/QuestionController.php
  43. 131 0
      app/Http/Controllers/Admin/RecommendationController.php
  44. 64 0
      app/Http/Controllers/Admin/ReportController.php
  45. 111 0
      app/Http/Controllers/Admin/RoleController.php
  46. 375 0
      app/Http/Controllers/Admin/SettingController.php
  47. 50 0
      app/Http/Controllers/Admin/SystemController.php
  48. 178 0
      app/Http/Controllers/Admin/TagController.php
  49. 62 0
      app/Http/Controllers/Admin/ToolController.php
  50. 175 0
      app/Http/Controllers/Admin/UserController.php
  51. 33 0
      app/Http/Controllers/Admin/XunSearchController.php
  52. 227 0
      app/Http/Controllers/AjaxController.php
  53. 229 0
      app/Http/Controllers/Ask/AnswerController.php
  54. 114 0
      app/Http/Controllers/Ask/CommentController.php
  55. 473 0
      app/Http/Controllers/Ask/QuestionController.php
  56. 32 0
      app/Http/Controllers/Ask/TagController.php
  57. 51 0
      app/Http/Controllers/AttachController.php
  58. 275 0
      app/Http/Controllers/Blog/ArticleController.php
  59. 190 0
      app/Http/Controllers/Controller.php
  60. 83 0
      app/Http/Controllers/ImageController.php
  61. 257 0
      app/Http/Controllers/IndexController.php
  62. 211 0
      app/Http/Controllers/Installer/InstallerController.php
  63. 24 0
      app/Http/Controllers/Shop/ExchangeController.php
  64. 86 0
      app/Http/Controllers/Shop/GoodsController.php
  65. 68 0
      app/Http/Controllers/SiteMapController.php
  66. 70 0
      app/Http/Kernel.php
  67. 40 0
      app/Http/Middleware/AdminAuthenticate.php
  68. 37 0
      app/Http/Middleware/AdminOperationLog.php
  69. 38 0
      app/Http/Middleware/BanIpCheck.php
  70. 42 0
      app/Http/Middleware/BanUserCheck.php
  71. 17 0
      app/Http/Middleware/EncryptCookies.php
  72. 33 0
      app/Http/Middleware/InstallerCheck.php
  73. 23 0
      app/Http/Middleware/RedictToInstall.php
  74. 26 0
      app/Http/Middleware/RedirectIfAuthenticated.php
  75. 18 0
      app/Http/Middleware/TrimStrings.php
  76. 23 0
      app/Http/Middleware/TrustProxies.php
  77. 19 0
      app/Http/Middleware/VerifyCsrfToken.php
  78. 41 0
      app/Http/Requests/Request.php
  79. 47 0
      app/Jobs/SendEmail.php
  80. 35 0
      app/Listeners/LoginListener.php
  81. 27 0
      app/Listeners/LogoutListener.php
  82. 41 0
      app/Mail/GlobalMail.php
  83. 52 0
      app/Models/Answer.php
  84. 70 0
      app/Models/Area.php
  85. 128 0
      app/Models/Article.php
  86. 19 0
      app/Models/Attention.php
  87. 95 0
      app/Models/Authentication.php
  88. 14 0
      app/Models/BanIp.php
  89. 234 0
      app/Models/Category.php
  90. 12 0
      app/Models/Collection.php
  91. 45 0
      app/Models/Comment.php
  92. 14 0
      app/Models/Credit.php
  93. 63 0
      app/Models/Doing.php
  94. 15 0
      app/Models/Draft.php
  95. 37 0
      app/Models/EmailToken.php
  96. 26 0
      app/Models/Exchange.php
  97. 12 0
      app/Models/FriendshipLink.php
  98. 18 0
      app/Models/Goods.php
  99. 13 0
      app/Models/Message.php
  100. 0 0
      app/Models/Notice.php

+ 19 - 0
.editorconfig

@@ -0,0 +1,19 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.php]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 4

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+.idea/
+vendor/
+bootstrap/cache/
+/storage/*.key
+/storage/installed
+.env

+ 1 - 0
.htaccess

@@ -0,0 +1 @@
+ 

+ 77 - 0
app/Console/Commands/AdoptAnswer.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 2017/4/24
+ * Time: 下午7:01
+ */
+
+namespace App\Console\Commands;
+
+
+use App\Models\Answer;
+use App\Models\Question;
+use App\Services\QuestionService;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+
+class AdoptAnswer extends Command
+{
+
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'adoptAnswer';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'auto adopt answer';
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $this->comment('start to adopt answer');
+        $answerAdoptPeriod =  Setting()->get('answer_adopt_period',0);
+        if( $answerAdoptPeriod > 0 ){
+            $startTime = Carbon::createFromTimestamp(Carbon::today()->timestamp - $answerAdoptPeriod * 24*3600);
+            $questions = Question::where("price",">",0)->where("created_at","<",$startTime)->where("status","=",1)->where("answers",'>',0)->orderBy('created_at','asc')->take(30)->get();
+            $this->comment('total question:'.$questions->count());
+            foreach( $questions as $question ){
+                $this->comment('doing question id: '.$question->id);
+                $answers = $question->answers()->whereNull('adopted_at')->where("status",">",0)->orderBy("supports",'desc')->orderBy("created_at","asc")->take(30)->get();
+                $this->comment('question['.$question->id.']total answers:'.$answers->count());
+                if( $answers->count() == 0 ){
+                    continue;
+                }
+
+                $bestAnswerId = null;
+                /*优先采纳专家答案*/
+                foreach( $answers as $answer ){
+                    if($answer->userData && $answer->userData->authentication_status == 1){
+                        $bestAnswerId = $answer->id;
+                    }
+                }
+
+                if(!$bestAnswerId){
+                    $bestAnswerId = $answers[0]->id;
+                }
+
+                if($bestAnswerId){
+                    $this->comment('best answer id: '.$bestAnswerId);
+                    QuestionService::adoptAnswer($bestAnswerId);
+                }
+
+            }
+        }
+        $this->comment('finished!');
+    }
+}

+ 42 - 0
app/Console/Kernel.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Console;
+
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+
+class Kernel extends ConsoleKernel
+{
+    /**
+     * The Artisan commands provided by your application.
+     *
+     * @var array
+     */
+    protected $commands = [
+    ];
+
+    /**
+     * Define the application's command schedule.
+     *
+     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        /*每天上午10点和下午3点自动采纳回答*/
+        $schedule->command('adoptAnswer')->twiceDaily(10, 15);
+
+    }
+
+    /**
+     * Register the commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        $this->load(__DIR__.'/Commands');
+
+        require base_path('routes/console.php');
+    }
+}

+ 60 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of the exception types that are not reported.
+     *
+     * @var array
+     */
+    protected $dontReport = [
+        //
+    ];
+
+    /**
+     * A list of the inputs that are never flashed for validation exceptions.
+     *
+     * @var array
+     */
+    protected $dontFlash = [
+        'password',
+        'password_confirmation',
+    ];
+
+    /**
+     * Report or log an exception.
+     *
+     * @param  \Exception  $exception
+     * @return void
+     */
+    public function report(Exception $exception)
+    {
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Exception  $exception
+     * @return \Illuminate\Http\Response
+     */
+    public function render($request, Exception $exception)
+    {
+        return parent::render($request, $exception);
+    }
+
+    protected function unauthenticated($request, AuthenticationException $exception)
+    {
+        return $request->expectsJson()
+            ? response()->json(['code'=>401,'message' => '您还未登录,需要登录后才能进行该操作'], 200)
+            : redirect()->guest(route('auth.user.login'));
+    }
+
+}

+ 85 - 0
app/Http/Controllers/Account/AttentionController.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Attention;
+use App\Models\Question;
+use App\Models\Tag;
+use App\Models\User;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class AttentionController extends Controller
+{
+
+    /**
+     * 添加模型的关注包含问题、用户等
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store($source_type,$source_id,Request $request)
+    {
+
+        if($source_type === 'question'){
+            $source  = Question::find($source_id);
+            $subject = $source->title;
+        }else if($source_type === 'user'){
+            $source  = User::find($source_id);
+            $subject = $source->name;
+        }else if($source_type==='tag'){
+            $source  = Tag::find($source_id);
+            $subject = $source->name;
+        }
+
+        if(!$source){
+            abort(404);
+        }
+
+        /*再次关注相当于是取消关注*/
+        $attention = Attention::where("user_id",'=',$request->user()->id)->where('source_type','=',get_class($source))->where('source_id','=',$source_id)->first();
+        if($attention){
+            $attention->delete();
+            if($source_type==='user'){
+                $source->userData->decrement('followers');
+            }else{
+                $source->decrement('followers');
+            }
+            return response('unfollowed');
+        }
+
+        $data = [
+            'user_id'     => $request->user()->id,
+            'source_id'   => $source_id,
+            'source_type' => get_class($source),
+        ];
+
+        $attention = Attention::create($data);
+
+        if($attention){
+            switch($source_type){
+                case 'question' :
+                    $this->notify($request->user()->id,$source->user_id,'follow_question',$subject,$source->id);
+                    $this->doing($request->user()->id,'follow_question',get_class($source),$source_id,$subject);
+                    $source->increment('followers');
+                    break;
+                case 'user':
+                    $source->userData->increment('followers');
+                    $this->notify($request->user()->id,$source->id,'follow_user');
+                    break;
+                case 'tag':
+                    $source->increment('followers');
+                    break;
+            }
+        }
+
+        return response('followed');
+
+
+    }
+
+
+
+
+}

+ 164 - 0
app/Http/Controllers/Account/AuthenticationController.php

@@ -0,0 +1,164 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Area;
+use App\Models\Attention;
+use App\Models\Authentication;
+use App\Models\Tag;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class AuthenticationController extends Controller
+{
+
+    protected  $validateRules = [
+        'real_name' => 'required|max:64',
+        'title' => 'required|max:128',
+        'description' => 'sometimes|max:9999',
+        'id_card' => 'required|max:64|unique:authentications',
+        'id_card_image' => 'required|image|max:2048',
+        'skill' => 'required|max:128',
+        'skill_image' => 'required|image|max:2048',
+        'captcha' => 'required|captcha',
+    ];
+    protected $validateMessages = [
+        'real_name.required' => '真实姓名不能为空',
+        'real_name.max' => '真实姓名长度不能超过:max个字符',
+        'title.required' => '身份职业不能为空',
+        'title.max' => '身份职业不能超过:max个字符',
+        'description.max' => '个人介绍不能超过:max个字符',
+        'id_card.required' => '身份号码不能为空',
+        'id_card.max' => '身份证号码不能超过:max个字符',
+        'id_card_image.required' => '身份证正面图片不能为空',
+        'skill.required' => '认证领域不能为空',
+        'skill_image.required' => '认证领域图片不能为空',
+        'captcha.required' => '验证码不能为空',
+        'captcha.captcha' => '验证码错误',
+    ];
+
+    /**
+     * 显示认证信息
+     */
+    public function getIndex(Request $request)
+    {
+        $provinces = Area::provinces();
+        $cities = Area::cities($request->user()->province);
+        $areaData = [
+            'provinces' => $provinces,
+            'cities' => $cities,
+        ];
+        return view('theme::authentication.index')->with(compact('areaData'));
+    }
+
+    /**
+     * 认证信息提交
+     */
+    public function postStore(Request $request)
+    {
+
+        $this->validate($request,$this->validateRules,$this->validateMessages);
+
+        $data = $request->all();
+
+        $data['user_id'] = $request->user()->id;
+
+        if($request->hasFile('id_card_image')){
+            $savePath = storage_path('app/authentications');
+            $file = $request->file('id_card_image');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $data['id_card_image'] = 'authentications-'.$fileName;
+            }
+        }
+
+        if($request->hasFile('skill_image')){
+            $savePath = storage_path('app/authentications');
+            $file = $request->file('skill_image');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $data['skill_image'] = 'authentications-'.$fileName;
+            }
+        }
+
+        Authentication::create($data);
+
+        $this->attendToTags(explode(",",$data['skill']),$request->user()->id);
+
+        return $this->success(route('auth.authentication.index'),'您的申请已经提交成功!我们会在3个工作日内完成审核,请耐心等待!');
+
+    }
+
+
+    /*修改认证信息*/
+    public function anyEdit(Request $request)
+    {
+
+        if($request->isMethod('post')) {
+            $this->validateRules['id_card'] = 'required|max:64|unique:authentications,id_card,'.$request->user()->id.',user_id';
+            $this->validate($request, $this->validateRules,$this->validateMessages);
+            $data = $request->all();
+            $data['status'] = 0;
+            if ($request->hasFile('id_card_image')) {
+                $savePath = storage_path('app/authentications');
+                $file = $request->file('id_card_image');
+                $fileName = uniqid(str_random(8)) . '.' . $file->getClientOriginalExtension();
+                $target = $file->move($savePath, $fileName);
+                if ($target) {
+                    $data['id_card_image'] = 'authentications-' . $fileName;
+                }
+            }
+
+            if ($request->hasFile('skill_image')) {
+                $savePath = storage_path('app/authentications');
+                $file = $request->file('skill_image');
+                $fileName = uniqid(str_random(8)) . '.' . $file->getClientOriginalExtension();
+                $target = $file->move($savePath, $fileName);
+                if ($target) {
+                    $data['skill_image'] = 'authentications-' . $fileName;
+                }
+            }
+
+            $request->user()->authentication->update($data);
+            Tag::multiSave($data['skill'],$request->user());
+            $this->attendToTags(explode(",",$data['skill']),$request->user()->id);
+
+            return $this->success(route('auth.authentication.index'),'您的申请已经提交成功!我们会在3个工作日内完成审核,请耐心等待!');
+        }
+
+        $authentication = $request->user()->authentication;
+        $provinces = Area::provinces();
+        $cities = Area::cities($authentication->province);
+        $areaData = [
+            'provinces' => $provinces,
+            'cities' => $cities,
+        ];
+        return view('theme::authentication.edit')->with(compact('authentication','areaData'));
+
+    }
+
+
+    /*关注标签*/
+    private function attendToTags($tags,$userId){
+        foreach( $tags as $tag ){
+            $newTag = Tag::firstOrCreate(['name'=>$tag]);
+            $attention = Attention::where("user_id",'=',$userId)->where('source_type','=',get_class($newTag))->where('source_id','=',$newTag->id)->first();
+            if(!$attention){
+                Attention::create([
+                    'user_id'     => $userId,
+                    'source_id'   => $newTag->id,
+                    'source_type' => get_class($newTag),
+                ]);
+                Tag::find($newTag->id)->increment('followers');
+            }
+
+        }
+    }
+
+
+
+}

+ 61 - 0
app/Http/Controllers/Account/CollectionController.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Article;
+use App\Models\Collection;
+use App\Models\Question;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class CollectionController extends Controller
+{
+
+    /**
+     * 添加收藏
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store($source_type,$source_id,Request $request)
+    {
+
+        if($source_type == 'question'){
+            $source  = Question::find($source_id);
+            $subject = $source->title;
+        }else if($source_type == 'article'){
+            $source  = Article::find($source_id);
+            $subject = $source->title;
+        }
+        if(!$source){
+            abort(404);
+        }
+
+        /*不能多次收藏*/
+        $userCollect = $request->user()->isCollected(get_class($source),$source_id);
+        if($userCollect){
+            $userCollect->delete();
+            $source->decrement('collections');
+            return response('uncollect');
+        }
+
+        $data = [
+            'user_id'     => $request->user()->id,
+            'source_id'   => $source_id,
+            'source_type' => get_class($source),
+            'subject'  => $subject,
+        ];
+
+        $collect = Collection::create($data);
+
+        if($collect){
+            $source->increment('collections');
+        }
+
+        return response('collected');
+
+
+    }
+
+
+}

+ 35 - 0
app/Http/Controllers/Account/DoingsController.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Doing;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\DB;
+
+class DoingsController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request,$filter='newest')
+    {
+        $query = null;
+        if($filter=='concerned'){
+            $query = Doing::concerned($request->user());
+        }else{
+            $query = Doing::newest();
+        }
+        $doings = $query->paginate(20);
+        $doings->map(function($doing){
+            $doing->action_text = Config::get('tipask.user_actions.'.$doing->action);
+        });
+        return view('theme::doing.index')->with(compact('filter','doings'));
+    }
+
+}

+ 101 - 0
app/Http/Controllers/Account/DraftController.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Draft;
+use App\Models\Question;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class DraftController extends Controller
+{
+    public function index(Request $request)
+    {
+        $user_id = $request->user()->id;
+        $drafts = Draft::where('user_id',$user_id)->get()->toArray();
+        return view('theme::draft.index')->with(compact('drafts'));
+    }
+
+    /**
+     * 创建草稿
+     * @param Request $request
+     * @param $type
+     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
+     */
+    public function create(Request $request, $type)
+    {
+        $loginUser = $request->user();
+        if ($type == 'question') {
+            $data = [
+                'id'             => $request->input('_token'),
+                'user_id'        => $loginUser->id,
+                'editor_content' => clean($request->input('description','')),
+                'subject'        => trim($request->input('title','')),
+                'source_type'    => 'question',
+                'source_id'      => $request->input('id', 0),
+                'form_data'      => json_encode([
+                    'category_id' => $request->input('category_id', 0),
+                    'price'       => $price = abs($request->input('price',0)),
+                    'hide'        => intval($request->input('hide',0)),
+                    'tags'        => trim($request->input('tags','')),
+                    'to_user_id' => $request->input('to_user_id',0)
+                ]),
+            ];
+        } elseif ($type == 'answer') {
+            $question = Question::find($request->input('question_id'));
+            $data = [
+                'id'             => $request->input('_token'),
+                'user_id'        => $loginUser->id,
+                'editor_content' => clean($request->input('content')),
+                'subject'        => $question->title,
+                'source_type'    => 'answer',
+                'source_id'      => $request->input('question_id', 0),
+                'form_data'      => json_encode([]),
+            ];
+        } elseif ($type == 'article') {
+            $data = [
+                'id'             => $request->input('_token'),
+                'user_id'        => $loginUser->id,
+                'editor_content' => clean($request->input('content')),
+                'subject'        => trim($request->input('title')),
+                'source_type'    => 'article',
+                'source_id'      => $request->input('id', 0),
+                'form_data'      => json_encode([
+                    'category_id' => $request->input('category_id', 0),
+                ]),
+            ];
+        }
+        $draft = Draft::find($data['id']);
+        if ( !empty($draft)) {
+            Draft::where('id', $data['id'])->update($data);
+        } else {
+            Draft::create($data);
+        }
+        return response()->json($data);
+    }
+
+    /**
+     * 清空草稿箱
+     * @param Request $request
+     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
+     */
+    public function cleanAll(Request $request)
+    {
+        $user_id = $request->user()->id;
+        $result = Draft::where('user_id', $user_id)->delete();
+        return $this->success(route('auth.draft.index'),'操作成功!');
+    }
+
+    /**
+     * 删除草稿
+     * @param Request $request
+     * @param $id
+     * @return
+     */
+    public function destroy(Request $request, $id)
+    {
+        $user_id = $request->user()->id;
+        $result = Draft::where('id', $id)->where('user_id', $user_id)->delete();
+        return $this->success(route('auth.draft.index'),'操作成功!');
+    }
+}

+ 90 - 0
app/Http/Controllers/Account/EmailController.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\EmailToken;
+use App\Models\User;
+use Illuminate\Contracts\Auth\Guard;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class EmailController extends Controller
+{
+
+    protected $auth;
+
+
+
+    public function __construct(Guard $auth){
+        $this->auth = $auth;
+    }
+
+
+    /*验证邮箱token*/
+    public function verifyToken($action,$token)
+    {
+        $emailToken = EmailToken::where('action','=',$action)->where('token','=',$token)->first();
+        if(!$emailToken){
+            return $this->error(route('website.ask'),'token信息不存在');
+        }
+
+        if($emailToken->created_at->diffInMinutes() > 60){
+
+            return $this->error(route('website.ask'),'token信息已失效,请重新发送');
+        }
+
+        $user = User::where('email','=',$emailToken->email)->first();
+        if(!$user){
+            return $this->error(route('website.ask'),'用户不存在或已被删除');
+        }
+
+
+        if(in_array($action,['register','verify'])){
+
+            if($user->status==0){
+                $user->status=1;
+                $user->save();
+                $user->userData->email_status = 1;
+                $user->userData->save();
+            }
+
+            $this->auth->login($user);
+            EmailToken::clear($user->email,$action);
+            return $this->success(route('auth.profile.base'),'邮箱验证成功');
+
+        }
+
+    }
+
+
+
+
+    public function sendToken(Request $request)
+    {
+        $lastEmailToken = EmailToken::where('email','=',$request->user()->email)->orderBy('created_at','DESC')->first();
+        if($lastEmailToken && $lastEmailToken->created_at->diffInMinutes() < 1)
+        {
+            return response('tooFast');
+        }
+
+        $emailToken = EmailToken::create([
+            'email' => $request->user()->email,
+            'action' => 'verify',
+            'token' => EmailToken::createToken(),
+        ]);
+
+        if($emailToken){
+            $subject = '请激活您在 '.Setting()->get('website_name').' 的邮箱!';
+            $content = "「".$request->user()->name."」您好,请激活您在 ".Setting()->get('website_name')." 的邮箱!<br /> 请在1小时内点击该链接激活注册账号 → ".route('auth.email.verifyToken',['action'=>$emailToken->action,'token'=>$emailToken->token])."<br />如非本人操作,请忽略此邮件!";
+            $this->sendEmail($emailToken->email,$subject,$content);
+            return response('success');
+        }
+
+        return response('failed');
+
+    }
+
+
+}

+ 209 - 0
app/Http/Controllers/Account/MessageController.php

@@ -0,0 +1,209 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Message;
+use App\Models\User;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\DB;
+
+class MessageController extends Controller
+{
+
+    /*问题创建校验*/
+    protected $validateRules = [
+        'content' => 'required|max:65535',
+        'to_user_id' => 'required|integer',
+    ];
+
+
+    /**
+     * 我的私信首页
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $loginUser = Auth()->user();
+
+        /*子查询进行分组*/
+        $subQuery = Message::where("to_user_id","=",$loginUser->id)->where("to_deleted","=",0)->orderBy("created_at","desc");
+
+        /*联查子查询再进行排序*/
+        $messages = DB::table(DB::raw("({$subQuery->toSql()}) as t "))
+            ->mergeBindings($subQuery->getQuery())
+            ->select("*")
+            ->groupBy("from_user_id")
+            ->orderBy("created_at","desc")
+            ->paginate(10);
+
+        $messages->map(function($message) {
+            $message->fromUser = User::find($message->from_user_id);
+        });
+
+        return view('theme::message.index')->with('messages',$messages);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        $loginUser = $request->user();
+        $toUser = User::find($request->input('to_user_id'));
+        if(!$toUser){
+            abort(404);
+        }
+
+        $this->validate($request,$this->validateRules);
+        $data = [
+            'from_user_id'      => $loginUser->id,
+            'to_user_id'      => $toUser->id,
+            'content'  => $request->input('content')
+        ];
+
+        $message = Message::create($data);
+
+        if($message){
+            return $this->success(route('auth.message.show',['user_id'=>$toUser->id]),'消息发送成功');
+        }
+
+        return  $this->error("消息发送失败,请稍后再试",route('website.index'));
+
+
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($user_id)
+    {
+        $toUser = User::find($user_id);
+        if(!$toUser){
+            abort(404);
+        }
+
+        /*设置该对话全部未读为已读*/
+        Message::where('to_user_id','=',Auth()->user()->id)->where('is_read','=',0)->update(['is_read'=>1]);
+
+        $messages = Message::where(function($query) use ($toUser) {
+                                $query->where('to_user_id','=',Auth()->user()->id)
+                                      ->where('from_user_id','=',$toUser->id)
+                                      ->where('to_deleted','=',0);
+                    })->orWhere(function($query) use ($toUser) {
+                                $query->where('to_user_id','=',$toUser->id)
+                                      ->where('from_user_id','=',Auth()->user()->id)
+                                      ->where('from_deleted','=',0);
+                   })->orderBy('created_at','desc')->paginate(10);
+
+
+        return view('theme::message.show')->with('toUser',$toUser)->with('messages',$messages);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request,$id)
+    {
+        $loginUser = $request->user();
+        $message = Message::find($id);
+
+        if(!$message){
+            abort(404);
+        }
+
+
+        /*收件人删除*/
+        if( $message->to_user_id === $loginUser->id )
+        {
+            $message->to_deleted = 1;
+            $message->save();
+        }else if( $message->from_user_id === $loginUser->id ){
+            $message->from_deleted = 1;
+            $message->save();
+        }else{
+            return response('error');
+        }
+
+        /*删除双方都删除过的信息*/
+        if( $message->to_deleted == 1 && $message->from_deleted == 1 ){
+            $message->delete();
+        }
+
+        return response('ok');
+
+    }
+
+    public function destroySession(Request $request,$from_user_id)
+    {
+
+        $loginUser = $request->user();
+
+        /*删除给我的消息*/
+
+        Message::where('to_user_id','=',$loginUser->id)
+               ->where('from_user_id','=',$from_user_id)
+               ->update(['to_deleted'=>1]);
+
+        /*删除我发的消息*/
+        Message::where('to_user_id','=',$from_user_id)
+            ->where('from_user_id','=',$loginUser->id)
+            ->update(['from_deleted'=>1]);
+
+
+        /*删除双方都删除的所有消息*/
+
+        Message::where('to_deleted','=',1)->where('from_deleted','=',1)->delete();
+
+        return response('ok');
+
+
+    }
+
+
+}

+ 43 - 0
app/Http/Controllers/Account/NotificationController.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Notification;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Config;
+
+class NotificationController extends Controller
+{
+    /**
+     * 显示用户通知
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function getIndex(Request $request)
+    {
+        $notifications = Notification::where('to_user_id',$request->user()->id)->orderBy('created_at','DESC')->paginate(12);
+        $notifications->map(function($notification){
+          $notification->type_text = Config::get('tipask.notification_types.'.$notification->type);
+        });
+        $this->readNotifications(0,'user');
+        return view('theme::notification.index')->with('notifications',$notifications);
+    }
+
+
+    public function getReadAll()
+    {
+        Notification::where('to_user_id','=',Auth()->user()->id)->where('is_read','=',0)->update(['is_read'=>1]);
+        return $this->success(route('auth.notification.index'),'设置成功');
+    }
+
+
+
+
+
+
+
+}

+ 174 - 0
app/Http/Controllers/Account/OauthController.php

@@ -0,0 +1,174 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 16/6/27
+ * Time: 下午3:04
+ */
+
+namespace App\Http\Controllers\Account;
+
+
+use App\Http\Controllers\Controller;
+use App\Models\EmailToken;
+use App\Models\User;
+use App\Models\UserOauth;
+use App\Repositories\OauthRepository;
+use App\Repositories\UserRepository;
+use App\Services\SmsService;
+use Illuminate\Contracts\Auth\Guard;
+use Illuminate\Contracts\Auth\Registrar;
+use Illuminate\Http\Request;
+use Laravel\Socialite\Facades\Socialite;
+
+class OauthController extends Controller
+{
+    public function login($type){
+        return Socialite::with($type)->redirect();
+    }
+
+    public function callback($type,Request $request,Guard $auth){
+
+        $oauthUser = Socialite::driver($type)->user();
+
+        if(!$oauthUser){
+            abort(500);
+        }
+
+        $refresh_token = '';
+        if(isset($oauthUser->accessTokenResponseBody['refresh_token'])){
+            $refresh_token = $oauthUser->accessTokenResponseBody['refresh_token'];
+        }
+
+        if( Auth()->check() ){ //用户登录时处理绑定请求
+            $request->user()->userOauth()->where("auth_type",'=',$type)->delete();
+            UserOauth::where('id','=',$oauthUser->id)->delete();
+            $userOauth = UserOauth::create([
+                'id'=>$oauthUser->id,
+                'auth_type'=>$type,
+                'user_id'=> $request->user()->id,
+                'nickname'=>$oauthUser->nickname,
+                'avatar'=>$oauthUser->avatar,
+                'access_token'=>$oauthUser->accessTokenResponseBody['access_token'],
+                'refresh_token'=>$refresh_token,
+                'expires_in'=>$oauthUser->accessTokenResponseBody['expires_in'],
+            ]);
+
+            if($userOauth){
+                return $this->success( route('auth.profile.oauth') , $type .'绑定成功!');
+            }
+
+            return $this->error(route('auth.profile.oauth'),'绑定失败请稍后重试!');
+
+        }
+
+        //游客登录处理注册流程
+         $userOauth = UserOauth::find($oauthUser->id);
+
+        if( $userOauth && $userOauth->user_id > 0 ){
+            $auth->loginUsingId($userOauth->user_id);
+            if($this->credit($request->user()->id,'login',Setting()->get('coins_login'),Setting()->get('credits_login'))){
+                $message = '登陆成功! '.get_credit_message(Setting()->get('credits_login'),Setting()->get('coins_login'));
+                return $this->success(route('website.index'),$message);
+            }
+            /*认证成功后跳转到首页*/
+            return redirect()->to(route('website.index'));
+        }
+
+        UserOauth::where('id','=',$oauthUser->id)->delete();
+
+        $oauthData = UserOauth::create([
+            'id'=>$oauthUser->id,
+            'auth_type'=>$type,
+            'user_id'=> 0,
+            'nickname'=>$oauthUser->nickname,
+            'avatar'=>$oauthUser->avatar,
+            'access_token'=>$oauthUser->accessTokenResponseBody['access_token'],
+            'refresh_token'=>$refresh_token,
+            'expires_in'=>$oauthUser->accessTokenResponseBody['expires_in'],
+        ]);
+
+
+        if($oauthData){
+            return redirect(route('auth.oauth.profile',['auth_id'=>$oauthUser->id]));
+        }
+
+        return $this->error(route('auth.profile.oauth'),$type.'登录错误,请稍后再试!');
+
+    }
+
+    public function unbind( $type , Request $request){
+        $request->user()->userOauth()->where('auth_type','=',$type)->delete();
+        return $this->success( route('auth.profile.oauth') , $type .'已解除绑定!');
+    }
+
+
+    public function profile($auth_id)
+    {
+        $userOauth = UserOauth::find($auth_id);
+        if(!$userOauth){
+            abort(404);
+        }
+        return view('theme::account.oauth')->with(compact('userOauth'));
+    }
+
+
+    public function register(Request $request,UserRepository $userRepository,Guard $auth,OauthRepository $oauthRepository)
+    {
+        $validateRules['name'] = 'required|min:2|max:100';
+        $request->flash();
+
+        if(Setting()->get('register_type') == 'email'){
+            $validateRules['email'] = 'required|email|max:255|unique:users';
+        }else{
+            $validateRules['mobile'] = 'required|regex:/^1[3456789]\d{9}$/';
+            $validateRules['code'] = 'required|min:4|:max:8';
+        }
+        /*表单数据校验*/
+        $this->validate($request,$validateRules);
+
+        $formData = $request->all();
+        $formData['password'] = 'oauth';
+        $formData['status'] = 0;
+        $formData['visit_ip'] = $request->getClientIp();
+        /*手机模式认证*/
+        $user = [];
+        if( Setting()->get('register_type') == 'mobile' ){
+            if( !SmsService::verifySmsCode($formData['mobile'],$request->input('code')) )  {
+                return view("theme::account.register")->withErrors(['code'=>'验证码错误']);
+            }
+            $formData['status'] = 1;
+            $user = User::where("mobile","=",$formData['mobile'])->where("status","=",1)->first();
+        }
+        if(!$user){
+            $user = $userRepository->register($formData);
+            $user->attachRole(2); //默认注册为普通用户角色
+        }
+        $oauthRepository->bind($formData['auth_id'],$user->id);
+        $auth->login($user);
+        $message = '登录成功!';
+
+        if($this->credit($request->user()->id,'register',Setting()->get('coins_register'),Setting()->get('coins_register'))){
+            $message .= get_credit_message(Setting()->get('credits_register'),Setting()->get('coins_register'));
+        }
+
+        if(Setting()->get('register_type')=='email') {
+            /*发送邮箱验证邮件*/
+            $emailToken = EmailToken::create([
+                'email' => $user->email,
+                'token' => EmailToken::createToken(),
+                'action' => 'register'
+            ]);
+
+            if ($emailToken) {
+                $subject = '欢迎注册' . Setting()->get('website_name') . ',请激活您注册的邮箱!';
+                $content = "「" . $request->user()->name . "」您好,请激活您在 " . Setting()->get('website_name') . " 的注册邮箱!<br /> 请在1小时内点击该链接激活注册账号 → " . route('auth.email.verifyToken', ['action' => $emailToken->action, 'token' => $emailToken->token]) . "<br />如非本人操作,请忽略此邮件!";
+                $this->sendEmail($emailToken->email, $subject, $content);
+            }
+        }
+
+        return $this->success(route('website.index'),$message);
+    }
+
+
+}

+ 32 - 0
app/Http/Controllers/Account/PasswordController.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\ResetsPasswords;
+
+class PasswordController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller is responsible for handling password reset requests
+    | and uses a simple trait to include this behavior. You're free to
+    | explore this trait and override any methods you wish to tweak.
+    |
+    */
+
+    use ResetsPasswords;
+
+    /**
+     * Create a new password controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+}

+ 257 - 0
app/Http/Controllers/Account/ProfileController.php

@@ -0,0 +1,257 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Area;
+use App\Models\EmailToken;
+use App\Models\User;
+use App\Services\SmsService;
+use Illuminate\Http\Request;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Storage;
+use Intervention\Image\Facades\Image;
+
+class ProfileController extends Controller
+{
+
+    /*个人基本资料*/
+    public function anyBase(Request $request)
+    {
+        $user = $request->user();
+        if($request->isMethod('POST')){
+            $request->flash();
+            $validateRules = [
+                'name' => 'required|max:128',
+                'title' => 'sometimes|max:128',
+                'description' => 'sometimes|max:9999',
+            ];
+            $this->validate($request,$validateRules);
+            $user->name = $request->input('name');
+            $user->gender = $request->input('gender');
+            if($request->input('birthday')){
+                $user->birthday = $request->input('birthday');
+            }
+            $user->title = $request->input('title');
+            $user->description = $request->input('description');
+            $user->province = $request->input('province');
+            $user->city = $request->input('city');
+            if($request->hasFile('qrcode')){
+                $validateRules = [
+                    'qrcode' => 'required|image|max:'.config('tipask.upload.image_size'),
+                ];
+                $this->validate($request,$validateRules);
+                $file = $request->file('qrcode');
+                $extension = $file->getClientOriginalExtension();
+                $filePath = 'qrcodes/'.gmdate("Y")."/".gmdate("m")."/".uniqid(str_random(8)).'.'.$extension;
+                Storage::disk('local')->put($filePath,File::get($file));
+                Image::make(storage_path('app/'.$filePath))->resize(320,435)->save();
+                $user->qrcode = str_replace("/","-",$filePath);
+            }
+
+            $user->save();
+            return $this->success(route('auth.profile.base'),'个人资料修改成功');
+
+        }
+        $provinces = Area::provinces();
+        $cities = Area::cities($user->province);
+        $data = [
+            'provinces' => $provinces,
+            'cities' => $cities,
+        ];
+
+        return view('theme::profile.base')->with('data',$data);
+    }
+
+    /**
+     * 修改用户头像
+     * @param Request $request
+     */
+    public function postAvatar(Request $request)
+    {
+        $validateRules = [
+            'user_avatar' => 'required|image',
+        ];
+
+        if($request->hasFile('user_avatar')){
+            $this->validate($request,$validateRules);
+            $user_id = $request->user()->id;
+            $file = $request->file('user_avatar');
+            $avatarDir = User::getAvatarDir($user_id);
+            $extension = strtolower($file->getClientOriginalExtension());
+            $extArray = array('png', 'gif', 'jpeg', 'jpg');
+
+            if(in_array($extension, $extArray)){
+	            if($extension != 'jpg'){
+                    Image::make(File::get($file))->save(storage_path('app/'.User::getAvatarPath($user_id,'origin')));
+                }else{
+                    Storage::disk('local')->put($avatarDir.'/'.User::getAvatarFileName($user_id,'origin').'.'.$extension,File::get($file));
+                }
+            }else{
+                return response('error');
+            }
+
+            return response()->json(array(
+                'status' => 1,
+                'msg' => '头像上传成功'
+            ));
+        }
+
+        if($request->isMethod('POST')){
+            $x = intval($request->input('x'));
+            $y = intval($request->input('y'));
+            $width = intval($request->input('width'));
+            $height = intval($request->input('height'));
+
+            $user_id = $request->user()->id;
+
+            File::delete(storage_path('app/'.User::getAvatarPath($user_id,'big')));
+            File::delete(storage_path('app/'.User::getAvatarPath($user_id,'middle')));
+            File::delete(storage_path('app/'.User::getAvatarPath($user_id,'small')));
+
+            Image::make(storage_path('app/'.User::getAvatarPath($user_id,'origin')))->crop($width,$height,$x,$y)->resize(128,128)->save(storage_path('app/'.User::getAvatarPath($user_id,'big')));
+            Image::make(storage_path('app/'.User::getAvatarPath($user_id,'origin')))->crop($width,$height,$x,$y)->resize(64,64)->save(storage_path('app/'.User::getAvatarPath($user_id,'middle')));
+            Image::make(storage_path('app/'.User::getAvatarPath($user_id,'origin')))->crop($width,$height,$x,$y)->resize(24,24)->save(storage_path('app/'.User::getAvatarPath($user_id,'small')));
+
+            return response()->json(array(
+                'status' => 1,
+                'msg' => '头像截剪成功'
+            ));
+        }
+
+    }
+
+    /**
+     * 修改用户密码
+     * @param Request $request
+     */
+    public function anyPassword(Request $request)
+    {
+        if($request->isMethod('POST')){
+            $validateRules = [
+                'old_password' => 'required|min:6|max:16',
+                'password' => 'required|min:6|max:16',
+                'password_confirmation'=>'same:password',
+                'captcha' => 'required|captcha',
+
+            ];
+            $this->validate($request,$validateRules);
+
+            $user = $request->user();
+
+            if(Hash::check($request->input('old_password'),$user->password)){
+                $user->password = Hash::make($request->input('password'));
+                $user->save();
+                Auth()->logout();
+                return $this->success(route('auth.user.login'),'密码修改成功,请重新登录');
+            }
+
+            return redirect(route('auth.profile.password'))
+                ->withErrors([
+                    'old_password' => '原密码错误!',
+                ]);
+        }
+        return view('theme::profile.password');
+    }
+
+    /*修改邮箱*/
+    public function anyEmail(Request $request)
+    {
+        if($request->isMethod('POST'))
+        {
+            $validateRules = [
+                'email' => 'required|email|unique:users,email,'.$request->user()->id,
+                'captcha' => 'required|captcha',
+            ];
+            $this->validate($request,$validateRules);
+
+            if($request->input('email') !== $request->user()->email){
+                $request->user()->email = $request->input('email');
+                $request->user()->status = 0;
+                $request->user()->save();
+                $emailToken = EmailToken::create([
+                    'email' =>$request->input('email'),
+                    'action' => 'verify',
+                    'token' => EmailToken::createToken(),
+                ]);
+
+                if($emailToken){
+                    $subject = '欢迎注册'.Setting()->get('website_name').',请激活您注册的邮箱!';
+                    $content = "「".$request->user()->name."」您好,请激活您在 ".Setting()->get('website_name')." 的注册邮箱!<br /> 请在1小时内点击该链接激活注册账号 → ".route('auth.email.verifyToken',['action'=>$emailToken->action,'token'=>$emailToken->token])."<br />如非本人操作,请忽略此邮件!";
+                    $this->sendEmail($emailToken->email,$subject,$content);
+                }
+
+                return $this->success(route('auth.profile.email'),'邮箱修改成功!一封验证邮件已经发到您的邮箱'.$request->user()->email.',请登陆邮箱进行验证!');
+            }
+
+        }
+        return view('theme::profile.email');
+    }
+
+
+    public function anyMobile(Request $request)
+    {
+        if($request->isMethod('post')){
+            $validateRules = [
+                'mobile' => 'required|max:11,'.$request->user()->id,
+                'code' => 'required|min:4|max:10',
+            ];
+
+            $this->validate($request,$validateRules);
+
+            $mobile = $request->input('mobile');
+            $code = $request->input('code');
+
+            if(!SmsService::verifySmsCode($mobile,$code)){
+                return $this->error(route('auth.profile.mobile'),"短信验证码错误,请重新验证");
+            }
+
+            $request->user()->mobile = $mobile;
+            $request->user()->status = 1;
+            $request->user()->save();
+            $request->user()->userData->mobile_status = 1;
+            $request->user()->userData->save();
+            return $this->success(route('auth.profile.mobile'),'手机号码绑定成功!');
+        }
+        return view('theme::profile.mobile');
+    }
+
+
+
+
+    /*第三方系统账号绑定*/
+    public function anyOauth()
+    {
+
+        return view('theme::profile.oauth');
+    }
+
+    /*消息通知设置*/
+    public function anyNotification(Request $request)
+    {
+        if($request->isMethod('post')){
+            $siteNotifications = $request->input('site_notifications','');
+            $emailNotifications = $request->input('email_notifications','');
+            $request->user()->site_notifications = '';
+            if($siteNotifications){
+                $request->user()->site_notifications = implode(",",$siteNotifications);
+            }
+            $request->user()->email_notifications = '';
+            if($emailNotifications){
+                $request->user()->email_notifications = implode(",",$emailNotifications);
+            }
+
+            $request->user()->save();
+            return $this->success(route('auth.profile.notification'),'通知提醒策略设置成功');
+
+        }
+        $siteNotifications = explode(",",$request->user()->site_notifications);
+        $emailNotifications = explode(",",$request->user()->email_notifications);
+        return view('theme::profile.notification')->with(compact('siteNotifications','emailNotifications'));
+
+    }
+
+
+}

+ 89 - 0
app/Http/Controllers/Account/QuestionInvitationController.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class QuestionInvitationController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $invitations = $request->user()->questionInvitations()->orderBy('created_at','desc')->paginate(20);
+        return view('theme::notification.invitation')->with('invitations',$invitations);
+
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 69 - 0
app/Http/Controllers/Account/ReportController.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Answer;
+use App\Models\Article;
+use App\Models\Question;
+use App\Models\Report;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class ReportController extends Controller
+{
+    /**
+     * 举报问题、文章、回答等
+     * @param  \Illuminate\Http\Request $request
+     * @return
+     */
+    public function store(Request $request)
+    {
+        $requestData = $request->all();
+        $source_id = $requestData['source_id'];
+        $source_type = $requestData['source_type'];
+        $subject = '';
+        if ($source_type == 'question') {
+            $source = Question::find($source_id);
+            $subject = $source->title;
+        } else if ($source_type == 'article') {
+            $source = Article::find($source_id);
+            $subject = $source->title;
+        } else if ($source_type == 'answer') {
+            $source = Answer::find($source_id);
+            $subject = str_limit(strip_tags($source->content), 255);
+        }
+
+        if ( !$source) {
+            abort(404);
+        }
+
+        // 同一类型、同一用户举报限制三次
+        $count = Report::where('user_id',$request->user()->id)->where('source_type',get_class($source))->where('source_id',$source_id)->count();
+        if ($count >= 3){
+            if ($source_type == 'question'){
+                return $this->error(route('ask.question.detail',['id'=>$source_id]),'您已举报超过三次,请耐心等待管理员处理');
+            }elseif ($source_type == 'article'){
+                return $this->error(route('blog.article.detail',['id'=>$source_id]),'您已举报超过三次,请耐心等待管理员处理');
+            }elseif ($source_type == 'answer'){
+                return $this->error(route('ask.question.detail',['id'=>$source->question_id]),'您已举报超过三次,请耐心等待管理员处理');
+            }
+        }
+
+        $data = [
+            'user_id'     => $request->user()->id,
+            'source_id'   => $source_id,
+            'subject'     => $subject,
+            'source_type' => get_class($source),
+            'report_type' => $request->input('report_type'),
+            'reason'      => $request->input('reason', ''),
+        ];
+        Report::create($data);
+        if ($source_type == 'question'){
+            return $this->success(route('ask.question.detail',['id'=>$source_id]),'举报成功!');
+        }elseif ($source_type == 'article'){
+            return $this->success(route('blog.article.detail',['id'=>$source_id]),'举报成功!');
+        }elseif ($source_type == 'answer'){
+            return $this->success(route('ask.question.detail',['id'=>$source->question_id]),'举报成功!');
+        }
+    }
+}

+ 103 - 0
app/Http/Controllers/Account/SearchController.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Question;
+use App\Models\XsSearch;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Pagination\LengthAwarePaginator as Paginator;
+
+class SearchController extends Controller
+{
+
+
+    public function show(Request $request){
+        $word = trim($request->input('word'));
+        $filter = trim($request->input('filter'));
+        return view('theme::search.show')->with(compact('word','filter'));
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request,$filter='all')
+    {
+
+        $validator = Validator::make($request->all(), [
+            'word' => 'required|max:128',
+        ]);
+
+        if ($validator->fails())
+        {
+            return $this->error(route('auth.search.show'),'搜索关键词不能为空');
+        }
+
+        $word = trim($request->input('word'));
+
+        if( Setting()->get('xunsearch_open',0) == 1 ){
+            $pageSize = 15;
+            $page = $request->query('page',1);
+            $startIndex = ($page - 1) * $pageSize;
+            $xsSearch = XsSearch::getSearch();
+            if($filter !== 'all'){
+                $model =  App::make('App\Models\\'.ucfirst(str_singular($filter)));
+                if($filter !== 'tags' ){
+                    $docs = $xsSearch->model($model)->addQuery($word)->setLimit($pageSize,$startIndex)->search();
+                }else{
+                    $docs = $xsSearch->model($model)->addQuery($word)->addRange('status',0,null)->setLimit($pageSize,$startIndex)->search();
+                }
+            }else{
+                $docs = $xsSearch->setFuzzy()->addQuery($word)->setLimit($pageSize,$startIndex)->search();
+            }
+            $dataList = [];
+
+            foreach($docs as $doc){
+                $data = [];
+                $data['class_uid'] = $doc->class_uid;
+                $data['id'] = $doc->id;
+                $data['status'] = $doc->status;
+                $data['subject'] = XsSearch::getSearch()->highlight($doc->subject);
+                $data['content'] = XsSearch::getSearch()->highlight($doc->content);
+                $dataList[] = $data;
+            }
+            $total = $xsSearch->count();
+
+            $list = new Paginator($dataList, $total, $pageSize, $page,[
+                'path'  => $request->url(),
+                'query' => $request->query()
+            ]);
+
+        }else{
+            if($filter === 'all'){
+                $filter = 'questions';
+            }
+            $model =  App::make('App\Models\\'.ucfirst(str_singular($filter)));
+            $list = $model::search($word);
+            if($filter === 'questions'){
+                $list->map(function($item) use ($word) {
+                    foreach (explode(" ", $word) as $k) {
+                        $item->title = $this->_highlight($k, $item->title);
+                        $item->description = $this->_highlight($k, $item->description);
+                    }
+                });
+            }
+        }
+
+
+        return view('theme::search.index')->with('word',$word)->with('filter',$filter)->with('list',$list);
+    }
+
+
+    private function _highlight($word,$subject){
+        return str_ireplace("$word","<em>$word</em>",$subject);
+    }
+
+
+}

+ 157 - 0
app/Http/Controllers/Account/SpaceController.php

@@ -0,0 +1,157 @@
+<?php
+/**
+ * 用户空间
+ */
+namespace App\Http\Controllers\Account;
+
+use App\Models\Credit;
+use App\Models\User;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\View;
+
+class SpaceController extends Controller
+{
+    protected $user;
+
+    public function __construct(Request $request){
+        if($request->route()){
+            $userId =  $request->route()->parameter('user_id',0);
+            $user  = User::with('userData')->find($userId);
+
+            if(!$user){
+                abort(404);
+            }
+            $this->user = $user;
+            View::share("userInfo",$user);
+        }
+
+    }
+
+    /**
+     * 用户空间首页
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $doings = $this->user->doings()->orderBy('created_at','DESC')->paginate(10);
+        $doings->map(function($doing){
+            $doing->action_text = Config::get('tipask.user_actions.'.$doing->action);
+        });
+        $this->user->userData->increment('views');
+        return view('theme::space.index')->with('doings',$doings);
+    }
+
+    /**
+     * 用户提问
+     * @return View
+     */
+    public function questions()
+    {
+        $questions = $this->user->questions()->orderBy('created_at','DESC')->paginate(10);
+        return view('theme::space.questions')->with('questions',$questions);
+    }
+
+    /**
+     * 用户回答
+     * @return mixed
+     */
+    public function answers()
+    {
+        $answers = $this->user->answers()->with('question')->orderBy('created_at','DESC')->paginate(10);
+        return view('theme::space.answers')->with('answers',$answers);
+    }
+
+    public function articles()
+    {
+        $articles = $this->user->articles()->orderBy('created_at','DESC')->paginate(10);
+        return view('theme::space.articles')->with('articles',$articles);
+    }
+
+    /*我的金币*/
+    public function coins()
+    {
+        $coins = Credit::where('user_id','=',$this->user->id)->where('coins','<>',0)->orderBy('created_at','DESC')->paginate(10);
+        $coins->map(function($coin){
+            $coin->actionText = Config::get('tipask.user_actions.'.$coin->action);
+        });
+        return view('theme::space.coins')->with('coins',$coins);
+    }
+
+
+    /*我的经验*/
+    public function credits()
+    {
+        $credits = Credit::where('user_id','=',$this->user->id)->where('credits','<>',0)->orderBy('created_at','DESC')->paginate(10);
+        $credits->map(function($credit){
+            $credit->actionText = Config::get('tipask.user_actions.'.$credit->action);
+        });
+        return view('theme::space.credits')->with('credits',$credits);
+    }
+
+
+    /*我的粉丝*/
+    public function followers()
+    {
+        $followers = $this->user->followers()->orderBy('attentions.created_at','asc')->paginate(10);
+        return view('theme::space.followers')->with('followers',$followers);
+    }
+
+
+    /*我的关注*/
+    public function attentions(Request $request)
+    {
+        $source_type = $request->route()->parameter('source_type');
+        $sourceClassMap = [
+            'questions' => 'App\Models\Question',
+            'users' => 'App\Models\User',
+            'tags' => 'App\Models\Tag',
+        ];
+
+        if(!isset($sourceClassMap[$source_type])){
+          abort(404);
+        }
+
+        $model = App::make($sourceClassMap[$source_type]);
+
+        $attentions = $this->user->attentions()->where('source_type','=',$sourceClassMap[$source_type])->orderBy('attentions.created_at','desc')->paginate(10);
+        $attentions->map(function($attention) use ($model) {
+            $attention['info'] = $model::find($attention->source_id);
+        });
+        return view('theme::space.attentions')->with('attentions',$attentions)->with('source_type',$source_type);
+
+    }
+
+    public function collections(Request $request)
+    {
+        $source_type = $request->route()->parameter('source_type');
+
+        $sourceClassMap = [
+            'questions' => 'App\Models\Question',
+            'articles' => 'App\Models\Article',
+        ];
+
+        if(!isset($sourceClassMap[$source_type])){
+            abort(404);
+        }
+
+        $model = App::make($sourceClassMap[$source_type]);
+
+        $collections = $this->user->collections()->where('source_type','=',$sourceClassMap[$source_type])->orderBy('collections.created_at','desc')->paginate(10);
+        $collections->map(function($collection) use ($model) {
+            $collection['info'] = $model::find($collection->source_id);
+        });
+
+        return view('theme::space.collections')->with('collections',$collections)->with('source_type',$source_type);
+
+
+    }
+
+
+
+
+
+}

+ 95 - 0
app/Http/Controllers/Account/SupportController.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\Answer;
+use App\Models\Article;
+use App\Models\Comment;
+use App\Models\Support;
+use App\Models\UserTag;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+
+class SupportController extends Controller
+{
+
+    /**
+     * 创建支持记录
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store($source_type,$source_id,Request $request)
+    {
+        if($source_type === 'answer'){
+            $source  = Answer::find($source_id);
+        }elseif($source_type === 'article'){
+            $source  = Article::find($source_id);
+        }elseif($source_type === 'comment'){
+            $source  = Comment::find($source_id);
+        }
+
+        if(!$source){
+            abort(404);
+        }
+
+
+        /*再次关注相当于是取消关注*/
+        $support = Support::where("session_id",'=',$request->session()->getId())->where('supportable_type','=',get_class($source))->where('supportable_id','=',$source_id)->first();
+        if($support){
+            return response('supported');
+        }
+
+        $user_id = null;
+        if($request->user()){
+            $user_id = $request->user()->id;
+        }
+
+        $data = [
+            'session_id'     => $request->session()->getId(),
+            'user_id'        => $user_id,
+            'supportable_id'   => $source_id,
+            'supportable_type' => get_class($source),
+        ];
+
+        $support = Support::create($data);
+
+        if($support){
+            $source->increment('supports');
+            $source->user->userData->increment('supports');
+            if($source_type=='answer'){
+                UserTag::multiIncrement($source->user_id,$source->question->tags()->get(),'supports');
+            }else if($source_type=='article'){
+                UserTag::multiIncrement($source->user_id,$source->tags()->get(),'supports');
+            }
+        }
+
+        return response('success');
+    }
+
+
+    public function check($source_type,$source_id,Request $request)
+    {
+        if($source_type === 'answer'){
+            $source  = Answer::find($source_id);
+        }
+
+        if(!$source){
+            abort(404);
+        }
+
+
+        /*再次关注相当于是取消关注*/
+        $support = Support::where("session_id",'=',$request->session()->getId())->where('supportable_type','=',get_class($source))->where('supportable_id','=',$source_id)->first();
+        if($support){
+            return response('failed');
+        }
+
+        return response('success');
+
+    }
+
+}

+ 55 - 0
app/Http/Controllers/Account/TopController.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Models\UserData;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Cache;
+
+class TopController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function coins()
+    {
+        $users = Cache::remember('top_coin_users',60,function() {
+            return  UserData::top('coins',50);
+        });
+        return view('theme::top.coins')->with('users',$users);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function answers()
+    {
+        $users = Cache::remember('top_answer_users',60,function() {
+            return  UserData::top('answers',50);
+        });
+
+        return view('theme::top.answers')->with('users',$users);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function articles()
+    {
+        $users = Cache::remember('top_article_users',60,function() {
+            return  UserData::top('articles',50);
+        });
+        return view('theme::top.articles')->with('users',$users);
+    }
+
+}

+ 305 - 0
app/Http/Controllers/Account/UserController.php

@@ -0,0 +1,305 @@
+<?php
+
+namespace App\Http\Controllers\Account;
+
+use App\Http\Controllers\Controller;
+use App\Models\EmailToken;
+use App\Models\User;
+use App\Repositories\UserRepository;
+use App\Services\CaptchaService;
+use App\Services\CreditService;
+use App\Services\SmsService;
+use Illuminate\Contracts\Auth\Guard;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Hash;
+
+class UserController extends Controller
+{
+
+    protected $auth;
+
+    protected $userRepository;
+    protected $captchaService;
+
+
+    public function __construct(Guard $auth,UserRepository $userRepository,CaptchaService $captchaService){
+        $this->auth = $auth;
+        $this->userRepository = $userRepository;
+        $this->captchaService = $captchaService;
+    }
+
+    public function login(Request $request){
+
+        /*登录表单处理*/
+        if($request->isMethod('post'))
+        {
+
+            $request->flashOnly('email');
+            $validateRules = [
+                'email' => 'required|min:8|max:128',
+                'password' => 'required|min:6'
+            ];
+
+            if( Setting()->get('code_login') == 1){
+                $this->captchaService->setValidateRules('code_login', $validateRules);
+            }
+
+            /*表单数据校验*/
+            $this->validate($request,$validateRules);
+
+            /*只接收email和password的值*/
+            $credentials = [
+                'password' => $request->input('password')
+            ];
+            if(is_email($request->input('email'))){
+                $credentials['email'] =   $request->input('email');
+            }else{
+                $credentials['mobile'] =   $request->input('email');
+            }
+
+            /*根据邮箱地址和密码进行认证*/
+            if ($this->auth->attempt($credentials, $request->has('remember')))
+            {
+
+                if($this->credit($request->user()->id,'login',Setting()->get('coins_login'),Setting()->get('credits_login'))){
+                    $message = '登陆成功! '.get_credit_message(Setting()->get('credits_login'),Setting()->get('coins_login'));
+                   return $this->success(route('website.index'),$message,true);
+                }
+                /*认证成功后跳转到首页*/
+                return $this->success(route('auth.doing.index'),'登陆成功!',true);
+            }
+
+            /*登录失败后跳转到首页,并提示错误信息*/
+            return redirect(route('auth.user.login'))
+                ->withInput($request->only('email', 'remember'))
+                ->withErrors([
+                    'password' => '用户名或密码错误,请核实!',
+                ]);
+        }
+
+        return view("theme::account.login");
+    }
+
+    /**
+     * 用户注册入口
+     * @param Request $request
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
+     */
+    public function register(Request $request)
+    {
+
+        /*注册是否开启*/
+        if(!Setting()->get('register_open',1)){
+            return $this->showErrorMsg(route('website.index'),'管理员已关闭了网站的注册功能!');
+        }
+
+        /*防灌水检查*/
+        if( Setting()->get('register_limit_num') > 0 ){
+            $registerCount = $this->counter('register_number_'.md5($request->ip()));
+            if( $registerCount >= Setting()->get('register_limit_num')){
+                return $this->showErrorMsg(route('website.index'),'您的当前的IP已经超过当日最大注册数目,如有疑问请联系管理员');
+            }
+        }
+
+        /*注册表单处理*/
+        if($request->isMethod('post'))
+        {
+            $request->flashExcept(['password','password_confirmation']);
+            /*表单数据校验*/
+            $validateRules = [
+                'name' => 'required|min:2|max:100|unique:users',
+                'password' => 'required|confirmed|min:6|max:16',
+            ];
+            if(Setting()->get('register_type') == 'email'){
+                $validateRules['email'] = 'required|email|max:255|unique:users';
+            }else{
+                $validateRules['mobile'] = 'required|regex:/^1[3456789]\d{9}$/|unique:users';
+                $validateRules['code'] = 'required|min:4|:max:8';
+            }
+
+            if( Setting()->get('code_register') == 1){
+                $this->captchaService->setValidateRules('code_register', $validateRules);
+            }
+
+            $this->validate($request,$validateRules);
+
+            $formData = $request->all();
+            $formData['status'] = 0;
+            $formData['visit_ip'] = $request->getClientIp();
+
+
+            if( Setting()->get('register_type') == 'mobile' ){
+                if( !SmsService::verifySmsCode($formData['mobile'],$request->input('code')) )  {
+                    return view("theme::account.register")->withErrors(['code'=>'短信验证码错误']);
+                }
+                $formData['status'] = 1;
+            }
+
+            $user = $this->userRepository->register($formData);
+            $user->attachRole(2); //默认注册为普通用户角色
+            $this->auth->login($user);
+            $message = '注册成功!';
+            if($this->credit($request->user()->id,'register',Setting()->get('coins_register'),Setting()->get('credits_register'))){
+                $message .= get_credit_message(Setting()->get('credits_register'),Setting()->get('coins_register'));
+            }
+
+            if(Setting()->get('register_type')=='email'){
+                /*发送邮箱验证邮件*/
+                $emailToken = EmailToken::create([
+                    'email' => $user->email,
+                    'token' => EmailToken::createToken(),
+                    'action'=> 'register'
+                ]);
+
+                if($emailToken){
+                    $subject = '欢迎注册'.Setting()->get('website_name').',请激活您注册的邮箱!';
+                    $content = "「".$request->user()->name."」您好,请激活您在 ".Setting()->get('website_name')." 的注册邮箱!<br /> 请在1小时内点击该链接激活注册账号 → ".route('auth.email.verifyToken',['action'=>$emailToken->action,'token'=>$emailToken->token])."<br />如非本人操作,请忽略此邮件!";
+                    $this->sendEmail($emailToken->email,$subject,$content);
+                }
+            }
+
+            /*记录注册ip*/
+            $this->counter('register_number_'.md5($request->ip()) , 1,86400 );
+
+            return $this->success(route('website.index'),$message);
+        }
+        return view("theme::account.register");
+    }
+
+
+    /*忘记密码*/
+    public function forgetPassword()
+    {
+        return view("theme::account.forgetPassword");
+    }
+
+
+    /*通过邮件方式找回密码*/
+    public function findByEmail(Request $request){
+        if($request->isMethod('post'))
+        {
+            $request->flashOnly('email');
+            /*表单数据校验*/
+            $this->validate($request, [
+                'email' => 'required|email|exists:users',
+                'captcha' => 'required|captcha'
+            ]);
+
+            $emailToken = EmailToken::create([
+                'email' =>  $request->input('email'),
+                'token' => EmailToken::createToken(),
+                'action'=> 'findPassword'
+            ]);
+
+            if($emailToken){
+                $subject = Setting()->get('website_name').' 找回密码通知';
+                $content = "如果您在 ".Setting()->get('website_name')."的密码丢失,请点击下方链接找回 → ".route('auth.user.findPassword',['token'=>$emailToken->token])."<br />如非本人操作,请忽略此邮件!";
+                $this->sendEmail($emailToken->email,$subject,$content);
+            }
+
+            return view("theme::account.findByEmail")->with('success','ok')->with('email',$request->input('email'));
+
+        }
+
+        return view("theme::account.findByEmail");
+    }
+
+
+    /*通过手机验证码找回密码*/
+    public function findByMobile(Request $request){
+        if($request->isMethod('post')){
+            $this->validate($request, [
+                'mobile' => 'required|regex:/^1[34578]\d{9}$/|exists:users',
+                'code' => 'required|min:4|max:6',
+                'password' => 'required|min:6|max:32'
+            ]);
+
+            $mobile = $request->input('mobile');
+            $code = $request->input('code');
+
+            if( !SmsService::verifySmsCode($mobile,$code) ){
+                return view("theme::account.findByMobile")->withErrors(['code'=>'验证码错误']);
+            }
+
+            $user = User::where('mobile','=',$mobile)->first();
+
+            if(!$user){
+                return view("theme::account.findByMobile")->withErrors(['mobile'=>'手机号不存在']);
+            }
+
+            $user->password = Hash::make($request->input('password'));
+            $user->save();
+
+            return $this->success(route('auth.user.login'),'密码修改成功,请重新登录');
+        }
+        return view("theme::account.findByMobile");
+
+    }
+
+
+    public function findPassword($token,Request $request)
+    {
+        if($request->isMethod('post')){
+
+            $this->validate($request, [
+                'password' => 'required|min:6|max:32',
+                'captcha' => 'required|captcha'
+            ]);
+
+            $emailToken = EmailToken::where('action','=','findPassword')->where('token','=',$token)->first();
+            if(!$emailToken){
+                return $this->error(route('website.ask'),'token信息不存在,请重新找回');
+            }
+
+            if($emailToken->created_at->diffInMinutes() > 60){
+
+                return $this->error(route('website.ask'),'token信息已失效,请重新找回');
+            }
+
+            $user = User::where('email','=',$emailToken->email)->first();
+
+            if(!$user){
+                return $this->error(route('website.ask'),'用户不存在或已被删除');
+            }
+
+            $user->password = Hash::make($request->input('password'));
+            $user->save();
+
+            EmailToken::clear($user->email,'findPassword');
+
+            return $this->success(route('auth.user.login'),'密码修改成功,请重新登录');
+
+        }
+
+        return view("theme::account.findPassword")->with('token',$token);
+
+    }
+
+    /*每日签到*/
+    public function sign(Request $request){
+        if(!Setting()->get('open_user_sign')){
+            abort(404);
+        }
+        $loginUser = $request->user();
+        if($loginUser->isSigned()){
+            return $this->error(route('website.index'),'今日已签到,不能重复签到');
+        }
+        $message = '签到成功!';
+        if(CreditService::create($loginUser->id, 'sign', Setting()->get('coins_sign'),Setting()->get('credits_sign'))){
+            $message .= get_credit_message(Setting()->get('credits_sign'),Setting()->get('coins_sign'));
+        }
+        return $this->success(route('website.index'),$message);
+    }
+
+
+    /**
+     * 用户登出
+     */
+    public function logout(){
+        $this->auth->logout();
+        return redirect()->to(route('website.index'));
+    }
+
+
+
+}

+ 91 - 0
app/Http/Controllers/Admin/AccountController.php

@@ -0,0 +1,91 @@
+<?php namespace App\Http\Controllers\Admin;
+
+/**
+ * 用户账号管理操作相关
+ */
+use App\Http\Controllers\Controller;
+use Illuminate\Contracts\Auth\Guard;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Session;
+
+class AccountController extends Controller {
+
+    /**
+     * The Guard implementation.
+     *认证模块
+     * @var Guard
+     */
+    protected $auth;
+
+
+    public function __construct(Guard $auth){
+
+        $this->auth = $auth;
+
+    }
+
+    /*
+     * 用户登录入口,包含登录页面显示以及登录处理
+     * @param Request $request
+     * @return \Illuminate\View\View
+     */
+    public function login(Request $request){
+
+        /*登录表单处理*/
+        if($request->isMethod('post'))
+        {
+
+            $request->flashOnly('email');
+            /*表单数据校验*/
+            $this->validate($request, [
+                'email' => 'required|email', 'password' => 'required|min:6',
+                'captcha' => 'required|captcha'
+            ]);
+
+            /*只接收email和password的值*/
+            $credentials = [
+                'email' => $request->user()->email,
+                'password' => $request->input('password')
+            ];
+
+            /*根据邮箱地址和密码进行认证*/
+            if ($this->auth->attempt($credentials))
+            {
+                session(['admin.login'=>true]);
+                /*认证成功后跳转到首页*/
+                return redirect()->to(route('admin.index.index'));
+
+            }
+
+
+            /*登录失败后跳转到首页,并提示错误信息*/
+            return redirect(route('admin.account.login'))
+                ->withInput($request->only('email'))
+                ->withErrors([
+                    'password' => '用户名或密码错误,请核实!',
+                ]);
+
+        }
+
+        return view("admin.account.login");
+    }
+
+
+
+    /**
+     * 用户登出
+     */
+    public function logout(){
+
+        Session::forget('admin.login');
+        return redirect()->guest(route('admin.account.login'));
+
+    }
+
+
+
+
+
+
+
+}

+ 40 - 0
app/Http/Controllers/Admin/AdminController.php

@@ -0,0 +1,40 @@
+<?php namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\Answer;
+use App\Models\Article;
+use App\Models\Authentication;
+use App\Models\Comment;
+use App\Models\Exchange;
+use App\Models\Question;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cookie;
+use Illuminate\Support\Facades\View;
+
+class AdminController extends Controller {
+
+    public function __construct(Request $request){
+        $startTime = Carbon::createFromTimestamp( Carbon::today()->timestamp - 7 * 24 * 3600 );
+        /*未审核专家数*/
+        $notVerifiedData['users'] = User::where('status','=',0)->where('created_at','>',$startTime)->count();
+        /*未审核专家数*/
+        $notVerifiedData['experts'] = Authentication::where('status','=',0)->count();
+        /*未审核问题数*/
+        $notVerifiedData['questions'] = Question::where('status','=',0)->count();
+        /*未审核回答数*/
+        $notVerifiedData['answers'] = Answer::where('status','=',0)->count();
+        /*未审核文章数*/
+        $notVerifiedData['articles'] = Article::where('status','=',0)->count();
+        /*未审核评论数*/
+        $notVerifiedData['comments'] = Comment::where('status','=',0)->count();
+        /*未审兑换数*/
+        $notVerifiedData['exchanges'] = Exchange::where('status','=',0)->count();
+
+        //当前是否开启小菜单
+        View::share('sidebar_collapse',Cookie::get('sidebar_collapse'));
+        View::share('notVerifiedData',$notVerifiedData);
+    }
+
+}

+ 122 - 0
app/Http/Controllers/Admin/AnswerController.php

@@ -0,0 +1,122 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Answer;
+use App\Services\CreditService;
+use App\Services\NotificationService;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+
+class AnswerController extends AdminController
+{
+    /**
+     * 回答列表
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+
+        $query = Answer::query();
+
+        /*提问人过滤*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+
+        /*问题过滤*/
+        if( isset($filter['question_id']) &&  $filter['question_id'] > 0 ){
+            $query->where('question_id','=',$filter['question_id']);
+        }
+
+        /*提问时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        /*问题状态过滤*/
+        if( isset($filter['status']) && $filter['status'] > -1 ){
+            $query->where('status','=',$filter['status']);
+        }
+        $answers = $query->orderBy('created_at','desc')->paginate(20);
+        return view("admin.answer.index")->with('answers',$answers)->with('filter',$filter);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /*回答审核*/
+    public function verify(Request $request)
+    {
+        $answerIds = $request->input('id');
+        // 积分策略
+        $answers = Answer::whereIn('id',$answerIds)->where('status','<>',1)->select('id','user_id','question_title')->get();
+        if (!empty($answers)){
+            foreach ($answers as $answer){
+                CreditService::create($answer->user_id,'answer',Setting()->get('coins_answer'),Setting()->get('credits_answer'),$answer->id,$answer->question_title);
+            }
+        }
+        Answer::whereIn('id',$answerIds)->update(['status'=>1]);
+        return $this->success(route('admin.answer.index').'?status=0','回答审核成功');
+
+    }
+
+    /**
+     *删除回答
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        $answerIds = $request->input('ids');
+        // 给文章所有者发送站内通知
+        $ids = explode(',',$answerIds);
+        if ($request->input('report_type') == 99){
+            $reason = $request->input('reason');
+        }else{
+            $reason = trans_report_type($request->input('report_type'));
+        }
+        foreach ($ids as $id){
+            $answer = Answer::find($id);
+            // 记录到通知
+            NotificationService::notify(Auth()->user()->id, $answer->user_id, 'remove_answer', $answer->question_title, $answer->id, $reason);
+            Answer::destroy($id);
+        }
+//        Answer::destroy($request->input('id'));
+        return $this->success(route('admin.answer.index'),'回答删除成功');
+    }
+}

+ 142 - 0
app/Http/Controllers/Admin/ArticleController.php

@@ -0,0 +1,142 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Article;
+use App\Models\Category;
+use App\Services\CreditService;
+use App\Services\NotificationService;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class ArticleController extends AdminController
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+
+        $query = Article::query();
+
+        $filter['category_id'] = $request->input('category_id',-1);
+
+
+        /*提问人过滤*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+
+        /*问题标题过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where('title','like', '%'.$filter['word'].'%');
+        }
+
+        /*提问时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        /*问题状态过滤*/
+        if( isset($filter['status']) && $filter['status'] > -1 ){
+            $query->where('status','=',$filter['status']);
+        }
+
+        /*分类过滤*/
+        /*分类过滤*/
+        if( $filter['category_id']> 0 ){
+            $category = Category::findFromCache($filter['category_id']);
+            if($category){
+                $query->whereIn('category_id',$category->getSubIds());
+            }
+        }
+
+
+        $articles = $query->orderBy('created_at','desc')->paginate(20);
+        return view("admin.article.index")->with('articles',$articles)->with('filter',$filter);
+    }
+
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+
+    /*文章审核*/
+    public function verify(Request $request)
+    {
+        $articleIds = $request->input('id');
+        // 积分策略
+        $articles = Article::whereIn('id',$articleIds)->where('status','<>',1)->select('id','user_id','title')->get();
+        if (!empty($articles)){
+            foreach ($articles as $article){
+                CreditService::create($article->user_id,'create_article',Setting()->get('coins_write_article'),Setting()->get('credits_write_article'),$article->id,$article->title);
+            }
+        }
+        Article::whereIn('id',$articleIds)->update(['status'=>1]);
+        return $this->success(route('admin.article.index').'?status=0','文章审核成功');
+
+    }
+
+    /*修改分类*/
+    public function changeCategories(Request $request){
+        $ids = $request->input('ids','');
+        $categoryId = $request->input('category_id',0);
+        if($ids){
+            Article::whereIn('id',explode(",",$ids))->update(['category_id'=>$categoryId]);
+        }
+        return $this->success(route('admin.article.index'),'分类修改成功');
+    }
+
+
+
+    /**
+     * 删除文章
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        $articleIds = $request->input('ids');
+        // 给文章所有者发送站内通知
+        $ids = explode(',',$articleIds);
+        if ($request->input('report_type') == 99){
+            $reason = $request->input('reason');
+        }else{
+            $reason = trans_report_type($request->input('report_type'));
+        }
+        foreach ($ids as $id){
+            $article = Article::find($id);
+            // 记录到通知
+            NotificationService::notify(Auth()->user()->id, $article->user_id, 'remove_article', $article->title, $article->id, $reason);
+            Article::destroy($id);
+        }
+//        Article::destroy($request->input('id'));
+        return $this->success(route('admin.article.index'),'文章删除成功');
+    }
+}

+ 214 - 0
app/Http/Controllers/Admin/AuthenticationController.php

@@ -0,0 +1,214 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Area;
+use App\Models\Authentication;
+use App\Models\Category;
+use App\Models\Tag;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+
+class AuthenticationController extends AdminController
+{
+
+
+    protected  $validateRules = [
+        'real_name' => 'required|max:64',
+        'title' => 'required|max:128',
+        'description' => 'sometimes|max:9999',
+        'id_card' => 'required|max:64|unique:authentications',
+        'id_card_image' => 'sometimes|image|max:2048',
+        'skill' => 'required|max:128',
+        'skill_image' => 'sometimes|image|max:2048',
+    ];
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $query = Authentication::query();
+        $filter =  $request->all();
+
+        $filter['category_id'] = $request->input('category_id',-1);
+
+        /*认证申请状态过滤*/
+        if(isset($filter['status']) && $filter['status'] > -1){
+            $query->where('status','=',$filter['status']);
+        }
+
+        if( isset($filter['id_card']) && $filter['id_card']){
+            $query->where('id_card','=',$filter['id_card']);
+        }
+
+        /*分类过滤*/
+        if( $filter['category_id']> 0 ){
+            $category = Category::findFromCache($filter['category_id']);
+            if($category){
+                $query->whereIn('category_id',$category->getSubIds());
+            }
+        }
+        $authentications = $query->orderBy('updated_at','desc')->paginate(20);
+        return view('admin.authentication.index')->with(compact('filter','authentications'));
+    }
+
+    public function create(){
+        $provinces = Area::provinces();
+        return view('admin.authentication.create')->with(compact('provinces'));
+    }
+
+    public function store(Request $request){
+        $request->flash();
+        $this->validateRules['user_id'] = 'required|integer|unique:authentications';
+        $this->validate($request,$this->validateRules);
+        $userId = $request->input('user_id');
+        $authUser = User::find($userId);
+        if(!$authUser){
+            return $this->error(route('admin.authentication.create'),'申请认证的用户不存在,请核实user_id');
+        }
+
+        $data = $request->all();
+        if(isset($data['is_recommend']) && $data['is_recommend'] ==1){
+            $data['recommend_at'] = Carbon::now();
+        }
+
+        if($request->hasFile('id_card_image')){
+            $savePath = storage_path('app/authentications');
+            $file = $request->file('id_card_image');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $data['id_card_image'] = 'authentications-'.$fileName;
+            }
+        }
+
+        if($request->hasFile('skill_image')){
+            $savePath = storage_path('app/authentications');
+            $file = $request->file('skill_image');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $data['skill_image'] = 'authentications-'.$fileName;
+            }
+        }
+
+        $authentication = Authentication::create($data);
+        if($authentication){
+            Tag::multiSave($request->input('skill'),$request->user());
+        }
+
+        return $this->success(route('admin.authentication.index'),'行家认证信添加成功');
+
+    }
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $authentication = Authentication::find($id);
+        $provinces = Area::provinces();
+        $cities = Area::cities($authentication->province);
+        $data = [
+            'provinces' => $provinces,
+            'cities' => $cities,
+        ];
+        return view('admin.authentication.edit')->with(compact('authentication','data'));
+
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $authentication = Authentication::find($id);
+        if(!$authentication){
+            return $this->error(route('admin.authentication.index'),'行家认证信息不存在,请核实');
+        }
+        $request->flash();
+        $oldStatus = $authentication->status;
+        $this->validateRules['id_card'] = 'required|max:64|unique:authentications,id_card,'.$authentication->user_id.',user_id';
+        $this->validate($request,$this->validateRules);
+
+        $data = $request->all();
+        $data['recommend_at'] = null;
+        if(isset($data['is_recommend']) && $data['is_recommend'] ==1){
+            $data['recommend_at'] = Carbon::now();
+        }
+        if ($request->hasFile('id_card_image')) {
+            $savePath = storage_path('app/authentications');
+            $file = $request->file('id_card_image');
+            $fileName = uniqid(str_random(8)) . '.' . $file->getClientOriginalExtension();
+            $target = $file->move($savePath, $fileName);
+            if ($target) {
+                $data['id_card_image'] = 'authentications-' . $fileName;
+            }
+        }
+
+        if ($request->hasFile('skill_image')) {
+            $savePath = storage_path('app/authentications');
+            $file = $request->file('skill_image');
+            $fileName = uniqid(str_random(8)) . '.' . $file->getClientOriginalExtension();
+            $target = $file->move($savePath, $fileName);
+            if ($target) {
+                $data['skill_image'] = 'authentications-' . $fileName;
+            }
+        }
+
+        $result = $authentication->update($data);
+
+        if($result){
+            Tag::multiSave($request->input('skill'),$request->user());
+        }
+
+        return $this->success(route('admin.authentication.index'),'行家认证信息修改成功');
+
+
+    }
+
+    /*修改分类*/
+    public function changeCategories(Request $request){
+        $ids = $request->input('ids','');
+        $categoryId = $request->input('category_id',0);
+        if($ids){
+            Authentication::whereIn('user_id',explode(",",$ids))->update(['category_id'=>$categoryId]);
+        }
+        return $this->success(route('admin.authentication.index'),'分类修改成功');
+    }
+
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        $ids = $request->input('id');
+        Authentication::destroy($ids);
+        return $this->success(route('admin.authentication.index'),'行家认证信息删除成功');
+    }
+
+    public function recommend(Request $request){
+        $ids = $request->input('id');
+        Authentication::whereIn('user_id', $ids)->where('status','>', 0)->update(['recommend_at'=>Carbon::now()]);
+        return $this->success(route('admin.authentication.index'),'行家推荐显示成功!');
+    }
+
+}

+ 130 - 0
app/Http/Controllers/Admin/BanIpController.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\BanIp;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Validator;
+
+class BanIpController extends AdminController
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+        $query = BanIp::query();
+
+        /*操作人过滤*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+
+        /*关键字过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where('ip','like', '%'.$filter['word'].'%');
+        }
+
+        /*时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        $ip = $query->orderBy('created_at','desc')->paginate(10);
+        return view('admin.banIp.index')->with(compact('filter','ip'));
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     *
+     */
+    public function store(Request $request)
+    {
+        $validate = [
+            'ip' => 'required|ip|unique:ban_ips',
+        ];
+        $validator = Validator::make($request->all(),$validate);
+        if($validator->fails()){
+            return $this->error(route('admin.banIp.index'),$validator->errors()->first());
+        }
+
+        $ip = $request->input('ip');
+        $banIp = BanIp::create([
+            'ip' => $ip,
+            'user_id' => $request->user()->id,
+            'created_at' => Carbon::now()
+        ]);
+        $this->updateIpCache();
+        return $this->success(route('admin.banIp.index'),'添加成功!');
+    }
+
+    public function updateIpCache()
+    {
+        Cache::forget('ip_blacklist');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        $ids = $request->input('id');
+        BanIp::destroy($ids);
+        $this->updateIpCache();
+        return $this->success(route('admin.banIp.index'),'删除成功');
+    }
+}

+ 134 - 0
app/Http/Controllers/Admin/CategoryController.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Category;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Artisan;
+
+class CategoryController extends AdminController
+{
+
+    /*权限验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:255',
+        'slug' => 'required|max:255|unique:categories',
+        'sort' => 'required|integer'
+    ];
+
+
+    /**
+     * 分类列表页面
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $parentId = $request->input("parent_id",0);
+        $parentCategoies = Category::getParentCategories($parentId);
+        $categories = Category::where("parent_id","=",$parentId)->orderBy('sort','asc')->orderBy('created_at','asc')->paginate(config('tipask.admin.page_size'));
+        return view("admin.category.index")->with(compact('categories','parentCategoies','parentId'));
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create(Request $request)
+    {
+        $parentId = $request->input("parent_id",0);
+        $parentCategory['id'] = $parentId ;
+        $parentCategory['name'] = '无' ;
+        if($parentId){
+            $category =  Category::findOrFail($parentId);
+            $parentCategory['name'] = $category->name;
+        }
+        return view('admin.category.create')->with(compact('parentCategory'));
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+        $parentId = $request->input("parent_id",0);
+        $types = $request->input("types",[]);
+        $formData = $request->all();
+        $formData['type'] = implode(",",$types);
+        $formData['grade'] = 1;
+        if($parentId){
+            $parentCategory =  Category::findOrFail($parentId);
+            $formData['grade'] = $parentCategory->grade + 1;
+        }
+        Category::create($formData);
+        Artisan::call('cache:clear');
+        return $this->success(route('admin.category.index',['parent_id'=>$parentId]),'分类添加成功');
+    }
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $category = Category::find($id);
+        if(!$category){
+            return $this->error(route('admin.category.index'),'分类不存在,请核实');
+        }
+        return view('admin.category.edit')->with(compact('category'));
+
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $category = Category::find($id);
+        if(!$category){
+            return $this->error(route('admin.category.index'),'分类不存在,请核实');
+        }
+
+        $this->validateRules['slug'] = "required|max:255|unique:categories,slug,".$category->id;
+
+        $this->validate($request,$this->validateRules);
+        $category->name = $request->input('name');
+        $category->slug = $request->input('slug');
+        $category->sort = $request->input('sort');
+        $category->status = $request->input('status');
+        $category->type = implode(",",$request->input('types'));
+        $category->save();
+        Artisan::call('cache:clear');
+        return $this->success(route('admin.category.index',['parent_id'=>$category->parent_id]),'分类添加成功');
+
+
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        Category::destroy($request->input('ids'));
+        Artisan::call('cache:clear');
+        return $this->success(route('admin.category.index'),'分类删除成功');
+    }
+}

+ 133 - 0
app/Http/Controllers/Admin/CommentController.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Comment;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+
+class CommentController extends AdminController
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+
+        $query = Comment::query();
+
+        /*提问人过滤*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+
+        /*问题标题过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where('content','like', '%'.$filter['word'].'%');
+        }
+
+        /*提问时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        /*问题状态过滤*/
+        if( isset($filter['status']) && $filter['status'] > -1 ){
+            $query->where('status','=',$filter['status']);
+        }
+
+        $comments = $query->orderBy('created_at','desc')->paginate(20);
+
+        return view("admin.comment.index")->with('comments',$comments)->with('filter',$filter);
+
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $comment = Comment::find($id);
+        if(!$comment){
+            abort(404);
+        }
+        return view('admin.comment.edit')->with('comment',$comment);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $comment = Comment::find($id);
+        $comment->content = $request->input('content');
+        $comment->save();
+        return $this->success(route('admin.comment.index'),'评论修改成功');
+
+    }
+
+
+    /*评论审核*/
+    public function verify(Request $request)
+    {
+        $commnetIds = $request->input('id');
+        Comment::whereIn('id',$commnetIds)->update(['status'=>1]);
+        return $this->success(route('admin.comment.index').'?status=0','评论审核成功');
+
+    }
+
+    /**
+     *删除评论内容
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        Comment::destroy($request->input('id'));
+        return $this->success(route('admin.comment.index'),'评论删除成功');
+    }
+}

+ 79 - 0
app/Http/Controllers/Admin/CreditController.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 2017/3/4
+ * Time: 上午12:01
+ */
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\Models\Credit;
+use App\Models\User;
+use Illuminate\Http\Request;
+
+class CreditController extends AdminController
+{
+
+    public function index(Request $request){
+        $filter['user_id'] = $request->input('user_id','');
+        $filter['action'] = $request->input('action','');
+        $filter['date_range'] = $request->input('date_range','');
+        $query = Credit::query();
+        $query->where(function($query){
+            $query->where('credits','<>',0)
+                ->orWhere('coins','<>',0);
+        });
+        /*充值人过滤*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+        /*类型过滤*/
+        if( isset($filter['action']) &&  $filter['action']){
+            $query->where('action','=',$filter['action']);
+        }
+
+        /*时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+        $credits = $query->orderBy('created_at','desc')->paginate(20);
+        $credits->map(function($credit){
+            $credit->actionText = config('tipask.user_actions.'.$credit->action);
+        });
+        return view('admin.credit.index')->with(compact('credits','filter'));
+    }
+
+
+    public function create(){
+        return view('admin.credit.create');
+    }
+
+    public function store(Request $request){
+        $validateRule = [
+            'user_id' => 'required|integer',
+            'action' => 'required|in:reward_user,punish_user',
+            'coins' => 'required|integer|min:0',
+            'credits' => 'required|integer|min:0'
+        ];
+        $request->flash();
+        $this->validate($request,$validateRule);
+
+        $userId = $request->input('user_id',0);
+        $user = User::find($userId);
+        if(!$user){
+            return $this->error(route('admin.credit.create'),'用户不存在,请核实');
+        }
+        $action = $request->input('action');
+        $coins = $request->input('coins');
+        $credits = $request->input('credits');
+        if( $action == 'punish_user'){
+            $credits = intval(-$credits);
+            $coins   = intval(-$coins);
+        }
+        $this->credit($userId,$action,$coins,$credits);
+        return $this->success(route('admin.credit.index'),'充值成功');
+    }
+
+}

+ 49 - 0
app/Http/Controllers/Admin/DraftController.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Draft;
+use Illuminate\Http\Request;
+
+class DraftController extends AdminController
+{
+    // 草稿列表
+    public function index(Request $request)
+    {
+        $filter = $request->all();
+
+        $query = Draft::query();
+
+        /*人过滤*/
+        if (isset($filter['user_id']) && $filter['user_id'] > 0) {
+            $query->where('user_id', '=', $filter['user_id']);
+        }
+
+        /*关键词过滤*/
+        if (isset($filter['word']) && $filter['word']) {
+            $query->where('subject', 'like', '%' . $filter['word'] . '%');
+        }
+
+        /*时间过滤*/
+        if (isset($filter['date_range']) && $filter['date_range']) {
+            $query->whereBetween('created_at', explode(" - ", $filter['date_range']));
+        }
+
+        /*草稿类型过滤*/
+        if (isset($filter['source_type'])) {
+            $query->where('source_type', '=', $filter['source_type']);
+        }
+
+        $drafts = $query->orderBy('created_at', 'desc')->paginate(10);
+
+        return view("admin.draft.index")->with('drafts', $drafts)->with('filter', $filter);
+
+    }
+
+    // 删除草稿
+    public function destroy(Request $request)
+    {
+        Draft::destroy($request->input('id'));
+        return $this->success(route('admin.draft.index'),'删除草稿成功');
+    }
+}

+ 130 - 0
app/Http/Controllers/Admin/ExchangeController.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Exchange;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Config;
+
+class ExchangeController extends AdminController
+{
+    /*权限验证规则*/
+    protected $validateRules = [
+        'real_name' => 'required|max:32',
+        'phone' => 'required|regex:/^1[3456789]{1}\d{9}$/',
+        'email' => 'required|email|max:64',
+        'comment' => 'max:512'
+    ];
+
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $exchanges = Exchange::orderBy('created_at','desc')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.exchange.index')->with('exchanges',$exchanges);
+
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $exchange = Exchange::find($id);
+        return view("admin.exchange.edit")->with('exchange',$exchange);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $exchange = Exchange::find($id);
+        if(!$exchange){
+            return $this->error(route('admin.exchange.index'),'记录不存在,请核实');
+        }
+        $this->validate($request,$this->validateRules);
+
+        $data = $request->all();
+
+        $exchange->update($data);
+
+        return $this->success(route('admin.exchange.index'),'兑换记录修改成功');
+
+    }
+
+
+    /*修改兑换记录状态*/
+    public function changeStatus($id,$status)
+    {
+        $exchange = Exchange::find($id);
+        if(!$exchange){
+            return $this->error(route('admin.exchange.index'),'记录不存在,请核实');
+        }
+
+        if($status === 'success' && $exchange->status ===0 ){
+            $exchange->update(['status'=>1]);
+        }else if($status === 'failed' && $exchange->status ===0 ){
+            $exchange->update(['status'=>4]);
+        }
+
+        return $this->success(route('admin.exchange.index'),'兑换记录状态修改成功');
+
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 107 - 0
app/Http/Controllers/Admin/FriendshipLinkController.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\FriendshipLink;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Config;
+use App\Http\Requests;
+
+class FriendshipLinkController extends AdminController
+{
+    /*权限验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:128',
+        'url' => 'required|url|max:128',
+        'slogan' => 'required|max:128',
+        'sort' => 'sometimes|integer',
+    ];
+
+
+
+    /**
+     * 显示友情链接列表
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $links = FriendshipLink::orderBy('sort','asc')->orderBy('updated_at','desc')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.friendshipLink.index')->with(compact('links'));
+    }
+
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.friendshipLink.create');
+    }
+
+
+
+    /**
+     * 保存添加的友情链接信息
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+        FriendshipLink::create($request->all());
+        return $this->success(route('admin.friendshipLink.index'),'友情链接添加成功');
+
+    }
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $link = FriendshipLink::find($id);
+        if(!$link){
+            return $this->error(route('admin.friendshipLink.index'),'友情链接不存在,请核实');
+        }
+        return view('admin.friendshipLink.edit')->with(compact('link'));
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $link = FriendshipLink::find($id);
+        if(!$link){
+            return $this->error(route('admin.friendshipLink.index'),'友情链接不存在,请核实');
+        }
+        $this->validate($request,$this->validateRules);
+        $link->update($request->all());
+        return $this->success(route('admin.friendshipLink.index'),'友情链接修改成功');
+    }
+
+    /**
+     * 删除友情链接
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        FriendshipLink::destroy($request->input('ids'));
+        return $this->success(route('admin.friendshipLink.index'),'友情链接删除成功');
+    }
+}

+ 132 - 0
app/Http/Controllers/Admin/GoodsController.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Goods;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Config;
+
+class GoodsController extends AdminController
+{
+    /*权限验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:128',
+        'description' => 'sometimes|max:65535',
+        'coins' => 'required|integer',
+        'remnants' => 'required|integer',
+    ];
+
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $word = $request->input("word",'');
+        $goods = Goods::where('name','like',"%$word%")->orderBy('updated_at','DESC')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.goods.index')->with('goods',$goods)->with('word',$word);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.goods.create');
+
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+        $goods = Goods::create($request->all());
+        if($request->hasFile('logo')){
+            $savePath = storage_path('app/goods/'.gmdate('ym'));
+            $file = $request->file('logo');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $goods->logo = 'goods-'.gmdate('ym').'-'.$fileName;
+                $goods->save();
+            }
+        }
+
+        return $this->success(route('admin.goods.index'),'商品添加成功');
+    }
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $goods = Goods::find($id);
+        if(!$goods){
+            return $this->error(route('admin.goods.index'),'商品不存在,请核实');
+        }
+        return view('admin.goods.edit')->with('goods',$goods);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $goods = Goods::find($id);
+        if(!$goods){
+            return $this->error(route('admin.goods.index'),'商品不存在,请核实');
+        }
+
+        $this->validate($request,$this->validateRules);
+        $goods->name = $request->input('name');
+        $goods->post_type = $request->input('post_type');
+        $goods->remnants = $request->input('remnants');
+        $goods->coins = $request->input('coins');
+        $goods->description = $request->input('description');
+        $goods->category_id = $request->input('category_id',0);
+        $goods->status = $request->input('status');
+
+        if($request->hasFile('logo')){
+            $savePath = storage_path('app/goods/'.gmdate('ym'));
+            $file = $request->file('logo');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $goods->logo = 'goods-'.gmdate('ym').'-'.$fileName;
+            }
+        }
+        $goods->save();
+        return $this->success(route('admin.goods.index'),'商品修改成功');
+
+    }
+
+    /**
+     * 删除商品
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        Goods::destroy($request->input('ids'));
+        return $this->success(route('admin.goods.index'),'商品删除成功');
+    }
+}

+ 162 - 0
app/Http/Controllers/Admin/IndexController.php

@@ -0,0 +1,162 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Answer;
+use App\Models\Article;
+use App\Models\Question;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cookie;
+
+class IndexController extends AdminController
+{
+    /**
+     *显示后台首页
+     */
+    public function index()
+    {
+        $totalUserNum = User::count();
+        $totalQuestionNum = Question::count();
+        $totalArticleNum = Article::count();
+        $totalAnswerNum = Answer::count();
+        $userChart = $this->drawUserChart();
+        $questionChart = $this->drawQuestionChart();
+        $systemInfo = $this->getSystemInfo();
+        return view("admin.index.index")->with(compact('totalUserNum','totalQuestionNum','totalArticleNum','totalAnswerNum','userChart','questionChart','systemInfo'));
+    }
+
+
+    /*显示或隐藏sidebar*/
+    public function sidebar(Request $request){
+        Cookie::forget('sidebar_collapse');
+        $cookie = Cookie::forever('sidebar_collapse',$request->get('collapse'));
+        return response()->json('ok')->withCookie($cookie);
+    }
+
+
+    private function drawUserChart()
+    {
+
+        /*生成Labels*/
+        $labelTimes = $chartLabels = [];
+
+        for( $i=0 ; $i < 7 ; $i++ ){
+            $labelTimes[$i] = Carbon::createFromTimestamp( Carbon::today()->timestamp - (6-$i) * 24 * 3600 );
+            $chartLabels[$i] = '"'.$labelTimes[$i]->month.'月-'.$labelTimes[$i]->day.'日'.'"';
+        }
+
+        $nowTime = Carbon::now();
+
+        $users = User::where('created_at','>',$labelTimes[0])->where('created_at','<',$nowTime)->get();
+
+        $registerRange = $verifyRange = $authRange = [0,0,0,0,0,0,0];
+
+        for( $i=0 ; $i < 7 ; $i++ ){
+            $startTime = $labelTimes[$i];
+            $endTime = $nowTime;
+            if(isset($labelTimes[$i+1])){
+                $endTime = $labelTimes[$i+1];
+            }
+            foreach($users as $user){
+                if( $user->created_at > $startTime && $user->created_at < $endTime ){
+                    $registerRange[$i]++;
+                    if( $user->status > 0 ){
+                        $verifyRange[$i]++;
+                    }
+
+                    if($user->userData && $user->userData->authentication_status === 1){
+                        $authRange[$i]++;
+                    }
+                }
+            }
+
+        }
+
+        return ['labels'=>$chartLabels,'registerUsers'=>$registerRange,'verifyUsers'=>$verifyRange,'authUsers'=>$authRange];
+    }
+
+    private function drawQuestionChart()
+    {
+
+        /*生成Labels*/
+        $labelTimes = $chartLabels = [];
+        for( $i=0 ; $i < 7 ; $i++ ){
+            $labelTimes[$i] = Carbon::createFromTimestamp( Carbon::today()->timestamp - (6-$i) * 24 * 3600 );
+            $chartLabels[$i] = '"'.$labelTimes[$i]->month.'月-'.$labelTimes[$i]->day.'日'.'"';
+        }
+
+        $nowTime = Carbon::now();
+
+
+        $questions = Question::where('created_at','>',$labelTimes[0])->where('created_at','<',$nowTime)->get();
+        $answers = Answer::where('created_at','>',$labelTimes[0])->where('created_at','<',$nowTime)->get();
+        $articles = Article::where('created_at','>',$labelTimes[0])->where('created_at','<',$nowTime)->get();
+
+        $questionRange = $answerRange = $articleRange = [0,0,0,0,0,0,0];
+
+        for( $i=0 ; $i < 7 ; $i++ ){
+            $startTime = $labelTimes[$i];
+            $endTime = $nowTime;
+            if(isset($labelTimes[$i+1])){
+                $endTime = $labelTimes[$i+1];
+            }
+
+            /*问题统计*/
+            foreach($questions as $question){
+                if( $question->created_at > $startTime && $question->created_at < $endTime ){
+                    $questionRange[$i]++;
+                }
+            }
+
+            /*回答统计*/
+            foreach($answers as $answer){
+                if( $answer->created_at > $startTime && $answer->created_at < $endTime ){
+                    $answerRange[$i]++;
+                }
+            }
+            /*文章统计*/
+            foreach($articles as $article){
+                if( $article->created_at > $startTime && $article->created_at < $endTime ){
+                    $articleRange[$i]++;
+                }
+            }
+
+        }
+
+        return [
+            'labels'  => $chartLabels,
+            'questionRange' => $questionRange,
+            'answerRange' => $answerRange,
+            'articleRange' => $articleRange,
+        ];
+
+    }
+
+
+    private function getSystemInfo()
+    {
+        $systemInfo['phpVersion'] = PHP_VERSION;
+        $systemInfo['runOS'] = PHP_OS;
+        $systemInfo['maxUploadSize'] = ini_get('upload_max_filesize');
+        $systemInfo['maxExecutionTime'] = ini_get('max_execution_time');
+        $systemInfo['hostName'] = '';
+        if(isset($_SERVER['SERVER_NAME'])){
+            $systemInfo['hostName'] .= $_SERVER['SERVER_NAME'].' / ';
+        }
+        if(isset($_SERVER['SERVER_ADDR'])){
+            $systemInfo['hostName'] .= $_SERVER['SERVER_ADDR'].' / ';
+        }
+        if(isset($_SERVER['SERVER_PORT'])){
+            $systemInfo['hostName'] .= $_SERVER['SERVER_PORT'];
+        }
+        $systemInfo['serverInfo'] = '';
+        if(isset($_SERVER['SERVER_SOFTWARE'])){
+            $systemInfo['serverInfo'] = $_SERVER['SERVER_SOFTWARE'];
+        }
+        return $systemInfo;
+    }
+
+
+}

+ 123 - 0
app/Http/Controllers/Admin/NoticeController.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Notice;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Config;
+use Mockery\Matcher\Not;
+
+class NoticeController extends AdminController
+{
+
+    /*权限验证规则*/
+    protected $validateRules = [
+        'subject' => 'required|max:255',
+        'url' => 'required|max:255',
+        'style' => 'sometimes|max:255'
+    ];
+
+
+    /**
+     * 显示公告列表
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $word = $request->input("word",'');
+        $notices = Notice::where('subject','like',"%$word%")->orderBy('updated_at','DESC')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.notice.index')->with('notices',$notices)->with('word',$word);
+    }
+
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.notice.create');
+    }
+
+
+
+    /**
+     * 保存添加的公告信息
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+        Notice::create($request->all());
+        return $this->success(route('admin.notice.index'),'公告添加成功');
+
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $notice = Notice::find($id);
+        if(!$notice){
+            return $this->error(route('admin.notice.index'),'公告不存在,请核实');
+        }
+        return view('admin.notice.edit')->with('notice',$notice);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $notice = Notice::find($id);
+        if(!$notice){
+            return $this->error(route('admin.notice.index'),'公告不存在,请核实');
+        }
+        $this->validate($request,$this->validateRules);
+        $notice->subject = $request->input('subject');
+        $notice->style = $request->input('style','');
+        $notice->url = $request->input('url');
+        $notice->status = $request->input('status');
+        $notice->save();
+        return $this->success(route('admin.notice.index'),'公告修改成功');
+    }
+
+    /**
+     * 删除公告
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        Notice::destroy($request->input('ids'));
+        return $this->success(route('admin.notice.index'),'公告删除成功');
+    }
+}

+ 105 - 0
app/Http/Controllers/Admin/OperationController.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\OperationLog;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class OperationController extends AdminController
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        $filter =  $request->all();
+        $query = OperationLog::query();
+
+        /*UID*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+
+        /*问题标题过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where('action','like', '%'.$filter['word'].'%');
+        }
+
+        /*提问时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+        $operations = $query->orderBy('created_at','desc')->paginate(10);
+        return view("admin.operation.index")->with('operations',$operations)->with('filter',$filter);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 93 - 0
app/Http/Controllers/Admin/PermissionController.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Permission;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Config;
+
+class PermissionController extends AdminController
+{
+
+    /*权限验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:128',
+        'model' => 'sometimes|max:128',
+        'description' => 'sometimes|max:255',
+    ];
+
+    /**
+     * 权限列表显示
+     */
+    public function index(Request $request)
+    {
+        $word = $request->input("word",'');
+        $permissions = Permission::where('name','like',"%$word%")->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.permission.index')->with('permissions',$permissions)->with('word',$word);
+    }
+
+    /**
+     * 显示权限创建页面
+     */
+    public function create()
+    {
+        return view('admin.permission.create');
+    }
+
+    /**
+     * 保存权限创建表单数据
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validateRules['slug'] = 'required|max:128|unique:permissions';
+        $this->validate($request,$this->validateRules);
+        Permission::create($request->only(['name', 'slug', 'model', 'description']));
+        return $this->success(route('admin.permission.index'),'权限添加成功');
+    }
+
+
+    /**
+     * 显示权限编辑页面
+     */
+    public function edit($id)
+    {
+        $permission = Permission::find($id);
+        if(!$permission){
+            return $this->error(route('admin.permission.index'),'权限不存在,请核实');
+        }
+        return view('admin.permission.edit')->with('permission',$permission);
+    }
+
+    /**
+     * 保存编辑表单数据
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $permission = Permission::find($id);
+        if(!$permission){
+           return $this->error(route('admin.permission.index'),'权限不存在,请核实');
+        }
+        $this->validateRules['slug'] = 'required|max:150|unique:permissions,slug,'.$permission->id;
+        $this->validate($request,$this->validateRules);
+        $permission->name = $request->input('name');
+        $permission->slug = $request->input('slug');
+        $permission->model = $request->input('model');
+        $permission->description = $request->input('description');
+        $permission->save();
+        return $this->success(route('admin.permission.index'),'权限修改成功');
+    }
+
+    /**
+     * 删除权限
+     */
+    public function destroy(Request $request)
+    {
+        Permission::destroy($request->input('id'));
+        return $this->success(route('admin.permission.index'),'权限删除成功');
+    }
+}

+ 128 - 0
app/Http/Controllers/Admin/QuestionController.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Category;
+use App\Models\Question;
+use App\Services\NotificationService;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Auth;
+
+class QuestionController extends AdminController
+{
+    /**
+     * 问题列表
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+
+        $query = Question::query();
+
+        $filter['category_id'] = $request->input('category_id',-1);
+
+
+        /*提问人过滤*/
+        if( isset($filter['user_id']) &&  $filter['user_id'] > 0 ){
+            $query->where('user_id','=',$filter['user_id']);
+        }
+
+        /*问题标题过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where('title','like', '%'.$filter['word'].'%');
+        }
+
+        /*提问时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        /*问题状态过滤*/
+        if( isset($filter['status']) && $filter['status'] > -1 ){
+            $query->where('status','=',$filter['status']);
+        }
+
+        /*分类过滤*/
+        if( $filter['category_id']> 0 ){
+            $category = Category::findFromCache($filter['category_id']);
+            if($category){
+                $query->whereIn('category_id',$category->getSubIds());
+            }
+        }
+
+        $questions = $query->orderBy('created_at','desc')->paginate(20);
+        return view("admin.question.index")->with('questions',$questions)->with('filter',$filter);
+    }
+
+
+    /**
+     * 显示问题编辑页面
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+
+    /*问题审核*/
+    public function verify(Request $request)
+    {
+        $questionIds = $request->input('id');
+        Question::whereIn('id',$questionIds)->update(['status'=>1]);
+        return $this->success(route('admin.question.index').'?status=0','问题审核成功');
+
+    }
+
+    /*修改分类*/
+    public function changeCategories(Request $request){
+        $ids = $request->input('ids','');
+        $categoryId = $request->input('category_id',0);
+        if($ids){
+            Question::whereIn('id',explode(",",$ids))->update(['category_id'=>$categoryId]);
+        }
+        return $this->success(route('admin.question.index'),'分类修改成功');
+    }
+
+    /**
+     * 删除问题
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        $questionIds = $request->input('ids');
+        // 给文章所有者发送站内通知
+        $ids = explode(',',$questionIds);
+        if ($request->input('report_type') == 99){
+            $reason = $request->input('reason');
+        }else{
+            $reason = trans_report_type($request->input('report_type'));
+        }
+        foreach ($ids as $id){
+            $question = Question::find($id);
+            // 记录到通知
+            NotificationService::notify(Auth()->user()->id, $question->user_id, 'remove_question', $question->title, $question->id, $reason);
+            Question::destroy($id);
+        }
+        return $this->success(route('admin.question.index'),'问题删除成功');
+    }
+}

+ 131 - 0
app/Http/Controllers/Admin/RecommendationController.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Recommendation;
+use Illuminate\Http\Request;
+use App\Http\Requests;
+use Illuminate\Support\Facades\Config;
+
+class RecommendationController extends AdminController
+{
+    /*权限验证规则*/
+    protected $validateRules = [
+        'subject' => 'required|max:255',
+        'url' => 'required|max:255',
+        'sort' => 'required|integer',
+    ];
+
+
+
+    /**
+     * 显示推荐列表
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $word = $request->input("word",'');
+        $recommendations = Recommendation::where('subject','like',"%$word%")->orderBy('sort','asc')->orderBy('updated_at','desc')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.recommendation.index')->with('recommendations',$recommendations)->with('word',$word);
+    }
+
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.recommendation.create');
+    }
+
+
+
+    /**
+     * 保存添加的推荐信息
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+        $recommendation = Recommendation::create($request->all());
+        if($request->hasFile('logo')){
+            $savePath = storage_path('app/recommendations');
+            $file = $request->file('logo');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $recommendation->logo = 'recommendations-'.$fileName;
+                $recommendation->save();
+            }
+        }
+
+        return $this->success(route('admin.recommendation.index'),'推荐添加成功');
+
+    }
+
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $recommendation = Recommendation::find($id);
+        if(!$recommendation){
+            return $this->error(route('admin.recommendation.index'),'推荐不存在,请核实');
+        }
+        return view('admin.recommendation.edit')->with('recommendation',$recommendation);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $recommendation = Recommendation::find($id);
+        if(!$recommendation){
+            return $this->error(route('admin.recommendation.index'),'推荐不存在,请核实');
+        }
+        $this->validate($request,$this->validateRules);
+        $recommendation->subject = $request->input('subject');
+        $recommendation->url = $request->input('url');
+        $recommendation->sort = $request->input('sort');
+        $recommendation->status = $request->input('status');
+        if($request->hasFile('logo')){
+            $savePath = storage_path('app/recommendations');
+            $file = $request->file('logo');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $recommendation->logo = 'recommendations-'.$fileName;
+            }
+        }
+        $recommendation->save();
+        return $this->success(route('admin.recommendation.index'),'推荐修改成功');
+    }
+
+    /**
+     * 删除推荐
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        Recommendation::destroy($request->input('ids'));
+        return $this->success(route('admin.recommendation.index'),'推荐删除成功');
+    }
+}

+ 64 - 0
app/Http/Controllers/Admin/ReportController.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Draft;
+use App\Models\Report;
+use Illuminate\Http\Request;
+
+class ReportController extends AdminController
+{
+    // 举报列表
+    public function index(Request $request)
+    {
+        $filter = $request->all();
+
+        $query = Report::query();
+
+        /*举报人过滤*/
+        if (isset($filter['user_id']) && $filter['user_id'] > 0) {
+            $query->where('user_id', '=', $filter['user_id']);
+        }
+
+        /*举报类型过滤*/
+        if (isset($filter['status'])) {
+            $query->where('status', '=', $filter['status']);
+        }
+
+        /*时间过滤*/
+        if (isset($filter['date_range']) && $filter['date_range']) {
+            $query->whereBetween('created_at', explode(" - ", $filter['date_range']));
+        }
+        $reports = $query->orderBy('created_at', 'desc')->paginate(config('tipask.admin.page_size'));
+        return view("admin.report.index")->with('reports', $reports)->with('filter', $filter);
+
+    }
+
+    // 删除举报
+    public function destroy(Request $request)
+    {
+        Report::destroy($request->input('id'));
+        return $this->success(route('admin.report.index'), '删除成功');
+    }
+
+    // 忽略举报
+    public function ignore(Request $request)
+    {
+        $ids = $request->input('id');
+        if ( !empty($ids)) {
+            Report::whereIn('id', $ids)->where('status', 0)->update(['status' => 4]);
+        }
+        return $this->success(route('admin.report.index'), '处理成功');
+    }
+
+    // 处理举报
+    public function dispose(Request $request)
+    {
+        $ids = $request->input('id');
+        if ( !empty($ids)) {
+            Report::whereIn('id', $ids)->where('status', 0)->update(['status' => 1]);
+        }
+        return $this->success(route('admin.report.index'), '处理成功');
+    }
+
+}

+ 111 - 0
app/Http/Controllers/Admin/RoleController.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+use App\Models\Permission;
+use App\Models\Role;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Config;
+
+class RoleController extends AdminController
+{
+    /*验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:128',
+        'description' => 'sometimes|max:255',
+    ];
+
+    /**
+     * 管理列表
+     */
+    public function index(Request $request)
+    {
+        $word = $request->input("word",'');
+        $roles = Role::where('name','like',"%$word%")->orderBy('sort','asc')->orderBy('id','asc')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.role.index')->with('roles',$roles)->with('word',$word);
+    }
+
+    /**
+     * 显示添加页面
+     */
+    public function create()
+    {
+        return view('admin.role.create');
+    }
+
+    /**
+     * 添加表单处理
+     */
+    public function store(Request $request)
+    {
+        $request->flash();
+        $this->validateRules['slug'] = 'required|max:128|unique:roles';
+        $this->validate($request,$this->validateRules);
+        Role::create($request->all());
+        return $this->success(route('admin.role.index'),'角色添加成功');
+    }
+
+
+    /**
+     * 显示编辑页面
+     */
+    public function edit($id)
+    {
+        $role = Role::find($id);
+        if(!$role){
+            return $this->error(route('admin.role.index'),'权限不存在,请核实');
+        }
+        /*获取角色已有权限*/
+        $role_permission_ids = $role->permissions()->get()->map(function($role_permission){
+            return $role_permission->pivot->permission_id;
+        });
+
+        $permission['admin'] = Permission::where('slug','like','admin.%')->orderBy('id', 'asc')->get();
+        return view('admin.role.edit')->with('role',$role)->with('permission',$permission)->with('role_permission_ids',$role_permission_ids);
+    }
+
+    /**
+     * 修改角色信息
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $role = Role::find($id);
+        if(!$role){
+            return $this->error(route('admin.role.index'),'角色不存在,请核实');
+        }
+        $this->validateRules['slug'] = 'required|max:150|unique:roles,slug,'.$role->id;
+        $this->validate($request,$this->validateRules);
+        $role->name = $request->input('name');
+        $role->slug = $request->input('slug');
+        $role->description = $request->input('description');
+        $role->sort = $request->input('sort');
+        $role->save();
+        return $this->success(route('admin.role.index'),'角色修改成功');
+    }
+
+
+    /**
+     * 权限设置
+     */
+    public function permission(Request $request){
+        $role = Role::find($request->input('id'));
+        $role->detachAllPermissions();
+        $permissions = $request->input('permissions',array());
+        foreach($permissions as $permission){
+            $role->attachPermission($permission);
+        }
+        return $this->success(route('admin.role.index'),'角色权限设置成功');
+
+    }
+
+
+    /**
+     * 删除某个角色
+     */
+    public function destroy(Request $request)
+    {
+
+    }
+}

+ 375 - 0
app/Http/Controllers/Admin/SettingController.php

@@ -0,0 +1,375 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Setting;
+use Illuminate\Http\Request;
+class SettingController extends AdminController
+{
+
+    /*站点设置*/
+    public function website(Request $request)
+    {
+        $validateRules = [
+            'website_name' => 'required|max:128',
+            'website_slogan' => 'sometimes|max:128',
+            'website_welcome' => 'sometimes|max:256',
+            'website_url' => 'required|url',
+            'website_icp' => 'sometimes|max:128',
+            'website_cache_time' => 'sometimes|digits_between:0,8640',
+            'website_admin_email' => 'required|email',
+        ];
+
+        $themes = [];
+        /*获取模板主题目录下主题列表*/
+        $themePath = base_path()."/resources/views/themes";
+        if ($dh = opendir($themePath)) {
+            while (($file = readdir($dh)) !== false) {
+                if( is_dir($themePath.'/'.$file) && $file{0} !== '.' ){
+                    $themes[] = $file;
+                }
+            }
+            closedir($dh);
+        }
+
+        if($request->isMethod('post')){
+            $this->validate($request,$validateRules);
+            $data = $request->except('_token');
+            foreach($data as $name=>$value){
+                Setting()->set($name,$value);
+            }
+            $envParams['APP_URL'] = $request->input('website_url','');
+            $envParams['WEBSITE_THEME'] = $request->input('website_theme','default');
+            $envParams['WEBSITE_SKIN'] = $request->input('website_skin','default');
+            $envParams['WEBSITE_ADMIN_EMAIL'] = $request->input('website_admin_email','');
+            Setting()->setEnvParams($envParams);
+            return $this->success(route('admin.setting.website'),'站点设置保存成功');
+
+        }
+
+        return view('admin.setting.website')->with('themes',$themes);
+    }
+
+
+    /*邮件配置*/
+    public function  email(Request $request)
+    {
+
+        if($request->isMethod('post')){
+            $data = $request->except('_token');
+            unset($data['_token']);
+            $envParams = [];
+            foreach($data as $name=>$value){
+                $envParams[strtoupper($name)] = $value;
+                Setting()->set($name,$value);
+            }
+            Setting()->setEnvParams($envParams);
+            return $this->success(route('admin.setting.email'),'邮箱配置保存成功');
+        }
+
+        return view('admin.setting.email');
+    }
+
+    /*时间格式设置*/
+    public function time(Request $request)
+    {
+        $timeOffsets = [
+            '-12' => '(标准时-12:00) 日界线西',
+            '-11' => '(标准时-11:00) 中途岛、萨摩亚群岛',
+            '-10' => '(标准时-10:00) 夏威夷',
+            '-9' => '(标准时-9:00) 阿拉斯加',
+            '-8' => '(标准时-8:00) 太平洋时间(美国和加拿大)',
+            '-7' => '(标准时-7:00) 山地时间(美国和加拿大)',
+            '-6' => '(标准时-6:00) 中部时间(美国和加拿大)、墨西哥城',
+            '-5' => '(标准时-5:00) 东部时间(美国和加拿大)、波哥大',
+            '-4' => '(标准时-4:00) 大西洋时间(加拿大)、加拉加斯',
+            '-3.5' => '(标准时-3:30) 纽芬兰',
+            '-3' => '(标准时-3:00) 巴西、布宜诺斯艾利斯、乔治敦',
+            '-2' => '(标准时-2:00) 中大西洋',
+            '-1' => '(标准时-1:00) 亚速尔群岛、佛得角群岛',
+            '0' => '(格林尼治标准时) 西欧时间、伦敦、卡萨布兰卡',
+            '1' => '(标准时+1:00) 中欧时间、安哥拉、利比亚',
+            '2' => '(标准时+2:00) 东欧时间、开罗,雅典',
+            '3' => '(标准时+3:00) 巴格达、科威特、莫斯科',
+            '3.5' => '(标准时+3:30) 德黑兰',
+            '4' => '(标准时+4:00) 阿布扎比、马斯喀特、巴库',
+            '4.5' => '(标准时+4:30) 喀布尔',
+            '5' => '(标准时+5:00) 叶卡捷琳堡、伊斯兰堡、卡拉奇',
+            '5.5' => '(标准时+5:30) 孟买、加尔各答、新德里',
+            '6' => '(标准时+6:00) 阿拉木图、 达卡、新亚伯利亚',
+            '7' => '(标准时+7:00) 曼谷、河内、雅加达',
+            '8' => '(标准时+8:00)北京、重庆、香港、新加坡',
+            '9' => '(标准时+9:00) 东京、汉城、大阪、雅库茨克',
+            '9.5' => '(标准时+9:30) 阿德莱德、达尔文',
+            '10' => '(标准时+10:00) 悉尼、关岛',
+            '11' => '(标准时+11:00) 马加丹、索罗门群岛',
+            '12' => '(标准时+12:00) 奥克兰、惠灵顿、堪察加半岛'
+        ];
+
+        $validateRules = [
+            'time_diff' => 'integer',
+        ];
+
+        if($request->isMethod('post'))
+        {
+            $this->validate($request,$validateRules);
+            $data = $request->except('_token');
+            foreach($data as $name=>$value){
+                Setting()->set($name,$value);
+            }
+            return $this->success(route('admin.setting.time'),'时间设置成功');
+        }
+
+        return view('admin.setting.time')->with('timeOffsets',$timeOffsets);
+    }
+
+    /*防灌水设置*/
+    public function irrigation(Request $request)
+    {
+        if($request->isMethod('post')){
+            $data = $request->except('_token');
+            $data['code_login'] = $request->input('code_login',0);
+            $data['code_register'] = $request->input('code_register',0);
+            $data['code_create_question'] = $request->input('code_create_question',0);
+            $data['code_create_article'] = $request->input('code_create_article',0);
+            $data['code_create_answer'] = $request->input('code_create_answer',0);
+            foreach($data as $name => $value ){
+                Setting()->set($name,$value);
+            }
+            return $this->success(route('admin.setting.irrigation'),'防灌水策略设置成功');
+
+        }
+
+        return view('admin.setting.irrigation');
+
+    }
+
+
+    /*注册策略设置*/
+    public function register(Request $request)
+    {
+        $validateRules = [];
+        if($request->isMethod('post')){
+            $this->validate($request,$validateRules);
+            $data = $request->except('_token');
+            foreach($data as $name=>$value){
+                Setting()->set($name,$value);
+            }
+            return $this->success(route('admin.setting.register'),'注册设置成功');
+
+        }
+
+        return view('admin.setting.register');
+
+    }
+
+
+    /*积分策略设置*/
+    public function credits(Request $request)
+    {
+        $validateRules = [
+            'credits_register' => 'required|integer',
+            'coins_register' => 'required|integer',
+            'credits_login' => 'required|integer',
+            'coins_login' => 'required|integer',
+            'credits_sign' => 'required|integer',
+            'coins_sign' => 'required|integer',
+            'credits_ask' => 'required|integer',
+            'coins_ask' => 'required|integer',
+            'credits_answer' => 'required|integer',
+            'coins_answer' => 'required|integer',
+            'credits_adopted' => 'required|integer',
+            'coins_adopted' => 'required|integer',
+        ];
+        if($request->isMethod('post')){
+            $this->validate($request,$validateRules);
+            $data = $request->except('_token');
+            unset($data['_token']);
+            foreach($data as $name=>$value){
+                Setting()->set($name,$value);
+            }
+            return $this->success(route('admin.setting.credits'),'积分策略设置成功');
+        }
+
+        return view('admin.setting.credits');
+    }
+
+    public function seo(Request $request)
+    {
+        if($request->isMethod('post')){
+            $data = $request->except('_token');
+            unset($data['_token']);
+            foreach($data as $name=>$value){
+                Setting()->set($name,$value);
+            }
+            return $this->success(route('admin.setting.seo'),'seo策略设置成功');
+        }
+        return view('admin.setting.seo');
+    }
+
+
+    /*变量设置*/
+    public function variables(Request $request){
+        return view('admin.setting.variables');
+    }
+
+
+    public function attach(Request $request){
+        if($request->isMethod('post')){
+            $validateRules = [
+                'attach_image_size' => 'required|integer',
+                'attach_file_size' => 'required|integer',
+            ];
+            $request->flash();
+            $this->validate($request,$validateRules);
+            $imageSize = $request->input('attach_image_size');
+            $attachSize = $request->input('attach_file_size');
+            $openWaterMark = $request->input('attach_open_watermark',0);
+            $envParams['UPLOAD_IMAGE_SIZE'] = $imageSize;
+            $envParams['UPLOAD_ATTACH_SIZE'] = $attachSize;
+            $envParams['UPLOAD_OPEN_WATERMARK'] = $openWaterMark;
+            if($request->hasFile('attach_watermark_image')){
+                $savePath = storage_path('app/watermarks');
+                $file = $request->file('attach_watermark_image');
+                $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+                $target = $file->move($savePath,$fileName);
+                if($target){
+                    $envParams['UPLOAD_WATERMARK_IMAGE'] = 'watermarks-'.$fileName;
+                }
+            }
+            Setting()->setEnvParams($envParams);
+            return $this->success(route('admin.setting.attach'),'设置成功');
+        }
+        return view('admin.setting.attach');
+    }
+
+
+    /*xunsearch整合*/
+    public function xunSearch(Request $request)
+    {
+        if($request->isMethod('post')){
+            $data = $request->except('_token');
+            foreach($data as $name=>$value){
+                Setting()->set($name,$value);
+            }
+            $envParams['XUNSEARCH_INDEX'] = $data['xunsearch_index'];
+            $envParams['XUNSEARCH_SEARCH'] = $data['xunsearch_search'];
+            Setting()->setEnvParams($envParams);
+            return $this->success(route('admin.setting.xunSearch'),'xunSearch设置成功');
+        }
+        return view('admin.setting.xunSearch');
+
+    }
+
+    public function oauth(Request $request)
+    {
+
+        if($request->isMethod('post')){
+            /*总开关*/
+            $envParams['OAUTH_OPEN'] = $request->input('oauth_open',0);
+            /*qq登陆参数*/
+            $envParams['OAUTH_QQ_OPEN'] = $request->input('oauth_qq_open',0);
+            $envParams['OAUTH_QQ_KEY'] = $request->input('oauth_qq_key','');
+            $envParams['OAUTH_QQ_SECRET'] = $request->input('oauth_qq_secret','');
+            $envParams['OAUTH_QQ_REDIRECT'] = $request->input('oauth_qq_redirect','');
+
+            /*微博登陆参数*/
+            $envParams['OAUTH_WEIBO_OPEN'] = $request->input('oauth_weibo_open',0);
+            $envParams['OAUTH_WEIBO_KEY'] = $request->input('oauth_weibo_key','');
+            $envParams['OAUTH_WEIBO_SECRET'] = $request->input('oauth_weibo_secret','');
+            $envParams['OAUTH_WEIBO_REDIRECT'] = $request->input('oauth_weibo_redirect','');
+
+            /*微信扫描登陆*/
+            $envParams['OAUTH_WEIXINWEB_OPEN'] = $request->input('oauth_weixinweb_open',0);
+            $envParams['OAUTH_WEIXINWEB_KEY'] = $request->input('oauth_weixinweb_key','');
+            $envParams['OAUTH_WEIXINWEB_SECRET'] = $request->input('oauth_weixinweb_secret','');
+            $envParams['OAUTH_WEIXINWEB_REDIRECT'] = $request->input('oauth_weixinweb_redirect','');
+
+            /*微信公众号内登陆*/
+            $envParams['OAUTH_WEIXIN_OPEN'] = $request->input('oauth_weixin_open',0);
+            $envParams['OAUTH_WEIXIN_KEY'] = $request->input('oauth_weixin_key','');
+            $envParams['OAUTH_WEIXIN_SECRET'] = $request->input('oauth_weixin_secret','');
+            $envParams['OAUTH_WEIXIN_REDIRECT'] = $request->input('oauth_weixin_redirect','');
+            Setting()->setEnvParams($envParams);
+            return $this->success(route('admin.setting.oauth'),'一键登录设置成功');
+        }
+
+        return view('admin.setting.oauth');
+
+    }
+
+    public function sms($type='sms', Request $request){
+        if($request->isMethod('post')){
+            if($type == 'sms'){//短信参数配置
+                $envParams['SMS_OPEN'] = $request->input('sms_open',0);
+                $envParams['SMS_SIGN_NAME'] = $request->input('sms_sign_name','');
+                $envParams['SMS_KEY_ID'] = $request->input('sms_key_id','');
+                $envParams['SMS_KEY_SECRET'] = $request->input('sms_key_secret','');
+                Setting()->setEnvParams($envParams);
+            }else if($type == 'template'){ //消息模板配置
+                $messageTemplates = $request->except(['_token']);
+                foreach ($messageTemplates as $key => $value){
+                    if(str_contains($key,'_template')){
+                        Setting()->set($key, $value);
+                    }
+                }
+            }
+            return $this->success(route('admin.setting.sms'),'配置保存成功');
+        }
+        return view('admin.setting.sms');
+    }
+
+
+    public function geetest(Request $request){
+        if($request->isMethod('post')){
+            $envParams['GEETEST_OPEN'] = $request->input('geetest_open',false);
+            $envParams['GEETEST_ID'] = $request->input('geetest_id','');
+            $envParams['GEETEST_KEY'] = $request->input('geetest_key','');
+            Setting()->setEnvParams($envParams);
+            return $this->success(route('admin.setting.geetest'),'配置保存成功');
+        }
+        return view('admin.setting.geetest');
+    }
+
+
+    /*系统功能自定义*/
+    public function custom(Request $request){
+        if($request->isMethod('post')){
+            $data = $request->except('_token');
+            foreach($data as $name => $value){
+                Setting()->set($name,$value);
+            }
+            return $this->success(route('admin.setting.custom'),'配置保存成功');
+
+        }
+        return view('admin.setting.custom');
+    }
+
+    /*微信小程序配置*/
+    public function weChatApp(Request $request){
+        if($request->isMethod('post')){
+            $data = $request->except(['_token','weapp_arcode_image']);
+            foreach($data as $name => $value){
+                Setting()->set($name,$value);
+            }
+            $envParams['WEAPP_OPEN'] = $request->input('weapp_open',0);
+            $envParams['WEAPP_APP_ID'] = $request->input('weapp_app_id','');
+            $envParams['WEAPP_APP_SECRET'] = $request->input('weapp_app_secret','');
+            Setting()->setEnvParams($envParams);
+            if($request->hasFile('weapp_qrcode_image')){
+                $savePath = storage_path('app/wechat');
+                $file = $request->file('weapp_qrcode_image');
+                $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+                $target = $file->move($savePath,$fileName);
+                if($target){
+                    Setting()->set('weapp_qrcode_image','wechat-'.$fileName);
+                }
+            }
+            return $this->success(route('admin.setting.weChatApp'),'配置保存成功');
+        }
+        return view('admin.setting.weChatApp');
+    }
+
+
+
+}

+ 50 - 0
app/Http/Controllers/Admin/SystemController.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 16/7/4
+ * Time: 上午11:57
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Category;
+use App\Models\User;
+use App\Models\UserTag;
+use App\Services\QuestionService;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Artisan;
+use Illuminate\Support\Facades\Log;
+use Maatwebsite\Excel\Facades\Excel;
+
+class SystemController extends AdminController
+{
+
+    /*系统工具首页*/
+    public function index(){
+        return view('admin.system.index');
+    }
+    
+    public function upgrade()
+    {
+        /*执行升级操作*/
+        try{
+            Artisan::call('migrate');
+        }catch(\Exception $e) {
+            return $this->error(route('admin.system.index'),'数据库连接出错:'.$e->getMessage());
+        }
+        return $this->success(route('admin.system.index'),'升级脚本执行成功!');
+
+    }
+
+
+    /*系统统计数据校准*/
+    public function adjust(){
+        set_time_limit(0);
+        ignore_user_abort(true);
+        UserTag::figures();
+        return $this->success(route('admin.system.index'),'用户标签数据同步成功!');
+    }
+
+}

+ 178 - 0
app/Http/Controllers/Admin/TagController.php

@@ -0,0 +1,178 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Category;
+use App\Models\Tag;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+
+class TagController extends AdminController
+{
+    /*权限验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:128|unique:tags',
+        'url' => 'sometimes|max:128',
+        'summary' => 'sometimes|max:255',
+        'description' => 'sometimes|max:65535',
+    ];
+
+
+    /**
+     *标签管理
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+        $query = Tag::query();
+
+        $filter['category_id'] = $request->input('category_id',-1);
+
+        /*问题标题过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where('name','like', '%'.$filter['word'].'%');
+        }
+
+        /*时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        /*分类过滤*/
+        /*分类过滤*/
+        if( $filter['category_id']> 0 ){
+            $category = Category::findFromCache($filter['category_id']);
+            if($category){
+                $query->whereIn('category_id',$category->getSubIds());
+            }
+        }
+
+        $tags = $query->orderBy('updated_at','desc')->paginate(20);
+        return view("admin.tag.index")->with('tags',$tags)->with('filter',$filter);
+
+
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.tag.create');
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+
+
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+        $data = $request->all();
+        if($request->hasFile('logo')){
+            $savePath = storage_path('app/tags/'.gmdate('ym'));
+            $file = $request->file('logo');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $data['logo'] = 'tags-'.gmdate('ym').'-'.$fileName;
+            }
+        }
+        Tag::create($data);
+        return $this->success(route('admin.tag.index'),'标签创建成功');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        $tag = Tag::find($id);
+        if(!$tag){
+           abort(404);
+        }
+        return view('admin.tag.edit')->with('tag',$tag);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $tag = Tag::find($id);
+        if(!$tag){
+            return $this->error(route('admin.tag.index'),'话题不存在,请核实');
+        }
+        $this->validateRules['name'] = 'required|max:128|unique:tags,name,'.$id;
+        $this->validate($request,$this->validateRules);
+        $tag->name = $request->input('name');
+        $tag->category_id = $request->input('category_id');
+        $tag->summary = $request->input('summary');
+        $tag->description = $request->input('description');
+        if($request->hasFile('logo')){
+            $savePath = storage_path('app/tags/'.gmdate('ym'));
+            $file = $request->file('logo');
+            $fileName = uniqid(str_random(8)).'.'.$file->getClientOriginalExtension();
+            $target = $file->move($savePath,$fileName);
+            if($target){
+                $tag->logo = 'tags-'.gmdate('ym').'-'.$fileName;
+            }
+        }
+        $tag->save();
+        return $this->success(route('admin.tag.index'),'标签修改成功');
+    }
+
+    /*修改分类*/
+    public function changeCategories(Request $request){
+        $ids = $request->input('ids','');
+        $categoryId = $request->input('category_id',0);
+        if($ids){
+            Tag::whereIn('id',explode(",",$ids))->update(['category_id'=>$categoryId]);
+        }
+        return $this->success(route('admin.tag.index'),'分类修改成功');
+    }
+
+
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request)
+    {
+        $tagIds = $request->input('id');
+        Tag::destroy($tagIds);
+        return $this->success(route('admin.tag.index'),'标签删除成功');
+    }
+
+}

+ 62 - 0
app/Http/Controllers/Admin/ToolController.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 16/5/27
+ * Time: 上午11:24
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Artisan;
+use Illuminate\Support\Facades\Mail;
+
+class ToolController extends AdminController
+{
+
+    /*清空缓存*/
+    public function clearCache(Request $request)
+    {
+        if($request->isMethod('post')){
+            $cacheItems = $request->input('cacheItems',[]);
+            if(in_array('data',$cacheItems)){
+               Artisan::call('cache:clear');
+               Artisan::call('config:clear');
+            }
+
+            if(in_array('view',$cacheItems)){
+                Artisan::call('view:clear');
+            }
+            return $this->success(route('admin.tool.clearCache'),'缓存更新成功');
+        }
+        return view('admin.tool.clearCache');
+
+    }
+
+
+    /*发送测试邮件*/
+    public function sendTestEmail(Request $request){
+        $validateRules = [
+            'sendTo' => 'required|email',
+            'content' => 'required|max:255',
+        ];
+
+        $this->validate($request,$validateRules);
+
+        $mailData = $request->all();
+
+        try{
+            Mail::send('emails.test',$mailData, function($message) use ($mailData)
+            {
+                $message->to($mailData['sendTo'])->subject(Setting()->get('website_name').'邮件测试');
+            });
+            return response('ok');
+        }catch (\Swift_SwiftException $e){
+            return response($e->getMessage());
+        }
+
+
+    }
+
+}

+ 175 - 0
app/Http/Controllers/Admin/UserController.php

@@ -0,0 +1,175 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Area;
+use App\Models\Role;
+use App\Models\User;
+use App\Repositories\UserRepository;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Storage;
+use Intervention\Image\Facades\Image;
+
+class UserController extends AdminController
+{
+    /*权限验证规则*/
+    protected $validateRules = [
+        'name' => 'required|max:100',
+        'email' => 'required|email|max:255|unique:users',
+        'password' => 'required|min:6|max:20',
+    ];
+    /**
+     * 用户管理首页
+     */
+    public function index(Request $request)
+    {
+        $filter =  $request->all();
+
+        $query = User::query();
+
+        if(isset($filter['user_id']) && $filter['user_id'] > 0){
+            $query->where("id","=",$filter['user_id']);
+        }
+
+        /*关键词过滤*/
+        if( isset($filter['word']) && $filter['word'] ){
+            $query->where(function($subQuery) use ($filter) {
+                return $subQuery->where('name','like',$filter['word'].'%')
+                         ->orWhere('email','like',$filter['word'].'%')
+                         ->orWhere('mobile','like',$filter['word'].'%');
+            });
+        }
+
+        /*注册时间过滤*/
+        if( isset($filter['date_range']) && $filter['date_range'] ){
+            $query->whereBetween('created_at',explode(" - ",$filter['date_range']));
+        }
+
+        /*状态过滤*/
+        if( isset($filter['status']) && $filter['status'] > -2 ){
+            $query->where('status','=',$filter['status']);
+        }
+
+        $users = $query->orderBy('created_at','desc')->paginate(Config::get('tipask.admin.page_size'));
+        return view('admin.user.index')->with('users',$users)->with('filter',$filter);
+    }
+
+    /**
+     * 显示用户添加页面
+     */
+    public function create()
+    {
+        $roles = Role::orderby('name','asc')->get();
+
+        return view('admin.user.create')->with(compact('roles'));
+    }
+
+    /**
+     * 保存创建用户信息
+     */
+    public function store(Request $request,UserRepository $userRepository)
+    {
+
+        $request->flash();
+        $this->validate($request,$this->validateRules);
+
+        $formData = $request->all();
+        $formData['status'] = 1;
+        $formData['visit_ip'] = $request->getClientIp();
+
+        $user = $userRepository->register($formData);
+        $user->attachRole($request->input('role_id'));
+        return $this->success(route('admin.user.index'),'用户添加成功!');
+
+    }
+
+
+    /**
+     * 显示用户编辑页面
+     */
+    public function edit($id)
+    {
+        $user = User::find($id);
+        $roles = Role::orderby('name','asc')->get();
+        $provinces = Area::provinces();
+        $cities = Area::cities($user->province);
+        $data = [
+            'provinces' => $provinces,
+            'cities' => $cities,
+        ];
+        return view('admin.user.edit')->with(compact('user','roles','data'));
+    }
+
+    /**
+     * 保存用户修改
+     */
+    public function update(Request $request, $id)
+    {
+        $request->flash();
+        $user = User::find($id);
+        if(!$user){
+            abort(404);
+        }
+        $this->validateRules['name'] = 'required|email|max:255|unique:users,name,'.$user->id;
+        $this->validateRules['email'] = 'required|email|max:255|unique:users,email,'.$user->id;
+        $this->validateRules['password'] = 'sometimes|min:6';
+        $password = $request->input('password');
+        if($password)
+        {
+            $user->password = bcrypt($password);
+        }
+        $user->name = $request->input('name');
+        $user->email = $request->input('email');
+        $user->title = $request->input('title','');
+        $user->gender = $request->input('gender',0);
+        $user->province = $request->input('province',0);
+        $user->city = $request->input('city',0);
+        $user->description = $request->input('description');
+        $user->status = $request->input('status',0);
+
+        if($request->hasFile('avatar')){
+            $user_id = $user->id;
+            $file = $request->file('avatar');
+            $avatarDir = User::getAvatarDir($user_id);
+            $extension = $file->getClientOriginalExtension();
+
+            File::delete(storage_path('app/'.User::getAvatarPath($user_id,'big')));
+            File::delete(storage_path('app/'.User::getAvatarPath($user_id,'middle')));
+            File::delete(storage_path('app/'.User::getAvatarPath($user_id,'small')));
+
+            Storage::disk('local')->put($avatarDir.'/'.User::getAvatarFileName($user_id,'origin').'.'.$extension,File::get($file));
+            Image::make(storage_path('app/'.User::getAvatarPath($user_id,'origin',$extension)))->resize(128,128)->save(storage_path('app/'.User::getAvatarPath($user_id,'big')));
+            Image::make(storage_path('app/'.User::getAvatarPath($user_id,'origin',$extension)))->resize(64,64)->save(storage_path('app/'.User::getAvatarPath($user_id,'middle')));
+            Image::make(storage_path('app/'.User::getAvatarPath($user_id,'origin',$extension)))->resize(24,24)->save(storage_path('app/'.User::getAvatarPath($user_id,'small')));
+        }
+
+        $user->save();
+        $user->detachAllRoles();
+        $user->attachRole($request->input('role_id'));
+        return $this->success(route('admin.user.index'),'用户修改成功');
+    }
+
+    /*用户审核*/
+    public function verify(Request $request)
+    {
+        $userIds = $request->input('id');
+        User::whereIn('id',$userIds)->update(['status'=>1]);
+        return $this->success(route('admin.user.index').'?status=0','用户审核成功');
+
+    }
+
+    /**
+     * 删除用户
+     */
+    public function destroy(Request $request)
+    {
+        $userIds = $request->input('id');
+        User::destroy($userIds);
+        return $this->success(route('admin.user.index'),'用户删除成功');
+
+    }
+}

+ 33 - 0
app/Http/Controllers/Admin/XunSearchController.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 16/6/27
+ * Time: 上午11:42
+ */
+
+namespace App\Http\Controllers\Admin;
+
+
+use Illuminate\Support\Facades\Artisan;
+
+class XunSearchController extends AdminController
+{
+
+    /*清空缓存*/
+    public function clear(){
+        set_time_limit(0);
+       Artisan::call('search:clear');
+        return $this->success(route('admin.setting.xunSearch'),'索引删除成功');
+
+    }
+
+
+    /*重建索引*/
+    public function rebuild(){
+        set_time_limit(0);
+        Artisan::call('search:rebuild');
+        return $this->success(route('admin.setting.xunSearch'),'索引重建成功,请3分钟后进行搜索测试!');
+    }
+
+}

+ 227 - 0
app/Http/Controllers/AjaxController.php

@@ -0,0 +1,227 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Area;
+use App\Models\Message;
+use App\Models\Notification;
+use App\Models\Question;
+use App\Models\Tag;
+use App\Models\Taggable;
+use App\Models\User;
+use App\Models\UserTag;
+use App\Services\SmsService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Validator;
+
+class AjaxController extends Controller
+{
+
+    /**
+     * 加载城市下拉项
+     * @param $province_id 省份ID
+     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
+     */
+    public function loadCities($province_id)
+    {
+
+
+        $cities = Area::cities($province_id);
+        $city_options = '';
+        foreach($cities as $city){
+            $city_options .= '<option value="'.$city->id.'">'.$city->name.'</option>';
+        }
+
+        return response($city_options);
+
+    }
+
+
+
+    /*未读通知数目*/
+    public function unreadNotifications()
+    {
+        $total = Notification::where('to_user_id','=',Auth()->user()->id)->where('is_read','=',0)->count();
+        $response = '<span class="fa fa-bell-o fa-lg"></span>';
+        if( $total > 0 ){
+            if($total > 99){
+                $total = '99+' ;
+            }
+            $response =  '<span class="fa fa-bell-o fa-lg"></span><span class="label label-danger">'.$total.'</span>';
+        }
+
+        return response($response);
+    }
+
+
+    public function unreadMessages()
+    {
+        $total = Message::where('to_user_id','=',Auth()->user()->id)->where('is_read','=',0)->where("to_deleted","<>",1)->where("from_deleted","<>",1)->count();
+        $response = '<span class="fa fa-envelope-o fa-lg"></span>';
+        if( $total > 0 ){
+            if($total > 99){
+                $total = '99+' ;
+            }
+            $response =  '<span class="fa fa-envelope-o fa-lg"></span><span class="label label-success">'.$total.'</span>';
+        }
+
+        return response($response);
+    }
+
+
+    public function loadTags(Request $request)
+    {
+        $word = $request->input('word');
+        $tags = [];
+        if( strlen($word) > 10 ){
+            return response()->json($tags);
+        }
+        $type = $request->input('type','all');
+        if(!$word){
+            $tags = Taggable::hottest($type,10);
+        }else{
+            $tags = Tag::where('name','like',$word.'%')->take(10)->get();
+        }
+
+        $selectTags = [];
+        foreach ($tags as $tag){
+            $selectTag = [];
+            $selectTag['id'] = $tag->name;
+            $selectTag['text'] = $tag->name;
+            $selectTags[] = $selectTag;
+        }
+        return response()->json($selectTags);
+    }
+
+
+
+    public function loadUsers(Request $request)
+    {
+        $word = $request->input('word');
+
+        $users = User::where('id','<>',$request->user()->id)->where('name','like',"$word%")->take(10)->get();
+        $users->map(function($user){
+            $user->avatar = get_user_avatar($user->id);
+            $user->coins = $user->userData->coins;
+            $user->answers = $user->userData->answers;
+            $user->followers = $user->userData->followers;
+        });
+        return response()->json($users->toArray());
+    }
+
+
+    public function loadInviteUsers(Request $request)
+    {
+        $questionId = $request->input('question_id',0);
+        $question = Question::find($questionId);
+        if(!$question){
+            return $this->ajaxError(10004,'notFund');
+        }
+        $tags = $question->tags()->get();
+
+        $tagIds = array_pluck($tags,"id");
+
+        if(!$tagIds){
+            return $this->ajaxError(10004,'noData');
+        }
+
+        $word = $request->input('word','');
+
+        if(trim($word)){
+            $users = User::where('id','<>',$request->user()->id)->where('name','like',"$word%")->take(10)->get();
+            $users->map(function($user) use($tagIds,$question) {
+                $user->tag_name = '';
+                $user->tag_answers = 0;
+                $userTag = UserTag::where("user_id","=",$user->id)->whereIn("tag_id",$tagIds)->orderBy("answers","desc")->orderBy("created_at","desc")->first();
+                if($userTag){
+                    $tag = Tag::find($userTag->tag_id);
+                    if($tag){
+                        $user->tag_name = $tag->name;
+                    }
+                    $user->tag_answers = $userTag->answers;
+                }
+                $user->avatar = get_user_avatar($user->id);
+                $user->url = route('auth.space.index',['user_id'=>$user->user_id]);
+                $user->isInvited = 0;
+
+            });
+        }else{
+
+            $invitations = $question->invitations()->get();
+            $invitedUserIds = array_pluck($invitations,'user_id');
+            $userTags = UserTag::whereIn("tag_id",$tagIds)->whereNotIn("user_id",$invitedUserIds)->orderBy("answers","desc")->orderBy("supports","desc")->select("user_id","tag_id","answers","supports")->take(16)->groupBy("user_id")->get();
+            $users = [];
+            foreach($userTags as $userTag){
+                $user = User::find($userTag->user_id);
+                if(!$user){
+                    continue;
+                }
+                $user->tag_name = '';
+                $user->tag_answers = 0;
+                $tag = Tag::find($userTag->tag_id);
+                if($tag){
+                    $user->tag_name = $tag->name;
+                }
+                $user->tag_answers = $userTag->answers;
+                $user->avatar = get_user_avatar($userTag->user_id);
+                $user->url = route('auth.space.index',['user_id'=>$userTag->user_id]);
+                $user->isInvited = 0;
+                $users[] = $user;
+            }
+        }
+
+        return $this->ajaxSuccess($users);
+    }
+
+
+    public function sendSmsCode(Request $request){
+
+        if($request->isMethod('post')){
+            $validateRules['code'] = 'required|captcha';
+            $validator = Validator::make($request->all(),$validateRules);
+            if($validator->fails()){
+                return $this->ajaxError(10003,'验证码错误');
+            }
+
+            $mobile = $request->input('mobile','');
+            if(!is_mobile($mobile)){
+                return $this->ajaxError(10004,'手机号格式码错误');
+            }
+            $sendType = $request->input('send_type','');
+            if($request->user() && $sendType=='bind'){ //绑定手机号绑定处理
+                /*黑名单校验*/
+                if( $request->user()->status == -1 ){
+                    return $this->ajaxError(10011,'你无权进行该操作');
+                }
+                /*避免重复发送短信校验*/
+                if($request->user()->mobile == $mobile && $request->user()->userData->mobile_status==1){
+                    return $this->ajaxError(10008,'您的手机号已绑定,不能重复绑定');
+                }
+                /*已注册手机号校验*/
+                if(User::where("mobile","=",$mobile)->where("id","<>",$request->user()->id)->count() > 0){
+                    return $this->ajaxError(10009,'该手机号已注册,不能重复绑定');
+                }
+            }
+            if( $sendType =='register' && User::where("mobile","=",$mobile)->count() > 0){ //注册发送处理
+                return $this->ajaxError(10011,'该手机号已注册,不能重复注册');
+            }else if($sendType =='findPassword' && User::where("mobile","=",$mobile)->count() == 0){ //找回密码处理
+                return $this->ajaxError(10011,'该手机号不存在,请核实');
+            }
+
+            /*次数限制*/
+            $sendTimes = $this->counter('send_sms_counter_'.$mobile);
+            if($sendTimes > config('tipask.sms_limit_times',10)){
+                return $this->ajaxError(10005,'短信验证码发送数量已超出当日最大限制,请明天再试');
+            }
+
+            if(!SmsService::sendSmsCode($mobile)){
+                return $this->ajaxError(10006,'短信发送失败,请稍后再试');
+            }
+            $this->counter('send_sms_counter_'.$mobile,1);
+            return $this->ajaxSuccess("success");
+        }
+
+        return $this->ajaxError(10007,"请求错误");
+    }
+
+}

+ 229 - 0
app/Http/Controllers/Ask/AnswerController.php

@@ -0,0 +1,229 @@
+<?php
+
+namespace App\Http\Controllers\Ask;
+
+use App\Models\Answer;
+use App\Models\Attention;
+use App\Models\Question;
+use App\Models\QuestionInvitation;
+use App\Models\UserTag;
+use App\Services\CaptchaService;
+use App\Services\QuestionService;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Gate;
+
+class AnswerController extends Controller
+{
+
+    /*问题创建校验*/
+    protected $validateRules = [
+        'content' => 'required|min:15|max:65535',
+    ];
+
+
+    /**
+     * 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);
+            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);
+        }
+        $request->flash();
+
+        /*防止重复回答*/
+        if($loginUser->isAnswered($question->id)){
+            return $this->showErrorMsg(route('ask.question.detail',['id'=>$question->id]),'您已经回答过该问题了,不能重复回答!');
+        }
+
+        /*普通用户修改需要输入验证码*/
+        if( Setting()->get('code_create_answer') ){
+            $captchaService->setValidateRules('code_create_answer',$this->validateRules);
+        }
+        $this->validate($request,$this->validateRules);
+        $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){
+
+            /*用户回答数+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,$question->id,$answer->content);
+            
+            /*回答后通知关注问题*/
+            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 redirect(route('ask.question.detail',['id'=>$question_id]));
+    }
+
+
+    public function edit($id,Request $request)
+    {
+        $answer = Answer::findOrFail($id);
+
+        $this->authorize('update', $answer);
+
+        /*编辑回答时效控制*/
+        if(!Gate::allows('updateInTime',$answer)){
+            return $this->showErrorMsg(route('ask.question.detail',['id'=>$answer->question_id]),'你已超过回答可编辑的最大时长,不能进行编辑了。如有疑问请联系管理员!');
+        }
+
+        return view("theme::question.edit_answer")->with('answer',$answer);
+    }
+
+
+    /*修改问题内容*/
+    public function update($id,Request $request)
+    {
+        $answer = Answer::find($id);
+        if(!$answer){
+            abort(404);
+        }
+
+        $this->authorize('update', $answer);
+
+        $request->flash();
+        /*普通用户修改需要输入验证码*/
+        if( Setting()->get('code_create_answer') ){
+            $this->validateRules['captcha'] = 'required|captcha';
+        }
+
+        $this->validate($request,$this->validateRules);
+
+        $answer->content = clean($request->input('content'));
+        $answer->status = 1;
+
+        $answer->save();
+
+        return $this->success(route('ask.answer.detail',['question_id'=>$answer->question_id,'id'=>$answer->id]),"回答编辑成功");
+
+    }
+
+
+    public function adopt($id,Request $request)
+    {
+        $answer = Answer::find($id);
+        if(!$answer){
+            abort(404);
+        }
+
+        $this->authorize('adopt',$answer);
+
+        $question = $answer->question;
+        if(!$question){
+            abort(404);
+        }
+        if( $question->status <= 0 ){
+            return $this->error(route('ask.question.detail',['question_id'=>$answer->question_id]),'问题未通过审核,不能采纳答案!');
+        }
+
+        /*防止重复采纳*/
+        if($answer->adopted_at){
+           return $this->error(route('ask.question.detail',['question_id'=>$answer->question_id]),'该回答已被采纳,不能重复采纳');
+        }
+
+        $result = QuestionService::adoptAnswer($answer->id);
+        /*悬赏处理*/
+        $percent = Setting()->get('best_answer_percent',100) / 100;
+        $earning = ceil($answer->question->price * $percent) + Setting()->get('coins_adopted',0);
+        if($result){
+            return $this->success(route('ask.question.detail',['question_id'=>$answer->question_id]),"回答采纳成功!".get_credit_message(Setting()->get('credits_adopted'),$earning));
+        }
+
+        return $this->error(route('ask.question.detail',['question_id'=>$answer->question_id]),"回答采纳失败,请稍后再试!");
+
+
+    }
+
+
+    /**
+     * 回答详情查看
+     */
+    public function detail($question_id,$id,Request $request)
+    {
+
+        $question = Question::findOrFail($question_id);
+
+        /*问题查看数+1*/
+        $question->increment('views');
+
+        $answer = $question->answers()->find($id);
+
+        /*设置通知为已读*/
+        if($request->user()){
+            $this->readNotifications($answer->id,'answer');
+        }
+
+        /*相关问题*/
+        $relatedQuestions = Question::correlations($question->tags()->pluck('tag_id'));
+        return view("theme::answer.detail")->with('question',$question)
+            ->with('answer',$answer)
+            ->with('relatedQuestions',$relatedQuestions);
+    }
+
+
+
+}

+ 114 - 0
app/Http/Controllers/Ask/CommentController.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace App\Http\Controllers\Ask;
+
+use App\Models\Answer;
+use App\Models\Article;
+use App\Models\Comment;
+use App\Models\Question;
+use Illuminate\Http\Request;
+
+use App\Http\Controllers\Controller;
+
+class CommentController extends Controller
+{
+
+    /*问题创建校验*/
+    protected $validateRules = [
+        'content' => 'required|max:10000',
+    ];
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+
+        $this->validate($request,$this->validateRules);
+
+        $source_type = $request->input('source_type');
+        $source_id = $request->input('source_id');
+        if($source_type === 'question'){
+            $source  = Question::find($source_id);
+            $notify_subject = $source->title;
+            $notify_type = 'comment_question';
+            $notify_refer_type = 'question';
+            $notify_refer_id = 0;
+        }else if($source_type === 'answer'){
+            $source = Answer::find($source_id);
+            $notify_subject = $source->content;
+            $notify_type = 'comment_answer';
+            $notify_refer_type = 'answer';
+            $notify_refer_id = $source->question_id;
+
+        }else if($source_type === 'article'){
+            $source = Article::find($source_id);
+            $notify_subject = $source->title;
+            $notify_type = 'comment_article';
+            $notify_refer_type = 'article';
+            $notify_refer_id = 0;
+        }
+
+        if(!$source){
+            abort(404);
+        }
+
+
+        $data = [
+            'user_id'     => $request->user()->id,
+            'content'     => $request->input('content'),
+            'source_id'   => $source_id,
+            'source_type' => get_class($source),
+            'to_user_id'  => $request->input('to_user_id'),
+            'status'      => 1,
+            'supports'    => 0
+        ];
+
+
+        $comment = Comment::create($data);
+        /*问题、回答、文章评论数+1*/
+        $comment->source()->increment('comments');
+
+        if( $comment->to_user_id > 0 ){
+            $this->notify($request->user()->id,$comment->to_user_id,'reply_comment',$notify_subject,$source_id,$comment->content,$notify_refer_type,$notify_refer_id);
+
+        }else{
+            $this->notify($request->user()->id,$source->user_id,$notify_type,$notify_subject,$source_id,$comment->content,$notify_refer_type,$notify_refer_id);
+        }
+
+
+        return view('theme::comment.item')->with('comment',$comment)
+                                            ->with('source_type',$source_type)
+                                            ->with('source_id',$source_id);
+    }
+
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($source_type,$source_id)
+    {
+        if($source_type === 'question'){
+            $source = Question::find($source_id);
+        }else if($source_type === 'answer'){
+            $source = Answer::find($source_id);
+        }else if($source_type === 'article'){
+            $source = Article::find($source_id);
+        }
+
+        if(!$source){
+            abort(404);
+        }
+        $comments = $source->comments()->orderBy('supports','desc')->orderBy('created_at','asc')->simplePaginate(15);
+
+       return view('theme::comment.paginate')->with('comments',$comments)
+                                         ->with('source_type',$source_type)
+                                         ->with('source_id',$source_id);
+    }
+
+}

+ 473 - 0
app/Http/Controllers/Ask/QuestionController.php

@@ -0,0 +1,473 @@
+<?php
+
+namespace App\Http\Controllers\Ask;
+
+use App\Http\Controllers\Controller;
+use App\Models\Draft;
+use App\Models\Question;
+use App\Models\QuestionInvitation;
+use App\Models\Tag;
+use App\Models\User;
+use App\Models\UserTag;
+use App\Models\XsSearch;
+use App\Services\CaptchaService;
+use App\Services\NotificationService;
+use Illuminate\Http\Request;
+
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Gate;
+use Illuminate\Support\Facades\Validator;
+
+class QuestionController extends Controller
+{
+
+    /*问题创建校验*/
+    protected $validateRules = [
+        'title' => 'required|max:255',
+        'description' => 'sometimes|max:65535',
+        'price' => 'sometimes|integer|min:0|max:1000',
+        'tags' => 'sometimes|max:128',
+        'category_id' => 'sometimes|numeric'
+    ];
+
+
+    /**
+     * 问题详情查看
+     */
+    public function detail($id, Request $request)
+    {
+        $question = Question::find($id);
+
+        if (empty($question)) {
+            abort(404);
+        }
+
+        /*待审核问题游客不可见,管理员和内容作者可见*/
+        if ($question->status == 0) {
+            if (Auth()->guest()) {
+                abort(404);
+            }
+            $this->authorize('show', $question);
+        }
+
+        /*问题查看数+1*/
+        $question->increment('views');
+
+        /*已解决问题*/
+        $bestAnswer = [];
+        if ($question->status === 2) {
+            $bestAnswer = $question->answers()->where('adopted_at', '>', 0)->first();
+        }
+
+        if ($request->input('sort', 'default') === 'created_at') {
+            $answers = $question->answers()->whereNull('adopted_at')->orderBy('created_at', 'DESC')->paginate(10);
+        } else {
+            $answers = $question->answers()->whereNull('adopted_at')->orderBy('supports', 'DESC')->orderBy('created_at',
+                'ASC')->paginate(10);
+        }
+
+
+        /*设置通知为已读*/
+        if ($request->user()) {
+            $this->readNotifications($question->id, 'question');
+        }
+
+        /*相关问题*/
+        $relatedQuestions = Question::correlations($question->tags()->pluck('tag_id'));
+
+        /*回答草稿箱处理*/
+        $draftId = $request->query('draftId', '');
+        $formData['content'] = '';
+        if($draftId){
+            $draft = Draft::find($draftId);
+            if($draft){
+                $formData['content'] = $draft->editor_content;
+            }
+        }
+        return view("theme::question.detail")->with('question', $question)
+            ->with('answers', $answers)->with('bestAnswer', $bestAnswer)
+            ->with('relatedQuestions', $relatedQuestions)->with('formData',$formData);
+    }
+
+
+    /**
+     * 问题添加页面显示
+     */
+    public function create(Request $request)
+    {
+        $to_user_id = $request->query('to_user_id', 0);
+        $draftId = $request->query('draftId', '');
+        $draft = Draft::find($draftId);
+        if ($draftId && !$draft) {
+            abort(404);
+        }
+        $formData = [];
+        $formData['subject'] = '';
+        $formData['content'] = '';
+        $formData['category_id'] = 0;
+        if($draft){
+            $draft->form_data = json_decode($draft->form_data,true);
+            $formData['subject'] = $draft->subject;
+            $formData['content'] = $draft->editor_content;
+            $formData['category_id'] = $draft->form_data['category_id'];
+            $to_user_id = $draft->formData['to_user_id'];
+        }
+        $toUser = User::find($to_user_id);
+        return view("theme::question.create")->with(compact('toUser', 'to_user_id','formData'));
+    }
+
+
+    /*创建提问*/
+    public function store(Request $request, CaptchaService $captchaService)
+    {
+        $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'),'操作失败!您的金币数不足!');
+        }
+
+        $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,
+        ];
+        $question = Question::create($data);
+        /*判断问题是否添加成功*/
+        if ($question) {
+            /*悬赏提问*/
+            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);
+
+            //记录动态 匿名状态不记录动态
+            if (!$question->hide) {
+                $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 edit($id, Request $request)
+    {
+        $question = Question::find($id);
+
+        if (!$question) {
+            abort(404);
+        }
+
+        /*编辑权限控制*/
+        $this->authorize('update', $question);
+
+        /*编辑问题时效控制*/
+        if (!Gate::allows('updateInTime', $question)) {
+            return $this->showErrorMsg(route('website.index'), '你已超过问题可编辑的最大时长,不能进行编辑了。如有疑问请联系管理员!');
+        }
+
+        return view("theme::question.edit")->with(compact('question'));
+    }
+
+
+    /*问题内容编辑*/
+    public function update(Request $request, CaptchaService $captchaService)
+    {
+        $question_id = $request->input('id');
+        $question = Question::find($question_id);
+        if (!$question) {
+            abort(404);
+        }
+
+        $this->authorize('update', $question);
+
+        $request->flash();
+
+        /*普通用户修改需要输入验证码*/
+        if (Setting()->get('code_create_question')) {
+            $captchaService->setValidateRules('code_create_question', $this->validateRules);
+        }
+
+        $this->validate($request, $this->validateRules);
+        $question->title = trim($request->input('title'));
+        $question->description = clean($request->input('description'));
+        $question->hide = intval($request->input('hide'));
+        $question->category_id = $request->input('category_id', 0);
+
+        $question->save();
+        $tagString = trim($request->input('tags'));
+
+        /*更新标签*/
+        Tag::multiSave($tagString, $question);
+
+        return $this->success(route('ask.question.detail', ['question_id' => $question->id]), "问题编辑成功");
+
+    }
+
+    /*追加悬赏*/
+    public function appendReward($id, Request $request)
+    {
+        $question = Question::find($id);
+        if (!$question) {
+            abort(404);
+        }
+
+        $validateRules = [
+            'coins' => 'required|digits_between:1,' . $request->user()->userData->coins
+        ];
+
+        $this->validate($request, $validateRules);
+
+        DB::beginTransaction();
+        try {
+            $this->credit($request->user()->id, 'append_reward', -abs($request->input('coins')), 0, $question->id,
+                $question->title);
+            $question->increment('price', $request->input('coins'));
+
+            DB::commit();
+            return $this->success(route('ask.question.detail', ['question_id' => $id]), "追加悬赏成功");
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return $this->error(route('ask.question.detail', ['question_id' => $id]), "追加悬赏失败,请稍后再试");
+        }
+
+    }
+
+    /*问题建议*/
+    public function suggest(Request $request)
+    {
+
+        $validateRules = [
+            'word' => 'required|min:2|max:255',
+        ];
+        $this->validate($request, $validateRules);
+
+        if (Setting()->get('xunsearch_open', 0) != 1) {
+            return response('');
+        }
+        $word = $request->input('word');
+
+        $xsSearch = XsSearch::getSearch();
+        $model = App::make('App\Models\\Question');
+        $docs = $xsSearch->model($model)->addQuery('subject:' . $word)->setLimit(10, 0)->search();
+        $suggestList = '';
+        foreach ($docs as $doc) {
+            $question = Question::find($doc->id);
+            if (!$question) {
+                continue;
+            }
+            $suggestList .= '<li>';
+
+            if ($question->status === 2) {
+                $suggestList .= '<span class="label label-success pull-left mr-5">解决</span>';
+            }
+            $suggestList .= '<a href="' . route('ask.question.detail',
+                    ['id' => $doc->id]) . '" target="_blank" class="mr-10">' . XsSearch::getSearch()->highlight($doc->subject) . '</a>';
+            $suggestList .= '<small class="text-muted">' . $question->answers . ' 回答</small>';
+            $suggestList .= '</li>';
+        }
+        return response($suggestList);
+
+    }
+
+
+    /*邀请回答*/
+    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);
+
+            // 记录到通知
+            NotificationService::notify($loginUser->id, $toUser->id, 'invite_answer', $question->title, $question->id);
+            return $this->ajaxSuccess('success');
+        }
+
+        return $this->ajaxError(10008, '邀请失败,请稍后再试');
+    }
+
+
+    public function inviteEmail($question_id, Request $request)
+    {
+
+        $loginUser = $request->user();
+
+        if ($this->counter('question_invite_num_' . $loginUser->id) > config('tipask.user_invite_limit')) {
+            return $this->ajaxError(50007, '超出每天最大邀请次数');
+        }
+
+        $question = Question::find($question_id);
+        if (!$question) {
+            return $this->ajaxError(50001, 'question not fund');
+        }
+
+        $validator = Validator::make($request->all(), [
+            'sendTo' => 'required|email|max:255',
+            'message' => 'required|min:10|max:10000',
+        ]);
+
+        if ($validator->fails()) {
+            $this->ajaxError(50011, '字段校验失败');
+        }
+
+        $loginUser = $request->user();
+        $email = $request->input('sendTo');
+        $content = $request->input('message');
+        /*是否已邀请,不能重复邀请*/
+        if ($question->isInvited($email, $loginUser->id)) {
+            return $this->ajaxError(50008, '该用户已被邀请,不能重复邀请');
+        }
+
+        $invitation = QuestionInvitation::create([
+            'from_user_id' => $loginUser->id,
+            'question_id' => $question->id,
+            'user_id' => 0,
+            'send_to' => $email
+        ]);
+
+        if ($invitation) {
+            $this->counter('question_invite_num_' . $loginUser->id, 1);
+            $subject = $loginUser->name . "在「" . Setting()->get('website_name') . "」向您发起了回答邀请";
+            $message = $content;
+            $this->sendEmail($invitation->send_to, $subject, $message);
+            return $this->ajaxSuccess('success');
+        }
+
+        return $this->ajaxError(10008, '邀请失败,请稍后再试');
+    }
+
+    public function invitations($question_id, $type)
+    {
+        $question = Question::find($question_id);
+        if (!$question) {
+            return $this->ajaxError(50001, 'question not fund');
+        }
+
+        $showRows = ($type == 'part') ? 3 : 100;
+
+        $invitations = $question->invitations()->where("user_id", ">", 0)->orderBy('created_at',
+            'desc')->groupBy('user_id')->take($showRows);
+
+        $invitedUsers = [];
+        foreach ($invitations->get() as $invitation) {
+            if ($invitation->user()) {
+                $invitedUsers[] = '<a target="_blank" href="' . route('auth.space.index',
+                        ['user_id' => $invitation->user->id]) . '">' . $invitation->user->name . '</a>';
+            }
+        }
+
+        $invitedHtml = implode(",&nbsp;", $invitedUsers);
+
+        $totalInvitedNum = $invitations->count();
+        if ($type == 'part' && $totalInvitedNum > $showRows) {
+            $invitedHtml .= '等 <a id="showAllInvitedUsers" href="javascript:void(0);">' . $totalInvitedNum . '</a> 人';
+        }
+        if ($totalInvitedNum > 0) {
+            $invitedHtml .= '&nbsp;已被邀请';
+        }
+
+        return response($invitedHtml);
+
+    }
+
+
+}

+ 32 - 0
app/Http/Controllers/Ask/TagController.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers\Ask;
+
+use App\Models\Tag;
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class TagController extends Controller
+{
+    /**
+     * tag显示页面
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index($id,$source_type='questions')
+    {
+        $tag = Tag::findOrFail($id);
+        $sources = [];
+        if($source_type=='questions'){
+            $sources = $tag->questions()->where("status",">",0)->orderBy('created_at','desc')->paginate(15);
+        }else if($source_type=='articles'){
+            $sources = $tag->articles()->where("status",">",0)->orderBy('created_at','desc')->paginate(15);
+        }
+        $followers = $tag->followers()->orderBy('user_data.credits','desc')->orderBy('user_data.supports','desc')->take(10)->get();
+        return view('theme::tag.index')->with('tag',$tag)
+                                       ->with('sources',$sources)
+                                       ->with('followers',$followers)
+                                       ->with('source_type',$source_type);
+    }
+
+}

+ 51 - 0
app/Http/Controllers/AttachController.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 2017/2/4
+ * Time: 下午3:12
+ */
+
+namespace App\Http\Controllers;
+
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Validator;
+
+class AttachController extends Controller
+{
+
+    public function upload(Request $request){
+        $validator = Validator::make($request->all(),[
+            'file' => 'required|max:'.config('tipask.upload.attach_size'),
+        ]);
+
+        if($validator->fails()){
+            return $this->ajaxError(10000,$validator->errors()->first('file'));
+        }
+
+        $file = $request->file('file');
+        $extension = $file->getClientOriginalExtension();
+        $filePath = 'attachments/'.gmdate("Y")."/".gmdate("m")."/".uniqid(str_random(8)).'.'.$extension;
+        Storage::disk('local')->put($filePath,File::get($file));
+
+        return $this->ajaxSuccess([
+            'url'=>route("website.attach.download",['name'=>str_replace("/","-",$filePath)]),
+            'name'=>$file->getClientOriginalName()
+        ]);
+    }
+
+
+    public function download($name){
+        $attachFile = storage_path('app/'.str_replace("-","/",$name));
+        if(!is_file($attachFile)){
+            abort(404);
+        }
+        return response()->download($attachFile);
+    }
+
+
+
+}

+ 275 - 0
app/Http/Controllers/Blog/ArticleController.php

@@ -0,0 +1,275 @@
+<?php
+
+namespace App\Http\Controllers\Blog;
+
+use App\Models\Article;
+use App\Models\Draft;
+use App\Models\Question;
+use App\Models\Tag;
+use App\Models\UserData;
+use App\Models\UserTag;
+use App\Services\CaptchaService;
+use Illuminate\Http\Request;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Gate;
+use Illuminate\Support\Facades\Storage;
+
+class ArticleController extends Controller
+{
+
+    /*问题创建校验*/
+    protected $validateRules = [
+        'title' => 'required|min:5|max:255',
+        'content' => 'required|min:50|max:16777215',
+        'summary' => 'sometimes|max:255',
+        'tags' => 'sometimes|max:128',
+        'category_id' => 'sometimes|numeric'
+    ];
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create(Request $request)
+    {
+        $draftId = $request->query('draftId', '');
+        $draft = Draft::find($draftId);
+        if ($draftId && !$draft) {
+            abort(404);
+        }
+        $formData = [];
+        $formData['subject'] = '';
+        $formData['content'] = '';
+        $formData['category_id'] = 0;
+        if($draft){
+            $draft->form_data = json_decode($draft->form_data,true);
+            $formData['subject'] = $draft->subject;
+            $formData['content'] = $draft->editor_content;
+            $formData['category_id'] = $draft->form_data['category_id'];
+        }
+        return view("theme::article.create")->with(compact('formData'));
+    }
+
+    /**
+     * 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($request->user()->status === 0){
+            return $this->error(route('website.index'),'操作失败!您的邮箱还未验证,验证后才能进行该操作!');
+        }
+
+        /*防灌水检查*/
+        if( Setting()->get('article_limit_num') > 0 ){
+            $questionCount = $this->counter('article_num_'. $loginUser->id);
+            if( $questionCount > Setting()->get('article_limit_num')){
+                return $this->showErrorMsg(route('website.index'),'你已超过每小时文章发表限制数'.Setting()->get('article_limit_num').',请稍后再进行该操作,如有疑问请联系管理员!');
+            }
+        }
+
+        $request->flash();
+
+        /*如果开启验证码则需要输入验证码*/
+        if( Setting()->get('code_create_article') ){
+            $captchaService->setValidateRules('code_create_article',$this->validateRules);
+        }
+
+        $this->validate($request,$this->validateRules);
+
+        $data = [
+            'user_id'      => $loginUser->id,
+            'category_id'      => intval($request->input('category_id',0)),
+            'title'        => trim($request->input('title')),
+            'content'  => clean($request->input('content')),
+            'summary'  => $request->input('summary'),
+            'status'       => 1,
+        ];
+
+        if($request->hasFile('logo')){
+            $validateRules = [
+                'logo' => 'required|image|max:'.config('tipask.upload.image_size'),
+            ];
+            $this->validate($request,$validateRules);
+            $file = $request->file('logo');
+            $extension = $file->getClientOriginalExtension();
+            $filePath = 'articles/'.gmdate("Y")."/".gmdate("m")."/".uniqid(str_random(8)).'.'.$extension;
+            Storage::disk('local')->put($filePath,File::get($file));
+            $data['logo'] = str_replace("/","-",$filePath);
+        }
+
+
+        $article = Article::create($data);
+
+        /*判断问题是否添加成功*/
+        if($article){
+
+            /*添加标签*/
+            $tagString = trim($request->input('tags'));
+            Tag::multiSave($tagString,$article);
+
+            //记录动态
+            $this->doing($article->user_id,'create_article',get_class($article),$article->id,$article->title,$article->summery);
+
+            /*用户提问数+1*/
+            $loginUser->userData()->increment('articles');
+
+            UserTag::multiIncrement($loginUser->id,$article->tags()->get(),'articles');
+
+            if($article->status === 1 ){
+                $this->credit($request->user()->id,'create_article',Setting()->get('coins_write_article'),Setting()->get('credits_write_article'),$article->id,$article->title);
+                $message = '文章发布成功! '.get_credit_message(Setting()->get('credits_write_article'),Setting()->get('coins_write_article'));
+            }else{
+                $message = '文章发布成功!为了确保文章的质量,我们会对您发布的文章进行审核。请耐心等待......';
+            }
+
+            $this->counter( 'article_num_'. $article->user_id , 1 , 60 );
+
+
+            return $this->success(route('blog.article.detail',['id'=>$article->id]),$message);
+
+
+        }
+
+        return  $this->error("文章发布失败,请稍后再试",route('website.index'));
+
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id,Request $request)
+    {
+        $article = Article::findOrFail($id);
+
+        /*待审核文章游客不可见,管理员和内容作者可见*/
+        if($article->status == 0){
+            if(Auth()->guest()){
+                abort(404);
+            }
+            $this->authorize('create',$article);
+        }
+        /*问题查看数+1*/
+        $article->increment('views');
+
+        $topUsers = Cache::remember('article_top_article_users',10,function() {
+            return  UserData::top('articles',8);
+        });
+
+        /*相关问题*/
+        $relatedQuestions = Question::correlations($article->tags()->pluck('tag_id'));
+
+        /*相关文章*/
+        $relatedArticles = Article::correlations($article->tags()->pluck('tag_id'));
+
+        /*设置通知为已读*/
+        if($request->user()){
+            $this->readNotifications($article->id,'article');
+        }
+
+        return view("theme::article.show")->with('article',$article)
+                                          ->with('topUsers',$topUsers)
+                                          ->with('relatedQuestions',$relatedQuestions)
+                                          ->with('relatedArticles',$relatedArticles);
+    }
+
+    /**
+     * 显示文字编辑页面
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id,Request $request)
+    {
+        $article = Article::find($id);
+
+        if(!$article){
+            abort(404);
+        }
+
+        /*编辑权限控制*/
+        $this->authorize('update', $article);
+
+        /*编辑问题时效控制*/
+        if(!Gate::allows('updateInTime',$article)){
+            return $this->showErrorMsg(route('website.index'),'你已超过文章可编辑的最大时长,不能进行编辑了。如有疑问请联系管理员!');
+        }
+        return view("theme::article.edit")->with(compact('article'));
+
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request,CaptchaService $captchaService)
+    {
+        $article_id = $request->input('id');
+        $article = Article::find($article_id);
+        if(!$article){
+            abort(404);
+        }
+
+        $this->authorize('update', $article);
+
+        $request->flash();
+
+        /*如果开启验证码则需要输入验证码*/
+        if( Setting()->get('code_create_article') ){
+            $captchaService->setValidateRules('code_create_article', $this->validateRules);
+        }
+
+
+        $this->validate($request,$this->validateRules);
+
+        $article->title = trim($request->input('title'));
+        $article->content = clean($request->input('content'));
+        $article->summary = $request->input('summary');
+        $article->category_id = $request->input('category_id',0);
+
+        if($request->hasFile('logo')){
+            $validateRules = [
+                'logo' => 'required|image|max:'.config('tipask.upload.image_size'),
+            ];
+            $this->validate($request,$validateRules);
+            $file = $request->file('logo');
+            $extension = $file->getClientOriginalExtension();
+            $filePath = 'articles/'.gmdate("Y")."/".gmdate("m")."/".uniqid(str_random(8)).'.'.$extension;
+            Storage::disk('local')->put($filePath,File::get($file));
+            $article->logo = str_replace("/","-",$filePath);
+        }
+
+
+        $article->save();
+        $tagString = trim($request->input('tags'));
+
+        /*更新标签*/
+        Tag::multiSave($tagString,$article);
+
+        return $this->success(route('blog.article.detail',['id'=>$article->id]),"文章编辑成功");
+
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 190 - 0
app/Http/Controllers/Controller.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Credit;
+use App\Models\Doing;
+use App\Models\Notification;
+use App\Models\User;
+use App\Models\UserData;
+use App\Services\CreditService;
+use App\Services\DoingService;
+use App\Services\NotificationService;
+use Carbon\Carbon;
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Routing\Controller as BaseController;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Facades\Session;
+use Larastarscn\AliDaYu\Facades\AliDaYu;
+
+abstract class Controller extends BaseController
+{
+    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
+
+    /**
+     * 操作成功提示
+     * @param $url string
+     * @param $message 消息内容
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+     */
+    protected function success($url,$message,$intended=false)
+    {
+        Session::flash('message',$message);
+        Session::flash('message_type',2);
+        if($intended){
+            return redirect()->intended($url);
+        }
+        return redirect($url);
+    }
+
+
+    protected function error($url,$message)
+    {
+        Session::flash('message',$message);
+        Session::flash('message_type',1);
+        return redirect($url);
+    }
+
+
+    protected function showErrorMsg($url , $message){
+        return view('errors.error')->with(compact('url','message'));
+    }
+
+    /**
+     * 成功回调
+     * @param $message
+     */
+    protected function ajaxSuccess($message){
+        $data = array(
+            'code' => 0,
+            'message' => $message
+        );
+        return response()->json($data);
+    }
+
+
+    /**
+     * 错误处理
+     * @param $code
+     * @param $message
+     */
+    protected function ajaxError($code,$message){
+        $data = array(
+            'code' => $code,
+            'message' => $message
+        );
+        return response()->json($data);
+    }
+
+
+    /**
+     * 修改用户积分
+     * @param $user_id 用户id
+     * @param $action  执行动作:提问、回答、发起文章
+     * @param int $source_id 源:问题id、回答id、文章id等
+     * @param string $source_subject 源主题:问题标题、文章标题等
+     * @param int $coins      金币数/财富值
+     * @param int $credits    经验值
+     * @return bool           操作成功返回true 否则  false
+     */
+    protected function credit($user_id,$action,$coins = 0,$credits = 0,$source_id = 0 ,$source_subject = null,$through=false)
+    {
+        return CreditService::create($user_id,$action,$coins,$credits,$source_id,$source_subject,$through);
+    }
+
+    /**
+     * 记录用户动态
+     * @param $user_id 动态发起人
+     * @param $action  动作 ['ask','answer',...]
+     * @param $source_id 问题或文章ID
+     * @param $subject   问题或文章标题
+     * @param string $content 回答或评论内容
+     * @param int $refer_id  问题或者文章ID
+     * @param int $refer_user_id 引用内容作者ID
+     * @param null $refer_content 引用内容
+     * @return static
+     */
+    protected function doing($user_id,$action,$source_type,$source_id,$subject,$content='',$refer_id=0,$refer_user_id=0,$refer_content=null)
+    {
+        return DoingService::create($user_id,$action,$source_type,$source_id,$subject,$content,$refer_id,$refer_user_id,$refer_content);
+    }
+
+
+    /**
+     * 发送用户通知
+     * @param $from_user_id
+     * @param $to_user_id
+     * @param $type
+     * @param $subject
+     * @param $source_id
+     * @return static
+     */
+    protected function notify($from_user_id,$to_user_id,$type,$subject='',$source_id=0,$content='',$refer_type='',$refer_id=0)
+    {
+
+       $notificationService = new NotificationService();
+
+       return $notificationService->notify($from_user_id,$to_user_id,$type,$subject,$source_id,$content,$refer_type,$refer_id);
+
+    }
+
+
+    /**
+     * 将通知设置为已读
+     * @param $source_id
+     * @param string $refer_type
+     * @return mixed
+     */
+    protected function readNotifications($source_id,$refer_type='question')
+    {
+        $types = [];
+        if($refer_type=='article'){
+            $types = ['comment_article'];
+        }else if($refer_type=='question'){
+            $types = ['answer','follow_question','comment_question','invite_answer','adopt_answer'];
+        }else if($refer_type=='answer'){
+            $types = ['comment_answer'];
+        }else if($refer_type == 'user'){
+            $types = ['follow_user'];
+        }
+        $types[] = 'reply_comment';
+        return Notification::where('to_user_id','=',Auth()->user()->id)->where('source_id','=',$source_id)->whereIn('type',$types)->where('is_read','=',0)->update(['is_read'=>1]);
+    }
+
+
+    /*邮件发送*/
+    protected function sendEmail($email,$subject,$message){
+
+        return NotificationService::sendEmail($email,$subject,$message);
+
+    }
+
+
+    /**
+     * 业务层计数器
+     * @param $key 计数器key
+     * @param null $step 级数步子
+     * @param int $expiration 有效期
+     * @return Int count
+     */
+    protected function counter($key,$step=null,$expiration=1440){
+
+        $count = Cache::get($key,0);
+        /*直接获取值*/
+        if( $step === null ){
+            return $count;
+        }
+
+        $count = $count + $step;
+
+        Cache::put($key,$count,$expiration);
+
+        return $count;
+    }
+
+}

+ 83 - 0
app/Http/Controllers/ImageController.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\User;
+use App\Http\Requests;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Validator;
+use Intervention\Image\Facades\Image;
+
+class ImageController extends Controller
+{
+
+    /**
+     * 显示用户头像
+     * @param $avatar_name
+     * @return mixed
+     */
+    public function avatar($avatar_name)
+    {
+        list($user_id,$size) = explode('_',str_replace(".jpg",'',$avatar_name));
+        $avatarFile = storage_path('app/'.User::getAvatarPath($user_id,$size));
+        if(!is_file($avatarFile)){
+            $avatarFile = public_path('static/images/default_avatar.jpg');
+        }
+        $image =   Image::make($avatarFile);
+        $response = response()->make($image->encode('jpg'));
+        $image->destroy();
+        $response->header('Content-Type', 'image/jpeg');
+        $response->header('Expires',  date(DATE_RFC822,strtotime(" 2 day")));
+        $response->header('Cache-Control', 'private, max-age=86400, pre-check=86400');
+        return $response;
+    }
+
+
+
+    public function show($image_name)
+    {
+        $imageFile = storage_path('app/'.str_replace("-","/",$image_name));
+        if(!is_file($imageFile)){
+            abort(404);
+        }
+
+
+        $image =   Image::make($imageFile);
+
+        if(config('tipask.upload.open_watermark') && $image_name != config('tipask.upload.watermark_image') && str_contains($image_name,'attachments')){
+            $watermarkImage = storage_path('app/'.str_replace("-","/",config('tipask.upload.watermark_image')));
+            $image->insert($watermarkImage, 'bottom-right', 15, 10);
+        }
+        $response = response()->make($image->encode('jpg'));
+        $response->header('Content-Type', 'image/jpeg');
+        $response->header('Expires',  date(DATE_RFC822,strtotime(" 7 day")));
+        $response->header('Cache-Control', 'private, max-age=259200, pre-check=259200');
+        return $response;
+    }
+
+
+
+    /*编辑器图片上传*/
+    public function upload(Request $request)
+    {
+        $validateRules = [
+            'file' => 'required|image|max:'.config('tipask.upload.attach_size'),
+        ];
+
+        if($request->hasFile('file')){
+            $validator = Validator::make($request->all(),$validateRules);
+            if ($validator->fails()) {
+                return response('error');
+            }
+            $file = $request->file('file');
+            $extension = $file->getClientOriginalExtension()?$file->getClientOriginalExtension():'jpg';
+            $filePath = 'attachments/'.gmdate("Y")."/".gmdate("m")."/".uniqid(str_random(8)).'.'.$extension;
+            Storage::disk('local')->put($filePath,File::get($file));
+            return response(route("website.image.show",['image_name'=>str_replace("/","-",$filePath)]));
+        }
+        return response('error');
+    }
+
+}

+ 257 - 0
app/Http/Controllers/IndexController.php

@@ -0,0 +1,257 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Jobs\SendEmail;
+use App\Models\Article;
+use App\Models\Authentication;
+use App\Models\Category;
+use App\Models\Exchange;
+use App\Models\FriendshipLink;
+use App\Models\Goods;
+use App\Models\Notice;
+use App\Models\Question;
+use App\Models\Recommendation;
+use App\Models\Tag;
+use App\Models\Taggable;
+use App\Models\User;
+use App\Models\UserData;
+use App\Services\MailService;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+
+class IndexController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return Response
+     */
+    public function index()
+    {
+        /*热门话题*/
+        $hotTags =  Taggable::globalHotTags();
+
+        /*推荐内容*/
+        $recommendItems= Cache::remember('recommend_items',Setting()->get('website_cache_time',1),function() {
+            return Recommendation::where('status','>',0)->orderBy('sort','asc')->orderBy('updated_at','desc')->take(11)->get();
+        });
+
+        /*热门专家*/
+        $hotExperts = Cache::remember('index_hot_experts',Setting()->get('website_cache_time',1),function(){
+            return  Authentication::hottest(8);
+        });
+        $hotUsers = Cache::remember('index_hot_users',30,function() {
+            return  UserData::hottest(20);
+        });
+
+        /*热门问题*/
+        $newestQuestions = Cache::remember('index_newest_questions',Setting()->get('website_cache_time',1),function() {
+            return  Question::newest(0,12);
+        });
+
+        /*悬赏问题*/
+        $rewardQuestions = Cache::remember('index_reward_questions',Setting()->get('website_cache_time',1),function() {
+            return  Question::reward(0,12);
+        });
+
+        /*热门文章*/
+        $hotArticles = Cache::remember('index_hot_articles',Setting()->get('website_cache_time',1),function() {
+            return  Article::hottest(0,12);
+        });
+
+        /*最新文章*/
+        $newestArticles = Cache::remember('index_newest_articles',Setting()->get('website_cache_time',1),function() {
+            return  Article::newest(0,12);
+        });
+
+        /*最新公告*/
+        $newestNotices = Cache::remember('newest_notices',Setting()->get('website_cache_time',1),function() {
+            return  Notice::where('status','>','0')->orderBy('updated_at','DESC')->take(6)->get();
+        });
+
+        /*财富榜*/
+        $topCoinUsers = Cache::remember('index_top_coin_users',Setting()->get('website_cache_time',1),function() {
+            return  UserData::top('coins',8);
+        });
+
+        /*友情链接*/
+        $friendshipLinks = Cache::remember('friendship_links',Setting()->get('website_cache_time',1),function() {
+            return  FriendshipLink::where('status','=',1)->orderBy('sort','asc')->orderBy('created_at','asc')->take(50)->get();
+        });
+        $signDate = Carbon::today();
+        return view('theme::home.index')->with(compact('hotUsers','recommendItems','hotExperts','newestQuestions','rewardQuestions','hotArticles','newestArticles','newestNotices','hotTags','topCoinUsers','friendshipLinks','signDate'));
+
+    }
+
+
+    /*问答模块*/
+    public function ask($categorySlug='all',$filter='newest')
+    {
+
+        $question = new Question();
+
+        if(!method_exists($question,$filter)){
+            abort(404);
+        }
+
+        $parentId=$currentCategoryId = 0;
+        $currentCategory = null;
+        if( $categorySlug != 'all' ){
+            $category = Category::where("slug","=",$categorySlug)->first();
+            if(!$category){
+                abort(404);
+            }
+            $currentCategoryId = $category->id;
+            $parentId = $category->parent_id;
+            $currentCategory = $category;
+            if($category->hasChild()){
+                $parentId = $category->id;
+            }
+        }
+
+        $questions =  call_user_func([$question,$filter] , $currentCategoryId );
+
+        /*热门话题*/
+        $hotTags =  Taggable::globalHotTags('questions');
+
+        $categories = load_categories('questions');
+        $parentCategories = Category::getParentCategories($parentId);
+        $hotUsers = Cache::remember('ask_hot_users',Setting()->get('website_cache_time',1),function() {
+            return  UserData::activities(8);
+        });
+        return view('theme::home.ask')->with(compact('currentCategory','questions','hotUsers','hotTags','filter','categories','currentCategoryId','parentId','parentCategories','categorySlug'));
+    }
+
+
+    public function blog($categorySlug='all', $filter='newest')
+    {
+        $article = new Article();
+        if(!method_exists($article,$filter)){
+            abort(404);
+        }
+        $parentId=$currentCategoryId = 0;
+        $currentCategory = null;
+        if( $categorySlug != 'all' ){
+            $category = Category::where("slug","=",$categorySlug)->first();
+            if(!$category){
+                abort(404);
+            }
+            $currentCategoryId = $category->id;
+            $parentId = $category->parent_id;
+            $currentCategory = $category;
+            if($category->hasChild()){
+                $parentId = $category->id;
+            }
+        }
+
+        $articles = call_user_func([$article,$filter],$currentCategoryId);
+
+        /*热门文章*/
+        $hotArticles = Cache::remember('hot_articles',Setting()->get('website_cache_time',1),function() {
+            return  Article::recommended(0,8);
+        });
+
+        $hotUsers = UserData::activeInArticles();
+        /*热门话题*/
+        $hotTags =  Taggable::globalHotTags('articles');
+        $tabData = get_category_tab_data("articles",7);
+        $parentCategories = Category::getParentCategories($parentId);
+        return view('theme::home.blog')->with(compact('tabData','currentCategory','articles','hotUsers','hotTags','filter','currentCategoryId','parentId','parentCategories','categorySlug','hotArticles'));
+    }
+
+    public function topic( $categorySlug='all')
+    {
+
+        $parentId=$currentCategoryId = 0;
+        $query = Tag::query();
+        if( $categorySlug != 'all' ){
+            $category = Category::where("slug","=",$categorySlug)->first();
+            if(!$category){
+                abort(404);
+            }
+            $currentCategoryId = $category->id;
+            $parentId = $category->parent_id;
+            if($category->hasChild()){
+                $parentId = $category->id;
+            }
+            $query->whereIn('category_id',$category->getSubIds());
+        }
+
+        $categories = load_categories('tags');
+        $parentCategories = Category::getParentCategories($parentId);
+        $topics = $query->orderBy('followers','DESC')->paginate(20);
+        return view('theme::home.topic')->with(compact('topics','categories','currentCategoryId','categorySlug','currentCategoryId','parentId','parentCategories'));
+    }
+
+
+    public function user()
+    {
+        $hotUsers = Cache::remember('index_user_hot_users',30,function() {
+            return  UserData::hottest(50);
+        });
+        $newUsers =  Cache::remember('index_new_users',10,function() {
+            return  User::where("status",">",0)->orderBy("created_at","desc")->take(50)->get();
+        });
+        return view('theme::home.user')->with(compact('hotUsers','newUsers'));
+    }
+
+    public function experts(Request $request,$categorySlug='all',$provinceId='all'){
+        $categories = load_categories('experts');
+        $hotProvinces = Cache::remember('hot_expert_cities',Setting()->get('website_cache_time',1),function() {
+            return  Authentication::select('province', DB::raw('COUNT(user_id) as total'))->groupBy('province')->orderBy('total','desc')->get();
+        });
+        $query = Authentication::leftJoin('user_data', 'user_data.user_id', '=', 'authentications.user_id')->where('user_data.authentication_status','=',1);
+        $categoryId = 0;
+        if( $categorySlug != 'all' ){
+            $category = Category::where("slug","=",$categorySlug)->first();
+            if($category){
+                $categoryId = $category->id;
+                $query->where("authentications.category_id","=",$categoryId);
+            }
+        }
+
+        if($provinceId != 'all'){
+            $query->where("authentications.province","=",$provinceId);
+        }
+
+        $word = $request->input('word','');
+        if($word){
+            $query->where("authentications.real_name",'like',"$word%");
+        }
+        $experts = $query->orderBy('user_data.answers','DESC')
+                        ->orderBy('user_data.articles','DESC')
+                        ->orderBy('authentications.updated_at','DESC')
+                        ->select('authentications.user_id','authentications.real_name','authentications.description','authentications.title','user_data.coins','user_data.credits','user_data.followers','user_data.supports','user_data.answers','user_data.articles','user_data.authentication_status')
+                        ->paginate(16);
+        return view('theme::home.expert')->with(compact('experts','categories','hotProvinces','categorySlug','categoryId','provinceId','word'));
+    }
+
+    public function shop($categorySlug='all')
+    {
+        $parentId=$currentCategoryId = 0;
+        $query = Goods::query();
+        if( $categorySlug != 'all' ){
+            $category = Category::where("slug","=",$categorySlug)->first();
+            if(!$category){
+                abort(404);
+            }
+            $currentCategoryId = $category->id;
+            $parentId = $category->parent_id;
+            if($category->hasChild()){
+                $parentId = $category->id;
+            }
+            $query->whereIn('category_id',$category->getSubIds());
+        }
+
+        $categories = load_categories('goods');
+        $parentCategories = Category::getParentCategories($parentId);
+        $goods = $query->where('status','>',0)->where('remnants','>',0)->orderBy('coins','asc')->paginate(16);
+        $exchanges = Exchange::newest();
+        return view('theme::home.shop')->with(compact('goods','exchanges','categories','currentCategoryId','categorySlug','currentCategoryId','parentId','parentCategories'));
+    }
+    
+}

+ 211 - 0
app/Http/Controllers/Installer/InstallerController.php

@@ -0,0 +1,211 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 16/6/6
+ * Time: 下午5:40
+ */
+
+namespace App\Http\Controllers\Installer;
+
+use App\Repositories\UserRepository;
+use Exception;
+use App\Models\Setting;
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Artisan;
+use Illuminate\Support\Str;
+
+class InstallerController extends Controller
+{
+
+    /*欢迎使用*/
+    public function welcome(){
+        return view("installer.welcome");
+    }
+
+    public function requirement(){
+        $requirements = $this->checkRequirements();
+        $folders = $this->checkPermissions();
+        $result = true;
+        if(isset($requirements['errors']) || isset($folders['errors'])){
+            $result = false;
+        }
+        return view("installer.environment")->with(compact('requirements','folders','result'));
+    }
+
+
+    public function config(Request $request){
+
+        if($request->isMethod('post')){
+            $validateRules = [
+                'database_host' => 'required|max:128',
+                'database_port' => 'required|digits_between:0,65535',
+                'database_username' => 'required|max:128',
+                'database_password' => 'sometimes|max:128',
+                'database_name' => 'required|max:128',
+                'database_prefix' => 'required|max:64',
+            ];
+            $request->flash();
+            $this->validate($request,$validateRules);
+            $envData = [
+                'APP_NAME'=>str_random(12),
+                'APP_ENV'=>'local',
+                'APP_DEBUG'=>'false',
+                'APP_KEY'=>str_random(32),
+                'DB_CONNECTION'=>$request->input('database_driver'),
+                'DB_HOST'=>$request->input('database_host'),
+                'DB_PORT'=>$request->input('database_port'),
+                'DB_DATABASE'=>$request->input('database_name'),
+                'DB_USERNAME'=>$request->input('database_username'),
+                'DB_PASSWORD'=>$request->input('database_password'),
+                'DB_PREFIX'=>$request->input('database_prefix'),
+            ];
+
+            /*写入配置文件*/
+            $env_path = base_path('.env');
+
+            if (!file_exists($env_path)){
+                if(!touch($env_path)){
+                    return $this->error(route('website.installer.config'),'配置文件创建失败,请在网站根目录创建名称 .env 空文件文件并添加读写权限!');
+                }
+            }
+
+            $env_content = '';
+
+            foreach($envData as $key => $value ){
+                $env_content .= $key.'='.$value."\n";
+            }
+
+            try {
+                file_put_contents($env_path,$env_content);
+            }catch(Exception $e) {
+                return $this->error(route('website.installer.config'),'配置文件写入失败,请将网站根目录创建名称为 .env 的文件添加读写权限!');
+            }
+
+
+            return redirect()->route('website.installer.initDB');
+        }
+
+        return view('installer.config');
+    }
+
+
+    public function initDB(){
+        set_time_limit(0);//不限制执行时间
+        /*创建表结构*/
+        try{
+            Artisan::call('migrate');
+        }catch(Exception $e) {
+            return $this->error(route('website.installer.config'),'数据库连接出错:'.$e->getMessage());
+        }
+
+        /*导入数据*/
+        try{
+            Artisan::call('db:seed');
+        }catch(Exception $e){
+            return $this->error(route('website.installer.config'),'数据插入失败:'.$e->getMessage());
+        }
+
+        return redirect()->route('website.installer.website');
+
+    }
+
+    public function website(Request $request,UserRepository $userRepository)
+    {
+
+        if($request->isMethod('post')){
+            $validateRules = [
+                'website_name' => 'required|max:256',
+                'website_url' => 'required|max:128',
+                'website_admin_name' => 'required|max:128',
+                'website_admin_email' => 'required|email',
+                'website_admin_pass' => 'required|min:6|max:32',
+            ];
+
+            $request->flash();
+            $this->validate($request,$validateRules);
+
+            $registerData = [
+                'name' => $request->input('website_admin_name'),
+                'email' => $request->input('website_admin_email'),
+                'password' => $request->input('website_admin_pass'),
+                'status' => 1,
+                'visit_ip' => $request->getClientIp(),
+            ];
+
+            Setting::set('website_name',$request->input('website_name'));
+            Setting::set('website_url',$request->input('website_url'));
+            Setting::set('website_admin_email',$request->input('website_admin_email'));
+            $envParams = [];
+            $envParams['APP_URL'] = $request->input('website_url','');
+            $envParams['WEBSITE_ADMIN_EMAIL'] = $request->input('website_admin_email','');
+            Setting()->setEnvParams($envParams);
+            $admin =  $userRepository->register($registerData);
+            $admin->attachRole(1);
+            return redirect()->route('website.installer.finished');
+        }
+
+        return view('installer.website');
+
+    }
+
+    public function finished(){
+        file_put_contents(storage_path('installed'), '');
+        return view('installer.finished');
+    }
+
+
+
+    private function checkRequirements(){
+        $requirements = config('installer.requirements');
+        $results = [];
+        foreach($requirements as $requirement)
+        {
+            $results['requirements'][$requirement] = true;
+
+            if(!extension_loaded($requirement))
+            {
+                $results['requirements'][$requirement] = false;
+
+                $results['errors'] = true;
+            }
+        }
+
+        return $results;
+    }
+
+    private function checkPermissions(){
+        $folders = config('installer.permissions');
+        $results = [];
+
+        foreach($folders as $folder => $permission)
+        {
+            $results['folders'][$folder]['status'] = true;
+            $results['folders'][$folder]['permission'] = $this->getPermission($folder);
+
+            if(!($results['folders'][$folder]['permission'] >= $permission))
+            {
+                $results['folders'][$folder]['status'] = false;
+                $results['errors'] = true;
+            }
+        }
+
+        return $results;
+    }
+
+
+    /**
+     * Get a folder permission.
+     *
+     * @param $folder
+     * @return string
+     */
+    private function getPermission($folder)
+    {
+        return substr(sprintf('%o', fileperms(base_path($folder))), -4);
+    }
+
+
+
+}

+ 24 - 0
app/Http/Controllers/Shop/ExchangeController.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Http\Controllers\Shop;
+
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+
+class ExchangeController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $exchanges = Auth()->user()->exchanges()->paginate(20);
+        return view('theme::goods.exchanges')->with(compact('exchanges'));
+    }
+
+
+}

+ 86 - 0
app/Http/Controllers/Shop/GoodsController.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace App\Http\Controllers\Shop;
+
+use App\Models\Exchange;
+use App\Models\Goods;
+use Illuminate\Http\Request;
+
+use App\Http\Requests;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Validator;
+
+class GoodsController extends Controller
+{
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        $goods = Goods::find($id);
+        return view('theme::goods.show')->with('goods',$goods);
+    }
+
+
+    /*兑换礼品*/
+    public function exchange(Request $request)
+    {
+        //$goods = Goods::find($goods_id);
+
+
+        $validator = Validator::make($request->all(), [
+            'goods_id' => 'required|integer',
+            'real_name' => 'required|max:32',
+            'phone' => 'required|regex:/^1[3456789]{1}\d{9}$/',
+            'email' => 'required|email|max:64',
+            'comment' => 'max:512'
+        ]);
+
+        if ($validator->fails()) {
+            return response()->json(['result'=>$validator->messages()], 200);
+        }
+
+        $goods = Goods::find($request->input('goods_id'));
+
+        if(!$goods){
+            return response()->json(['result'=>['common'=>['商品不存在,请核实!']]], 200);
+        }
+
+        if($goods->remnants <= 0){
+            return response()->json(['result'=>['common'=>['商品库存不足,请选择其他商品进行兑换!']]], 200);
+        }
+
+        if($request->user()->userData->coins < $goods->coins ){
+            return response()->json(['result'=>['common'=>['抱歉!您的金币不足!']]], 200);
+        }
+
+        if ($validator->fails()) {
+            return response()->json(['result'=>$validator->messages()], 200);
+        }
+
+        $this->credit($request->user()->id,'exchange',-$goods->coins,0,$goods->id,$goods->name);
+
+        try{
+            $goods->decrement('remnants');
+            $goods->save();
+            $data = $request->all();
+            $data['user_id'] = $request->user()->id;
+            $data['coins'] = $goods->coins;
+            $data['status'] = 0;
+            Exchange::create($data);
+        }catch(\Exception $e){
+            return response()->json(['result'=>['common'=>['数据库操作失败,请稍后再试!']]], 200);
+        }
+
+        return response()->json(['result'=>'ok','data'=>$data],200);
+
+
+
+    }
+
+}

+ 68 - 0
app/Http/Controllers/SiteMapController.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sdf_sky
+ * Date: 2017/3/8
+ * Time: 下午1:23
+ */
+
+namespace App\Http\Controllers;
+
+
+use App\Models\Article;
+use App\Models\Question;
+use App\Models\Tag;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\URL;
+
+class SiteMapController extends Controller
+{
+
+    public function index(){
+        $sitemap = App::make('sitemap');
+        $sitemap->setCache('tipask.sitemap', 30);
+        if (!$sitemap->isCached()) {
+            /*静态链接*/
+            $sitemap->add(URL::to(route('website.index')), null, '1.0', 'daily');
+            $sitemap->add(URL::to(route('website.ask')), null, '1.0', 'daily');
+            $sitemap->add(URL::to(route('website.topic')), null, '1.0', 'daily');
+            $sitemap->add(URL::to(route('website.blog')), null, '1.0', 'daily');
+            $sitemap->add(URL::to(route('website.user')), null, '0.9', 'daily');
+            $sitemap->add(URL::to(route('website.shop')), null, '0.7', 'weekly');
+            $sitemap->add(URL::to(route('website.experts')), null, '0.8', 'weekly');
+            $sitemap->add(URL::to(route('auth.top.coins')), null, '0.7', 'weekly');
+            $sitemap->add(URL::to(route('auth.top.answers')), null, '0.7', 'weekly');
+            $sitemap->add(URL::to(route('auth.top.articles')), null, '0.7', 'weekly');
+
+            $startTime = Carbon::now()->subMonth(12);
+            /*问题*/
+            $questions = Question::where("status", ">", 0)->where('created_at', '>', $startTime)->orderBy('created_at', 'desc')->take(2000)->get();
+            foreach ($questions as $question) {
+                $sitemap->add(URL::to(route('ask.question.detail', ['id' => $question->id])), $question->created_at, '0.9', 'daily');
+            }
+
+            /*文章*/
+            $articles = Article::where("status", ">", 0)->where('created_at', '>', $startTime)->orderBy('created_at', 'desc')->take(1200)->get();
+            foreach ($articles as $article) {
+                $sitemap->add(URL::to(route('blog.article.detail', ['id' => $article->id])), $article->created_at, '0.9', 'daily');
+            }
+
+            /*话题*/
+            $topics = Tag::where('created_at', '>', $startTime)->orderBy('created_at', 'desc')->take(500)->get();
+            foreach ($topics as $topic) {
+                $sitemap->add(URL::to(route('ask.tag.index', ['id' => $topic->id])), $topic->created_at, '0.8', 'daily');
+            }
+
+            /*用户*/
+            $users = User::where("status", ">", 0)->where('created_at', '>', $startTime)->orderBy('created_at', 'desc')->take(500)->get();
+            foreach ($users as $user) {
+                $sitemap->add(URL::to(route('auth.space.index', ['id' => $user->id])), $user->created_at, '0.8', 'daily');
+            }
+        }
+
+        return $sitemap->render('xml');
+    }
+
+}

+ 70 - 0
app/Http/Kernel.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Http;
+
+use Illuminate\Foundation\Http\Kernel as HttpKernel;
+
+class Kernel extends HttpKernel
+{
+    /**
+     * The application's global HTTP middleware stack.
+     *
+     * These middleware are run during every request to your application.
+     *
+     * @var array
+     */
+    protected $middleware = [
+        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
+        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
+        \App\Http\Middleware\TrimStrings::class,
+        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+        \App\Http\Middleware\TrustProxies::class,
+        \App\Http\Middleware\BanIpCheck::class,
+    ];
+
+    /**
+     * The application's route middleware groups.
+     *
+     * @var array
+     */
+    protected $middlewareGroups = [
+        'web' => [
+            \App\Http\Middleware\EncryptCookies::class,
+            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+            \Illuminate\Session\Middleware\StartSession::class,
+            // \Illuminate\Session\Middleware\AuthenticateSession::class,
+            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+            \App\Http\Middleware\VerifyCsrfToken::class,
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
+
+        'api' => [
+            'throttle:60,1',
+            'bindings',
+        ],
+    ];
+
+    /**
+     * The application's route middleware.
+     *
+     * These middleware may be assigned to groups or used individually.
+     *
+     * @var array
+     */
+    protected $routeMiddleware = [
+        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
+        'auth.admin' => \App\Http\Middleware\AdminAuthenticate::class,
+        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+        'can' => \Illuminate\Auth\Middleware\Authorize::class,
+        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
+        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+        'ban.user' => \App\Http\Middleware\BanUserCheck::class,
+        'ban.ip' => \App\Http\Middleware\BanIpCheck::class,
+        'installer' => \App\Http\Middleware\InstallerCheck::class,
+        'guide.install' => \App\Http\Middleware\RedictToInstall::class,
+        'operation.log' => \App\Http\Middleware\AdminOperationLog::class,
+    ];
+}

+ 40 - 0
app/Http/Middleware/AdminAuthenticate.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class AdminAuthenticate
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+
+        if($request->user()->cannot('admin.login')){
+            abort(403);
+        }
+        $routeName = $request->route()->getName();
+        if(!$request->session()->get('admin.login') &&  $routeName !== 'admin.account.login'){
+            return redirect(route('admin.account.login'));
+        }
+
+        /*超级管理员不受权限策略影响*/
+        if(in_array($routeName,['admin.account.login','admin.account.logout'])){
+            return $next($request);
+        }
+        /*加入权限检测逻辑*/
+        if(!str_contains($routeName,['admin.setting'])){
+            $routeName = substr($routeName,0,strripos($routeName,".")) . '.index';
+        }
+        if(!$request->user()->hasPermission($routeName)){
+            abort(403);
+        }
+        return $next($request);
+    }
+}

+ 37 - 0
app/Http/Middleware/AdminOperationLog.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Models\OperationLog;
+use Closure;
+use Illuminate\Support\Facades\Auth;
+
+class AdminOperationLog
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        $user_id = 0;
+        if (Auth::check()){
+            $user_id = Auth::id();
+        }
+        if ($request->method() != 'GET'){
+            $input = $request->all();
+            $operationLog = new OperationLog();
+            $operationLog->user_id = $user_id;
+            $operationLog->method = $request->method();
+            $operationLog->action = $request->path();
+            $operationLog->data = json_encode($input);
+            $operationLog->ip = $request->ip();
+            $operationLog->created_at = date('Y-m-d H:i:s',time());
+            $operationLog->save();
+        }
+        return $next($request);
+    }
+}

+ 38 - 0
app/Http/Middleware/BanIpCheck.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Models\BanIp;
+use Closure;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Cache;
+
+class BanIpCheck
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  \Closure $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        $ip = $request->getClientIp();
+        $ips = Cache::rememberForever('ip_blacklist', function () {
+            if (!file_exists(storage_path('installed'))) {
+                return [];
+            }
+            return BanIp::all()->pluck('ip')->toArray();
+        });
+
+        if (Auth::check() && !Auth::user()->isSuperAdmin()) {
+            if (in_array($ip, $ips)) {
+                abort('403');
+                return false;
+            }
+        }
+
+        return $next($request);
+    }
+}

+ 42 - 0
app/Http/Middleware/BanUserCheck.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Contracts\Auth\Guard;
+use Closure;
+
+class BanUserCheck
+{
+    /**
+     * The Guard implementation.
+     *
+     * @var Guard
+     */
+    protected $auth;
+
+    /**
+     * Create a new filter instance.
+     *
+     * @param  Guard  $auth
+     * @return void
+     */
+    public function __construct(Guard $auth)
+    {
+        $this->auth = $auth;
+    }
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        if($this->auth->check() && $this->auth->user()->status === -1){
+            abort(403);
+        }
+        return $next($request);
+    }
+}

+ 17 - 0
app/Http/Middleware/EncryptCookies.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
+
+class EncryptCookies extends Middleware
+{
+    /**
+     * The names of the cookies that should not be encrypted.
+     *
+     * @var array
+     */
+    protected $except = [
+        //
+    ];
+}

+ 33 - 0
app/Http/Middleware/InstallerCheck.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class InstallerCheck
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        if($this->alreadyInstalled()) {
+            abort(404);
+        }
+        return $next($request);
+    }
+
+    /**
+     * If application is already installed.
+     *
+     * @return bool
+     */
+    public function alreadyInstalled()
+    {
+        return file_exists(storage_path('installed'));
+    }
+}

+ 23 - 0
app/Http/Middleware/RedictToInstall.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class RedictToInstall
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        if(!file_exists(storage_path('installed'))){
+            return redirect(route('website.installer.welcome'));
+        }
+        return $next($request);
+    }
+}

+ 26 - 0
app/Http/Middleware/RedirectIfAuthenticated.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Support\Facades\Auth;
+
+class RedirectIfAuthenticated
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @param  string|null  $guard
+     * @return mixed
+     */
+    public function handle($request, Closure $next, $guard = null)
+    {
+        if (Auth::guard($guard)->check()) {
+            return redirect('/home');
+        }
+
+        return $next($request);
+    }
+}

+ 18 - 0
app/Http/Middleware/TrimStrings.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
+
+class TrimStrings extends Middleware
+{
+    /**
+     * The names of the attributes that should not be trimmed.
+     *
+     * @var array
+     */
+    protected $except = [
+        'password',
+        'password_confirmation',
+    ];
+}

+ 23 - 0
app/Http/Middleware/TrustProxies.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Http\Request;
+use Fideloper\Proxy\TrustProxies as Middleware;
+
+class TrustProxies extends Middleware
+{
+    /**
+     * The trusted proxies for this application.
+     *
+     * @var array
+     */
+    protected $proxies;
+
+    /**
+     * The headers that should be used to detect proxies.
+     *
+     * @var int
+     */
+    protected $headers = Request::HEADER_X_FORWARDED_ALL;
+}

+ 19 - 0
app/Http/Middleware/VerifyCsrfToken.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
+
+class VerifyCsrfToken extends Middleware
+{
+    /**
+     * The URIs that should be excluded from CSRF verification.
+     *
+     * @var array
+     */
+    protected $except = [
+        'wechat/callback',
+        'alipay/callback',
+        'api/*'
+    ];
+}

+ 41 - 0
app/Http/Requests/Request.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+abstract class Request extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return $this->getRules();
+    }
+
+    public function getRules()
+    {
+        list(, $action) = explode('@', $this->route()->getActionName());
+        $ruleMethodName = 'get' . ucfirst($action) . 'Rules';
+        if(method_exists($this, $ruleMethodName)){
+            $rules = $this->$ruleMethodName();
+            if(is_array($rules)){
+                return $rules;
+            }
+        }
+        return [];
+    }
+}

+ 47 - 0
app/Jobs/SendEmail.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Mail\GlobalMail;
+use Illuminate\Bus\Queueable;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Mail;
+
+class SendEmail implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    protected $email;
+    protected $blade;
+    protected $subject;
+    protected $data;
+    /**
+     * Create a new job instance.
+     *
+     * @return void
+     */
+    public function __construct($email, $subject, $data=[], $blade)
+    {
+        //
+        $this->email = $email;
+        $this->blade = $blade;
+        $this->subject = $subject;
+        $this->data = $data;
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @return void
+     */
+    public function handle()
+    {
+        //
+        Log::info('SendEmail_to_'.$this->email.'_start');
+        Mail::to($this->email)->send(new GlobalMail($this->blade, $this->subject, $this->data));
+    }
+}

+ 35 - 0
app/Listeners/LoginListener.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Listeners;
+
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Support\Facades\Log;
+
+class LoginListener
+{
+    /**
+     * Create the event listener.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        //
+    }
+
+    /**
+     * Handle the event.
+     *
+     * @param  object $event
+     * @return void
+     */
+    public function handle($event)
+    {
+        $role = $event->user->roles()->first();
+        if ($role && $role->slug != 'member' && !session()->has('user_permissions')) {
+            $permissions = $role->permissions()->pluck('slug')->toArray();
+            request()->session()->put('user_permissions', $permissions);
+        }
+    }
+}

+ 27 - 0
app/Listeners/LogoutListener.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Listeners;
+
+class LogoutListener
+{
+    /**
+     * Create the event listener.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        //
+    }
+
+    /**
+     * Handle the event.
+     *
+     * @param  object  $event
+     * @return void
+     */
+    public function handle($event)
+    {
+        request()->session()->forget('user_permissions');
+    }
+}

+ 41 - 0
app/Mail/GlobalMail.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Support\Facades\Log;
+
+class GlobalMail extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    public $blade; // 视图模板
+    public $sub; // 主题
+    public $data; // 参数
+    /**
+     * Create a new message instance.
+     *
+     * @return void
+     */
+    public function __construct($blade, $sub, $data=[])
+    {
+        //
+        $this->blade = $blade;
+        $this->sub = $sub;
+        $this->data = $data;
+    }
+
+    /**
+     * Build the message.
+     *
+     * @return $this
+     */
+    public function build()
+    {
+        Log::info('GlobalMail_'.$this->blade,$this->data);
+        return $this->subject($this->sub)->view('emails.'.$this->blade)->with($this->data);
+    }
+}

+ 52 - 0
app/Models/Answer.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserDataTrait;
+use App\Models\Relations\BelongsToUserTrait;
+use App\Models\Relations\MorphManyCommentsTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Answer extends Model
+{
+    use MorphManyCommentsTrait,BelongsToUserTrait;
+    protected $table = 'answers';
+    protected $fillable = ['question_title','question_id','user_id','device','content','status','created_at','updated_at'];
+
+    public static function boot()
+    {
+        parent::boot();
+
+        /*监听创建*/
+        static::creating(function($answer){
+            /*开启状态检查*/
+            if(Setting()->get('verify_answer')==1){
+                $answer->status = 0;
+            }
+
+        });
+        /*监听删除事件*/
+        static::deleting(function($answer){
+
+            /*问题回答数 -1 */
+            $answer->question()->where('answers','>',0)->decrement('answers');
+
+            /*用户回答数 -1 */
+            $answer->user->userData()->where('answers','>',0)->decrement('answers');
+
+            /*删除动态*/
+            Doing::where('source_type','=',get_class($answer))->where('source_id','=',$answer->id)->delete();
+
+            /*删除回答评论*/
+            Comment::where('source_type','=',get_class($answer))->where('source_id','=',$answer->id)->delete();
+
+        });
+
+    }
+
+
+
+    public function question(){
+        return $this->belongsTo('App\Models\Question');
+    }
+}

+ 70 - 0
app/Models/Area.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+
+class Area extends Model
+{
+    protected $table = 'areas';
+    protected $fillable = ['name', 'parent_id', 'grade'];
+
+
+    /**
+     *从缓存中加载所有数据
+     * @return mixed
+     */
+    public static function loadFromCache(){
+        $areas = Cache::rememberForever('all_area_data', function() {
+            return DB::table('areas')->orderBy('id','ASC')->get();
+        });
+        return $areas;
+    }
+
+    /**
+     * 获取所有省份信息
+     * @return mixed 省份信息
+     */
+    public static function provinces()
+    {
+        $provinces = [];
+        foreach(self::loadFromCache() as $area){
+            if($area->parent_id == 0){
+                array_push($provinces,$area);
+            }
+        }
+        return $provinces;
+    }
+
+    /**
+     * 获取省份所有城市信息
+     * @param $province_id
+     * @return mixed 城市信息
+     */
+    public static function cities($province_id)
+    {
+        $cities = [];
+        foreach(self::loadFromCache() as $area){
+            if($area->parent_id == $province_id){
+                array_push($cities,$area);
+            }
+        }
+        return $cities;
+    }
+
+
+    public static function getName($id)
+    {
+        foreach(self::loadFromCache() as $area){
+            if($area->id == $id){
+               return $area->name;
+            }
+        }
+        return '';
+    }
+
+
+
+}

+ 128 - 0
app/Models/Article.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToCategoryTrait;
+use App\Models\Relations\BelongsToUserTrait;
+use App\Models\Relations\MorphManyCommentsTrait;
+use App\Models\Relations\MorphManyTagsTrait;
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\App;
+
+class Article extends Model
+{
+    use BelongsToUserTrait,MorphManyTagsTrait,MorphManyCommentsTrait,BelongsToCategoryTrait;
+    protected $table = 'articles';
+    protected $fillable = ['title', 'user_id','category_id', 'content','tags','summary','status','logo'];
+
+
+    public static function boot()
+    {
+        parent::boot();
+
+        /*监听创建*/
+        static::creating(function($article){
+            /*开启状态检查*/
+            if(Setting()->get('verify_article')==1){
+                $article->status = 0;
+            }
+            if( trim($article->summary) === '' ){
+                $article->summary = str_limit(strip_tags($article->content),180);
+            }
+
+        });
+
+        static::saved(function($article){
+
+            if(Setting()->get('xunsearch_open',0) == 1){
+                App::offsetGet('search')->update($article);
+            }
+        });
+        /*监听删除事件*/
+        static::deleting(function($article){
+
+            /*用户文章数 -1 */
+            $article->user->userData()->where("articles",">",0)->decrement('articles');
+
+            Collection::where('source_type','=',get_class($article))->where('source_id','=',$article->id)->delete();
+
+            /*删除回答评论*/
+            Comment::where('source_type','=',get_class($article))->where('source_id','=',$article->id)->delete();
+            /*删除动态*/
+            Doing::where('source_type','=',get_class($article))->where('source_id','=',$article->id)->delete();
+
+
+        });
+
+        static::deleted(function($article){
+            if(Setting()->get('xunsearch_open',0) == 1){
+                App::offsetGet('search')->delete($article);
+            }
+        });
+    }
+
+    /*获取相关文章*/
+    public static function correlations($tagIds,$size=6)
+    {
+        $questions = self::whereHas('tags', function($query) use ($tagIds) {
+            $query->whereIn('tag_id', $tagIds);
+        })->orderBy('created_at','DESC')->take($size)->get();
+        return $questions;
+    }
+
+
+    /*搜索*/
+    public static function search($word,$size=16)
+    {
+        $list = self::where('title','like',"$word%")->paginate($size);
+        return $list;
+    }
+
+
+    /*推荐文章*/
+    public static function recommended($categoryId=0 , $pageSize=20)
+    {
+        $query = self::query();
+        $category = Category::findFromCache($categoryId);
+        if( $category ){
+            $query->whereIn('category_id',$category->getSubIds());
+        }
+
+        $list = $query->where('status','>',0)->orderBy('supports','DESC')->orderBy('created_at','DESC')->paginate($pageSize);
+        return $list;
+    }
+
+    /*热门文章*/
+    public static function hottest($categoryId=0 , $pageSize=20)
+    {
+        $query = self::query();
+        $category = Category::findFromCache($categoryId);
+        if( $category ){
+            $query->whereIn('category_id',$category->getSubIds());
+        }
+        if(Setting()->get('hot_content_period',365)){
+            $query->where('created_at', ">" , Carbon::now()->subDays(Setting()->get('hot_content_period',365)));
+        }
+        $list = $query->where('status','>',0)->orderBy('views','DESC')->orderBy('collections','DESC')->orderBy('created_at','DESC')->paginate($pageSize);
+        return $list;
+
+    }
+
+
+    /*最新问题*/
+    public static function newest($categoryId=0 , $pageSize=20)
+    {
+        $query = self::query();
+        $category = Category::findFromCache($categoryId);
+        if( $category ){
+            $query->whereIn('category_id',$category->getSubIds());
+        }
+        $list = $query->where('status','>',0)->orderBy('created_at','DESC')->paginate($pageSize);
+        return $list;
+    }
+
+
+
+
+}

+ 19 - 0
app/Models/Attention.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Attention extends Model
+{
+    protected $table = 'attentions';
+    protected $fillable = ['user_id','source_id','source_type'];
+
+
+    public static function store(){
+
+    }
+
+
+
+}

+ 95 - 0
app/Models/Authentication.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToCategoryTrait;
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Authentication extends Model
+{
+    use BelongsToUserTrait, BelongsToCategoryTrait;
+    protected $table = 'authentications';
+    protected $primaryKey = 'user_id';
+    protected $fillable = [
+        'user_id',
+        'real_name',
+        'province',
+        'city',
+        'gender',
+        'title',
+        'description',
+        'id_card',
+        'id_card_image',
+        'skill',
+        'skill_image',
+        'status',
+        'category_id',
+        'recommend_at'
+    ];
+
+
+    public static function boot()
+    {
+        parent::boot();
+        static::saved(function ($authentication) {
+            if ($authentication->userData) {
+                if ($authentication->status == 1) {
+                    $authentication->userData->authentication_status = 1;
+                } else {
+                    $authentication->userData->authentication_status = 0;
+                }
+                $authentication->userData->save();
+            }
+        });
+        static::deleted(function ($authentication) {
+            UserData::where("user_id","=",$authentication->user_id)->update(['authentication_status'=>0]);
+        });
+    }
+
+    public function userData()
+    {
+        return $this->belongsTo('App\Models\UserData', 'user_id', 'user_id');
+    }
+
+    /*用户统计标签*/
+    public function userTags()
+    {
+        return $this->hasMany('App\Models\UserTag', 'user_id', 'user_id');
+    }
+
+    public function hotTags()
+    {
+        $hotTagIds = $this->userTags()->select("tag_id")->distinct()->orderBy('supports', 'desc')->orderBy('answers',
+            'desc')->orderBy('created_at', 'desc')->take(5)->pluck('tag_id');
+        $tags = [];
+        foreach ($hotTagIds as $hotTagId) {
+            $tag = Tag::find($hotTagId);
+            if ($tag) {
+                $tags[] = $tag;
+            }
+
+        }
+        return $tags;
+    }
+
+    /*推荐行家*/
+    public static function hottest($size)
+    {
+        return self::leftJoin('user_data', 'user_data.user_id', '=', 'authentications.user_id')
+            ->where('authentications.recommend_at', '>', 0)
+            ->where('user_data.authentication_status', '=', 1)
+            ->orderBy('user_data.answers', 'DESC')
+            ->orderBy('user_data.articles', 'DESC')
+            ->orderBy('authentications.recommend_at', 'DESC')
+            ->select('authentications.user_id', 'authentications.real_name', 'authentications.title', 'user_data.coins',
+                'user_data.credits', 'user_data.followers', 'user_data.supports', 'user_data.answers',
+                'user_data.articles', 'user_data.authentication_status')
+            ->take($size)->get();
+    }
+
+    public function getRecommendAtAttribute($value)
+    {
+        return $value > 0 ? 1 : 0;
+    }
+}

+ 14 - 0
app/Models/BanIp.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class BanIp extends Model
+{
+    use BelongsToUserTrait;
+    protected $table = 'ban_ips';
+    protected $fillable = ['user_id','ip','created_at'];
+    public $timestamps = false;
+}

+ 234 - 0
app/Models/Category.php

@@ -0,0 +1,234 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+
+class Category extends Model
+{
+    protected $table = 'categories';
+    protected $fillable = ['parent_id','grade','name','slug','icon','status','sort','type','role_id','category_id'];
+
+
+    public static function boot()
+    {
+        parent::boot();
+
+        /*添加事件监听*/
+        static::creating(function($category){
+
+        });
+
+        /*监听删除事件*/
+        static::deleting(function($category){
+            $category->questions()->update(['category_id'=>0]);
+            $category->articles()->update(['category_id'=>0]);
+            $category->tags()->update(['category_id'=>0]);
+            $category->experts()->update(['category_id'=>0]);
+            Category::destroy($category->getSubIds(false));
+        });
+    }
+
+
+    /**
+     * 获取用户问题
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function questions()
+    {
+        return $this->hasMany('App\Models\Question','category_id');
+    }
+
+
+    /**
+     * 获取用户问题
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function articles()
+    {
+        return $this->hasMany('App\Models\Article','category_id');
+    }
+
+    /**
+     * 获取用户问题
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function tags()
+    {
+        return $this->hasMany('App\Models\Tag','category_id');
+    }
+
+
+    /**
+     * 获取用户问题
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function experts()
+    {
+        return $this->hasMany('App\Models\Authentication','category_id');
+    }
+
+
+    /**
+     * 生成select下拉选择框
+     * @param $categories
+     * @param int $parentId
+     * @param int $depth
+     * @return string
+     */
+    public static function makeOptionTree($categories,$selectId=0, $parentId=0, $depth = 0){
+        $childTree = '';
+        foreach ($categories as $category) {
+            if ( $parentId == $category->parent_id ) {
+                if($category->id == $selectId){
+                    $childTree .= "<option value=\"{$category->id}\" selected>";
+                }else{
+                    $childTree .= "<option value=\"{$category->id}\">";
+                }
+                $depthStr = str_repeat("--", $depth);
+                $childTree .= $depth ? "&nbsp;&nbsp;|{$depthStr}&nbsp;{$category->name}</option>" : "{$category->name}</option>";
+                $childTree .= self::makeOptionTree($categories,$selectId, $category->id, $depth + 1);
+            }
+        }
+        return $childTree;
+    }
+
+
+    public static function getSubCategoryIds($parentId=0){
+        $ids = '';
+        $categories  =  self::loadFromCache('all');
+        foreach ($categories as $category) {
+            if ( $parentId == $category->parent_id ) {
+                $ids .= $category->id.' ';
+                $ids .= self::getSubCategoryIds($category->id);
+            }
+        }
+
+        return $ids;
+
+    }
+
+
+    /*获取分类子分类ID*/
+    public function getSubIds($wrap=true){
+       $ids = self::getSubCategoryIds($this->id);
+       $subIds = trim($ids);
+       if($wrap){
+            $subIds = $this->id.' '.$subIds;
+       }
+       $subCategoryIds =  explode(" ",$subIds);
+       $subCategoryIds =array_filter($subCategoryIds);
+       sort($subCategoryIds);
+       return $subCategoryIds;
+    }
+
+
+    public static function loadFromCache($type='all'){
+
+        $globalCategories = Cache::rememberForever('global_all_categories',function() {
+            return self::where('status','>',0)->orderBy('sort','asc')->orderBy('created_at','asc')->get();
+        });
+
+        /*返回所有分类*/
+        if($type == 'all'){
+            return $globalCategories;
+        }
+
+        /*按类文档型返回分类*/
+        $categories = [];
+        foreach( $globalCategories as $category ){
+            if( str_contains($category->type,$type) ){
+                $categories[] = $category;
+            }
+        }
+        return $categories;
+
+    }
+
+    /*查找摸一个分类信息*/
+    public static function findFromCache($categoryId){
+        $categories = self::loadFromCache('all');
+        foreach($categories as $category){
+            if($categoryId == $category->id){
+                return  $category;
+            }
+        }
+        return false;
+    }
+
+
+    /**生成树状结构
+     * @param $categories
+     * @param int $parentId
+     * @return array
+     */
+    public static function makeTree($categories,$parentId=0){
+        $tree = [];
+        foreach ($categories as $category) {
+            if ( $parentId == $category->parent_id ) {
+                $category->_child = self::makeTree($categories,$category->id);
+                $tree[] = $category;
+            }
+        }
+        return $tree;
+    }
+
+
+
+
+
+    /**
+     * 获取参数的所有父级分类
+     * @param int $cid 分类id
+     * @return array 参数分类和父类的信息集合
+     * @author huajie <banhuajie@163.com>
+     */
+    public  static function getParentCategories($categoryId){
+        if(!$categoryId){
+            return [];
+        }
+        $allCategories  =  self::loadFromCache('all');
+        $category  =   self::findFromCache($categoryId);
+        $parentId    =   $category->parent_id;
+        $parentCategories[]  =   $category;
+        while(true){
+            foreach ($allCategories as $key => $value){
+                if($value->id == $parentId){
+                    $parentId = $value->parent_id;
+                    array_unshift($parentCategories, $value);	//将父分类插入到数组第一个元素前
+                }
+            }
+            if($parentId == 0){
+                break;
+            }
+        }
+        return $parentCategories;
+    }
+
+
+    /*判断是否有子分类*/
+    public function hasChild(){
+        $categories = self::loadFromCache('all');
+        foreach($categories as $category){
+            if($category->parent_id == $this->id){
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /*获取分类下的子分类*/
+    public function getSubChild(){
+        $categories = self::loadFromCache('all');
+        $children = [];
+        foreach($categories as $category){
+            if($category->parent_id == $this->id){
+                $children[] = $category;
+            }
+        }
+        return $children;
+    }
+
+}

+ 12 - 0
app/Models/Collection.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Collection extends Model
+{
+    protected $table = 'collections';
+    protected $fillable = ['user_id','source_id','source_type','subject'];
+
+}

+ 45 - 0
app/Models/Comment.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Comment extends Model
+{
+    use BelongsToUserTrait;
+    protected $table = 'comments';
+    protected $fillable = ['user_id', 'content','source_id','source_type','to_user_id','supports','device','status'];
+
+
+    public static function boot()
+    {
+        parent::boot();
+
+        /*监听创建*/
+        static::creating(function($comment){
+            /*开启状态检查*/
+            if(Setting()->get('verify_comment')==1){
+                $comment->status = 0;
+            }
+        });
+
+        /*监听删除事件*/
+        static::deleting(function($comment){
+            /*问题、回答、文章评论数 -1*/
+            $comment->source()->where("comments",">",0)->decrement('comments');
+        });
+    }
+
+    public function source()
+    {
+        return $this->morphTo();
+    }
+
+
+    public function toUser(){
+        return $this->belongsTo('App\Models\User','to_user_id');
+    }
+
+
+}

+ 14 - 0
app/Models/Credit.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Credit extends Model
+{
+    use BelongsToUserTrait;
+    protected $table = 'credits';
+    protected $fillable = ['user_id', 'action','coins','credits','source_id','source_subject','created_at'];
+    public $timestamps = false;
+}

+ 63 - 0
app/Models/Doing.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+
+class Doing extends Model
+{
+    use BelongsToUserTrait;
+    protected $table = 'doings';
+    protected $fillable = ['user_id', 'action','source_type','source_id','subject','content','refer_id','refer_user_id','refer_content','created_at'];
+    public $timestamps = false;
+
+    public static function concerned(User $user)
+    {
+      $attentions = $user->attentions()->get();
+      $tags = $questions = $users = [];
+
+      foreach($attentions as $attention){
+          if($attention->source_type == 'App\Models\Tag'){
+                $tags[] = $attention->source_id;
+          }elseif($attention->source_type == 'App\Models\User'){
+                $users[] = $attention->source_id;
+          }elseif($attention->source_type == 'App\Models\Question'){
+                $questions[] = $attention->source_id;
+          }
+      }
+
+      /*追加用户标签*/
+      foreach( $user->tags()->get() as $tag ){
+          $tags[] = $tag->id;
+      }
+
+      if($tags){
+            $taggables = DB::table("taggables")->whereIn("tag_id",$tags)->get();
+            foreach($taggables as $tagable){
+                if($tagable->taggable_type == 'App\Models\Question'){
+                    $questions[] = $tagable->taggable_id;
+                }
+            }
+      }
+
+      return self::where(function($query) use($users){
+                     $query->whereIn("user_id",$users);
+                 })
+                 ->oRwhere(function($query) use($questions){
+                     $query->whereIn("source_id",$questions)->where("source_type","=","App\Models\Question");
+
+                 })
+                 ->where('doings.user_id','<>',$user->id)
+             //->where('attentions.created_at','<','doings.created_at')
+             ->select('doings.*')
+             ->orderBy('doings.created_at','DESC');
+    }
+
+   public static function newest(){
+        return self::where("source_type","=","App\Models\Question")->orderBy('created_at','desc');
+    }
+
+
+}

+ 15 - 0
app/Models/Draft.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Draft extends Model
+{
+    use BelongsToUserTrait;
+    //
+    public $incrementing = false;
+    protected $fillable = ['id', 'user_id','editor_content','subject','form_data','source_type','source_id'];
+
+}

+ 37 - 0
app/Models/EmailToken.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Models;
+
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Str;
+
+class EmailToken extends Model
+{
+    protected $table = 'email_tokens';
+
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = ['email','action','token'];
+
+
+    /*清空toke信息*/
+    public static function clear($email,$action)
+    {
+        self::where('email','=',$email)->where('action','=',$action)->delete();
+    }
+
+
+
+    public static function createToken()
+    {
+        return hash_hmac('sha256',Str::random(40),Config::get('key'));
+    }
+
+
+}

+ 26 - 0
app/Models/Exchange.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToUserTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Exchange extends Model
+{
+    use BelongsToUserTrait;
+    protected $table = 'exchanges';
+    protected $fillable = ['user_id','coins', 'goods_id','real_name','phone','email','comment','status'];
+
+
+    static function newest()
+    {
+        return self::orderBy('created_at','desc')->take(10)->get();
+    }
+
+
+    public function goods(){
+        return $this->belongsTo('App\Models\Goods','goods_id');
+    }
+
+
+}

+ 12 - 0
app/Models/FriendshipLink.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class FriendshipLink extends Model
+{
+    protected $table = 'friendship_links';
+    protected $fillable = ['name', 'slogan','url','sort','status'];
+
+}

+ 18 - 0
app/Models/Goods.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Relations\BelongsToCategoryTrait;
+use Illuminate\Database\Eloquent\Model;
+
+class Goods extends Model
+{
+    use BelongsToCategoryTrait;
+    protected $table = 'goods';
+    protected $fillable = ['name', 'logo','post_type','description','coins','remnants','category_id','status'];
+
+
+
+
+
+}

+ 13 - 0
app/Models/Message.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Message extends Model
+{
+    protected $table = 'messages';
+    protected $fillable = ['from_user_id', 'to_user_id','content','is_read'];
+
+
+}

+ 0 - 0
app/Models/Notice.php


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff