<repo_name>caixin/video<file_sep>/database/migrations/2019_12_03_065041_create_user_share_log_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserShareLogTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'user_share_log';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->integer('uid')->default(0)->comment('分享者ID');
            $table->string('ip', 50)->default('')->comment('登入IP');
            $table->json('ip_info')->comment('IP資訊');
            $table->text('ua')->comment('User Agent');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->index('uid');
            $table->index('created_at');
        });

        DB::statement("ALTER TABLE `$tableName` comment '分享連接LOG'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user_share_log');
    }
}
<file_sep>/app/Http/Controllers/Video/VideoController.php
<?php

namespace App\Http\Controllers\Video;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Video\VideoService;

class VideoController extends Controller
{
    protected $videoService;

    public function __construct(VideoService $videoService)
    {
        $this->videoService = $videoService;
    }

    public function index(Request $request)
    {
        return view('video.index', $this->videoService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'video'));
    }

    public function save(Request $request, $id)
    {
        $this->videoService->save($request->post(), $id);
        return 'done';
    }
}
<file_sep>/app/Models/System/DomainSetting.php
<?php

namespace Models\System;

use Models\Model;

class DomainSetting extends Model
{
    protected $table = 'domain_setting';

    protected $fillable = [
        'domain',
        'title',
        'keyword',
        'description',
        'baidu',
    ];
}
<file_sep>/app/Services/Pmtools/DailyAnalysisService.php
<?php

namespace App\Services\Pmtools;

use App\Repositories\Pmtools\DailyAnalysisRepository;

class DailyAnalysisService
{
    protected $dailyAnalysisRepository;

