Răsfoiți Sursa

1.定时任务
2.节点订阅

bingo 8 ani în urmă
părinte
comite
599cac3001

+ 43 - 0
app/Console/Commands/AutoDecGoodsTrafficJob.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Http\Models\Goods;
+use App\Http\Models\OrderGoods;
+use App\Http\Models\User;
+use Log;
+
+class AutoDecGoodsTrafficJob extends Command
+{
+    protected $signature = 'command:autoDecGoodsTrafficJob';
+    protected $description = '商品到期自动扣购买该商品的账号流量';
+
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function handle()
+    {
+        $goodsList = Goods::where('end_time', '<', date('Y-m-d H:i:s'))->get();
+        foreach ($goodsList as $goods) {
+            // 所有购买过该商品的用户
+            $orderGoods = OrderGoods::where('goods_id', $goods->id)->get();
+            foreach ($orderGoods as $og) {
+                $u = User::where('id', $og->user_id)->first();
+                if (empty($u)) {
+                    continue;
+                }
+
+                if ($u->transfer_enable - $goods->traffic * 1024 * 1024 < 0) {
+                    User::where('id', $og->user_id)->update(['transfer_enable' => 0]);
+                } else {
+                    User::where('id', $og->user_id)->decrement('transfer_enable', $goods->traffic * 1024 * 1024);
+                }
+            }
+        }
+
+        Log::info('定时任务:' . $this->description);
+    }
+}

+ 26 - 0
app/Console/Commands/DisableExpireUserJob.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\User;
+use Illuminate\Console\Command;
+use Log;
+
+class DisableExpireUserJob extends Command
+{
+    protected $signature = 'command:disableExpireUserJob';
+    protected $description = '禁用到期账号';
+
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function handle()
+    {
+        // 到期账号禁用
+        User::where('enable', 1)->where('expire_time', '<=', date('Y-m-d'))->update(['enable' => 0]);
+
+        Log::info('定时任务:' . $this->description);
+    }
+}

+ 30 - 0
app/Console/Commands/InviteExpire.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Invite;
+use Illuminate\Console\Command;
+use Log;
+
+class InviteExpire extends Command
+{
+    protected $signature = 'command:inviteExpire';
+    protected $description = '邀请码过期废除';
+
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function handle()
+    {
+        $inviteList = Invite::where('status', 0)->where('dateline', '<=', date('Y-m-d H:i:s'))->get();
+        if ($inviteList->isEmpty()) {
+            foreach ($inviteList as $invite) {
+                Invite::where('id', $invite->id)->update(['status' => 2]);
+            }
+        }
+
+        Log::info('定时任务:' . $this->description);
+    }
+}

+ 59 - 0
app/Console/Commands/UserExpireWarningJob.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Http\Models\Config;
+use App\Http\Models\User;
+use App\Mail\userExpireWarning;
+use Mail;
+use Log;
+
+class UserExpireWarningJob extends Command
+{
+    protected $signature = 'command:userExpireWarningJob';
+    protected $description = '用户到期提醒发邮件';
+
+    protected static $config;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $config = Config::get();
+        $data = [];
+        foreach ($config as $vo) {
+            $data[$vo->name] = $vo->value;
+        }
+
+        self::$config = $data;
+    }
+
+    public function handle()
+    {
+        if (self::$config['expire_warning']) {
+            $userList = User::where('transfer_enable', '>', 0)->whereIn('status', [0, 1])->where('enable', 1)->get();
+            foreach ($userList as $user) {
+                // 用户名不是邮箱的跳过
+                if (false === filter_var($user->username, FILTER_VALIDATE_EMAIL)) {
+                    continue;
+                }
+
+                $lastCanUseDays = floor(round(strtotime($user->expire_time) - strtotime(date('Y-m-d H:i:s'))) / 3600 / 24);
+                if ($lastCanUseDays > 0 && $lastCanUseDays <= self::$config['expire_days']) {
+                    $title = '账号过期提醒';
+                    $content = '账号还剩【' . $lastCanUseDays . '】天即将过期';
+
+                    try {
+                        Mail::to($user->username)->send(new userExpireWarning(self::$config['website_name'], $lastCanUseDays));
+                        $this->sendEmailLog($user->id, $title, $content);
+                    } catch (\Exception $e) {
+                        $this->sendEmailLog($user->id, $title, $content, 0, $e->getMessage());
+                    }
+                }
+            }
+        }
+
+        Log::info('定时任务:' . $this->description);
+    }
+}