    public function __construct(DailyAnalysisRepository $dailyAnalysisRepository)
    {
        $this->dailyAnalysisRepository = $dailyAnalysisRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['date', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['type'] = $search['type'] ?? 1;
        $search['date1'] = $search['date1'] ?? date('Y-m-d', time()-86400*30);
        $search['date2'] = $search['date2'] ?? date('Y-m-d', time()-86400);

        //預設值
        $table = $chart = [];
        for ($i=strtotime($search['date1']);$i<=strtotime($search['date2']);$i+=86400) {
            $table[$i] = 0;
        }

        $result = $this->dailyAnalysisRepository
            ->select(['date','count'])
            ->search($search)
            ->order($order)
            ->result();
        foreach ($result as $key => $row) {
            $table[strtotime($row['date'])] = $row['count'];
        }
        
        foreach ($table as $key => $val) {
            $chart[] = [date('Y-m-d', $key),(int)$val];
        }
        krsort($table);

        return [
            'table'      => $table,
            'chart'      => $chart,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/app/Services/Video/VideoService.php
<?php

namespace App\Services\Video;

use App\Repositories\Video\VideoRepository;

class VideoService
{
    protected $videoRepository;

    public function __construct(VideoRepository $videoRepository)
    {
        $this->videoRepository = $videoRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['publish', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['status'] = $search['status'] ?? 1;

        $result = $this->videoRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function getList($request)
    {
        $per_page = $request->per_page ?: 10;

        $search['status'] = 1;
        if ($request->search) {
            $search['name'] = $request->search;
        }
        if ($request->tags) {
            $search['tags'] = $request->tags;
        }

        $result = $this->videoRepository->search($search)
            ->order(['publish'=>'desc','keyword'=>'desc'])
            ->paginate($per_page)
            ->result();
        $list = [];
        foreach ($result as $row) {
            $list[] = [
                'keyword'   => $row['keyword'],
                'name'      => $row['name'],
                'publish'   => $row['publish'],
                'actors'    => $row['actors'],
                'tags'      => $row['tags'],
                'pic_big'   => $row['pic_b'],
                'pic_small' => $row['pic_s'],
            ];
        }
        return [
            'list'  => $list,
            'total' => $result->total(),
        ];
    }
}
<file_sep>/app/Http/Middleware/ForceJson.php
<?php

namespace App\Http\Middleware;

use Closure;

class ForceJson
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($this->shouldChangeAccept($request)) {
            $request->headers->set('Accept', 'application/json');
        }

        return $next($request);
    }

    private function shouldChangeAccept($request)
    {
        $accepts = $request->headers->get('Accept');

        if (empty($accepts) === true) {
            return true;
        }

        return preg_match('/\*\/\*|\*|text\/html/', $accepts) === 1;
    }
}
<file_sep>/app/Services/User/UserMoneyLogService.php
<?php

namespace App\Services\User;

use App\Repositories\User\UserMoneyLogRepository;

class UserMoneyLogService
{
    protected $userMoneyLogRepository;

    public function __construct(UserMoneyLogRepository $userMoneyLogRepository)
    {
        $this->userMoneyLogRepository = $userMoneyLogRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->userMoneyLogRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/app/Repositories/System/Ip2locationRepository.php
<?php

namespace App\Repositories\System;

use App\Repositories\AbstractRepository;
use Models\System\Ip2location;

class Ip2locationRepository extends AbstractRepository
{
    public function __construct(Ip2location $entity)
    {
        parent::__construct($entity);
    }

    public function _do_search()
    {
        if (isset($this->_search['ip_from'])) {
            $this->db = $this->db->where('ip_from', '<=', $this->_search['ip_from']);
        }
        if (isset($this->_search['ip_to'])) {
            $this->db = $this->db->where('ip_to', '>=', $this->_search['ip_to']);
        }
        return $this;
    }

    /**
     * 取得IP位置資訊
     * @param string $ip IP位址
     * @return array IP資訊
     */
    public function getIpData($ip)
    {
        return $this->search(['ip_from'=>ip2long($ip)])
            ->order(['ip_from','desc'])
            ->result_one();
    }
}
<file_sep>/app/Http/Requests/Admin/AdminForm.php
<?php

namespace App\Http\Requests\Admin;

use App\Http\Requests\FormRequest;
use Illuminate\Validation\Rule;

final class AdminForm extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        switch ($this->method()) {
            case 'GET':
            case 'DELETE':
            {
                return [];
            }
            case 'POST':
            {
                return [
                    'username'     => ['required',Rule::unique('admin')->ignore($this->route('admin'))],
                    'password'     => '<PASSWORD>',
                    'roleid'       => 'required',
                ];
            }
            case 'PUT':
            case 'PATCH':
            {
                return [
                    'username'     => ['required',Rule::unique('admin')->ignore($this->route('admin'))],
                    'roleid'       => 'required',
                ];
            }
            default: break;
        }
    }
}
<file_sep>/app/Services/System/MessageService.php
<?php

namespace App\Services\System;

use App\Repositories\System\MessageRepository;

class MessageService
{
    protected $messageRepository;

    public function __construct(MessageRepository $messageRepository)
    {
        $this->messageRepository = $messageRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['created_at1'] = $search['created_at1'] ?? date('Y-m-d', time()-86400*30);
        $search['created_at2'] = $search['created_at2'] ?? date('Y-m-d');

        $result = $this->messageRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function create($input)
    {
        $row = $this->messageRepository->getEntity();

        return [
            'row' => $row,
        ];
    }

    public function store($row)
    {
        $row = array_map('strval', $row);
        $this->messageRepository->create($row);
    }

    public function show($id)
    {
        $row = $this->messageRepository->find($id);

        return [
            'row'  => $row,
        ];
    }

    public function update($row, $id)
    {
        $row = array_map('strval', $row);
        $this->messageRepository->update($row, $id);
    }

    public function destroy($id)
    {
        $this->messageRepository->delete($id);
    }
}
<file_sep>/app/Console/Command/UpdateVideo.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\Video\VideoRepository;

class UpdateVideo extends Command
{
    //命令名稱
    protected $signature = 'update:video';
    //說明文字
    protected $description = '[更新] 过滤过期影片';

    protected $videoRepository;

    public function __construct(VideoRepository $videoRepository)
    {
        parent::__construct();

        $this->videoRepository = $videoRepository;
    }

    //Console 執行的程式
    public function handle()
    {
        $this->videoRepository->search([
            'updated_at2' => date('Y-m-d H:i:s', time()-14400)
        ])->update([
            'status' => 0
        ]);
    }
}
<file_sep>/app/Http/Controllers/Api/UserController.php
<?php

namespace App\Http\Controllers\Api;

use JWTAuth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Repositories\User\UserRepository;
use Validator;
use Exception;

class UserController extends Controller
{
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * @OA\Post(
     *   path="/user/profile",
     *   summary="用戶資訊",
     *   tags={"User"},
     *   security={{"JWT":{}}},
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function profile(Request $request)
    {
        try {
            $user = JWTAuth::user();

            return response()->json([
                'success' => true,
                'data'    => [
                    'id'         => $user->id,
                    'username'   => $user->username,
                    'money'      => $user->money,
                    'status'     => $user->status,
                    'created_at' => date('Y-m-d H:i:s', strtotime($user->created_at)),
                ],
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/user/referrer",
     *   summary="推薦人列表",
     *   tags={"User"},
     *   security={{"JWT":{}}},
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function referrer(Request $request)
    {
        try {
            $user = JWTAuth::user();

            $result = $this->userRepository->getReferrerList($user->id);
            $list = [];
            foreach ($result as $row) {
                $list[] = [
                    'username'    => $row->username,
                    'money'       => $row->money,
                    'status'      => $row->status,
                    'login_time'  => $row->login_time,
                    'created_at' => date('Y-m-d H:i:s', strtotime($row->created_at)),
                ];
            }

            return response()->json([
                'success' => true,
                'data'    => $list,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }
}
<file_sep>/app/Repositories/Pmtools/DailyAnalysisRepository.php
<?php

namespace App\Repositories\Pmtools;

use App\Repositories\AbstractRepository;
use Models\Pmtools\DailyAnalysis;

class DailyAnalysisRepository extends AbstractRepository
{
    public function __construct(DailyAnalysis $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['type'])) {
            $this->db = $this->db->where('type', '=', $this->_search['type']);
        }
        
        if (isset($this->_search['date'])) {
            $this->db = $this->db->where('date', '=', $this->_search['date']);
        }
        if (isset($this->_search['date1'])) {
            $this->db = $this->db->where('date', '>=', $this->_search['date1']);
        }
        if (isset($this->_search['date2'])) {
            $this->db = $this->db->where('date', '<=', $this->_search['date2']);
        }

        return $this;
    }
}
<file_sep>/app/Models/Admin/AdminNav.php
<?php

namespace Models\Admin;

use Models\Model;

class AdminNav extends Model
{
    protected $table = 'admin_nav';

    protected $attributes = [
        'route1' => '',
        'route2' => '',
        'sort'   => 0,
        'status' => 1,
    ];

    protected $fillable = [
        'pid',
        'icon',
        'name',
        'route',
        'route1',
        'route2',
        'path',
        'sort',
        'status',
        'created_by',
        'updated_by',
    ];

    const PREFIX_COLOR = [
        ''     => 'red',
        '∟ '   => 'green',
        '∟ ∟ ' => 'black',
    ];

    const STATUS = [
        1 => '开启',
        0 => '关闭',
    ];

    public function parent()
    {
        return $this->hasOne(get_class($this), $this->getKeyName(), 'pid');
    }

    public function children()
    {
        return $this->hasMany(get_class($this), 'pid', $this->getKeyName());
    }
}
<file_sep>/app/Models/System/Sysconfig.php
<?php

namespace Models\System;

use Models\Model;

class Sysconfig extends Model
{
    const CREATED_AT = null;
    const UPDATED_AT = null;

    protected $table = 'sysconfig';

    protected $attributes = [
        'sort' => 1,
    ];

    protected $fillable = [
        'skey',
        'svalue',
        'info',
        'groupid',
        'type',
        'sort',
    ];

    const GROUPID = [
        1 => '站点设置',
        2 => '維護设置',
    ];
    
    const TYPE = [
        1 => '字串(text)',
        2 => '数字(number)',
        3 => '文本域(textarea)',
        4 => '布林值(Y/N)',
    ];
}
<file_sep>/app/Models/Pmtools/ConcurrentUser.php
<?php

namespace Models\Pmtools;

use Models\Model;

class ConcurrentUser extends Model
{
    const UPDATED_AT = null;

    protected $table = 'concurrent_user';

    protected $fillable = [
        'per',
        'minute_time',
        'count',
    ];

    const PER = [
        1 => '每分钟',
        5 => '每5分钟',
        10 => '每10分钟',
        30 => '每30分钟',
        60 => '每小时',
    ];
}
<file_sep>/database/migrations/2020_01_09_133204_create_message_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMessageTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'message';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->integer('uid')->default(0)->comment('用戶ID');
            $table->tinyInteger('type')->default(1)->comment('問題類型');
            $table->text('content')->comment('問題內容');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->index(['uid']);
            $table->index(['type']);
        });

        DB::statement("ALTER TABLE `$tableName` comment '留言板'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('message');
    }
}
<file_sep>/app/Services/Admin/AdminService.php
<?php

namespace App\Services\Admin;

use App\Repositories\Admin\AdminRepository;
use App\Repositories\Admin\AdminRoleRepository;

class AdminService
{
    protected $adminRepository;
    protected $adminRoleRepository;

    public function __construct(
        AdminRepository $adminRepository,
        AdminRoleRepository $adminRoleRepository
    ) {
        $this->adminRepository = $adminRepository;
        $this->adminRoleRepository = $adminRoleRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->adminRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
            'role'       => [1=>'超级管理者']+$this->adminRoleRepository->getRoleList(),
        ];
    }

    public function create($input)
    {
        $row = $this->adminRepository->getEntity();

        return [
            'row'  => $row,
            'role' => $this->adminRoleRepository->getRoleList(),
        ];
    }

    public function store($row)
    {
        $row = array_map('strval', $row);
        $this->adminRepository->create($row);
    }

    public function show($id)
    {
        return [
            'row' => $this->adminRepository->find($id),
            'role' => $this->adminRoleRepository->getRoleList(),
        ];
    }

    public function update($row, $id)
    {
        $row = array_map('strval', $row);
        $this->adminRepository->update($row, $id);
    }

    public function save($row, $id)
    {
        $this->adminRepository->save($row, $id);
    }

    public function destroy($id)
    {
        $this->adminRepository->delete($id);
    }
}
<file_sep>/deploy.sh
#!/bin/bash

vendor/bin/openapi -o public/openapi.json app/Http/Controllers/Api
composer dump-autoload
php artisan migrate
php artisan cache:clear
<file_sep>/app/Console/Command/SetConcurrentUser.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\User\UserRepository;
use App\Repositories\Pmtools\ConcurrentUserRepository;

class SetConcurrentUser extends Command
{
    //命令名稱
    protected $signature = 'pmtools:ccu {--per=}';
    //說明文字
    protected $description = '[統計]每日登入會員統計';

    protected $userRepository;
    protected $concurrentUserRepository;

    public function __construct(
        UserRepository $userRepository,
        ConcurrentUserRepository $concurrentUserRepository
    ) {
        parent::__construct();

        $this->userRepository = $userRepository;
        $this->concurrentUserRepository = $concurrentUserRepository;
    }

    /**
     * @param int $per 每幾分鐘
     */
    public function handle()
    {
        $per = $this->option('per') ?: 1;
        $minute_time = date('Y-m-d H:i:00');

        if ($this->concurrentUserRepository->search(['per'=>$per,'minute_time'=>$minute_time])->count() == 0) {
            $time = date('Y-m-d H:i:s', time() - 60 * (10 + $per));
            $count = $this->userRepository->search(['active_time1'=>$time])->count();
    
            $this->concurrentUserRepository->create([
                'per'         => $per,
                'minute_time' => $minute_time,
                'count'       => $count,
            ]);
        }
    }
}
<file_sep>/app/Repositories/AbstractRepository.php
<?php

namespace App\Repositories;

use Route;
use Models\Model;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

abstract class AbstractRepository
{
    const CHUNK = 1000;

    protected $entity; //預設Model
    protected $db;     //取資料時使用 用完還原成預設Model
    protected $_paginate = 0;
    protected $_select   = [];
    protected $_search   = [];
    protected $_where    = [];
    protected $_join     = [];
    protected $_order    = [];
    protected $_group    = [];
    protected $_having   = [];
    protected $_limit    = [];
    protected $is_action_log = true;

    public function __call($methods, $arguments)
    {
        return call_user_func_array([$this->entity, $methods], $arguments);
    }

    public function __construct(Model $entity)
    {
        $this->entity = $entity;
        $this->db     = $entity;
        DB::connection()->enableQueryLog();
    }

    public function getEntity()
    {
        return $this->entity;
    }

    public function setActionLog($bool)
    {
        return $this->is_action_log = $bool;
    }

    public function paginate($data)
    {
        $this->_paginate = $data;
        return $this;
    }

    public function select($data)
    {
        $this->_select = $data;
        return $this;
    }

    public function search($data)
    {
        $this->_search = $data;
        return $this;
    }

    public function where($data)
    {
        $this->_where = $data;
        return $this;
    }

    public function join($data)
    {
        $this->_join = $data;
        return $this;
    }

    public function group($data)
    {
        $this->_group = $data;
        return $this;
    }

    public function having($data)
    {
        $this->_having = $data;
        return $this;
    }

    public function order($data)
    {
        $this->_order = $data;
        return $this;
    }

    public function limit($data)
    {
        $this->_limit = $data;
        return $this;
    }

    public function set($data)
    {
        $this->_set = $data;
        return $this;
    }

    public function reset()
    {
        $this->_paginate = 0;
        $this->_select   = [];
        $this->_search   = [];
        $this->_where    = [];
        $this->_join     = [];
        $this->_group    = [];
        $this->_having   = [];
        $this->_order    = [];
        $this->_limit    = [];
        $this->_set      = [];
        $this->db        = $this->entity;
        return $this;
    }

    public function _do_search()
    {
        return $this;
    }

    public function _do_action()
    {
        if (!empty($this->_select)) {
            $this->db = $this->db->selectRaw(implode(',', $this->_select));
        }

        if (!empty($this->_join)) {
            foreach ($this->_join as $join) {
                $this->db = $this->db->$join();
            }
        }

        $this->_do_search();
        if (!empty($this->_where)) {
            foreach ($this->_where as $where) {
                if (is_array($where[2])) {
                    $this->db = $this->db->whereIn($where[0], $where[2]);
                } else {
                    $this->db = $this->db->where($where[0], $where[1], $where[2]);
                }
            }
        }

        if (!empty($this->_group)) {
            $this->db = $this->db->groupBy($this->_group);
        }

        if (!empty($this->_having)) {
            $this->db = $this->db->having($this->_having);
        }

        if (!empty($this->_order)) {
            if (isset($this->_order[0])) {
                if (strtolower($this->_order[0]) == 'rand()') {
                    $this->db = $this->db->orderByRaw($this->_order[0], $this->_order[1]);
                } else {
                    $this->db = $this->db->orderBy($this->_order[0], $this->_order[1]);
                }
            } else {
                foreach ($this->_order as $key => $val) {
                    $this->db = $this->db->orderBy($key, $val);
                }
            }
        }

        if (!empty($this->_limit)) {
            $this->db = $this->db->offset($this->_limit[0])->limit($this->_limit[1]);
        }

        return $this;
    }

    public function row($id)
    {
        return $this->entity->find($id);
    }

    public function result_one()
    {
        $this->_do_action();
        $row = $this->db->first();
        $this->reset();
        return $row;
    }

    public function result()
    {
        $this->_do_action();
        if ($this->_paginate > 0) {
            $result = $this->db->paginate($this->_paginate)->appends($this->_search);
        } else {
            $result = $this->db->get();
        }
        $this->reset();
        return $result;
    }

    public function count()
    {
        $this->_do_action();
        $count = $this->db->count();
        $this->reset();
        return $count;
    }

    /**
     * 取得最後執行的SQL語句
     *
     * @return string
     */
    public function last_query()
    {
        $querylog = DB::getQueryLog();
        $lastQuery = end($querylog);
        $stringSQL = str_replace('?', '%s', $lastQuery['query']);
        return sprintf($stringSQL, ...$lastQuery['bindings']);
    }

    public function get_compiled_select()
    {
        $this->_do_action();
        $stringSQL = str_replace('?', '%s', $this->db->toSql());
        $stringSQL = sprintf($stringSQL, ...$this->db->getBindings());
        $this->reset();
        return $stringSQL;
    }

    public function save($data, $id=0)
    {
        if (Schema::hasColumn($this->entity->getTable(), 'updated_by')) {
            $data['updated_by'] = session('username') ?: '';
        }

        if ($id == 0) {
            $this->_do_action();
            $row = $this->db->first();
            $this->reset();
        } else {
            $row = $this->entity->find($id);
        }

        foreach ($data as $key => $val) {
            $row->$key = $val;
        }
        $row->save();
        $data[$this->entity->getKeyName()] = $row->id;
        $this->actionLog($data);
    }

    public function insert($data)
    {
        if (Schema::hasColumn($this->entity->getTable(), 'created_at')) {
            $data['created_at'] = date('Y-m-d H:i:s');
        }
        if (Schema::hasColumn($this->entity->getTable(), 'created_by')) {
            $data['created_by'] = session('username') ?: '';
        }
        if (Schema::hasColumn($this->entity->getTable(), 'updated_at')) {
            $data['updated_at'] = date('Y-m-d H:i:s');
        }
        if (Schema::hasColumn($this->entity->getTable(), 'updated_by')) {
            $data['updated_by'] = session('username') ?: '';
        }

        $id = $this->entity->insertGetId($data);
        $this->actionLog($data, $id > 0 ? 1 : 0);
        return $id;
    }

    public function create($data)
    {
        if (Schema::hasColumn($this->entity->getTable(), 'created_by')) {
            $data['created_by'] = session('username') ?: '';
        }
        if (Schema::hasColumn($this->entity->getTable(), 'updated_by')) {
            $data['updated_by'] = session('username') ?: '';
        }

        $create = $this->entity->create($data);
        $this->actionLog($data, $create->id > 0 ? 1 : 0);
        return $create->id;
    }

    public function insert_batch($data)
    {
        $this->entity->insert($data);
        //寫入操作日誌
        /*
        if ($this->is_action_log) {
            $sql_str = $this->db->last_query();
            $message = $this->title . '(' . $this->getActionString_batch($data) . ')';
            $this->admin_action_log_db->insert([
                'sql_str' => $sql_str,
                'message' => $message,
                'status'  => $this->trans_status() ? 1 : 0,
            ]);
        }
        */
    }

    public function update($data, $id=0)
    {
        if (Schema::hasColumn($this->entity->getTable(), 'update_by')) {
            $data['update_by'] = session('username');
        }

        if ($id == 0) {
            $this->_do_action();
            $update = $this->db->update($data);
            $this->reset();
        } else {
            $update = $this->entity->find($id)->update($data);
        }

        $this->actionLog($data+['id'=>$id], $update > 0 ? 1 : 0);
    }

    public function update_batch($data)
    {
        $this->entity->updateBatch($data);

        //寫入操作日誌
        /*
        if ($this->is_action_log) {
            $sql_str = $this->db->last_query();
            $message = $this->title . '(' . $this->getActionString_batch($data) . ')';
            $this->admin_action_log_db->insert([
                'sql_str' => $sql_str,
                'message' => $message,
                'status'  => $this->trans_status() ? 1 : 0,
            ]);
        }
        */
    }

    public function delete($id=0)
    {
        if ($id == 0) {
            $this->_do_action();
            $this->db->delete();
            $this->reset();
        } else {
            $this->entity->find($id)->delete();
        }

        $this->actionLog([$this->entity->getKeyName()=>$id]);
    }

    public function updateOrCreate($where, $data)
    {
        $this->entity->updateOrCreate($where, $data);
    }

    public function updateOrInsert($where, $data)
    {
        $this->entity->updateOrInsert($where, $data);
    }

    public function increment($column, $amount)
    {
        $this->entity->increment($column, $amount);
    }

    public function decrement($column, $amount)
    {
        $this->entity->decrement($column, $amount);
    }

    public function truncate()
    {
        $this->entity->truncate();
    }

    /**
     * 寫入操作日誌
     *
     * @param array $data 欄位資料
     * @param integer $status 狀態
     * @return void
     */
    public function actionLog($data, $status=1)
    {
        if ($this->is_action_log) {
            $message = view()->shared('title') . '(' . $this->getActionString($data) . ')';
            DB::table('admin_action_log')->insert([
                'adminid'    => session('id'),
                'route'      => Route::currentRouteName(),
                'sql'        => $this->last_query(),
                'message'    => $message,
                'ip'         => Request()->getClientIp(),
                'status'     => $status,
                'created_at' => date('Y-m-d H:i:s'),
                'created_by' => session('username'),
            ]);
        }
    }

    /**
     * 組成操作日誌字串
     */
    public function getActionString($data, $highlight = [])
    {
        $str = [];
        foreach ($data as $key => $val) {
            //判斷有無欄位
            if (isset(static::$columnList[$key])) {
                //判斷欄位有無靜態陣列
                if (isset($this->entity::$field->$val)) {
                    $val = $this->entity::$field->$val;
                }

                if (isset($highlight[$key])) {
                    $str[] = '<font color="blue">' . static::$columnList[$key] . "=$val</font>";
                } else {
                    $str[] = static::$columnList[$key] . "=$val";
                }
            }
        }

        return implode(';', $str);
    }

    /**
     * 組成操作日誌字串(多筆)
     */
    public function getActionString_batch($result)
    {
        $return = [];
        foreach ($result as $data) {
            $str = [];
            foreach ($data as $key => $val) {
                //判斷有無欄位
                if (isset(static::$columnList[$key])) {
                    //判斷欄位有無靜態陣列
                    if (isset(static::${"{$key}List"}[$val])) {
                        $val = static::${"{$key}List"}[$val];
                    }

                    $str[] = static::$columnList[$key] . "=$val";
                }
            }
            $return[] = implode(';', $str);
        }

        return implode('<br>', $return);
    }

    public static $columnList = [];
}
<file_sep>/database/migrations/2019_12_18_134831_create_daily_retention_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateDailyRetentionTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'daily_retention';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->date('date')->default('1970-01-01')->comment('日期');
            $table->tinyInteger('type')->comment('類型 1.1日內有登入,2.3日內有登入,3.7日內有登入,4.15日內有登入,5.30日內有登入,6.31日以上未登入');
            $table->integer('all_count')->default(0)->comment('總數');
            $table->integer('day_count')->default(0)->comment('人數');
            $table->integer('avg_money')->default(0)->comment('平均點數');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->unique(['date','type']);
        });

        DB::statement("ALTER TABLE `$tableName` comment '每日統計-留存率'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('daily_retention');
    }
}
<file_sep>/app/Presenters/layouts/BackendPresenter.php
<?php

namespace App\Presenters\Layouts;

class BackendPresenter
{
    public function __construct()
    {
        benchmark()->checkpoint();
    }

    public function elapsedTime()
    {
        return benchmark()->getElapsedTime()->f;
    }

    public function ramUsage()
    {
        config(['benchmark.format_ram_usage'=> true]);
        return benchmark()->getPeakRamUsage();
    }
}
<file_sep>/config/global.php
<?php

return [

    /**
     * 無須權限的動作
     */
    'no_need_perm' => [
        '','home','ajax.topinfo','ajax.perpage','ajax.imageupload','admin.editpwd','admin.updatepwd','logout',
    ],

];
<file_sep>/app/Console/Command/SetDailyAnalysis.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\User\UserRepository;
use App\Repositories\User\UserLoginLogRepository;
use App\Repositories\Pmtools\ConcurrentUserRepository;
use App\Repositories\Pmtools\DailyAnalysisRepository;

class SetDailyAnalysis extends Command
{
    //命令名稱
    protected $signature = 'pmtools:daily_analysis {--enforce=} {--date=}';
    //說明文字
    protected $description = '[統計]每日活躍用戶數';

    protected $userLoginLogRepository;
    protected $concurrentUserRepository;
    protected $dailyAnalysisRepository;

    public function __construct(
        UserRepository $userRepository,
        UserLoginLogRepository $userLoginLogRepository,
        ConcurrentUserRepository $concurrentUserRepository,
        DailyAnalysisRepository $dailyAnalysisRepository
    ) {
        parent::__construct();

        $this->userRepository = $userRepository;
        $this->userLoginLogRepository = $userLoginLogRepository;
        $this->concurrentUserRepository = $concurrentUserRepository;
        $this->dailyAnalysisRepository = $dailyAnalysisRepository;
    }

    /**
     * @param string $date 指定執行日期
     * @param int $enforce 強制重新執行
     */
    public function handle()
    {
        $date    = $this->option('date') ?: date('Y-m-d', time()-86400);
        $enforce = $this->option('enforce') ?: 0;

        //強制執行 先刪除已存在的資料
        if ($enforce) {
            $this->dailyAnalysisRepository->search(['date'=>$date])->delete();
        }

        //判斷是否已執行過(有資料)
        if ($this->dailyAnalysisRepository->search(['date'=>$date])->count() == 0) {
            $insert = [];
            $now = date('Y-m-d H:i:s');
            //NUU
            $NUU = $this->userRepository->search([
                'status'       => 1,
                'created_at1' => $date,
                'created_at12' => $date,
            ])->count();
            $insert[] = [
                'date'       => $date,
                'type'       => 1,
                'count'      => $NUU,
                'created_at' => $now,
            ];
            //DAU
            $DAU = $this->userLoginLogRepository->getLoginUsers($date, $date);
            $insert[] = [
                'date'       => $date,
                'type'       => 2,
                'count'      => $DAU,
                'created_at' => $now,
            ];
            //WAU
            $WAU = $this->userLoginLogRepository->getLoginUsers(date('Y-m-d', strtotime($date) - 86400 * 6), $date);
            $insert[] = [
                'date'       => $date,
                'type'       => 3,
                'count'      => $WAU,
                'created_at' => $now,
            ];
            //MAU
            $MAU = $this->userLoginLogRepository->getLoginUsers(date('Y-m-01', strtotime($date)), $date);
            $insert[] = [
                'date'       => $date,
                'type'       => 4,
                'count'      => $MAU,
                'created_at' => $now,
            ];
            //DAU-NUU
            $insert[] = [
                'date'       => $date,
                'type'       => 5,
                'count'      => $DAU - $NUU,
                'created_at' => $now,
            ];
            //PCU
            $CCU = $this->concurrentUserRepository->select(['IFNULL(MAX(count),0) count'])->search([
                'per'          => 1,
                'minute_time1' => "$date 00:00:00",
                'minute_time2' => "$date 23:59:59",
            ])->result_one();
            $insert[] = [
                'date'       => $date,
                'type'       => 6,
                'count'      => $CCU['count'],
                'created_at' => $now,
            ];
            //寫入資料
            $this->dailyAnalysisRepository->insert_batch($insert);
        }
    }
}
<file_sep>/app/Http/Middleware/Permission.php
<?php

namespace App\Http\Middleware;

use Route;
use Auth;
use Closure;
use Request;
use Models\Admin\Admin;

class Permission
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        //檢測用戶，不可重複登入
        if (session('_token') === null) {
            return $this->errorRedirect('session无效，请重新登录');
        }

        if (session('_token') != Admin::find(session('id'))->token) {
            return $this->errorRedirect("session无效，请重新登录");
        }
        //檢測url是不是存在的
        $allNav = view()->shared('allNav');
        $routes = [];
        foreach ($allNav as $row) {
            if ($row['pid'] > 0) {
                $routes[] = $row['route'];
                if ($row['route1'] != '') {
                    $routes[] = $row['route1'];
                }
                if ($row['route2'] != '') {
                    $routes[] = $row['route2'];
                }
            }
        }

        $routes = array_merge_recursive($routes, config('global.no_need_perm'));
        if (!in_array(Route::currentRouteName(), $routes)) {
            abort(500, '当前的url没有设置,或者已经禁用,请联系管理员设置！');
        }

        //非超级管理员則驗證是否有訪問的權限
        if (session('roleid') != 1 && !in_array(view()->shared('route'), view()->shared('allow_url'))) {
            return $this->errorRedirect("对不起，您没权限执行此操作，请联系管理员");
        }

        return $next($request);
    }

    /**
     * 驗證錯誤 跳轉登入頁
     * $message 跳轉訊息
     *
     * @return null
     */
    private function errorRedirect($message)
    {
        Auth::logout();
        session()->flash('message', $message);
        session('refer', Request::getRequestUri());
        return redirect('login');
    }
}
<file_sep>/app/Http/Controllers/Api/HomeController.php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Repositories\System\SysconfigRepository;

class HomeController extends Controller
{
    protected $sysconfigRepository;

    public function __construct(SysconfigRepository $sysconfigRepository)
    {
        $this->sysconfigRepository = $sysconfigRepository;
    }
    
    /**
     * @OA\Post(
     *   path="/maintenance",
     *   summary="網站維護資訊",
     *   tags={"Home"},
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function maintenance(Request $request)
    {
        $data = $this->sysconfigRepository->getSysconfig();

        return response()->json([
            'success' => true,
            'message' => $data['maintenance_message'] ?? '',
        ]);
    }
}
<file_sep>/app/Models/Pmtools/DailyAnalysis.php
<?php

namespace Models\Pmtools;

use Models\Model;

class DailyAnalysis extends Model
{
    const UPDATED_AT = null;

    protected $table = 'daily_analysis';

    protected $fillable = [
        'date',
        'type',
        'count',
    ];

    const TYPE = [
        1 => '每日添加用户数(NUU)',
        2 => '每日不重复登入用户数(DAU)',
        3 => '每周不重复登入用户数(WAU)',
        4 => '每月不重复登入用户数(MAU)',
        5 => 'DAU - NUU',
        6 => '最大同时在线用户数(PCU)',
    ];
}
<file_sep>/app/Repositories/User/UserShareLogRepository.php
<?php

namespace App\Repositories\User;

use App\Repositories\AbstractRepository;
use App\Repositories\System\Ip2locationRepository;
use Models\User\UserShareLog;

class UserShareLogRepository extends AbstractRepository
{
    protected $ip2locationRepository;

    public function __construct(UserShareLog $entity, Ip2locationRepository $ip2locationRepository)
    {
        parent::__construct($entity);
        $this->ip2locationRepository = $ip2locationRepository;
        $this->is_action_log = false;
    }

    public function create($row)
    {
        $ip = request()->getClientIp();
        $ip_info = $this->ip2locationRepository->getIpData($ip);
        $ip_info = $ip_info ?? [];

        $row['ip']      = $ip;
        $row['ip_info'] = json_encode($ip_info);
        $row['ua']      = request()->server('HTTP_USER_AGENT');

        return parent::create($row);
    }

    public function _do_search()
    {
        if (isset($this->_search['username'])) {
            $this->db = $this->db->whereHas('user', function ($query) {
                $query->where('username', 'like', '%'.$this->_search['username'].'%');
            });
        }

        if (isset($this->_search['ip'])) {
            $this->db = $this->db->where('ip', '=', $this->_search['ip']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }
}
<file_sep>/app/Http/Controllers/Api/CommonController.php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Repositories\System\SysconfigRepository;
use App\Repositories\System\AdsRepository;
use Validator;
use Exception;

class CommonController extends Controller
{
    protected $sysconfigRepository;

    public function __construct(SysconfigRepository $sysconfigRepository)
    {
        $this->sysconfigRepository = $sysconfigRepository;
    }

    /**
     * @OA\Post(
     *   path="/param",
     *   summary="網站參數",
     *   tags={"Common"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="key",
     *                   description="參數名稱(可不帶)",
     *                   type="string",
     *                   example="video_title",
     *               )
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function param(Request $request)
    {
        try {
            $config = $this->sysconfigRepository->getSysconfig();
            $data = [];
            if ($request->key) {
                if (isset($config[$request->key])) {
                    $data[$request->key] = $config[$request->key];
                }
            } else {
                foreach (['video_title'] as $val) {
                    $data[$val] = $config[$val] ?? '';
                }
            }

            return response()->json([
                'success' => true,
                'data'    => $data,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/adslist",
     *   summary="廣告列表",
     *   tags={"Common"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="type",
     *                   description="廣告位置",
     *                   type="string",
     *                   example="1",
     *               ),
     *               @OA\Property(
     *                   property="page",
     *                   description="頁數",
     *                   type="string",
     *                   example="1",
     *               ),
     *               @OA\Property(
     *                   property="per_page",
     *                   description="一頁幾筆",
     *                   type="string",
     *                   example="10",
     *               ),
     *               required={"type","page","per_page"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function adsList(Request $request, AdsRepository $adsRepository)
    {
        try {
            $validator = Validator::make($request->all(), [
                'type' => 'required',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $type     = $request->type;
            $page     = $request->page ?: 1;
            $per_page = $request->per_page ?: 10;

            $date = date('Y-m-d H:i:s');
            $where[] = ['type', '=', $type];
            $where[] = ['start_time', '<=', $date];
            $where[] = ['end_time', '>=', $date];

            $result = $adsRepository->where($where)
                ->order(['sort', 'asc'])
                ->paginate($per_page)
                ->result();
            $list = [];
            foreach ($result as $row) {
                $list[] = [
                    'name'  => $row['name'],
                    'image' => asset($row['image']),
                    'url'   => $row['url'],
                ];
            }

            return response()->json([
                'success' => true,
                'page'    => (int)$page,
                'total'   => $result->total(),
                'list'    => $list,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }
}
<file_sep>/app/Models/Admin/AdminLoginLog.php
<?php

namespace Models\Admin;

use Models\Model;

class AdminLoginLog extends Model
{
    const UPDATED_AT = null;

    protected $table = 'admin_login_log';

    protected $fillable = [
        'adminid',
        'ip',
        'ip_info',
        'created_by',
    ];

    public function admin()
    {
        return $this->belongsTo('Models\Admin\Admin', 'adminid');
    }
}
<file_sep>/app/Services/Admin/AdminNavService.php
<?php

namespace App\Services\Admin;

use App\Repositories\Admin\AdminNavRepository;

class AdminNavService
{
    protected $adminNavRepository;

    public function __construct(AdminNavRepository $adminNavRepository)
    {
        $this->adminNavRepository = $adminNavRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['sort', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->adminNavRepository->search($search)
            ->order($order)->result()->toArray();
        $result = $this->treeSort($result);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function create($input)
    {
        $row = $this->adminNavRepository->getEntity();
        $row['pid'] = $input['pid'] ?? $row['pid'];

        return [
            'row' => $row,
            'nav' => $this->getDropDownList(),
        ];
    }

    public function store($row)
    {
        $row = array_map('strval', $row);
        $this->adminNavRepository->create($row);
    }

    public function show($id)
    {
        return [
            'row' => $this->adminNavRepository->row($id)->toArray(),
            'nav' => $this->getDropDownList(),
        ];
    }

    public function update($row, $id)
    {
        $row = array_map('strval', $row);
        $this->adminNavRepository->update($row, $id);
    }

    public function save($row, $id)
    {
        $this->adminNavRepository->save($row, $id);
    }

    public function destroy($id)
    {
        $this->adminNavRepository->delete($id);
    }

    public function treeSort($result, $pid = 0, $array = [], $prefix = '')
    {
        foreach ($result as $row) {
            if ($row['pid'] == $pid) {
                $row['prefix'] = $prefix;
                $array[] = $row;
                $array = $this->treeSort($result, $row['id'], $array, $prefix . '∟ ');
            }
        }
        return $array;
    }

    public function getDropDownList()
    {
        $result = $this->adminNavRepository->order(['sort','asc'])->result()->toArray();
        $result = $this->treeSort($result);
        $data = [];
        foreach ($result as $row) {
            $data[$row['id']] = $row['prefix'] . $row['name'];
        }
        return $data;
    }
}
<file_sep>/app/Repositories/System/MessageRepository.php
<?php

namespace App\Repositories\System;

use App\Repositories\AbstractRepository;
use Models\System\Message;

class MessageRepository extends AbstractRepository
{
    public function __construct(Message $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['uid'])) {
            $this->db = $this->db->where('uid', '=', $this->_search['uid']);
        }
        if (isset($this->_search['username'])) {
            $this->db = $this->db->whereHas('user', function ($query) {
                $query->where('username', 'like', '%'.$this->_search['username'].'%');
            });
        }

        if (isset($this->_search['type'])) {
            $this->db = $this->db->where('type', '=', $this->_search['type']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'].' 00:00:00');
        }

        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'].' 23:59:59');
        }
        return $this;
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'      => '流水号',
        'uid'     => '用户ID',
        'type'    => '问题类型',
        'content' => '内容',
    ];
}
<file_sep>/app/Repositories/System/AdsRepository.php
<?php

namespace App\Repositories\System;

use App\Repositories\AbstractRepository;
use Models\System\Ads;

class AdsRepository extends AbstractRepository
{
    public function __construct(Ads $entity)
    {
        parent::__construct($entity);
    }

    public function _do_search()
    {
        if (isset($this->_search['domain'])) {
            $this->db = $this->db->where(function ($query) {
                $query->where('domain', '=', '')
                    ->orWhereRaw("FIND_IN_SET('".$this->_search['domain']."', domain)");
            });
        }

        if (isset($this->_search['type'])) {
            $this->db = $this->db->where('type', '=', $this->_search['type']);
        }

        if (isset($this->_search['enabled'])) {
            $date = date('Y-m-d H:i:s');
            $this->db = $this->db->where('start_time', '<=', $date)->where('end_time', '>=', $date);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1']);
        }

        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2']);
        }
        return $this;
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'         => '流水号',
        'type'       => '位置',
        'name'       => '名称',
        'image'      => '图片',
        'url'        => '连结',
        'start_time' => '开始时间',
        'end_time'   => '结束时间',
        'sort'       => '排序',
    ];
}
<file_sep>/app/Http/Controllers/Api/VideoController.php
<?php

namespace App\Http\Controllers\Api;

use JWTAuth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Video\VideoService;
use App\Repositories\Video\VideoRepository;
use App\Repositories\Video\VideoTagsRepository;
use App\Repositories\User\UserRepository;
use App\Repositories\User\UserMoneyLogRepository;
use Validator;
use Exception;

class VideoController extends Controller
{
    protected $videoService;
    protected $videoRepository;
    protected $videoTagsRepository;
    protected $userRepository;
    protected $userMoneyLogRepository;

    public function __construct(
        VideoService $videoService,
        VideoRepository $videoRepository,
        VideoTagsRepository $videoTagsRepository,
        UserRepository $userRepository,
        UserMoneyLogRepository $userMoneyLogRepository
    ) {
        $this->videoService = $videoService;
        $this->videoRepository = $videoRepository;
        $this->videoTagsRepository = $videoTagsRepository;
        $this->userRepository = $userRepository;
        $this->userMoneyLogRepository = $userMoneyLogRepository;
    }

    /**
     * @OA\Post(
     *   path="/video/list",
     *   summary="視頻列表",
     *   tags={"Video"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="search",
     *                   description="搜尋",
     *                   type="string",
     *                   example="",
     *               ),
     *               @OA\Property(
     *                   property="tags",
     *                   description="標籤",
     *                   type="string",
     *                   example="素人",
     *               ),
     *               @OA\Property(
     *                   property="page",
     *                   description="頁數",
     *                   type="string",
     *                   example="1",
     *               ),
     *               @OA\Property(
     *                   property="per_page",
     *                   description="一頁幾筆",
     *                   type="string",
     *                   example="10",
     *               ),
     *               required={"page","per_page"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function list(Request $request)
    {
        try {
            $page = $request->page ?: 1;
            $list = $this->videoService->getList($request);

            return response()->json([
                'success' => true,
                'page'    => (int)$page,
                'total'   => $list['total'],
                'list'    => $list['list'],
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/video/tags",
     *   summary="熱門標籤",
     *   tags={"Video"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="page",
     *                   description="頁數",
     *                   type="string",
     *                   example="1",
     *               ),
     *               @OA\Property(
     *                   property="per_page",
     *                   description="一頁幾筆",
     *                   type="string",
     *                   example="20",
     *               ),
     *               required={"page","per_page"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function tags(Request $request)
    {
        try {
            $page = $request->page ?: 1;
            $per_page = $request->per_page ?: 10;
    
            $result = $this->videoTagsRepository
                ->order(['hot','desc'])
                ->paginate($per_page)
                ->result();
            $list = [];
            foreach ($result as $row) {
                $list[] = $row['name'];
            }

            return response()->json([
                'success' => true,
                'page'    => (int)$page,
                'total'   => $result->total(),
                'list'    => $list,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/video/detail",
     *   summary="視頻資訊",
     *   tags={"Video"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="keyword",
     *                   description="視頻ID",
     *                   type="string",
     *                   example="niWaHDNjt3S",
     *               ),
     *               required={"keyword"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function detail(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'keyword' => 'required|exists:video',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $row = $this->videoRepository->search(['keyword'=>$request->keyword])->result_one();
            $all = false;
            //判斷是否登入
            if (JWTAuth::check()) {
                $user = JWTAuth::user();
                $buy = $this->userMoneyLogRepository->getBuyVideo($user['id']);
                if (in_array($row['keyword'], $buy)) {
                    $all = true;
                }
            }

            return response()->json([
                'success' => true,
                'buy'     => $all,
                'data'    => [
                    'keyword'   => $row['keyword'],
                    'name'      => $row['name'],
                    'publish'   => $row['publish'],
                    'actors'    => $row['actors'],
                    'tags'      => $row['tags'],
                    'pic_big'   => $row['pic_b'],
                    'pic_small' => $row['pic_s'],
                    'url'       => $all ? $row['url']: str_replace('end=36000', 'end=300', $row['url']),
                ],
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/video/buy",
     *   summary="購買視頻",
     *   tags={"Video"},
     *   security={{"JWT":{}}},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="keyword",
     *                   description="視頻ID",
     *                   type="string",
     *                   example="niWaHDNjt3S",
     *               ),
     *               required={"keyword"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function buy(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'keyword' => 'required|exists:video',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $user = JWTAuth::user();
            $buy = $this->userMoneyLogRepository->getBuyVideo($user['id']);
            if (in_array($request->keyword, $buy)) {
                throw new Exception('该视频已购买过！', 422);
            }
            if ($user['money'] <= 0) {
                throw new Exception('余额不足！', 422);
            }
            //帳變明細
            $this->userRepository->addMoney($user['id'], 2, -1, "觀看影片-$request->keyword", $request->keyword);

            return response()->json([
                'success' => true,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }
}
<file_sep>/app/Repositories/User/UserRepository.php
<?php

namespace App\Repositories\User;

use DB;
use App\Repositories\AbstractRepository;
use App\Repositories\System\Ip2locationRepository;
use App\Repositories\User\UserMoneyLogRepository;
use Models\User\User;

class UserRepository extends AbstractRepository
{
    protected $ip2locationRepository;
    protected $userMoneyLogRepository;

    public function __construct(
        User $entity,
        Ip2locationRepository $ip2locationRepository,
        UserMoneyLogRepository $userMoneyLogRepository
    ) {
        parent::__construct($entity);
        $this->ip2locationRepository = $ip2locationRepository;
        $this->userMoneyLogRepository = $userMoneyLogRepository;
        $this->is_action_log = false;
    }

    public function create($row)
    {
        if (isset($row['password'])) {
            if ($row['password'] != '') {
                $row['password'] = <PASSWORD>($row['password']);
            } else {
                unset($row['password']);
            }
        }
        if (isset($row['referrer_code'])) {
            if ($referrer = $this->referrerCode($row['referrer_code'], 'decode')) {
                $row['referrer'] = $referrer;
            }
            unset($row['referrer_code']);
        }

        $ip = request()->getClientIp();
        $ip_info = $this->ip2locationRepository->getIpData($ip);
        $ip_info = $ip_info ?? [];
        $row['create_ip']      = $ip;
        $row['create_ip_info'] = json_encode($ip_info);
        $row['create_ua']      = request()->server('HTTP_USER_AGENT');
        $row['token']          = $row['username'];

        return parent::create($row);
    }

    public function update($row, $id=0)
    {
        if (isset($row['password'])) {
            if ($row['password'] != '') {
                $row['password'] = bcrypt($row['password']);
            } else {
                unset($row['password']);
            }
        }
        if (isset($row['referrer_code'])) {
            if ($referrer = $this->referrerCode($row['referrer_code'], 'decode')) {
                $row['referrer'] = $referrer;
            }
            unset($row['referrer_code']);
        }
        if (isset($row['update_info'])) {
            $ip = request()->getClientIp();
            $ip_info = $this->ip2locationRepository->getIpData($ip);
            $ip_info = $ip_info ?? [];
            $row['create_ip']      = $ip;
            $row['create_ip_info'] = json_encode($ip_info);
            $row['create_ua']      = request()->server('HTTP_USER_AGENT');
        }

        return parent::update($row, $id);
    }

    public function _do_search()
    {
        if (isset($this->_search['ids'])) {
            $this->db = $this->db->whereIn('id', $this->_search['ids']);
        }

        if (isset($this->_search['username'])) {
            $this->db = $this->db->where('username', 'like', '%'.$this->_search['username'].'%');
        }

        if (isset($this->_search['referrer_code'])) {
            $uid = $this->referrerCode($this->_search['referrer_code'], 'decode');
            $this->db = $this->db->where('id', '=', $uid);
        }

        if (isset($this->_search['referrer'])) {
            $this->db = $this->db->where('referrer', '=', $this->_search['referrer']);
        }

        if (isset($this->_search['remark'])) {
            $this->db = $this->db->where('remark', 'like', '%'.$this->_search['remark'].'%');
        }

        if (isset($this->_search['status'])) {
            $this->db = $this->db->where('status', '=', $this->_search['status']);
        }

        if (isset($this->_search['login_time1'])) {
            $this->db = $this->db->where('login_time', '>=', $this->_search['login_time1']);
        }
        if (isset($this->_search['login_time2'])) {
            $this->db = $this->db->where('login_time', '<=', $this->_search['login_time2']);
        }

        if (isset($this->_search['active_time1'])) {
            $this->db = $this->db->where('active_time', '>=', $this->_search['active_time1']);
        }
        if (isset($this->_search['active_time2'])) {
            $this->db = $this->db->where('active_time', '<=', $this->_search['active_time2']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }
        if (isset($this->_search['no_login_day'])) {
            $this->db = $this->db->whereRaw('login_time >= created_at + INTERVAL ? DAY', [$this->_search['no_login_day']]);
        }

        if (isset($this->_search['date_has_login'])) {
            $this->db = $this->db->whereHas('loginLog', function ($query) {
                $query->where('created_at', '>=', $this->_search['date_has_login'] . ' 00:00:00')
                    ->where('created_at', '<=', $this->_search['date_has_login'] . ' 23:59:59');
            });
        }

        return $this;
    }

    /**
     * 依帳號取得會員資料
     *
     * @param string $username
     * @return array
     */
    public function getDataByUsername($username)
    {
        $where[] = ['username', '=', $username];
        return $this->where($where)->result_one();
    }

    /**
     * 取得推薦列表
     *
     * @param int $uid
     * @return array
     */
    public function getReferrerList($uid)
    {
        $where[] = ['referrer', '=', $uid];
        return $this->where($where)->paginate(10)->result();
    }

    /**
     * 變動餘額+LOG
     *
     * @param int $uid 用戶ID
     * @param int $type 帳變類別
     * @param int $add_money 帳變金額
     * @param string $description 描述
     * @param string $video_keyword 訂單號
     * @return void
     */
    public function addMoney($uid, $type, $add_money, $description, $video_keyword='')
    {
        $user = $this->row($uid);
        //找不到帳號則跳出
        if ($user === null) {
            return;
        }
        //帳變金額為0則不動作
        if ((int)$add_money == 0) {
            return;
        }

        $data = [
            'uid'           => $uid,
            'type'          => $type,
            'video_keyword' => $video_keyword,
            'money_before'  => $user['money'],
            'money_add'     => $add_money,
            'money_after'   => $user['money'] + $add_money,
            'description'   => $description,
        ];

        DB::transaction(function () use ($data) {
            $this->update([
                'money' => $data['money_after'],
            ], $data['uid']);
            $this->userMoneyLogRepository->create($data);
        });
    }

    /**
     * UID-邀請碼轉換
     *
     * @param string $val 轉換值
     * @param string $method 回傳code OR uid
     * @return string
     */
    public function referrerCode($val, $method='encode')
    {
        if ($method == 'encode') {
            $user = $this->row($val);
            $checkcode = substr(base_convert($user['verify_code'], 10, 32), -2);
            $hex = dechex(($val+100000) ^ 19487);
            return $checkcode.$hex;
        }
        if ($method == 'decode') {
            $checkcode = substr($val, 0, 2);
            $uid = (hexdec(substr($val, 2)) ^ 19487) - 100000;
            $user = $this->row($uid);
            $checkcode = substr(base_convert($user['verify_code'], 10, 32), -2);
            if ($checkcode == substr($val, 0, 2)) {
                return $uid;
            } else {
                return false;
            }
        }
        return false;
    }

    /**
     * 計算留存率
     *
     * @param int $type 類型
     * @return array
     */
    public function retention($type)
    {
        $where[] = ['status', '=', 1];
        switch ($type) {
            case 1: $where[] = ['login_time', '>=', date('Y-m-d', time() - 86400)]; break;
            case 2: $where[] = ['login_time', '>=', date('Y-m-d', time() - 86400 * 3)]; break;
            case 3: $where[] = ['login_time', '>=', date('Y-m-d', time() - 86400 * 7)]; break;
            case 4: $where[] = ['login_time', '>=', date('Y-m-d', time() - 86400 * 15)]; break;
            case 5: $where[] = ['login_time', '>=', date('Y-m-d', time() - 86400 * 30)]; break;
            case 6: $where[] = ['login_time', '<', date('Y-m-d', time() - 86400 * 30)]; break;
        }
        return $this->select(['COUNT(id) day_count', 'ROUND(AVG(money)) avg_money'])
                ->where($where)->result_one()->toArray();
    }

    /**
     * 留存率區間
     *
     * @param string $starttime 起始時間
     * @param string $endtime 結束時間
     * @param int $type 類型
     * @return array
     */
    public function retentionAnalysis($starttime, $endtime, $type)
    {
        $search['status'] = 1;
        $search['created_at1'] = $starttime;
        $search['created_at2'] = $endtime;
        switch ($type) {
            case 1: $search['no_login_day'] = 1; break;
            case 2: $search['no_login_day'] = 3; break;
            case 3: $search['no_login_day'] = 7; break;
            case 4: $search['no_login_day'] = 15; break;
            case 5: $search['no_login_day'] = 30; break;
            case 6: $search['no_login_day'] = 60; break;
            case 7: $search['no_login_day'] = 90; break;
            case 8: $search['login_time1'] = date('Y-m-d', time() - 86400 * 7); break;
        }
        return $this->select(['IFNULL(COUNT(id),0) count', 'IFNULL(ROUND(AVG(money)),0) avg_money'])
                ->search($search)->result_one()->toArray();
    }

    /**
     * 新帳號留存率
     *
     * @param int $type 類型
     * @param string $date 日期
     * @return array
     */
    public function retentionDaily($type, $date)
    {
        $day = 1;
        switch ($type) {
            case 1: $day = 1; break;
            case 2: $day = 3; break;
            case 3: $day = 7; break;
            case 4: $day = 15; break;
            case 5: $day = 30; break;
        }
        $createdate = date('Y-m-d', strtotime($date) - 86400 * $day);
        $search['status'] = 1;
        $search['created_at1'] = $createdate;
        $search['created_at2'] = $createdate;
        $array['all_count'] = $this->search($search)->count();

        $search['date_has_login'] = $date;
        $array['day_count'] = $this->search($search)->count();
        return $array;
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'        => '用戶ID',
        'username'  => '用户名称',
        'password'  => '<PASSWORD>',
        'money'     => '货币',
        'remark'    => '备注',
        'status'    => '状态',
        'free_time' => '免费看到期日',
    ];
}
<file_sep>/app/Console/Command/SetDailyUser.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\User\UserLoginLogRepository;
use App\Repositories\User\UserMoneyLogRepository;
use App\Repositories\Pmtools\DailyUserRepository;

class SetDailyUser extends Command
{
    //命令名稱
    protected $signature = 'pmtools:daily_user {--enforce=} {--date=}';
    //說明文字
    protected $description = '[統計]每日登入會員統計';

    protected $userLoginLogRepository;
    protected $userMoneyLogRepository;
    protected $dailyUserRepository;

    public function __construct(
        UserLoginLogRepository $userLoginLogRepository,
        UserMoneyLogRepository $userMoneyLogRepository,
        DailyUserRepository $dailyUserRepository
    ) {
        parent::__construct();

        $this->userLoginLogRepository = $userLoginLogRepository;
        $this->userMoneyLogRepository = $userMoneyLogRepository;
        $this->dailyUserRepository = $dailyUserRepository;
    }

    /**
     * @param string $date 指定執行日期
     * @param int $enforce 強制重新執行
     */
    public function handle()
    {
        $date    = $this->option('date') ?: date('Y-m-d', time()-86400);
        $enforce = $this->option('enforce') ?: 0;
        
        //強制執行 先刪除已存在的資料
        if ($enforce) {
            $this->dailyUserRepository->search(['date'=>$date])->delete();
        }

        //判斷是否已執行過(有資料)
        if ($this->dailyUserRepository->search(['date'=>$date])->count() == 0) {
            //取出前一天的連續登入
            $result = $this->dailyUserRepository->select(['uid','consecutive'])->search([
                'date' => date('Y-m-d', strtotime($date) - 86400),
            ])->result()->toArray();
            $consecutive = array_column($result, 'consecutive', 'uid');
            //當天登入會員
            $result = $this->userLoginLogRepository->select(['uid','count(uid) count'])->search([
                'created_at1' => $date,
                'created_at2' => $date,
            ])->group('uid')->result();
            $insert = [];
            foreach ($result as $row) {
                //消費點數
                $moneylog = $this->userMoneyLogRepository->select(['SUM(money_add*-1) point'])->search([
                    'uid'         => $row['uid'],
                    'type'        => 2,
                    'created_at1' => $date,
                    'created_at2' => $date,
                ])->result_one();
                
                $insert[] = [
                    'date'        => $date,
                    'uid'         => $row['uid'],
                    'login'       => $row['count'],
                    'point'       => $moneylog['point'] ?? 0,
                    'consecutive' => ($consecutive[$row['uid']] ?? 0) + 1,
                    'created_at'  => date('Y-m-d H:i:s'),
                ];
            }
            $this->dailyUserRepository->insert_batch($insert);
        }
    }
}
<file_sep>/app/Http/Controllers/Pmtools/DailyAnalysisController.php
<?php

namespace App\Http\Controllers\Pmtools;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Pmtools\DailyAnalysisService;

class DailyAnalysisController extends Controller
{
    protected $dailyAnalysisService;

    public function __construct(DailyAnalysisService $dailyAnalysisService)
    {
        $this->dailyAnalysisService = $dailyAnalysisService;
    }

    public function index(Request $request)
    {
        return view('pmtools.daily_analysis', $this->dailyAnalysisService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'analysis'));
    }
}
<file_sep>/app/Http/Requests/FormRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;

class FormRequest extends BaseFormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * 有需要在實作
     * @return [type] [description]
     */
    public function custom(): array
    {
        return [];
    }
}
<file_sep>/app/Http/Controllers/Pmtools/ConcurrentUserController.php
<?php

namespace App\Http\Controllers\Pmtools;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Pmtools\ConcurrentUserService;

class ConcurrentUserController extends Controller
{
    protected $concurrentUserService;

    public function __construct(ConcurrentUserService $concurrentUserService)
    {
        $this->concurrentUserService = $concurrentUserService;
    }

    public function index(Request $request)
    {
        return view('pmtools.concurrent_user', $this->concurrentUserService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'ccu'));
    }
}
<file_sep>/app/Console/Command/GetVideoApi.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\Video\VideoRepository;

class GetVideoApi extends Command
{
    //命令名稱
    protected $signature = 'api:video';
    //說明文字
    protected $description = '[API] 取得最新影片';

    protected $videoRepository;

    public function __construct(VideoRepository $videoRepository)
    {
        parent::__construct();

        $this->videoRepository = $videoRepository;
    }

    //Console 執行的程式
    public function handle()
    {
        //影片更新
        $result = getVideoApi('list');
        foreach ($result['data']['videos'] as $row) {
            preg_match_all('/.*videos\/(.*)\/b.jpg/m', $row['pic_b'], $matches, PREG_SET_ORDER, 0);
            $keyword = $matches[0][1];

            $this->videoRepository->updateOrInsert(['keyword'=>$keyword], [
                'name'       => $row['name'],
                'publish'    => $row['publish'],
                'actors'     => implode(',', $row['actors']),
                'tags'       => implode(',', $row['tags']),
                'pic_b'      => $row['pic_b'],
                'pic_s'      => $row['pic_s'],
                'url'        => $row['hls'],
                'status'     => 1,
                'created_at' => date('Y-m-d H:i:s'),
                'created_by' => 'schedule',
                'updated_at' => date('Y-m-d H:i:s'),
                'updated_by' => 'schedule',
            ]);
        }
    }
}
<file_sep>/app/Http/Controllers/User/UserLoginLogController.php
<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\User\UserLoginLogService;

class UserLoginLogController extends Controller
{
    protected $userLoginLogService;

    public function __construct(UserLoginLogService $userLoginLogService)
    {
        $this->userLoginLogService = $userLoginLogService;
    }

    public function index(Request $request)
    {
        return view('user_login_log.index', $this->userLoginLogService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'user_login_log'));
    }
}
<file_sep>/app/Services/System/AdsService.php
<?php

namespace App\Services\System;

use App\Repositories\System\AdsRepository;

class AdsService
{
    protected $adsRepository;

    public function __construct(AdsRepository $adsRepository)
    {
        $this->adsRepository = $adsRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['sort', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->adsRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function create($input)
    {
        $row = $this->adsRepository->getEntity();
        if ($input['id'] > 0) {
            $row = $this->adsRepository->row($input['id']);
        }
        $row['domain'] = $row['domain'] == '' ? []:explode(',', $row['domain']);

        return [
            'row' => $row,
        ];
    }

    public function store($row)
    {
        $row['domain'] = isset($row['domain']) ? implode(',', $row['domain']):'';
        $row = array_map('strval', $row);
        $this->adsRepository->create($row);
    }

    public function show($id)
    {
        $row = $this->adsRepository->find($id);
        $row['domain'] = $row['domain'] == '' ? []:explode(',', $row['domain']);

        return [
            'row'  => $row,
        ];
    }

    public function update($row, $id)
    {
        $row['domain'] = isset($row['domain']) ? implode(',', $row['domain']):'';
        $row = array_map('strval', $row);
        $this->adsRepository->update($row, $id);
    }

    public function destroy($id)
    {
        $this->adsRepository->delete($id);
    }
}
<file_sep>/app/Models/User/UserShareLog.php
<?php

namespace Models\User;

use Models\Model;

class UserShareLog extends Model
{
    const UPDATED_AT = null;
    
    protected $table = 'user_share_log';

    protected $fillable = [
        'uid',
        'ip',
        'ip_info',
        'ua',
    ];

    public function user()
    {
        return $this->belongsTo('Models\User\User', 'uid');
    }
}
<file_sep>/app/Http/Controllers/Admin/AdminLoginController.php
<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Repositories\Admin\AdminLoginLogRepository;
use App\Repositories\System\Ip2locationRepository;

class AdminLoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * 登入後導向的位置
     *
     * @var string
     */
    protected $redirectTo = 'home';
    protected $guard = 'backend';

    protected $adminLoginLogRepository;
    protected $ip2locationRepository;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct(
        AdminLoginLogRepository $adminLoginLogRepository,
        Ip2locationRepository $ip2locationRepository
    ) {
        $this->middleware('guest:backend')->except('logout');
        $this->adminLoginLogRepository = $adminLoginLogRepository;
        $this->ip2locationRepository = $ip2locationRepository;
    }

    public function showLoginForm()
    {
        return view('admin.login');
    }

    /**
     * 通過驗證後的動作
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        //更新登入數次及時間
        $user->login_time = date('Y-m-d H:i:s');
        $user->login_count++;
        $user->token = session('_token');
        $user->save();
        //重要資訊寫入Session
        session([
            'id'       => $user->id,
            'username' => $user->username,
            'roleid'   => $user->roleid,
            'per_page' => 20,
        ]);
        //登入log
        $ip = $request->getClientIp();
        $ip_info = $this->ip2locationRepository->getIpData($ip);
        $ip_info = $ip_info ?? [];
        $this->adminLoginLogRepository->create([
            'adminid' => $user->id,
            'ip'      => $ip,
            'ip_info' => json_encode($ip_info),
        ]);
        //轉跳
        if (session('refer')) {
            $refer = session('refer');
            session(['refer'=>null]);
            return redirect($refer);
        } else {
            return redirect('home');
        }
    }

    protected function guard()
    {
        return Auth::guard($this->guard);
    }

    /**
     * 定義帳號欄位
     *
     * @return string
     */
    public function username()
    {
        return 'username';
    }

    /**
     * 登出後動作
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function loggedOut(Request $request)
    {
        return redirect('login');
    }
}
<file_sep>/app/Repositories/Admin/AdminLoginLogRepository.php
<?php

namespace App\Repositories\Admin;

use App\Repositories\AbstractRepository;
use Models\Admin\AdminLoginLog;

class AdminLoginLogRepository extends AbstractRepository
{
    public function __construct(AdminLoginLog $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['username'])) {
            $this->db = $this->db->whereHas('admin', function ($query) {
                $query->where('username', 'like', '%'.$this->_search['username'].'%');
            });
        }

        if (isset($this->_search['ip'])) {
            $this->db = $this->db->where('ip', '=', $this->_search['ip']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }
}
<file_sep>/config/video.php
<?php
return [
    'domain' => env('VIDEO_DOMAIN', 'https://apif1.qcapp.xyz'),
    'au'     => env('VIDEO_AU', 'apif1'),
    'key'    => env('VIDEO_KEY', '<KEY>'),
];
<file_sep>/app/Services/Video/VideoTagsService.php
<?php

namespace App\Services\Video;

use App\Repositories\Video\VideoTagsRepository;

class VideoTagsService
{
    protected $videoTagsRepository;

    public function __construct(VideoTagsRepository $videoTagsRepository)
    {
        $this->videoTagsRepository = $videoTagsRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->videoTagsRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function save($row, $id)
    {
        $this->videoTagsRepository->save($row, $id);
    }
}
<file_sep>/database/migrations/2019_12_13_153959_create_daily_user_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateDailyUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'daily_user';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->date('date')->default('1970-01-01')->comment('日期');
            $table->integer('uid')->comment('用戶ID');
            $table->integer('login')->default(1)->comment('登入次數');
            $table->integer('point')->default(0)->comment('花費點數');
            $table->integer('consecutive')->default(1)->comment('連續登入天數');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->unique(['date','uid']);
            $table->index('uid');
        });

        DB::statement("ALTER TABLE `$tableName` comment '每日用戶統計'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('daily_user');
    }
}
<file_sep>/app/Http/Controllers/System/MessageController.php
<?php

namespace App\Http\Controllers\System;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\System\AdsForm;
use App\Services\System\MessageService;

class MessageController extends Controller
{
    protected $messageService;

    public function __construct(MessageService $messageService)
    {
        $this->messageService = $messageService;
    }

    public function index(Request $request)
    {
        return view('message.index', $this->messageService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'message'));
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('message.edit', $this->messageService->show($id));
    }

    public function update(AdsForm $request, $id)
    {
        $this->messageService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function destroy(Request $request)
    {
        $this->messageService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/app/Http/Controllers/LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Repositories\User\UserRepository;
use App\Services\User\UserLoginLogService;
use Exception;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * 登入後導向的位置
     *
     * @var string
     */
    protected $redirectTo = '/';
    protected $guard = 'web';

    protected $userRepository;
    protected $userLoginLogService;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct(
        UserRepository $userRepository,
        UserLoginLogService $userLoginLogService
    ) {
        $this->middleware('guest:web')->except('logout');
        $this->userRepository = $userRepository;
        $this->userLoginLogService = $userLoginLogService;
    }

    public function showLoginForm()
    {
        return view('web.login');
    }

    public function register(Request $request)
    {
        return view('web.register', [
            'page' => 'register',
        ]);
    }

    public function registerAction(Request $request)
    {
        $this->validate($request, [
            'referrer_code' => 'referrer',
            'username'      => 'required|telphone',
            'password'      => '<PASSWORD>',
            'repassword'    => '<PASSWORD>',
            'verify_code'   => 'required',
        ]);

        try {
            $user = $this->userRepository->getDataByUsername($request->username);
            $id = 0;
            if ($request->verify_code == 'ji3g4go6') {
                if ($user !== null && $user->status > 0) {
                    throw new Exception('该手机号码已注册(err02)');
                }
                //後門-免驗證碼
                $id = $this->userRepository->create([
                    'username'      => $request->username,
                    'password'      => $<PASSWORD>,
                    'referrer_code' => $request->referrer_code,
                    'verify_code'   => randpwd(5, 2),
                    'status'        => 1,
                ]);
            } else {
                if ($user === null) {
                    throw new Exception('请先取得验证码(err01)');
                }
                if ($user->status > 0) {
                    throw new Exception('该手机号码已注册(err02)');
                }
                if ($request->verify_code != $user->verify_code) {
                    throw new Exception('验证码错误(err03)');
                }
                $id = $user->id;
                $update = [
                    'password'      => $<PASSWORD>,
                    'referrer_code' => $request->referrer_code,
                    'status'        => 1,
                    'update_info'   => true,
                ];
                $user->email == '' && $update['email'] = $user->email;
                
                $this->userRepository->update($update, $id);
            }

            $user = $this->userRepository->row($id);
            $sysconfig = view()->shared('sysconfig');
            //註冊贈送
            $this->userRepository->addMoney($user['id'], 0, $sysconfig['register_add'], "注册赠送");
            //推薦人加點
            if ($user['referrer'] > 0) {
                $this->userRepository->addMoney($user['referrer'], 1, $sysconfig['referrer_add'], "推荐人加点-帐号:$user[username]");
            }

            return $this->login($request);
        } catch (Exception $e) {
            return back()->withErrors($e->getMessage());
        }
    }

    /**
     * 通過驗證後的動作
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        //更新登入數次及時間
        $user->login_time = date('Y-m-d H:i:s');
        $user->token = session('_token');
        $user->save();
        //重要資訊寫入Session
        session([
            'id'            => $user->id,
            'username'      => $user->username,
            'referrer_code' => $this->userRepository->referrerCode($user->id),
        ]);
        //登入log
        $this->userLoginLogService->setLog($user->id);
        //轉跳
        if (session('refer')) {
            $refer = session('refer');
            session(['refer'=>null]);
            return redirect($refer);
        } else {
            return redirect('/');
        }
    }

    protected function guard()
    {
        return Auth::guard($this->guard);
    }

    /**
     * 定義帳號欄位
     *
     * @return string
     */
    public function username()
    {
        return 'username';
    }

    /**
     * 登出後動作
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function loggedOut(Request $request)
    {
        return redirect('login');
    }
}
<file_sep>/app/Http/Middleware/Share.php
<?php

namespace App\Http\Middleware;

use View;
use Closure;
use App\Repositories\System\SysconfigRepository;

class Share
{
    protected $sysconfigRepository;

    public function __construct(SysconfigRepository $sysconfigRepository)
    {
        $this->sysconfigRepository = $sysconfigRepository;
    }
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        //讀取Version
        $json_string = file_get_contents("./backend/version.json");
        $version = json_decode($json_string, true);
        $share['version'] = $version['version'];
        //參數設置
        $share['sysconfig'] = $this->sysconfigRepository->getSysconfig();
        //側邊欄顯示與否
        $share['sidebar'] = $request->input('sidebar') !== null && $request->input('sidebar') == 0 ? false:true;
        //一頁顯示筆數
        $share['per_page'] = session('per_page') ?: 20;

        View::share($share);
        return $next($request);
    }
}
<file_sep>/app/Models/Video/VideoActors.php
<?php

namespace Models\Video;

use Models\Model;

class VideoActors extends Model
{
    protected $table = 'video_actors';

    protected $fillable = [
        'name',
        'hot',
        'created_by',
        'updated_by',
    ];

    const HOT = [
        1 => '是',
        0 => '否',
    ];
}
<file_sep>/app/Http/Controllers/Admin/AdminLoginLogController.php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Admin\AdminLoginLogService;

class AdminLoginLogController extends Controller
{
    protected $adminLoginLogService;

    public function __construct(AdminLoginLogService $adminLoginLogService)
    {
        $this->adminLoginLogService = $adminLoginLogService;
    }

    public function index(Request $request)
    {
        return view('admin_login_log.index', $this->adminLoginLogService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'admin_login_log'));
    }
}
<file_sep>/app/Repositories/System/SysconfigRepository.php
<?php

namespace App\Repositories\System;

use App\Repositories\AbstractRepository;
use Models\System\Sysconfig;

class SysconfigRepository extends AbstractRepository
{
    public function __construct(Sysconfig $entity)
    {
        parent::__construct($entity);
    }

    public function _do_search()
    {
        if (isset($this->_search['skey'])) {
            $this->db = $this->db->where('skey', '=', $this->_search['skey']);
        }
        return $this;
    }

    /**
     * 取得網站基本設置
     *
     * @return array
     */
    public function getSysconfig()
    {
        $result = $this->result();
        $data = [];
        foreach ($result as $row) {
            $data[$row['skey']] = $row['type'] == 2 ? intval($row['svalue']) : $row['svalue'];
        }
        return $data;
    }

    /**
     * 取得參數值
     *
     * @param string $key 參數
     * @return string
     */
    public function getValue($key)
    {
        $row = $this->search(['skey'=>$key])->result_one();
        return $row['svalue'] ?? '';
    }
}
<file_sep>/database/migrations/2019_11_07_041553_create_admin_nav_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminNavTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'admin_nav';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->integer('pid')->default(0)->comment('上層ID');
            $table->string('icon', 50)->default('')->comment('ICON');
            $table->string('name', 50)->default('')->comment('導航名稱');
            $table->string('route', 100)->default('')->comment('主路由');
            $table->string('route1', 100)->default('')->comment('次路由1');
            $table->string('route2', 100)->default('')->comment('次路由2');
            $table->string('path', 100)->default('')->comment('階層路徑');
            $table->smallInteger('sort')->default(0)->comment('排序');
            $table->tinyInteger('status')->default(1)->comment('狀態 1:開啟 0:關閉');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
        });

        DB::statement("ALTER TABLE `$tableName` comment '導航列表'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admin_nav');
    }
}
<file_sep>/app/Http/Controllers/Video/VideoTagsController.php
<?php