+ 59 - 0
app/Console/Commands/UserTrafficWarningJob.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Http\Models\Config;
+use App\Http\Models\User;
+use App\Mail\userTrafficWarning;
+use Mail;
+use Log;
+
+class UserTrafficWarningJob extends Command
+{
+    protected $signature = 'command:userTrafficWarningJob';
+    protected $description = '用户流量警告提醒发邮件';
+
+    protected static $config;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $config = Config::get();
+        $data = [];
+        foreach ($config as $vo) {
+            $data[$vo->name] = $vo->value;
+        }
+
+        self::$config = $data;
+    }
+
+    public function handle()
+    {
+        if (self::$config['traffic_warning']) {
+            $userList = User::where('transfer_enable', '>', 0)->whereIn('status', [0, 1])->where('enable', 1)->get();
+            foreach ($userList as $user) {
+                // 用户名不是邮箱的跳过
+                if (false === filter_var($user->username, FILTER_VALIDATE_EMAIL)) {
+                    continue;
+                }
+
+                $usedPercent = round(($user->d + $user->u) / $user->transfer_enable, 2) * 100; // 已使用流量百分比
+                if ($usedPercent >= self::$config['traffic_warning_percent']) {
+                    $title = '流量警告';
+                    $content = '流量已使用:' . $usedPercent . '%,超过设置的流量阈值' . self::$config['traffic_warning_percent'] . '%';
+
+                    try {
+                        Mail::to($user->username)->send(new userTrafficWarning(self::$config['website_name'], $usedPercent));
+                        $this->sendEmailLog($user->id, $title, $content);
+                    } catch (\Exception $e) {
+                        $this->sendEmailLog($user->id, $title, $content, 0, $e->getMessage());
+                    }
+                }
+            }
+        }
+
+        Log::info('定时任务:' . $this->description);
+    }
+}

+ 10 - 3
app/Console/Kernel.php