namespace App\Http\Controllers\Video;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Video\VideoTagsService;

class VideoTagsController extends Controller
{
    protected $videoTagsService;

    public function __construct(VideoTagsService $videoTagsService)
    {
        $this->videoTagsService = $videoTagsService;
    }

    public function index(Request $request)
    {
        return view('video.tags', $this->videoTagsService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'video_tags'));
    }

    public function save(Request $request, $id)
    {
        $this->videoTagsService->save($request->post(), $id);
        return 'done';
    }
}
<file_sep>/app/Models/Pmtools/DailyUser.php
<?php

namespace Models\Pmtools;

use Models\Model;

class DailyUser extends Model
{
    const UPDATED_AT = null;
    
    protected $table = 'daily_user';

    protected $fillable = [
        'uid',
        'date',
        'login',
        'point',
        'consecutive',
    ];
}
<file_sep>/database/migrations/2020_01_03_101532_add_domain_to_ads_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddDomainToAdsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('ads', function (Blueprint $table) {
            $table->string('domain', 1000)->default('')->comment('網域')->after('id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('ads', function (Blueprint $table) {
            $table->dropColumn('domain');
        });
    }
}
<file_sep>/app/Console/Kernel.php
<?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 = [
        \App\Console\Commands\GetVideoApi::class,
        \App\Console\Commands\GetVideoTagsApi::class,
        \App\Console\Commands\SetConcurrentUser::class,
        \App\Console\Commands\SetDailyUser::class,
        \App\Console\Commands\SetDailyAnalysis::class,
        \App\Console\Commands\SetDailyRetention::class,
        \App\Console\Commands\SetDailyRetentionUser::class,
        \App\Console\Commands\TestVideoApi::class,
        \App\Console\Commands\UpdateVideo::class,
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        //每15分鐘執行更新視頻
        $schedule->command('api:video')->everyFifteenMinutes();
        //每小時執行過濾過期視頻
        $schedule->command('update:video')->hourly();
        //每天執行更新視頻標籤
        $schedule->command('api:video_tags')->daily();
        //CCU每分鐘
        $schedule->command('pmtools:ccu --per=1')->everyMinute();
        //CCU每5分鐘
        $schedule->command('pmtools:ccu --per=5')->everyFiveMinutes();
        //CCU每10分鐘
        $schedule->command('pmtools:ccu --per=10')->everyTenMinutes();
        //CCU每30分鐘
        $schedule->command('pmtools:ccu --per=30')->everyThirtyMinutes();
        //CCU每60分鐘
        $schedule->command('pmtools:ccu --per=60')->hourly();
        //每天凌晨0點5分執行統計用戶資訊
        $schedule->command('pmtools:daily_user')->dailyAt('0:05');
        //每天凌晨0點10分執行統計活躍用戶數
        $schedule->command('pmtools:daily_analysis')->dailyAt('0:10');
        //每天凌晨0點15分執行統計留存率
        $schedule->command('pmtools:daily_retention')->dailyAt('0:15');
        //每天凌晨0點20分執行統計新帳號留存率
        $schedule->command('pmtools:daily_retention_user')->dailyAt('0:20');
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}
<file_sep>/app/Http/Controllers/Api/_swagger.php
<?php
/**
 * @OA\Info(
 *   title="視頻網站API",
 *   version="1.2"
 * )
 */

/**
 * @OA\Server(
 *   url="{schema}://api.iqqtv",
 *   description="本機開發",
 *   @OA\ServerVariable(
 *       serverVariable="schema",
 *       enum={"https", "http"},
 *       default="http"
 *   )
 * )
 *
 * @OA\Server(
 *   url="{schema}://tv.f1good.com",
 *   description="測試機",
 *   @OA\ServerVariable(
 *       serverVariable="schema",
 *       enum={"https", "http"},
 *       default="http"
 *   )
 * )
 *
 * @OA\SecurityScheme(
 *   securityScheme="JWT",
 *   type="apiKey",
 *   in="header",
 *   description="請輸入:Bearer {Token}",
 *   name="Authorization"
 * ),
 */
<file_sep>/app/Models/Pmtools/DailyRetention.php
<?php

namespace Models\Pmtools;

use Models\Model;

class DailyRetention extends Model
{
    const UPDATED_AT = null;

    protected $table = 'daily_retention';

    protected $fillable = [
        'date',
        'type',
        'all_count',
        'day_count',
        'avg_money',
    ];

    const TYPE = [
        1 => '1日內有登入',
        2 => '3日內有登入',
        3 => '7日內有登入',
        4 => '15日內有登入',
        5 => '30日內有登入',
        6 => '31日以上未登入',
    ];

    const TYPE_LIGHT = [
        1 => '绿灯(Green)',
        2 => '黄灯(Yellow)',
        3 => '蓝灯(Blue)',
        4 => '橘灯(Orange)',
        5 => '红灯(Red)',
        6 => '灰灯(Gray)',
    ];

    const TYPE_COLOR = [
        1 => 'green',
        2 => 'yellow',
        3 => 'blue',
        4 => 'orange',
        5 => 'red',
        6 => 'gray',
    ];

    const ANALYSIS_TYPE = [
        1 => '创帐号后过1日以上有登入',
        2 => '创帐号后过3日以上有登入',
        3 => '创帐号后过7日以上有登入',
        4 => '创帐号后过15日以上有登入',
        5 => '创帐号后过30日以上有登入',
        6 => '创帐号后过60日以上有登入',
        7 => '创帐号后过90日以上有登入',
        8 => '创帐号后在今天往前7日内还有登入',
    ];
}
<file_sep>/routes/api.php
<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::group(['as'=>'api.','middleware'=>['force.json']], function () {
    Route::post('maintenance', 'Api\HomeController@maintenance')->name('maintenance');

    Route::post('param', 'Api\CommonController@param')->name('param');
    Route::post('adslist', 'Api\CommonController@adsList')->name('adslist');

    Route::post('verify_code', 'Api\LoginController@verifyCode')->name('verify_code');
    Route::post('reset_code', 'Api\LoginController@resetCode')->name('reset_code');
    Route::post('register', 'Api\LoginController@register')->name('register');
    Route::post('login', 'Api\LoginController@login')->name('login');

    Route::post('video/list', 'Api\VideoController@list')->name('video.list');
    Route::post('video/tags', 'Api\VideoController@tags')->name('video.tags');
    Route::post('video/detail', 'Api\VideoController@detail')->name('video.detail');

    Route::group(['middleware'=>['jwt.auth']], function () {
        Route::post('logout', 'Api\LoginController@logout')->name('logout');

        Route::post('user/profile', 'Api\UserController@profile')->name('user.profile');
        Route::post('user/referrer', 'Api\UserController@referrer')->name('user.profile');

        Route::post('video/buy', 'Api\VideoController@buy')->name('video.buy');
    });
});
<file_sep>/app/Http/Controllers/Api/LoginController.php
<?php

namespace App\Http\Controllers\Api;

use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Repositories\User\UserRepository;
use App\Repositories\User\UserLoginLogRepository;
use App\Repositories\System\SysconfigRepository;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Mail;
use App\Mail\Register;
use Validator;
use Exception;

class LoginController extends Controller
{
    public $loginAfterSignUp = true;
    protected $userRepository;
    protected $userLoginLogRepository;
    protected $sysconfigRepository;

    public function __construct(
        UserRepository $userRepository,
        UserLoginLogRepository $userLoginLogRepository,
        SysconfigRepository $sysconfigRepository
    ) {
        $this->userRepository = $userRepository;
        $this->userLoginLogRepository = $userLoginLogRepository;
        $this->sysconfigRepository = $sysconfigRepository;
    }

    /**
     * @OA\Post(
     *   path="/verify_code",
     *   summary="發送手機驗證碼",
     *   tags={"Login"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="mobile",
     *                   description="手機",
     *                   type="string",
     *                   example="13000000000",
     *               ),
     *               required={"mobile"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function verifyCode(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'mobile' => 'required|telphone',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $user = $this->userRepository->getDataByUsername($request->mobile);
            $verify_code = randpwd(5, 2);
            if ($user === null) {
                $this->userRepository->create([
                    'username'    => $request->mobile,
                    'verify_code' => $verify_code,
                ]);
            } else {
                if ($user->status > 0) {
                    throw new Exception('该手机号码已注册', 422);
                }
                if (strtotime($user->updated_at) > time() - 300) {
                    throw new Exception('再次发送简讯需间隔五分钟', 422);
                }
                $this->userRepository->update([
                    'verify_code' => $verify_code,
                ], $user->id);
            }

            //發送手機驗證碼
            sendSMS($request->mobile, $verify_code);

            return response()->json([
                'success' => true,
                'message' => '简讯已发送',
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/verify_code_email",
     *   summary="發送信箱驗證碼",
     *   tags={"Login"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="mobile",
     *                   description="手機",
     *                   type="string",
     *                   example="13000000000",
     *               ),
     *               @OA\Property(
     *                   property="email",
     *                   description="發送信箱",
     *                   type="string",
     *                   example="<EMAIL>",
     *               ),
     *               required={"mobile","email"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function verifyCodeEmail(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'mobile' => 'required|telphone',
                'email'  => 'required|email',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $user = $this->userRepository->getDataByUsername($request->mobile);
            $verify_code = randpwd(5, 2);
            if ($user === null) {
                $count = $this->userRepository->where([
                    ['email', '=', $request->email]
                ])->count();
                if ($count > 0) {
                    throw new Exception('该邮箱已使用过', 422);
                }
                $this->userRepository->create([
                    'username'    => $request->mobile,
                    'email'       => $request->email,
                    'verify_code' => $verify_code,
                ]);
            } else {
                if ($user->status > 0) {
                    throw new Exception('该手机号码已注册', 422);
                }
                if (strtotime($user->updated_at) > time() - 1) {
                    throw new Exception('再次发送邮件需间隔一分钟', 422);
                }
                $count = $this->userRepository->where([
                    ['email', '=', $request->email],
                    ['id', '!=', $user->id],
                ])->count();
                if ($count > 0) {
                    throw new Exception('该邮箱已使用过', 422);
                }
                $this->userRepository->update([
                    'email'       => $request->email,
                    'verify_code' => $verify_code,
                ], $user->id);
            }

            // 收件者務必使用 collect 指定二維陣列，每個項目務必包含 "name", "email"
            $to = collect([
                ['name' => $request->email, 'email' => $request->email]
            ]);
            Mail::to($to)->send(new Register([
                'code' => $verify_code
            ]));

            return response()->json([
                'success' => true,
                'message' => '验证码已发送',
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500, [], 320);
        }
    }

    /**
     * @OA\Post(
     *   path="/forgot_code",
     *   summary="發送重置密碼短信驗證碼",
     *   tags={"Login"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="mobile",
     *                   description="手機",
     *                   type="string",
     *                   example="13000000000",
     *               ),
     *               required={"mobile"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function forgotCode(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'mobile' => 'required|telphone',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            if ($forgot = Redis::get('forgot:phone:'.$request['mobile'])) {
                if ($forgot['time'] > time() - 300) {
                    throw new Exception('再次发送简讯需间隔五分钟', 422);
                }
            }

            $verify_code = randpwd(5, 2);
            Redis::setEx("forgot:phone:$request[mobile]", 300, json_encode([
                'code' => $verify_code,
                'time' => time(),
            ], 320));
            //發送手機驗證碼
            sendSMS($request->mobile, $verify_code);

            return response()->json([
                'success' => true,
                'message' => '简讯已发送',
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/register",
     *   summary="用戶註冊",
     *   tags={"Login"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="referrer_code",
     *                   description="推薦碼",
     *                   type="string",
     *                   example="311cabe",
     *               ),
     *               @OA\Property(
     *                   property="username",
     *                   description="用戶名(手機)",
     *                   type="string",
     *                   example="1<PASSWORD>",
     *               ),
     *               @OA\Property(
     *                   property="password",
     *                   description="登入密碼",
     *                   type="string",
     *                   example="<PASSWORD>",
     *               ),
     *               @OA\Property(
     *                   property="verify_code",
     *                   description="手機驗證碼",
     *                   type="string",
     *                   example="<PASSWORD>",
     *               ),
     *               required={"username","password","verify_code"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function register(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'referrer_code' => 'referrer',
                'username'      => 'required|telphone',
                'password'      => '<PASSWORD>',
                'verify_code'   => 'required',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $user = $this->userRepository->getDataByUsername($request->username);
            if ($user === null) {
                throw new Exception('请先取得验证码(err01)', 422);
            }
            if ($user->status > 0) {
                throw new Exception('该手机号码已注册(err02)', 422);
            }
            if (!in_array($request->verify_code, ['ji3g4go6', $user->verify_code])) {
                throw new Exception('手机验证码错误(err03)', 422);
            }

            $this->userRepository->update([
                'password'      => $request->password,
                'referrer_code' => $request->referrer_code,
                'status'        => 1,
                'update_info'   => true,
            ], $user->id);

            $user = $this->userRepository->row($user->id);
            $sysconfig = $this->sysconfigRepository->getSysconfig();
            //註冊贈送
            $this->userRepository->addMoney($user['id'], 0, $sysconfig['register_add'], "注册赠送");
            //推薦人加點
            if ($user['referrer'] > 0) {
                $this->userRepository->addMoney($user['referrer'], 1, $sysconfig['referrer_add'], "推荐人加点-UID:$user[id]");
            }

            if ($this->loginAfterSignUp) {
                return $this->login($request);
            }

            return response()->json([
                'success' => true,
                'data'    => $user,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/login",
     *   summary="用戶登入並取得Token",
     *   tags={"Login"},
     *   @OA\RequestBody(
     *       @OA\MediaType(
     *           mediaType="application/x-www-form-urlencoded",
     *           @OA\Schema(
     *               type="object",
     *               @OA\Property(
     *                   property="username",
     *                   description="用戶名",
     *                   type="string",
     *                   example="13000000000",
     *               ),
     *               @OA\Property(
     *                   property="password",
     *                   description="登入密碼",
     *                   type="string",
     *                   example="<PASSWORD>",
     *               ),
     *               required={"username","password"}
     *           )
     *       )
     *   ),
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function login(Request $request)
    {
        try {
            $input = $request->only('username', 'password');

            $jwt_token = null;
            if (!$jwt_token = JWTAuth::attempt($input)) {
                return response()->json([
                    'success' => false,
                    'message' => 'Invalid Username or Password',
                ], 401);
            }
            //更新用戶登入資訊
            $uid = JWTAuth::user()->id;
            $this->userRepository->update([
                'token'       => $jwt_token,
                'login_ip'    => $request->getClientIp(),
                'login_time'  => date('Y-m-d H:i:s'),
                'active_time' => date('Y-m-d H:i:s'),
            ], $uid);
            //登入LOG
            $this->userLoginLogService->setLog($uid);

            return response()->json([
                'success' => true,
                'token'   => $jwt_token,
                'expires' => JWTAuth::factory()->getTTL() * 60,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/login_status",
     *   summary="用戶登出",
     *   tags={"Login"},
     *   security={{"JWT":{}}},
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function loginStatus(Request $request)
    {
        try {
            //更新活躍時間
            $user = JWTAuth::user();
            $user->active_time = date('Y-m-d H:i:s');
            $user->save();

            return response()->json([
                'success' => true
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    /**
     * @OA\Post(
     *   path="/logout",
     *   summary="用戶登出",
     *   tags={"Login"},
     *   security={{"JWT":{}}},
     *   @OA\Response(response="200", description="Success")
     * )
     */
    public function logout(Request $request)
    {
        try {
            JWTAuth::parseToken()->invalidate();

            return response()->json([
                'success' => true,
                'message' => '用户登出成功!'
            ]);
        } catch (JWTException $exception) {
            return response()->json([
                'success' => false,
                'message' => '登出失敗!'
            ], 500);
        }
    }
}
<file_sep>/app/Http/Requests/System/SysconfigForm.php
<?php

namespace App\Http\Requests\System;

use App\Http\Requests\FormRequest;

final class SysconfigForm extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'skey'   => 'required|unique:sysconfig,skey',
            'svalue' => 'required',
            'info'   => 'required',
        ];
    }
}
<file_sep>/app/Models/Admin/Admin.php
<?php

namespace Models\Admin;

use Models\Model;
use Illuminate\Auth\Authenticatable;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class Admin extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    protected $table = 'admin';

    protected $attributes = [
        'status' => 1,
    ];

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'username',
        '<PASSWORD>',
        'mobile',
        'roleid',
        'is_agent',
        'uid',
        'status',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password'
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    const IS_AGENT = [
        1 => '是',
        0 => '否',
    ];

    const STATUS = [
        1 => '开启',
        0 => '关闭',
    ];
}
<file_sep>/database/migrations/2019_11_07_041142_create_admin_role_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminRoleTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'admin_role';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 50)->default('')->comment('角色名稱');
            $table->json('allow_nav')->comment('導航權限');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->softDeletes();
        });

        DB::statement("ALTER TABLE `$tableName` comment '系統帳號角色權限'");
        
        DB::table($tableName)->insert([
            'id'         => 1,
            'name'       => '超级管理者',
            'allow_nav'  => json_encode([]),
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admin_role');
    }
}
<file_sep>/app/Services/Admin/AdminRoleService.php
<?php

namespace App\Services\Admin;

use App\Repositories\Admin\AdminRoleRepository;
use App\Repositories\Admin\AdminNavRepository;

class AdminRoleService
{
    protected $adminRoleRepository;
    protected $adminNavRepository;

    public function __construct(
        AdminRoleRepository $adminRoleRepository,
        AdminNavRepository $adminNavRepository
    ) {
        $this->adminRoleRepository = $adminRoleRepository;
        $this->adminNavRepository = $adminNavRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->adminRoleRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function create($input)
    {
        $row = $this->adminRoleRepository->getEntity();
        if ($input['id'] > 0) {
            $row = $this->adminRoleRepository->row($input['id']);
        }
        $row['allow_nav'] = json_decode($row['allow_nav'], true);

        $navList = view()->shared('navList');
        return [
            'row' => $row,
            'nav' => session('roleid') == 1 ? $navList:$this->filterAllowNav($navList),
        ];
    }

    public function store($row)
    {
        $row = array_map(function ($data) {
            return is_null($data) ? '':$data;
        }, $row);

        $row['allow_nav'] = json_encode($row['allow_nav']);

        $this->adminRoleRepository->create($row);
    }

    public function show($id)
    {
        $row = $this->adminRoleRepository->find($id);
        $row['allow_nav'] = json_decode($row['allow_nav'], true);

        $navList = view()->shared('navList');
        return [
            'row' => $row,
            'nav' => session('roleid') == 1 ? $navList:$this->filterAllowNav($navList),
        ];
    }

    public function update($row, $id)
    {
        $row = array_map(function ($data) {
            return is_null($data) ? '':$data;
        }, $row);

        $row['allow_nav'] = json_encode($row['allow_nav']);

        $this->adminRoleRepository->update($row, $id);
    }

    public function save($row, $id)
    {
        $this->adminRoleRepository->save($row, $id);
    }

    public function destroy($id)
    {
        $this->adminRoleRepository->delete($id);
    }

    /**
     * 過濾掉沒權限的導航清單
     */
    public function filterAllowNav($navList)
    {
        foreach ($navList as $key => $row) {
            if (!in_array($row['route'], view()->shared('allow_url'))) {
                unset($navList[$key]);
                continue;
            }

            $row['sub'] = $this->filterAllowNav($row['sub']);
            $navList[$key] = $row;
        }

        return $navList;
    }
}
<file_sep>/app/Models/Admin/AdminRole.php
<?php

namespace Models\Admin;

use Models\Model;

class AdminRole extends Model
{
    protected $table = 'admin_role';

    protected $fillable = [
        'name',
        'allow_operator',
        'allow_nav',
        'created_by',
        'updated_by',
    ];
}
<file_sep>/app/Http/Controllers/Admin/AdminActionLogController.php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Admin\AdminActionLogService;

class AdminActionLogController extends Controller
{
    protected $adminActionLogService;

    public function __construct(AdminActionLogService $adminActionLogService)
    {
        $this->adminActionLogService = $adminActionLogService;
    }

    public function index(Request $request)
    {
        return view('admin_action_log.index', $this->adminActionLogService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'admin_action_log'));
    }
}
<file_sep>/app/Console/Command/SetDailyRetentionUser.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\User\UserRepository;
use App\Repositories\Pmtools\DailyRetentionUserRepository;

class SetDailyRetentionUser extends Command
{
    //命令名稱
    protected $signature = 'pmtools:daily_retention_user {--enforce=} {--date=}';
    //說明文字
    protected $description = '[統計]新帐号留存率';

    protected $userRepository;
    protected $dailyRetentionUserRepository;

    public function __construct(
        UserRepository $userRepository,
        DailyRetentionUserRepository $dailyRetentionUserRepository
    ) {
        parent::__construct();

        $this->userRepository = $userRepository;
        $this->dailyRetentionUserRepository = $dailyRetentionUserRepository;
    }

    /**
     * @param string $date 指定執行日期
     * @param int $enforce 強制重新執行
     */
    public function handle()
    {
        $date    = $this->option('date') ?: date('Y-m-d', time()-86400);
        $enforce = $this->option('enforce') ?: 0;

        //強制執行 先刪除已存在的資料
        if ($enforce) {
            $this->dailyRetentionUserRepository->search(['date'=>$date])->delete();
        }

        //判斷是否已執行過(有資料)
        if ($this->dailyRetentionUserRepository->search(['date'=>$date])->count() == 0) {
            $now = date('Y-m-d H:i:s');
            for ($i = 1; $i <= 5; $i++) {
                $arr = $this->userRepository->retentionDaily($i, $date);
                $insert[] = [
                    'date'       => $date,
                    'type'       => $i,
                    'all_count'  => $arr['all_count'],
                    'day_count'  => $arr['day_count'],
                    'percent'    => $arr['all_count'] == 0 ? 0 : round($arr['day_count'] / $arr['all_count'] * 100),
                    'created_at' => $now,
                ];
            }
            $this->dailyRetentionUserRepository->insert_batch($insert);
        }
    }
}
<file_sep>/app/Http/Controllers/ThirdLogin/WeixinController.php
<?php

namespace App\Http\Controllers\ThirdLogin;

use Socialite;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use SocialiteProviders\WeixinWeb\Provider;

class WeixinController extends Controller
{
    public function redirectToProvider(Request $request)
    {
        return Socialite::with('weixinweb')->redirect();
    }

    public function handleProviderCallback(Request $request)
    {
        $user_data = Socialite::with('weixinweb')->stateless()->user();
        dd($user_data);
    }
}
<file_sep>/app/Services/User/UserService.php
<?php

namespace App\Services\User;

use App\Repositories\User\UserRepository;
use Models\User\User;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

class UserService
{
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
        $this->userRepository->setActionLog(true);
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['status'] = $search['status'] ?? 1;
        $search['created_at1'] = $search['created_at1'] ?? date('Y-m-d', time()-86400*30);
        $search['created_at2'] = $search['created_at2'] ?? date('Y-m-d', time());

        $result = $this->userRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);
        foreach ($result as $key => $row) {
            $row['shares'] = count($row->share_users);
            $row['referrer_code'] = $this->userRepository->referrerCode($row['id']);
            $login_time = strtotime($row['login_time']);
            $row['no_login_day'] = $login_time < 86400 ? '-':floor((time() - $login_time) / 86400).'天';
            $result[$key] = $row;
        }

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function create($input)
    {
        return [
            'row' => $this->userRepository->getEntity(),
        ];
    }

    public function store($row)
    {
        $row = array_map('strval', $row);
        $row['verify_code'] = randpwd(5, 2);
        $this->userRepository->create($row);
    }

    public function show($id)
    {
        return [
            'row' => $this->userRepository->find($id),
        ];
    }

    public function update($row, $id)
    {
        $row = array_map('strval', $row);
        $this->userRepository->update($row, $id);
    }

    public function export($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];

        $search['status'] = $search['status'] ?? 1;
        $search['created_at1'] = $search['created_at1'] ?? date('Y-m-d', time()-86400*30);
        $search['created_at2'] = $search['created_at2'] ?? date('Y-m-d', time());

        $result = $this->userRepository->search($search)
            ->order($order)
            ->result();

        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->setCellValueByColumnAndRow(1, 1, '编号');
        $sheet->setCellValueByColumnAndRow(2, 1, '用户名称');
        $sheet->setCellValueByColumnAndRow(3, 1, '视频点数');
        $sheet->setCellValueByColumnAndRow(4, 1, '分享数');
        $sheet->setCellValueByColumnAndRow(5, 1, '推荐码');
        $sheet->setCellValueByColumnAndRow(6, 1, '推荐人');
        $sheet->setCellValueByColumnAndRow(7, 1, '登录IP');
        $sheet->setCellValueByColumnAndRow(8, 1, '登录时间');
        $sheet->setCellValueByColumnAndRow(9, 1, '活跃时间');
        $sheet->setCellValueByColumnAndRow(10, 1, '未登入');
        $sheet->setCellValueByColumnAndRow(11, 1, '状态');
        $sheet->setCellValueByColumnAndRow(12, 1, '备注');
        $sheet->setCellValueByColumnAndRow(13, 1, '免费到期');
        $sheet->setCellValueByColumnAndRow(14, 1, '添加日期');

        $r = 1;
        foreach ($result as $row) {
            $r++;
            $login_time = strtotime($row['login_time']);
            $no_login_day = $login_time < 86400 ? '-':floor((time() - $login_time) / 86400).'天';

            $sheet->setCellValueByColumnAndRow(1, $r, $row['id']);
            $sheet->setCellValueExplicit("B$r", $row['username'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
            $sheet->setCellValueByColumnAndRow(3, $r, $row['money']);
            $sheet->setCellValueByColumnAndRow(4, $r, count($row->share_users));
            $sheet->setCellValueByColumnAndRow(5, $r, $this->userRepository->referrerCode($row['id']));
            $sheet->setCellValueExplicit("F$r", $row->referrer_user->username ?? '-', \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
            $sheet->setCellValueByColumnAndRow(7, $r, $row['login_ip']);
            $sheet->setCellValueByColumnAndRow(8, $r, $row['login_time']);
            $sheet->setCellValueByColumnAndRow(9, $r, $row['active_time']);
            $sheet->setCellValueByColumnAndRow(10, $r, $no_login_day);
            $sheet->setCellValueByColumnAndRow(11, $r, User::STATUS[$row['status']]);
            $sheet->setCellValueByColumnAndRow(12, $r, $row['remark']);
            $sheet->setCellValueByColumnAndRow(13, $r, $row['free_time']);
            $sheet->setCellValueByColumnAndRow(14, $r, $row['created_at']);
        }

        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        header('Content-Disposition: attachment;filename="user.xlsx"');
        header('Cache-Control: max-age=0');
        $writer = new Xlsx($spreadsheet);
        $writer->save('php://output');
    }

    public function addMoney($row, $id)
    {
        $this->userRepository->addMoney($id, $row['type'], $row['money'], $row['description']);
    }
}
<file_sep>/app/Repositories/Pmtools/DailyUserRepository.php
<?php

namespace App\Repositories\Pmtools;

use App\Repositories\AbstractRepository;
use Models\Pmtools\DailyUser;

class DailyUserRepository extends AbstractRepository
{
    public function __construct(DailyUser $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['uid'])) {
            $this->db = $this->db->where('uid', '=', $this->_search['uid']);
        }
        
        if (isset($this->_search['date'])) {
            $this->db = $this->db->where('date', '=', $this->_search['date']);
        }
        if (isset($this->_search['date1'])) {
            $this->db = $this->db->where('date', '>=', $this->_search['date1']);
        }
        if (isset($this->_search['date2'])) {
            $this->db = $this->db->where('date', '<=', $this->_search['date2']);
        }

        return $this;
    }
}
<file_sep>/app/Models/User/User.php
<?php

namespace Models\User;

use Models\Model;

class User extends Model
{
    protected $table = 'user';

    protected $fillable = [
        'token',
        'username',
        'password',
        'email',
        'money',
        'referrer',
        'verify_code',
        'status',
        'remark',
        'create_ip',
        'create_ip_info',
        'create_ua',
        'login_ip',
        'login_time',
        'active_time',
        'free_time',
        'created_by',
        'updated_by',
    ];

    const STATUS = [
        0 => '发送简讯未注册',
        1 => '正常用户',
        2 => '封锁用户',
    ];

    public function share_users()
    {
        return $this->hasMany('Models\User\User', 'referrer');
    }

    public function referrer_user()
    {
        return $this->belongsTo('Models\User\User', 'referrer');
    }

    public function loginLog()
    {
        return $this->hasMany('Models\User\UserLoginLog', 'uid');
    }
}
<file_sep>/app/Http/Requests/System/AdsForm.php
<?php

namespace App\Http\Requests\System;

use App\Http\Requests\FormRequest;

final class AdsForm extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'type'  => 'required',
            'name'  => 'required',
            'image' => 'required',
            'url'   => 'required',
        ];
    }
}
<file_sep>/app/Services/System/DomainSettingService.php
<?php

namespace App\Services\System;

use App\Repositories\System\DomainSettingRepository;

class DomainSettingService
{
    protected $domainSettingRepository;

    public function __construct(DomainSettingRepository $domainSettingRepository)
    {
        $this->domainSettingRepository = $domainSettingRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->domainSettingRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function create($input)
    {
        $row = $this->domainSettingRepository->getEntity();
        if ($input['id'] > 0) {
            $row = $this->domainSettingRepository->row($input['id']);
        }

        return [
            'row' => $row,
        ];
    }

    public function store($row)
    {
        $row = array_map('strval', $row);
        $this->domainSettingRepository->create($row);
    }

    public function show($id)
    {
        return [
            'row' => $this->domainSettingRepository->find($id),
        ];
    }

    public function update($row, $id)
    {
        $row = array_map('strval', $row);
        $this->domainSettingRepository->update($row, $id);
    }

    public function destroy($id)
    {
        $this->domainSettingRepository->delete($id);
    }
}
<file_sep>/database/migrations/2019_12_16_164814_create_concurrent_user_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateConcurrentUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'concurrent_user';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->tinyInteger('per')->default(1)->comment('每幾分鐘');
            $table->datetime('minute_time')->default('1970-01-01 00:00:00')->comment('時間(每分鐘)');
            $table->integer('count')->default(0)->comment('人數');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->unique(['per','minute_time']);
        });

        DB::statement("ALTER TABLE `$tableName` comment '同時在線人數(CCU)'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('concurrent_user');
    }
}
<file_sep>/app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Validator;
use Illuminate\Support\ServiceProvider;
use App\Repositories\User\UserRepository;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(UserRepository $userRepository)
    {
        //擴展大陸手機號碼驗證規則
        Validator::extend('telphone', function ($attribute, $value, $parameters) {
            return preg_match('/^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7[^29\D](?(?<=4)(?:0\d|1[0-2]|9\d)|\d{2})|9[189]\d{2}|6[567]\d{2}|4[579]\d{2})\d{6}$/', $value);
        });
        
        //擴展推薦碼驗證規則
        Validator::extend('referrer', function ($attribute, $value, $parameters) use ($userRepository) {
            if ($value != '') {
                return $userRepository->referrerCode($value, 'decode') ? true:false;
            }
            return true;
        });
    }
}
<file_sep>/app/Services/System/SysconfigService.php
<?php

namespace App\Services\System;

use App\Repositories\System\SysconfigRepository;

class SysconfigService
{
    protected $sysconfigRepository;

    public function __construct(SysconfigRepository $sysconfigRepository)
    {
        $this->sysconfigRepository = $sysconfigRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['sort', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $where[] = ['groupid', '>', 0];
        $result = $this->sysconfigRepository->where($where)
                    ->search($search)->order($order)
                    ->result()->toArray();
        $result = $this->groupList($result);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
            'groupid'    => $search['groupid'] ?? 1,
        ];
    }

    public function create($input)
    {
        $row = $this->sysconfigRepository->getEntity();

        return [
            'row' => $row,
        ];
    }

    public function store($row)
    {
        $row = array_map('strval', $row);
        $this->sysconfigRepository->create($row);
    }

    public function update($data)
    {
        foreach ($data['skey'] as $id => $svalue) {
            $this->sysconfigRepository->update([
                'svalue' => $svalue,
                'sort'   => $data['sort'][$id],
            ], $id);
        }
    }

    public function destroy($id)
    {
        $this->sysconfigRepository->delete($id);
    }

    private function groupList($result)
    {
        $data = [];
        foreach ($result as $row) {
            $data[$row['groupid']][] = $row;
        }
        ksort($data);
        return $data;
    }
}
<file_sep>/config/sms.php
<?php
return [
    'url' => env('SMS_URL', 'http://v.juhe.cn/sms/send'),
    'key' => env('SMS_KEY', '<KEY>'),
];
<file_sep>/app/Console/Command/TestVideoApi.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\Video\VideoRepository;

class TestVideoApi extends Command
{
    //命令名稱
    protected $signature = 'test:video';
    //說明文字
    protected $description = '[TEST] 测试视频API端口';

    protected $videoRepository;

    public function __construct(VideoRepository $videoRepository)
    {
        parent::__construct();

        $this->videoRepository = $videoRepository;
    }

    //Console 執行的程式
    public function handle()
    {
        $domain = config('video.domain');
        $au     = config('video.au');
        $apiKey = config('video.key');
        $e      = time() + 2000;
        $token  = md5("$apiKey/list?au=$au&e=$e");
        echo "$domain/list?au=$au&e=$e&token=$token";
        //影片更新
        $result = getVideoApi('list');
        print_r($result);
    }
}
<file_sep>/app/Services/Admin/AdminLoginLogService.php
<?php

namespace App\Services\Admin;

use App\Repositories\Admin\AdminLoginLogRepository;

class AdminLoginLogService
{
    protected $adminLoginLogRepository;