@@ -13,7 +13,11 @@ class Kernel extends ConsoleKernel
      * @var array
      */
     protected $commands = [
-        //
+        \App\Console\Commands\DisableExpireUserJob::class,
+        \App\Console\Commands\AutoDecGoodsTrafficJob::class,
+        \App\Console\Commands\UserTrafficWarningJob::class,
+        \App\Console\Commands\UserExpireWarningJob::class,
+        \App\Console\Commands\InviteExpire::class,
     ];
 
     /**
@@ -24,8 +28,11 @@ class Kernel extends ConsoleKernel
      */
     protected function schedule(Schedule $schedule)
     {
-        // $schedule->command('inspire')
-        //          ->hourly();
+        $schedule->command('command:disableExpireUserJob')->everyMinute();
+        $schedule->command('command:autoDecGoodsTrafficJob')->everyTenMinutes();
+        $schedule->command('command:userTrafficWarningJob')->daily();
+        $schedule->command('command:userExpireWarningJob')->daily();
+        $schedule->command('command:inviteExpire')->everyThirtyMinutes();
     }
 
     /**

+ 0 - 75
app/Http/Controllers/AdminController.php

@@ -4,9 +4,7 @@ namespace App\Http\Controllers;
 
 use App\Http\Models\Article;
 use App\Http\Models\Config;
-use App\Http\Models\Goods;
 use App\Http\Models\Invite;
-use App\Http\Models\OrderGoods;
 use App\Http\Models\ReferralApply;
 use App\Http\Models\ReferralLog;
 use App\Http\Models\SsConfig;
@@ -17,12 +15,9 @@ use App\Http\Models\SsNodeInfo;
 use App\Http\Models\SsNodeOnlineLog;
 use App\Http\Models\User;
 use App\Http\Models\UserTrafficLog;
-use App\Mail\userExpireWarning;
-use App\Mail\userTrafficWarning;
 use Illuminate\Http\Request;
 use Redirect;
 use Response;
-use Mail;
 
 class AdminController extends BaseController
 {
@@ -50,76 +45,6 @@ class AdminController extends BaseController
         $view['totalRefAmount'] = ReferralApply::where('status', 2)->sum('amount');
         $view['expireWarningUserCount'] = User::where('expire_time', '<=', date('Y-m-d', strtotime("+15 days")))->where('enable', 1)->count();
 
-        // 到期账号禁用 TODO:加入定时任务
-        User::where('enable', 1)->where('expire_time', '<=', date('Y-m-d'))->update(['enable' => 0]);
-
-        // 商品到期自动扣购买该商品的流量 TODO:加入定时任务
-        $goodsList = Goods::where('end_time', '<', date('Y-m-d H:i:s'))->get();
-        foreach ($goodsList as $goods) {
-            // 所有购买过该商品的用户
-            $orderGoods = OrderGoods::where('goods_id', $goods->id)->get();
-            foreach ($orderGoods as $og) {
-                $u = User::where('id', $og->user_id)->first();
-                if (empty($u)) {
-                    continue;
-                }
-
-                if ($u->transfer_enable - $goods->traffic * 1024 * 1024 < 0) {
-                    User::where('id', $og->user_id)->update(['transfer_enable' => 0]);
-                } else {
-                    User::where('id', $og->user_id)->decrement('transfer_enable', $goods->traffic * 1024 * 1024);
-                }
-            }
-        }
-
-        // 用户流量警告提醒发邮件 TODO:加入定时任务
-        if (self::$config['traffic_warning']) {
-            $userList = User::where('transfer_enable', '>', 0)->whereIn('status', [0, 1])->where('enable', 1)->get();
-            foreach ($userList as $user) {
-                // 用户名不是邮箱的跳过
-                if (false === filter_var($user->username, FILTER_VALIDATE_EMAIL)) {
-                    continue;
-                }
-
-                $usedPercent = round(($user->d + $user->u) / $user->transfer_enable, 2) * 100; // 已使用流量百分比
-                if ($usedPercent >= self::$config['traffic_warning_percent']) {
-                    $title = '流量警告';
-                    $content = '流量已使用:' . $usedPercent . '%,超过设置的流量阈值' . self::$config['traffic_warning_percent'] . '%';
-
-                    try {
-                        Mail::to($user->username)->send(new userTrafficWarning(self::$config['website_name'], $usedPercent));
-                        $this->sendEmailLog($user->id, $title, $content);
-                    } catch (\Exception $e) {
-                        $this->sendEmailLog($user->id, $title, $content, 0, $e->getMessage());
-                    }
-                }
-            }
-        }
-
-        // 用户到期提醒发邮件 TODO:加入定时任务
-        if (self::$config['expire_warning']) {
-            $userList = User::where('transfer_enable', '>', 0)->whereIn('status', [0, 1])->where('enable', 1)->get();
-            foreach ($userList as $user) {
-                // 用户名不是邮箱的跳过
-                if (false === filter_var($user->username, FILTER_VALIDATE_EMAIL)) {
-                    continue;
-                }
-
-                $lastCanUseDays = floor(round(strtotime($user->expire_time) - strtotime(date('Y-m-d H:i:s'))) / 3600 / 24);
-                if ($lastCanUseDays > 0 && $lastCanUseDays <= self::$config['expire_days']) {
-                    $title = '账号过期提醒';
-                    $content = '账号还剩【' . $lastCanUseDays . '】天即将过期';
-
-                    try {
-                        Mail::to($user->username)->send(new userExpireWarning(self::$config['website_name'], $lastCanUseDays));
-                        $this->sendEmailLog($user->id, $title, $content);
-                    } catch (\Exception $e) {
-                        $this->sendEmailLog($user->id, $title, $content, 0, $e->getMessage());
-                    }
-                }
-            }
-        }
-
         return Response::view('admin/index', $view);
     }
 

+ 88 - 0
app/Http/Controllers/SubscribeController.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Models\SsGroup;
+use App\Http\Models\SsGroupNode;
+use App\Http\Models\SsNode;
+use App\Http\Models\User;
+use App\Http\Models\UserScoreLog;
+use App\Http\Models\UserSubscribe;
+use App\Http\Models\UserSubscribeLog;
+use Illuminate\Http\Request;
+use Response;
+use Redirect;
+use Cache;
+
+/**
+ * 订阅控制器
+ * Class SubscribeController
+ * @package App\Http\Controllers
+ */
+class SubscribeController extends BaseController
+{
+    protected static $config;
+
+    function __construct()
+    {
+        self::$config = $this->systemConfig();
+    }
+
+    // 登录页
+    public function index(Request $request, $code)
+    {
+        if (empty($code)) {
+            return Redirect::to('login');
+        }
+
+        // 校验合法性
+        $subscribe = UserSubscribe::where('code', $code)->with('user')->first();
+        if (empty($subscribe)) {
+            exit('非法请求');
+        }
+
+        $user = User::where('id', $subscribe->user_id)->whereIn('status', [0, 1])->where('enable', 1)->first();
+        if (empty($user)) {
+            exit('非法请求');
+        }
+
+        // 更新访问次数
+        $subscribe->increment('times', 1);
+
+        // 记录每次请求
+        $log = new UserSubscribeLog();
+        $log->sid = $subscribe->id;
+        $log->request_ip = $request->getClientIp();
+        $log->request_time = date('Y-m-d H:i:s');
+        $log->save();
+
+        // 获取这个账号可用节点
+        $group_ids = SsGroup::where('level', '<=', $user->level)->select(['id'])->get();
+        if (empty($group_ids)) {
+            exit();
+        }
+
+        $node_ids = SsGroupNode::whereIn('group_id', $group_ids)->select(['node_id'])->get();
+        $nodeList = SsNode::whereIn('id', $node_ids)->get();
+        $scheme = [];
+        foreach ($nodeList as $node) {
+            // 生成ssr scheme
+            $ssr_str = '';
+            $ssr_str .= $node->server . ':' . $user->port;
+            $ssr_str .= ':' . $user->protocol . ':' . $user->method;
+            $ssr_str .= ':' . $user->obfs . ':' . base64_encode($user->passwd);
+            $ssr_str .= '/?obfsparam=' . $user->obfs_param;
+            $ssr_str .= '&=protoparam' . $user->protocol_param;
+            $ssr_str .= '&remarks=' . base64_encode($node->name);
+            $ssr_str = $this->base64url_encode($ssr_str);
+            $scheme[] = 'ssr://' . $ssr_str;
+        }
+
+        foreach ($scheme as $vo) {
+            echo $vo . "\n";
+        }
+
+        exit();
+    }
+
+}