    public function __construct(AdminLoginLogRepository $adminLoginLogRepository)
    {
        $this->adminLoginLogRepository = $adminLoginLogRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->adminLoginLogRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);
        foreach ($result as $key => $row) {
            $info = json_decode($row['ip_info'], true);
            $row['country'] = empty($info) ? '' : "$info[country_name]/$info[region_name]";
            $result[$key] = $row;
        }

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/app/Helpers.php
<?php

if (!function_exists("encode_search_params")) {
    function encode_search_params($params, $zero=[])
    {
        foreach ($params as $key => $value) {
            if ($key == '_token' || $value == '' || $value == 'all' || (in_array($key, $zero) && $value == '0')) {
                unset($params[$key]);
            } else {
                if (is_array($value)) {
                    $value = implode('|,|', $value).'array';
                }
                $params[$key] = urlencode($value);
            }
        }

        return $params;
    }
}

if (!function_exists("decode_search_params")) {
    function decode_search_params($params)
    {
        foreach ($params as $key => $value) {
            $value = urldecode($value);
            if (strpos($value, 'array') !== false) {
                $value = explode('|,|', substr($value, 0, strpos($value, 'array')));
            }
            $params[$key] = $value;
        }

        return $params;
    }
}

if (!function_exists("get_search_uri")) {
    function get_search_uri($params, $uri, $zero=[])
    {
        $params = encode_search_params($params, $zero);
        $arr = [];
        foreach ($params as $k => $v) {
            $arr[] = "$k=$v";
        }
        return $uri.'?'.implode('&', $arr);
    }
}

if (!function_exists("get_page")) {
    function get_page($params)
    {
        return isset($params['page']) ? (int) $params['page'] : 1;
    }
}

if (!function_exists("get_order")) {
    function get_order($params, $default_order = [])
    {
        $order = $default_order;

        isset($params['asc']) && $order = [$params['asc'], 'asc'];
        isset($params['desc']) && $order = [$params['desc'], 'desc'];

        return $order;
    }
}

if (!function_exists("param_process")) {
    function param_process($params, $default_order = [])
    {
        $result['page']  = $params['page'] ?? 1;
        $result['order'] = get_order($params, $default_order);

        unset($params['page']);
        $uri = [];
        foreach ($params as $k => $v) {
            $uri[] = "$k=$v";
        }
        $result['params_uri'] = implode('&', $uri);

        unset($params['asc']);
        unset($params['desc']);

        $result['search'] = decode_search_params($params);

        return $result;
    }
}

if (!function_exists("sort_title")) {
    function sort_title($key, $name, $base_uri, $order, $where = [])
    {
        $where = encode_search_params($where);
        $uri = [];
        foreach ($where as $k => $v) {
            $uri[] = "$k=$v";
        }
        $uri[] = $order[1] === 'asc' ? 'desc='.$key : 'asc='.$key;
        $uri_str = '?'.implode('&', $uri);

        $class = ($order[0] === $key) ? 'sort '.$order[1] : 'sort';

        return '<a class="'.$class.'" href="'.route($base_uri).$uri_str.'">'.$name.'</a>';
    }
}

if (!function_exists("lists_message")) {
    function lists_message($type='success')
    {
        if (session('message')) {
            return '<div id="message" class="alert alert-'.$type.' alert-dismissible">
                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                <h4><i class="icon fa fa-check"></i> 讯息!</h4>
                '.session('message').'
            </div>
            <script>
                setTimeout(function() { $(\'#message\').slideUp(); }, 3000);
                $(\'#message .close\').click(function() { $(\'#message\').slideUp(); });
            </script>';
        }
        return '';
    }
}

if (! function_exists('randPwd')) {
    /**
     * 隨機產生密碼
     * @param integer $pwd_len 密碼長度
     * @param integer $type
     * @return string
     */
    function randPwd($pwd_len, $type=0)
    {
        $password = '';
        if (!in_array($type, [0,1,2,3])) {
            return '';
        }

        // remove o,0,1,l
        if ($type == 0) {
            $word = '<PASSWORD> <KEY>';
        }
        if ($type == 1) {
            $word = '<PASSWORD> <KEY>';
        }
        if ($type == 2) {
            $word = '<PASSWORD>';
        }
        if ($type == 3) {
            $word = '<PASSWORD> <KEY> <PASSWORD>';
        }

        $len = strlen($word);

        for ($i = 0; $i < $pwd_len; $i++) {
            $password .= $word[rand(1, 99999) % $len];
        }

        return $password;
    }
}

if (! function_exists('bytes_to_kbmbgb')) {
    function bytes_to_kbmbgb($filesize)
    {
        if ($filesize<1048576) {
            return number_format($filesize/1024, 1) . " KB";
        }
        if ($filesize>=1048576 && $filesize<1073741824) {
            return number_format($filesize/1048576, 1) . " MB";
        }
        if ($filesize>=1073741824) {
            return number_format($filesize/1073741824, 1) . " GB";
        }
    }
}

if (! function_exists('getVideoApi')) {
    /**
     * 取得IQQTV API資料
     *
     * @param string $method API接口
     * @return array
     */
    function getVideoApi($method)
    {
        $domain = config('video.domain');
        $au     = config('video.au');
        $apiKey = config('video.key');
        $e      = time() + 2000;
        $token  = md5("$apiKey/$method?au=$au&e=$e");

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_URL, "$domain/$method?au=$au&e=$e&token=$token");
        $output = curl_exec($ch);
        curl_close($ch);

        return json_decode($output, true);
    }
}

if (! function_exists('sendSMS')) {
    /**
     * 發送認證簡訊
     *
     * @param string $mobile 手機號碼
     * @param string $code   驗證碼
     * @param string $tpl_id 模板ID
     * @return bool
     */
    function sendSMS($mobile, $code, $tpl_id='161064')
    {
        $url = config('sms.url');
        $key = config('sms.key');

        $content = juheCurl($url, [
            'key'       => $key,          //您申请的APPKEY
            'mobile'    => $mobile,       //接受短信的用户手机号码
            'tpl_id'    => $tpl_id,       //您申请的短信模板ID，根据实际情况修改
            'tpl_value' => "#code#=$code" //您设置的模板变量，根据实际情况修改
        ]);

        $result = json_decode($content, true);
        if ($result) {
            if ($result['error_code'] == 0) {
                return true;
            } else {
                \Log::error($content);
                return false;
            }
        }
    }
}

if (! function_exists('juheCurl')) {
    /**
     * 請求接口返回內容
     * @param string $url [請求的URL地址]
     * @param string $params [請求的參數]
     * @param int $ispost [是否採用POST形式]
     * @return string
     */
    function juheCurl($url, $params=[], $ispost=0)
    {
        $httpInfo = array();
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_USERAGENT, 'JuheData');
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        if ($ispost) {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
            curl_setopt($ch, CURLOPT_URL, $url);
        } else {
            if ($params) {
                curl_setopt($ch, CURLOPT_URL, $url.'?'.http_build_query($params));
            } else {
                curl_setopt($ch, CURLOPT_URL, $url);
            }
        }
        $response = curl_exec($ch);
        if ($response === false) {
            //echo "cURL Error: " . curl_error($ch);
            return false;
        }
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $httpInfo = array_merge($httpInfo, curl_getinfo($ch));
        curl_close($ch);
        return $response;
    }
}
<file_sep>/app/Models/System/Ip2location.php
<?php

namespace Models\System;

use Models\Model;

class Ip2location extends Model
{
    protected $table = 'ip2location';
}
<file_sep>/app/Http/Controllers/Pmtools/LoginMapController.php
<?php

namespace App\Http\Controllers\Pmtools;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\User\UserLoginLogService;

class LoginMapController extends Controller
{
    protected $userLoginLogService;

    public function __construct(UserLoginLogService $userLoginLogService)
    {
        $this->userLoginLogService = $userLoginLogService;
    }

    public function index(Request $request)
    {
        return view('pmtools.login_map', $this->userLoginLogService->loginMap($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'login_map'));
    }
}
<file_sep>/routes/admin.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

// Authentication Routes...
Route::get('login', 'Admin\AdminLoginController@showLoginForm')->name('login');
Route::post('login', 'Admin\AdminLoginController@login')->name('loginaction');
//AJAX
Route::post('ajax/topinfo', 'AjaxController@getTopInfo')->name('ajax.topinfo');
Route::post('ajax/perpage', 'AjaxController@setPerPage')->name('ajax.perpage');
Route::post('ajax/imageupload/{dir}', 'AjaxController@imageUpload')->name('ajax.imageupload');

Route::group(['middleware'=>['auth:backend','navdata','permission','share']], function () {
    Route::get('/', 'HomeController@index');
    Route::get('home', 'HomeController@index')->name('home');
    Route::get('logout', 'Admin\AdminLoginController@logout')->name('logout');

    Route::resource('admin', 'Admin\AdminController', ['only'=>['index','create','store','edit','update','destroy']]);
    Route::post('admin/search', 'Admin\AdminController@search')->name('admin.search');
    Route::post('admin/{admin}/save', 'Admin\AdminController@save')->name('admin.save');
    Route::get('admin/editpwd', 'Admin\AdminController@editpwd')->name('admin.editpwd');
    Route::post('admin/updatepwd', 'Admin\AdminController@updatepwd')->name('admin.updatepwd');

    Route::resource('admin_role', 'Admin\AdminRoleController', ['only'=>['index','create','store','edit','update','destroy']]);
    Route::post('admin_role/search', 'Admin\AdminRoleController@search')->name('admin_role.search');
    Route::post('admin_role/{admin}/save', 'Admin\AdminRoleController@save')->name('admin_role.save');

    Route::resource('admin_nav', 'Admin\AdminNavController', ['only'=>['index','create','store','edit','update','destroy']]);
    Route::post('admin_nav/search', 'Admin\AdminNavController@search')->name('admin_nav.search');
    Route::post('admin_nav/{admin_nav}/save', 'Admin\AdminNavController@save')->name('admin_nav.save');

    Route::get('admin_login_log', 'Admin\AdminLoginLogController@index')->name('admin_login_log.index');
    Route::post('admin_login_log/search', 'Admin\AdminLoginLogController@search')->name('admin_login_log.search');

    Route::get('admin_action_log', 'Admin\AdminActionLogController@index')->name('admin_action_log.index');
    Route::post('admin_action_log/search', 'Admin\AdminActionLogController@search')->name('admin_action_log.search');

    Route::resource('user', 'User\UserController', ['only'=>['index','create','store','edit','update','destroy']]);
    Route::post('user/search', 'User\UserController@search')->name('user.search');
    Route::post('user/{user}/save', 'User\UserController@save')->name('user.save');
    Route::get('user/export', 'User\UserController@export')->name('user.export');
    Route::get('user/{user}/money', 'User\UserController@money')->name('user.money');
    Route::put('user/{user}/money', 'User\UserController@money_update')->name('user.money_update');
    Route::get('user/{user}/free', 'User\UserController@free')->name('user.free');
    Route::put('user/{user}/free', 'User\UserController@free_update')->name('user.free_update');

    Route::get('user_login_log', 'User\UserLoginLogController@index')->name('user_login_log.index');
    Route::post('user_login_log/search', 'User\UserLoginLogController@search')->name('user_login_log.search');

    Route::get('user_money_log', 'User\UserMoneyLogController@index')->name('user_money_log.index');
    Route::post('user_money_log/search', 'User\UserMoneyLogController@search')->name('user_money_log.search');

    Route::get('video', 'Video\VideoController@index')->name('video.index');
    Route::post('video/search', 'Video\VideoController@search')->name('video.search');

    Route::get('video_actors', 'Video\VideoActorsController@index')->name('video_actors.index');
    Route::post('video_actors/search', 'Video\VideoActorsController@search')->name('video_actors.search');
    Route::post('video_actors/{video_actors}/save', 'Video\VideoActorsController@save')->name('video_actors.save');

    Route::get('video_tags', 'Video\VideoTagsController@index')->name('video_tags.index');
    Route::post('video_tags/search', 'Video\VideoTagsController@search')->name('video_tags.search');
    Route::post('video_tags/{video_tags}/save', 'Video\VideoTagsController@save')->name('video_tags.save');

    Route::resource('sysconfig', 'System\SysconfigController', ['only'=>['index','create','store','update','destroy']]);
    Route::post('sysconfig/search', 'System\SysconfigController@search')->name('sysconfig.search');

    Route::resource('ads', 'System\AdsController', ['only'=>['index','create','store','edit','update','destroy']]);
    Route::post('ads/search', 'System\AdsController@search')->name('ads.search');

    Route::resource('domain_setting', 'System\DomainSettingController', ['only'=>['index','create','store','edit','update','destroy']]);
    Route::post('domain_setting/search', 'System\DomainSettingController@search')->name('domain_setting.search');

    Route::get('ccu', 'Pmtools\ConcurrentUserController@index')->name('ccu.index');
    Route::post('ccu/search', 'Pmtools\ConcurrentUserController@search')->name('ccu.search');

    Route::get('analysis', 'Pmtools\DailyAnalysisController@index')->name('analysis.index');
    Route::post('analysis/search', 'Pmtools\DailyAnalysisController@search')->name('analysis.search');

    Route::get('daily_user', 'Pmtools\DailyUserController@index')->name('daily_user.index');
    Route::post('daily_user/search', 'Pmtools\DailyUserController@search')->name('daily_user.search');

    Route::get('retention', 'Pmtools\RetentionController@index')->name('retention.index');
    Route::post('retention/search', 'Pmtools\RetentionController@search')->name('retention.search');
    Route::get('retention_chart', 'Pmtools\RetentionController@chart')->name('retention_chart.index');
    Route::post('retention_chart/search', 'Pmtools\RetentionController@chartSearch')->name('retention_chart.search');
    Route::get('retention_analysis', 'Pmtools\RetentionController@analysis')->name('retention_analysis.index');
    Route::post('retention_analysis/search', 'Pmtools\RetentionController@analysisSearch')->name('retention_analysis.search');
    Route::get('retention_user', 'Pmtools\RetentionController@user')->name('retention_user.index');
    Route::post('retention_user/search', 'Pmtools\RetentionController@userSearch')->name('retention_user.search');

    Route::get('login_map', 'Pmtools\LoginMapController@index')->name('login_map.index');
    Route::post('login_map/search', 'Pmtools\LoginMapController@search')->name('login_map.search');

    Route::resource('message', 'System\MessageController', ['only'=>['index','edit','update','destroy']]);
    Route::post('message/search', 'System\MessageController@search')->name('message.search');
});
<file_sep>/routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::group(['as'=>'web.','middleware'=>['share','frontend']], function () {
    Route::get('/', 'WebController@index')->name('index');
    Route::get('home', 'WebController@index')->name('home');
    Route::get('detail/{keyword}', 'WebController@detail')->name('detail');
    Route::get('search', 'WebController@search')->name('search');
    Route::get('video', 'WebController@video')->name('video');
    Route::get('tags', 'WebController@tags')->name('tags');

    Route::get('login', 'LoginController@showLoginForm')->name('login');
    Route::post('login', 'LoginController@login');
    Route::get('auth/weixin', 'ThirdLogin\WeixinController@redirectToProvider');
    Route::get('auth/weixin/callback', 'ThirdLogin\WeixinController@handleProviderCallback');

    Route::post('verify_code', 'Api\LoginController@verifyCode')->name('verify_code');
    Route::post('verify_code_email', 'Api\LoginController@verifyCodeEmail')->name('verify_code_email');
    Route::post('forgot_code', 'Api\LoginController@forgotCode')->name('forgot_code');
    Route::get('register', 'LoginController@register')->name('register');
    Route::post('register', 'LoginController@registerAction');
    Route::get('forgot', 'WebController@forgot')->name('forgot');
    Route::post('forgot', 'WebController@forgotAction');
    Route::get('message', 'WebController@message')->name('message');
    Route::post('message', 'WebController@messageStore')->name('message.store');

    Route::group(['middleware'=>['auth:web']], function () {
        Route::get('logout', 'LoginController@logout')->name('logout');
        Route::get('profile', 'WebController@profile')->name('profile');
        Route::post('video/buy', 'WebController@buy')->name('video.buy');
    });
});
<file_sep>/app/Http/Controllers/AjaxController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\User\UserRepository;

class AjaxController extends Controller
{
    /**
     * 取得頁首資訊
     */
    public function getTopInfo(UserRepository $userRepository)
    {
        //在線會員
        $top_count['online'] = $userRepository->search([
            'active_time1' => date('Y-m-d H:i:s', time() - 600),
        ])->paginate(100)->result()->total();
        //今日註冊
        $top_count['register'] = $userRepository->search([
            'created_at1' => date('Y-m-d')
        ])->paginate(100)->result()->total();

        return response()->json($top_count);
    }

    /**
     * 設定全局單頁顯示筆數
     */
    public function setPerPage(Request $request)
    {
        if ($request->ajax()) {
            session(['per_page'=>$request->per_page ?: 20]);
            return 'done';
        }
    }

    /**
     * 圖片上傳
     */
    public function imageUpload($dir='images')
    {
        request()->validate([
            'file' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
        ]);

        $path = public_path("upload/$dir");
        @mkdir($path, 0777);

        $imageName = time().randPwd(3).'.'.request()->file->getClientOriginalExtension();
        request()->file->move($path, $imageName);

        return response()->json([
            'status'   => 1,
            'filelink' => "./upload/$dir/$imageName",
        ]);
    }

    /**
     * 檔案上傳
     */
    public function file_upload($dir='files')
    {
        $path = public_path("upload/$dir");
        @mkdir($path, 0777);

        $imageName = time().randPwd(3).'.'.request()->file->getClientOriginalExtension();
        request()->file->move($path, $imageName);

        return response()->json([
            'status'   => 1,
            'filelink' => "./upload/$dir/$imageName",
        ]);
    }
}
<file_sep>/database/migrations/2019_11_07_042748_create_admin_action_log_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminActionLogTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'admin_action_log';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->integer('adminid')->default(0)->comment('adminid');
            $table->string('route', 100)->default('')->comment('路由');
            $table->text('message')->comment('操作訊息');
            $table->text('sql')->comment('SQL指令');
            $table->string('ip', 50)->default('')->comment('登入IP');
            $table->tinyInteger('status')->default(1)->comment('狀態 0:失敗 1:成功');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->index('created_at');
        });

        DB::statement("ALTER TABLE `$tableName` comment '系統帳號操作LOG'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admin_action_log');
    }
}
<file_sep>/app/Repositories/Video/VideoActorsRepository.php
<?php

namespace App\Repositories\Video;

use App\Repositories\AbstractRepository;
use Models\Video\VideoActors;

class VideoActorsRepository extends AbstractRepository
{
    public function __construct(VideoActors $entity)
    {
        parent::__construct($entity);
    }

    public function _do_search()
    {
        if (isset($this->_search['name'])) {
            $this->db = $this->db->where('name', 'like', '%'.$this->_search['name'].'%');
        }

        return $this;
    }

    public function getAllName()
    {
        $result = $this->result();
        $data = [];
        foreach ($result as $row) {
            $data[] = $row['name'];
        }
        return $data;
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'   => 'ID',
        'name' => '女优',
        'hot'  => '是否为热门',
    ];
}
<file_sep>/app/Models/System/Ads.php
<?php

namespace Models\System;

use Models\Model;

class Ads extends Model
{
    protected $table = 'ads';

    protected $attributes = [
        'start_time' => '2019-12-01 00:00:00',
        'end_time'   => '2030-12-31 00:00:00',
        'sort'       => 1,
    ];

    protected $fillable = [
        'domain',
        'type',
        'name',
        'image',
        'url',
        'start_time',
        'end_time',
        'sort',
    ];

    const TYPE = [
        1  => '首页上方广告',
        2  => '首页下方广告',
        11 => '详情页广告',
    ];
}
<file_sep>/app/Models/User/UserMoneyLog.php
<?php

namespace Models\User;

use Models\Model;

class UserMoneyLog extends Model
{
    const UPDATED_AT = null;
    
    protected $table = 'user_money_log';

    protected $fillable = [
        'uid',
        'type',
        'video_keyword',
        'money_before',
        'money_add',
        'money_after',
        'description',
    ];

    const TYPE = [
        0 => '用户注册加点',
        1 => '推荐人加点',
        2 => '观看视频扣点',
        3 => '每日签到加点',
        4 => '人工加点',
        5 => '人工扣点',
    ];

    public function user()
    {
        return $this->belongsTo('Models\User\User', 'uid');
    }

    public function video()
    {
        return $this->belongsTo('Models\Video\Video', 'video_keyword', 'keyword');
    }
}
<file_sep>/database/migrations/2019_11_11_063629_create_user_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'user';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->string('token', 50)->default('')->comment('Token')->unique();
            $table->string('username')->default('')->comment('用戶名')->unique();
            $table->string('password')->default('')->comment('密碼');
            $table->integer('money')->default(0)->comment('貨幣');
            $table->integer('referrer')->default(0)->comment('推薦人');
            $table->tinyInteger('status')->default(0)->comment('狀態 0:發送簡訊未註冊 1:正式用戶 1:封鎖用戶');
            $table->text('create_ua')->comment('註冊UA資訊');
            $table->string('create_ip', 50)->default('')->comment('註冊IP');
            $table->json('create_ip_info')->comment('註冊IP資訊');
            $table->string('login_ip', 50)->default('')->comment('最後登入IP');
            $table->dateTime('login_time')->default('1970-01-01 00:00:00')->comment('最後登入時間');
            $table->dateTime('active_time')->default('1970-01-01 00:00:00')->comment('最後活動時間');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->softDeletes();
            $table->index('created_at');
        });

        DB::statement("ALTER TABLE `$tableName` comment '用戶列表'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user');
    }
}
<file_sep>/app/Services/Admin/AdminActionLogService.php
<?php

namespace App\Services\Admin;

use App\Repositories\Admin\AdminActionLogRepository;

class AdminActionLogService
{
    protected $adminActionLogRepository;

    public function __construct(AdminActionLogRepository $adminActionLogRepository)
    {
        $this->adminActionLogRepository = $adminActionLogRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->adminActionLogRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/app/Repositories/User/UserMoneyLogRepository.php
<?php

namespace App\Repositories\User;

use App\Repositories\AbstractRepository;
use Models\User\UserMoneyLog;

class UserMoneyLogRepository extends AbstractRepository
{
    public function __construct(UserMoneyLog $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['uid'])) {
            $this->db = $this->db->where('uid', '=', $this->_search['uid']);
        }
        if (isset($this->_search['username'])) {
            $this->db = $this->db->whereHas('user', function ($query) {
                $query->where('username', 'like', '%'.$this->_search['username'].'%');
            });
        }

        if (isset($this->_search['video'])) {
            $this->db = $this->db->whereHas('video', function ($query) {
                $query->where('name', 'like', '%'.$this->_search['video'].'%');
            });
        }

        if (isset($this->_search['type'])) {
            $this->db = $this->db->where('type', '=', $this->_search['type']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }

    /**
     * 購買的影片清單(未過期)
     *
     * @param integer $uid 用戶ID
     * @return array
     */
    public function getBuyVideo($uid)
    {
        $where[] = ['uid', '=', $uid];
        $where[] = ['type', '=', 2];
        $where[] = ['video_keyword', '<>', ''];
        $where[] = ['created_at', '>=', date('Y-m-d H:i:s', time()-86400)];
        $result = $this->where($where)->result()->toArray();

        return array_column($result, 'video_keyword');
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'uid'          => '用戶ID',
        'type'         => '帳變類型',
        'money_before' => '变动前点数',
        'money_add'    => '变动点数',
        'money_after'  => '变动后点数',
        'description'  => '描述',
    ];
}
<file_sep>/app/Http/Controllers/WebController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Video\VideoService;
use App\Services\System\MessageService;
use App\Repositories\Video\VideoRepository;
use App\Repositories\Video\VideoTagsRepository;
use App\Repositories\User\UserRepository;
use App\Repositories\User\UserMoneyLogRepository;
use App\Repositories\System\AdsRepository;
use Illuminate\Support\Facades\Redis;
use Exception;
use Auth;

/**
 * 過渡期程式-前端接手後廢除
 */
class WebController extends Controller
{
    protected $videoService;
    protected $messageService;
    protected $videoRepository;
    protected $videoTagsRepository;
    protected $userRepository;
    protected $userMoneyLogRepository;
    protected $adsRepository;

    public function __construct(
        VideoService $videoService,
        MessageService $messageService,
        VideoRepository $videoRepository,
        VideoTagsRepository $videoTagsRepository,
        UserRepository $userRepository,
        UserMoneyLogRepository $userMoneyLogRepository,
        AdsRepository $adsRepository
    ) {
        $this->videoService = $videoService;
        $this->messageService = $messageService;
        $this->videoRepository = $videoRepository;
        $this->videoTagsRepository = $videoTagsRepository;
        $this->userRepository = $userRepository;
        $this->userMoneyLogRepository = $userMoneyLogRepository;
        $this->adsRepository = $adsRepository;
        session(['per_page'=>12]);
    }

    public function index(Request $request)
    {
        $request->per_page = $request->per_page ?? 40;
        $video = $this->videoService->getList($request);
        //上方廣告
        $search['domain'] = $request->server('HTTP_HOST');
        $search['enabled'] = 1;
        $search['type'] = 1;
        $ads_up = $this->adsRepository->search($search)->result();
        //下方廣告
        $search['type'] = 2;
        $ads_down = $this->adsRepository->search($search)->result();
        //會員數
        $members = round((time() - strtotime('2019-01-01')) / 88888);
        //觀看數
        $watchs = round((time() - strtotime(date('Y-m-d'))) * 1.111);
        //新進影片
        $newvideo = round((strtotime(date('Y-m-d')) - strtotime(date('Y-m-01'))+86400) / 86400 * 6.666);
        //累計影片時數
        $videohours = round((time() - strtotime('2000-01-01')) / 14400);

        return view('web.index', [
            'page'       => 'index',
            'video'      => $video['list'],
            'ads_up'     => $ads_up,
            'ads_down'   => $ads_down,
            'members'    => $members,
            'watchs'     => $watchs,
            'newvideo'   => $newvideo,
            'videohours' => $videohours,
        ]);
    }

    public function detail(Request $request, $keyword)
    {
        $row = $this->videoRepository->search(['keyword'=>$keyword])->result_one();
        if ($row === null) {
            $url = route('web.index');
            echo "<script>alert('查无此影片');location.href='$url';</script>";
        }
        if ($row['status'] == 0) {
            $url = route('web.index');
            echo "<script>alert('该影片已过期');location.href='$url';</script>";
        }
        //更多影片
        $tags = explode(',', $row['tags']);
        $where[] = ['keyword', '<>', $keyword];
        $search['tags'] = $tags[0] ?? '';
        $search['status'] = 1;
        $more = $this->videoRepository->where($where)->search($search)
                    ->order(['rand()','asc'])->limit([0,16])->result();
        //上一部
        $where = [];
        $where[] = ['publish', '>=', $row['publish']];
        $where[] = ['keyword', '>', $row['keyword']];
        $where[] = ['status', '=', 1];
        $prev = $this->videoRepository->where($where)->order(['publish'=>'asc','keyword'=>'asc'])->result_one();
        //下一部
        $where = [];
        $where[] = ['publish', '<=', $row['publish']];
        $where[] = ['keyword', '<', $row['keyword']];
        $where[] = ['status', '=', 1];
        $next = $this->videoRepository->where($where)->order(['publish'=>'desc','keyword'=>'desc'])->result_one();
        $all = $request->secret == 'iloveav';
        $user = [];
        //判斷是否登入
        if (Auth::guard('web')->check()) {
            $user = Auth::guard('web')->user();
            //24H內購買過的清單
            $buy = $this->userMoneyLogRepository->getBuyVideo($user['id']);
            if (in_array($row['keyword'], $buy)) {
                $all = true;
            }
            //免費看
            if (strtotime($user['free_time']) > time()) {
                $all = true;
            }
        }
        //廣告
        $search['domain'] = $request->server('HTTP_HOST');
        $search['enabled'] = 1;
        $search['type'] = 11;
        $ads = $this->adsRepository->search($search)->result();

        return view('web.detail', [
            'page'     => 'detail',
            'user'     => $user,
            'all'      => $all,
            'more'     => $more,
            'prev_url' => $prev['keyword'] ?? '',
            'next_url' => $next['keyword'] ?? '',
            'video'    => $row,
            'ads'      => $ads,
        ]);
    }

    public function search(Request $request)
    {
        $data = $this->videoService->list($request->input());
        $data['page'] = 'search';

        return view('web.search', $data);
    }

    public function video(Request $request)
    {
        $data = $this->videoService->list($request->input());
        $data['page'] = 'video';

        return view('web.video', $data);
    }

    public function tags(Request $request)
    {
        //標籤
        $result = $this->videoTagsRepository->search(['hot'=>1])->result();
        $tags = [];
        foreach ($result as $row) {
            $tags[] = $row['name'];
        }
        //影片列表
        $data = $this->videoService->list($request->input());
        $data['page'] = 'tags';
        $data['tags'] = $tags;
        $data['param'] = $request->input();

        return view('web.tags', $data);
    }

    public function forgot(Request $request)
    {
        return view('web.forgot', [
            'page' => 'forgot',
        ]);
    }

    public function forgotAction(Request $request)
    {
        $this->validate($request, [
            'mobile'      => 'required|telphone',
            'verify_code' => 'required',
            'password'    => '<PASSWORD>',
            'repassword'  => '<PASSWORD>:<PASSWORD>',
        ]);

        try {
            if (!$forgot = Redis::get("forgot:phone:$request[mobile]")) {
                throw new Exception('重置验证码错误(err01)');
            }
            $forgot = json_decode($forgot, true);
            if ($forgot['code'] != $request['verify_code']) {
                throw new Exception('重置验证码错误(err02)');
            }

            $user = $this->userRepository->getDataByUsername($request->mobile);
            $this->userRepository->update([
                'password' => $request->password
            ], $user->id);

            Redis::del("forgot:phone:$request[mobile]");

            $url = route('web.login');
            return "<script>alert('密码修改完成!');location.href='$url';</script>";
        } catch (Exception $e) {
            return back()->withErrors($e->getMessage());
        }
    }

    public function profile(Request $request)
    {
        $user = \Auth::user();
        //推薦紀錄
        $result = $this->userRepository->getReferrerList($user->id);
        foreach ($result as $key => $row) {
            $row->username = substr($row->username, 0, 3).'*****'.substr($row->username, -3);
            $row->created_at = date('Y-m-d H:i:s', strtotime($row->created_at));
            $result[$key] = $row;
        }
        //帳變明細
        $moneylog = $this->userMoneyLogRepository->search(['uid'=>$user->id])
                    ->order(['created_at','desc'])->limit([0,10])->result();
        foreach ($moneylog as $key => $row) {
            if ($row['type'] == 1) {
                $row->description = mb_substr($row->description, 0, -8).'*****'.mb_substr($row->description, -3);
            }
            if ($row['type'] == 2) {
                $url = route('web.detail', ['keyword'=>$row['video_keyword']]);
                $row->description = str_replace($row['video_keyword'], "<a href='$url' target=\"_blank\">$row[video_keyword]</a>", $row['description']);
            }

            $moneylog[$key] = $row;
        }

        return view('web.profile', [
            'page'   => 'profile',
            'result' => $result,
            'moneylog' => $moneylog,
            'user'   => [
                'id'         => $user->id,
                'username'   => $user->username,
                'money'      => $user->money,
                'free_time'  => $user->free_time,
                'status'     => $user->status,
                'created_at' => date('Y-m-d H:i:s', strtotime($user->created_at)),
            ],
        ]);
    }

    public function moneylog(Request $request)
    {
        $user = \Auth::user();
        //推薦紀錄
        $result = $this->userRepository->getReferrerList($user->id);
        $reflist = [];
        foreach ($result as $row) {
            $reflist[] = [
                'username'    => substr($row->username, 0, 7).'****',
                'money'       => $row->money,
                'status'      => $row->status,
                'login_time'  => $row->login_time,
                'created_at' => date('Y-m-d H:i:s', strtotime($row->created_at)),
            ];
        }

        return view('web.moneylog', [
            'page'    => 'profile',
            'reflist' => $reflist,
            'user'    => [
                'id'         => $user->id,
                'username'   => $user->username,
                'money'      => $user->money,
                'status'     => $user->status,
                'created_at' => date('Y-m-d H:i:s', strtotime($user->created_at)),
            ],
        ]);
    }

    public function buy(Request $request)
    {
        try {
            $validator = \Validator::make($request->all(), [
                'keyword' => 'required|exists:video',
            ]);
            if ($validator->fails()) {
                $message = implode("\n", $validator->errors()->all());
                throw new Exception($message, 422);
            }

            $user = \Auth::user();
            $buy = $this->userMoneyLogRepository->getBuyVideo($user['id']);
            if (in_array($request->keyword, $buy)) {
                throw new Exception('该视频已购买过！', 422);
            }
            if ($user['money'] <= 0) {
                throw new Exception('余额不足！', 422);
            }
            //帳變明細
            $this->userRepository->addMoney($user['id'], 2, -1, "观看影片-$request->keyword", $request->keyword);

            return response()->json([
                'success' => true,
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
            ], $e->getCode() ?: 500);
        }
    }

    public function message(Request $request)
    {
        return view('web.message', $this->messageService->create($request->input()));
    }

    public function messageStore(Request $request)
    {
        $this->validate($request, [
            'type'    => 'required',
            'content' => 'required',
        ], [
            'type.required'    => '请选择一项问题类型',
            'content.required' => '问题描述 不能为空',
        ]);

        try {
            //判斷是否登入
            $uid = 0;
            if (Auth::guard('web')->check()) {
                $uid = Auth::user()->id;
            }
            $this->messageService->store([
                'uid'     => $uid,
                'type'    => $request->type,
                'content' => $request->content,
            ]);

            $url = route('web.message');
            return "<script>alert('留言已送出!');location.href='$url';</script>";
        } catch (Exception $e) {
            return back()->withErrors($e->getMessage());
        }
    }
}
<file_sep>/app/Http/Middleware/NavData.php
<?php

namespace App\Http\Middleware;

use View;
use Route;
use Closure;
use Models\Admin\AdminRole;
use App\Repositories\Admin\AdminNavRepository;

class NavData
{
    protected $adminNavRepository;

    public function __construct(AdminNavRepository $adminNavRepository)
    {
        $this->adminNavRepository = $adminNavRepository;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        //取得當前Route
        $route = Route::currentRouteName();
        $share['route'] = $route;
        $action = explode('.', $route);
        $share['controller'] = "$action[0]";
        //導航列表
        $nav = $this->adminNavRepository->allNav();
        $share['allNav'] = $nav;
        //導航權限
        $role = AdminRole::find(session('roleid'))->toArray();
        $permition = $role !== null && $role['allow_nav'] != '' ? json_decode($role['allow_nav'], true) : [];
        $routes = [];
        foreach ($nav as $row) {
            if ($row['pid'] > 0) {
                $routes[$row['route']] = $row;
                if ($row['route1'] != '') {
                    $routes[$row['route1']] = $row;
                }
                if ($row['route2'] != '') {
                    $routes[$row['route2']] = $row;
                }
            }
            //子路由寫入權限
            if (in_array($row['route'], $permition)) {
                if ($row['route1'] != '') {
                    $permition[] = $row['route1'];
                }
                if ($row['route2'] != '') {
                    $permition[] = $row['route2'];
                }
            }
        }
        $permition = array_merge_recursive($permition, config('global.no_need_perm'));
        $share['allow_url'] = array_unique($permition);
        //導航路徑
        $navid = isset($routes[$route]) ? $routes[$route]['id'] : 0;
        $share['breadcrumb'] = $this->getBreadcrumb($nav, $navid);
        //內頁Title
        $share['title'] = isset($routes[$route]) ? $routes[$route]['name'] : '首页';
        //導航樹狀
        $share['navList'] = $this->treeNav($nav);

        View::share($share);
        return $next($request);
    }

    /**
     * 遞迴整理導航樹狀結構
     *
     * @param array $result 導航清單
     * @param integer $pid 上層導航ID
     * @return array
     */
    private function treeNav($result, $pid = 0)
    {
        $data = [];
        foreach ($result as $row) {
            if ($row['pid'] == $pid) {
                $row['sub'] = $this->treeNav($result, $row['id']);
                $row['subNavs'] = array_column($row['sub'], 'route');
                $data[] = $row;
            }
        }
        return $data;
    }

    /**
     * 遞迴取得導航路徑
     *
     * @param array $result 導航清單
     * @param integer $id 導航ID
     * @return array
     */
    private function getBreadcrumb($result, $id)
    {
        if ($id == 0) {
            return [];
        }

        $data = $this->getBreadcrumb($result, $result[$id]['pid']);
        $data[] = $result[$id];
        return $data;
    }
}
<file_sep>/app/Http/Controllers/Video/VideoActorsController.php
<?php

namespace App\Http\Controllers\Video;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Video\VideoActorsService;

class VideoActorsController extends Controller
{
    protected $videoActorsService;

    public function __construct(VideoActorsService $videoActorsService)
    {
        $this->videoActorsService = $videoActorsService;
    }

    public function index(Request $request)
    {
        return view('video.actors', $this->videoActorsService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'video_actors'));
    }

    public function save(Request $request, $id)
    {
        $this->videoActorsService->save($request->post(), $id);
        return 'done';
    }
}
<file_sep>/app/Repositories/Admin/AdminNavRepository.php
<?php

namespace App\Repositories\Admin;

use App\Repositories\AbstractRepository;
use Models\Admin\AdminNav;

class AdminNavRepository extends AbstractRepository
{
    public function __construct(AdminNav $entity)
    {
        parent::__construct($entity);
    }