+ 26 - 0
app/Http/Controllers/UserController.php

@@ -20,6 +20,7 @@ use App\Http\Models\TicketReply;
 use App\Http\Models\User;
 use App\Http\Models\UserBalanceLog;
 use App\Http\Models\UserScoreLog;
+use App\Http\Models\UserSubscribe;
 use App\Http\Models\UserTrafficLog;
 use App\Http\Models\Verify;
 use App\Mail\activeUser;
@@ -908,4 +909,29 @@ TXT;
 
         return Response::json(['status' => 'success', 'data' => '', 'message' => '申请成功,请等待管理员审核']);
     }
+
+    // 节点订阅
+    public function subscribe(Request $request)
+    {
+        $user = $request->session()->get('user');
+
+        // 如果没有唯一码则生成一个
+        $subscribe = UserSubscribe::where('user_id', $user['id'])->first();
+        if (empty($subscribe)) {
+            $code = mb_substr(md5($user['id'] . '-' . $user['username']), 8, 16);
+
+            $obj = new UserSubscribe();
+            $obj->user_id = $user['id'];
+            $obj->code = $code;
+            $obj->times = 0;
+            $obj->created_at = date('Y-m-d H:i:s');
+            $obj->save();
+        } else {
+            $code = $subscribe->code;
+        }
+
+        $view['link'] = self::$config['website_url'] . '/subscribe/' . $code;
+
+        return Response::view('/user/subscribe', $view);
+    }
 }