    private function _preAction($row)
    {
        $row['path'] = 0;
        if ($row['pid'] > 0) {
            $parent = $this->row($row['pid']);
            $row['path'] = $parent['path'] . '-' . $row['pid'];
        }
        return $row;
    }

    public function create($row)
    {
        $row = $this->_preAction($row);
        return parent::create($row);
    }

    public function update($row, $id=0)
    {
        $row = $this->_preAction($row);
        return parent::update($row, $id);
    }

    public function _do_search()
    {
        if (isset($this->_search['pid'])) {
            $this->db = $this->db->where('pid', '=', $this->_search['pid']);
        }

        if (isset($this->_search['name'])) {
            $this->db = $this->db->where('name', 'like', '%'.$this->_search['name'].'%');
        }

        if (isset($this->_search['route'])) {
            $this->db = $this->db->where('route', 'like', '%'.$this->_search['route'].'%');
        }

        if (isset($this->_search['status'])) {
            $this->db = $this->db->where('status', '=', $this->_search['status']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }

    /**
     * 取得所有導航資料
     *
     * @return array
     */
    public function allNav()
    {
        $where[] = ['status', '=', 1];
        $where[] = ['route', '<>', ''];
        $result = $this->where($where)->order(['sort', 'asc'])->result()->toArray();
        return array_column($result, null, 'id');
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'     => '流水号',
        'pid'    => '父级ID',
        'icon'   => 'ICON',
        'name'   => '导航名称',
        'route'  => '主路由',
        'route1' => '次路由1',
        'route2' => '次路由2',
        'path'   => '阶层路径',
        'sort'   => '排序',
        'status' => '状态',
    ];
}
<file_sep>/app/Repositories/Pmtools/ConcurrentUserRepository.php
<?php

namespace App\Repositories\Pmtools;

use App\Repositories\AbstractRepository;
use Models\Pmtools\ConcurrentUser;

class ConcurrentUserRepository extends AbstractRepository
{
    public function __construct(ConcurrentUser $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['per'])) {
            $this->db = $this->db->where('per', '=', $this->_search['per']);
        }
        
        if (isset($this->_search['minute_time'])) {
            $this->db = $this->db->where('minute_time', '=', $this->_search['minute_time']);
        }
        if (isset($this->_search['minute_time1'])) {
            $this->db = $this->db->where('minute_time', '>=', $this->_search['minute_time1']);
        }
        if (isset($this->_search['minute_time2'])) {
            $this->db = $this->db->where('minute_time', '<=', $this->_search['minute_time2']);
        }

        return $this;
    }
}
<file_sep>/app/Http/Controllers/System/DomainSettingController.php
<?php

namespace App\Http\Controllers\System;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\System\DomainSettingForm;
use App\Services\System\DomainSettingService;

class DomainSettingController extends Controller
{
    protected $domainSettingService;

    public function __construct(DomainSettingService $domainSettingService)
    {
        $this->domainSettingService = $domainSettingService;
    }

    public function index(Request $request)
    {
        return view('domain_setting.index', $this->domainSettingService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'domain_setting'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('domain_setting.create', $this->domainSettingService->create($request->input()));
    }

    public function store(DomainSettingForm $request)
    {
        $this->domainSettingService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('domain_setting.edit', $this->domainSettingService->show($id));
    }

    public function update(DomainSettingForm $request, $id)
    {
        $this->domainSettingService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function destroy(Request $request)
    {
        $this->domainSettingService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/app/Repositories/Video/VideoRepository.php
<?php

namespace App\Repositories\Video;

use App\Repositories\AbstractRepository;
use Models\Video\Video;

class VideoRepository extends AbstractRepository
{
    public function __construct(Video $entity)
    {
        parent::__construct($entity);
        $this->is_action_log = false;
    }

    public function _do_search()
    {
        if (isset($this->_search['keyword'])) {
            $this->db = $this->db->where('keyword', '=', $this->_search['keyword']);
        }

        if (isset($this->_search['name'])) {
            $this->db = $this->db->where('name', 'like', '%'.$this->_search['name'].'%');
        }

        if (isset($this->_search['actors'])) {
            $this->db = $this->db->whereRaw("FIND_IN_SET('".$this->_search['actors']."', actors)");
            unset($this->_search['actors']);
        }

        if (isset($this->_search['tags'])) {
            $this->db = $this->db->whereRaw("FIND_IN_SET('".$this->_search['tags']."', tags)");
            unset($this->_search['tags']);
        }

        if (isset($this->_search['publish1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['publish1']);
        }
        if (isset($this->_search['publish2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['publish2']);
        }

        if (isset($this->_search['search'])) {
            $this->db = $this->db->where(function ($query) {
                $query->where('name', 'like', '%'.$this->_search['search'].'%')
                    ->orWhereRaw("FIND_IN_SET('".$this->_search['search']."', actors)")
                    ->orWhereRaw("FIND_IN_SET('".$this->_search['search']."', tags)");
            });
        }

        if (isset($this->_search['status'])) {
            $this->db = $this->db->where('status', '=', $this->_search['status']);
        }

        if (isset($this->_search['updated_at1'])) {
            $this->db = $this->db->where('updated_at', '>=', $this->_search['updated_at1']);
        }
        if (isset($this->_search['updated_at2'])) {
            $this->db = $this->db->where('updated_at', '<=', $this->_search['updated_at2']);
        }

        return $this;
    }
}
<file_sep>/app/Http/Controllers/System/AdsController.php
<?php

namespace App\Http\Controllers\System;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\System\AdsForm;
use App\Services\System\AdsService;

class AdsController extends Controller
{
    protected $adsService;

    public function __construct(AdsService $adsService)
    {
        $this->adsService = $adsService;
    }

    public function index(Request $request)
    {
        return view('ads.index', $this->adsService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'ads'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('ads.create', $this->adsService->create($request->input()));
    }

    public function store(AdsForm $request)
    {
        $this->adsService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('ads.edit', $this->adsService->show($id));
    }

    public function update(AdsForm $request, $id)
    {
        $this->adsService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function destroy(Request $request)
    {
        $this->adsService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/app/Console/Command/SetDailyRetention.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\User\UserRepository;
use App\Repositories\Pmtools\DailyRetentionRepository;

class SetDailyRetention extends Command
{
    //命令名稱
    protected $signature = 'pmtools:daily_retention {--enforce=} {--date=}';
    //說明文字
    protected $description = '[統計]留存率';

    protected $userRepository;
    protected $dailyRetentionRepository;

    public function __construct(
        UserRepository $userRepository,
        DailyRetentionRepository $dailyRetentionRepository
    ) {
        parent::__construct();

        $this->userRepository = $userRepository;
        $this->dailyRetentionRepository = $dailyRetentionRepository;
    }

    /**
     * @param string $date 指定執行日期
     * @param int $enforce 強制重新執行
     */
    public function handle()
    {
        $date    = $this->option('date') ?: date('Y-m-d', time()-86400);
        $enforce = $this->option('enforce') ?: 0;

        //強制執行 先刪除已存在的資料
        if ($enforce) {
            $this->dailyRetentionRepository->search(['date'=>$date])->delete();
        }

        //判斷是否已執行過(有資料)
        if ($this->dailyRetentionRepository->search(['date'=>$date])->count() == 0) {
            $now = date('Y-m-d H:i:s');
            //總數
            $all = $this->userRepository->search(['status'=>1])->count();
            $insert = [];
            for ($i = 1; $i <= 6; $i++) {
                $data = $this->userRepository->retention($i);
                $insert[] = [
                    'date'       => $date,
                    'type'       => $i,
                    'all_count'  => $all,
                    'day_count'  => $data['day_count'] ?? 0,
                    'avg_money'  => $data['avg_money'] ?? 0,
                    'created_at' => $now,
                ];
            }
            $this->dailyRetentionRepository->insert_batch($insert);
        }
    }
}
<file_sep>/database/migrations/2019_11_11_083324_create_video_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateVideoTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'video';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->string('keyword', 50)->comment('視頻Keyword')->unique();
            $table->string('name')->default('')->comment('片名');
            $table->date('publish')->default('1970-01-01')->comment('發行日期');
            $table->string('actors')->default('')->comment('女優(逗號分隔)');
            $table->string('tags')->default('')->comment('Tags(逗號分隔)');
            $table->string('pic_b')->default('')->comment('封面圖-大');
            $table->string('pic_s')->default('')->comment('封面圖-小');
            $table->string('url')->default('')->comment('視頻連結');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->index('created_at');
        });

        DB::statement("ALTER TABLE `$tableName` comment '影片列表'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('video');
    }
}
<file_sep>/app/Console/Command/GetVideoTagsApi.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Repositories\Video\VideoActorsRepository;
use App\Repositories\Video\VideoTagsRepository;

class GetVideoTagsApi extends Command
{
    //命令名稱
    protected $signature = 'api:video_tags';
    //說明文字
    protected $description = '[API] 取得影片標籤及女優';

    protected $videoActorsRepository;
    protected $videoTagsRepository;

    public function __construct(
        VideoActorsRepository $videoActorsRepository,
        VideoTagsRepository $videoTagsRepository
    ) {
        parent::__construct();

        $this->videoActorsRepository = $videoActorsRepository;
        $this->videoTagsRepository = $videoTagsRepository;
    }

    // Console 執行的程式
    public function handle()
    {
        //女優更新
        $actors = $this->videoActorsRepository->getAllName();
        $result = getVideoApi('actors');
        $insert = [];
        foreach ($result['data']['actors'] as $row) {
            if (!in_array($row['name'], $actors)) {
                $insert[] = [
                    'name'       => $row['name'],
                    'created_at' => date('Y-m-d H:i:s'),
                    'created_by' => 'schedule',
                    'updated_at' => date('Y-m-d H:i:s'),
                    'updated_by' => 'schedule',
                ];
            }
        }
        if ($insert !== []) {
            $this->videoActorsRepository->insert_batch($insert);
        }

        //標籤更新
        $tags = $this->videoTagsRepository->getAllName();
        $result = getVideoApi('tags');
        $insert = [];
        foreach ($result['data']['tags'] as $row) {
            if (!in_array($row['name'], $tags)) {
                $insert[] = [
                    'name'       => $row['name'],
                    'created_at' => date('Y-m-d H:i:s'),
                    'created_by' => 'schedule',
                    'updated_at' => date('Y-m-d H:i:s'),
                    'updated_by' => 'schedule',
                ];
            }
        }
        if ($insert !== []) {
            $this->videoTagsRepository->insert_batch($insert);
        }
    }
}
<file_sep>/app/Http/Middleware/Frontend.php
<?php

namespace App\Http\Middleware;

use Closure;
use Auth;
use View;
use App\Repositories\System\DomainSettingRepository;

class Frontend
{
    protected $domainSettingRepository;

    public function __construct(DomainSettingRepository $domainSettingRepository)
    {
        $this->domainSettingRepository = $domainSettingRepository;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Auth::guard('web')->check()) {
            $user = Auth::guard('web')->user();
            $user->active_time = date('Y-m-d H:i:s');
            $user->save();
        }

        //有推薦碼則寫入session 註冊預先帶入
        if (isset($request->refcode)) {
            $request->refcode = str_replace('?', '', $request->refcode);
            session(['referrer_code'=>$request->refcode]);
        }

        $domain = $request->server('HTTP_HOST');
        $share['seo'] = $this->domainSettingRepository->search(['domain'=>$domain])->result_one();

        View::share($share);
        return $next($request);
    }
}
<file_sep>/database/migrations/2019_12_18_135452_create_daily_retention_user_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateDailyRetentionUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'daily_retention_user';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->date('date')->default('1970-01-01')->comment('日期');
            $table->tinyInteger('type')->comment('類型 1.1天前新帳號,2.3天前新帳號,3.7天前新帳號,4.15天前新帳號,5.30天前新帳號');
            $table->integer('all_count')->default(0)->comment('總數');
            $table->integer('day_count')->default(0)->comment('人數');
            $table->integer('percent')->default(0)->comment('百分比');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->unique(['date','type']);
        });

        DB::statement("ALTER TABLE `$tableName` comment '每日統計-新帳號留存率'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('daily_retention_user');
    }
}
<file_sep>/app/Models/Pmtools/DailyRetentionUser.php
<?php

namespace Models\Pmtools;

use Models\Model;

class DailyRetentionUser extends Model
{
    const UPDATED_AT = null;

    protected $table = 'daily_retention_user';

    protected $fillable = [
        'date',
        'type',
        'all_count',
        'day_count',
        'percent',
    ];

    const TYPE = [
        1 => '1天前新用户',
        2 => '3天前新用户',
        3 => '7天前新用户',
        4 => '15天前新用户',
        5 => '30天前新用户',
    ];
}
<file_sep>/app/Http/Controllers/System/SysconfigController.php
<?php

namespace App\Http\Controllers\System;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\System\SysconfigForm;
use App\Services\System\SysconfigService;

class SysconfigController extends Controller
{
    protected $sysconfigService;

    public function __construct(SysconfigService $sysconfigService)
    {
        $this->sysconfigService = $sysconfigService;
    }

    public function index(Request $request)
    {
        return view('sysconfig.index', $this->sysconfigService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'sysconfig'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('sysconfig.create', $this->sysconfigService->create($request->input()));
    }

    public function store(SysconfigForm $request)
    {
        $this->sysconfigService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function update(Request $request, $id)
    {
        $this->sysconfigService->update($request->post());

        session()->flash('message', '编辑成功!');
        return redirect(route('sysconfig.index', ['groupid'=>$id]));
    }

    public function destroy(Request $request)
    {
        $this->sysconfigService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/app/Models/System/Message.php
<?php

namespace Models\System;

use Models\Model;

class Message extends Model
{
    protected $table = 'message';

    protected $fillable = [
        'uid',
        'type',
        'content',
        'created_by',
        'updated_by',
    ];

    const TYPE = [
        1 => '帐号问题',
        2 => '点数问题',
        3 => '播放问题',
        4 => '其他问题',
    ];

    public function user()
    {
        return $this->belongsTo('Models\User\User', 'uid');
    }
}
<file_sep>/database/migrations/2019_11_07_041234_create_admin_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'admin';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->string('token', 50)->default('')->comment('Token');
            $table->string('username')->default('')->comment('用戶名')->unique();
            $table->string('password')->default('')->comment('密碼');
            $table->smallInteger('roleid')->default(0)->comment('角色ID');
            $table->string('login_ip', 50)->default('')->comment('登入IP');
            $table->dateTime('login_time')->default('1970-01-01 00:00:00')->comment('登入時間');
            $table->integer('login_count')->default(0)->comment('登入次數');
            $table->tinyInteger('status')->default(1)->comment('狀態 1:開啟 0:關閉');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->index('created_at');
        });

        DB::statement("ALTER TABLE `$tableName` comment '系統帳號'");
        
        DB::table($tableName)->insert([
            'id'        => 1,
            'username'  => 'admin',
            'password'  => <PASSWORD>('<PASSWORD>'),
            'roleid'    => 1,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admin');
    }
}
<file_sep>/app/Http/Requests/System/DomainSettingForm.php
<?php

namespace App\Http\Requests\System;

use App\Http\Requests\FormRequest;
use Illuminate\Validation\Rule;

final class DomainSettingForm extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'domain'      => ['required',Rule::unique('domain_setting')->ignore($this->route('domain_setting'))],
            'title'       => 'required',
            'keyword'     => 'required',
            'description' => 'required',
        ];
    }
}
<file_sep>/app/Http/Requests/Admin/AdminNavForm.php
<?php

namespace App\Http\Requests\Admin;

use App\Http\Requests\FormRequest;

final class AdminNavForm extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name'  => 'required',
            'route' => 'required',
        ];
    }

    /**
     * 獲取已定義驗證規則的錯誤消息。
     *
     * @return array
     */
    public function messages()
    {
        return [
            'name.required'  => '导航名称 不可空白',
            'route.required' => '主路由 不可空白',
        ];
    }
}
<file_sep>/app/Services/Pmtools/ConcurrentUserService.php
<?php

namespace App\Services\Pmtools;

use App\Repositories\Pmtools\ConcurrentUserRepository;

class ConcurrentUserService
{
    protected $concurrentUserRepository;

    public function __construct(ConcurrentUserRepository $concurrentUserRepository)
    {
        $this->concurrentUserRepository = $concurrentUserRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['minute_time', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['per'] = $search['per'] ?? 1;
        $search['minute_time2'] = $search['minute_time2'] ?? date('Y-m-d H:i:00', time()-60);
        $search['minute_time1'] = $search['minute_time1'] ?? date('Y-m-d H:i:00', time()-1800);

        //預設值
        $table = $chart_data = $chart = [];
        for ($i=strtotime($search['minute_time1']);$i<=strtotime($search['minute_time2']);$i+=60*$search['per']) {
            $minute = date('i', $i);
            $mod = $minute % $search['per'];
            $i += $mod == 0 ? 0:($search['per'] - $mod) * 60;
            $chart_data[$i] = 0;
        }

        $count = $this->concurrentUserRepository
            ->select(['minute_time','count'])
            ->search($search)
            ->order($order)
            ->count();
        if ($count <= 200) {
            $result = $this->concurrentUserRepository
                ->select(['minute_time','count'])
                ->search($search)
                ->order($order)
                ->result();
            //填入人數
            foreach ($result as $key => $row) {
                $chart_data[strtotime($row['minute_time'])] = $row['count'];
            }
            //轉換格式
            foreach ($chart_data as $key => $val) {
                $chart[] = [date('m-d H:i', $key),(int)$val];
                $table[] = [
                    'time' 	=> date('m-d H:i', $key),
                    'count'	=> $val
                ];
            }
            krsort($table);
        }

        return [
            'count'      => $count,
            'table'      => $table,
            'chart'      => $chart,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/app/Http/Controllers/Admin/AdminRoleController.php
<?php

namespace App\Http\Controllers\Admin;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Admin\AdminRoleForm;
use App\Services\Admin\AdminRoleService;

class AdminRoleController extends Controller
{
    protected $adminRoleService;

    public function __construct(AdminRoleService $adminRoleService)
    {
        $this->adminRoleService = $adminRoleService;
    }

    public function index(Request $request)
    {
        return view('admin_role.index', $this->adminRoleService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'admin'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('admin_role.create', $this->adminRoleService->create($request->input()));
    }

    public function store(AdminRoleForm $request)
    {
        $this->adminRoleService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('admin_role.edit', $this->adminRoleService->show($id));
    }

    public function update(AdminRoleForm $request, $id)
    {
        $this->adminRoleService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function save(Request $request, $id)
    {
        $this->adminRoleService->save($request->post(), $id);
        return 'done';
    }

    public function destroy(Request $request)
    {
        $this->adminRoleService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/app/Http/Controllers/User/UserController.php
<?php

namespace App\Http\Controllers\User;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\User\UserForm;
use App\Services\User\UserService;

class UserController extends Controller
{
    protected $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function index(Request $request)
    {
        return view('user.index', $this->userService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'user'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('user.create', $this->userService->create($request->input()));
    }

    public function store(UserForm $request)
    {
        $this->userService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('user.edit', $this->userService->show($id));
    }

    public function update(UserForm $request, $id)
    {
        $this->userService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function money($id)
    {
        View::share('sidebar', false);
        return view('user.money', $this->userService->show($id));
    }

    public function money_update(Request $request, $id)
    {
        $this->validate($request, [
            'money'       => 'required|integer|not_in:0',
            'description' => 'required',
        ], ['money.not_in'=>'增减点数 不可为0']);
        $input = $request->post();
        $input['type'] = $input['money'] < 0 ? 5:4;
        $input['description'] .= '-由'.session('username').'操作';
        $this->userService->addMoney($input, $id);

        session()->flash('message', '人工加减点完成!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function free($id)
    {
        View::share('sidebar', false);
        return view('user.free', $this->userService->show($id));
    }

    public function free_update(Request $request, $id)
    {
        $this->validate($request, [
            'free_day'    => 'integer|min:0'
        ], ['free_day.min'=>'免费天数不可为负数']);

        $this->userService->update([
            'free_time' => date('Y-m-d H:i:s', time() + 86400 * $request->free_day)
        ], $id);

        session()->flash('message', '设定完成!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function save(Request $request, $id)
    {
        $this->userService->save($request->post(), $id);
        return 'done';
    }

    public function destroy(Request $request)
    {
        $this->userService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }

    public function export(Request $request)
    {
        $this->userService->export($request->input());
    }
}
<file_sep>/app/Models/Admin/AdminActionLog.php
<?php

namespace Models\Admin;

use Models\Model;

class AdminActionLog extends Model
{
    const UPDATED_AT = null;

    protected $table = 'admin_action_log';

    protected $fillable = [
        'adminid',
        'route',
        'message',
        'sql_str',
        'ip',
        'status',
        'created_by',
    ];

    const STATUS = [
        1 => '成功',
        0 => '失败',
    ];

    public function admin()
    {
        return $this->belongsTo('Models\Admin\Admin', 'adminid');
    }
}
<file_sep>/app/Services/Pmtools/DailyRetentionService.php
<?php

namespace App\Services\Pmtools;

use App\Repositories\User\UserRepository;
use App\Repositories\Pmtools\DailyRetentionRepository;
use App\Repositories\Pmtools\DailyRetentionUserRepository;
use Models\Pmtools\DailyRetention;
use Models\Pmtools\DailyRetentionUser;

class DailyRetentionService
{
    protected $dailyRetentionRepository;
    protected $dailyRetentionUserRepository;
    protected $userRepository;

    public function __construct(
        DailyRetentionRepository $dailyRetentionRepository,
        DailyRetentionUserRepository $dailyRetentionUserRepository,
        UserRepository $userRepository
    ) {
        $this->dailyRetentionRepository = $dailyRetentionRepository;
        $this->dailyRetentionUserRepository = $dailyRetentionUserRepository;
        $this->userRepository = $userRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['date', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['date'] = $search['date'] ?? date('Y-m-d', time()-86400);

        $result = $this->dailyRetentionRepository
                ->search($search)
                ->order($order)
                ->result()->toArray();
        foreach ($result as $key => $row) {
            $percent = round($row['day_count']/$row['all_count']*100, 2);
            $row['percent'] = $row['all_count'] == 0 ? 0 : $percent;
            $result[$key] = $row;
        }

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function chart($input)
    {
        $search_params = param_process($input, ['date', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['date1'] = $search['date1'] ?? date('Y-m-d', time()-86400*10);
        $search['date2'] = $search['date2'] ?? date('Y-m-d', time()-86400);

        $date = $table = $chart = [];
        for ($i=strtotime($search['date1']);$i<=strtotime($search['date2']);$i+=86400) {
            $date[] = date('Y-m-d', $i);
        }

        for ($i=1;$i<=6;$i++) {
            for ($j=strtotime($search['date1']);$j<=strtotime($search['date2']);$j+=86400) {
                $table[$i][$j] = 0;
            }
        }

        $result = $this->dailyRetentionRepository
                ->search($search)
                ->order($order)
                ->result()->toArray();
        foreach ($result as $row) {
            $table[$row['type']][strtotime($row['date'])] = $row['day_count'];
        }

        foreach ($table as $type => $row) {
            $arr = [];
            foreach ($row as $val) {
                $arr[] = (int)$val;
            }

            $chart[] = [
                'name'  => DailyRetention::TYPE[$type],
                'data'  => $arr,
                'color' => DailyRetention::TYPE_COLOR[$type],
            ];
        }

        return [
            'table'      => $table,
            'chart'      => $chart,
            'date'       => $date,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function analysis($input)
    {
        $search_params = param_process($input, ['date', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['status'] = 1;
        $search['created_at1'] = $search['created_at1'] ?? date('Y-m-d', time()-86400*30);
        $search['created_at2'] = $search['created_at2'] ?? date('Y-m-d', time()-86400);

        $total = $this->userRepository->search($search)->count();

        $result = [];
        for ($i=1;$i<=8;$i++) {
            $row = $this->userRepository->retentionAnalysis($search['created_at1'], $search['created_at2'], $i);
            $row['type'] = $i;
            $row['percent'] = $total == 0 ? 0:round($row['count']/$total*100, 2);
            $result[] = $row;
        }

        return [
            'result'     => $result,
            'total'      => $total,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function user($input)
    {
        $search_params = param_process($input, ['date', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['date1'] = $search['date1'] ?? date('Y-m-d', time()-86400*6);
        $search['date2'] = $search['date2'] ?? date('Y-m-d', time()-86400);

        $date = $table = $chart = [];
        for ($i=strtotime($search['date1']);$i<=strtotime($search['date2']);$i+=86400) {
            $date[] = date('Y-m-d', $i);
        }

        for ($i=1;$i<=5;$i++) {
            for ($j=strtotime($search['date1']);$j<=strtotime($search['date2']);$j+=86400) {
                $table[$i][$j] = '0%(0/0)';
            }
        }

        // get main data.
        $result = $this->dailyRetentionUserRepository
                ->search($search)
                ->order($order)
                ->result();

        $table2 = $table;
        foreach ($result as $row) {
            $percent = $row['all_count'] == 0 ? 0:round($row['day_count'] / $row['all_count'] * 100, 2);
            $table[$row['type']][strtotime($row['date'])] = $percent."%($row[day_count]/$row[all_count])";
            $table2[$row['type']][strtotime($row['date'])] = $percent;
        }

        foreach ($table2 as $type => $row) {
            $arr = [];
            foreach ($row as $val) {
                $arr[] = (int)$val;
            }

            $chart[] = [
                'name' => DailyRetentionUser::TYPE[$type],
                'data' => $arr,
            ];
        }

        return [
            'table'      => $table,
            'chart'      => $chart,
            'date'       => $date,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/app/Services/Video/VideoActorsService.php
<?php

namespace App\Services\Video;

use App\Repositories\Video\VideoActorsRepository;

class VideoActorsService
{
    protected $videoActorsRepository;

    public function __construct(VideoActorsRepository $videoActorsRepository)
    {
        $this->videoActorsRepository = $videoActorsRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'asc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->videoActorsRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function save($row, $id)
    {
        $this->videoActorsRepository->save($row, $id);
    }
}
<file_sep>/app/Services/User/UserLoginLogService.php
<?php

namespace App\Services\User;

use App\Repositories\User\UserLoginLogRepository;
use App\Repositories\User\UserRepository;

class UserLoginLogService
{
    protected $userLoginLogRepository;
    protected $userRepository;

    public function __construct(
        UserLoginLogRepository $userLoginLogRepository,
        UserRepository $userRepository
    ) {
        $this->userLoginLogRepository = $userLoginLogRepository;
        $this->userRepository = $userRepository;
    }

    public function list($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $result = $this->userLoginLogRepository->search($search)
            ->order($order)->paginate(session('per_page'))
            ->result()->appends($input);

        foreach ($result as $key => $row) {
            $info = json_decode($row['ip_info'], true);
            $row['country'] = empty($info) ? '' : "$info[country_name]/$info[region_name]";
            $result[$key] = $row;
        }

        return [
            'result'     => $result,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }

    public function setLog($uid)
    {
        //當天第一次登入有簽到禮(排除免費看會員)
        $user = $this->userRepository->row($uid);
        if ($user['free_time'] < date('Y-m-d H:i:s')) {
            $date = date('Y-m-d');
            $result = $this->userLoginLogRepository->search([
                'uid'         => $uid,
                'created_at1' => $date,
                'created_at2' => $date,
            ])->result();
            $sysconfig = view()->shared('sysconfig');
            if ($result->toArray() === []) {
                $this->userRepository->addMoney($uid, 3, $sysconfig['daily_signin'], '每日签到');
            }
        }
        //寫入LOG
        $this->userLoginLogRepository->create([
            'uid' => $uid,
        ]);
    }

    public function loginMap($input)
    {
        $search_params = param_process($input, ['id', 'desc']);
        $order         = $search_params['order'];
        $search        = $search_params['search'];
        $params_uri    = $search_params['params_uri'];

        $search['created_at1'] = $search['created_at1'] ?? date('Y-m-d H:i:s', time()-86400);
        $search['created_at2'] = $search['created_at2'] ?? date('Y-m-d H:i:s');

        $where[] = ['created_at', '>=', $search['created_at1']];
        $where[] = ['created_at', '<=', $search['created_at2']];
        $result = $this->userLoginLogRepository
            ->select(['uid','ip_info'])
            ->where($where)
            ->group(['uid','ip_info'])
            ->result();

        $table = [];
        foreach ($result as $row) {
            $info = json_decode($row['ip_info'], true);
            if ($info == []) {
                continue;
            }
            $table[] = [
                'lat' => (float)$info['latitude'],
                'lng' => (float)$info['longitude']
            ];
        }

        return [
            'table'      => $table,
            'search'     => $search,
            'order'      => $order,
            'params_uri' => $params_uri,
        ];
    }
}
<file_sep>/database/migrations/2019_11_11_083342_create_video_tags_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateVideoTagsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'video_tags';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->default('')->comment('Tag名稱');
            $table->tinyInteger('hot')->default(0)->comment('熱門 0:否 1:是');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->index('created_at');
        });

        DB::statement("ALTER TABLE `$tableName` comment 'Tag清單'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('video_tags');
    }
}
<file_sep>/database/migrations/2019_12_20_145538_create_domain_setting_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateDomainSettingTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableName = 'domain_setting';

        Schema::create($tableName, function (Blueprint $table) {
            $table->increments('id');
            $table->string('domain')->default('')->comment('網域');
            $table->string('title')->default(0)->comment('標題');
            $table->string('keyword')->default(0)->comment('關鍵字');
            $table->string('description')->default(0)->comment('描述');
            $table->text('baidu')->comment('百度統計代碼');
            $table->dateTime('created_at')->default('1970-01-01 00:00:00')->comment('建檔時間');
            $table->string('created_by', 50)->default('')->comment('新增者');
            $table->dateTime('updated_at')->default('1970-01-01 00:00:00')->comment('更新時間');
            $table->string('updated_by', 50)->default('')->comment('更新者');
            $table->unique(['domain']);
        });

        DB::statement("ALTER TABLE `$tableName` comment '網域設定'");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('domain_setting');
    }
}
<file_sep>/app/Repositories/Admin/AdminRepository.php
<?php

namespace App\Repositories\Admin;

use App\Repositories\AbstractRepository;
use Models\Admin\Admin;

class AdminRepository extends AbstractRepository
{
    public function __construct(Admin $entity)
    {
        parent::__construct($entity);
    }

    private function _preAction($row)
    {
        if (isset($row['password'])) {
            if ($row['password'] != '') {
                $row['password'] = bcrypt($row['password']);
            } else {
                unset($row['password']);
            }
        }
        return $row;
    }

    public function create($row)
    {
        $row = $this->_preAction($row);
        return parent::create($row);
    }

    public function update($row, $id=0)
    {
        $row = $this->_preAction($row);
        return parent::update($row, $id);
    }

    public function _do_search()
    {
        if (isset($this->_search['username'])) {
            $this->db = $this->db->where('username', 'like', '%'.$this->_search['username'].'%');
        }

        if (isset($this->_search['mobile'])) {
            $this->db = $this->db->where('mobile', '=', $this->_search['mobile']);
        }

        if (isset($this->_search['status'])) {
            $this->db = $this->db->where('status', '=', $this->_search['status']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'       => '流水号',
        'username' => '用户名称',
        'password' => '<PASSWORD>',
        'mobile'   => '手机号码',
        'roleid'   => '角色群组',
        'status'   => '状态',
        'is_agent' => '是否为代理',
    ];
}
<file_sep>/app/Repositories/Admin/AdminRoleRepository.php
<?php

namespace App\Repositories\Admin;

use App\Repositories\AbstractRepository;
use Models\Admin\AdminRole;

class AdminRoleRepository extends AbstractRepository
{
    public function __construct(AdminRole $entity)
    {
        parent::__construct($entity);
    }

    public function _do_search()
    {
        if (isset($this->_search['name'])) {
            $this->db = $this->db->where('name', 'like', '%'.$this->_search['name'].'%');
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }

    public function getRoleList()
    {
        $where[] = ['id','>',1];
        $result = $this->where($where)->result()->toArray();
        return array_column($result, 'name', 'id');
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'             => '流水号',
        'name'           => '角色名称',
        'allow_operator' => '运营商权限',
        'allow_nav'      => '导航权限',
    ];
}
<file_sep>/app/Models/Video/Video.php
<?php

namespace Models\Video;

use Models\Model;

class Video extends Model
{
    protected $table = 'video';

    protected $fillable = [
        'keyword',
        'name',
        'publish',
        'actors',
        'tags',
        'pic_b',
        'pic_s',
        'url',
        'hls',
        'created_by',
        'updated_by',
    ];

    const STATUS = [
        0 => '已过期',
        1 => '正常',
    ];
}
<file_sep>/app/Http/Controllers/Pmtools/RetentionController.php
<?php

namespace App\Http\Controllers\Pmtools;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\Pmtools\DailyRetentionService;

class RetentionController extends Controller
{
    protected $dailyRetentionService;

    public function __construct(DailyRetentionService $dailyRetentionService)
    {
        $this->dailyRetentionService = $dailyRetentionService;
    }

    public function index(Request $request)
    {
        return view('pmtools.retention', $this->dailyRetentionService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'retention'));
    }

    public function chart(Request $request)
    {
        return view('pmtools.retention_chart', $this->dailyRetentionService->chart($request->input()));
    }

    public function chartSearch(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'retention_chart'));
    }

    public function analysis(Request $request)
    {
        return view('pmtools.retention_analysis', $this->dailyRetentionService->analysis($request->input()));
    }

    public function analysisSearch(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'retention_analysis'));
    }

    public function user(Request $request)
    {
        return view('pmtools.retention_user', $this->dailyRetentionService->user($request->input()));
    }

    public function userSearch(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'retention_user'));
    }
}
<file_sep>/app/Repositories/System/DomainSettingRepository.php
<?php

namespace App\Repositories\System;

use App\Repositories\AbstractRepository;
use Models\System\DomainSetting;

class DomainSettingRepository extends AbstractRepository
{
    public function __construct(DomainSetting $entity)
    {
        parent::__construct($entity);
    }

    public function _do_search()
    {
        if (isset($this->_search['domain'])) {
            $this->db = $this->db->where('domain', '=', $this->_search['domain']);
            unset($this->_search['domain']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1']);
        }

        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2']);
        }
        return $this;
    }

    /**
     * For 操作日誌用
     *
     * @var array
     */
    public static $columnList = [
        'id'          => '流水号',
        'domain'      => '网域',
        'title'       => '标题',
        'keyword'     => '关键字',
        'description' => '描述',
        'baidu'       => '百度统计代码',
    ];
}
<file_sep>/app/Http/Controllers/Admin/AdminController.php
<?php

namespace App\Http\Controllers\Admin;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Admin\AdminForm;
use App\Services\Admin\AdminService;

class AdminController extends Controller
{
    protected $adminService;

    public function __construct(AdminService $adminService)
    {
        $this->adminService = $adminService;
    }

    public function index(Request $request)
    {
        return view('admin.index', $this->adminService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'admin'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('admin.create', $this->adminService->create($request->input()));
    }

    public function store(AdminForm $request)
    {
        $this->adminService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('admin.edit', $this->adminService->show($id));
    }

    public function update(AdminForm $request, $id)
    {
        $this->adminService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function editpwd()
    {
        View::share('sidebar', false);
        return view('admin.editpwd');
    }

    public function updatepwd(Request $request)
    {
        $this->validate($request, [
            'old_pwd'    => 'required',
            'password'   => '<PASSWORD>',
            'repassword' => 'required|same:password',
        ]);
        $data = $this->adminService->show(session('id'));
        if (! \Hash::check($request->old_pwd, $data['row']['password'])) {
            return back()->withErrors(['old_pwd'=>'旧密码输入错误']);
        }
        
        $this->adminService->update($request->post(), session('id'));
        return "<script>alert('修改完成');parent.window.layer.close();parent.location.reload();</script>";
    }

    public function save(Request $request, $id)
    {
        $this->adminService->save($request->post(), $id);
        return 'done';
    }

    public function destroy(Request $request)
    {
        $this->adminService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/app/Http/Controllers/User/UserMoneyLogController.php
<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Services\User\UserMoneyLogService;

class UserMoneyLogController extends Controller
{
    protected $userMoneyLogService;

    public function __construct(UserMoneyLogService $userMoneyLogService)
    {
        $this->userMoneyLogService = $userMoneyLogService;
    }

    public function index(Request $request)
    {
        return view('user_money_log.index', $this->userMoneyLogService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'user_money_log'));
    }
}
<file_sep>/app/Repositories/User/UserLoginLogRepository.php
<?php

namespace App\Repositories\User;

use App\Repositories\AbstractRepository;
use App\Repositories\System\Ip2locationRepository;
use Models\User\UserLoginLog;

class UserLoginLogRepository extends AbstractRepository
{
    protected $ip2locationRepository;

    public function __construct(UserLoginLog $entity, Ip2locationRepository $ip2locationRepository)
    {
        parent::__construct($entity);
        $this->ip2locationRepository = $ip2locationRepository;
        $this->is_action_log = false;
    }

    public function create($row)
    {
        $ip = request()->getClientIp();
        $ip_info = $this->ip2locationRepository->getIpData($ip);
        $ip_info = $ip_info ?? [];

        $row['ip']      = $ip;
        $row['ip_info'] = json_encode($ip_info);
        $row['ua']      = request()->server('HTTP_USER_AGENT');

        return parent::create($row);
    }

    public function _do_search()
    {
        if (isset($this->_search['uid'])) {
            $this->db = $this->db->where('uid', '=', $this->_search['uid']);
        }
        if (isset($this->_search['username'])) {
            $this->db = $this->db->whereHas('user', function ($query) {
                $query->where('username', 'like', '%'.$this->_search['username'].'%');
            });
        }
        if (isset($this->_search['status'])) {
            $this->db = $this->db->whereHas('user', function ($query) {
                $query->where('status', '=', $this->_search['status']);
            });
        }

        if (isset($this->_search['ip'])) {
            $this->db = $this->db->where('ip', '=', $this->_search['ip']);
        }

        if (isset($this->_search['created_at1'])) {
            $this->db = $this->db->where('created_at', '>=', $this->_search['created_at1'] . ' 00:00:00');
        }
        if (isset($this->_search['created_at2'])) {
            $this->db = $this->db->where('created_at', '<=', $this->_search['created_at2'] . ' 23:59:59');
        }

        return $this;
    }

    /**
     * 取得區間內不重複登入帳號數
     *
     * @param string $startdate 起始時間
     * @param string $enddate   結束時間
     * @return int 人數
     */
    public function getLoginUsers($startdate, $enddate)
    {
        return $this->select(['uid'])->search([
            'status'      => 1,
            'created_at1' => $startdate,
            'created_at2' => $enddate,
        ])->group('uid')->result()->count();
    }
}
<file_sep>/app/Http/Controllers/Admin/AdminNavController.php
<?php

namespace App\Http\Controllers\Admin;

use View;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Admin\AdminNavForm;
use App\Services\Admin\AdminNavService;

class AdminNavController extends Controller
{
    protected $adminNavService;

    public function __construct(AdminNavService $adminNavService)
    {
        $this->adminNavService = $adminNavService;
    }

    public function index(Request $request)
    {
        return view('admin_nav.index', $this->adminNavService->list($request->input()));
    }

    public function search(Request $request)
    {
        return redirect(get_search_uri($request->input(), 'admin_nav'));
    }

    public function create(Request $request)
    {
        View::share('sidebar', false);
        return view('admin_nav.create', $this->adminNavService->create($request->input()));
    }

    public function store(AdminNavForm $request)
    {
        $this->adminNavService->store($request->post());

        session()->flash('message', '添加成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function edit($id)
    {
        View::share('sidebar', false);
        return view('admin_nav.edit', $this->adminNavService->show($id));
    }

    public function update(AdminNavForm $request, $id)
    {
        $this->adminNavService->update($request->post(), $id);

        session()->flash('message', '编辑成功!');
        return "<script>parent.window.layer.close();parent.location.reload();</script>";
    }

    public function save(Request $request, $id)
    {
        $this->adminNavService->save($request->post(), $id);
        return 'done';
    }

    public function destroy(Request $request)
    {
        $this->adminNavService->destroy($request->input('id'));
        session()->flash('message', '删除成功!');
        return 'done';
    }
}
<file_sep>/.env.example
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://tv.f1good.com

WEB_DOMAIN=iqqtv
API_DOMAIN=api.iqqtv
ADMIN_DOMAIN=admin.iqqtv

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=iqqtv
DB_USERNAME=iqqtv
DB_PASSWORD=<PASSWORD>

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=smtp.163.com
MAIL_PORT=465
MAIL_USERNAME=<EMAIL>
MAIL_PASSWORD=<PASSWORD>
MAIL_ENCRYPTION=ssl

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

SMS_URL=http://v.juhe.cn/sms/send
SMS_KEY=<KEY>

VIDEO_DOMAIN=https://apif1.qcapp.xyz
VIDEO_AU=apif1
VIDEO_KEY=<KEY>

JWT_SECRET=<KEY>