+ 28 - 0
app/Http/Models/UserSubscribe.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * 用户订阅地址
+ * Class UserSubscribe
+ * @package App\Http\Models
+ */
+class UserSubscribe extends Model
+{
+    protected $table = 'user_subscribe';
+    protected $primaryKey = 'id';
+    public $timestamps = false;
+    protected $fillable = [
+        'user_id',
+        'code',
+        'times',
+        'created_at'
+    ];
+
+    public function User()
+    {
+        return $this->hasOne(User::class, 'id', 'user_id');
+    }
+}

+ 22 - 0
app/Http/Models/UserSubscribeLog.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Http\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * 用户订阅地址请求日志
+ * Class UserSubscribeLog
+ * @package App\Http\Models
+ */
+class UserSubscribeLog extends Model
+{
+    protected $table = 'user_subscribe_log';
+    protected $primaryKey = 'id';
+    public $timestamps = false;
+    protected $fillable = [
+        'sid',
+        'request_ip',
+        'request_time'
+    ];
+}

+ 47 - 25
readme.md

@@ -1,8 +1,8 @@
 ## 安装步骤
-#### 0.环境要求
+#### 环境要求
 ````
-PHP 7.1
-MYSQL 5.7
+PHP 7.1 (必须)
+MYSQL 5.5 (推荐5.6+)
 内存 1G+
 磁盘空间 10G+
 KVM
@@ -16,25 +16,39 @@ telegram群组:https://t.me/chatssrpanel
 用户名:admin 密码:123456
 ````
 
-#### 打赏作者一个巨无霸汉堡
+#### VPS推荐
+````
+部署面板必须得用到VPS,也就是服务器
+强烈推荐使用1G以上内存的KVM架构的服务器
+
+https://github.com/ssrpanel/ssrpanel/wiki/VPS%E6%8E%A8%E8%8D%90
+````
+![VPS推荐](https://github.com/ssrpanel/ssrpanel/wiki/VPS%E6%8E%A8%E8%8D%90)
+
+
+#### 打赏作者
 ````
 哈哈,如果你觉得这套代码好用,可以请我吃一个巨无霸汉堡,微信扫一下
 将持续开发,喜欢请star一下
 ````
 ![打赏作者](https://github.com/ssrpanel/ssrpanel/blob/master/public/assets/images/donate.jpeg?raw=true)
 
-### 谢谢以下的人请我吃麦当劳
+### 捐赠名单
 | 昵称      |    金额 |
 | :------- | --------:| 
 | Law-杰   | ¥10 | 
 | Err      | ¥51 | 
 | 緃噺開始 |  ¥5 | 
-|【要求匿名】|¥67|
-|、无奈|¥5|
+|【要求匿名】|¥267|
+|、无奈 |¥5|
 |Sunny Woon| ¥10|
 |aazzpp678 | ¥26|
 |风云_1688|¥15|
-
+截止目前收到的捐赠:¥389
+这些捐赠的用途:
+1.买了1台VPS做开发测试用
+2.一个Beyond Compare 4的正版激活码(2017-10-01)
+3.谢谢大家及时反馈BUG,发现BUG请提到issue里
 
 
 #### PHP7环境配置
@@ -83,12 +97,12 @@ service nginx reload
 
 ## SSR服务端
 ````
-把 server目录下的 ssr-3.4.0.zip 拷贝到 /root/,解压缩,怎么运行自己上网搜
-把userapiconfig.py里的 API_INTERFACE 设置为 glzjinmod
-把user-config.json里的 connect_verbose_info 设置为 1
+把 server 目录下的 ssr-3.4.0.zip 拷贝到 /root/,解压缩,怎么运行自己上网搜
+把 userapiconfig.py 里的 API_INTERFACE 设置为 glzjinmod
+把 user-config.json 里的 connect_verbose_info 设置为 1
 ````
 
-## 日志分析(目前仅支持单节点)
+## 日志分析(目前仅支持单机单节点)
 ````
 找到SSR服务端所在的ssserver.log文件
 进入ssrpanel所在目录,建立一个软连接,并授权
@@ -97,21 +111,29 @@ ln -S ssserver.log /root/shadowsocksr/ssserver.log
 chown www:www ssserver.log
 ````
 
+## 定时任务(所有自动发邮件的地方都要用到,所以请务必配置)
+````
+编辑crontab
+crontab -e
+
+然后加入如下(请自行修改ssrpanel路径)
+* * * * * php /home/wwwroot/ssrpanel/artisan schedule:run >> /dev/null 2>&1
+````
+
 ## 说明
 ````
-1.账号管理面板
-2.需配合SSR3.4版本后端使用
-3.强大的管理后台
-4.美观的界面
-5.支持手机自适应,方便管理账号
-6.商品(流量包)
-7.优惠券(用于购买流量包时抵用、促销)
-8.抽奖(开发中)
-9.可以注册账号,邮件激活等等
-10.剩下的自己发掘
-11.如果OK请打赏一下,接下来可能会使用glzjin大牛的魔改SSR后端,等我研究下怎么用先
-12.注意:这只是个面板,glzjin大神的面板有点丑,但是它的后端很牛逼
-(因为我不会python,改不动大神的魔改后端,目前在自学Go中,可能以后会写个Go后端)
+1.多节点账号管理面板
+2.需配合SSR 3.4 Python版后端使用
+3.强大的管理后台、美观的界面、简单易用的开关、支持移动端自适应
+4.内含简单的购物、优惠券、流量兑换、邀请码、推广返利&提现、文章管理、工单等系统
+5.节点可以分组,不同级别的用户可以看到不同级别分组的节点
+6.SS配置转SSR配置,方便使用SS后端一键把账号转入到系统
+7.流量日志、单机单节点日志分析功能,知道用户最近都看了哪些网站
+7.定时任务、所有邮件投递都有记录
+8.后台一键添加加密方式、混淆、协议
+9.强大的后台配置功能
+10.更多功能自己发掘
+11.ssrpanel的定位:比sspanel强大,比sspanel mod弱鸡
 ````
 
 ![Markdown](http://i4.bvimg.com/1949/aac73bf589fbd785.png)

+ 1 - 1
resources/views/admin/inviteList.blade.php

@@ -66,7 +66,7 @@
                                                     <td> {{$invite->id}} </td>
                                                     <td> {{$invite->code}} </td>
                                                     <td> {{$invite->dateline}} </td>
-                                                    <td> {{$invite->generator->username}} </td>
+                                                    <td> {{empty($invite->generator) ? '【账号已删除】' : $invite->generator->username}} </td>
                                                     <td> {{empty($invite->user) ? '' : $invite->user->username}} </td>
                                                     <td>
                                                         @if($invite->status == '0')

+ 2 - 2
resources/views/user/layouts.blade.php

@@ -107,14 +107,14 @@
             <!-- DOC: Set data-keep-expand="true" to keep the submenues expanded -->
             <!-- DOC: Set data-auto-speed="200" to adjust the sub menu slide up/down speed -->
             <ul class="page-sidebar-menu   " data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200">
-                <li class="nav-item start {{Request::getRequestUri() == '/user' ? 'active open' : ''}}">
+                <li class="nav-item start {{(Request::getRequestUri() == '/' || Request::getRequestUri() == '/user') ? 'active open' : ''}}">
                     <a href="{{url('user')}}" class="nav-link nav-toggle">
                         <i class="icon-home"></i>
                         <span class="title">首页</span>
                         <span class="selected"></span>
                     </a>
                 </li>
-                <li class="nav-item {{Request::getRequestUri() == '/user/nodeList' ? 'active open' : ''}}">
+                <li class="nav-item {{Request::getRequestUri() == '/user/nodeList' || Request::getRequestUri() == '/user/subscribe' ? 'active open' : ''}}">
                     <a href="{{url('user/nodeList')}}" class="nav-link nav-toggle">
                         <i class="icon-list"></i>
                         <span class="title">节点列表</span>

+ 10 - 0
resources/views/user/nodeList.blade.php

@@ -26,6 +26,11 @@
                             <i class="icon-list font-dark"></i>
                             <span class="caption-subject bold uppercase"> 节点列表 </span>
                         </div>
+                        <div class="actions">
+                            <div class="btn-group">
+                                <button class="btn sbold blue" onclick="subscribe()"> 订阅 </button>
+                            </div>
+                        </div>
                     </div>
                     <div class="portlet-body">
                         <div class="alert alert-danger">
@@ -183,5 +188,10 @@
             $('#qrcode_ssr_img_{{$node->id}}').qrcode("{{$node->ssr_scheme}}");
             $('#qrcode_ss_img_{{$node->id}}').qrcode("{{$node->ss_scheme}}");
         @endforeach
+
+        // 节点订阅
+        function subscribe() {
+            window.location.href = '{{url('/user/subscribe')}}';
+        }
     </script>
 @endsection

+ 69 - 0
resources/views/user/subscribe.blade.php

@@ -0,0 +1,69 @@
+@extends('user.layouts')
+
+@section('css')
+    <link href="/assets/global/plugins/fancybox/source/jquery.fancybox.css" rel="stylesheet" type="text/css" />
+    <style>
+        .fancybox > img {
+            width: 75px;
+            height: 75px;
+        }
+    </style>
+@endsection
+@section('title', '控制面板')
+@section('content')
+    <!-- BEGIN CONTENT BODY -->
+    <div class="page-content">
+        <!-- BEGIN PAGE BREADCRUMB -->
+        <ul class="page-breadcrumb breadcrumb">
+            <li>
+                <a href="{{url('user/nodeList')}}">节点列表</a>
+                <i class="fa fa-circle"></i>
+            </li>
+            <li>
+                <a href="{{url('user/subscribe')}}">订阅</a>
+            </li>
+        </ul>
+        <!-- END PAGE BREADCRUMB -->
+        <!-- BEGIN PAGE BASE CONTENT -->
+        <div class="row">
+            <div class="col-md-12">
+                <div class="alert alert-danger">
+                    <strong>警告:</strong>该订阅地址仅限个人使用,请勿传播该地址,否则会导致您的账号流量异常。
+                </div>
+                <div class="portlet light form-fit bordered">
+                    <div class="portlet-title">
+                        <div class="caption">
+                            <i class="icon-link font-blue"></i>
+                            <span class="caption-subject font-blue bold uppercase">订阅地址</span>
+                        </div>
+                    </div>
+                    <div class="portlet-body form">
+                        <div class="mt-clipboard-container">
+                            <input type="text" id="mt-target-1" class="form-control" value="{{$link}}" />
+                            <a href="javascript:;" class="btn blue mt-clipboard" data-clipboard-action="copy" data-clipboard-target="#mt-target-1">
+                                <i class="icon-note"></i> 复制链接
+                            </a>
+                        </div>
+                    </div>
+                </div>
+
+            </div>
+        </div>
+        <!-- END PAGE BASE CONTENT -->
+    </div>
+    <!-- END CONTENT BODY -->
+@endsection
+@section('script')
+    <script src="/assets/global/plugins/bootbox/bootbox.min.js" type="text/javascript"></script>
+    <script src="/assets/global/plugins/clipboardjs/clipboard.min.js" type="text/javascript"></script>
+    <script src="/assets/pages/scripts/components-clipboard.min.js" type="text/javascript"></script>
+
+    <script type="text/javascript">
+        // 申请提现
+        function extractMoney() {
+            $.post("{{url('user/extractMoney')}}", {_token:'{{csrf_token()}}'}, function (ret) {
+                bootbox.alert(ret.message);
+            });
+        }
+    </script>
+@endsection

+ 2 - 0
routes/web.php

@@ -7,6 +7,7 @@ Route::any('resetPassword', 'UserController@resetPassword'); // 重设密码
 Route::any('reset/{token}', 'UserController@reset'); // 重设密码
 Route::any('activeUser', 'UserController@activeUser'); // 激活账号
 Route::get('active/{token}', 'UserController@active'); // 激活账号
+Route::get('subscribe/{code}', 'SubscribeController@index'); // 节点订阅地址
 
 Route::group(['middleware' => ['user', 'admin']], function() {
     Route::get('admin', 'AdminController@index'); // 后台首页
@@ -77,6 +78,7 @@ Route::group(['middleware' => ['user']], function(){
     Route::any('user', 'UserController@index'); // 用户首页
     Route::any('user/article', 'UserController@article'); // 文章详情
     Route::get('user/nodeList', 'UserController@nodeList'); // 节点列表
+    Route::get('user/subscribe', 'UserController@subscribe'); // 节点订阅
     Route::get('user/goodsList', 'UserController@goodsList'); // 商品列表
     Route::get('user/trafficLog', 'UserController@trafficLog'); // 流量日志
     Route::get('user/ticketList', 'UserController@ticketList'); // 工单

+ 25 - 0
sql/db.sql

@@ -515,6 +515,31 @@ CREATE TABLE `email_log` (
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邮件投递记录';
 
 
+-- ----------------------------
+-- Table structure for `user_subscribe`
+-- ----------------------------
+CREATE TABLE `user_subscribe` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID',
+  `code` varchar(255) DEFAULT '' COMMENT '订阅地址唯一识别码',
+  `times` int(11) NOT NULL DEFAULT '0' COMMENT '地址请求次数',
+  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+
+-- ----------------------------
+-- Table structure for `user_subscribe_log`
+-- ----------------------------
+CREATE TABLE `user_subscribe_log` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `sid` int(11) DEFAULT NULL COMMENT '对应user_subscribe的id',
+  `request_ip` varchar(20) DEFAULT NULL COMMENT '请求IP',
+  `request_time` datetime DEFAULT NULL COMMENT '请求时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+
 /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
 /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;

+ 23 - 0
sql/update/20171002.sql

@@ -0,0 +1,23 @@
+-- ----------------------------
+-- Table structure for `user_subscribe`
+-- ----------------------------
+CREATE TABLE `user_subscribe` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID',
+  `code` varchar(255) DEFAULT '' COMMENT '订阅地址唯一识别码',
+  `times` int(11) NOT NULL DEFAULT '0' COMMENT '地址请求次数',
+  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+
+-- ----------------------------
+-- Table structure for `user_subscribe_log`
+-- ----------------------------
+CREATE TABLE `user_subscribe_log` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `sid` int(11) DEFAULT NULL COMMENT '对应user_subscribe的id',
+  `request_ip` varchar(20) DEFAULT NULL COMMENT '请求IP',
+  `request_time` datetime DEFAULT NULL COMMENT '请求时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;