ソースを参照

Initial Commit

钱半仙 7 年 前
コミット
2461adcb14
100 ファイル変更11571 行追加0 行削除
  1. 1 0
      .env
  2. 1 0
      .env.example
  3. 2 0
      .gitignore
  4. 0 0
      .gitmodules
  5. 26 0
      404.html
  6. 22 0
      LICENSE
  7. 71 0
      app/Command/DailyMail.php
  8. 53 0
      app/Command/ExtMail.php
  9. 802 0
      app/Command/Job.php
  10. 227 0
      app/Command/SyncRadius.php
  11. 227 0
      app/Command/XCat.php
  12. 112 0
      app/Controllers/Admin/AnnController.php
  13. 77 0
      app/Controllers/Admin/AutoController.php
  14. 142 0
      app/Controllers/Admin/CodeController.php
  15. 149 0
      app/Controllers/Admin/DetectController.php
  16. 192 0
      app/Controllers/Admin/IpController.php
  17. 293 0
      app/Controllers/Admin/NodeController.php
  18. 328 0
      app/Controllers/Admin/RelayController.php
  19. 276 0
      app/Controllers/Admin/ShopController.php
  20. 128 0
      app/Controllers/Admin/TicketController.php
  21. 247 0
      app/Controllers/Admin/UserController.php
  22. 180 0
      app/Controllers/AdminController.php
  23. 164 0
      app/Controllers/ApiController.php
  24. 432 0
      app/Controllers/AuthController.php
  25. 42 0
      app/Controllers/BaseController.php
  26. 112 0
      app/Controllers/HomeController.php
  27. 1758 0
      app/Controllers/LinkController.php
  28. 220 0
      app/Controllers/Mod_Mu/FuncController.php
  29. 75 0
      app/Controllers/Mod_Mu/NodeController.php
  30. 230 0
      app/Controllers/Mod_Mu/UserController.php
  31. 57 0
      app/Controllers/Mu/NodeController.php
  32. 124 0
      app/Controllers/Mu/UserController.php
  33. 96 0
      app/Controllers/PasswordController.php
  34. 426 0
      app/Controllers/RelayController.php
  35. 20 0
      app/Controllers/ResController.php
  36. 1713 0
      app/Controllers/UserController.php
  37. 29 0
      app/Middleware/Admin.php
  38. 38 0
      app/Middleware/Api.php
  39. 46 0
      app/Middleware/Auth.php
  40. 21 0
      app/Middleware/Guest.php
  41. 52 0
      app/Middleware/Mod_Mu.php
  42. 52 0
      app/Middleware/Mu.php
  43. 15 0
      app/Models/Ann.php
  44. 24 0
      app/Models/Auto.php
  45. 24 0
      app/Models/BlockIp.php
  46. 35 0
      app/Models/Bought.php
  47. 10 0
      app/Models/CheckInLog.php
  48. 22 0
      app/Models/Code.php
  49. 31 0
      app/Models/Coupon.php
  50. 48 0
      app/Models/DetectLog.php
  51. 13 0
      app/Models/DetectRule.php
  52. 13 0
      app/Models/Disconnect.php
  53. 13 0
      app/Models/EmailVerify.php
  54. 13 0
      app/Models/InviteCode.php
  55. 43 0
      app/Models/Ip.php
  56. 9 0
      app/Models/Link.php
  57. 31 0
      app/Models/LoginIp.php
  58. 14 0
      app/Models/Model.php
  59. 180 0
      app/Models/Node.php
  60. 21 0
      app/Models/NodeInfoLog.php
  61. 10 0
      app/Models/NodeOnlineLog.php
  62. 9 0
      app/Models/PasswordReset.php
  63. 20 0
      app/Models/Payback.php
  64. 9 0
      app/Models/Paylist.php
  65. 9 0
      app/Models/RadiusBan.php
  66. 9 0
      app/Models/RadiusNas.php
  67. 9 0
      app/Models/RadiusRadAcct.php
  68. 9 0
      app/Models/RadiusRadCheck.php
  69. 9 0
      app/Models/RadiusRadPostauth.php
  70. 9 0
      app/Models/RadiusRadUserGroup.php
  71. 50 0
      app/Models/Relay.php
  72. 13 0
      app/Models/Role.php
  73. 166 0
      app/Models/Shop.php
  74. 85 0
      app/Models/Speedtest.php
  75. 29 0
      app/Models/TelegramSession.php
  76. 29 0
      app/Models/Ticket.php
  77. 9 0
      app/Models/Token.php
  78. 49 0
      app/Models/TrafficLog.php
  79. 28 0
      app/Models/UnblockIp.php
  80. 337 0
      app/Models/User.php
  81. 10 0
      app/Models/WecenterUser.php
  82. 31 0
      app/Services/Analytic.php
  83. 118 0
      app/Services/Analytics.php
  84. 35 0
      app/Services/Auth.php
  85. 10 0
      app/Services/Auth/Base.php
  86. 78 0
      app/Services/Auth/Cookie.php
  87. 7 0
      app/Services/Auth/File.php
  88. 37 0
      app/Services/Auth/JwtToken.php
  89. 73 0
      app/Services/Auth/Redis.php
  90. 31 0
      app/Services/Auth/Token.php
  91. 19 0
      app/Services/Aws/Client.php
  92. 34 0
      app/Services/Aws/Factory.php
  93. 50 0
      app/Services/Boot.php
  94. 7 0
      app/Services/Cache.php
  95. 111 0
      app/Services/Config.php
  96. 46 0
      app/Services/Factory.php
  97. 29 0
      app/Services/Jwt.php
  98. 71 0
      app/Services/Mail.php
  99. 9 0
      app/Services/Mail/Base.php
  100. 46 0
      app/Services/Mail/Mailgun.php

+ 1 - 0
.env

@@ -0,0 +1 @@
+#为了能更愉快地 debug,此文件已经作废,请各位 移步 config/config.php 进行设置

+ 1 - 0
.env.example

@@ -0,0 +1 @@
+#为了能更愉快地 debug,此文件已经作废,请各位 移步 config/config.php 进行设置

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*/.DS_Store
+.DS_Store

+ 0 - 0
.gitmodules


+ 26 - 0
404.html

@@ -0,0 +1,26 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+<title>404</title>
+<style>
+	body{
+		background-color:#444;
+		font-size:14px;
+	}
+	h3{
+		font-size:60px;
+		color:#eee;
+		text-align:center;
+		padding-top:30px;
+		font-weight:normal;
+	}
+</style>
+</head>
+
+<body>
+<h3>404,您请求的文件不存在!</h3>
+</body>
+</html>

+ 22 - 0
LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 orvice
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 71 - 0
app/Command/DailyMail.php

@@ -0,0 +1,71 @@
+<?php
+
+
+namespace App\Command;
+
+use App\Models\User;
+use App\Models\Ann;
+use App\Services\Config;
+use App\Services\Mail;
+use App\Utils\Telegram;
+use App\Utils\Tools;
+use App\Services\Analytics;
+
+class DailyMail
+{
+    public static function sendDailyMail()
+    {
+        $users = User::all();
+        $logs = Ann::orderBy('id', 'desc')->get();
+        $text1="";
+        
+        foreach ($logs as $log) {
+            if (strpos($log->content, "Links")===false) {
+                $text1=$text1.$log->content."<br><br>";
+            }
+        }
+        
+        $lastday_total = 0;
+        
+        foreach ($users as $user) {
+            $lastday = (($user->u+$user->d)-$user->last_day_t)/1024/1024;
+            $lastday_total += (($user->u+$user->d)-$user->last_day_t);
+            
+            if ($user->sendDailyMail==1) {
+                echo "Send daily mail to user: ".$user->id;
+                $subject = Config::get('appName')."-每日流量报告以及公告";
+                $to = $user->email;
+                $text = "下面是系统中目前的公告:<br><br>".$text1."<br><br>晚安!";
+                
+                try {
+                    Mail::send($to, $subject, 'news/daily-traffic-report.tpl', [
+                        "user" => $user,"text" => $text,"lastday"=>$lastday
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+                $text="";
+            }
+        }
+        
+        $sts = new Analytics();
+        
+        Telegram::Send("各位老爷少奶奶,我来为大家报告一下系统今天的运行状况哈~".
+        PHP_EOL.
+        "今日签到人数:".$sts->getTodayCheckinUser().PHP_EOL.
+        "今日使用总流量:".Tools::flowAutoShow($lastday_total).PHP_EOL.
+        "晚安~"
+        );
+    }
+
+
+    public static function reall()
+    {
+        $users = User::all();
+        foreach ($users as $user) {
+            $user->last_day_t=($user->u+$user->d);
+            $user->save();
+        }
+    }
+}

+ 53 - 0
app/Command/ExtMail.php

@@ -0,0 +1,53 @@
+<?php
+
+
+namespace App\Command;
+
+use App\Models\User;
+use App\Services\Config;
+use App\Services\Mail;
+
+class ExtMail
+{
+    public static function sendNoMail()
+    {
+        $users = User::all();
+        foreach ($users as $user) {
+            if ($user->t==0) {
+                echo "Send daily mail to user: ".$user->id;
+                $subject = Config::get('appName')."-期待您的回归";
+                $to = $user->email;
+                $text = "似乎您在".Config::get('appName')."上的流量一直是 0 呢(P.S:也可能是您没有使用 ss 而使用了其他还不能计入流量的方式....),如果您在使用上遇到了任何困难,请不要犹豫,登录到".Config::get('appName').",您就会知道如何使用了,特别是对于 iOS 用户,最近在使用的优化上大家都付出了很多的努力。期待您的回归~" ;
+                try {
+                    Mail::send($to, $subject, 'ext/back.tpl', [
+                    "user" => $user,"text" => $text
+                ], [
+                ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+            }
+        }
+    }
+
+    public static function sendOldMail()
+    {
+        $users = User::all();
+        foreach ($users as $user) {
+            if ($user->t!=0&&$user->t<1451577599) {
+                echo "Send daily mail to user: ".$user->id;
+                $subject = Config::get('appName')."-期待您的回归";
+                $to = $user->email;
+                $text = "似乎您在 2017 年以来就没有使用过".Config::get('appName')."了呢,如果您在使用上遇到了任何困难,请不要犹豫,登录到".Config::get('appName').",您就会知道如何使用了,特别是对于 iOS 用户,最近在使用的优化上大家都付出了很多的努力。期待您的回归~" ;
+                try {
+                    Mail::send($to, $subject, 'ext/back.tpl', [
+                        "user" => $user,"text" => $text
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+            }
+        }
+    }
+}

+ 802 - 0
app/Command/Job.php

@@ -0,0 +1,802 @@
+<?php
+
+namespace App\Command;
+
+use App\Models\Node;
+use App\Models\User;
+use App\Models\RadiusBan;
+use App\Models\LoginIp;
+use App\Models\Speedtest;
+use App\Models\Shop;
+use App\Models\Bought;
+use App\Models\Coupon;
+use App\Models\Ip;
+use App\Models\NodeInfoLog;
+use App\Models\NodeOnlineLog;
+use App\Models\TrafficLog;
+use App\Models\DetectLog;
+use App\Models\BlockIp;
+use App\Models\TelegramSession;
+use App\Models\EmailVerify;
+use App\Services\Config;
+use App\Utils\Radius;
+use App\Utils\Wecenter;
+use App\Utils\Tools;
+use App\Services\Mail;
+use App\Utils\QQWry;
+use App\Utils\Duoshuo;
+use App\Utils\GA;
+use App\Utils\Telegram;
+use CloudXNS\Api;
+use App\Models\Disconnect;
+use App\Models\UnblockIp;
+
+class Job
+{
+    public static function syncnode()
+    {
+        $nodes = Node::all();
+        foreach ($nodes as $node) {
+            if ($node->sort==0) {
+                $ip=gethostbyname($node->server);
+                $node->node_ip=$ip;
+                $node->save();
+            }
+        }
+    }
+
+    public static function backup()
+    {
+        mkdir('/tmp/ssmodbackup/');
+
+        $db_address_array = explode(':', Config::get('db_host'));
+
+        system('mysqldump --user='.Config::get('db_username').' --password='.Config::get('db_password').' --host='.$db_address_array[0].' '.(isset($db_address_array[1])?'-P '.$db_address_array[1]:'').' '.Config::get('db_database').' announcement auto blockip bought code coupon disconnect_ip link login_ip payback radius_ban shop speedtest ss_invite_code ss_node ss_password_reset ticket unblockip user user_token email_verify detect_list relay paylist> /tmp/ssmodbackup/mod.sql', $ret);
+
+
+        system('mysqldump --opt --user='.Config::get('db_username').' --password='.Config::get('db_password').' --host='.$db_address_array[0].' '.(isset($db_address_array[1])?'-P '.$db_address_array[1]:'').' -d '.Config::get('db_database').' alive_ip ss_node_info ss_node_online_log user_traffic_log detect_log telegram_session >> /tmp/ssmodbackup/mod.sql', $ret);
+
+        if (Config::get('enable_radius')=='true') {
+            $db_address_array = explode(':', Config::get('radius_db_host'));
+            system('mysqldump --user='.Config::get('radius_db_user').' --password='.Config::get('radius_db_password').' --host='.$db_address_array[0].' '.(isset($db_address_array[1])?'-P '.$db_address_array[1]:'').''.Config::get('radius_db_database').'> /tmp/ssmodbackup/radius.sql', $ret);
+        }
+
+        if (Config::get('enable_wecenter')=='true') {
+            $db_address_array = explode(':', Config::get('wecenter_db_host'));
+            system('mysqldump --user='.Config::get('wecenter_db_user').' --password='.Config::get('wecenter_db_password').' --host='.(isset($db_address_array[1])?'-P '.$db_address_array[1]:'').' '.Config::get('wecenter_db_database').'> /tmp/ssmodbackup/wecenter.sql', $ret);
+        }
+
+        system("cp ".Config::get('auto_backup_webroot')."/config/.config.php /tmp/ssmodbackup/configbak.php", $ret);
+        echo $ret;
+        system("zip -r /tmp/ssmodbackup.zip /tmp/ssmodbackup/* -P ".Config::get('auto_backup_passwd'), $ret);
+
+        $subject = Config::get('appName')."-备份成功";
+        $to = Config::get('auto_backup_email');
+        $text = "您好,系统已经为您自动备份,请查看附件,用您设定的密码解压。" ;
+        try {
+            Mail::send($to, $subject, 'news/backup.tpl', [
+                "text" => $text
+            ], ["/tmp/ssmodbackup.zip"
+            ]);
+        } catch (Exception $e) {
+            echo $e->getMessage();
+        }
+
+        system("rm -rf /tmp/ssmodbackup", $ret);
+        system("rm /tmp/ssmodbackup.zip", $ret);
+
+        Telegram::Send("备份完毕了喵~今天又是安全祥和的一天呢。");
+    }
+
+    public static function SyncDuoshuo()
+    {
+        $users = User::all();
+        foreach ($users as $user) {
+            Duoshuo::add($user);
+        }
+        echo "ok";
+    }
+
+    public static function UserGa()
+    {
+        $users = User::all();
+        foreach ($users as $user) {
+            $ga = new GA();
+            $secret = $ga->createSecret();
+
+            $user->ga_token=$secret;
+            $user->save();
+        }
+        echo "ok";
+    }
+
+    public static function syncnasnode()
+    {
+        $nodes = Node::all();
+        foreach ($nodes as $node) {
+            if ($node->sort==1) {
+                $ip=gethostbyname($node->server);
+                $node->node_ip=$ip;
+                $node->save();
+
+                Radius::AddNas($node->node_ip, $node->server);
+            }
+        }
+    }
+
+    public static function DailyJob()
+    {
+        $nodes = Node::all();
+        foreach ($nodes as $node) {
+            if ($node->sort == 0 || $node->sort == 10) {
+                if (date("d")==$node->bandwidthlimit_resetday) {
+                    $node->node_bandwidth=0;
+                    $node->save();
+                }
+            }
+        }
+
+        NodeInfoLog::where("log_time", "<", time()-86400*3)->delete();
+        NodeOnlineLog::where("log_time", "<", time()-86400*3)->delete();
+        TrafficLog::where("log_time", "<", time()-86400*3)->delete();
+        DetectLog::where("datetime", "<", time()-86400*3)->delete();
+        Speedtest::where("datetime", "<", time()-86400*3)->delete();
+        EmailVerify::where("expire_in", "<", time()-86400*3)->delete();
+        Telegram::Send("姐姐姐姐,数据库被清理了,感觉身体被掏空了呢~");
+
+        //auto reset
+        $boughts=Bought::all();
+        foreach ($boughts as $bought) {
+            $user=User::where("id", $bought->userid)->first();
+
+            if ($user == null) {
+                $bought->delete();
+                continue;
+            }
+
+            $shop=Shop::where("id", $bought->shopid)->first();
+
+            if ($shop == null) {
+                $bought->delete();
+                continue;
+            }
+
+            if($shop->reset() != 0 && $shop->reset_value() != 0 && $shop->reset_exp() != 0) {
+              if(time() - $shop->reset_exp() * 86400 < $bought->datetime) {
+                if(intval((time() - $bought->datetime) / 86400) % $shop->reset() == 0 && intval((time() - $bought->datetime) / 86400) != 0) {
+                  echo("流量重置-".$user->id."\n");
+                  $user->transfer_enable = Tools::toGB($shop->reset_value());
+                  $user->u = 0;
+                  $user->d = 0;
+                  $user->last_day_t = 0;
+                  $user->save();
+
+                  $subject = Config::get('appName')."-您的流量被重置了";
+                  $to = $user->email;
+                  $text = "您好,根据您所订购的订单 ID:".$bought->id.",流量已经被重置为".$shop->reset_value().'GB' ;
+                  try {
+                      Mail::send($to, $subject, 'news/warn.tpl', [
+                          "user" => $user,"text" => $text
+                      ], [
+                      ]);
+                  } catch (Exception $e) {
+                      echo $e->getMessage();
+                  }
+                }
+              }
+            }
+
+        }
+
+
+        $users = User::all();
+        foreach ($users as $user) {
+            $user->last_day_t=($user->u+$user->d);
+            $user->save();
+
+
+            if (date("d") == $user->auto_reset_day) {
+                $user->u = 0;
+                $user->d = 0;
+                $user->last_day_t = 0;
+                $user->transfer_enable = $user->auto_reset_bandwidth*1024*1024*1024;
+                $user->save();
+
+                $subject = Config::get('appName')."-您的流量被重置了";
+                $to = $user->email;
+                $text = "您好,根据管理员的设置,流量已经被重置为".$user->auto_reset_bandwidth.'GB' ;
+                try {
+                    Mail::send($to, $subject, 'news/warn.tpl', [
+                        "user" => $user,"text" => $text
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+            }
+        }
+
+
+
+        #https://github.com/shuax/QQWryUpdate/blob/master/update.php
+
+        $copywrite = file_get_contents("https://github.com/esdeathlove/qqwry-download/raw/master/copywrite.rar");
+
+        $adminUser = User::where("is_admin", "=", "1")->get();
+
+        $newmd5 = md5($copywrite);
+        $oldmd5 = file_get_contents(BASE_PATH."/storage/qqwry.md5");
+
+        if ($newmd5 != $oldmd5) {
+            file_put_contents(BASE_PATH."/storage/qqwry.md5", $newmd5);
+            $qqwry = file_get_contents("https://github.com/esdeathlove/qqwry-download/raw/master/qqwry.rar");
+            if ($qqwry != "") {
+                $key = unpack("V6", $copywrite)[6];
+                for ($i=0; $i<0x200; $i++) {
+                    $key *= 0x805;
+                    $key ++;
+                    $key = $key & 0xFF;
+                    $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key);
+                }
+                $qqwry = gzuncompress($qqwry);
+                rename(BASE_PATH."/storage/qqwry.dat", BASE_PATH."/storage/qqwry.dat.bak");
+                $fp = fopen(BASE_PATH."/storage/qqwry.dat", "wb");
+                if ($fp) {
+                    fwrite($fp, $qqwry);
+                    fclose($fp);
+                }
+            }
+        }
+
+        $iplocation = new QQWry();
+        $location=$iplocation->getlocation("8.8.8.8");
+        $Userlocation = $location['country'];
+        if (iconv('gbk', 'utf-8//IGNORE', $Userlocation)!="美国") {
+            unlink(BASE_PATH."/storage/qqwry.dat");
+            rename(BASE_PATH."/storage/qqwry.dat.bak", BASE_PATH."/storage/qqwry.dat");
+        }
+
+
+
+        if (Config::get('enable_auto_backup') == 'true') {
+            Job::backup();
+        }
+
+        Job::updatedownload();
+    }
+
+    public static function updatedownload()
+    {
+        system('cd '.BASE_PATH."/public/ssr-download/ && git pull", $ret);
+        echo $ret;
+    }
+
+    public static function CheckJob()
+    {
+        //在线人数检测
+        $users = User::where('node_connector', '>', 0)->get();
+
+        $full_alive_ips = Ip::where("datetime", ">=", time()-60)->orderBy("ip")->get();
+
+        $alive_ipset = array();
+
+        foreach ($full_alive_ips as $full_alive_ip) {
+            $full_alive_ip->ip = Tools::getRealIp($full_alive_ip->ip);
+            $is_node = Node::where("node_ip", $full_alive_ip->ip)->first();
+            if($is_node) {
+                continue;
+            }
+
+            if (!isset($alive_ipset[$full_alive_ip->userid])) {
+                $alive_ipset[$full_alive_ip->userid] = new \ArrayObject();
+            }
+
+            $alive_ipset[$full_alive_ip->userid]->append($full_alive_ip);
+        }
+
+        foreach ($users as $user) {
+            $alive_ips = (isset($alive_ipset[$user->id])?$alive_ipset[$user->id]:new \ArrayObject());
+            $ips = array();
+
+            $disconnected_ips = explode(",", $user->disconnect_ip);
+
+            foreach ($alive_ips as $alive_ip) {
+                if (!isset($ips[$alive_ip->ip]) && !in_array($alive_ip->ip, $disconnected_ips)) {
+                    $ips[$alive_ip->ip]=1;
+                    if ($user->node_connector < count($ips)) {
+                        //暂时封禁
+                        $isDisconnect = Disconnect::where('id', '=', $alive_ip->ip)->where('userid', '=', $user->id)->first();
+
+                        if ($isDisconnect == null) {
+                            $disconnect = new Disconnect();
+                            $disconnect->userid = $user->id;
+                            $disconnect->ip = $alive_ip->ip;
+                            $disconnect->datetime = time();
+                            $disconnect->save();
+
+                            if ($user->disconnect_ip == null||$user->disconnect_ip == "") {
+                                $user->disconnect_ip = $alive_ip->ip;
+                            } else {
+                                $user->disconnect_ip .= ",".$alive_ip->ip;
+                            }
+                            $user->save();
+                        }
+                    }
+                }
+            }
+        }
+
+        //解封
+        $disconnecteds = Disconnect::where("datetime", "<", time()-300)->get();
+        foreach ($disconnecteds as $disconnected) {
+            $user = User::where('id', '=', $disconnected->userid)->first();
+
+            $ips = explode(",", $user->disconnect_ip);
+            $new_ips = "";
+            $first = 1;
+
+            foreach ($ips as $ip) {
+                if ($ip != $disconnected->ip && $ip != "") {
+                    if ($first == 1) {
+                        $new_ips .= $ip;
+                        $first = 0;
+                    } else {
+                        $new_ips .= ",".$ip;
+                    }
+                }
+            }
+
+            $user->disconnect_ip = $new_ips;
+
+            if ($new_ips == "") {
+                $user->disconnect_ip = null;
+            }
+
+            $user->save();
+
+            $disconnected->delete();
+        }
+
+        //auto renew
+        $boughts=Bought::where("renew", "<", time())->where("renew", "<>", 0)->get();
+        foreach ($boughts as $bought) {
+            $user=User::where("id", $bought->userid)->first();
+
+            if ($user == null) {
+                $bought->delete();
+                continue;
+            }
+
+            if ($user->money>=$bought->price) {
+                $shop=Shop::where("id", $bought->shopid)->first();
+
+                if ($shop == null) {
+                    $bought->delete();
+                    continue;
+                }
+
+                $user->money=$user->money-$bought->price;
+
+                $user->save();
+
+                $shop->buy($user, 1);
+
+                $bought->renew=0;
+                $bought->save();
+
+
+                $bought_new=new Bought();
+                $bought_new->userid=$user->id;
+                $bought_new->shopid=$shop->id;
+                $bought_new->datetime=time();
+                $bought_new->renew=time()+$shop->auto_renew*86400;
+                $bought_new->price=$bought->price;
+                $bought_new->coupon="";
+                $bought_new->save();
+
+                $subject = Config::get('appName')."-续费成功";
+                $to = $user->email;
+                $text = "您好,系统已经为您自动续费,商品名:".$shop->name.",金额:".$bought->price." 元。" ;
+                try {
+                    Mail::send($to, $subject, 'news/warn.tpl', [
+                        "user" => $user,"text" => $text
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+
+                if (file_exists(BASE_PATH."/storage/".$bought->id.".renew")) {
+                    unlink(BASE_PATH."/storage/".$bought->id.".renew");
+                }
+            } else {
+                if (!file_exists(BASE_PATH."/storage/".$bought->id.".renew")) {
+                    $subject = Config::get('appName')."-续费失败";
+                    $to = $user->email;
+                    $text = "您好,系统为您自动续费商品名:".$shop->name.",金额:".$bought->price." 元 时,发现您余额不足,请及时充值,当您充值之后,稍等一会系统就会自动扣费为您续费了。" ;
+                    try {
+                        Mail::send($to, $subject, 'news/warn.tpl', [
+                            "user" => $user,"text" => $text
+                        ], [
+                        ]);
+                    } catch (Exception $e) {
+                        echo $e->getMessage();
+                    }
+                    $myfile = fopen(BASE_PATH."/storage/".$bought->id.".renew", "w+") or die("Unable to open file!");
+                    $txt = "1";
+                    fwrite($myfile, $txt);
+                    fclose($myfile);
+                }
+            }
+        }
+
+        Ip::where("datetime", "<", time()-300)->delete();
+        UnblockIp::where("datetime", "<", time()-300)->delete();
+        BlockIp::where("datetime", "<", time()-86400)->delete();
+        TelegramSession::where("datetime", "<", time()-900)->delete();
+
+
+        $adminUser = User::where("is_admin", "=", "1")->get();
+
+        $latest_content = file_get_contents("https://github.com/esdeathlove/ss-panel-v3-mod/raw/new_master/bootstrap.php");
+        $newmd5 = md5($latest_content);
+        $oldmd5 = md5(file_get_contents(BASE_PATH."/bootstrap.php"));
+
+        if ($latest_content!="") {
+            if ($newmd5 == $oldmd5) {
+                if (file_exists(BASE_PATH."/storage/update.md5")) {
+                    unlink(BASE_PATH."/storage/update.md5");
+                }
+            } else {
+                if (!file_exists(BASE_PATH."/storage/update.md5")) {
+                    foreach ($adminUser as $user) {
+                        echo "Send mail to user: ".$user->id;
+                        $subject = Config::get('appName')."-系统提示";
+                        $to = $user->email;
+                        $text = "管理员您好,系统发现有了新版本,您可以到 <a href=\"https://github.com/esdeathlove/ss-panel-v3-mod/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97\">https://github.com/esdeathlove/ss-panel-v3-mod/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97</a> 按照步骤进行升级。" ;
+                        try {
+                            Mail::send($to, $subject, 'news/warn.tpl', [
+                                "user" => $user,"text" => $text
+                            ], [
+                            ]);
+                        } catch (Exception $e) {
+                            echo $e->getMessage();
+                        }
+                    }
+
+                    Telegram::Send("姐姐姐姐,面板程序有更新了呢~看看你的邮箱吧~");
+
+                    $myfile = fopen(BASE_PATH."/storage/update.md5", "w+") or die("Unable to open file!");
+                    $txt = "1";
+                    fwrite($myfile, $txt);
+                    fclose($myfile);
+                }
+            }
+        }
+
+
+        //节点掉线检测
+        if (Config::get("node_offline_warn")=="true") {
+            $nodes = Node::all();
+
+            foreach ($nodes as $node) {
+                if ($node->isNodeOnline() === false && time() - $node->node_heartbeat <= 360) {
+                    foreach ($adminUser as $user) {
+                        echo "Send offline mail to user: ".$user->id;
+                        $subject = Config::get('appName')."-系统警告";
+                        $to = $user->email;
+                        $text = "管理员您好,系统发现节点 ".$node->name." 掉线了,请您及时处理。" ;
+                        try {
+                            Mail::send($to, $subject, 'news/warn.tpl', [
+                                "user" => $user,"text" => $text
+                            ], [
+                            ]);
+                        } catch (Exception $e) {
+                            echo $e->getMessage();
+                        }
+
+                        if (Config::get('enable_cloudxns')=='true' && ($node->sort==0 || $node->sort==10)) {
+                            $api=new Api();
+                            $api->setApiKey(Config::get("cloudxns_apikey"));//修改成自己API KEY
+                            $api->setSecretKey(Config::get("cloudxns_apisecret"));//修改成自己的SECERET KEY
+
+                            $api->setProtocol(true);
+
+                            $domain_json=json_decode($api->domain->domainList());
+
+                            foreach ($domain_json->data as $domain) {
+                                if (strpos($domain->domain, Config::get('cloudxns_domain'))!==false) {
+                                    $domain_id=$domain->id;
+                                }
+                            }
+
+                            $record_json=json_decode($api->record->recordList($domain_id, 0, 0, 2000));
+
+                            foreach ($record_json->data as $record) {
+                                if (($record->host.".".Config::get('cloudxns_domain'))==$node->server) {
+                                    $record_id=$record->record_id;
+
+                                    $Temp_node=Node::where('node_class', '<=', $node->node_class)->where(
+                                        function ($query) use ($node) {
+                                            $query->where("node_group", "=", $node->node_group)
+                                                ->orWhere("node_group", "=", 0);
+                                        }
+                                    )->whereRaw('UNIX_TIMESTAMP()-`node_heartbeat`<300')->first();
+
+                                    if ($Temp_node!=null) {
+                                        $api->record->recordUpdate($domain_id, $record->host, $Temp_node->server, 'CNAME', 55, 60, 1, '', $record_id);
+                                    }
+
+                                    $notice_text = "喵喵喵~ ".$node->name." 节点掉线了喵~域名解析被切换到了 ".$Temp_node->name." 上了喵~";
+                                }
+                            }
+                        } else {
+                            $notice_text = "喵喵喵~ ".$node->name." 节点掉线了喵~";
+                        }
+                    }
+
+                    Telegram::Send($notice_text);
+
+                    $myfile = fopen(BASE_PATH."/storage/".$node->id.".offline", "w+") or die("Unable to open file!");
+                    $txt = "1";
+                    fwrite($myfile, $txt);
+                    fclose($myfile);
+                }
+            }
+
+
+            foreach ($nodes as $node) {
+                if (time()-$node->node_heartbeat<60&&file_exists(BASE_PATH."/storage/".$node->id.".offline")&&$node->node_heartbeat!=0&&($node->sort==0||$node->sort==7||$node->sort==8||$node->sort==10)) {
+                    foreach ($adminUser as $user) {
+                        echo "Send offline mail to user: ".$user->id;
+                        $subject = Config::get('appName')."-系统提示";
+                        $to = $user->email;
+                        $text = "管理员您好,系统发现节点 ".$node->name." 恢复上线了。" ;
+                        try {
+                            Mail::send($to, $subject, 'news/warn.tpl', [
+                                "user" => $user,"text" => $text
+                            ], [
+                            ]);
+                        } catch (Exception $e) {
+                            echo $e->getMessage();
+                        }
+
+
+                        if (Config::get('enable_cloudxns')=='true'&& ($node->sort==0 || $node->sort==10)) {
+                            $api=new Api();
+                            $api->setApiKey(Config::get("cloudxns_apikey"));//修改成自己API KEY
+                            $api->setSecretKey(Config::get("cloudxns_apisecret"));//修改成自己的SECERET KEY
+
+                            $api->setProtocol(true);
+
+                            $domain_json=json_decode($api->domain->domainList());
+
+                            foreach ($domain_json->data as $domain) {
+                                if (strpos($domain->domain, Config::get('cloudxns_domain'))!==false) {
+                                    $domain_id=$domain->id;
+                                }
+                            }
+
+                            $record_json=json_decode($api->record->recordList($domain_id, 0, 0, 2000));
+
+                            foreach ($record_json->data as $record) {
+                                if (($record->host.".".Config::get('cloudxns_domain'))==$node->server) {
+                                    $record_id=$record->record_id;
+
+                                    $api->record->recordUpdate($domain_id, $record->host, $node->getNodeIp(), 'A', 55, 600, 1, '', $record_id);
+                                }
+                            }
+
+
+                            $notice_text = "喵喵喵~ ".$node->name." 节点恢复了喵~域名解析被切换回来了喵~";
+                        } else {
+                            $notice_text = "喵喵喵~ ".$node->name." 节点恢复了喵~";
+                        }
+                    }
+
+                    Telegram::Send($notice_text);
+
+                    unlink(BASE_PATH."/storage/".$node->id.".offline");
+                }
+            }
+        }
+
+        //登录地检测
+        if (Config::get("login_warn")=="true") {
+            $iplocation = new QQWry();
+            $Logs = LoginIp::where("datetime", ">", time()-60)->get();
+            foreach ($Logs as $log) {
+                $UserLogs=LoginIp::where("userid", "=", $log->userid)->orderBy("id", "desc")->take(2)->get();
+                if ($UserLogs->count()==2) {
+                    $i = 0;
+                    $Userlocation = "";
+                    foreach ($UserLogs as $userlog) {
+                        if ($i == 0) {
+                            $location=$iplocation->getlocation($userlog->ip);
+                            $ip=$userlog->ip;
+                            $Userlocation = $location['country'];
+                            $i++;
+                        } else {
+                            $location=$iplocation->getlocation($userlog->ip);
+                            $nodes=Node::where("node_ip", "LIKE", $ip.'%')->first();
+                            $nodes2=Node::where("node_ip", "LIKE", $userlog->ip.'%')->first();
+                            if ($Userlocation!=$location['country']&&$nodes==null&&$nodes2==null) {
+                                $user=User::where("id", "=", $userlog->userid)->first();
+                                echo "Send warn mail to user: ".$user->id."-".iconv('gbk', 'utf-8//IGNORE', $Userlocation)."-".iconv('gbk', 'utf-8//IGNORE', $location['country']);
+                                $subject = Config::get('appName')."-系统警告";
+                                $to = $user->email;
+                                $text = "您好,系统发现您的账号在 ".iconv('gbk', 'utf-8//IGNORE', $Userlocation)." 有异常登录,请您自己自行核实登录行为。有异常请及时修改密码。" ;
+                                try {
+                                    Mail::send($to, $subject, 'news/warn.tpl', [
+                                        "user" => $user,"text" => $text
+                                    ], [
+                                    ]);
+                                } catch (Exception $e) {
+                                    echo $e->getMessage();
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+
+
+
+
+
+        $users = User::all();
+        foreach ($users as $user) {
+            if (($user->transfer_enable<=$user->u+$user->d||$user->enable==0||(strtotime($user->expire_in)<time()&&strtotime($user->expire_in)>644447105))&&RadiusBan::where("userid", $user->id)->first()==null) {
+                $rb=new RadiusBan();
+                $rb->userid=$user->id;
+                $rb->save();
+                Radius::Delete($user->email);
+            }
+
+            if (strtotime($user->expire_in) < time() && (((Config::get('enable_account_expire_reset')=='true' && strtotime($user->expire_in) < time()) ? $user->transfer_enable != Tools::toGB(Config::get('enable_account_expire_reset_traffic')) : true) && ((Config::get('enable_class_expire_reset')=='true' && ($user->class!=0 && strtotime($user->class_expire)<time() && strtotime($user->class_expire) > 1420041600))? $user->transfer_enable != Tools::toGB(Config::get('enable_class_expire_reset_traffic')) : true))) {
+                if (Config::get('enable_account_expire_reset')=='true') {
+                    $user->transfer_enable = Tools::toGB(Config::get('enable_account_expire_reset_traffic'));
+                    $user->u = 0;
+                    $user->d = 0;
+                    $user->last_day_t = 0;
+
+                    $subject = Config::get('appName')."-您的用户账户已经过期了";
+                    $to = $user->email;
+                    $text = "您好,系统发现您的账号已经过期了。流量已经被重置为".Config::get('enable_account_expire_reset_traffic').'GB' ;
+                    try {
+                        Mail::send($to, $subject, 'news/warn.tpl', [
+                            "user" => $user,"text" => $text
+                        ], [
+                        ]);
+                    } catch (Exception $e) {
+                        echo $e->getMessage();
+                    }
+                }
+            }
+
+            if (strtotime($user->expire_in)+((int)Config::get('enable_account_expire_delete_days')*86400)<time()) {
+                if (Config::get('enable_account_expire_delete')=='true') {
+                    $subject = Config::get('appName')."-您的用户账户已经被删除了";
+                    $to = $user->email;
+                    $text = "您好,系统发现您的账号已经过期 ".Config::get('enable_account_expire_delete_days')." 天了,帐号已经被删除。" ;
+                    try {
+                        Mail::send($to, $subject, 'news/warn.tpl', [
+                            "user" => $user,"text" => $text
+                        ], [
+                        ]);
+                    } catch (Exception $e) {
+                        echo $e->getMessage();
+                    }
+
+                    $user->kill_user();
+
+
+                    continue;
+                }
+            }
+
+
+
+            if ((int)Config::get('enable_auto_clean_uncheck_days')!=0 && max($user->last_check_in_time, strtotime($user->reg_date)) + ((int)Config::get('enable_auto_clean_uncheck_days')*86400) < time() && $user->class == 0) {
+                if (Config::get('enable_auto_clean_uncheck')=='true') {
+                    $subject = Config::get('appName')."-您的用户账户已经被删除了";
+                    $to = $user->email;
+                    $text = "您好,系统发现您的账号已经 ".Config::get('enable_auto_clean_uncheck_days')." 天没签到了,帐号已经被删除。" ;
+                    try {
+                        Mail::send($to, $subject, 'news/warn.tpl', [
+                            "user" => $user,"text" => $text
+                        ], [
+                        ]);
+                    } catch (Exception $e) {
+                        echo $e->getMessage();
+                    }
+
+                    Radius::Delete($user->email);
+
+                    RadiusBan::where('userid', '=', $user->id)->delete();
+
+                    Wecenter::Delete($user->email);
+
+                    $user->delete();
+
+
+                    continue;
+                }
+            }
+
+
+            if ((int)Config::get('enable_auto_clean_unused_days')!=0 && max($user->t, strtotime($user->reg_date)) + ((int)Config::get('enable_auto_clean_unused_days')*86400) < time() && $user->class == 0) {
+                if (Config::get('enable_auto_clean_unused')=='true') {
+                    $subject = Config::get('appName')."-您的用户账户已经被删除了";
+                    $to = $user->email;
+                    $text = "您好,系统发现您的账号已经 ".Config::get('enable_auto_clean_unused_days')." 天没使用了,帐号已经被删除。" ;
+                    try {
+                        Mail::send($to, $subject, 'news/warn.tpl', [
+                            "user" => $user,"text" => $text
+                        ], [
+                        ]);
+                    } catch (Exception $e) {
+                        echo $e->getMessage();
+                    }
+
+                    Radius::Delete($user->email);
+
+                    RadiusBan::where('userid', '=', $user->id)->delete();
+
+                    Wecenter::Delete($user->email);
+
+                    $user->delete();
+
+
+                    continue;
+                }
+            }
+
+            if ($user->class!=0 && (((Config::get('enable_account_expire_reset')=='true' && strtotime($user->expire_in) < time()) ? $user->transfer_enable != Tools::toGB(Config::get('enable_account_expire_reset_traffic')) : true) && ((Config::get('enable_class_expire_reset')=='true' && ($user->class!=0 && strtotime($user->class_expire)<time() && strtotime($user->class_expire) > 1420041600))? $user->transfer_enable != Tools::toGB(Config::get('enable_class_expire_reset_traffic')) : true)) && strtotime($user->class_expire)<time() && strtotime($user->class_expire) > 1420041600) {
+                if (Config::get('enable_class_expire_reset')=='true') {
+                    $user->transfer_enable = Tools::toGB(Config::get('enable_class_expire_reset_traffic'));
+                    $user->u = 0;
+                    $user->d = 0;
+                    $user->last_day_t = 0;
+
+                    $subject = Config::get('appName')."-您的用户等级已经过期了";
+                    $to = $user->email;
+                    $text = "您好,系统发现您的账号等级已经过期了。流量已经被重置为".Config::get('enable_class_expire_reset_traffic').'GB' ;
+                    try {
+                        Mail::send($to, $subject, 'news/warn.tpl', [
+                            "user" => $user,"text" => $text
+                        ], [
+                        ]);
+                    } catch (Exception $e) {
+                        echo $e->getMessage();
+                    }
+                }
+
+                $user->class=0;
+            }
+
+            if ($user->class!=0 && strtotime($user->class_expire)<time() && strtotime($user->class_expire) > 1420041600) {
+                $user->class=0;
+            }
+
+            $user->save();
+        }
+
+        $rbusers = RadiusBan::all();
+        foreach ($rbusers as $sinuser) {
+            $user=User::find($sinuser->userid);
+
+            if ($user == null) {
+                $sinuser->delete();
+                continue;
+            }
+
+            if ($user->enable==1&&(strtotime($user->expire_in)>time()||strtotime($user->expire_in)<644447105)&&$user->transfer_enable>$user->u+$user->d) {
+                $sinuser->delete();
+                Radius::Add($user, $user->passwd);
+            }
+        }
+    }
+}

+ 227 - 0
app/Command/SyncRadius.php

@@ -0,0 +1,227 @@
+<?php
+
+
+
+
+
+namespace App\Command;
+
+use App\Models\User;
+use App\Models\Node;
+use App\Models\RadiusRadPostauth;
+use App\Models\RadiusRadAcct;
+use App\Models\RadiusNas;
+use App\Services\Config;
+use App\Services\Mail;
+use App\Models\TrafficLog;
+use App\Utils\Tools;
+use App\Utils\Radius;
+use App\Utils\Da;
+
+class SyncRadius
+{
+    public static function synclogin()
+    {
+        if (Config::get('enable_radius')=="false") {
+            return;
+        }
+        $tempuserbox=array();
+        $users = User::all();
+        foreach ($users as $user) {
+            $email=$user->email;
+            $email=Radius::GetUserName($email);
+            $tempuserbox[$email]=$user->id;
+        }
+        
+        /*$tempnodebox=array();
+        $nodes = Node::all();
+        foreach($nodes as $node){
+            if(strpos($node->name,"Shadowsocks")!=FALSE)
+            {
+                $ip=gethostbyname($node->server);
+                $tempnodebox[$ip]=$node->id;
+            }
+        }*/
+        
+        
+        
+        $logs = RadiusRadPostauth::where('authdate', '<', date("Y-m-d H:i:s"))->where('authdate', '>', date("Y-m-d H:i:s", time()-60))->get();
+        
+        foreach ($logs as $log) {
+            if (isset($tempuserbox[$log->username])) {
+                $traffic = new TrafficLog();
+                $traffic->user_id = $tempuserbox[$log->username];
+                $traffic->u = 0;
+                $traffic->d = 10000;
+                $traffic->node_id = 1;
+                $traffic->rate = 1;
+                $traffic->traffic = Tools::flowAutoShow(10000);
+                $traffic->log_time = time();
+                $traffic->save();
+                
+                $user=User::find($tempuserbox[$log->username]);
+                $user->t = time();
+                $user->u = $user->u + 0;
+                $user->d = $user->d + 10000;
+                $user->save();
+            }
+        }
+        
+        
+        /*$stmt = $db->query("SELECT * FROM `radacct` WHERE `acctstoptime`<'".date("Y-m-d H:i:s")."' AND `acctstoptime`>'".date("Y-m-d H:i:s",time()-60)."'");
+        $result = $stmt->fetchAll();
+
+        foreach($result as $row)
+        {
+            $traffic = new TrafficLog();
+            $traffic->user_id = $tempuserbox[$row["username"]];
+            $traffic->u = $row["acctinputoctets"];
+            $traffic->d = $row["acctoutputoctets"];
+            $traffic->node_id = 150;
+            $traffic->rate = 1;
+            $traffic->traffic = Tools::flowAutoShow(($row["acctinputoctets"]+$row["acctoutputoctets"])/1024/1024);
+            $traffic->log_time = time();
+            $traffic->save();
+
+            $user->t = time();
+            $user->u = $user->u + $row["acctinputoctets"];
+            $user->d = $user->d + $row["acctoutputoctets"];
+            $user->save();
+        }  */
+    }
+    
+    
+    public static function syncvpn()
+    {
+        if (Config::get('radius_db_host')=="") {
+            return;
+        }
+        
+        $tempuserbox=array();
+        $users = User::all();
+        foreach ($users as $user) {
+            $email=$user->email;
+            $email=Radius::GetUserName($email);
+            $tempuserbox[$email]=$user->id;
+        }
+        
+        /*$tempnodebox=array();
+        $nodes = Node::all();
+        foreach($nodes as $node){
+            if(strpos($node->name,"Shadowsocks")!=FALSE)
+            {
+                $ip=gethostbyname($node->server);
+                $tempnodebox[$ip]=$node->id;
+            }
+        }*/
+        
+        /*$stmt = $db->query("SELECT * FROM `radpostauth` WHERE `authdate`<'".date("Y-m-d H:i:s")."' AND`authdate`>'".date("Y-m-d H:i:s",time()-60)."'");
+        $result = $stmt->fetchAll();
+
+        foreach($result as $row)
+        {
+            //if($row["pass"]!="")
+            {
+                $traffic = new TrafficLog();
+                $traffic->user_id = $tempuserbox[$row["username"]];
+                $traffic->u = 0;
+                $traffic->d = 10000;
+                $traffic->node_id = 149;
+                $traffic->rate = 1;
+                $traffic->traffic = Tools::flowAutoShow(10000);
+                $traffic->log_time = time();
+                $traffic->save();
+
+                $user->t = time();
+                $user->u = $user->u + 0;
+                $user->d = $user->d + 10000;
+                $user->save();
+            }
+        }
+        */
+        
+        
+        $logs = RadiusRadAcct::where('acctstoptime', '<', date("Y-m-d H:i:s"))->where('acctstoptime', '>', date("Y-m-d H:i:s", time()-60))->get();
+        
+        foreach ($logs as $log) {
+            $traffic = new TrafficLog();
+            $traffic->user_id = $tempuserbox[$log->username];
+            $traffic->u = $log->acctinputoctets;
+            $traffic->d = $log->acctoutputoctets;
+            $traffic->node_id = 2;
+            $traffic->rate = 1;
+            $traffic->traffic = Tools::flowAutoShow(($log->acctinputoctets + $log->acctoutputoctets));
+            $traffic->log_time = time();
+            $traffic->save();
+            
+            $user=User::find($tempuserbox[$log->username]);
+            $user->t = time();
+            $user->u = $user->u + $log->acctinputoctets;
+            $user->d = $user->d + $log->acctoutputoctets;
+            $user->save();
+        }
+    }
+    
+    public static function syncusers()
+    {
+        $users = User::all();
+        foreach ($users as $user) {
+            Radius::Add($user, $user->passwd);
+
+            echo "Send sync mail to user: ".$user->id;
+            $subject = Config::get('appName')."-密码更新通知";
+            $to = $user->email;
+            $text = "您好,为了保证密码系统的统一,刚刚系统已经将您 vpn 等连接方式的用户名已经重置为:".Radius::GetUserName($user->email).",密码自动重置为您 ss 的密码:". $user->passwd."  了,以后您修改 ss 密码就会自动修改 vpn 等连接方式的密码了,感谢您的支持。 " ;
+            try {
+                Mail::send($to, $subject, 'password/vpn.tpl', [
+                    "user" => $user,"text" => $text
+                ], [
+                ]);
+            } catch (Exception $e) {
+                echo $e->getMessage();
+            }
+        }
+    }
+    
+    public static function syncnas()
+    {
+        if (Config::get('radius_db_host')!="") {
+            $md5txt="";
+            
+            $nases = RadiusNas::all();
+        
+            foreach ($nases as $nas) {
+                //if($row["pass"]!="")
+                {
+                    $md5txt=$md5txt.$nas->id.$nas->nasname.$nas->shortname.$nas->secret.$nas->description;
+                }
+            }
+            
+            $md5=MD5($md5txt);
+            
+            
+            $oldmd5=file_get_contents(BASE_PATH."/storage/nas.md5");
+            
+            if ($oldmd5!=$md5) {
+                //Restart radius
+                $myfile = fopen(BASE_PATH."/storage/nas.md5", "w+") or die("Unable to open file!");
+                echo("Restarting...");
+                system("/bin/bash /sbin/service radiusd restart", $retval);
+                echo($retval);
+                $txt = $md5;
+                fwrite($myfile, $txt);
+                fclose($myfile);
+            }
+        }
+        
+        $Nodes = Node::where('sort', 0)->where('node_ip', "<>", "")->where('node_ip', "<>", 'NULL')->get();
+        foreach ($Nodes as $Node) {
+            if (file_exists("/usr/local/psionic/portsentry/portsentry.ignore")) {
+                $content=file_get_contents("/usr/local/psionic/portsentry/portsentry.ignore");
+                if (strpos($content, $Node->node_ip)===false) {
+                    file_put_contents("/usr/local/psionic/portsentry/portsentry.ignore", "\n".$Node->node_ip."/32", FILE_APPEND);
+                }
+            }
+        }
+    }
+}

+ 227 - 0
app/Command/XCat.php

@@ -0,0 +1,227 @@
+<?php
+
+namespace App\Command;
+
+/***
+ * Class XCat
+ * @package App\Command
+ */
+
+use App\Models\User;
+use App\Models\Relay;
+use App\Utils\Hash;
+use App\Utils\Tools;
+use App\Services\Config;
+
+use App\Utils\GA;
+use App\Utils\QRcode;
+
+class XCat
+{
+    public $argv;
+
+    public function __construct($argv)
+    {
+        $this->argv = $argv;
+    }
+
+    public function boot()
+    {
+        switch ($this->argv[1]) {
+            case("install"):
+                    return $this->install();
+            case("createAdmin"):
+                return $this->createAdmin();
+            case("resetTraffic"):
+                return $this->resetTraffic();
+            case("setTelegram"):
+                    return $this->setTelegram();
+            case("initQQWry"):
+                    return $this->initQQWry();
+            case("sendDiaryMail"):
+                return DailyMail::sendDailyMail();
+            case("reall"):
+                    return DailyMail::reall();
+            case("syncusers"):
+                    return SyncRadius::syncusers();
+            case("synclogin"):
+                    return SyncRadius::synclogin();
+            case("syncvpn"):
+                return SyncRadius::syncvpn();
+            case("nousers"):
+                    return ExtMail::sendNoMail();
+            case("oldusers"):
+                    return ExtMail::sendOldMail();
+            case("syncnode"):
+                    return Job::syncnode();
+            case("syncnasnode"):
+                    return Job::syncnasnode();
+            case("syncnas"):
+                    return SyncRadius::syncnas();
+            case("dailyjob"):
+                return Job::DailyJob();
+            case("checkjob"):
+                return Job::CheckJob();
+            case("syncduoshuo"):
+                return Job::SyncDuoshuo();
+            case("userga"):
+                return Job::UserGa();
+            case("backup"):
+                return Job::backup();
+            case("initdownload"):
+                return $this->initdownload();
+            case("updatedownload"):
+                return Job::updatedownload();
+            case("cleanRelayRule"):
+                return $this->cleanRelayRule();
+            default:
+                return $this->defaultAction();
+        }
+    }
+
+    public function defaultAction()
+    {
+        echo "Memo";
+    }
+
+    public function cleanRelayRule()
+    {
+        $rules = Relay::all();
+        foreach ($rules as $rule) {
+            echo($rule->id."\n");
+            if ($rule->source_node_id == 0) {
+                echo($rule->id."被删除!\n");
+                $rule->delete();
+                continue;
+            }
+
+            $ruleset = Relay::where('user_id', $rule->user_id)->orwhere('user_id', 0)->get();
+            $maybe_rule_id = Tools::has_conflict_rule($rule, $ruleset, $rule->id);
+            if ($maybe_rule_id != 0) {
+                echo($rule->id."被删除!\n");
+                $rule->delete();
+            }
+        }
+    }
+
+    public function install()
+    {
+        echo "x cat will install ss-panel v3...../n";
+    }
+
+    public function initdownload()
+    {
+        system('git clone https://github.com/esdeathlove/panel-download.git '.BASE_PATH."/public/ssr-download/", $ret);
+        echo $ret;
+    }
+
+    public function createAdmin()
+    {
+        $this->initQQWry();
+        $this->initdownload();
+        echo "add admin/ 创建管理员帐号.....";
+        // ask for input
+        fwrite(STDOUT, "Enter your email/输入管理员邮箱: ");
+        // get input
+        $email = trim(fgets(STDIN));
+        // write input back
+        fwrite(STDOUT, "Enter password for: $email / 为 $email 添加密码 ");
+        $passwd = trim(fgets(STDIN));
+        echo "Email: $email, Password: $passwd! ";
+        fwrite(STDOUT, "Press [Y] to create admin..... 按下[Y]确认来确认创建管理员账户..... ");
+        $y = trim(fgets(STDIN));
+        if (strtolower($y) == "y") {
+            echo "start create admin account";
+            // create admin user
+            // do reg user
+            $user = new User();
+            $user->user_name = "admin";
+            $user->email = $email;
+            $user->pass = Hash::passwordHash($passwd);
+            $user->passwd = Tools::genRandomChar(6);
+            $user->port = Tools::getLastPort()+1;
+            $user->t = 0;
+            $user->u = 0;
+            $user->d = 0;
+            $user->transfer_enable = Tools::toGB(Config::get('defaultTraffic'));
+            $user->invite_num = Config::get('inviteNum');
+            $user->ref_by = 0;
+            $user->is_admin = 1;
+            $user->expire_in=date("Y-m-d H:i:s", time()+Config::get('user_expire_in_default')*86400);
+            $user->reg_date=date("Y-m-d H:i:s");
+            $user->money=0;
+            $user->im_type=1;
+            $user->im_value="";
+            $user->class=0;
+            $user->plan='A';
+            $user->node_speedlimit=0;
+            $user->theme=Config::get('theme');
+
+
+
+            $ga = new GA();
+            $secret = $ga->createSecret();
+            $user->ga_token=$secret;
+            $user->ga_enable=0;
+
+
+
+            if ($user->save()) {
+                echo "Successful/添加成功!";
+                return true;
+            }
+            echo "添加失败";
+            return false;
+        }
+        echo "cancel";
+        return false;
+    }
+
+    public function resetTraffic()
+    {
+        try {
+            User::where("enable", 1)->update([
+            'd' => 0,
+            'u' => 0,
+            ]);
+        } catch (\Exception $e) {
+            echo $e->getMessage();
+            return false;
+        }
+        return "reset traffic successful";
+    }
+
+
+    public function setTelegram()
+    {
+        $bot = new \TelegramBot\Api\BotApi(Config::get('telegram_token'));
+        if ($bot->setWebhook(Config::get('baseUrl')."/telegram_callback?token=".Config::get('telegram_request_token')) == 1) {
+            echo("设置成功!");
+        }
+    }
+
+    public function initQQWry()
+    {
+        echo("downloading....");
+        $copywrite = file_get_contents("https://github.com/esdeathlove/qqwry-download/raw/master/copywrite.rar");
+        $newmd5 = md5($copywrite);
+        file_put_contents(BASE_PATH."/storage/qqwry.md5", $newmd5);
+        $qqwry = file_get_contents("https://github.com/esdeathlove/qqwry-download/raw/master/qqwry.rar");
+        if ($qqwry != "") {
+            $key = unpack("V6", $copywrite)[6];
+            for ($i=0; $i<0x200; $i++) {
+                $key *= 0x805;
+                $key ++;
+                $key = $key & 0xFF;
+                $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key);
+            }
+            $qqwry = gzuncompress($qqwry);
+            $fp = fopen(BASE_PATH."/storage/qqwry.dat", "wb");
+            if ($fp) {
+                fwrite($fp, $qqwry);
+                fclose($fp);
+            }
+            echo("finish....");
+        }
+    }
+}

+ 112 - 0
app/Controllers/Admin/AnnController.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Ann;
+use App\Controllers\AdminController;
+use App\Utils\Telegram;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class AnnController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID",
+                              "date" => "日期", "content" => "内容");
+        $table_config['default_show_column'] = array("op", "id",
+                                                    "date", "content");
+        $table_config['ajax_url'] = 'announcement/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/announcement/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        return $this->view()->display('admin/announcement/create.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $ann = new Ann();
+        $ann->date =  date("Y-m-d H:i:s");
+        $ann->content =  $request->getParam('content');
+        $ann->markdown =  $request->getParam('markdown');
+
+        if (!$ann->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::SendMarkdown("新公告:".PHP_EOL.$request->getParam('markdown'));
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "公告添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+        $ann = Ann::find($id);
+        if ($ann == null) {
+        }
+        return $this->view()->assign('ann', $ann)->display('admin/announcement/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $ann = Ann::find($id);
+
+        $ann->content =  $request->getParam('content');
+        $ann->markdown =  $request->getParam('markdown');
+        $ann->date =  date("Y-m-d H:i:s");
+
+        if (!$ann->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "修改失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::SendMarkdown("公告更新:".PHP_EOL.$request->getParam('markdown'));
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "修改成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $ann = Ann::find($id);
+        if (!$ann->delete()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select id as op,id,date,content from announcement');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand" href="/admin/announcement/'.$data['id'].'/edit">编辑</a>
+                    <a class="btn btn-brand-accent" id="delete" value="'.$data['id'].'" href="javascript:void(0);" onClick="delete_modal_show(\''.$data['id'].'\')">删除</a>';
+        });
+
+        $datatables->edit('DT_RowId', function ($data) {
+            return 'row_1_'.$data['id'];
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 77 - 0
app/Controllers/Admin/AutoController.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Auto;
+use App\Controllers\AdminController;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class autoController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID",
+                              "datetime" => "时间", "type" => "类型", "value" => "内容");
+        $table_config['default_show_column'] = array("op", "id",
+                                  "datetime", "type", "value");
+        $table_config['ajax_url'] = 'auto/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/auto/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        return $this->view()->display('admin/auto/add.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $auto = new Auto();
+        $auto->datetime =  time();
+        $auto->value =  $request->getParam('content');
+        $auto->sign =  $request->getParam('sign');
+        $auto->type =  1;
+
+        if (!$auto->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $auto = Auto::find($id);
+        if (!$auto->delete()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select id,datetime,type,value from auto');
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $datatables->edit('type', function ($data) {
+            return $data['type'] == 1 ? '命令下发' : '命令被执行';
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 142 - 0
app/Controllers/Admin/CodeController.php

@@ -0,0 +1,142 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Code;
+use App\Models\User;
+use App\Controllers\AdminController;
+use App\Utils\Tools;
+use App\Services\Auth;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class CodeController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "code" => "内容",
+                        "type" => "类型", "number" => "操作",
+                        "isused" => "是否已经使用", "userid" => "用户ID",
+                        "user_name" => "用户名", "usedatetime" => "使用时间");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'code/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/code/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        return $this->view()->display('admin/code/add.tpl');
+    }
+
+    public function donate_create($request, $response, $args)
+    {
+        return $this->view()->display('admin/code/add_donate.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $n = $request->getParam('amount');
+        $type = $request->getParam('type');
+        $number = $request->getParam('number');
+
+
+        for ($i = 0; $i < $n; $i++) {
+            $char = Tools::genRandomChar(32);
+            $code = new Code();
+            $code->code = time() . $char;
+            $code->type = -1;
+            $code->number = $number;
+            $code->userid=0;
+            $code->usedatetime="1989:06:04 02:30:00";
+            $code->save();
+        }
+
+
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "充值码添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function donate_add($request, $response, $args)
+    {
+        $amount = $request->getParam('amount');
+        $type = $request->getParam('type');
+        $text = $request->getParam('code');
+
+        $code = new Code();
+        $code->code = $text;
+        $code->type = $type;
+        $code->number = $amount;
+        $code->userid = Auth::getUser()->id;
+        $code->isused = 1;
+        $code->usedatetime = date("Y:m:d H:i:s");
+
+        $code->save();
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax_code($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select code.id,code.code,code.type,code.number,code.isused,code.userid,code.userid as user_name,code.usedatetime from code');
+
+        $datatables->edit('number', function ($data) {
+            switch ($data['type']) {
+              case -1:
+                return "充值 ".$data['number']." 元";
+
+              case -2:
+                return "支出 ".$data['number']." 元";
+
+              default:
+                return "已经废弃";
+            }
+        });
+
+        $datatables->edit('isused', function ($data) {
+            return $data['isused'] == 1 ? '已使用' : '未使用';
+        });
+
+        $datatables->edit('userid', function ($data) {
+            return $data['userid'] == 0 ? '未使用' : $data['userid'];
+        });
+
+        $datatables->edit('user_name', function ($data) {
+            $user = User::find($data['user_name']);
+            if ($user == null) {
+                return "未使用";
+            }
+
+            return $user->user_name;
+        });
+
+        $datatables->edit('type', function ($data) {
+            switch ($data['type']) {
+              case -1:
+                return "充值金额";
+
+              case -2:
+                return "财务支出";
+
+              default:
+                return "已经废弃";
+            }
+        });
+
+        $datatables->edit('usedatetime', function ($data) {
+            return $data['usedatetime'] > '2000-1-1 0:0:0' ? $data['usedatetime'] : "未使用";
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 149 - 0
app/Controllers/Admin/DetectController.php

@@ -0,0 +1,149 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\DetectLog;
+use App\Models\DetectRule;
+use App\Utils\Telegram;
+use App\Controllers\AdminController;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class DetectController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID", "name" => "名称",
+                          "text" => "介绍", "regex" => "正则表达式",
+                          "type" => "类型");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'detect/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/detect/index.tpl');
+    }
+
+    public function log($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "user_id" => "用户ID",
+                          "user_name" => "用户名", "node_id" => "节点ID",
+                          "node_name" => "节点名", "rule_id" => "规则ID",
+                          "rule_name" => "规则名", "rule_text" => "规则描述",
+                          "rule_regex" => "规则正则表达式", "rule_type" => "规则类型",
+                          "datetime" => "时间");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'log/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/detect/log.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        return $this->view()->display('admin/detect/add.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $rule = new DetectRule();
+        $rule->name =  $request->getParam('name');
+        $rule->text =  $request->getParam('text');
+        $rule->regex =  $request->getParam('regex');
+        $rule->type =  $request->getParam('type');
+
+        if (!$rule->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::SendMarkdown("有新的审计规则:".$rule->name);
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+        $rule = DetectRule::find($id);
+        return $this->view()->assign('rule', $rule)->display('admin/detect/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $rule = DetectRule::find($id);
+
+        $rule->name =  $request->getParam('name');
+        $rule->text =  $request->getParam('text');
+        $rule->regex =  $request->getParam('regex');
+        $rule->type =  $request->getParam('type');
+
+        if (!$rule->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "修改失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::SendMarkdown("规则更新:".PHP_EOL.$request->getParam('name'));
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "修改成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $rule = DetectRule::find($id);
+        if (!$rule->delete()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax_rule($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select id as op,id,name,text,regex,type from detect_list');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand" href="/admin/detect/'.$data['id'].'/edit">编辑</a>
+                    <a class="btn btn-brand-accent" id="delete" value="'.$data['id'].'" href="javascript:void(0);" onClick="delete_modal_show(\''.$data['id'].'\')">删除</a>';
+        });
+
+        $datatables->edit('type', function ($data) {
+            return $data['type'] == 1 ? '数据包明文匹配' : '数据包十六进制匹配';
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+    public function ajax_log($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select detect_log.id,user_id,user.user_name,node_id,node.name as node_name,list.id as rule_id,list.name as rule_name,list.text as rule_text,list.regex as rule_regex,list.type as rule_type,detect_log.datetime from detect_log,user,ss_node as node,detect_list as list where user.id=detect_log.user_id and node.id = detect_log.node_id and list.id = detect_log.list_id');
+
+        $datatables->edit('rule_type', function ($data) {
+            return $data['rule_type'] == 1 ? '数据包明文匹配' : '数据包十六进制匹配';
+        });
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 192 - 0
app/Controllers/Admin/IpController.php

@@ -0,0 +1,192 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Ip;
+use App\Models\LoginIp;
+use App\Models\BlockIp;
+use App\Models\UnblockIp;
+use App\Models\Node;
+use App\Controllers\AdminController;
+use App\Utils\QQWry;
+use App\Utils\Tools;
+use App\Services\Auth;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class IpController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "userid" => "用户ID",
+                          "user_name" => "用户名", "ip" => "IP",
+                          "location" => "归属地", "datetime" => "时间", "type" => "类型");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'login/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/ip/login.tpl');
+    }
+
+    public function alive($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "userid" => "用户ID",
+                          "user_name" => "用户名", "nodeid" => "节点ID",
+                          "node_name" => "节点名", "ip" => "IP",
+                          "location" => "归属地", "datetime" => "时间", "is_node" => "是否为中转连接");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'alive/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/ip/alive.tpl');
+    }
+
+    public function block($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID",
+                        "name" => "节点名称", "ip" => "IP",
+                        "location" => "归属地", "datetime" => "时间");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'block/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/ip/block.tpl');
+    }
+
+    public function unblock($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "userid" => "用户ID",
+                          "user_name" => "用户名", "ip" => "IP",
+                          "location" => "归属地", "datetime" => "时间");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'unblock/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/ip/unblock.tpl');
+    }
+
+    public function doUnblock($request, $response, $args)
+    {
+        $ip = $request->getParam('ip');
+
+        $user = Auth::getUser();
+        $BIP = BlockIp::where("ip", $ip)->get();
+        foreach ($BIP as $bi) {
+            $bi->delete();
+        }
+
+        $UIP = new UnblockIp();
+        $UIP->userid = $user->id;
+        $UIP->ip = $ip;
+        $UIP->datetime = time();
+        $UIP->save();
+
+
+        $res['ret'] = 1;
+        $res['msg'] = "发送解封命令解封 ".$ip." 成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function ajax_block($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select blockip.id,node.name,blockip.ip,blockip.ip as location,datetime from blockip,ss_node as node WHERE blockip.nodeid = node.id');
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $iplocation = new QQWry();
+
+        $datatables->edit('location', function ($data) use ($iplocation) {
+            $location=$iplocation->getlocation($data['location']);
+            return iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+    public function ajax_unblock($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select unblockip.id,userid,user.user_name,unblockip.ip,unblockip.ip as location,datetime from unblockip,user WHERE unblockip.userid = user.id');
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $iplocation = new QQWry();
+
+        $datatables->edit('location', function ($data) use ($iplocation) {
+            $location=$iplocation->getlocation($data['location']);
+            return iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+    public function ajax_login($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select login_ip.id,login_ip.userid,user.user_name,login_ip.ip,login_ip.ip as location,login_ip.datetime,login_ip.type from login_ip,user WHERE login_ip.userid = user.id');
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+
+        $iplocation = new QQWry();
+        $datatables->edit('location', function ($data) use ($iplocation) {
+            $location=$iplocation->getlocation($data['location']);
+            return iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+        });
+
+        $datatables->edit('type', function ($data) {
+            return $data['type'] == 0 ? '成功' : '失败';
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+
+    public function ajax_alive($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select alive_ip.id,alive_ip.userid,user.user_name,alive_ip.nodeid,ss_node.name as node_name,alive_ip.ip,alive_ip.ip as location,alive_ip.datetime,alive_ip.id as is_node from alive_ip,user,ss_node WHERE alive_ip.userid = user.id and alive_ip.nodeid = ss_node.id and `datetime` > UNIX_TIMESTAMP() - 60');
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $iplocation = new QQWry();
+
+        $datatables->edit('ip', function ($data){
+            return Tools::getRealIp($data['ip']);
+        });
+
+        $datatables->edit('is_node', function ($data){
+            $is_node = Node::where("node_ip", Tools::getRealIp($data['ip']))->first();
+            if($is_node) {
+                return "是";
+            } else {
+                return "否";
+            }
+        });
+
+        $datatables->edit('location', function ($data) use ($iplocation) {
+            $location=$iplocation->getlocation(Tools::getRealIp($data['location']));
+            return iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 293 - 0
app/Controllers/Admin/NodeController.php

@@ -0,0 +1,293 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Node;
+use App\Utils\Radius;
+use App\Utils\Telegram;
+use App\Utils\Tools;
+use App\Controllers\AdminController;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class NodeController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = Array("op" => "操作", "id" => "ID", "name" => "节点名称",
+                            "type" => "显示与隐藏", "sort" => "类型",
+                            "server" => "节点地址", "node_ip" => "节点IP",
+                            "info" => "节点信息",
+                            "status" => "状态", "traffic_rate" => "流量比率", "node_group" => "节点群组",
+                            "node_class" => "节点等级", "node_speedlimit" => "节点限速/Mbps",
+                            "node_bandwidth" => "已走流量/GB", "node_bandwidth_limit" => "流量限制/GB",
+                            "bandwidthlimit_resetday" => "流量重置日", "node_heartbeat" => "上一次活跃时间",
+                            "custom_method" => "自定义加密", "custom_rss" => "自定义协议以及混淆",
+                            "mu_only" => "只启用单端口多用户");
+        $table_config['default_show_column'] = Array("op", "id", "name", "sort");
+        $table_config['ajax_url'] = 'node/ajax';
+
+        return $this->view()->assign('table_config', $table_config)->display('admin/node/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        return $this->view()->display('admin/node/create.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $node = new Node();
+        $node->name =  $request->getParam('name');
+        $node->server =  $request->getParam('server');
+        $node->method =  $request->getParam('method');
+        $node->custom_method =  $request->getParam('custom_method');
+        $node->custom_rss =  $request->getParam('custom_rss');
+        $node->mu_only =  $request->getParam('mu_only');
+        $node->traffic_rate = $request->getParam('rate');
+        $node->info = $request->getParam('info');
+        $node->type = $request->getParam('type');
+        $node->node_group = $request->getParam('group');
+        $node->node_speedlimit = $request->getParam('node_speedlimit');
+        $node->status = $request->getParam('status');
+        $node->sort = $request->getParam('sort');
+        if ($node->sort == 0 || $node->sort == 1 || $node->sort == 10) {
+            if ($request->getParam('node_ip') != '') {
+                $node->node_ip = $request->getParam('node_ip');
+            } else {
+                $node->node_ip = gethostbyname($request->getParam('server'));
+            }
+        } else {
+            $node->node_ip="";
+        }
+
+        if ($node->sort==1) {
+            Radius::AddNas($node->node_ip, $request->getParam('server'));
+        }
+        $node->node_class=$request->getParam('class');
+        $node->node_bandwidth_limit=$request->getParam('node_bandwidth_limit')*1024*1024*1024;
+        $node->bandwidthlimit_resetday=$request->getParam('bandwidthlimit_resetday');
+
+        if (!$node->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::Send("新节点添加~".$request->getParam('name'));
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "节点添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+        $node = Node::find($id);
+        if ($node == null) {
+        }
+        return $this->view()->assign('node', $node)->display('admin/node/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $node = Node::find($id);
+
+        $node->name =  $request->getParam('name');
+        $node->node_group =  $request->getParam('group');
+        $node->server =  $request->getParam('server');
+        $node->method =  $request->getParam('method');
+        $node->custom_method =  $request->getParam('custom_method');
+        $node->custom_rss =  $request->getParam('custom_rss');
+        $node->mu_only =  $request->getParam('mu_only');
+        $node->traffic_rate = $request->getParam('rate');
+        $node->info = $request->getParam('info');
+        $node->node_speedlimit = $request->getParam('node_speedlimit');
+        $node->type = $request->getParam('type');
+        $node->sort = $request->getParam('sort');
+
+        if ($node->sort == 0 || $node->sort == 1 || $node->sort == 10) {
+            if ($request->getParam('node_ip') != '') {
+                $node->node_ip = $request->getParam('node_ip');
+            } else {
+                if ($node->isNodeOnline()) {
+                    if (!$node->changeNodeIp($request->getParam('server'))) {
+                        $rs['ret'] = 0;
+                        $rs['msg'] = "更新节点IP失败,请检查您输入的节点地址是否正确!";
+                        return $response->getBody()->write(json_encode($rs));
+                    }
+                }
+            }
+        } else {
+            $node->node_ip="";
+        }
+
+        if ($node->sort == 0 || $node->sort == 10) {
+            Tools::updateRelayRuleIp($node);
+        }
+
+        if ($node->sort==1) {
+            $SS_Node=Node::where('sort', '=', 0)->where('server', '=', $request->getParam('server'))->first();
+            if ($SS_Node!=null) {
+                if (time()-$SS_Node->node_heartbeat<300||$SS_Node->node_heartbeat==0) {
+                    Radius::AddNas(gethostbyname($request->getParam('server')), $request->getParam('server'));
+                }
+            } else {
+                Radius::AddNas(gethostbyname($request->getParam('server')), $request->getParam('server'));
+            }
+        }
+
+        $node->status = $request->getParam('status');
+        $node->node_class=$request->getParam('class');
+        $node->node_bandwidth_limit=$request->getParam('node_bandwidth_limit')*1024*1024*1024;
+        $node->bandwidthlimit_resetday=$request->getParam('bandwidthlimit_resetday');
+
+        if (!$node->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "修改失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::Send("节点信息被修改~".$request->getParam('name'));
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "修改成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $node = Node::find($id);
+        if ($node->sort==1) {
+            Radius::DelNas($node->node_ip);
+        }
+
+        $name = $node->name;
+
+        if (!$node->delete()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        Telegram::Send("节点被删除~".$name);
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+
+
+        $total_column = Array("op" => "操作", "id" => "ID", "name" => "节点名称",
+                              "type" => "显示与隐藏", "sort" => "类型",
+                              "server" => "节点地址", "node_ip" => "节点IP",
+                              "info" => "节点信息",
+                              "status" => "状态", "traffic_rate" => "流量比率", "node_group" => "节点群组",
+                              "node_class" => "节点等级", "node_speedlimit" => "节点限速/Mbps",
+                              "node_bandwidth" => "已走流量/GB", "node_bandwidth_limit" => "流量限制/GB",
+                              "bandwidthlimit_resetday" => "流量重置日", "node_heartbeat" => "上一次活跃时间",
+                              "custom_method" => "自定义加密", "custom_rss" => "自定义协议以及混淆",
+                              "mu_only" => "只启用单端口多用户");
+        $key_str = '';
+        foreach($total_column as $single_key => $single_value) {
+            if($single_key == 'op') {
+                $key_str .= 'id as op';
+                continue;
+            }
+
+            $key_str .= ','.$single_key;
+        }
+        $datatables->query('Select '.$key_str.' from ss_node');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand" '.($data['sort'] == 999 ? 'disabled' : 'href="/admin/node/'.$data['id'].'/edit"').'>编辑</a>
+                    <a class="btn btn-brand-accent" '.($data['sort'] == 999 ? 'disabled' : 'id="delete" value="'.$data['id'].'" href="javascript:void(0);" onClick="delete_modal_show(\''.$data['id'].'\')"').'>删除</a>';
+        });
+
+        $datatables->edit('node_bandwidth', function ($data) {
+            return Tools::flowToGB($data['node_bandwidth']);
+        });
+
+        $datatables->edit('node_bandwidth_limit', function ($data) {
+            return Tools::flowToGB($data['node_bandwidth_limit']);
+        });
+
+        $datatables->edit('sort', function ($data) {
+            $sort = '';
+            switch($data['sort']) {
+                case 0:
+                  $sort = 'Shadowsocks';
+                  break;
+                case 1:
+                  $sort = 'VPN/Radius基础';
+                  break;
+                case 2:
+                  $sort = 'SSH';
+                  break;
+                case 3:
+                  $sort = 'PAC';
+                  break;
+                case 4:
+                  $sort = 'APN文件外链';
+                  break;
+                case 5:
+                  $sort = 'Anyconnect';
+                  break;
+                case 6:
+                  $sort = 'APN';
+                  break;
+                case 7:
+                  $sort = 'PAC PLUS(Socks 代理生成 PAC文件)';
+                  break;
+                case 8:
+                  $sort = 'PAC PLUS PLUS(HTTPS 代理生成 PAC文件)';
+                  break;
+                case 9:
+                  $sort = 'Shadowsocks - 单端口多用户';
+                  break;
+                case 10:
+                  $sort = 'Shadowsocks - 中转';
+                  break;
+                default:
+                  $sort = '系统保留';
+            }
+            return $sort;
+        });
+
+        $datatables->edit('type', function ($data) {
+            return $data['type'] == 1 ? '显示' : '隐藏';
+        });
+
+        $datatables->edit('custom_method', function ($data) {
+            return $data['custom_method'] == 1 ? '启用' : '关闭';
+        });
+
+        $datatables->edit('custom_rss', function ($data) {
+            return $data['custom_rss'] == 1 ? '启用' : '关闭';
+        });
+
+        $datatables->edit('mu_only', function ($data) {
+            return $data['mu_only'] == 1 ? '启用' : '关闭';
+        });
+
+        $datatables->edit('node_heartbeat', function ($data) {
+            return date('Y-m-d H:i:s', $data['node_heartbeat']);
+        });
+
+        $datatables->edit('DT_RowId', function ($data) {
+            return 'row_1_'.$data['id'];
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 328 - 0
app/Controllers/Admin/RelayController.php

@@ -0,0 +1,328 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Relay;
+use App\Models\Node;
+use App\Models\User;
+use App\Utils\Tools;
+use App\Services\Auth;
+use App\Controllers\AdminController;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class RelayController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID", "user_id" => "用户ID",
+                          "user_name" => "用户名", "source_node_name" => "起源节点",
+                          "dist_node_name" => "目标节点", "port" => "端口", "priority" => "优先级");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'relay/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/relay/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        $user = Auth::getUser();
+        $source_nodes = Node::where('sort', 10)->orderBy('name')->get();
+
+        $dist_nodes = Node::where(
+            function ($query) {
+                $query->Where('sort', 0)
+                    ->orWhere('sort', 10);
+            }
+        )->orderBy('name')->get();
+
+
+        return $this->view()->assign('source_nodes', $source_nodes)->assign('dist_nodes', $dist_nodes)->display('admin/relay/add.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $dist_node_id = $request->getParam('dist_node');
+        $source_node_id = $request->getParam('source_node');
+        $port = $request->getParam('port');
+        $priority = $request->getParam('priority');
+        $user_id = $request->getParam('user_id');
+
+        $source_node = Node::where('id', $source_node_id)->first();
+        if ($source_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "起源节点错误。";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $dist_node = Node::where('id', $dist_node_id)->first();
+        if ($dist_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "目标节点错误。";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rule = new Relay();
+        $rule->user_id = $user_id;
+        $rule->dist_node_id = $dist_node_id;
+
+        $dist_node_ip = Tools::getRelayNodeIp($source_node, $dist_node);
+        $rule->dist_ip = $dist_node_ip;
+
+        $rule->source_node_id = $source_node_id;
+        $rule->port = $port;
+        $rule->priority = $priority;
+
+        if ($user_id == 0) {
+            $ruleset = Relay::all();
+        } else {
+            $ruleset = Relay::where('user_id', $user_id)->orwhere('user_id', 0)->get();
+        }
+        $maybe_rule_id = Tools::has_conflict_rule($rule, $ruleset, 0, $rule->source_node_id);
+        if ($maybe_rule_id != 0) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "您即将添加的规则与规则 ID:".$maybe_rule_id." 冲突!";
+            if ($maybe_rule_id == -1) {
+                $rs['msg'] = "您即将添加的规则可能会造成冲突!";
+            }
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if (!$rule->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+
+        $user = Auth::getUser();
+        $rule = Relay::where('id', $id)->first();
+
+        if ($rule == null) {
+            exit(0);
+        }
+
+        $source_nodes = Node::where('sort', 10)->orderBy('name')->get();
+
+        $dist_nodes = Node::where(
+            function ($query) {
+                $query->Where('sort', 0)
+                    ->orWhere('sort', 10);
+            }
+        )->orderBy('name')->get();
+
+        return $this->view()->assign('rule', $rule)->assign('source_nodes', $source_nodes)->assign('dist_nodes', $dist_nodes)->display('admin/relay/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $rule = Relay::where('id', $id)->first();
+
+        if ($rule == null) {
+            exit(0);
+        }
+
+        $dist_node_id = $request->getParam('dist_node');
+        $source_node_id = $request->getParam('source_node');
+        $port = $request->getParam('port');
+        $user_id = $request->getParam('user_id');
+        $priority = $request->getParam('priority');
+
+        $source_node = Node::where('id', $source_node_id)->first();
+        if ($source_node == null && $source_node_id != 0) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "起源节点 ID 错误。";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $dist_node = Node::where('id', $dist_node_id)->first();
+        if ($dist_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "目标节点 ID 错误。";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rule->user_id = $user_id;
+        $rule->dist_node_id = $dist_node_id;
+
+        $dist_node_ip = Tools::getRelayNodeIp($source_node, $dist_node);
+        $rule->dist_ip = $dist_node_ip;
+
+        $rule->source_node_id = $source_node_id;
+        $rule->port = $port;
+        $rule->priority = $priority;
+
+        if ($user_id == 0) {
+            $ruleset = Relay::all();
+        } else {
+            $ruleset = Relay::where('user_id', $user_id)->orwhere('user_id', 0)->get();
+        }
+        $maybe_rule_id = Tools::has_conflict_rule($rule, $ruleset, $rule->id, $rule->source_node_id);
+        if ($maybe_rule_id != 0) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "您即将添加的规则与规则 ID:".$maybe_rule_id." 冲突!";
+            if ($maybe_rule_id == -1) {
+                $rs['msg'] = "您即将添加的规则可能会造成冲突!";
+            }
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+
+        if (!$rule->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "修改失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "修改成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $user = Auth::getUser();
+        $rule = Relay::where('id', $id)->first();
+
+        if ($rule == null) {
+            exit(0);
+        }
+
+        if (!$rule->delete()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function path_search($request, $response, $args)
+    {
+        $uid=$args["id"];
+
+        $user = User::find($uid);
+
+        if ($user == null) {
+            $pathset = new \ArrayObject();
+            return $this->view()->assign('pathset', $pathset)->display('admin/relay/search.tpl');
+        }
+
+        $nodes = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                      ->orWhere("node_group", "=", 0);
+            }
+        )->where("sort", "=", 10)->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $pathset = new \ArrayObject();
+
+        $relay_rules = Relay::where('user_id', $user->id)->orwhere('user_id', 0)->get();
+        $mu_nodes = Node::where('sort', 9)->where('node_class', '<=', $user->class)->where("type", "1")->where(
+            function ($query) use ($user) {
+                $query->where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->get();
+
+        foreach ($nodes as $node) {
+            if ($node->mu_only == 0) {
+                $relay_rule = Tools::pick_out_relay_rule($node->id, $user->port, $relay_rules);
+
+                if ($relay_rule != null) {
+                    $pathset = Tools::insertPathRule($relay_rule, $pathset, $user->port);
+                }
+            }
+
+            if ($node->custom_rss == 1) {
+                foreach ($mu_nodes as $mu_node) {
+                    $mu_user = User::where('port', '=', $mu_node->server)->first();
+
+                    if ($mu_user == null) {
+                        continue;
+                    }
+
+                    if (!($mu_user->class >= $node->node_class && ($node->node_group == 0 || $node->node_group == $mu_user->node_group))) {
+                        continue;
+                    }
+
+                    if ($mu_user->is_multi_user != 2) {
+                        $relay_rule = Tools::pick_out_relay_rule($node->id, $mu_user->port, $relay_rules);
+
+                        if ($relay_rule != null) {
+                            $pathset = Tools::insertPathRule($relay_rule, $pathset, $mu_user->port);
+                        }
+                    }
+                }
+            }
+        }
+
+        foreach ($pathset as $path) {
+            foreach ($pathset as $index => $single_path) {
+                if ($path != $single_path && $path->port == $single_path->port) {
+                    if ($single_path->end_node->id == $path->begin_node->id) {
+                        $path->begin_node = $single_path->begin_node;
+                        if ($path->begin_node->isNodeAccessable() == false) {
+                            $path->path = '<font color="#FF0000">'.$single_path->begin_node->name.'</font>'." → ".$path->path;
+                            $path->status = "阻断";
+                        } else {
+                            $path->path = $single_path->begin_node->name." → ".$path->path;
+                            $path->status = "通畅";
+                        }
+
+                        $pathset->offsetUnset($index);
+                        continue;
+                    }
+
+                    if ($path->end_node->id == $single_path->begin_node->id) {
+                        $path->end_node = $single_path->end_node;
+                        if ($single_path->end_node->isNodeAccessable() == false) {
+                            $path->path = $path->path." → ".'<font color="#FF0000">'.$single_path->end_node->name.'</font>';
+                            $path->status = "阻断";
+                        } else {
+                            $path->path = $path->path." → ".$single_path->end_node->name;
+                        }
+
+                        $pathset->offsetUnset($index);
+                        continue;
+                    }
+                }
+            }
+        }
+
+        return $this->view()->assign('pathset', $pathset)->display('admin/relay/search.tpl');
+    }
+
+    public function ajax_relay($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select relay.id as op,relay.id,relay.user_id,user.user_name,source_node.name as source_node_name,dist_node.name as dist_node_name,relay.port,relay.priority from relay,user,ss_node as source_node,ss_node as dist_node WHERE (relay.user_id = user.id or relay.user_id = 0) and source_node.id = relay.source_node_id and dist_node.id = relay.dist_node_id group by id');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand" href="/admin/relay/'.$data['id'].'/edit">编辑</a>
+                    <a class="btn btn-brand-accent" id="delete" value="'.$data['id'].'" href="javascript:void(0);" onClick="delete_modal_show(\''.$data['id'].'\')">删除</a>';
+        });
+
+        $datatables->edit('user_name', function ($data) {
+            return ($data['user_id'] == 0 ? '全体用户' : $data['user_name']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 276 - 0
app/Controllers/Admin/ShopController.php

@@ -0,0 +1,276 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Shop;
+use App\Models\Bought;
+use App\Controllers\AdminController;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+class ShopController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID", "name" => "商品名称",
+                        "price" => "价格", "content" => "商品内容",
+                        "auto_renew" => "自动续费", "auto_reset_bandwidth" => "续费时是否重置流量",
+                        "status" => "状态");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'shop/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/shop/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        return $this->view()->display('admin/shop/create.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $shop = new Shop();
+        $shop->name =  $request->getParam('name');
+        $shop->price =  $request->getParam('price');
+        $shop->auto_renew =  $request->getParam('auto_renew');
+        $shop->auto_reset_bandwidth =  $request->getParam('auto_reset_bandwidth');
+
+        $content=array();
+        if ($request->getParam('bandwidth')!=0) {
+            $content["bandwidth"]=$request->getParam('bandwidth');
+        }
+
+        if ($request->getParam('expire')!=0) {
+            $content["expire"]=$request->getParam('expire');
+        }
+
+        if ($request->getParam('class')!=0) {
+            $content["class"]=$request->getParam('class');
+        }
+
+        if ($request->getParam('class_expire')!=0) {
+            $content["class_expire"]=$request->getParam('class_expire');
+        }
+
+        if ($request->getParam('reset')!=0) {
+            $content["reset"]=$request->getParam('reset');
+        }
+
+        if ($request->getParam('reset_value')!=0) {
+            $content["reset_value"]=$request->getParam('reset_value');
+        }
+
+        if ($request->getParam('reset_exp')!=0) {
+            $content["reset_exp"]=$request->getParam('reset_exp');
+        }
+
+        $shop->content=json_encode($content);
+
+
+        if (!$shop->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+        $shop = Shop::find($id);
+        if ($shop == null) {
+        }
+        return $this->view()->assign('shop', $shop)->display('admin/shop/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $shop = Shop::find($id);
+
+        $shop->name =  $request->getParam('name');
+        $shop->price =  $request->getParam('price');
+        $shop->auto_renew =  $request->getParam('auto_renew');
+
+        if ($shop->auto_reset_bandwidth == 1 && $request->getParam('auto_reset_bandwidth') == 0) {
+            $boughts = Bought::where("shopid", $id)->get();
+
+            foreach ($boughts as $bought) {
+                $bought->renew=0;
+                $bought->save();
+            }
+        }
+
+        $shop->auto_reset_bandwidth =  $request->getParam('auto_reset_bandwidth');
+        $shop->status=1;
+
+        $content=array();
+        if ($request->getParam('bandwidth')!=0) {
+            $content["bandwidth"]=$request->getParam('bandwidth');
+        }
+
+        if ($request->getParam('expire')!=0) {
+            $content["expire"]=$request->getParam('expire');
+        }
+
+        if ($request->getParam('class')!=0) {
+            $content["class"]=$request->getParam('class');
+        }
+
+        if ($request->getParam('class_expire')!=0) {
+            $content["class_expire"]=$request->getParam('class_expire');
+        }
+
+        if ($request->getParam('reset')!=0) {
+            $content["reset"]=$request->getParam('reset');
+        }
+
+        if ($request->getParam('reset_value')!=0) {
+            $content["reset_value"]=$request->getParam('reset_value');
+        }
+
+        if ($request->getParam('reset_exp')!=0) {
+            $content["reset_exp"]=$request->getParam('reset_exp');
+        }
+
+        $shop->content=json_encode($content);
+
+        if (!$shop->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "保存失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "保存成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+
+    public function deleteGet($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $shop = Shop::find($id);
+        $shop->status=0;
+        if (!$shop->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "下架失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $boughts = Bought::where("shopid", $id)->get();
+
+        foreach ($boughts as $bought) {
+            $bought->renew=0;
+            $bought->save();
+        }
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "下架成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function bought($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID", "content" => "内容",
+                        "price" => "价格", "user_id" => "用户ID",
+                        "user_name" => "用户名", "renew" => "自动续费时间", "auto_reset_bandwidth" => "续费时是否重置流量",
+                        "datetime" => "自动重置");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'bought/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/shop/bought.tpl');
+    }
+
+    public function deleteBoughtGet($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $shop = Bought::find($id);
+        $shop->renew=0;
+        if (!$shop->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "退订失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "退订成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax_shop($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select id as op,id,name,price,content,auto_renew,auto_reset_bandwidth,status from shop');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand" href="/admin/shop/'.$data['id'].'/edit">编辑</a>
+                    <a class="btn btn-brand-accent" '.($data['status'] == 0 ? "disabled" : 'id="row_delete_'.$data['id'].'" href="javascript:void(0);" onClick="delete_modal_show(\''.$data['id'].'\')"').'>下架</a>';
+        });
+
+        $datatables->edit('content', function ($data) {
+            $shop = Shop::find($data['id']);
+            return $shop->content();
+        });
+
+        $datatables->edit('auto_renew', function ($data) {
+            if ($data['auto_renew'] == 0) {
+                return "不自动续费";
+            } else {
+                return $data['auto_renew']." 天后续费";
+            }
+        });
+
+        $datatables->edit('auto_reset_bandwidth', function ($data) {
+            return $data['auto_reset_bandwidth'] == 0 ? '不自动重置' : '自动重置';
+        });
+
+        $datatables->edit('status', function ($data) {
+            return $data['status'] == 1 ? '上架' : '下架';
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+    public function ajax_bought($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select bought.id as op,bought.id as id,shop.id as content,bought.price,user.id as user_id,user.user_name,renew,shop.auto_reset_bandwidth,bought.datetime from bought,user,shop where bought.shopid = shop.id and bought.userid = user.id');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand-accent" '.($data['renew'] == 0 ? "disabled" : ' id="row_delete_'.$data['id'].'" href="javascript:void(0);" onClick="delete_modal_show(\''.$data['id'].'\')"').'>中止</a>';
+        });
+
+        $datatables->edit('content', function ($data) {
+            $shop = Shop::find($data['content']);
+            return $shop->content();
+        });
+
+        $datatables->edit('renew', function ($data) {
+            if ($data['renew'] == 0) {
+                return "不自动续费";
+            } else {
+                return date('Y-m-d H:i:s', $data['renew'])." 续费";
+            }
+        });
+
+        $datatables->edit('auto_reset_bandwidth', function ($data) {
+            return $data['auto_reset_bandwidth'] == 0 ? '不自动重置' : '自动重置';
+        });
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 128 - 0
app/Controllers/Admin/TicketController.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\Ticket;
+use App\Models\User;
+
+use voku\helper\AntiXSS;
+use App\Services\Auth;
+
+use App\Services\Mail;
+use App\Services\Config;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+use App\Controllers\AdminController;
+
+class TicketController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID",
+                              "datetime" => "时间", "title" => "标题", "userid" => "用户ID",
+                              "user_name" => "用户名", "status" => "状态");
+        $table_config['default_show_column'] = array("op", "id",
+                                  "datetime", "title", "userid", "user_name", "status");
+        $table_config['ajax_url'] = 'ticket/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/ticket/index.tpl');
+    }
+
+
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $content = $request->getParam('content');
+        $status = $request->getParam('status');
+
+
+        if ($content==""||$status=="") {
+            $res['ret'] = 0;
+            $res['msg'] = "请填全";
+            return $this->echoJson($response, $res);
+        }
+
+        if (strpos($content, "admin")!=false||strpos($content, "user")!=false) {
+            $res['ret'] = 0;
+            $res['msg'] = "请求中有不正当的词语。";
+            return $this->echoJson($response, $res);
+        }
+
+
+        $ticket_main=Ticket::where("id", "=", $id)->where("rootid", "=", 0)->first();
+
+        //if($status==1&&$ticket_main->status!=$status)
+        {
+            $adminUser = User::where("id", "=", $ticket_main->userid)->get();
+            foreach ($adminUser as $user) {
+                $subject = Config::get('appName')."-工单被回复";
+                $to = $user->email;
+                $text = "您好,有人回复了<a href=\"".Config::get('baseUrl')."/user/ticket/".$ticket_main->id."/view\">工单</a>,请您查看。" ;
+                try {
+                    Mail::send($to, $subject, 'news/warn.tpl', [
+                        "user" => $user,"text" => $text
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+            }
+        }
+
+        $antiXss = new AntiXSS();
+
+        $ticket=new Ticket();
+        $ticket->title=$antiXss->xss_clean($ticket_main->title);
+        $ticket->content=$antiXss->xss_clean($content);
+        $ticket->rootid=$ticket_main->id;
+        $ticket->userid=Auth::getUser()->id;
+        $ticket->datetime=time();
+        $ticket_main->status=$status;
+
+        $ticket_main->save();
+        $ticket->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "提交成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function show($request, $response, $args)
+    {
+        $id = $args['id'];
+
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+
+
+        $ticketset=Ticket::where("id", $id)->orWhere("rootid", "=", $id)->orderBy("datetime", "desc")->paginate(5, ['*'], 'page', $pageNum);
+        $ticketset->setPath('/admin/ticket/'.$id."/view");
+
+        return $this->view()->assign('ticketset', $ticketset)->assign("id", $id)->display('admin/ticket/view.tpl');
+    }
+
+    public function ajax($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select ticket.id as op,ticket.id,ticket.datetime,ticket.title,ticket.userid,user.user_name,ticket.status from ticket,user where ticket.userid = user.id and ticket.rootid = 0');
+
+        $datatables->edit('op', function ($data) {
+            return '<a class="btn btn-brand" href="/admin/ticket/'.$data['id'].'/view">查看</a>';
+        });
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $datatables->edit('status', function ($data) {
+            return $data['status'] == 1 ? '开启' : '关闭';
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 247 - 0
app/Controllers/Admin/UserController.php

@@ -0,0 +1,247 @@
+<?php
+
+namespace App\Controllers\Admin;
+
+use App\Models\User;
+use App\Models\Ip;
+use App\Models\RadiusBan;
+use App\Models\Relay;
+use App\Controllers\AdminController;
+use App\Utils\Hash;
+use App\Utils\Radius;
+use App\Utils\QQWry;
+use App\Utils\Wecenter;
+use App\Utils\Tools;
+
+class UserController extends AdminController
+{
+    public function index($request, $response, $args)
+    {
+        $table_config['total_column'] = array("op" => "操作", "id" => "ID", "user_name" => "用户名",
+                            "remark" => "备注", "email" => "邮箱", "money" => "金钱",
+                            "im_type" => "联络方式类型", "im_value" => "联络方式详情",
+                            "node_group" => "群组", "account_expire_in" => "账户过期时间",
+                            "class" => "等级", "class_expire" => "等级过期时间",
+                            "passwd" => "连接密码","port" => "连接端口", "method" => "加密方式",
+                            "protocol" => "连接协议", "obfs" => "连接混淆方式",
+                            "online_ip_count" => "在线IP数", "last_ss_time" => "上次使用时间",
+                            "used_traffic" => "已用流量/GB", "enable_traffic" => "总流量/GB",
+                            "last_checkin_time" => "上次签到时间", "today_traffic" => "今日流量/MB",
+                            "is_enable" => "是否启用", "reg_date" => "注册时间",
+                            "reg_location" => "注册IP", "auto_reset_day" => "自动重置流量日",
+                            "auto_reset_bandwidth" => "自动重置流量/GB", "ref_by" => "邀请人ID", "ref_by_user_name" => "邀请人用户名");
+        $table_config['default_show_column'] = array("op", "id", "user_name", "remark", "email");
+        $table_config['ajax_url'] = 'user/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/user/index.tpl');
+    }
+
+    public function search($request, $response, $args)
+    {
+        $pageNum = 1;
+        $text=$args["text"];
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+
+        $users = User::where("email", "LIKE", "%".$text."%")->orWhere("user_name", "LIKE", "%".$text."%")->orWhere("im_value", "LIKE", "%".$text."%")->orWhere("port", "LIKE", "%".$text."%")->orWhere("remark", "LIKE", "%".$text."%")->paginate(20, ['*'], 'page', $pageNum);
+        $users->setPath('/admin/user/search/'.$text);
+
+
+
+        //Ip::where("datetime","<",time()-90)->get()->delete();
+        $total = Ip::where("datetime", ">=", time()-90)->orderBy('userid', 'desc')->get();
+
+
+        $userip=array();
+        $useripcount=array();
+        $regloc=array();
+
+        $iplocation = new QQWry();
+        foreach ($users as $user) {
+            $useripcount[$user->id]=0;
+            $userip[$user->id]=array();
+
+            $location=$iplocation->getlocation($user->reg_ip);
+            $regloc[$user->id]=iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+        }
+
+
+
+        foreach ($total as $single) {
+            if (isset($useripcount[$single->userid])) {
+                if (!isset($userip[$single->userid][$single->ip])) {
+                    $useripcount[$single->userid]=$useripcount[$single->userid]+1;
+                    $location=$iplocation->getlocation($single->ip);
+                    $userip[$single->userid][$single->ip]=iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+                }
+            }
+        }
+
+
+        return $this->view()->assign('users', $users)->assign("regloc", $regloc)->assign("useripcount", $useripcount)->assign("userip", $userip)->display('admin/user/index.tpl');
+    }
+
+    public function sort($request, $response, $args)
+    {
+        $pageNum = 1;
+        $text=$args["text"];
+        $asc=$args["asc"];
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+
+
+        $users->setPath('/admin/user/sort/'.$text."/".$asc);
+
+
+
+        //Ip::where("datetime","<",time()-90)->get()->delete();
+        $total = Ip::where("datetime", ">=", time()-90)->orderBy('userid', 'desc')->get();
+
+
+        $userip=array();
+        $useripcount=array();
+        $regloc=array();
+
+        $iplocation = new QQWry();
+        foreach ($users as $user) {
+            $useripcount[$user->id]=0;
+            $userip[$user->id]=array();
+
+            $location=$iplocation->getlocation($user->reg_ip);
+            $regloc[$user->id]=iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+        }
+
+
+
+        foreach ($total as $single) {
+            if (isset($useripcount[$single->userid])) {
+                if (!isset($userip[$single->userid][$single->ip])) {
+                    $useripcount[$single->userid]=$useripcount[$single->userid]+1;
+                    $location=$iplocation->getlocation($single->ip);
+                    $userip[$single->userid][$single->ip]=iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+                }
+            }
+        }
+
+
+        return $this->view()->assign('users', $users)->assign("regloc", $regloc)->assign("useripcount", $useripcount)->assign("userip", $userip)->display('admin/user/index.tpl');
+    }
+
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+        $user = User::find($id);
+        if ($user == null) {
+        }
+        return $this->view()->assign('edit_user', $user)->display('admin/user/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $user = User::find($id);
+
+        $email1=$user->email;
+
+        $user->email =  $request->getParam('email');
+
+        $email2=$request->getParam('email');
+
+        $passwd=$request->getParam('passwd');
+
+        Radius::ChangeUserName($email1, $email2, $passwd);
+
+
+        if ($request->getParam('pass') != '') {
+            $user->pass = Hash::passwordHash($request->getParam('pass'));
+            Wecenter::ChangeUserName($email1, $email2, $request->getParam('pass'), $user->user_name);
+            $user->clean_link();
+        }
+
+        $user->auto_reset_day =  $request->getParam('auto_reset_day');
+        $user->auto_reset_bandwidth = $request->getParam('auto_reset_bandwidth');
+        $origin_port = $user->port;
+        $user->port =  $request->getParam('port');
+
+        $relay_rules = Relay::where('user_id', $user->id)->where('port', $origin_port)->get();
+        foreach ($relay_rules as $rule) {
+            $rule->port = $user->port;
+            $rule->save();
+        }
+
+        $user->passwd = $request->getParam('passwd');
+        $user->protocol = $request->getParam('protocol');
+        $user->protocol_param = $request->getParam('protocol_param');
+        $user->obfs = $request->getParam('obfs');
+        $user->obfs_param = $request->getParam('obfs_param');
+        $user->is_multi_user = $request->getParam('is_multi_user');
+        $user->transfer_enable = Tools::toGB($request->getParam('transfer_enable'));
+        $user->invite_num = $request->getParam('invite_num');
+        $user->method = $request->getParam('method');
+        $user->node_speedlimit = $request->getParam('node_speedlimit');
+        $user->node_connector = $request->getParam('node_connector');
+        $user->enable = $request->getParam('enable');
+        $user->is_admin = $request->getParam('is_admin');
+        $user->node_group = $request->getParam('group');
+        $user->ref_by = $request->getParam('ref_by');
+        $user->remark = $request->getParam('remark');
+        $user->money = $request->getParam('money');
+        $user->class = $request->getParam('class');
+        $user->class_expire = $request->getParam('class_expire');
+        $user->expire_in = $request->getParam('expire_in');
+
+        $user->forbidden_ip = str_replace(PHP_EOL, ",", $request->getParam('forbidden_ip'));
+        $user->forbidden_port = str_replace(PHP_EOL, ",", $request->getParam('forbidden_port'));
+
+        if (!$user->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "修改失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "修改成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $user = User::find($id);
+
+        $email1=$user->email;
+
+        if (!$user->kill_user()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function ajax($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+
+        $users = User::skip(($pageNum - 1) * 100)->limit(100)->get();
+        $total_conut = User::count();
+        if($total_conut < $pageNum * 100) {
+            $res['next'] = 0;
+        } else {
+            $res['next'] = 1;
+        }
+
+        $res['data'] = array();
+        foreach ($users as $user) {
+            array_push($res['data'], $user->get_table_json_array());
+        }
+
+        return $this->echoJson($response, $res);
+    }
+}

+ 180 - 0
app/Controllers/AdminController.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Models\InviteCode;
+use App\Models\Node;
+use App\Models\TrafficLog;
+use App\Models\Payback;
+use App\Models\Coupon;
+use App\Models\User;
+use App\Utils\Tools;
+use App\Services\Analytics;
+
+use Ozdemir\Datatables\Datatables;
+use App\Utils\DatatablesHelper;
+
+/**
+ *  Admin Controller
+ */
+class AdminController extends UserController
+{
+    public function index($request, $response, $args)
+    {
+        $sts = new Analytics();
+        return $this->view()->assign('sts', $sts)->display('admin/index.tpl');
+    }
+
+    public function node($request, $response, $args)
+    {
+        $nodes = Node::all();
+        return $this->view()->assign('nodes', $nodes)->display('admin/node.tpl');
+    }
+
+    public function sys()
+    {
+        return $this->view()->display('admin/index.tpl');
+    }
+
+    public function invite($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID",
+                        "total" => "原始金额", "event_user_id" => "发起用户ID",
+                        "event_user_name" => "发起用户名", "ref_user_id" => "获利用户ID",
+                        "ref_user_name" => "获利用户名", "ref_get" => "获利金额",
+                        "datetime" => "时间");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'payback/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/invite.tpl');
+    }
+
+    public function addInvite($request, $response, $args)
+    {
+        $n = $request->getParam('num');
+        $prefix = $request->getParam('prefix');
+
+        if ($request->getParam('uid')!="0") {
+            if (strpos($request->getParam('uid'), "@")!=false) {
+                $user=User::where("email", "=", $request->getParam('uid'))->first();
+            } else {
+                $user=User::Where("id", "=", $request->getParam('uid'))->first();
+            }
+
+            if ($user==null) {
+                $res['ret'] = 0;
+                $res['msg'] = "输入不正确";
+                return $response->getBody()->write(json_encode($res));
+            }
+            $uid = $user->id;
+        } else {
+            $uid=0;
+        }
+
+        if ($n < 1) {
+            $res['ret'] = 0;
+            return $response->getBody()->write(json_encode($res));
+        }
+        for ($i = 0; $i < $n; $i++) {
+            $char = Tools::genRandomChar(32);
+            $code = new InviteCode();
+            $code->code = $prefix . $char;
+            $code->user_id = $uid;
+            $code->save();
+        }
+        $res['ret'] = 1;
+        $res['msg'] = "邀请码添加成功";
+        return $response->getBody()->write(json_encode($res));
+    }
+
+
+    public function coupon($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "code" => "优惠码",
+                          "expire" => "过期时间", "shop" => "限定商品ID",
+                          "credit" => "额度");
+        $table_config['default_show_column'] = array();
+        foreach ($table_config['total_column'] as $column => $value) {
+            array_push($table_config['default_show_column'], $column);
+        }
+        $table_config['ajax_url'] = 'coupon/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/coupon.tpl');
+    }
+
+    public function addCoupon($request, $response, $args)
+    {
+        $code = new Coupon();
+        $code->onetime=$request->getParam('onetime');
+
+        $code->code=$request->getParam('prefix').Tools::genRandomChar(8);
+        $code->expire=time()+$request->getParam('expire')*3600;
+        $code->shop=$request->getParam('shop');
+        $code->credit=$request->getParam('credit');
+
+        $code->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "优惠码添加成功";
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function trafficLog($request, $response, $args)
+    {
+        $table_config['total_column'] = array("id" => "ID", "user_id" => "用户ID",
+                          "user_name" => "用户名", "node_name" => "使用节点",
+                          "rate" => "倍率", "origin_traffic" => "实际使用流量",
+                          "traffic" => "结算流量",
+                          "log_time" => "记录时间");
+        $table_config['default_show_column'] = array("id", "user_id",
+                                  "user_name", "node_name",
+                                  "rate", "origin_traffic",
+                                  "traffic", "log_time");
+        $table_config['ajax_url'] = 'trafficlog/ajax';
+        return $this->view()->assign('table_config', $table_config)->display('admin/trafficlog.tpl');
+    }
+
+    public function ajax_trafficLog($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select log.id,log.user_id,user.user_name,node.name as node_name,log.rate,(log.u + log.d) as origin_traffic,log.traffic,log.log_time from user_traffic_log as log,user,ss_node as node WHERE log.user_id = user.id AND log.node_id = node.id');
+
+        $datatables->edit('log_time', function ($data) {
+            return date('Y-m-d H:i:s', $data['log_time']);
+        });
+
+        $datatables->edit('origin_traffic', function ($data) {
+            return Tools::flowAutoShow($data['origin_traffic']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+    public function ajax_payback($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select payback.id,payback.total,payback.userid as event_user_id,event_user.user_name as event_user_name,payback.ref_by as ref_user_id,ref_user.user_name as ref_user_name,payback.ref_get,payback.datetime from payback,user as event_user,user as ref_user where event_user.id = payback.userid and ref_user.id = payback.ref_by');
+
+        $datatables->edit('datetime', function ($data) {
+            return date('Y-m-d H:i:s', $data['datetime']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+
+    public function ajax_coupon($request, $response, $args)
+    {
+        $datatables = new Datatables(new DatatablesHelper());
+        $datatables->query('Select id,code,expire,shop,credit from coupon');
+
+        $datatables->edit('expire', function ($data) {
+            return date('Y-m-d H:i:s', $data['expire']);
+        });
+
+        $body = $response->getBody();
+        $body->write($datatables->generate());
+    }
+}

+ 164 - 0
app/Controllers/ApiController.php

@@ -0,0 +1,164 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Models\InviteCode;
+use App\Models\Node;
+use App\Models\User;
+use App\Services\Factory;
+use App\Services\Config;
+use App\Utils\Tools;
+use App\Utils\Hash;
+use App\Utils\Helper;
+
+/**
+ *  ApiController
+ */
+
+class ApiController extends BaseController
+{
+    public function index()
+    {
+    }
+
+    public function token($request, $response, $args)
+    {
+        $accessToken = $id = $args['token'];
+        $storage = Factory::createTokenStorage();
+        $token = $storage->get($accessToken);
+        if ($token==null) {
+            $res['ret'] = 0;
+            $res['msg'] = "token is null";
+            return $this->echoJson($response, $res);
+        }
+        $res['ret'] = 1;
+        $res['msg'] = "ok";
+        $res['data'] = $token;
+        return $this->echoJson($response, $res);
+    }
+
+    public function newToken($request, $response, $args)
+    {
+        // $data = $request->post('sdf');
+        $email =  $request->getParam('email');
+        
+        $email = strtolower($email);
+        $passwd = $request->getParam('passwd');
+
+        // Handle Login
+        $user = User::where('email', '=', $email)->first();
+
+        if ($user == null) {
+            $res['ret'] = 0;
+            $res['msg'] = "401 邮箱或者密码错误";
+            return $this->echoJson($response, $res);
+        }
+
+        if (!Hash::checkPassword($user->pass, $passwd)) {
+            $res['ret'] = 0;
+            $res['msg'] = "402 邮箱或者密码错误";
+            return $this->echoJson($response, $res);
+        }
+        $tokenStr = Tools::genToken();
+        $storage = Factory::createTokenStorage();
+        $expireTime = time() + 3600*24*7;
+        if ($storage->store($tokenStr, $user, $expireTime)) {
+            $res['ret'] = 1;
+            $res['msg'] = "ok";
+            $res['data']['token'] = $tokenStr;
+            $res['data']['user_id'] = $user->id;
+            return $this->echoJson($response, $res);
+        }
+        $res['ret'] = 0;
+        $res['msg'] = "system error";
+        return $this->echoJson($response, $res);
+    }
+
+    public function node($request, $response, $args)
+    {
+        $accessToken = Helper::getTokenFromReq($request);
+        $storage = Factory::createTokenStorage();
+        $token = $storage->get($accessToken);
+        $user = User::find($token->userId);
+        $nodes = Node::where('sort', 0)->where("type", "1")->where(
+            function ($query) use ($user) {
+                $query->where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->get();
+        
+        $mu_nodes = Node::where('sort', 9)->where('node_class', '<=', $user->class)->where("type", "1")->where(
+            function ($query) use ($user) {
+                $query->where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->get();
+        
+        $temparray=array();
+        foreach ($nodes as $node) {
+            if ($node->mu_only == 0) {
+                array_push($temparray, array("remarks"=>$node->name,
+                                            "server"=>$node->server,
+                                            "server_port"=>$user->port,
+                                            "method"=>($node->custom_method==1?$user->method:$node->method),
+                                            "obfs"=>str_replace("_compatible", "", (($node->custom_rss==1&&!($user->obfs=='plain'&&$user->protocol=='origin'))?$user->obfs:"plain")),
+                                            "obfsparam"=>(($node->custom_rss==1&&!($user->obfs=='plain'&&$user->protocol=='origin'))?$user->obfs_param:""),
+                                            "remarks_base64"=>base64_encode($node->name),
+                                            "password"=>$user->passwd,
+                                            "tcp_over_udp"=>false,
+                                            "udp_over_tcp"=>false,
+                                            "group"=>Config::get('appName'),
+                                            "protocol"=>str_replace("_compatible", "", (($node->custom_rss==1&&!($user->obfs=='plain'&&$user->protocol=='origin'))?$user->protocol:"origin")),
+                                            "obfs_udp"=>false,
+                                            "enable"=>true));
+            }
+            
+            if ($node->custom_rss == 1) {
+                foreach ($mu_nodes as $mu_node) {
+                    $mu_user = User::where('port', '=', $mu_node->server)->first();
+                    $mu_user->obfs_param = $user->getMuMd5();
+                    
+                    array_push($temparray, array("remarks"=>$node->name."- ".$mu_node->server." 端口单端口多用户",
+                                        "server"=>$node->server,
+                                        "server_port"=>$mu_user->port,
+                                        "method"=>$mu_user->method,
+                                        "group"=>Config::get('appName'),
+                                        "obfs"=>str_replace("_compatible", "", (($node->custom_rss==1&&!($mu_user->obfs=='plain'&&$mu_user->protocol=='origin'))?$mu_user->obfs:"plain")),
+                                        "obfsparam"=>(($node->custom_rss==1&&!($mu_user->obfs=='plain'&&$mu_user->protocol=='origin'))?$mu_user->obfs_param:""),
+                                        "remarks_base64"=>base64_encode($node->name."- ".$mu_node->server." 端口单端口多用户"),
+                                        "password"=>$mu_user->passwd,
+                                        "tcp_over_udp"=>false,
+                                        "udp_over_tcp"=>false,
+                                        "protocol"=>str_replace("_compatible", "", (($node->custom_rss==1&&!($mu_user->obfs=='plain'&&$mu_user->protocol=='origin'))?$mu_user->protocol:"origin")),
+                                        "obfs_udp"=>false,
+                                        "enable"=>true));
+                }
+            }
+        }
+        
+        $res['ret'] = 1;
+        $res['msg'] = "ok";
+        $res['data'] = $temparray;
+        return $this->echoJson($response, $res);
+    }
+
+    public function userInfo($request, $response, $args)
+    {
+        $id = $args['id'];
+        $accessToken = Helper::getTokenFromReq($request);
+        $storage = Factory::createTokenStorage();
+        $token = $storage->get($accessToken);
+        if ($id != $token->userId) {
+            $res['ret'] = 0;
+            $res['msg'] = "access denied";
+            return $this->echoJson($response, $res);
+        }
+        $user = User::find($token->userId);
+        $user->pass = null;
+        $data = $user;
+        $res['ret'] = 1;
+        $res['msg'] = "ok";
+        $res['data'] = $data;
+        return $this->echoJson($response, $res);
+    }
+}

+ 432 - 0
app/Controllers/AuthController.php

@@ -0,0 +1,432 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Models\InviteCode;
+use App\Services\Config;
+use App\Utils\Check;
+use App\Utils\Tools;
+use App\Utils\Radius;
+use voku\helper\AntiXSS;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+use App\Utils\Hash;
+use App\Utils\Da;
+use App\Services\Auth;
+use App\Services\Mail;
+use App\Models\User;
+use App\Models\LoginIp;
+use App\Models\EmailVerify;
+use App\Utils\Duoshuo;
+use App\Utils\GA;
+use App\Utils\Wecenter;
+use App\Utils\Geetest;
+use App\Utils\TelegramSessionManager;
+
+/**
+ *  AuthController
+ */
+
+class AuthController extends BaseController
+{
+    public function login()
+    {
+        $uid = time().rand(1, 10000) ;
+        if (Config::get('enable_geetest_login') == 'true') {
+            $GtSdk = Geetest::get($uid);
+        } else {
+            $GtSdk = null;
+        }
+
+        if (Config::get('enable_telegram') == 'true') {
+            $login_text = TelegramSessionManager::add_login_session();
+            $login = explode("|", $login_text);
+            $login_token = $login[0];
+            $login_number = $login[1];
+        } else {
+            $login_token = '';
+            $login_number = '';
+        }
+
+        return $this->view()->assign('geetest_html', $GtSdk)->assign('login_token', $login_token)->assign('login_number', $login_number)->assign('telegram_bot', Config::get('telegram_bot'))->display('auth/login.tpl');
+    }
+
+    public function loginHandle($request, $response, $args)
+    {
+        // $data = $request->post('sdf');
+        $email =  $request->getParam('email');
+        $email = strtolower($email);
+        $passwd = $request->getParam('passwd');
+        $code = $request->getParam('code');
+        $rememberMe = $request->getParam('remember_me');
+
+        if (Config::get('enable_geetest_login') == 'true') {
+            $ret = Geetest::verify($request->getParam('geetest_challenge'), $request->getParam('geetest_validate'), $request->getParam('geetest_seccode'));
+            if (!$ret) {
+                $res['ret'] = 0;
+                $res['msg'] = "系统无法接受您的验证结果,请刷新页面后重试。";
+                return $response->getBody()->write(json_encode($res));
+            }
+        }
+
+        // Handle Login
+        $user = User::where('email', '=', $email)->first();
+
+        if ($user == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "401 邮箱或者密码错误";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if (!Hash::checkPassword($user->pass, $passwd)) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "402 邮箱或者密码错误";
+
+
+            $loginip=new LoginIp();
+            $loginip->ip=$_SERVER["REMOTE_ADDR"];
+            $loginip->userid=$user->id;
+            $loginip->datetime=time();
+            $loginip->type=1;
+            $loginip->save();
+
+            return $response->getBody()->write(json_encode($rs));
+        }
+        // @todo
+        $time =  3600*24;
+        if ($rememberMe) {
+            $time = 3600*24*7;
+        }
+
+        if ($user->ga_enable==1) {
+            $ga = new GA();
+            $rcode = $ga->verifyCode($user->ga_token, $code);
+
+            if (!$rcode) {
+                $res['ret'] = 0;
+                $res['msg'] = "403 两步验证码错误,如果您是丢失了生成器或者错误地设置了这个选项,您可以尝试重置密码,即可取消这个选项。";
+                return $response->getBody()->write(json_encode($res));
+            }
+        }
+
+        Auth::login($user->id, $time);
+        $rs['ret'] = 1;
+        $rs['msg'] = "欢迎回来";
+
+        $loginip=new LoginIp();
+        $loginip->ip=$_SERVER["REMOTE_ADDR"];
+        $loginip->userid=$user->id;
+        $loginip->datetime=time();
+        $loginip->type=0;
+        $loginip->save();
+
+        Wecenter::add($user, $passwd);
+        Wecenter::Login($user, $passwd, $time);
+
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function qrcode_loginHandle($request, $response, $args)
+    {
+        // $data = $request->post('sdf');
+        $token =  $request->getParam('token');
+        $number =  $request->getParam('number');
+
+        $ret = TelegramSessionManager::step2_verify_login_session($token, $number);
+        if (!$ret) {
+            $res['ret'] = 0;
+            $res['msg'] = "此令牌无法被使用。";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+
+        // Handle Login
+        $user = User::where('id', '=', $ret)->first();
+        // @todo
+        $time =  3600*24;
+
+        Auth::login($user->id, $time);
+        $rs['ret'] = 1;
+        $rs['msg'] = "欢迎回来";
+
+        $loginip=new LoginIp();
+        $loginip->ip=$_SERVER["REMOTE_ADDR"];
+        $loginip->userid=$user->id;
+        $loginip->datetime=time();
+        $loginip->type=0;
+        $loginip->save();
+
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function register($request, $response, $next)
+    {
+        $ary = $request->getQueryParams();
+        $code = "";
+        if (isset($ary['code'])) {
+            $antiXss = new AntiXSS();
+            $code = $antiXss->xss_clean($ary['code']);
+        }
+
+        $uid = time().rand(1, 10000) ;
+
+        if (Config::get('enable_geetest_reg') == 'true') {
+            $GtSdk = Geetest::get($uid);
+        } else {
+            $GtSdk = null;
+        }
+
+
+
+        return $this->view()->assign('enable_invite_code', Config::get('enable_invite_code'))->assign('geetest_html', $GtSdk)->assign('enable_email_verify', Config::get('enable_email_verify'))->assign('code', $code)->display('auth/register.tpl');
+    }
+
+
+    public function sendVerify($request, $response, $next)
+    {
+        if (Config::get('enable_email_verify')=='true') {
+            $email = $request->getParam('email');
+
+            if ($email=="") {
+                $res['ret'] = 0;
+                $res['msg'] = "哦?你填了你的邮箱了吗?";
+                return $response->getBody()->write(json_encode($res));
+            }
+
+            // check email format
+            if (!Check::isEmailLegal($email)) {
+                $res['ret'] = 0;
+                $res['msg'] = "邮箱无效";
+                return $response->getBody()->write(json_encode($res));
+            }
+
+
+            $user = User::where('email', '=', $email)->first();
+            if ($user!=null) {
+                $res['ret'] = 0;
+                $res['msg'] = "此邮箱已经注册";
+                return $response->getBody()->write(json_encode($res));
+            }
+
+            $ipcount = EmailVerify::where('ip', '=', $_SERVER["REMOTE_ADDR"])->where('expire_in', '>', time())->count();
+            if ($ipcount>=(int)Config::get('email_verify_iplimit')) {
+                $res['ret'] = 0;
+                $res['msg'] = "此IP请求次数过多";
+                return $response->getBody()->write(json_encode($res));
+            }
+
+
+            $mailcount = EmailVerify::where('email', '=', $email)->where('expire_in', '>', time())->count();
+            if ($mailcount>=3) {
+                $res['ret'] = 0;
+                $res['msg'] = "此邮箱请求次数过多";
+                return $response->getBody()->write(json_encode($res));
+            }
+
+            $code = Tools::genRandomChar(6);
+
+            $ev = new EmailVerify();
+            $ev->expire_in = time() + Config::get('email_verify_ttl');
+            $ev->ip = $_SERVER["REMOTE_ADDR"];
+            $ev->email = $email;
+            $ev->code = $code;
+            $ev->save();
+
+            $subject = Config::get('appName')."- 验证邮件";
+
+            try {
+                Mail::send($email, $subject, 'auth/verify.tpl', [
+                    "code" => $code,"expire" => date("Y-m-d H:i:s", time() + Config::get('email_verify_ttl'))
+                ], [
+                    //BASE_PATH.'/public/assets/email/styles.css'
+                ]);
+            } catch (Exception $e) {
+                return false;
+            }
+
+            $res['ret'] = 1;
+            $res['msg'] = "验证码发送成功,请查收邮件。(邮件可能位于垃圾箱)";
+            return $response->getBody()->write(json_encode($res));
+        }
+    }
+
+    public function registerHandle($request, $response, $next)
+    {
+        $name =  $request->getParam('name');
+        $email =  $request->getParam('email');
+        $email = strtolower($email);
+        $passwd = $request->getParam('passwd');
+        $repasswd = $request->getParam('repasswd');
+        $code = $request->getParam('code');
+        $imtype = $request->getParam('imtype');
+        $emailcode = $request->getParam('emailcode');
+        $wechat = $request->getParam('wechat');
+        // check code
+
+        if (Config::get('enable_geetest_reg') == 'true') {
+            $ret = Geetest::verify($request->getParam('geetest_challenge'), $request->getParam('geetest_validate'), $request->getParam('geetest_seccode'));
+            if (!$ret) {
+                $res['ret'] = 0;
+                $res['msg'] = "系统无法接受您的验证结果,请刷新页面后重试。";
+                return $response->getBody()->write(json_encode($res));
+            }
+        }
+
+        if (Config::get('enable_invite_code')=='true') {
+            $c = InviteCode::where('code', $code)->first();
+            if ($c == null) {
+                $res['ret'] = 0;
+                $res['msg'] = "邀请码无效";
+                return $response->getBody()->write(json_encode($res));
+            }
+        }
+
+        // check email format
+        if (!Check::isEmailLegal($email)) {
+            $res['ret'] = 0;
+            $res['msg'] = "邮箱无效";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (Config::get('enable_email_verify')=='true') {
+            $mailcount = EmailVerify::where('email', '=', $email)->where('code', '=', $emailcode)->where('expire_in', '>', time())->first();
+            if ($mailcount == null) {
+                $res['ret'] = 0;
+                $res['msg'] = "您的邮箱验证码不正确";
+                return $response->getBody()->write(json_encode($res));
+            }
+            EmailVerify::where('email', '=', $email)->delete();
+        }
+
+        // check pwd length
+        if (strlen($passwd)<8) {
+            $res['ret'] = 0;
+            $res['msg'] = "密码太短";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        // check pwd re
+        if ($passwd != $repasswd) {
+            $res['ret'] = 0;
+            $res['msg'] = "两次密码输入不符";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        // check email
+        $user = User::where('email', $email)->first();
+        if ($user != null) {
+            $res['ret'] = 0;
+            $res['msg'] = "邮箱已经被注册了";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if ($imtype==""||$wechat=="") {
+            $res['ret'] = 0;
+            $res['msg'] = "要填上你的联络方式哦";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user = User::where('im_value', $wechat)->where('im_type', $imtype)->first();
+        if ($user != null) {
+            $res['ret'] = 0;
+            $res['msg'] = "此联络方式已经被注册了";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        // do reg user
+        $user = new User();
+
+        $antiXss = new AntiXSS();
+
+
+        $user->user_name = $antiXss->xss_clean($name);
+        $user->email = $email;
+        $user->pass = Hash::passwordHash($passwd);
+        $user->passwd = Tools::genRandomChar(6);
+        $user->port = Tools::getAvPort();
+        $user->t = 0;
+        $user->u = 0;
+        $user->d = 0;
+        $user->method = Config::get('reg_method');
+        $user->protocol = Config::get('reg_protocol');
+        $user->protocol_param = Config::get('reg_protocol_param');
+        $user->obfs = Config::get('reg_obfs');
+        $user->obfs_param = Config::get('reg_obfs_param');
+        $user->forbidden_ip = Config::get('reg_forbidden_ip');
+        $user->forbidden_port = Config::get('reg_forbidden_port');
+        $user->im_type =  $imtype;
+        $user->im_value =  $antiXss->xss_clean($wechat);
+        $user->transfer_enable = Tools::toGB(Config::get('defaultTraffic'));
+        $user->invite_num = Config::get('inviteNum');
+        $user->auto_reset_day = Config::get('reg_auto_reset_day');
+        $user->auto_reset_bandwidth = Config::get('reg_auto_reset_bandwidth');
+        if (Config::get('enable_invite_code')=='true') {
+            $user->ref_by = $c->user_id;
+        } else {
+            $user->ref_by = 0;
+        }
+        $user->expire_in=date("Y-m-d H:i:s", time()+Config::get('user_expire_in_default')*86400);
+        $user->reg_date=date("Y-m-d H:i:s");
+        $user->reg_ip=$_SERVER["REMOTE_ADDR"];
+        $user->money=0;
+        $user->class=0;
+        $user->plan='A';
+        $user->node_speedlimit=0;
+        $user->theme=Config::get('theme');
+
+        $group=Config::get('ramdom_group');
+        $Garray=explode(",", $group);
+
+        $user->node_group=$Garray[rand(0, count($group)-1)];
+        $user->node_group=$Garray[rand(0, count($Garray)-1)];
+
+        $ga = new GA();
+        $secret = $ga->createSecret();
+
+        $user->ga_token=$secret;
+        $user->ga_enable=0;
+
+
+        if ($user->save()) {
+            $res['ret'] = 1;
+            $res['msg'] = "注册成功,正在跳转到用户中心....";
+
+            Duoshuo::add($user);
+
+
+            Radius::Add($user, $user->passwd);
+
+            if (Config::get('enable_invite_code')=='true') {
+                $c->delete();
+            }
+
+            return $response->getBody()->write(json_encode($res));
+        }
+        $res['ret'] = 0;
+        $res['msg'] = "未知错误";
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function logout($request, $response, $next)
+    {
+        Auth::logout();
+        $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login');
+        return $newResponse;
+    }
+
+    public function qrcode_check($request, $response, $args)
+    {
+        $token = $request->getQueryParams()["token"];
+        $number = $request->getQueryParams()["number"];
+
+        if (Config::get('enable_telegram') == 'true') {
+            $ret = TelegramSessionManager::check_login_session($token, $number);
+            $res['ret'] = $ret;
+            return $response->getBody()->write(json_encode($res));
+        } else {
+            $res['ret'] = 0;
+            return $response->getBody()->write(json_encode($res));
+        }
+    }
+}

+ 42 - 0
app/Controllers/BaseController.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Services\Auth;
+use App\Services\View;
+
+/**
+ * BaseController
+ */
+
+class BaseController
+{
+    public $view;
+
+    public $smarty;
+    
+    public function construct__()
+    {
+    }
+
+    public function smarty()
+    {
+        $this->smarty = View::getSmarty();
+        return $this->smarty;
+    }
+
+    public function view()
+    {
+        return $this->smarty();
+    }
+
+    /**
+     * @param $response
+     * @param $res
+     * @return mixed
+     */
+    public function echoJson($response, $res)
+    {
+        return $response->getBody()->write(json_encode($res));
+    }
+}

+ 112 - 0
app/Controllers/HomeController.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Models\InviteCode;
+use App\Models\User;
+use App\Models\Code;
+use App\Models\Payback;
+use App\Models\Paylist;
+use App\Services\Auth;
+use App\Services\Config;
+use App\Utils\Tools;
+use App\Utils\Telegram;
+use App\Utils\Tuling;
+use App\Utils\TelegramSessionManager;
+use App\Utils\QRcode;
+use App\Utils\Pay;
+use App\Utils\TelegramProcess;
+use App\Utils\Spay_tool;
+
+/**
+ *  HomeController
+ */
+class HomeController extends BaseController
+{
+    public function index()
+    {
+        return $this->view()->display('index.tpl');
+    }
+
+    public function code()
+    {
+        $codes = InviteCode::where('user_id', '=', '0')->take(10)->get();
+        return $this->view()->assign('codes', $codes)->display('code.tpl');
+    }
+
+    public function down()
+    {
+    }
+
+    public function tos()
+    {
+        return $this->view()->display('tos.tpl');
+    }
+    
+    public function staff()
+    {
+        return $this->view()->display('staff.tpl');
+    }
+    
+    public function telegram($request, $response, $args)
+    {
+        $token = "";
+        if (isset($request->getQueryParams()["token"])) {
+            $token = $request->getQueryParams()["token"];
+        }
+        
+        if ($token == Config::get('telegram_request_token')) {
+            TelegramProcess::process();
+        } else {
+            echo("不正确请求!");
+        }
+    }
+    
+    public function page404($request, $response, $args)
+    {
+        $pics=scandir(BASE_PATH."/public/theme/".(Auth::getUser()->isLogin==false?Config::get("theme"):Auth::getUser()->theme)."/images/error/404/");
+        
+        if (count($pics)>2) {
+            $pic=$pics[rand(2, count($pics)-1)];
+        } else {
+            $pic="4041.png";
+        }
+        
+        $newResponse = $response->withStatus(404);
+        $newResponse->getBody()->write($this->view()->assign("pic", "/theme/".(Auth::getUser()->isLogin==false?Config::get("theme"):Auth::getUser()->theme)."/images/error/404/".$pic)->display('404.tpl'));
+        return $newResponse;
+    }
+    
+    public function page405($request, $response, $args)
+    {
+        $pics=scandir(BASE_PATH."/public/theme/".(Auth::getUser()->isLogin==false?Config::get("theme"):Auth::getUser()->theme)."/images/error/405/");
+        if (count($pics)>2) {
+            $pic=$pics[rand(2, count($pics)-1)];
+        } else {
+            $pic="4051.png";
+        }
+        
+        $newResponse = $response->withStatus(405);
+        $newResponse->getBody()->write($this->view()->assign("pic", "/theme/".(Auth::getUser()->isLogin==false?Config::get("theme"):Auth::getUser()->theme)."/images/error/405/".$pic)->display('405.tpl'));
+        return $newResponse;
+    }
+    
+    public function page500($request, $response, $args)
+    {
+        $pics=scandir(BASE_PATH."/public/theme/".(Auth::getUser()->isLogin==false?Config::get("theme"):Auth::getUser()->theme)."/images/error/500/");
+        if (count($pics)>2) {
+            $pic=$pics[rand(2, count($pics)-1)];
+        } else {
+            $pic="5001.png";
+        }
+        
+        $newResponse = $response->withStatus(500);
+        $newResponse->getBody()->write($this->view()->assign("pic", "/theme/".(Auth::getUser()->isLogin==false?Config::get("theme"):Auth::getUser()->theme)."/images/error/500/".$pic)->display('500.tpl'));
+        return $newResponse;
+    }
+    
+    public function pay_callback($request, $response, $args)
+    {
+        Pay::callback($request);
+    }
+}

+ 1758 - 0
app/Controllers/LinkController.php

@@ -0,0 +1,1758 @@
+<?php
+
+//Thanks to http://blog.csdn.net/jollyjumper/article/details/9823047
+
+namespace App\Controllers;
+
+use App\Models\Link;
+use App\Models\User;
+use App\Models\Node;
+use App\Models\Relay;
+use App\Models\Smartline;
+use App\Utils\Tools;
+use App\Utils\URL;
+use App\Services\Config;
+
+/**
+ *  HomeController
+ */
+class LinkController extends BaseController
+{
+    public function __construct()
+    {
+    }
+
+    public static function GenerateRandomLink()
+    {
+        $i =0;
+        for ($i = 0; $i < 10; $i++) {
+            $token = Tools::genRandomChar(16);
+            $Elink = Link::where("token", "=", $token)->first();
+            if ($Elink == null) {
+                return $token;
+            }
+        }
+
+        return "couldn't alloc token";
+    }
+
+    public static function GenerateCode($type, $address, $port, $ios, $userid)
+    {
+        $Elink = Link::where("type", "=", $type)->where("address", "=", $address)->where("port", "=", $port)->where("ios", "=", $ios)->where("userid", "=", $userid)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = $type;
+        $NLink->address = $address;
+        $NLink->port = $port;
+        $NLink->ios = $ios;
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+
+
+
+
+    public static function GenerateApnCode($isp, $address, $port, $userid)
+    {
+        $Elink = Link::where("type", "=", 6)->where("address", "=", $address)->where("port", "=", $port)->where("userid", "=", $userid)->where("isp", "=", $isp)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = 6;
+        $NLink->address = $address;
+        $NLink->port = $port;
+        $NLink->ios = 1;
+        $NLink->isp = $isp;
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+
+    public static function GenerateSurgeCode($address, $port, $userid, $geo, $method)
+    {
+        $Elink = Link::where("type", "=", 0)->where("address", "=", $address)->where("port", "=", $port)->where("userid", "=", $userid)->where("geo", "=", $geo)->where("method", "=", $method)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = 0;
+        $NLink->address = $address;
+        $NLink->port = $port;
+        $NLink->ios = 1;
+        $NLink->geo = $geo;
+        $NLink->method = $method;
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+    public static function GenerateIosCode($address, $port, $userid, $geo, $method)
+    {
+        $Elink = Link::where("type", "=", -1)->where("address", "=", $address)->where("port", "=", $port)->where("userid", "=", $userid)->where("geo", "=", $geo)->where("method", "=", $method)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = -1;
+        $NLink->address = $address;
+        $NLink->port = $port;
+        $NLink->ios = 1;
+        $NLink->geo = $geo;
+        $NLink->method = $method;
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+    public static function GenerateAclCode($address, $port, $userid, $geo, $method)
+    {
+        $Elink = Link::where("type", "=", 9)->where("address", "=", $address)->where("port", "=", $port)->where("userid", "=", $userid)->where("geo", "=", $geo)->where("method", "=", $method)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = 9;
+        $NLink->address = $address;
+        $NLink->port = $port;
+        $NLink->ios = 0;
+        $NLink->geo = $geo;
+        $NLink->method = $method;
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+    public static function GenerateRouterCode($userid, $without_mu)
+    {
+        $Elink = Link::where("type", "=", 10)->where("userid", "=", $userid)->where("geo", $without_mu)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = 10;
+        $NLink->address = "";
+        $NLink->port = 0;
+        $NLink->ios = 0;
+        $NLink->geo = $without_mu;
+        $NLink->method = "";
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+    public static function GenerateSSRSubCode($userid, $without_mu)
+    {
+        $Elink = Link::where("type", "=", 11)->where("userid", "=", $userid)->where("geo", $without_mu)->first();
+        if ($Elink != null) {
+            return $Elink->token;
+        }
+        $NLink = new Link();
+        $NLink->type = 11;
+        $NLink->address = "";
+        $NLink->port = 0;
+        $NLink->ios = 0;
+        $NLink->geo = $without_mu;
+        $NLink->method = "";
+        $NLink->userid = $userid;
+        $NLink->token = LinkController::GenerateRandomLink();
+        $NLink->save();
+
+        return $NLink->token;
+    }
+
+    public static function GetContent($request, $response, $args)
+    {
+        $token = $args['token'];
+
+        //$builder->getPhrase();
+        $Elink = Link::where("token", "=", $token)->first();
+        if ($Elink == null) {
+            return null;
+        }
+
+        switch ($Elink->type) {
+            case -1:
+                $user=User::where("id", $Elink->userid)->first();
+                if ($user == null) {
+                    return null;
+                }
+
+                $is_ss = 1;
+                if (isset($request->getQueryParams()["is_ss"])) {
+                    $is_ss = $request->getQueryParams()["is_ss"];
+                }
+
+                $is_mu = 0;
+                if (isset($request->getQueryParams()["is_mu"])) {
+                    $is_mu = $request->getQueryParams()["is_mu"];
+                }
+
+                $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename=allinone.conf');//->getBody()->write($builder->output());
+                $newResponse->getBody()->write(LinkController::GetIosConf($user, $is_mu, $is_ss));
+                return $newResponse;
+            case 3:
+                $type = "PROXY";
+                break;
+            case 7:
+                $type = "PROXY";
+                break;
+            case 6:
+                $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename='.$token.'.mobileconfig');//->getBody()->write($builder->output());
+                $newResponse->getBody()->write(LinkController::GetApn($Elink->isp, $Elink->address, $Elink->port, User::where("id", "=", $Elink->userid)->first()->pac));
+                return $newResponse;
+            case 0:
+                if ($Elink->geo==0) {
+                    $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename='.$token.'.conf');//->getBody()->write($builder->output());
+                    $newResponse->getBody()->write(LinkController::GetSurge(User::where("id", "=", $Elink->userid)->first()->passwd, $Elink->method, $Elink->address, $Elink->port, User::where("id", "=", $Elink->userid)->first()->pac));
+                    return $newResponse;
+                } else {
+                    $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename='.$token.'.conf');//->getBody()->write($builder->output());
+                    $newResponse->getBody()->write(LinkController::GetSurgeGeo(User::where("id", "=", $Elink->userid)->first()->passwd, $Elink->method, $Elink->address, $Elink->port));
+                    return $newResponse;
+                }
+            case 8:
+                if ($Elink->ios==0) {
+                    $type = "SOCKS5";
+                } else {
+                    $type = "SOCKS";
+                }
+                break;
+            case 9:
+                $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename='.$token.'.acl');//->getBody()->write($builder->output());
+                $newResponse->getBody()->write(LinkController::GetAcl(User::where("id", "=", $Elink->userid)->first()));
+                return $newResponse;
+            case 10:
+                $user=User::where("id", $Elink->userid)->first();
+                if ($user == null) {
+                    return null;
+                }
+
+                $is_ss = 0;
+                if (isset($request->getQueryParams()["is_ss"])) {
+                    $is_ss = $request->getQueryParams()["is_ss"];
+                }
+
+                $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename='.$token.'.sh');//->getBody()->write($builder->output());
+                $newResponse->getBody()->write(LinkController::GetRouter(User::where("id", "=", $Elink->userid)->first(), $Elink->geo, $is_ss));
+                return $newResponse;
+            case 11:
+                $user=User::where("id", $Elink->userid)->first();
+                if ($user == null) {
+                    return null;
+                }
+
+                $max = 0;
+                if (isset($request->getQueryParams()["max"])) {
+                    $max = (int)$request->getQueryParams()["max"];
+                }
+
+                $mu = 0;
+                if (isset($request->getQueryParams()["mu"])) {
+                    $mu = (int)$request->getQueryParams()["mu"];
+                }
+
+                $newResponse = $response->withHeader('Content-type', ' application/octet-stream; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename='.$token.'.txt');
+                $newResponse->getBody()->write(LinkController::GetSSRSub(User::where("id", "=", $Elink->userid)->first(), $mu, $max));
+                return $newResponse;
+            default:
+                break;
+        }
+        $newResponse = $response->withHeader('Content-type', ' application/x-ns-proxy-autoconfig; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate');//->getBody()->write($builder->output());
+        $newResponse->getBody()->write(LinkController::GetPac($type, $Elink->address, $Elink->port, User::where("id", "=", $Elink->userid)->first()->pac));
+        return $newResponse;
+    }
+
+
+    public static function GetGfwlistJs($request, $response, $args)
+    {
+        $newResponse = $response->withHeader('Content-type', ' application/x-ns-proxy-autoconfig; charset=utf-8')->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')->withHeader('Content-Disposition', ' attachment; filename=gfwlist.js');
+        ;//->getBody()->write($builder->output());
+        $newResponse->getBody()->write(LinkController::GetMacPac());
+        return $newResponse;
+    }
+
+    public static function GetPcConf($user, $is_mu = 0, $is_ss = 0)
+    {
+        $string='
+	{
+	"index" : 0,
+	"random" : false,
+	"sysProxyMode" : 0,
+	"shareOverLan" : false,
+	"bypassWhiteList" : false,
+	"localPort" : 1080,
+	"localAuthPassword" : "'.Tools::genRandomChar(26).'",
+	"dns_server" : "",
+	"reconnectTimes" : 4,
+	"randomAlgorithm" : 0,
+	"TTL" : 60,
+	"connect_timeout" : 5,
+	"proxyRuleMode" : 1,
+	"proxyEnable" : false,
+	"pacDirectGoProxy" : false,
+	"proxyType" : 0,
+	"proxyHost" : "",
+	"proxyPort" : 0,
+	"proxyAuthUser" : "",
+	"proxyAuthPass" : "",
+	"proxyUserAgent" : "",
+	"authUser" : "",
+	"authPass" : "",
+	"autoBan" : false,
+	"sameHostForSameTarget" : true,
+	"keepVisitTime" : 180,
+	"isHideTips" : true,
+	"token" : {
+
+	},
+	"portMap" : {
+
+	}
+	}
+		';
+
+
+        $json=json_decode($string, true);
+        $temparray=array();
+
+        $items = URL::getAllItems($user, $is_mu, $is_ss);
+        foreach($items as $item) {
+            array_push($temparray, array("remarks"=>$item['remark'],
+                                        "server"=>$item['address'],
+                                        "server_port"=>$item['port'],
+                                        "method"=>$item['method'],
+                                        "obfs"=>$item['obfs'],
+                                        "obfsparam"=>$item['obfs_param'],
+                                        "remarks_base64"=>base64_encode($item['remark']),
+                                        "password"=>$item['passwd'],
+                                        "tcp_over_udp"=>false,
+                                        "udp_over_tcp"=>false,
+                                        "group"=>$item['group'],
+                                        "protocol"=>$item['protocol'],
+                                        "protoparam"=>$item['protocol_param'],
+                                        "obfs_udp"=>false,
+                                        "enable"=>true));
+        }
+
+        $json["configs"]=$temparray;
+        return json_encode($json, JSON_PRETTY_PRINT);
+    }
+
+
+    public static function GetIosConf($user, $is_mu = 0, $is_ss = 0)
+    {
+        $proxy_name="";
+        $proxy_group="";
+
+        $items = URL::getAllItems($user, $is_mu, $is_ss);
+        foreach($items as $item) {
+            $proxy_group .= $item['remark'].' = custom,'.$item['address'].','.$item['port'].','.$item['method'].','.$item['passwd'].','.Config::get('baseUrl').'/downloads/SSEncrypt.module'.URL::getSurgeObfs($item)."\n";
+            $proxy_name .= ",".$item['remark'];
+        }
+
+        return '
+[General]
+
+skip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, localhost, *.local
+
+bypass-tun = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
+
+dns-server = 119.29.29.29, 223.5.5.5, 114.114.114.114
+loglevel = notify
+
+
+
+[Proxy]
+DIRECT = direct
+'.$proxy_group.'
+
+[Proxy Group]
+Proxy = select,DIRECT'.$proxy_name.'
+
+
+[Rule]
+
+DOMAIN-KEYWORD,adsmogo,REJECT
+
+DOMAIN-SUFFIX,acs86.com,REJECT
+
+DOMAIN-SUFFIX,adcome.cn,REJECT
+
+DOMAIN-SUFFIX,adinfuse.com,REJECT
+
+DOMAIN-SUFFIX,admaster.com.cn,REJECT
+
+DOMAIN-SUFFIX,admob.com,REJECT
+
+DOMAIN-SUFFIX,adsage.cn,REJECT
+
+DOMAIN-SUFFIX,adsage.com,REJECT
+
+DOMAIN-SUFFIX,adsmogo.org,REJECT
+
+DOMAIN-SUFFIX,ads.mobclix.com,REJECT
+
+DOMAIN-SUFFIX,adview.cn,REJECT
+
+DOMAIN-SUFFIX,adwhirl.com,REJECT
+
+DOMAIN-SUFFIX,adwo.com,REJECT
+
+DOMAIN-SUFFIX,appads.com,REJECT
+
+DOMAIN-SUFFIX,domob.cn,REJECT
+
+DOMAIN-SUFFIX,domob.com.cn,REJECT
+
+DOMAIN-SUFFIX,domob.org,REJECT
+
+DOMAIN-SUFFIX,doubleclick.net,REJECT
+
+DOMAIN-SUFFIX,duomeng.cn,REJECT
+
+DOMAIN-SUFFIX,duomeng.net,REJECT
+
+DOMAIN-SUFFIX,duomeng.org,REJECT
+
+DOMAIN-SUFFIX,googeadsserving.cn,REJECT
+
+DOMAIN-SUFFIX,guomob.com,REJECT
+
+DOMAIN-SUFFIX,immob.cn,REJECT
+
+DOMAIN-SUFFIX,inmobi.com,REJECT
+
+DOMAIN-SUFFIX,mobads.baidu.com,REJECT
+
+DOMAIN-SUFFIX,mobads-logs.baidu.com,REJECT
+
+DOMAIN-SUFFIX,smartadserver.com,REJECT
+
+DOMAIN-SUFFIX,tapjoyads.com,REJECT
+
+DOMAIN-SUFFIX,umeng.co,REJECT
+
+DOMAIN-SUFFIX,umeng.com,REJECT
+
+DOMAIN-SUFFIX,umtrack.com,REJECT
+
+DOMAIN-SUFFIX,uyunad.com,REJECT
+
+DOMAIN-SUFFIX,youmi.net,REJECT
+
+GEOIP,AD,Proxy
+GEOIP,AE,Proxy
+GEOIP,AF,Proxy
+GEOIP,AG,Proxy
+GEOIP,AI,Proxy
+GEOIP,AL,Proxy
+GEOIP,AM,Proxy
+GEOIP,AO,Proxy
+GEOIP,AQ,Proxy
+GEOIP,AR,Proxy
+GEOIP,AS,Proxy
+GEOIP,AS,Proxy
+GEOIP,AS,Proxy
+GEOIP,AS,Proxy
+GEOIP,AT,Proxy
+GEOIP,AU,Proxy
+GEOIP,AW,Proxy
+GEOIP,AX,Proxy
+GEOIP,AZ,Proxy
+GEOIP,BA,Proxy
+GEOIP,BD,Proxy
+GEOIP,BE,Proxy
+GEOIP,BF,Proxy
+GEOIP,BG,Proxy
+GEOIP,BH,Proxy
+GEOIP,BI,Proxy
+GEOIP,BJ,Proxy
+GEOIP,BL,Proxy
+GEOIP,BM,Proxy
+GEOIP,BN,Proxy
+GEOIP,BO,Proxy
+GEOIP,BQ,Proxy
+GEOIP,BR,Proxy
+GEOIP,BS,Proxy
+GEOIP,BT,Proxy
+GEOIP,BW,Proxy
+GEOIP,BY,Proxy
+GEOIP,BZ,Proxy
+GEOIP,CA,Proxy
+GEOIP,CC,Proxy
+GEOIP,CD,Proxy
+GEOIP,CF,Proxy
+GEOIP,CG,Proxy
+GEOIP,CH,Proxy
+GEOIP,CI,Proxy
+GEOIP,CK,Proxy
+GEOIP,CL,Proxy
+GEOIP,CM,Proxy
+GEOIP,CO,Proxy
+GEOIP,CR,Proxy
+GEOIP,CU,Proxy
+GEOIP,CV,Proxy
+GEOIP,CW,Proxy
+GEOIP,CX,Proxy
+GEOIP,CY,Proxy
+GEOIP,CZ,Proxy
+GEOIP,DE,Proxy
+GEOIP,DJ,Proxy
+GEOIP,DK,Proxy
+GEOIP,DM,Proxy
+GEOIP,DO,Proxy
+GEOIP,DZ,Proxy
+GEOIP,EC,Proxy
+GEOIP,EE,Proxy
+GEOIP,EG,Proxy
+GEOIP,EG,Proxy
+GEOIP,EH,Proxy
+GEOIP,ER,Proxy
+GEOIP,ES,Proxy
+GEOIP,ET,Proxy
+GEOIP,FI,Proxy
+GEOIP,FJ,Proxy
+GEOIP,FK,Proxy
+GEOIP,FM,Proxy
+GEOIP,FO,Proxy
+GEOIP,FR,Proxy
+GEOIP,GA,Proxy
+GEOIP,GB,Proxy
+GEOIP,GD,Proxy
+GEOIP,GE,Proxy
+GEOIP,GF,Proxy
+GEOIP,GG,Proxy
+GEOIP,GH,Proxy
+GEOIP,GI,Proxy
+GEOIP,GL,Proxy
+GEOIP,GM,Proxy
+GEOIP,GN,Proxy
+GEOIP,GP,Proxy
+GEOIP,GQ,Proxy
+GEOIP,GR,Proxy
+GEOIP,GS,Proxy
+GEOIP,GT,Proxy
+GEOIP,GU,Proxy
+GEOIP,GW,Proxy
+GEOIP,GY,Proxy
+GEOIP,HK,Proxy
+GEOIP,HM,Proxy
+GEOIP,HN,Proxy
+GEOIP,HR,Proxy
+GEOIP,HT,Proxy
+GEOIP,HU,Proxy
+GEOIP,ID,Proxy
+GEOIP,IE,Proxy
+GEOIP,IL,Proxy
+GEOIP,IM,Proxy
+GEOIP,IN,Proxy
+GEOIP,IO,Proxy
+GEOIP,IQ,Proxy
+GEOIP,IR,Proxy
+GEOIP,IS,Proxy
+GEOIP,IT,Proxy
+GEOIP,JE,Proxy
+GEOIP,JM,Proxy
+GEOIP,JO,Proxy
+GEOIP,JP,Proxy
+GEOIP,KE,Proxy
+GEOIP,KG,Proxy
+GEOIP,KH,Proxy
+GEOIP,KI,Proxy
+GEOIP,KM,Proxy
+GEOIP,KN,Proxy
+GEOIP,KP,Proxy
+GEOIP,KR,Proxy
+GEOIP,KW,Proxy
+GEOIP,KY,Proxy
+GEOIP,KZ,Proxy
+GEOIP,LA,Proxy
+GEOIP,LB,Proxy
+GEOIP,LC,Proxy
+GEOIP,LI,Proxy
+GEOIP,LK,Proxy
+GEOIP,LR,Proxy
+GEOIP,LS,Proxy
+GEOIP,LT,Proxy
+GEOIP,LU,Proxy
+GEOIP,LV,Proxy
+GEOIP,LY,Proxy
+GEOIP,MA,Proxy
+GEOIP,MC,Proxy
+GEOIP,MD,Proxy
+GEOIP,ME,Proxy
+GEOIP,MF,Proxy
+GEOIP,MG,Proxy
+GEOIP,MH,Proxy
+GEOIP,MK,Proxy
+GEOIP,ML,Proxy
+GEOIP,MM,Proxy
+GEOIP,MN,Proxy
+GEOIP,MO,Proxy
+GEOIP,MP,Proxy
+GEOIP,MQ,Proxy
+GEOIP,MR,Proxy
+GEOIP,MS,Proxy
+GEOIP,MT,Proxy
+GEOIP,MU,Proxy
+GEOIP,MV,Proxy
+GEOIP,MW,Proxy
+GEOIP,MX,Proxy
+GEOIP,MY,Proxy
+GEOIP,MZ,Proxy
+GEOIP,NA,Proxy
+GEOIP,NC,Proxy
+GEOIP,NE,Proxy
+GEOIP,NF,Proxy
+GEOIP,NG,Proxy
+GEOIP,NI,Proxy
+GEOIP,NL,Proxy
+GEOIP,NO,Proxy
+GEOIP,NP,Proxy
+GEOIP,NR,Proxy
+GEOIP,NU,Proxy
+GEOIP,NZ,Proxy
+GEOIP,OM,Proxy
+GEOIP,PA,Proxy
+GEOIP,PE,Proxy
+GEOIP,PF,Proxy
+GEOIP,PG,Proxy
+GEOIP,PH,Proxy
+GEOIP,PK,Proxy
+GEOIP,PL,Proxy
+GEOIP,PM,Proxy
+GEOIP,PN,Proxy
+GEOIP,PR,Proxy
+GEOIP,PS,Proxy
+GEOIP,PT,Proxy
+GEOIP,PW,Proxy
+GEOIP,PY,Proxy
+GEOIP,QA,Proxy
+GEOIP,RE,Proxy
+GEOIP,RO,Proxy
+GEOIP,RS,Proxy
+GEOIP,RU,Proxy
+GEOIP,RW,Proxy
+GEOIP,SA,Proxy
+GEOIP,SB,Proxy
+GEOIP,SC,Proxy
+GEOIP,SD,Proxy
+GEOIP,SE,Proxy
+GEOIP,SG,Proxy
+GEOIP,SH,Proxy
+GEOIP,SI,Proxy
+GEOIP,SJ,Proxy
+GEOIP,SK,Proxy
+GEOIP,SL,Proxy
+GEOIP,SM,Proxy
+GEOIP,SN,Proxy
+GEOIP,SO,Proxy
+GEOIP,SR,Proxy
+GEOIP,SS,Proxy
+GEOIP,ST,Proxy
+GEOIP,SV,Proxy
+GEOIP,SX,Proxy
+GEOIP,SY,Proxy
+GEOIP,SZ,Proxy
+GEOIP,TC,Proxy
+GEOIP,TD,Proxy
+GEOIP,TF,Proxy
+GEOIP,TG,Proxy
+GEOIP,TH,Proxy
+GEOIP,TJ,Proxy
+GEOIP,TK,Proxy
+GEOIP,TL,Proxy
+GEOIP,TM,Proxy
+GEOIP,TN,Proxy
+GEOIP,TO,Proxy
+GEOIP,TR,Proxy
+GEOIP,TT,Proxy
+GEOIP,TV,Proxy
+GEOIP,TW,Proxy
+GEOIP,TZ,Proxy
+GEOIP,UA,Proxy
+GEOIP,UG,Proxy
+GEOIP,UM,Proxy
+GEOIP,US,Proxy
+GEOIP,UY,Proxy
+GEOIP,UZ,Proxy
+GEOIP,VA,Proxy
+GEOIP,VC,Proxy
+GEOIP,VE,Proxy
+GEOIP,VG,Proxy
+GEOIP,VI,Proxy
+GEOIP,VN,Proxy
+GEOIP,VU,Proxy
+GEOIP,WF,Proxy
+GEOIP,WS,Proxy
+GEOIP,YE,Proxy
+GEOIP,YT,Proxy
+GEOIP,ZA,Proxy
+GEOIP,ZM,Proxy
+GEOIP,ZW,Proxy
+IP-CIDR,91.108.4.0/22,Proxy,no-resolve
+
+IP-CIDR,91.108.56.0/22,Proxy,no-resolve
+
+IP-CIDR,109.239.140.0/24,Proxy,no-resolve
+
+IP-CIDR,149.154.160.0/20,Proxy,no-resolve
+
+IP-CIDR,10.0.0.0/8,DIRECT
+
+IP-CIDR,127.0.0.0/8,DIRECT
+
+IP-CIDR,172.16.0.0/12,DIRECT
+
+IP-CIDR,192.168.0.0/16,DIRECT
+
+GEOIP,CN,DIRECT
+
+FINAL,Proxy';
+    }
+
+    private static function GetSurge($passwd, $method, $server, $port, $defined)
+    {
+        $rulelist = base64_decode(file_get_contents("https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"))."\n".$defined;
+        $gfwlist = explode("\n", $rulelist);
+
+        $count = 0;
+        $pac_content = '';
+        $find_function_content = '
+[General]
+skip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, localhost, *.local
+bypass-tun = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
+dns-server = 119.29.29.29, 223.5.5.5, 114.114.114.114
+loglevel = notify
+
+[Proxy]
+Proxy = custom,'.$server.','.$port.','.$method.','.$passwd.','.Config::get('baseUrl').'/downloads/SSEncrypt.module
+
+[Rule]
+DOMAIN-KEYWORD,adsmogo,REJECT
+DOMAIN-SUFFIX,acs86.com,REJECT
+DOMAIN-SUFFIX,adcome.cn,REJECT
+DOMAIN-SUFFIX,adinfuse.com,REJECT
+DOMAIN-SUFFIX,admaster.com.cn,REJECT
+DOMAIN-SUFFIX,admob.com,REJECT
+DOMAIN-SUFFIX,adsage.cn,REJECT
+DOMAIN-SUFFIX,adsage.com,REJECT
+DOMAIN-SUFFIX,adsmogo.org,REJECT
+DOMAIN-SUFFIX,ads.mobclix.com,REJECT
+DOMAIN-SUFFIX,adview.cn,REJECT
+DOMAIN-SUFFIX,adwhirl.com,REJECT
+DOMAIN-SUFFIX,adwo.com,REJECT
+DOMAIN-SUFFIX,appads.com,REJECT
+DOMAIN-SUFFIX,domob.cn,REJECT
+DOMAIN-SUFFIX,domob.com.cn,REJECT
+DOMAIN-SUFFIX,domob.org,REJECT
+DOMAIN-SUFFIX,doubleclick.net,REJECT
+DOMAIN-SUFFIX,duomeng.cn,REJECT
+DOMAIN-SUFFIX,duomeng.net,REJECT
+DOMAIN-SUFFIX,duomeng.org,REJECT
+DOMAIN-SUFFIX,googeadsserving.cn,REJECT
+DOMAIN-SUFFIX,guomob.com,REJECT
+DOMAIN-SUFFIX,immob.cn,REJECT
+DOMAIN-SUFFIX,inmobi.com,REJECT
+DOMAIN-SUFFIX,mobads.baidu.com,REJECT
+DOMAIN-SUFFIX,mobads-logs.baidu.com,REJECT
+DOMAIN-SUFFIX,smartadserver.com,REJECT
+DOMAIN-SUFFIX,tapjoyads.com,REJECT
+DOMAIN-SUFFIX,umeng.co,REJECT
+DOMAIN-SUFFIX,umeng.com,REJECT
+DOMAIN-SUFFIX,umtrack.com,REJECT
+DOMAIN-SUFFIX,uyunad.com,REJECT
+DOMAIN-SUFFIX,youmi.net,REJECT'."\n";
+        $isget=array();
+        foreach ($gfwlist as $index=>$rule) {
+            if (empty($rule)) {
+                continue;
+            } elseif (substr($rule, 0, 1) == '!' || substr($rule, 0, 1) == '[') {
+                continue;
+            }
+
+            if (substr($rule, 0, 2) == '@@') {
+                // ||开头表示前面还有路径
+                if (substr($rule, 2, 2) =='||') {
+                    //$rule_reg = preg_match("/^((http|https):\/\/)?([^\/]+)/i",substr($rule, 2), $matches);
+                    $host = substr($rule, 4);
+                    //preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    $find_function_content.="DOMAIN,".$host.",DIRECT,force-remote-dns\n";
+                    continue;
+                // !开头相当于正则表达式^
+                } elseif (substr($rule, 2, 1) == '|') {
+                    preg_match("/(\d{1,3}\.){3}\d{1,3}/", substr($rule, 3), $matches);
+                    if (!isset($matches[0])) {
+                        continue;
+                    }
+
+                    $host = $matches[0];
+                    if ($host != "") {
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        $find_function_content.="IP-CIDR,".$host."/32,DIRECT,no-resolve \n";
+                        continue;
+                    } else {
+                        preg_match_all("~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i", substr($rule, 3), $matches);
+
+                        if (!isset($matches[4][0])) {
+                            continue;
+                        }
+
+                        $host = $matches[4][0];
+                        if ($host != "") {
+                            if (isset($isget[$host])) {
+                                continue;
+                            }
+                            $isget[$host]=1;
+                            $find_function_content.="DOMAIN-SUFFIX,".$host.",DIRECT,force-remote-dns\n";
+                            continue;
+                        }
+                    }
+                } elseif (substr($rule, 2, 1) == '.') {
+                    $host = substr($rule, 3);
+                    if ($host != "") {
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        $find_function_content.="DOMAIN-SUFFIX,".$host.",DIRECT,force-remote-dns \n";
+                        continue;
+                    }
+                }
+            }
+
+            // ||开头表示前面还有路径
+            if (substr($rule, 0, 2) =='||') {
+                //$rule_reg = preg_match("/^((http|https):\/\/)?([^\/]+)/i",substr($rule, 2), $matches);
+                $host = substr($rule, 2);
+                //preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
+
+                if (strpos($host, "*")!==false) {
+                    $host = substr($host, strpos($host, "*")+1);
+                    if (strpos($host, ".")!==false) {
+                        $host = substr($host, strpos($host, ".")+1);
+                    }
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    $find_function_content.="DOMAIN-KEYWORD,".$host.",Proxy,force-remote-dns\n";
+                    continue;
+                }
+
+                if (isset($isget[$host])) {
+                    continue;
+                }
+                $isget[$host]=1;
+                $find_function_content.="DOMAIN,".$host.",Proxy,force-remote-dns\n";
+            // !开头相当于正则表达式^
+            } elseif (substr($rule, 0, 1) == '|') {
+                preg_match("/(\d{1,3}\.){3}\d{1,3}/", substr($rule, 1), $matches);
+
+                if (!isset($matches[0])) {
+                    continue;
+                }
+
+                $host = $matches[0];
+                if ($host != "") {
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    $find_function_content.="IP-CIDR,".$host."/32,Proxy,no-resolve \n";
+                    continue;
+                } else {
+                    preg_match_all("~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i", substr($rule, 1), $matches);
+
+                    if (!isset($matches[4][0])) {
+                        continue;
+                    }
+
+                    $host = $matches[4][0];
+                    if (strpos($host, "*")!==false) {
+                        $host = substr($host, strpos($host, "*")+1);
+                        if (strpos($host, ".")!==false) {
+                            $host = substr($host, strpos($host, ".")+1);
+                        }
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        $find_function_content.="DOMAIN-KEYWORD,".$host.",Proxy,force-remote-dns\n";
+                        continue;
+                    }
+
+                    if ($host != "") {
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        $find_function_content.="DOMAIN-SUFFIX,".$host.",Proxy,force-remote-dns\n";
+                        continue;
+                    }
+                }
+            } else {
+                $host = substr($rule, 0);
+                if (strpos($host, "/")!==false) {
+                    $host = substr($host, 0, strpos($host, "/"));
+                }
+
+                if ($host != "") {
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    $find_function_content.="DOMAIN-KEYWORD,".$host.",PROXY,force-remote-dns \n";
+                    continue;
+                }
+            }
+
+
+            $count = $count + 1;
+        }
+        $find_function_content.='
+DOMAIN-KEYWORD,google,Proxy,force-remote-dns
+IP-CIDR,91.108.4.0/22,Proxy,no-resolve
+IP-CIDR,91.108.56.0/22,Proxy,no-resolve
+IP-CIDR,109.239.140.0/24,Proxy,no-resolve
+IP-CIDR,149.154.160.0/20,Proxy,no-resolve
+IP-CIDR,10.0.0.0/8,DIRECT
+IP-CIDR,127.0.0.0/8,DIRECT
+IP-CIDR,172.16.0.0/12,DIRECT
+IP-CIDR,192.168.0.0/16,DIRECT
+GEOIP,CN,DIRECT
+FINAL,DIRECT
+	  ';
+        $pac_content.=$find_function_content;
+        return $pac_content;
+    }
+
+
+    private static function GetSurgeGeo($passwd, $method, $server, $port)
+    {
+        return '
+[General]
+
+skip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, localhost, *.local
+
+bypass-tun = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
+
+dns-server = 119.29.29.29, 223.5.5.5, 114.114.114.114
+loglevel = notify
+
+
+
+[Proxy]
+
+Proxy = custom,'.$server.','.$port.','.$method.','.$passwd.','.Config::get('baseUrl').'/downloads/SSEncrypt.module
+
+
+
+[Rule]
+
+DOMAIN-KEYWORD,adsmogo,REJECT
+
+DOMAIN-SUFFIX,acs86.com,REJECT
+
+DOMAIN-SUFFIX,adcome.cn,REJECT
+
+DOMAIN-SUFFIX,adinfuse.com,REJECT
+
+DOMAIN-SUFFIX,admaster.com.cn,REJECT
+
+DOMAIN-SUFFIX,admob.com,REJECT
+
+DOMAIN-SUFFIX,adsage.cn,REJECT
+
+DOMAIN-SUFFIX,adsage.com,REJECT
+
+DOMAIN-SUFFIX,adsmogo.org,REJECT
+
+DOMAIN-SUFFIX,ads.mobclix.com,REJECT
+
+DOMAIN-SUFFIX,adview.cn,REJECT
+
+DOMAIN-SUFFIX,adwhirl.com,REJECT
+
+DOMAIN-SUFFIX,adwo.com,REJECT
+
+DOMAIN-SUFFIX,appads.com,REJECT
+
+DOMAIN-SUFFIX,domob.cn,REJECT
+
+DOMAIN-SUFFIX,domob.com.cn,REJECT
+
+DOMAIN-SUFFIX,domob.org,REJECT
+
+DOMAIN-SUFFIX,doubleclick.net,REJECT
+
+DOMAIN-SUFFIX,duomeng.cn,REJECT
+
+DOMAIN-SUFFIX,duomeng.net,REJECT
+
+DOMAIN-SUFFIX,duomeng.org,REJECT
+
+DOMAIN-SUFFIX,googeadsserving.cn,REJECT
+
+DOMAIN-SUFFIX,guomob.com,REJECT
+
+DOMAIN-SUFFIX,immob.cn,REJECT
+
+DOMAIN-SUFFIX,inmobi.com,REJECT
+
+DOMAIN-SUFFIX,mobads.baidu.com,REJECT
+
+DOMAIN-SUFFIX,mobads-logs.baidu.com,REJECT
+
+DOMAIN-SUFFIX,smartadserver.com,REJECT
+
+DOMAIN-SUFFIX,tapjoyads.com,REJECT
+
+DOMAIN-SUFFIX,umeng.co,REJECT
+
+DOMAIN-SUFFIX,umeng.com,REJECT
+
+DOMAIN-SUFFIX,umtrack.com,REJECT
+
+DOMAIN-SUFFIX,uyunad.com,REJECT
+
+DOMAIN-SUFFIX,youmi.net,REJECT
+
+GEOIP,AD,Proxy
+GEOIP,AE,Proxy
+GEOIP,AF,Proxy
+GEOIP,AG,Proxy
+GEOIP,AI,Proxy
+GEOIP,AL,Proxy
+GEOIP,AM,Proxy
+GEOIP,AO,Proxy
+GEOIP,AQ,Proxy
+GEOIP,AR,Proxy
+GEOIP,AS,Proxy
+GEOIP,AS,Proxy
+GEOIP,AS,Proxy
+GEOIP,AS,Proxy
+GEOIP,AT,Proxy
+GEOIP,AU,Proxy
+GEOIP,AW,Proxy
+GEOIP,AX,Proxy
+GEOIP,AZ,Proxy
+GEOIP,BA,Proxy
+GEOIP,BD,Proxy
+GEOIP,BE,Proxy
+GEOIP,BF,Proxy
+GEOIP,BG,Proxy
+GEOIP,BH,Proxy
+GEOIP,BI,Proxy
+GEOIP,BJ,Proxy
+GEOIP,BL,Proxy
+GEOIP,BM,Proxy
+GEOIP,BN,Proxy
+GEOIP,BO,Proxy
+GEOIP,BQ,Proxy
+GEOIP,BR,Proxy
+GEOIP,BS,Proxy
+GEOIP,BT,Proxy
+GEOIP,BW,Proxy
+GEOIP,BY,Proxy
+GEOIP,BZ,Proxy
+GEOIP,CA,Proxy
+GEOIP,CC,Proxy
+GEOIP,CD,Proxy
+GEOIP,CF,Proxy
+GEOIP,CG,Proxy
+GEOIP,CH,Proxy
+GEOIP,CI,Proxy
+GEOIP,CK,Proxy
+GEOIP,CL,Proxy
+GEOIP,CM,Proxy
+GEOIP,CO,Proxy
+GEOIP,CR,Proxy
+GEOIP,CU,Proxy
+GEOIP,CV,Proxy
+GEOIP,CW,Proxy
+GEOIP,CX,Proxy
+GEOIP,CY,Proxy
+GEOIP,CZ,Proxy
+GEOIP,DE,Proxy
+GEOIP,DJ,Proxy
+GEOIP,DK,Proxy
+GEOIP,DM,Proxy
+GEOIP,DO,Proxy
+GEOIP,DZ,Proxy
+GEOIP,EC,Proxy
+GEOIP,EE,Proxy
+GEOIP,EG,Proxy
+GEOIP,EG,Proxy
+GEOIP,EH,Proxy
+GEOIP,ER,Proxy
+GEOIP,ES,Proxy
+GEOIP,ET,Proxy
+GEOIP,FI,Proxy
+GEOIP,FJ,Proxy
+GEOIP,FK,Proxy
+GEOIP,FM,Proxy
+GEOIP,FO,Proxy
+GEOIP,FR,Proxy
+GEOIP,GA,Proxy
+GEOIP,GB,Proxy
+GEOIP,GD,Proxy
+GEOIP,GE,Proxy
+GEOIP,GF,Proxy
+GEOIP,GG,Proxy
+GEOIP,GH,Proxy
+GEOIP,GI,Proxy
+GEOIP,GL,Proxy
+GEOIP,GM,Proxy
+GEOIP,GN,Proxy
+GEOIP,GP,Proxy
+GEOIP,GQ,Proxy
+GEOIP,GR,Proxy
+GEOIP,GS,Proxy
+GEOIP,GT,Proxy
+GEOIP,GU,Proxy
+GEOIP,GW,Proxy
+GEOIP,GY,Proxy
+GEOIP,HK,Proxy
+GEOIP,HM,Proxy
+GEOIP,HN,Proxy
+GEOIP,HR,Proxy
+GEOIP,HT,Proxy
+GEOIP,HU,Proxy
+GEOIP,ID,Proxy
+GEOIP,IE,Proxy
+GEOIP,IL,Proxy
+GEOIP,IM,Proxy
+GEOIP,IN,Proxy
+GEOIP,IO,Proxy
+GEOIP,IQ,Proxy
+GEOIP,IR,Proxy
+GEOIP,IS,Proxy
+GEOIP,IT,Proxy
+GEOIP,JE,Proxy
+GEOIP,JM,Proxy
+GEOIP,JO,Proxy
+GEOIP,JP,Proxy
+GEOIP,KE,Proxy
+GEOIP,KG,Proxy
+GEOIP,KH,Proxy
+GEOIP,KI,Proxy
+GEOIP,KM,Proxy
+GEOIP,KN,Proxy
+GEOIP,KP,Proxy
+GEOIP,KR,Proxy
+GEOIP,KW,Proxy
+GEOIP,KY,Proxy
+GEOIP,KZ,Proxy
+GEOIP,LA,Proxy
+GEOIP,LB,Proxy
+GEOIP,LC,Proxy
+GEOIP,LI,Proxy
+GEOIP,LK,Proxy
+GEOIP,LR,Proxy
+GEOIP,LS,Proxy
+GEOIP,LT,Proxy
+GEOIP,LU,Proxy
+GEOIP,LV,Proxy
+GEOIP,LY,Proxy
+GEOIP,MA,Proxy
+GEOIP,MC,Proxy
+GEOIP,MD,Proxy
+GEOIP,ME,Proxy
+GEOIP,MF,Proxy
+GEOIP,MG,Proxy
+GEOIP,MH,Proxy
+GEOIP,MK,Proxy
+GEOIP,ML,Proxy
+GEOIP,MM,Proxy
+GEOIP,MN,Proxy
+GEOIP,MO,Proxy
+GEOIP,MP,Proxy
+GEOIP,MQ,Proxy
+GEOIP,MR,Proxy
+GEOIP,MS,Proxy
+GEOIP,MT,Proxy
+GEOIP,MU,Proxy
+GEOIP,MV,Proxy
+GEOIP,MW,Proxy
+GEOIP,MX,Proxy
+GEOIP,MY,Proxy
+GEOIP,MZ,Proxy
+GEOIP,NA,Proxy
+GEOIP,NC,Proxy
+GEOIP,NE,Proxy
+GEOIP,NF,Proxy
+GEOIP,NG,Proxy
+GEOIP,NI,Proxy
+GEOIP,NL,Proxy
+GEOIP,NO,Proxy
+GEOIP,NP,Proxy
+GEOIP,NR,Proxy
+GEOIP,NU,Proxy
+GEOIP,NZ,Proxy
+GEOIP,OM,Proxy
+GEOIP,PA,Proxy
+GEOIP,PE,Proxy
+GEOIP,PF,Proxy
+GEOIP,PG,Proxy
+GEOIP,PH,Proxy
+GEOIP,PK,Proxy
+GEOIP,PL,Proxy
+GEOIP,PM,Proxy
+GEOIP,PN,Proxy
+GEOIP,PR,Proxy
+GEOIP,PS,Proxy
+GEOIP,PT,Proxy
+GEOIP,PW,Proxy
+GEOIP,PY,Proxy
+GEOIP,QA,Proxy
+GEOIP,RE,Proxy
+GEOIP,RO,Proxy
+GEOIP,RS,Proxy
+GEOIP,RU,Proxy
+GEOIP,RW,Proxy
+GEOIP,SA,Proxy
+GEOIP,SB,Proxy
+GEOIP,SC,Proxy
+GEOIP,SD,Proxy
+GEOIP,SE,Proxy
+GEOIP,SG,Proxy
+GEOIP,SH,Proxy
+GEOIP,SI,Proxy
+GEOIP,SJ,Proxy
+GEOIP,SK,Proxy
+GEOIP,SL,Proxy
+GEOIP,SM,Proxy
+GEOIP,SN,Proxy
+GEOIP,SO,Proxy
+GEOIP,SR,Proxy
+GEOIP,SS,Proxy
+GEOIP,ST,Proxy
+GEOIP,SV,Proxy
+GEOIP,SX,Proxy
+GEOIP,SY,Proxy
+GEOIP,SZ,Proxy
+GEOIP,TC,Proxy
+GEOIP,TD,Proxy
+GEOIP,TF,Proxy
+GEOIP,TG,Proxy
+GEOIP,TH,Proxy
+GEOIP,TJ,Proxy
+GEOIP,TK,Proxy
+GEOIP,TL,Proxy
+GEOIP,TM,Proxy
+GEOIP,TN,Proxy
+GEOIP,TO,Proxy
+GEOIP,TR,Proxy
+GEOIP,TT,Proxy
+GEOIP,TV,Proxy
+GEOIP,TW,Proxy
+GEOIP,TZ,Proxy
+GEOIP,UA,Proxy
+GEOIP,UG,Proxy
+GEOIP,UM,Proxy
+GEOIP,US,Proxy
+GEOIP,UY,Proxy
+GEOIP,UZ,Proxy
+GEOIP,VA,Proxy
+GEOIP,VC,Proxy
+GEOIP,VE,Proxy
+GEOIP,VG,Proxy
+GEOIP,VI,Proxy
+GEOIP,VN,Proxy
+GEOIP,VU,Proxy
+GEOIP,WF,Proxy
+GEOIP,WS,Proxy
+GEOIP,YE,Proxy
+GEOIP,YT,Proxy
+GEOIP,ZA,Proxy
+GEOIP,ZM,Proxy
+GEOIP,ZW,Proxy
+IP-CIDR,91.108.4.0/22,Proxy,no-resolve
+
+IP-CIDR,91.108.56.0/22,Proxy,no-resolve
+
+IP-CIDR,109.239.140.0/24,Proxy,no-resolve
+
+IP-CIDR,149.154.160.0/20,Proxy,no-resolve
+
+IP-CIDR,10.0.0.0/8,DIRECT
+
+IP-CIDR,127.0.0.0/8,DIRECT
+
+IP-CIDR,172.16.0.0/12,DIRECT
+
+IP-CIDR,192.168.0.0/16,DIRECT
+
+GEOIP,CN,DIRECT
+
+FINAL,Proxy';
+    }
+
+    private static function GetApn($apn, $server, $port)
+    {
+        return '
+		<?xml version="1.0" encoding="UTF-8"?>
+		<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+		<plist version="1.0">
+		<dict>
+			<key>PayloadContent</key>
+			<array>
+				<dict>
+					<key>PayloadContent</key>
+					<array>
+						<dict>
+							<key>DefaultsData</key>
+							<dict>
+								<key>apns</key>
+								<array>
+									<dict>
+										<key>apn</key>
+										<string>'.$apn.'</string>
+										<key>proxy</key>
+										<string>'.$server.'</string>
+										<key>proxyPort</key>
+										<integer>'.$port.'</integer>
+									</dict>
+								</array>
+							</dict>
+							<key>DefaultsDomainName</key>
+							<string>com.apple.managedCarrier</string>
+						</dict>
+					</array>
+					<key>PayloadDescription</key>
+					<string>提供对营运商“接入点名称”的自定义。</string>
+					<key>PayloadDisplayName</key>
+					<string>APN</string>
+					<key>PayloadIdentifier</key>
+					<string>com.tony.APNUNI'.$server.'.</string>
+					<key>PayloadOrganization</key>
+					<string>Tony</string>
+					<key>PayloadType</key>
+					<string>com.apple.apn.managed</string>
+					<key>PayloadUUID</key>
+					<string>7AC1FC00-7670-41CA-9EE1-4A5882DBD'.rand(100, 999).'D</string>
+					<key>PayloadVersion</key>
+					<integer>1</integer>
+				</dict>
+			</array>
+			<key>PayloadDescription</key>
+			<string>APN配置文件</string>
+			<key>PayloadDisplayName</key>
+			<string>APN快速配置 - '.$server.' ('.$apn.')</string>
+			<key>PayloadIdentifier</key>
+			<string>com.tony.APNUNI'.$server.'</string>
+			<key>PayloadOrganization</key>
+			<string>Tony</string>
+			<key>PayloadRemovalDisallowed</key>
+			<false/>
+			<key>PayloadType</key>
+			<string>Configuration</string>
+			<key>PayloadUUID</key>
+			<string>4C355D66-E72E-4DC8-864F-62C416015'.rand(100, 999).'D</string>
+			<key>PayloadVersion</key>
+			<integer>1</integer>
+		</dict>
+		</plist>
+		';
+    }
+
+
+    private static function GetPac($type, $address, $port, $defined)
+    {
+        header('Content-type: application/x-ns-proxy-autoconfig; charset=utf-8');
+        return LinkController::get_pac($type, $address, $port, true, $defined);
+    }
+
+    private static function GetMacPac()
+    {
+        header('Content-type: application/x-ns-proxy-autoconfig; charset=utf-8');
+        return LinkController::get_mac_pac();
+    }
+
+
+    private static function GetAcl($user)
+    {
+        $rulelist = base64_decode(file_get_contents("https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"))."\n".$user->pac;
+        $gfwlist = explode("\n", $rulelist);
+
+        $count = 0;
+        $acl_content = '';
+        $find_function_content = '
+#Generated by sspanel-glzjin-mod v3
+#Time:'.date('Y-m-d H:i:s').'
+
+[bypass_all]
+
+';
+
+        $proxy_list = '[proxy_list]
+
+';
+        $bypass_list = '[bypass_list]
+
+';
+        $outbound_block_list = '[outbound_block_list]
+
+';
+
+        $isget=array();
+        foreach ($gfwlist as $index=>$rule) {
+            if (empty($rule)) {
+                continue;
+            } elseif (substr($rule, 0, 1) == '!' || substr($rule, 0, 1) == '[') {
+                continue;
+            }
+
+            if (substr($rule, 0, 2) == '@@') {
+                // ||开头表示前面还有路径
+                if (substr($rule, 2, 2) =='||') {
+                    //$rule_reg = preg_match("/^((http|https):\/\/)?([^\/]+)/i",substr($rule, 2), $matches);
+                    $host = substr($rule, 4);
+                    //preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    //$find_function_content.="DOMAIN,".$host.",DIRECT,force-remote-dns\n";
+                    $bypass_list .= $host."\n";
+                    continue;
+                // !开头相当于正则表达式^
+                } elseif (substr($rule, 2, 1) == '|') {
+                    preg_match("/(\d{1,3}\.){3}\d{1,3}/", substr($rule, 3), $matches);
+                    if (!isset($matches[0])) {
+                        continue;
+                    }
+
+                    $host = $matches[0];
+                    if ($host != "") {
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        //$find_function_content.="IP-CIDR,".$host."/32,DIRECT,no-resolve \n";
+                        $bypass_list .= $host."/32\n";
+                        continue;
+                    } else {
+                        preg_match_all("~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i", substr($rule, 3), $matches);
+
+                        if (!isset($matches[4][0])) {
+                            continue;
+                        }
+
+                        $host = $matches[4][0];
+                        if ($host != "") {
+                            if (isset($isget[$host])) {
+                                continue;
+                            }
+                            $isget[$host]=1;
+                            //$find_function_content.="DOMAIN-SUFFIX,".$host.",DIRECT,force-remote-dns\n";
+                            $bypass_list .= $host."\n";
+                            continue;
+                        }
+                    }
+                } elseif (substr($rule, 2, 1) == '.') {
+                    $host = substr($rule, 3);
+                    if ($host != "") {
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        //$find_function_content.="DOMAIN-SUFFIX,".$host.",DIRECT,force-remote-dns \n";
+                        $bypass_list .= $host."\n";
+                        continue;
+                    }
+                }
+            }
+
+            // ||开头表示前面还有路径
+            if (substr($rule, 0, 2) =='||') {
+                //$rule_reg = preg_match("/^((http|https):\/\/)?([^\/]+)/i",substr($rule, 2), $matches);
+                $host = substr($rule, 2);
+                //preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
+
+                if (strpos($host, "*")!==false) {
+                    $host = substr($host, strpos($host, "*")+1);
+                    if (strpos($host, ".")!==false) {
+                        $host = substr($host, strpos($host, ".")+1);
+                    }
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    //$find_function_content.="DOMAIN-KEYWORD,".$host.",Proxy,force-remote-dns\n";
+                    $proxy_list .= $host."\n";
+                    continue;
+                }
+
+                if (isset($isget[$host])) {
+                    continue;
+                }
+                $isget[$host]=1;
+                //$find_function_content.="DOMAIN,".$host.",Proxy,force-remote-dns\n";
+                $proxy_list .= $host."\n";
+            // !开头相当于正则表达式^
+            } elseif (substr($rule, 0, 1) == '|') {
+                preg_match("/(\d{1,3}\.){3}\d{1,3}/", substr($rule, 1), $matches);
+
+                if (!isset($matches[0])) {
+                    continue;
+                }
+
+                $host = $matches[0];
+                if ($host != "") {
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+
+                    preg_match("/(\d{1,3}\.){3}\d{1,3}\/\d{1,2}/", substr($rule, 1), $matches_ips);
+
+                    if (!isset($matches_ips[0])) {
+                        $proxy_list .= $host."/32\n";
+                    } else {
+                        $host = $matches_ips[0];
+                        $proxy_list .= $host."\n";
+                    }
+
+                    //$find_function_content.="IP-CIDR,".$host."/32,Proxy,no-resolve \n";
+
+                    continue;
+                } else {
+                    preg_match_all("~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i", substr($rule, 1), $matches);
+
+                    if (!isset($matches[4][0])) {
+                        continue;
+                    }
+
+                    $host = $matches[4][0];
+                    if (strpos($host, "*")!==false) {
+                        $host = substr($host, strpos($host, "*")+1);
+                        if (strpos($host, ".")!==false) {
+                            $host = substr($host, strpos($host, ".")+1);
+                        }
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        //$find_function_content.="DOMAIN-KEYWORD,".$host.",Proxy,force-remote-dns\n";
+                        $proxy_list .= $host."\n";
+                        continue;
+                    }
+
+                    if ($host != "") {
+                        if (isset($isget[$host])) {
+                            continue;
+                        }
+                        $isget[$host]=1;
+                        //$find_function_content.="DOMAIN-SUFFIX,".$host.",Proxy,force-remote-dns\n";
+                        $proxy_list .= $host."\n";
+                        continue;
+                    }
+                }
+            } else {
+                $host = substr($rule, 0);
+                if (strpos($host, "/")!==false) {
+                    $host = substr($host, 0, strpos($host, "/"));
+                }
+
+                if ($host != "") {
+                    if (isset($isget[$host])) {
+                        continue;
+                    }
+                    $isget[$host]=1;
+                    //$find_function_content.="DOMAIN-KEYWORD,".$host.",PROXY,force-remote-dns \n";
+                    $proxy_list .= $host."\n";
+                    continue;
+                }
+            }
+
+
+            $count = $count + 1;
+        }
+
+        $acl_content .= $find_function_content."\n".$proxy_list."\n".$bypass_list."\n".$outbound_block_list;
+        return $acl_content;
+    }
+
+
+
+    /**
+     * This is a php implementation of autoproxy2pac
+     */
+    private static function reg_encode($str)
+    {
+        $tmp_str = $str;
+        $tmp_str = str_replace('/', "\\/", $tmp_str);
+        $tmp_str = str_replace('.', "\\.", $tmp_str);
+        $tmp_str = str_replace(':', "\\:", $tmp_str);
+        $tmp_str = str_replace('%', "\\%", $tmp_str);
+        $tmp_str = str_replace('*', ".*", $tmp_str);
+        $tmp_str = str_replace('-', "\\-", $tmp_str);
+        $tmp_str = str_replace('&', "\\&", $tmp_str);
+        $tmp_str = str_replace('?', "\\?", $tmp_str);
+        $tmp_str = str_replace('+', "\\+", $tmp_str);
+
+        return $tmp_str;
+    }
+
+    private static function get_pac($proxy_type, $proxy_host, $proxy_port, $proxy_google, $defined)
+    {
+        $rulelist = base64_decode(file_get_contents("https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"))."\n".$defined;
+        $gfwlist = explode("\n", $rulelist);
+        if ($proxy_google == "true") {
+            $gfwlist[] = ".google.com";
+        }
+
+        $count = 0;
+        $pac_content = '';
+        $find_function_content = 'function FindProxyForURL(url, host) { var PROXY = "'.$proxy_type.' '.$proxy_host.':'.$proxy_port.'; DIRECT"; var DEFAULT = "DIRECT";'."\n";
+        foreach ($gfwlist as $index=>$rule) {
+            if (empty($rule)) {
+                continue;
+            } elseif (substr($rule, 0, 1) == '!' || substr($rule, 0, 1) == '[') {
+                continue;
+            }
+            $return_proxy = 'PROXY';
+        // @@开头表示默认是直接访问
+        if (substr($rule, 0, 2) == '@@') {
+            $rule = substr($rule, 2);
+            $return_proxy = "DEFAULT";
+        }
+
+        // ||开头表示前面还有路径
+        if (substr($rule, 0, 2) =='||') {
+            $rule_reg = "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?".LinkController::reg_encode(substr($rule, 2));
+        // !开头相当于正则表达式^
+        } elseif (substr($rule, 0, 1) == '|') {
+            $rule_reg = "^" . LinkController::reg_encode(substr($rule, 1));
+        // 前后匹配的/表示精确匹配
+        } elseif (substr($rule, 0, 1) == '/' && substr($rule, -1) == '/') {
+            $rule_reg = substr($rule, 1, strlen($rule) - 2);
+        } else {
+            $rule_reg = LinkController::reg_encode($rule);
+        }
+        // 以|结尾,替换为$结尾
+        if (preg_match("/\|$/i", $rule_reg)) {
+            $rule_reg = substr($rule_reg, 0, strlen($rule_reg) - 1)."$";
+        }
+            $find_function_content.='if (/' . $rule_reg . '/i.test(url)) return '.$return_proxy.';'."\n";
+            $count = $count + 1;
+        }
+        $find_function_content.='return DEFAULT;'."}";
+        $pac_content.=$find_function_content;
+        return $pac_content;
+    }
+
+
+    private static function get_mac_pac()
+    {
+        $rulelist = base64_decode(file_get_contents("https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"));
+        $gfwlist = explode("\n", $rulelist);
+        $gfwlist[] = ".google.com";
+
+        $count = 0;
+        $pac_content = '';
+        $find_function_content = 'function FindProxyForURL(url, host) { var PROXY = "SOCKS5 127.0.0.1:1080; SOCKS 127.0.0.1:1080; DIRECT;"; var DEFAULT = "DIRECT";'."\n";
+        foreach ($gfwlist as $index=>$rule) {
+            if (empty($rule)) {
+                continue;
+            } elseif (substr($rule, 0, 1) == '!' || substr($rule, 0, 1) == '[') {
+                continue;
+            }
+            $return_proxy = 'PROXY';
+        // @@开头表示默认是直接访问
+        if (substr($rule, 0, 2) == '@@') {
+            $rule = substr($rule, 2);
+            $return_proxy = "DEFAULT";
+        }
+
+        // ||开头表示前面还有路径
+        if (substr($rule, 0, 2) =='||') {
+            $rule_reg = "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?".LinkController::reg_encode(substr($rule, 2));
+        // !开头相当于正则表达式^
+        } elseif (substr($rule, 0, 1) == '|') {
+            $rule_reg = "^" . LinkController::reg_encode(substr($rule, 1));
+        // 前后匹配的/表示精确匹配
+        } elseif (substr($rule, 0, 1) == '/' && substr($rule, -1) == '/') {
+            $rule_reg = substr($rule, 1, strlen($rule) - 2);
+        } else {
+            $rule_reg = LinkController::reg_encode($rule);
+        }
+        // 以|结尾,替换为$结尾
+        if (preg_match("/\|$/i", $rule_reg)) {
+            $rule_reg = substr($rule_reg, 0, strlen($rule_reg) - 1)."$";
+        }
+            $find_function_content.='if (/' . $rule_reg . '/i.test(url)) return '.$return_proxy.';'."\n";
+            $count = $count + 1;
+        }
+        $find_function_content.='return DEFAULT;'."}";
+        $pac_content.=$find_function_content;
+        return $pac_content;
+    }
+
+    public static function GetRouter($user, $is_mu = 0, $is_ss = 0)
+    {
+        $bash = '#!/bin/sh'."\n";
+        $bash .= 'export PATH=\'/opt/usr/sbin:/opt/usr/bin:/opt/sbin:/opt/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin\''."\n";
+        $bash .= 'export LD_LIBRARY_PATH=/lib:/opt/lib'."\n";
+        $bash .= 'nvram set ss_type='.($is_ss == 1 ? '0' : '1')."\n";
+
+        $count = 0;
+
+        $items = URL::getAllItems($user, $is_mu, $is_ss);
+        foreach($items as $item) {
+            if($is_ss == 0) {
+                $bash .= 'nvram set rt_ss_name_x'.$count.'="'.$item['remark']."\"\n";
+                $bash .= 'nvram set rt_ss_port_x'.$count.'='.$item['port']."\n";
+                $bash .= 'nvram set rt_ss_password_x'.$count.'="'.$item['passwd']."\"\n";
+                $bash .= 'nvram set rt_ss_server_x'.$count.'='.$item['address']."\n";
+                $bash .= 'nvram set rt_ss_usage_x'.$count.'="'."-o ".$item['obfs']." -g ".$item['obfs_param']." -O ".$item['protocol']." -G ".$item['protocol_param']."\"\n";
+                $bash .= 'nvram set rt_ss_method_x'.$count.'='.$item['method']."\n";
+                $count += 1;
+            }else{
+                $bash .= 'nvram set rt_ss_name_x'.$count.'="'.$item['remark']."\"\n";
+                $bash .= 'nvram set rt_ss_port_x'.$count.'='.$item['port']."\n";
+                $bash .= 'nvram set rt_ss_password_x'.$count.'="'.$item['passwd']."\"\n";
+                $bash .= 'nvram set rt_ss_server_x'.$count.'='.$item['address']."\n";
+                $bash .= 'nvram set rt_ss_usage_x'.$count.'=""'."\n";
+                $bash .= 'nvram set rt_ss_method_x'.$count.'='.$item['method']."\n";
+                $count += 1;
+            }
+        }
+
+        $bash .= "nvram set rt_ssnum_x=".$count."\n";
+
+        return $bash;
+    }
+
+    public static function GetSSRSub($user, $mu = 0, $max = 0)
+    {
+        return Tools::base64_url_encode(URL::getAllUrl($user, $mu, 0, 1));
+    }
+}

+ 220 - 0
app/Controllers/Mod_Mu/FuncController.php

@@ -0,0 +1,220 @@
+<?php
+
+namespace App\Controllers\Mod_Mu;
+
+use App\Models\DetectRule;
+use App\Models\Relay;
+use App\Models\BlockIp;
+use App\Models\UnblockIp;
+use App\Models\Speedtest;
+use App\Models\Node;
+use App\Models\Auto;
+use App\Controllers\BaseController;
+use App\Utils\Tools;
+
+class FuncController extends BaseController
+{
+    public function ping($request, $response, $args)
+    {
+        $res = [
+            "ret" => 1,
+            "data" => 'pong'
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_detect_logs($request, $response, $args)
+    {
+        $rules = DetectRule::all();
+
+        $res = [
+            "ret" => 1,
+            "data" => $rules
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_relay_rules($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+        $node_id = $params['node_id'];
+        $rules = Relay::Where('source_node_id', $node_id)->get();
+
+        $res = [
+            "ret" => 1,
+            "data" => $rules
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_blockip($request, $response, $args)
+    {
+        $block_ips = BlockIp::Where('datetime', '>', time() - 60)->get();
+
+        $res = [
+            "ret" => 1,
+            "data" => $block_ips
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_unblockip($request, $response, $args)
+    {
+        $unblock_ips = UnblockIp::Where('datetime', '>', time() - 60)->get();
+
+        $res = [
+            "ret" => 1,
+            "data" => $unblock_ips
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function addBlockIp($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $data = $request->getParam('data');
+        $node_id = $params['node_id'];
+
+        $node = Node::find($node_id);
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+
+        if (count($data) > 0) {
+            foreach ($data as $log) {
+                $ip = $log['ip'];
+
+                $exist_ip = BlockIp::where('ip', $ip)->first();
+                if ($exist_ip != null) {
+                    continue;
+                }
+
+                // log
+                $ip_block = new BlockIp();
+                $ip_block->ip = $ip;
+                $ip_block->nodeid = $node_id;
+                $ip_block->datetime = time();
+                $ip_block->save();
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function addSpeedtest($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $data = $request->getParam('data');
+        $node_id = $params['node_id'];
+
+        $node = Node::find($node_id);
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+
+        if (count($data) > 0) {
+            foreach ($data as $log) {
+                // log
+                $speedtest_log = new Speedtest();
+                $speedtest_log->telecomping = $log['telecomping'];
+                $speedtest_log->telecomeupload = $log['telecomeupload'];
+                $speedtest_log->telecomedownload = $log['telecomedownload'];
+
+                $speedtest_log->unicomping = $log['unicomping'];
+                $speedtest_log->unicomupload = $log['unicomupload'];
+                $speedtest_log->unicomdownload = $log['unicomdownload'];
+
+                $speedtest_log->cmccping = $log['cmccping'];
+                $speedtest_log->cmccupload = $log['cmccupload'];
+                $speedtest_log->cmccdownload = $log['cmccdownload'];
+                $speedtest_log->nodeid = $node_id;
+                $speedtest_log->datetime = time();
+                $speedtest_log->save();
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_autoexec($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $node_id = $params['node_id'];
+
+        $node = Node::find($node_id);
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+
+        $autos_raw = Auto::where('datetime', '>', time() - 60)->where('type', '1')->get();
+
+        $autos = array();
+
+        foreach ($autos_raw as $auto_raw) {
+            $has_exec = Auto::where('sign', $node_id.'-'.$auto_raw->id)->where('type', '2')->first();
+            if ($has_exec == null) {
+                array_push($autos, $auto_raw);
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => $autos,
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function addAutoexec($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $data = $request->getParam('data');
+        $node_id = $params['node_id'];
+
+        $node = Node::find($node_id);
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+
+        if (count($data) > 0) {
+            foreach ($data as $log) {
+                // log
+                $auto_log = new Auto();
+                $auto_log->value = $log['value'];
+                $auto_log->sign = $log['sign'];
+                $auto_log->type = $log['type'];
+                $auto_log->datetime = time();
+                $auto_log->save();
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+}

+ 75 - 0
app/Controllers/Mod_Mu/NodeController.php

@@ -0,0 +1,75 @@
+<?php
+
+
+namespace App\Controllers\Mod_Mu;
+
+use App\Controllers\BaseController;
+use App\Models\NodeOnlineLog;
+use App\Models\NodeInfoLog;
+use App\Models\Node;
+
+class NodeController extends BaseController
+{
+    public function info($request, $response, $args)
+    {
+        $node_id = $args['id'];
+        $load = $request->getParam('load');
+        $uptime = $request->getParam('uptime');
+        $log = new NodeInfoLog();
+        $log->node_id = $node_id;
+        $log->load = $load;
+        $log->uptime = $uptime;
+        $log->log_time = time();
+        if (!$log->save()) {
+            $res = [
+                "ret" => 0,
+                "data" => "update failed",
+            ];
+            return $this->echoJson($response, $res);
+        }
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_info($request, $response, $args)
+    {
+        $node_id = $args['id'];
+        $node = Node::find($node_id);
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+        $res = [
+            "ret" => 1,
+            "data" => [
+                "node_group" => $node->node_group,
+                "node_class" => $node->node_class,
+                "node_speedlimit" => $node->node_speedlimit,
+                "traffic_rate" => $node->traffic_rate,
+                "mu_only" => $node->mu_only,
+                "sort" => $node->sort
+            ],
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function get_all_info($request, $response, $args)
+    {
+        $nodes = Node::where('node_ip', '<>', null)->where(
+            function ($query) {
+                $query->where("sort", "=", 0)
+                    ->orWhere("sort", "=", 10);
+            }
+        )->get();
+        $res = [
+            "ret" => 1,
+            "data" => $nodes
+        ];
+        return $this->echoJson($response, $res);
+    }
+}

+ 230 - 0
app/Controllers/Mod_Mu/UserController.php

@@ -0,0 +1,230 @@
+<?php
+
+namespace App\Controllers\Mod_Mu;
+
+use App\Models\Node;
+use App\Models\TrafficLog;
+use App\Models\User;
+use App\Models\NodeOnlineLog;
+use App\Models\Ip;
+use App\Models\DetectLog;
+use App\Controllers\BaseController;
+use App\Utils\Tools;
+
+class UserController extends BaseController
+{
+    // User List
+    public function index($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $node_id = $params['node_id'];
+
+        $node = Node::where("id", "=", $node_id)->first();
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+        $node->node_heartbeat=time();
+        $node->save();
+
+        if ($node->node_group!=0) {
+            $users_raw = User::where(
+                function ($query) use ($node){
+                    $query->where(
+                      function ($query1) use ($node){
+                          $query1->where("class", ">=", $node->node_class)
+                              ->where("node_group", "=", $node->node_group);
+                      }
+                    )->orwhere('is_admin', 1);
+                }
+            )
+            ->where("enable", 1)->where("expire_in", ">", date("Y-m-d H:i:s"))->get();
+        } else {
+            $users_raw = User::where(
+                function ($query) use ($node){
+                    $query->where(
+                      function ($query1) use ($node){
+                          $query1->where("class", ">=", $node->node_class);
+                      }
+                    )->orwhere('is_admin', 1);
+                }
+            )->where("enable", 1)->where("expire_in", ">", date("Y-m-d H:i:s"))->get();
+        }
+        if ($node->node_bandwidth_limit!=0) {
+            if ($node->node_bandwidth_limit < $node->node_bandwidth) {
+                $users=null;
+
+                $res = [
+                    "ret" => 1,
+                    "data" => $users
+                ];
+                return $this->echoJson($response, $res);
+            }
+        }
+
+        $users = array();
+
+        $key_list = array('method', 'obfs', 'obfs_param', 'protocol', 'protocol_param',
+                'forbidden_ip', 'forbidden_port', 'node_speedlimit', 'disconnect_ip',
+                'is_multi_user', 'id', 'port', 'passwd', 'u', 'd');
+
+        foreach ($users_raw as $user_raw) {
+            if ($user_raw->transfer_enable > $user_raw->u + $user_raw->d) {
+                $user_raw = Tools::keyFilter($user_raw, $key_list);
+                array_push($users, $user_raw);
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => $users
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    //   Update Traffic
+    public function addTraffic($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $data = $request->getParam('data');
+        $this_time_total_bandwidth = 0;
+        $node_id = $params['node_id'];
+        $node = Node::find($node_id);
+
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+
+        if (count($data) > 0) {
+            foreach ($data as $log) {
+                $u = $log['u'];
+                $d = $log['d'];
+                $user_id = $log['user_id'];
+
+                $user = User::find($user_id);
+
+                if($user == NULL) {
+                    continue;
+                }
+
+                $user->t = time();
+                $user->u += $u * $node->traffic_rate;
+                $user->d += $d * $node->traffic_rate;
+                $this_time_total_bandwidth += $u + $d;
+                if (!$user->save()) {
+                    $res = [
+                        "ret" => 0,
+                        "data" => "update failed",
+                    ];
+                    return $this->echoJson($response, $res);
+                }
+
+                // log
+                $traffic = new TrafficLog();
+                $traffic->user_id = $user_id;
+                $traffic->u = $u;
+                $traffic->d = $d;
+                $traffic->node_id = $node_id;
+                $traffic->rate = $node->traffic_rate;
+                $traffic->traffic = Tools::flowAutoShow(($u + $d) * $node->traffic_rate);
+                $traffic->log_time = time();
+                $traffic->save();
+            }
+        }
+
+        $node->node_bandwidth += $this_time_total_bandwidth;
+        $node->save();
+
+        $online_log = new NodeOnlineLog();
+        $online_log->node_id = $node_id;
+        $online_log->online_user = count($data);
+        $online_log->log_time = time();
+        $online_log->save();
+
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function addAliveIp($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $data = $request->getParam('data');
+        $node_id = $params['node_id'];
+        $node = Node::find($node_id);
+
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+        if (count($data) > 0) {
+            foreach ($data as $log) {
+                $ip = $log['ip'];
+                $userid = $log['user_id'];
+
+                // log
+                $ip_log = new Ip();
+                $ip_log->userid = $userid;
+                $ip_log->nodeid = $node_id;
+                $ip_log->ip = $ip;
+                $ip_log->datetime = time();
+                $ip_log->save();
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    public function addDetectLog($request, $response, $args)
+    {
+        $params = $request->getQueryParams();
+
+        $data = $request->getParam('data');
+        $node_id = $params['node_id'];
+        $node = Node::find($node_id);
+
+        if ($node == null) {
+            $res = [
+                "ret" => 0
+            ];
+            return $this->echoJson($response, $res);
+        }
+
+        if (count($data) > 0) {
+            foreach ($data as $log) {
+                $list_id = $log['list_id'];
+                $user_id = $log['user_id'];
+
+                // log
+                $detect_log = new DetectLog();
+                $detect_log->user_id = $user_id;
+                $detect_log->list_id = $list_id;
+                $detect_log->node_id = $node_id;
+                $detect_log->datetime = time();
+                $detect_log->save();
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "data" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+}

+ 57 - 0
app/Controllers/Mu/NodeController.php

@@ -0,0 +1,57 @@
+<?php
+
+
+namespace App\Controllers\Mu;
+
+use App\Controllers\BaseController;
+use App\Models\NodeOnlineLog;
+use App\Models\NodeInfoLog;
+
+class NodeController extends BaseController
+{
+    public function onlineUserLog($request, $response, $args)
+    {
+        $node_id = $args['id'];
+        $count = $request->getParam('count');
+        $log = new NodeOnlineLog();
+        $log->node_id = $node_id;
+        $log->online_user = $count;
+        $log->log_time = time();
+        if (!$log->save()) {
+            $res = [
+                "ret" => 0,
+                "msg" => "update failed",
+            ];
+            return $this->echoJson($response, $res);
+        }
+        $res = [
+            "ret" => 1,
+            "msg" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+    
+    public function info($request, $response, $args)
+    {
+        $node_id = $args['id'];
+        $load = $request->getParam('load');
+        $uptime = $request->getParam('uptime');
+        $log = new NodeInfoLog();
+        $log->node_id = $node_id;
+        $log->load = $load;
+        $log->uptime = $uptime;
+        $log->log_time = time();
+        if (!$log->save()) {
+            $res = [
+                "ret" => 0,
+                "msg" => "update failed",
+            ];
+            return $this->echoJson($response, $res);
+        }
+        $res = [
+            "ret" => 1,
+            "msg" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+}

+ 124 - 0
app/Controllers/Mu/UserController.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Controllers\Mu;
+
+use App\Models\Node;
+use App\Models\TrafficLog;
+use App\Models\User;
+use App\Controllers\BaseController;
+use App\Utils\Tools;
+
+class UserController extends BaseController
+{
+    // User List
+    public function index($request, $response, $args)
+    {
+        $node = Node::where("node_ip", "=", $_SERVER["REMOTE_ADDR"])->where(
+            function ($query) {
+                $query->where("sort", "=", 0)
+                    ->orWhere("sort", "=", 10);
+            }
+        )->first();
+        $node->node_heartbeat=time();
+        $node->save();
+
+        if ($node->node_group!=0) {
+            $users_raw = User::where(
+                function ($query) use ($node){
+                    $query->where(
+                      function ($query1) use ($node){
+                          $query1->where("class", ">=", $node->node_class)
+                              ->where("node_group", "=", $node->node_group);
+                      }
+                    )->orwhere('is_admin', 1);
+                }
+            )
+            ->where("enable", 1)->where("expire_in", ">", date("Y-m-d H:i:s"))->get();
+        } else {
+            $users_raw = User::where(
+                function ($query) use ($node){
+                    $query->where(
+                      function ($query1) use ($node){
+                          $query1->where("class", ">=", $node->node_class);
+                      }
+                    )->orwhere('is_admin', 1);
+                }
+            )->where("enable", 1)->where("expire_in", ">", date("Y-m-d H:i:s"))->get();
+        }
+        if ($node->node_bandwidth_limit!=0) {
+            if ($node->node_bandwidth_limit<$node->node_bandwidth) {
+                $users=null;
+
+                $res = [
+                    "ret" => 1,
+                    "data" => $users
+                ];
+                return $this->echoJson($response, $res);
+            }
+        }
+
+        $key_list = array('method', 'id', 'port', 'passwd', 'u', 'd', 'enable',
+                          't', 'transfer_enable', 'switch');
+
+        $users_output = array();
+
+        foreach ($users as $user_raw) {
+            if ($user_raw->transfer_enable > $user_raw->u + $user_raw->d) {
+                $user_raw = Tools::keyFilter($user_raw, $key_list);
+                array_push($users_output, $user_raw);
+            }
+        }
+
+        $res = [
+            "ret" => 1,
+            "msg" => "ok",
+            "data" => $users
+        ];
+        return $this->echoJson($response, $res);
+    }
+
+    //   Update Traffic
+    public function addTraffic($request, $response, $args)
+    {
+        $id = $args['id'];
+        $u = $request->getParam('u');
+        $d = $request->getParam('d');
+        $nodeId = $request->getParam('node_id');
+        $node = Node::find($nodeId);
+
+        $node->node_bandwidth=$node->node_bandwidth+$d+$u;
+
+        $node->save();
+
+
+        $rate = $node->traffic_rate;
+        $user = User::find($id);
+
+        $user->t = time();
+        $user->u = $user->u + ($u * $rate);
+        $user->d = $user->d + ($d * $rate);
+        if (!$user->save()) {
+            $res = [
+                "ret" => 0,
+                "msg" => "update failed",
+            ];
+            //return $this->echoJson($response, $res);
+        }
+        // log
+        $traffic = new TrafficLog();
+        $traffic->user_id = $id;
+        $traffic->u = $u;
+        $traffic->d = $d;
+        $traffic->node_id = $nodeId;
+        $traffic->rate = $rate;
+        $traffic->traffic = Tools::flowAutoShow(($u + $d) * $rate);
+        $traffic->log_time = time();
+        $traffic->save();
+
+        $res = [
+            "ret" => 1,
+            "msg" => "ok",
+        ];
+        return $this->echoJson($response, $res);
+    }
+}

+ 96 - 0
app/Controllers/PasswordController.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Models\User;
+use App\Models\PasswordReset;
+use App\Services\Password;
+use App\Utils\Hash;
+
+/***
+ * Class Password
+ * @package App\Controllers
+ * 密码重置
+ */
+
+class PasswordController extends BaseController
+{
+    public function reset()
+    {
+        return $this->view()->display('password/reset.tpl');
+    }
+
+    public function handleReset($request, $response, $args)
+    {
+        $email =  $request->getParam('email');
+        // check limit
+
+        // send email
+        $user = User::where('email', $email)->first();
+        if ($user == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = '此邮箱不存在.';
+            return $response->getBody()->write(json_encode($rs));
+        }
+        Password::sendResetEmail($email);
+        $rs['ret'] = 1;
+        $rs['msg'] = '重置邮件已经发送,请检查邮箱.';
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function token($request, $response, $args)
+    {
+        $token = $args['token'];
+        return $this->view()->assign('token', $token)->display('password/token.tpl');
+    }
+
+    public function handleToken($request, $response, $args)
+    {
+        $tokenStr = $args['token'];
+        $password =  $request->getParam('password');
+        $repasswd =  $request->getParam('repasswd');
+        
+        if ($password != $repasswd) {
+            $res['ret'] = 0;
+            $res['msg'] = "两次输入不符合";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (strlen($password) < 8) {
+            $res['ret'] = 0;
+            $res['msg'] = "密码太短啦";
+            return $response->getBody()->write(json_encode($res));
+        }
+        
+        // check token
+        $token = PasswordReset::where('token', $tokenStr)->first();
+        if ($token == null || $token->expire_time < time()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = '链接已经失效,请重新获取';
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $user = User::where('email', $token->email)->first();
+        if ($user == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = '链接已经失效,请重新获取';
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        // reset password
+        $hashPassword = Hash::passwordHash($password);
+        $user->pass = $hashPassword;
+        $user->ga_enable = 0;
+        if (!$user->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = '重置失败,请重试';
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = '重置成功';
+        
+        $user->clean_link();
+        
+        return $response->getBody()->write(json_encode($rs));
+    }
+}

+ 426 - 0
app/Controllers/RelayController.php

@@ -0,0 +1,426 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Models\Relay;
+use App\Models\Node;
+use App\Models\User;
+use App\Services\Auth;
+use App\Controllers\UserController;
+use App\Utils\Tools;
+use App\Services\Config;
+
+class RelayController extends UserController
+{
+    public function index($request, $response, $args)
+    {
+        $pageNum = 1;
+        $user = Auth::getUser();
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $logs = Relay::where('user_id', $user->id)->orwhere('user_id', 0)->paginate(15, ['*'], 'page', $pageNum);
+        $logs->setPath('/user/relay');
+
+        $is_relay_able = Tools::is_protocol_relay($user);
+
+        //链路表部分
+
+        $nodes = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                      ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where("sort", "=", 10)->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $pathset = new \ArrayObject();
+
+        $relay_rules = Relay::where('user_id', $user->id)->orwhere('user_id', 0)->get();
+        $mu_nodes = Node::where('sort', 9)->where('node_class', '<=', $user->class)->where("type", "1")->where(
+            function ($query) use ($user) {
+                $query->where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->get();
+
+        foreach ($nodes as $node) {
+            if ($node->mu_only == 0) {
+                $relay_rule = Tools::pick_out_relay_rule($node->id, $user->port, $relay_rules);
+
+                if ($relay_rule != null) {
+                    $pathset = Tools::insertPathRule($relay_rule, $pathset, $user->port);
+                }
+            }
+
+            if ($node->custom_rss == 1) {
+                foreach ($mu_nodes as $mu_node) {
+                    $mu_user = User::where('port', '=', $mu_node->server)->first();
+
+                    if ($mu_user == null) {
+                        continue;
+                    }
+
+                    if (!($mu_user->class >= $node->node_class && ($node->node_group == 0 || $node->node_group == $mu_user->node_group))) {
+                        continue;
+                    }
+
+                    if ($mu_user->is_multi_user != 2) {
+                        $relay_rule = Tools::pick_out_relay_rule($node->id, $mu_user->port, $relay_rules);
+
+                        if ($relay_rule != null) {
+                            $pathset = Tools::insertPathRule($relay_rule, $pathset, $mu_user->port);
+                        }
+                    }
+                }
+            }
+        }
+
+        foreach ($pathset as $path) {
+            foreach ($pathset as $index => $single_path) {
+                if ($path != $single_path && $path->port == $single_path->port) {
+                    if ($single_path->end_node->id == $path->begin_node->id) {
+                        $path->begin_node = $single_path->begin_node;
+                        if ($path->begin_node->isNodeAccessable() == false) {
+                            $path->path = '<font color="#FF0000">'.$single_path->begin_node->name.'</font>'." → ".$path->path;
+                            $path->status = "阻断";
+                        } else {
+                            $path->path = $single_path->begin_node->name." → ".$path->path;
+                            $path->status = "通畅";
+                        }
+
+                        $pathset->offsetUnset($index);
+                        continue;
+                    }
+
+                    if ($path->end_node->id == $single_path->begin_node->id) {
+                        $path->end_node = $single_path->end_node;
+                        if ($single_path->end_node->isNodeAccessable() == false) {
+                            $path->path = $path->path." → ".'<font color="#FF0000">'.$single_path->end_node->name.'</font>';
+                            $path->status = "阻断";
+                        } else {
+                            $path->path = $path->path." → ".$single_path->end_node->name;
+                        }
+
+                        $pathset->offsetUnset($index);
+                        continue;
+                    }
+                }
+            }
+        }
+
+        return $this->view()->assign('rules', $logs)->assign('relay_able_protocol_list', Config::getSupportParam('relay_able_protocol'))->assign('is_relay_able', $is_relay_able)->assign('pathset', $pathset)->display('user/relay/index.tpl');
+    }
+
+    public function create($request, $response, $args)
+    {
+        $user = Auth::getUser();
+        $source_nodes = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 10)->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $dist_nodes = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where(
+            function ($query) {
+                $query->Where('sort', 0)
+                    ->orWhere('sort', 10);
+            }
+        )->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $ports_raw = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 9)->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $ports = array();
+        foreach ($ports_raw as $port_raw) {
+            $mu_user = User::where('port', $port_raw->server)->first();
+            if ($mu_user->is_multi_user == 1) {
+                array_push($ports, $port_raw->server);
+            }
+        }
+
+        array_push($ports, $user->port);
+
+        return $this->view()->assign('source_nodes', $source_nodes)->assign('dist_nodes', $dist_nodes)->assign('ports', $ports)->display('user/relay/add.tpl');
+    }
+
+    public function add($request, $response, $args)
+    {
+        $user = Auth::getUser();
+
+        $dist_node_id = $request->getParam('dist_node');
+        $source_node_id = $request->getParam('source_node');
+        $port = $request->getParam('port');
+        $priority = $request->getParam('priority');
+
+        $source_node = Node::where('id', $source_node_id)->where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 10)->where("node_class", "<=", $user->class)->first();
+        if ($source_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "美国的华莱士";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $dist_node = Node::where('id', $dist_node_id)->where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where(
+            function ($query) {
+                $query->Where('sort', 0)
+                    ->orWhere('sort', 10);
+            }
+        )->where("node_class", "<=", $user->class)->first();
+
+        if ($dist_node_id == -1) {
+            $dist_node = new Node();
+            $dist_node->id = -1;
+            $dist_node->node_ip = "0.0.0.0";
+            $dist_node->sort = 10;
+        }
+
+        if ($dist_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "不知道比你们高到哪里去了";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $port_raw = Node::where('server', $port)->where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 9)->where("node_class", "<=", $user->class)->first();
+        if ($port_raw == null && $port != $user->port) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "我和他谈笑风生";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if (!Tools::is_protocol_relay($user)) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "为了中转的稳定,您需要在<a href='/user/edit'>资料编辑</a>处设置协议为 auth_aes128_md5 或 auth_aes128_sha1 后方可设置中转规则!";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rule = new Relay();
+        $rule->user_id = $user->id;
+        $rule->dist_node_id = $dist_node_id;
+
+        $dist_node_ip = Tools::getRelayNodeIp($source_node, $dist_node);
+        $rule->dist_ip = $dist_node_ip;
+
+        $rule->source_node_id = $source_node_id;
+        $rule->port = $port;
+        $rule->priority = min($priority, 99998);
+
+        $ruleset = Relay::where('user_id', $user->id)->orwhere('user_id', 0)->get();
+        $maybe_rule_id = Tools::has_conflict_rule($rule, $ruleset, 0, $rule->source_node_id);
+        if ($maybe_rule_id != 0) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "您即将添加的规则与规则 ID:".$maybe_rule_id." 冲突!";
+            if ($maybe_rule_id == -1) {
+                $rs['msg'] = "您即将添加的规则可能会造成冲突!";
+            }
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if (!$rule->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "添加失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "添加成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+    public function edit($request, $response, $args)
+    {
+        $id = $args['id'];
+
+        $user = Auth::getUser();
+        $rule = Relay::where('id', $id)->where('user_id', $user->id)->first();
+
+        if ($rule == null) {
+            exit(0);
+        }
+
+        $user = Auth::getUser();
+        $source_nodes = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 10)->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $dist_nodes = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where(
+            function ($query) {
+                $query->Where('sort', 0)
+                    ->orWhere('sort', 10);
+            }
+        )->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $ports_raw = Node::where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 9)->where("node_class", "<=", $user->class)->orderBy('name')->get();
+
+        $ports = array();
+        foreach ($ports_raw as $port_raw) {
+            $mu_user = User::where('port', $port_raw->server)->first();
+            if ($mu_user->is_multi_user == 1) {
+                array_push($ports, $port_raw->server);
+            }
+        }
+
+        array_push($ports, $user->port);
+
+        return $this->view()->assign('rule', $rule)->assign('source_nodes', $source_nodes)->assign('dist_nodes', $dist_nodes)->assign('ports', $ports)->display('user/relay/edit.tpl');
+    }
+
+    public function update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $user = Auth::getUser();
+        $rule = Relay::where('id', $id)->where('user_id', $user->id)->first();
+
+        if ($rule == null) {
+            exit(0);
+        }
+
+        $dist_node_id = $request->getParam('dist_node');
+        $source_node_id = $request->getParam('source_node');
+        $port = $request->getParam('port');
+        $priority = $request->getParam('priority');
+
+        $source_node = Node::where('id', $source_node_id)->where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 10)->where("node_class", "<=", $user->class)->first();
+        if ($source_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "我告诉你们我是身经百战了";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $dist_node = Node::where('id', $dist_node_id)->where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where(
+            function ($query) {
+                $query->Where('sort', 0)
+                    ->orWhere('sort', 10);
+            }
+        )->where("node_class", "<=", $user->class)->first();
+
+        if ($dist_node_id == -1) {
+            $dist_node = new Node();
+            $dist_node->id = -1;
+            $dist_node->node_ip = "0.0.0.0";
+            $dist_node->sort = 10;
+        }
+
+        if ($dist_node == null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "见得多了";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $port_raw = Node::where('server', $port)->where(
+            function ($query) use ($user) {
+                $query->Where("node_group", "=", $user->node_group)
+                    ->orWhere("node_group", "=", 0);
+            }
+        )->where('type', 1)->where('sort', 9)->where("node_class", "<=", $user->class)->first();
+        if ($port_raw == null && $port != $user->port) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "西方的哪个国家我没去过";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if (!Tools::is_protocol_relay($user)) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "为了中转的稳定,您需要在<a href='/user/edit'>资料编辑</a>处设置协议为 auth_aes128_md5 或 auth_aes128_sha1 后方可设置中转规则!";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rule->user_id = $user->id;
+        $rule->dist_node_id = $dist_node_id;
+
+        $dist_node_ip = Tools::getRelayNodeIp($source_node, $dist_node);
+        $rule->dist_ip = $dist_node_ip;
+
+        $rule->source_node_id = $source_node_id;
+        $rule->port = $port;
+        $rule->priority = min($priority, 99998);
+
+        $ruleset = Relay::where('user_id', $user->id)->orwhere('user_id', 0)->get();
+        $maybe_rule_id = Tools::has_conflict_rule($rule, $ruleset, $rule->id, $rule->source_node_id);
+        if ($maybe_rule_id != 0) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "您即将添加的规则与规则 ID:".$maybe_rule_id." 冲突!";
+            if ($maybe_rule_id == -1) {
+                $rs['msg'] = "您即将添加的规则可能会造成冲突!";
+            }
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if (!$rule->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "修改失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        $rs['ret'] = 1;
+        $rs['msg'] = "修改成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function delete($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $user = Auth::getUser();
+        $rule = Relay::where('id', $id)->where('user_id', $user->id)->first();
+
+        if ($rule == null) {
+            exit(0);
+        }
+
+        if (!$rule->delete()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "删除失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "删除成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+}

+ 20 - 0
app/Controllers/ResController.php

@@ -0,0 +1,20 @@
+<?php
+
+
+namespace App\Controllers;
+
+use Gregwar\Captcha\CaptchaBuilder;
+
+class ResController
+{
+    public function captcha($request, $response, $args)
+    {
+        $id = $args['id'];
+        $builder = new CaptchaBuilder;
+        $builder->build();
+        //$builder->getPhrase();
+        $newResponse = $response->withHeader('Content-type', ' image/jpeg');//->getBody()->write($builder->output());
+        $newResponse->getBody()->write($builder->output());
+        return $newResponse;
+    }
+}

+ 1713 - 0
app/Controllers/UserController.php

@@ -0,0 +1,1713 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Services\Auth;
+use App\Models\Node;
+use App\Models\TrafficLog;
+use App\Models\InviteCode;
+use App\Models\CheckInLog;
+use App\Models\Ann;
+use App\Models\Speedtest;
+use App\Models\Shop;
+use App\Models\Coupon;
+use App\Models\Bought;
+use App\Models\Ticket;
+use App\Services\Config;
+use App\Utils\Hash;
+use App\Utils\Tools;
+use App\Utils\Radius;
+use App\Utils\Wecenter;
+use App\Models\RadiusBan;
+use App\Models\DetectLog;
+use App\Models\DetectRule;
+
+use voku\helper\AntiXSS;
+
+use App\Models\User;
+use App\Models\Code;
+use App\Models\Ip;
+use App\Models\Paylist;
+use App\Models\LoginIp;
+use App\Models\BlockIp;
+use App\Models\UnblockIp;
+use App\Models\Payback;
+use App\Models\Relay;
+use App\Utils\QQWry;
+use App\Utils\GA;
+use App\Utils\Geetest;
+use App\Utils\Telegram;
+use App\Utils\TelegramSessionManager;
+use App\Utils\Pay;
+use App\Utils\URL;
+use App\Services\Mail;
+
+/**
+ *  HomeController
+ */
+class UserController extends BaseController
+{
+    private $user;
+
+    public function __construct()
+    {
+        $this->user = Auth::getUser();
+    }
+
+    public function index($request, $response, $args)
+    {
+
+        $user = $this->user;
+
+        $ios_token = LinkController::GenerateIosCode("smart", 0, $this->user->id, 0, "smart");
+
+        $acl_token = LinkController::GenerateAclCode("smart", 0, $this->user->id, 0, "smart");
+
+        $router_token = LinkController::GenerateRouterCode($this->user->id, 0);
+        $router_token_without_mu = LinkController::GenerateRouterCode($this->user->id, 1);
+
+        $ssr_sub_token = LinkController::GenerateSSRSubCode($this->user->id, 0);
+
+        $uid = time().rand(1, 10000) ;
+        if (Config::get('enable_geetest_checkin') == 'true') {
+            $GtSdk = Geetest::get($uid);
+        } else {
+            $GtSdk = null;
+        }
+
+        $Ann = Ann::orderBy('date', 'desc')->first();
+
+
+        return $this->view()->assign("ssr_sub_token", $ssr_sub_token)->assign("router_token", $router_token)
+                ->assign("router_token_without_mu", $router_token_without_mu)->assign("acl_token", $acl_token)
+                ->assign('ann', $Ann)->assign('geetest_html', $GtSdk)->assign("ios_token", $ios_token)
+                ->assign('enable_duoshuo', Config::get('enable_duoshuo'))->assign('duoshuo_shortname', Config::get('duoshuo_shortname'))
+                ->assign("user", $this->user)->registerClass("URL", "App\Utils\URL")->assign('baseUrl', Config::get('baseUrl'))->display('user/index.tpl');
+    }
+
+
+ public function panel($request, $response, $args)
+    {
+
+        $user = $this->user;
+
+        $ios_token = LinkController::GenerateIosCode("smart", 0, $this->user->id, 0, "smart");
+
+        $acl_token = LinkController::GenerateAclCode("smart", 0, $this->user->id, 0, "smart");
+
+        $router_token = LinkController::GenerateRouterCode($this->user->id, 0);
+        $router_token_without_mu = LinkController::GenerateRouterCode($this->user->id, 1);
+
+        $ssr_sub_token = LinkController::GenerateSSRSubCode($this->user->id, 0);
+
+        $uid = time().rand(1, 10000) ;
+        if (Config::get('enable_geetest_checkin') == 'true') {
+            $GtSdk = Geetest::get($uid);
+        } else {
+            $GtSdk = null;
+        }
+
+        $Ann = Ann::orderBy('date', 'desc')->first();
+
+
+        return $this->view()->assign("ssr_sub_token", $ssr_sub_token)->assign("router_token", $router_token)
+                ->assign("router_token_without_mu", $router_token_without_mu)->assign("acl_token", $acl_token)
+                ->assign('ann', $Ann)->assign('geetest_html', $GtSdk)->assign("ios_token", $ios_token)
+                ->assign('enable_duoshuo', Config::get('enable_duoshuo'))->assign('duoshuo_shortname', Config::get('duoshuo_shortname'))
+                ->assign("user", $this->user)->registerClass("URL", "App\Utils\URL")->assign('baseUrl', Config::get('baseUrl'))->display('user/panel.tpl');
+    }
+    public function lookingglass($request, $response, $args)
+    {
+        $Speedtest=Speedtest::where("datetime", ">", time()-Config::get('Speedtest_duration')*3600)->orderBy('datetime', 'desc')->get();
+
+        return $this->view()->assign('speedtest', $Speedtest)->assign('hour', Config::get('Speedtest_duration'))->display('user/lookingglass.tpl');
+    }
+
+
+ public function node_admin($request, $response, $args)
+    {
+        $user = Auth::getUser();
+        if ($user->is_admin) {
+            $nodes = Node::where('type', 1)->orderBy('name')->get();
+        } else {
+            $nodes = Node::where(
+                function ($query) {
+                    $query->Where("node_group", "=", $this->user->node_group)
+                        ->orWhere("node_group", "=", 0);
+                }
+            )->where('type', 1)->where("node_class", "<=", $this->user->class)->orderBy('name')->get();
+        }
+
+        $relay_rules = Relay::where('user_id', $this->user->id)->orwhere('user_id', 0)->orderBy('id', 'asc')->get();
+
+        if (!Tools::is_protocol_relay($user)) {
+            $relay_rules = array();
+        }
+
+        $node_prefix=array();
+        $node_method=array();
+        $a=0;
+        $node_order=array();
+        $node_alive=array();
+        $node_prealive=array();
+        $node_heartbeat=array();
+        $node_bandwidth=array();
+        $node_muport=array();
+        $node_isv6=array();
+        if ($user->is_admin) {
+            $ports_count = Node::where('type', 1)->where('sort', 9)->orderBy('name')->count();
+        } else {
+            $ports_count = Node::where(
+                function ($query) use ($user) {
+                    $query->Where("node_group", "=", $user->node_group)
+                        ->orWhere("node_group", "=", 0);
+                }
+            )->where('type', 1)->where('sort', 9)->where("node_class", "<=", $user->class)->orderBy('name')->count();
+        }
+
+        $ports_count += 1;
+
+        foreach ($nodes as $node) {
+            if ((($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0))||$user->is_admin)&&(!$node->isNodeTrafficOut())) {
+                if ($node->sort==9) {
+                    $mu_user=User::where('port', '=', $node->server)->first();
+                    $mu_user->obfs_param=$this->user->getMuMd5();
+                    array_push($node_muport, array('server'=>$node,'user'=>$mu_user));
+                    continue;
+                }
+
+                $temp=explode(" - ", $node->name);
+
+                $node_isv6[$temp[0]]=$node->isv6;
+
+
+                if (!isset($node_prefix[$temp[0]])) {
+                    $node_prefix[$temp[0]]=array();
+                    $node_order[$temp[0]]=$a;
+                    $node_alive[$temp[0]]=0;
+
+                    if (isset($temp[1])) {
+                        $node_method[$temp[0]]=$temp[1];
+                    } else {
+                        $node_method[$temp[0]]="";
+                    }
+
+                    $a++;
+                }
+
+
+                if ($node->sort==0||$node->sort==7||$node->sort==8||$node->sort==10) {
+                    $node_tempalive=$node->getOnlineUserCount();
+                    $node_prealive[$node->id]=$node_tempalive;
+                    if ($node->isNodeOnline() !== null) {
+                        if ($node->isNodeOnline() === false) {
+                            $node_heartbeat[$temp[0]]="离线";
+                        } else {
+                            $node_heartbeat[$temp[0]]="在线";
+                        }
+                    } else {
+                        if (!isset($node_heartbeat[$temp[0]])) {
+                            $node_heartbeat[$temp[0]]="暂无数据";
+                        }
+                    }
+
+                    if ($node->node_bandwidth_limit==0) {
+                        $node_bandwidth[$temp[0]]=(int)($node->node_bandwidth/1024/1024/1024)." GB / 不限";
+                    } else {
+                        $node_bandwidth[$temp[0]]=(int)($node->node_bandwidth/1024/1024/1024)." GB / ".(int)($node->node_bandwidth_limit/1024/1024/1024)." GB - ".$node->bandwidthlimit_resetday." 日重置";
+                    }
+
+                    if ($node_tempalive!="暂无数据") {
+                        $node_alive[$temp[0]]=$node_alive[$temp[0]]+$node_tempalive;
+                    }
+                } else {
+                    $node_prealive[$node->id]="暂无数据";
+                    if (!isset($node_heartbeat[$temp[0]])) {
+                        $node_heartbeat[$temp[0]]="暂无数据";
+                    }
+                }
+
+                if (isset($temp[1])) {
+                    if (strpos($node_method[$temp[0]], $temp[1])===false) {
+                        $node_method[$temp[0]]=$node_method[$temp[0]]." ".$temp[1];
+                    }
+                }
+
+
+
+
+
+                array_push($node_prefix[$temp[0]], $node);
+            }
+        }
+        $node_prefix=(object)$node_prefix;
+        $node_order=(object)$node_order;
+        $tools = new Tools();
+        return $this->view()->assign('relay_rules', $relay_rules)->assign('node_isv6', $node_isv6)->assign('tools', $tools)->assign('node_method', $node_method)->assign('node_muport', $node_muport)->assign('node_bandwidth', $node_bandwidth)->assign('node_heartbeat', $node_heartbeat)->assign('node_prefix', $node_prefix)->assign('node_prealive', $node_prealive)->assign('node_order', $node_order)->assign('user', $user)->assign('node_alive', $node_alive)->display('user/node_admin.tpl');
+    }
+
+    public function code($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $codes = Code::where('type', '<>', '-2')->where('userid', '=', $this->user->id)->orderBy('id', 'desc')->paginate(15, ['*'], 'page', $pageNum);
+        $codes->setPath('/user/code');
+        return $this->view()->assign('codes', $codes)->assign('pmw', Pay::getHTML($this->user))->display('user/code.tpl');
+    }
+
+
+
+
+    public function donate($request, $response, $args)
+    {
+        if (Config::get('enable_donate') != 'true') {
+            exit(0);
+        }
+
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $codes = Code::where(
+            function ($query) {
+                $query->where("type", "=", -1)
+                    ->orWhere("type", "=", -2);
+            }
+        )->where("isused", 1)->orderBy('id', 'desc')->paginate(15, ['*'], 'page', $pageNum);
+        $codes->setPath('/user/donate');
+        return $this->view()->assign('codes', $codes)->assign('total_in', Code::where('isused', 1)->where('type', -1)->sum('number'))->assign('total_out', Code::where('isused', 1)->where('type', -2)->sum('number'))->display('user/donate.tpl');
+    }
+
+
+
+    public function code_check($request, $response, $args)
+    {
+        $time = $request->getQueryParams()["time"];
+        $codes = Code::where('userid', '=', $this->user->id)->where('usedatetime', '>', date('Y-m-d H:i:s', $time))->first();
+        if ($codes!=null && strpos($codes->code, "充值") !== false) {
+            $res['ret'] = 1;
+            return $response->getBody()->write(json_encode($res));
+        } else {
+            $res['ret'] = 0;
+            return $response->getBody()->write(json_encode($res));
+        }
+    }
+
+    public function f2fpayget($request, $response, $args)
+    {
+        $time = $request->getQueryParams()["time"];
+        $res['ret'] = 1;
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function f2fpay($request, $response, $args)
+    {
+        $amount = $request->getParam('amount');
+        if ($amount == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "订单金额错误:".$amount;
+            return $response->getBody()->write(json_encode($res));
+        }
+        $user = $this->user;
+        
+        //生成二维码
+        $qrPayResult = Pay::alipay_get_qrcode($user, $amount, $qrPay);
+        //  根据状态值进行业务处理
+        switch ($qrPayResult->getTradeStatus()){
+            case "SUCCESS":
+                $aliresponse = $qrPayResult->getResponse();
+                $res['ret'] = 1;
+                $res['msg'] = "二维码生成成功";
+                $res['amount'] = $amount;
+                $res['qrcode'] = $qrPay->create_erweima($aliresponse->qr_code);
+                
+                break;
+            case "FAILED":
+                $res['ret'] = 0;
+                $res['msg'] = "支付宝创建订单二维码失败!!! 请使用其他方式付款。";
+
+                break;
+            case "UNKNOWN":
+                $res['ret'] = 0;
+                $res['msg'] = "系统异常,状态未知!!!!!! 请使用其他方式付款。";
+                
+                break;
+            default:
+                $res['ret'] = 0;
+                $res['msg'] = "创建订单二维码返回异常!!!!!! 请使用其他方式付款。";
+                
+                break;
+        }
+        
+        return $response->getBody()->write(json_encode($res));
+    }
+    
+    public function alipay($request, $response, $args)
+    {
+        $amount = $request->getQueryParams()["amount"];
+        Pay::getGen($this->user, $amount);
+    }
+
+
+    public function codepost($request, $response, $args)
+    {
+        $code = $request->getParam('code');
+        $user = $this->user;
+
+
+
+        if ($code == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "请填好充值码";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $codeq=Code::where("code", "=", $code)->where("isused", "=", 0)->first();
+        if ($codeq == null) {
+            $res['ret'] = 0;
+            $res['msg'] = "此充值码错误";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $codeq->isused=1;
+        $codeq->usedatetime=date("Y-m-d H:i:s");
+        $codeq->userid=$user->id;
+        $codeq->save();
+
+        if ($codeq->type==-1) {
+            $user->money=($user->money+$codeq->number);
+            $user->save();
+
+            if ($user->ref_by!=""&&$user->ref_by!=0&&$user->ref_by!=null) {
+                $gift_user=User::where("id", "=", $user->ref_by)->first();
+                $gift_user->money=($gift_user->money+($codeq->number*(Config::get('code_payback')/100)));
+                $gift_user->save();
+
+                $Payback=new Payback();
+                $Payback->total=$codeq->number;
+                $Payback->userid=$this->user->id;
+                $Payback->ref_by=$this->user->ref_by;
+                $Payback->ref_get=$codeq->number*(Config::get('code_payback')/100);
+                $Payback->datetime=time();
+                $Payback->save();
+            }
+
+            $res['ret'] = 1;
+            $res['msg'] = "充值成功,充值的金额为".$codeq->number."元。";
+
+            if (Config::get('enable_donate') == 'true') {
+                if ($this->user->is_hide == 1) {
+                    Telegram::Send("姐姐姐姐,一位不愿透露姓名的大老爷给我们捐了 ".$codeq->number." 元呢~");
+                } else {
+                    Telegram::Send("姐姐姐姐,".$this->user->user_name." 大老爷给我们捐了 ".$codeq->number." 元呢~");
+                }
+            }
+
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if ($codeq->type==10001) {
+            $user->transfer_enable=$user->transfer_enable+$codeq->number*1024*1024*1024;
+            $user->save();
+        }
+
+        if ($codeq->type==10002) {
+            if (time()>strtotime($user->expire_in)) {
+                $user->expire_in=date("Y-m-d H:i:s", time()+$codeq->number*86400);
+            } else {
+                $user->expire_in=date("Y-m-d H:i:s", strtotime($user->expire_in)+$codeq->number*86400);
+            }
+            $user->save();
+        }
+
+        if ($codeq->type>=1&&$codeq->type<=10000) {
+            if ($user->class==0||$user->class!=$codeq->type) {
+                $user->class_expire=date("Y-m-d H:i:s", time());
+                $user->save();
+            }
+            $user->class_expire=date("Y-m-d H:i:s", strtotime($user->class_expire)+$codeq->number*86400);
+            $user->class=$codeq->type;
+            $user->save();
+        }
+    }
+
+
+
+
+    public function GaCheck($request, $response, $args)
+    {
+        $code = $request->getParam('code');
+        $user = $this->user;
+
+
+
+        if ($code == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $ga = new GA();
+        $rcode = $ga->verifyCode($user->ga_token, $code);
+        if (!$rcode) {
+            $res['ret'] = 0;
+            $res['msg'] = "测试错误";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+
+        $res['ret'] = 1;
+        $res['msg'] = "测试成功";
+        return $response->getBody()->write(json_encode($res));
+    }
+
+
+    public function GaSet($request, $response, $args)
+    {
+        $enable = $request->getParam('enable');
+        $user = $this->user;
+
+
+
+        if ($enable == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user->ga_enable=$enable;
+        $user->save();
+
+
+        $res['ret'] = 1;
+        $res['msg'] = "设置成功";
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function ResetPort($request, $response, $args)
+    {
+        $user = $this->user;
+
+        $origin_port = $user->port;
+
+        $user->port = Tools::getAvPort();
+        $user->save();
+
+        $relay_rules = Relay::where('user_id', $user->id)->where('port', $origin_port)->get();
+        foreach ($relay_rules as $rule) {
+            $rule->port = $user->port;
+            $rule->save();
+        }
+
+
+        $res['ret'] = 1;
+        $res['msg'] = "设置成功,新端口是".$user->port;
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function GaReset($request, $response, $args)
+    {
+        $user = $this->user;
+        $ga = new GA();
+        $secret = $ga->createSecret();
+
+        $user->ga_token=$secret;
+        $user->save();
+        $newResponse = $response->withStatus(302)->withHeader('Location', '/user/edit');
+        return $newResponse;
+    }
+
+
+    public function nodeAjax($request, $response, $args)
+    {
+        $id = $args['id'];
+        $point_node=Node::find($id);
+        $prefix=explode(" - ", $point_node->name);
+        return $this->view()->assign('point_node', $point_node)->assign('prefix', $prefix[0])->assign('id', $id)->display('user/nodeajax.tpl');
+    }
+
+
+ 
+    public function node($request, $response, $args)
+    {
+        $user = Auth::getUser();
+            $nodes = Node::where('type', 1)->orderBy('name')->get();
+        $relay_rules = Relay::where('user_id', $this->user->id)->orwhere('user_id', 0)->orderBy('id', 'asc')->get();
+
+        if (!Tools::is_protocol_relay($user)) {
+            $relay_rules = array();
+        }
+
+        $node_prefix=array();
+        $node_method=array();
+        $a=0;
+        $node_order=array();
+        $node_alive=array();
+        $node_prealive=array();
+        $node_heartbeat=array();
+        $node_bandwidth=array();
+        $node_muport=array();
+        $node_isv6=array();
+        $node_class=array();
+
+            $ports_count = Node::where('type', 1)->where('sort', 9)->orderBy('name')->count();
+  
+
+        $ports_count += 1;
+
+        foreach ($nodes as $node) {
+            if (((($user->node_group==$node->node_group||$node->node_group==0))||$user->is_admin)&&(!$node->isNodeTrafficOut())) {
+                if ($node->sort==9) {
+                    $mu_user=User::where('port', '=', $node->server)->first();
+                    $mu_user->obfs_param=$this->user->getMuMd5();
+                    array_push($node_muport, array('server'=>$node,'user'=>$mu_user));
+                    continue;
+                }
+
+                $temp=explode(" - ", $node->name);
+
+                $node_isv6[$temp[0]]=$node->isv6;
+                $node_class[$temp[0]]=$node->node_class;
+
+
+                if (!isset($node_prefix[$temp[0]])) {
+                    $node_prefix[$temp[0]]=array();
+                    $node_order[$temp[0]]=$a;
+                    $node_alive[$temp[0]]=0;
+
+                    if (isset($temp[1])) {
+                        $node_method[$temp[0]]=$temp[1];
+                    } else {
+                        $node_method[$temp[0]]="";
+                    }
+
+                    $a++;
+                }
+
+
+                if ($node->sort==0||$node->sort==7||$node->sort==8||$node->sort==10) {
+                    $node_tempalive=$node->getOnlineUserCount();
+                    $node_prealive[$node->id]=$node_tempalive;
+                    if ($node->isNodeOnline() !== null) {
+                        if ($node->isNodeOnline() === false) {
+                            $node_heartbeat[$temp[0]]="离线";
+                        } else {
+                            $node_heartbeat[$temp[0]]="在线";
+                        }
+                    } else {
+                        if (!isset($node_heartbeat[$temp[0]])) {
+                            $node_heartbeat[$temp[0]]="暂无数据";
+                        }
+                    }
+
+                    if ($node->node_bandwidth_limit==0) {
+                        $node_bandwidth[$temp[0]]=(int)($node->node_bandwidth/1024/1024/1024)." GB 已用";
+                    } else {
+                        $node_bandwidth[$temp[0]]=(int)($node->node_bandwidth/1024/1024/1024)." GB / ".(int)($node->node_bandwidth_limit/1024/1024/1024)." GB - ".$node->bandwidthlimit_resetday." 日重置";
+                    }
+
+                    if ($node_tempalive!="暂无数据") {
+                        $node_alive[$temp[0]]=$node_alive[$temp[0]]+$node_tempalive;
+                    }
+                } else {
+                    $node_prealive[$node->id]="暂无数据";
+                    if (!isset($node_heartbeat[$temp[0]])) {
+                        $node_heartbeat[$temp[0]]="暂无数据";
+                    }
+                }
+
+                if (isset($temp[1])) {
+                    if (strpos($node_method[$temp[0]], $temp[1])===false) {
+                        $node_method[$temp[0]]=$node_method[$temp[0]]." ".$temp[1];
+                    }
+                }
+
+
+
+
+
+                array_push($node_prefix[$temp[0]], $node);
+            }
+        }
+        $node_prefix=(object)$node_prefix;
+        $node_order=(object)$node_order;
+        $tools = new Tools();
+        return $this->view()->assign('relay_rules', $relay_rules)->assign('node_class', $node_class)->assign('node_isv6', $node_isv6)->assign('tools', $tools)->assign('node_method', $node_method)->assign('node_muport', $node_muport)->assign('node_bandwidth', $node_bandwidth)->assign('node_heartbeat', $node_heartbeat)->assign('node_prefix', $node_prefix)->assign('node_prealive', $node_prealive)->assign('node_order', $node_order)->assign('user', $user)->assign('node_alive', $node_alive)->display('user/node.tpl');
+    }
+
+
+
+    public function nodeInfo($request, $response, $args)
+    {
+        $user = Auth::getUser();
+        $id = $args['id'];
+        $mu = $request->getQueryParams()["ismu"];
+        $relay_rule_id = $request->getQueryParams()["relay_rule"];
+        $node = Node::find($id);
+
+        if ($node == null) {
+            return null;
+        }
+
+
+        switch ($node->sort) {
+
+            case 0:
+                if ((($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0))||$user->is_admin)&&($node->node_bandwidth_limit==0||$node->node_bandwidth<$node->node_bandwidth_limit)) {
+                    return $this->view()->assign('node', $node)->assign('user', $user)->assign('mu', $mu)->assign('relay_rule_id', $relay_rule_id)->registerClass("URL", "App\Utils\URL")->display('user/nodeinfo.tpl');
+                }
+            break;
+
+            case 1:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $json_show="VPN 信息<br>地址:".$node->server."<br>"."用户名:".$email."<br>密码:".$this->user->passwd."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfovpn.tpl');
+                }
+            break;
+
+            case 2:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $json_show="SSH 信息<br>地址:".$node->server."<br>"."用户名:".$email."<br>密码:".$this->user->passwd."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfossh.tpl');
+                }
+
+            break;
+
+
+            case 3:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $exp = explode(":", $node->server);
+                    $token = LinkController::GenerateCode(3, $exp[0], $exp[1], 0, $this->user->id);
+                    $json_show="PAC 信息<br>地址:".Config::get('baseUrl')."/link/".$token."<br>"."用户名:".$email."<br>密码:".$this->user->passwd."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfopac.tpl');
+                }
+
+            break;
+
+            case 4:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $json_show="APN 信息<br>下载地址:".$node->server."<br>"."用户名:".$email."<br>密码:".$this->user->passwd."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfoapn.tpl');
+                }
+
+            break;
+
+            case 5:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+
+                    $json_show="Anyconnect 信息<br>地址:".$node->server."<br>"."用户名:".$email."<br>密码:".$this->user->passwd."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfoanyconnect.tpl');
+                }
+
+
+            break;
+
+            case 6:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $exp = explode(":", $node->server);
+
+                    $token_cmcc = LinkController::GenerateApnCode("cmnet", $exp[0], $exp[1], $this->user->id);
+                    $token_cnunc = LinkController::GenerateApnCode("3gnet", $exp[0], $exp[1], $this->user->id);
+                    $token_ctnet = LinkController::GenerateApnCode("ctnet", $exp[0], $exp[1], $this->user->id);
+
+                    $json_show="APN 文件<br>移动地址:".Config::get('baseUrl')."/link/".$token_cmcc."<br>联通地址:".Config::get('baseUrl')."/link/".$token_cnunc."<br>电信地址:".Config::get('baseUrl')."/link/".$token_ctnet."<br>"."用户名:".$email."<br>密码:".$this->user->passwd."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfoapndownload.tpl');
+                }
+
+
+            break;
+
+            case 7:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $token = LinkController::GenerateCode(7, $node->server, ($this->user->port-20000), 0, $this->user->id);
+                    $json_show="PAC Plus 信息<br>PAC 地址:".Config::get('baseUrl')."/link/".$token."<br>支持方式:".$node->method."<br>备注:".$node->info;
+
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfopacplus.tpl');
+                }
+
+
+            break;
+
+            case 8:
+                if ($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0)) {
+                    $email=$this->user->email;
+                    $email=Radius::GetUserName($email);
+                    $token = LinkController::GenerateCode(8, $node->server, ($this->user->port-20000), 0, $this->user->id);
+                    $token_ios = LinkController::GenerateCode(8, $node->server, ($this->user->port-20000), 1, $this->user->id);
+                    $json_show="PAC Plus Plus信息<br>PAC 一般地址:".Config::get('baseUrl')."/link/".$token."<br>PAC iOS 地址:".Config::get('baseUrl')."/link/".$token_ios."<br>"."备注:".$node->info;
+
+                    return $this->view()->assign('json_show', $json_show)->display('user/nodeinfopacpp.tpl');
+                }
+
+
+            break;
+
+
+            case 10:
+                if ((($user->class>=$node->node_class&&($user->node_group==$node->node_group||$node->node_group==0))||$user->is_admin)&&($node->node_bandwidth_limit==0||$node->node_bandwidth<$node->node_bandwidth_limit)) {
+                    return $this->view()->assign('node', $node)->assign('user', $user)->assign('mu', $mu)->assign('relay_rule_id', $relay_rule_id)->registerClass("URL", "App\Utils\URL")->display('user/nodeinfo.tpl');
+                }
+                break;
+            default:
+                echo "微笑";
+
+        }
+    }
+
+    public function GetPcConf($request, $response, $args)
+    {
+        $is_mu = $request->getQueryParams()["is_mu"];
+        $is_ss = $request->getQueryParams()["is_ss"];
+
+        $newResponse = $response->withHeader('Content-type', ' application/octet-stream')->withHeader('Content-Disposition', ' attachment; filename=gui-config.json');//->getBody()->write($builder->output());
+        $newResponse->getBody()->write(LinkController::GetPcConf($this->user, $is_mu, $is_ss));
+
+        return $newResponse;
+    }
+
+    public function GetIosConf($request, $response, $args)
+    {
+        $newResponse = $response->withHeader('Content-type', ' application/octet-stream')->withHeader('Content-Disposition', ' attachment; filename=allinone.conf');//->getBody()->write($builder->output());
+        if ($this->user->is_admin) {
+            $newResponse->getBody()->write(LinkController::GetIosConf(Node::where(
+                function ($query) {
+                    $query->where('sort', 0)
+                        ->orWhere('sort', 10);
+                }
+            )->where("type", "1")->get(), $this->user));
+        } else {
+            $newResponse->getBody()->write(LinkController::GetIosConf(Node::where(
+                function ($query) {
+                    $query->where('sort', 0)
+                        ->orWhere('sort', 10);
+                }
+            )->where("type", "1")->where(
+                function ($query) {
+                    $query->where("node_group", "=", $this->user->node_group)
+                        ->orWhere("node_group", "=", 0);
+                }
+            )->where("node_class", "<=", $this->user->class)->get(), $this->user));
+        }
+        return $newResponse;
+    }
+
+
+    public function profile($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $paybacks = Payback::where("ref_by", $this->user->id)->orderBy("datetime", "desc")->paginate(15, ['*'], 'page', $pageNum);
+        $paybacks->setPath('/user/profile');
+
+        $iplocation = new QQWry();
+
+        $userip=array();
+
+        $total = Ip::where("datetime",">=",time()-300)->where('userid', '=',$this->user->id)->get();
+
+        $totallogin = LoginIp::where('userid', '=', $this->user->id)->where("type", "=", 0)->orderBy("datetime", "desc")->take(10)->get();
+
+        $userloginip=array();
+
+        foreach ($totallogin as $single) {
+            //if(isset($useripcount[$single->userid]))
+            {
+                if (!isset($userloginip[$single->ip])) {
+                    //$useripcount[$single->userid]=$useripcount[$single->userid]+1;
+                    $location=$iplocation->getlocation($single->ip);
+                    $userloginip[$single->ip]=iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+                }
+            }
+        }
+
+        foreach($total as $single)
+        {
+            //if(isset($useripcount[$single->userid]))
+            {
+                $single->ip = Tools::getRealIp($single->ip);
+                $is_node = Node::where("node_ip", $single->ip)->first();
+                if($is_node) {
+                    continue;
+                }
+
+
+                if(!isset($userip[$single->ip]))
+                {
+                    //$useripcount[$single->userid]=$useripcount[$single->userid]+1;
+                    $location=$iplocation->getlocation($single->ip);
+                    $userip[$single->ip]=iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+                }
+            }
+        }
+
+
+
+        return $this->view()->assign("userip",$userip)->assign("userloginip", $userloginip)->assign("paybacks", $paybacks)->display('user/profile.tpl');
+    }
+
+
+    public function announcement($request, $response, $args)
+    {
+        $Anns = Ann::orderBy('date', 'desc')->get();
+
+
+
+        return $this->view()->assign("anns", $Anns)->display('user/announcement.tpl');
+    }
+
+
+
+
+    public function edit($request, $response, $args)
+    {
+        $themes=Tools::getDir(BASE_PATH."/resources/views");
+
+        $BIP = BlockIp::where("ip", $_SERVER["REMOTE_ADDR"])->first();
+        if ($BIP == null) {
+            $Block = "IP: ".$_SERVER["REMOTE_ADDR"]." 没有被封";
+            $isBlock = 0;
+        } else {
+            $Block = "IP: ".$_SERVER["REMOTE_ADDR"]." 已被封";
+            $isBlock = 1;
+        }
+
+        $bind_token = TelegramSessionManager::add_bind_session($this->user);
+
+        $config_service = new Config();
+
+        return $this->view()->assign('user', $this->user)->assign('themes', $themes)->assign('isBlock', $isBlock)->assign('Block', $Block)->assign('bind_token', $bind_token)->assign('telegram_bot', Config::get('telegram_bot'))->assign('config_service', $config_service)
+                    ->registerClass("URL", "App\Utils\URL")->display('user/edit.tpl');
+    }
+
+
+    public function invite($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $codes=InviteCode::where('user_id', $this->user->id)->orderBy("created_at", "desc")->paginate(15, ['*'], 'page', $pageNum);
+        $codes->setPath('/user/invite');
+
+
+
+        return $this->view()->assign('codes', $codes)->display('user/invite.tpl');
+    }
+
+    public function doInvite($request, $response, $args)
+    {
+        $n = $this->user->invite_num;
+        if ($n < 1) {
+            $res['ret'] = 0;
+            $res['msg'] = "失败";
+            return $response->getBody()->write(json_encode($res));
+        }
+        for ($i = 0; $i < $n; $i++) {
+            $char = Tools::genRandomChar(32);
+            $code = new InviteCode();
+            $code->code = $char;
+            $code->user_id = $this->user->id;
+            $code->save();
+        }
+        $this->user->invite_num = 0;
+        $this->user->save();
+        $res['ret'] = 1;
+        $res['msg'] = "生成成功。";
+        return $this->echoJson($response, $res);
+    }
+
+    public function sys()
+    {
+        return $this->view()->assign('ana', "")->display('user/sys.tpl');
+    }
+
+    public function updatePassword($request, $response, $args)
+    {
+        $oldpwd = $request->getParam('oldpwd');
+        $pwd = $request->getParam('pwd');
+        $repwd = $request->getParam('repwd');
+        $user = $this->user;
+        if (!Hash::checkPassword($user->pass, $oldpwd)) {
+            $res['ret'] = 0;
+            $res['msg'] = "旧密码错误";
+            return $response->getBody()->write(json_encode($res));
+        }
+        if ($pwd != $repwd) {
+            $res['ret'] = 0;
+            $res['msg'] = "两次输入不符合";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (strlen($pwd) < 8) {
+            $res['ret'] = 0;
+            $res['msg'] = "密码太短啦";
+            return $response->getBody()->write(json_encode($res));
+        }
+        $hashPwd = Hash::passwordHash($pwd);
+        $user->pass = $hashPwd;
+        $user->save();
+
+        $user->clean_link();
+
+        $res['ret'] = 1;
+        $res['msg'] = "修改成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function updateHide($request, $response, $args)
+    {
+        $hide = $request->getParam('hide');
+        $user = $this->user;
+        $user->is_hide = $hide;
+        $user->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "修改成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function Unblock($request, $response, $args)
+    {
+        $user = $this->user;
+        $BIP = BlockIp::where("ip", $_SERVER["REMOTE_ADDR"])->get();
+        foreach ($BIP as $bi) {
+            $bi->delete();
+        }
+
+        $UIP = new UnblockIp();
+        $UIP->userid = $user->id;
+        $UIP->ip = $_SERVER["REMOTE_ADDR"];
+        $UIP->datetime = time();
+        $UIP->save();
+
+
+        $res['ret'] = 1;
+        $res['msg'] = "发送解封命令解封 ".$_SERVER["REMOTE_ADDR"]." 成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function shop($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $shops = Shop::where("status", 1)->paginate(15, ['*'], 'page', $pageNum);
+        $shops->setPath('/user/shop');
+
+        return $this->view()->assign('shops', $shops)->display('user/shop.tpl');
+    }
+
+    public function CouponCheck($request, $response, $args)
+    {
+        $coupon = $request->getParam('coupon');
+        $shop = $request->getParam('shop');
+
+        $shop=Shop::where("id", $shop)->where("status", 1)->first();
+
+        if ($shop==null) {
+            $res['ret'] = 0;
+            $res['msg'] = "非法请求";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if ($coupon=="") {
+            $res['ret'] = 1;
+            $res['name'] = $shop->name;
+            $res['credit'] = "0 %";
+            $res['total'] = $shop->price."元";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $coupon=Coupon::where("code", $coupon)->first();
+
+        if ($coupon==null) {
+            $res['ret'] = 0;
+            $res['msg'] = "优惠码无效";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if ($coupon->order($shop->id)==false) {
+            $res['ret'] = 0;
+            $res['msg'] = "此优惠码不可用于此商品";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $res['ret'] = 1;
+        $res['name'] = $shop->name;
+        $res['credit'] = $coupon->credit." %";
+        $res['total'] = $shop->price*((100-$coupon->credit)/100)."元";
+
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function buy($request, $response, $args)
+    {
+        $coupon = $request->getParam('coupon');
+        $code = $coupon;
+        $shop = $request->getParam('shop');
+
+        $autorenew = $request->getParam('autorenew');
+
+        $shop=Shop::where("id", $shop)->where("status", 1)->first();
+
+        if ($shop==null) {
+            $res['ret'] = 0;
+            $res['msg'] = "非法请求";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if ($coupon=="") {
+            $credit=0;
+        } else {
+            $coupon=Coupon::where("code", $coupon)->first();
+
+            if ($coupon==null) {
+                $credit=0;
+            } else {
+                if ($coupon->onetime==1) {
+                    $onetime=true;
+                }
+
+                $credit=$coupon->credit;
+            }
+
+            if ($coupon->order($shop->id)==false) {
+                $res['ret'] = 0;
+                $res['msg'] = "此优惠码不可用于此商品";
+                return $response->getBody()->write(json_encode($res));
+            }
+
+            if ($coupon->expire<time()) {
+                $res['ret'] = 0;
+                $res['msg'] = "此优惠码已过期";
+                return $response->getBody()->write(json_encode($res));
+            }
+        }
+
+        $price=$shop->price*((100-$credit)/100);
+        $user=$this->user;
+
+        if ((float)$user->money<(float)$price) {
+            $res['ret'] = 0;
+            $res['msg'] = "余额不足,总价为".$price."元。";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user->money=$user->money-$price;
+        $user->save();
+
+        $bought=new Bought();
+        $bought->userid=$user->id;
+        $bought->shopid=$shop->id;
+        $bought->datetime=time();
+        if ($autorenew==0||$shop->auto_renew==0) {
+            $bought->renew=0;
+        } else {
+            $bought->renew=time()+$shop->auto_renew*86400;
+        }
+
+        $bought->coupon=$code;
+
+
+        if (isset($onetime)) {
+            $price=$shop->price;
+        }
+        $bought->price=$price;
+        $bought->save();
+
+        $shop->buy($user);
+
+        $res['ret'] = 1;
+        $res['msg'] = "购买成功";
+
+        return $response->getBody()->write(json_encode($res));
+    }
+
+    public function bought($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $shops = Bought::where("userid", $this->user->id)->orderBy("id", "desc")->paginate(15, ['*'], 'page', $pageNum);
+        $shops->setPath('/user/bought');
+
+        return $this->view()->assign('shops', $shops)->display('user/bought.tpl');
+    }
+
+    public function deleteBoughtGet($request, $response, $args)
+    {
+        $id = $request->getParam('id');
+        $shop = Bought::where("id", $id)->where("userid", $this->user->id)->first();
+
+        if ($shop==null) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "退订失败,订单不存在。";
+            return $response->getBody()->write(json_encode($rs));
+        }
+
+        if ($this->user->id==$shop->userid) {
+            $shop->renew=0;
+        }
+
+        if (!$shop->save()) {
+            $rs['ret'] = 0;
+            $rs['msg'] = "退订失败";
+            return $response->getBody()->write(json_encode($rs));
+        }
+        $rs['ret'] = 1;
+        $rs['msg'] = "退订成功";
+        return $response->getBody()->write(json_encode($rs));
+    }
+
+
+    public function ticket($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $tickets = Ticket::where("userid", $this->user->id)->where("rootid", 0)->orderBy("datetime", "desc")->paginate(15, ['*'], 'page', $pageNum);
+        $tickets->setPath('/user/ticket');
+
+        return $this->view()->assign('tickets', $tickets)->display('user/ticket.tpl');
+    }
+
+    public function ticket_create($request, $response, $args)
+    {
+        return $this->view()->display('user/ticket_create.tpl');
+    }
+
+    public function ticket_add($request, $response, $args)
+    {
+        $title = $request->getParam('title');
+        $content = $request->getParam('content');
+
+
+        if ($title==""||$content=="") {
+            $res['ret'] = 0;
+            $res['msg'] = "请填全";
+            return $this->echoJson($response, $res);
+        }
+
+        if (strpos($content, "admin")!=false||strpos($content, "user")!=false) {
+            $res['ret'] = 0;
+            $res['msg'] = "请求中有不正当的词语。";
+            return $this->echoJson($response, $res);
+        }
+
+
+        $ticket=new Ticket();
+
+        $antiXss = new AntiXSS();
+
+        $ticket->title=$antiXss->xss_clean($title);
+        $ticket->content=$antiXss->xss_clean($content);
+        $ticket->rootid=0;
+        $ticket->userid=$this->user->id;
+        $ticket->datetime=time();
+        $ticket->save();
+
+        $adminUser = User::where("is_admin", "=", "1")->get();
+        foreach ($adminUser as $user) {
+            $subject = Config::get('appName')."-新工单被开启";
+            $to = $user->email;
+            $text = "管理员您好,有人开启了新的工单,请您及时处理。。" ;
+            try {
+                Mail::send($to, $subject, 'news/warn.tpl', [
+                    "user" => $user,"text" => $text
+                ], [
+                ]);
+            } catch (Exception $e) {
+                echo $e->getMessage();
+            }
+        }
+
+        $res['ret'] = 1;
+        $res['msg'] = "提交成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function ticket_update($request, $response, $args)
+    {
+        $id = $args['id'];
+        $content = $request->getParam('content');
+        $status = $request->getParam('status');
+
+        if ($content==""||$status=="") {
+            $res['ret'] = 0;
+            $res['msg'] = "请填全";
+            return $this->echoJson($response, $res);
+        }
+
+        if (strpos($content, "admin")!=false||strpos($content, "user")!=false) {
+            $res['ret'] = 0;
+            $res['msg'] = "请求中有不正当的词语。";
+            return $this->echoJson($response, $res);
+        }
+
+
+        $ticket_main=Ticket::where("id", "=", $id)->where("rootid", "=", 0)->first();
+        if ($ticket_main->userid!=$this->user->id) {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/user/ticket');
+            return $newResponse;
+        }
+
+        if ($status==1&&$ticket_main->status!=$status) {
+            $adminUser = User::where("is_admin", "=", "1")->get();
+            foreach ($adminUser as $user) {
+                $subject = Config::get('appName')."-工单被重新开启";
+                $to = $user->email;
+                $text = "管理员您好,有人重新开启了<a href=\"".Config::get('baseUrl')."/admin/ticket/".$ticket_main->id."/view\">工单</a>,请您及时处理。" ;
+                try {
+                    Mail::send($to, $subject, 'news/warn.tpl', [
+                        "user" => $user,"text" => $text
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+            }
+        } else {
+            $adminUser = User::where("is_admin", "=", "1")->get();
+            foreach ($adminUser as $user) {
+                $subject = Config::get('appName')."-工单被回复";
+                $to = $user->email;
+                $text = "管理员您好,有人回复了<a href=\"".Config::get('baseUrl')."/admin/ticket/".$ticket_main->id."/view\">工单</a>,请您及时处理。" ;
+                try {
+                    Mail::send($to, $subject, 'news/warn.tpl', [
+                        "user" => $user,"text" => $text
+                    ], [
+                    ]);
+                } catch (Exception $e) {
+                    echo $e->getMessage();
+                }
+            }
+        }
+
+        $antiXss = new AntiXSS();
+
+        $ticket=new Ticket();
+        $ticket->title=$antiXss->xss_clean($ticket_main->title);
+        $ticket->content=$antiXss->xss_clean($content);
+        $ticket->rootid=$ticket_main->id;
+        $ticket->userid=$this->user->id;
+        $ticket->datetime=time();
+        $ticket_main->status=$status;
+
+        $ticket_main->save();
+        $ticket->save();
+
+
+
+
+        $res['ret'] = 1;
+        $res['msg'] = "提交成功";
+        return $this->echoJson($response, $res);
+    }
+
+    public function ticket_view($request, $response, $args)
+    {
+        $id = $args['id'];
+        $ticket_main=Ticket::where("id", "=", $id)->where("rootid", "=", 0)->first();
+        if ($ticket_main->userid!=$this->user->id) {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/user/ticket');
+            return $newResponse;
+        }
+
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+
+
+        $ticketset=Ticket::where("id", $id)->orWhere("rootid", "=", $id)->orderBy("datetime", "desc")->paginate(5, ['*'], 'page', $pageNum);
+        $ticketset->setPath('/user/ticket/'.$id."/view");
+
+
+        return $this->view()->assign('ticketset', $ticketset)->assign("id", $id)->display('user/ticket_view.tpl');
+    }
+
+
+    public function updateWechat($request, $response, $args)
+    {
+        $type = $request->getParam('imtype');
+        $wechat = $request->getParam('wechat');
+
+        $user = $this->user;
+
+        if ($user->telegram_id != 0) {
+            $res['ret'] = 0;
+            $res['msg'] = "您绑定了 Telegram ,所以此项并不能被修改。";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if ($wechat == ""||$type == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "请填好";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user1 = User::where('im_value', $wechat)->where('im_type', $type)->first();
+        if ($user1 != null) {
+            $res['ret'] = 0;
+            $res['msg'] = "此联络方式已经被注册了";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user->im_type = $type;
+        $antiXss = new AntiXSS();
+        $user->im_value = $antiXss->xss_clean($wechat);
+        $user->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "修改成功";
+        return $this->echoJson($response, $res);
+    }
+
+
+    public function updateSSR($request, $response, $args)
+    {
+        $protocol = $request->getParam('protocol');
+        $obfs = $request->getParam('obfs');
+
+        $user = $this->user;
+
+        if ($obfs == ""||$protocol == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "请填好";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (!Tools::is_param_validate('obfs', $obfs)) {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (!Tools::is_param_validate('protocol', $protocol)) {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $antiXss = new AntiXSS();
+
+        $user->protocol = $antiXss->xss_clean($protocol);
+        $user->obfs = $antiXss->xss_clean($obfs);
+
+
+        if (!Tools::checkNoneProtocol($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "您好,系统检测到您目前的加密方式为 none ,但您将要设置为的协议并不在以下协议<br>".implode(',', Config::getSupportParam('allow_none_protocol')).'<br>之内,请您先修改您的加密方式,再来修改此处设置。';
+            return $this->echoJson($response, $res);
+        }
+
+        if(!URL::SSCanConnect($user) && !URL::SSRCanConnect($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "您这样设置之后,就没有客户端能连接上了,所以系统拒绝了您的设置,请您检查您的设置之后再进行操作。";
+            return $this->echoJson($response, $res);
+        }
+
+        $user->save();
+
+        if(!URL::SSCanConnect($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "设置成功,但您目前的协议,混淆,加密方式设置会导致 Shadowsocks原版客户端无法连接,请您自行更换到 ShadowsocksR 客户端。";
+            return $this->echoJson($response, $res);
+        }
+
+        if(!URL::SSRCanConnect($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "设置成功,但您目前的协议,混淆,加密方式设置会导致 ShadowsocksR 客户端无法连接,请您自行更换到 Shadowsocks 客户端。";
+            return $this->echoJson($response, $res);
+        }
+
+        $res['ret'] = 0;
+        $res['msg'] = "设置成功,您可自由选用客户端来连接。";
+        return $this->echoJson($response, $res);
+    }
+
+    public function updateTheme($request, $response, $args)
+    {
+        $theme = $request->getParam('theme');
+
+        $user = $this->user;
+
+        if ($theme == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "???";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+
+        $user->theme = filter_var($theme, FILTER_SANITIZE_STRING);
+        $user->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "ok";
+        return $this->echoJson($response, $res);
+    }
+
+
+    public function updateMail($request, $response, $args)
+    {
+        $mail = $request->getParam('mail');
+
+        $user = $this->user;
+
+        if (!($mail == "1"||$mail == "0")) {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+
+        $user->sendDailyMail = $mail;
+        $user->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "ok";
+        return $this->echoJson($response, $res);
+    }
+
+    public function PacSet($request, $response, $args)
+    {
+        $pac = $request->getParam('pac');
+
+        $user = $this->user;
+
+        if ($pac == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+
+        $user->pac = $pac;
+        $user->save();
+
+        $res['ret'] = 1;
+        $res['msg'] = "ok";
+        return $this->echoJson($response, $res);
+    }
+
+
+    public function updateSsPwd($request, $response, $args)
+    {
+        $user = Auth::getUser();
+        $pwd = $request->getParam('sspwd');
+
+        if ($pwd == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (!Tools::is_validate($pwd)) {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user->updateSsPwd($pwd);
+        $res['ret'] = 1;
+
+
+        Radius::Add($user, $pwd);
+
+
+
+
+        return $this->echoJson($response, $res);
+    }
+
+    public function updateMethod($request, $response, $args)
+    {
+        $user = Auth::getUser();
+        $method = $request->getParam('method');
+        $method = strtolower($method);
+
+        if ($method == "") {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        if (!Tools::is_param_validate('method', $method)) {
+            $res['ret'] = 0;
+            $res['msg'] = "悟空别闹";
+            return $response->getBody()->write(json_encode($res));
+        }
+
+        $user->method = $method;
+
+        if (!Tools::checkNoneProtocol($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "您好,系统检测到您将要设置的加密方式为 none ,但您的协议并不在以下协议<br>".implode(',', Config::getSupportParam('allow_none_protocol')).'<br>之内,请您先修改您的协议,再来修改此处设置。';
+            return $this->echoJson($response, $res);
+        }
+
+        if(!URL::SSCanConnect($user) && !URL::SSRCanConnect($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "您这样设置之后,就没有客户端能连接上了,所以系统拒绝了您的设置,请您检查您的设置之后再进行操作。";
+            return $this->echoJson($response, $res);
+        }
+
+        $user->updateMethod($method);
+
+        if(!URL::SSCanConnect($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "设置成功,但您目前的协议,混淆,加密方式设置会导致 Shadowsocks原版客户端无法连接,请您自行更换到 ShadowsocksR 客户端。";
+            return $this->echoJson($response, $res);
+        }
+
+        if(!URL::SSRCanConnect($user)) {
+            $res['ret'] = 0;
+            $res['msg'] = "设置成功,但您目前的协议,混淆,加密方式设置会导致 ShadowsocksR 客户端无法连接,请您自行更换到 Shadowsocks 客户端。";
+            return $this->echoJson($response, $res);
+        }
+
+        $res['ret'] = 0;
+        $res['msg'] = "设置成功,您可自由选用两种客户端来进行连接。";
+        return $this->echoJson($response, $res);
+    }
+
+    public function logout($request, $response, $args)
+    {
+        Auth::logout();
+        $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login');
+        return $newResponse;
+    }
+
+    public function doCheckIn($request, $response, $args)
+    {
+        if (Config::get('enable_geetest_checkin') == 'true') {
+            $ret = Geetest::verify($request->getParam('geetest_challenge'), $request->getParam('geetest_validate'), $request->getParam('geetest_seccode'));
+            if (!$ret) {
+                $res['ret'] = 0;
+                $res['msg'] = "系统无法接受您的验证结果,请刷新页面后重试。";
+                return $response->getBody()->write(json_encode($res));
+            }
+        }
+
+        if (!$this->user->isAbleToCheckin()) {
+            $res['msg'] = "您似乎已经续命过了...";
+            $res['ret'] = 1;
+            return $response->getBody()->write(json_encode($res));
+        }
+        $traffic = rand(Config::get('checkinMin'), Config::get('checkinMax'));
+        $this->user->transfer_enable = $this->user->transfer_enable + Tools::toMB($traffic);
+        $this->user->last_check_in_time = time();
+        $this->user->save();
+        $res['msg'] = sprintf("获得了 %u MB流量.", $traffic);
+        $res['ret'] = 1;
+        return $this->echoJson($response, $res);
+    }
+
+    public function kill($request, $response, $args)
+    {
+        return $this->view()->display('user/kill.tpl');
+    }
+
+    public function handleKill($request, $response, $args)
+    {
+        $user = Auth::getUser();
+
+        $email=$user->email;
+
+        $passwd = $request->getParam('passwd');
+        // check passwd
+        $res = array();
+        if (!Hash::checkPassword($user->pass, $passwd)) {
+            $res['ret'] = 0;
+            $res['msg'] = " 密码错误";
+            return $this->echoJson($response, $res);
+        }
+
+ //       Auth::logout();
+   //     $user->kill_user();
+        $res['ret'] = 1;
+        $res['msg'] = "您没有使用此功能的权限。";  
+        return $this->echoJson($response, $res);
+    }
+
+    public function trafficLog($request, $response, $args)
+    {
+        $traffic=TrafficLog::where('user_id', $this->user->id)->where("log_time", ">", (time()-3*86400))->orderBy('id', 'desc')->get();
+        return $this->view()->assign('logs', $traffic)->display('user/trafficlog.tpl');
+    }
+
+
+
+
+    public function detect_index($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $logs = DetectRule::paginate(15, ['*'], 'page', $pageNum);
+        $logs->setPath('/user/detect');
+        return $this->view()->assign('rules', $logs)->display('user/detect_index.tpl');
+    }
+
+    public function detect_log($request, $response, $args)
+    {
+        $pageNum = 1;
+        if (isset($request->getQueryParams()["page"])) {
+            $pageNum = $request->getQueryParams()["page"];
+        }
+        $logs = DetectLog::orderBy('id', 'desc')->where('user_id', $this->user->id)->paginate(15, ['*'], 'page', $pageNum);
+        $logs->setPath('/user/detect/log');
+        return $this->view()->assign('logs', $logs)->display('user/detect_log.tpl');
+    }
+
+    public function disable($request, $response, $args)
+    {
+        return $this->view()->display('user/disable.tpl');
+    }
+
+    public function telegram_reset($request, $response, $args)
+    {
+        $user = $this->user;
+        $user->telegram_id = 0;
+        $user->save();
+        $newResponse = $response->withStatus(302)->withHeader('Location', '/user/edit');
+        return $newResponse;
+    }
+
+    public function resetURL($request, $response, $args)
+    {
+        $user = $this->user;
+        $user->clean_link();
+        $newResponse = $response->withStatus(302)->withHeader('Location', '/user');
+        return $newResponse;
+    }
+}

+ 29 - 0
app/Middleware/Admin.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use App\Services\Auth as AuthService;
+use App\Services\Config;
+
+class Admin
+{
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+    {
+        //$response->getBody()->write('BEFORE');
+        $user = AuthService::getUser();
+        if (!$user->isLogin) {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login');
+            return $newResponse;
+        }
+
+        if (!$user->isAdmin()) {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/user');
+            return $newResponse;
+        }
+        
+        $response = $next($request, $response);
+        return $response;
+    }
+}

+ 38 - 0
app/Middleware/Api.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use App\Services\Factory;
+use App\Utils\Helper;
+
+class Api
+{
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+    {
+        $accessToken = Helper::getTokenFromReq($request);
+        if ($accessToken==null) {
+            $res['ret'] = 0;
+            $res['msg'] = "token is null";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+        $storage = Factory::createTokenStorage();
+        $token = $storage->get($accessToken);
+        if ($token==null) {
+            $res['ret'] = 0;
+            $res['msg'] = "token is null";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+        if ($token->expireTime < time()) {
+            $res['ret'] = 0;
+            $res['msg'] = "token is expire";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+        $response = $next($request, $response);
+        return $response;
+    }
+}

+ 46 - 0
app/Middleware/Auth.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use App\Services\Auth as AuthService;
+use App\Services\Config;
+
+use App\Services\Jwt;
+
+class Auth
+{
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+    {
+        $user = AuthService::getUser();
+        if (!$user->isLogin) {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login');
+            return $newResponse;
+        }
+        
+        
+        if ($user->enable == 0 && $_SERVER["REQUEST_URI"] != "/user/disable") {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/user/disable');
+            return $newResponse;
+        }
+        
+        if (Config::get('enable_duoshuo')=='true') {
+            $token = array(
+                "short_name"=>Config::get('duoshuo_shortname'),
+                "user_key"=>$user->id,
+                "name"=>$user->user_name,
+                "email"=>$user->email
+            );
+            
+            
+            
+            $duoshuoToken = JWT::encode_withkey($token, Config::get('duoshuo_apptoken'));
+            
+            setcookie('duoshuo_token', $duoshuoToken);
+        }
+        
+        $response = $next($request, $response);
+        return $response;
+    }
+}

+ 21 - 0
app/Middleware/Guest.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use App\Services\Auth as AuthService;
+
+class Guest
+{
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+    {
+        $user = AuthService::getUser();
+        if ($user->isLogin) {
+            $newResponse = $response->withStatus(302)->withHeader('Location', '/user');
+            return $newResponse;
+        }
+        $response = $next($request, $response);
+        return $response;
+    }
+}

+ 52 - 0
app/Middleware/Mod_Mu.php

@@ -0,0 +1,52 @@
+<?php
+
+
+namespace App\Middleware;
+
+use App\Services\Config;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use App\Services\Factory;
+use App\Utils\Helper;
+use App\Models\Node;
+
+class Mod_Mu
+{
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+    {
+        $key = Helper::getMuKeyFromReq($request);
+        if ($key == null) {
+            $res['ret'] = 0;
+            $res['data'] = "key is null";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+
+        $auth=false;
+        $keyset=explode(",", Config::get('muKey'));
+        foreach ($keyset as $sinkey) {
+            if ($key==$sinkey) {
+                $auth=true;
+                break;
+            }
+        }
+
+        if ($auth==false) {
+            $res['ret'] = 0;
+            $res['data'] = "token or source is invalid";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+
+        $node = Node::where("node_ip", "LIKE", $_SERVER["REMOTE_ADDR"].'%')->first();
+        if ($node==null && $_SERVER["REMOTE_ADDR"] != '127.0.0.1') {
+            $res['ret'] = 0;
+            $res['data'] = "token or source is invalid";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+
+        $response = $next($request, $response);
+        return $response;
+    }
+}

+ 52 - 0
app/Middleware/Mu.php

@@ -0,0 +1,52 @@
+<?php
+
+
+namespace App\Middleware;
+
+use App\Services\Config;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use App\Services\Factory;
+use App\Utils\Helper;
+use App\Models\Node;
+
+class Mu
+{
+    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+    {
+        $key = Helper::getMuKeyFromReq($request);
+        if ($key == null) {
+            $res['ret'] = 0;
+            $res['msg'] = "key is null";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+
+        $auth=false;
+        $keyset=explode(",", Config::get('muKey'));
+        foreach ($keyset as $sinkey) {
+            if ($key==$sinkey) {
+                $auth=true;
+                break;
+            }
+        }
+
+        if ($auth==false) {
+            $res['ret'] = 0;
+            $res['msg'] = "token or source is invalid";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+
+        $node = Node::where("node_ip", "LIKE", $_SERVER["REMOTE_ADDR"].'%')->first();
+        if ($node==null && $_SERVER["REMOTE_ADDR"] != '127.0.0.1') {
+            $res['ret'] = 0;
+            $res['msg'] = "token or source is invalid";
+            $response->getBody()->write(json_encode($res));
+            return $response;
+        }
+
+        $response = $next($request, $response);
+        return $response;
+    }
+}

+ 15 - 0
app/Models/Ann.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Ann Model
+ */
+
+use App\Utils\Tools;
+
+class Ann extends Model
+{
+    protected $connection = "default";
+    protected $table = "announcement";
+}

+ 24 - 0
app/Models/Auto.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Models;
+
+use App\Utils\Tools;
+
+class Auto extends Model
+{
+    protected $connection = "default";
+    protected $table = "auto";
+
+
+
+    public function datetime()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+    
+    public function content()
+    {
+        return str_replace(PHP_EOL, '<br>', $this->attributes['value']);
+        ;
+    }
+}

+ 24 - 0
app/Models/BlockIp.php

@@ -0,0 +1,24 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Utils\Tools;
+
+class BlockIp extends Model
+{
+    protected $connection = "default";
+    protected $table = "blockip";
+
+    
+    
+    public function node()
+    {
+        return Node::where("id", $this->attributes['nodeid'])->first();
+    }
+    
+    public function time()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+}

+ 35 - 0
app/Models/Bought.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Models;
+
+class Bought extends Model
+{
+    protected $connection = "default";
+    protected $table = "bought";
+
+    public function renew_date()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['renew']);
+    }
+
+    public function datetime()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+    
+    public function user()
+    {
+        $user = User::where("id", $this->attributes['userid'])->first();
+        if ($user == null) {
+            Bought::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+    
+    public function shop()
+    {
+        return Shop::where("id", $this->attributes['shopid'])->first();
+    }
+}

+ 10 - 0
app/Models/CheckInLog.php

@@ -0,0 +1,10 @@
+<?php
+
+
+namespace App\Models;
+
+class CheckInLog
+{
+    protected $connection = "default";
+    protected $table = "ss_checkin_log";
+}

+ 22 - 0
app/Models/Code.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Code Model
+ */
+
+use App\Utils\Tools;
+
+class Code extends Model
+{
+    protected $connection = "default";
+    protected $table = "code";
+
+    
+    
+    public function user()
+    {
+        return User::where("id", $this->attributes['userid'])->first();
+    }
+}

+ 31 - 0
app/Models/Coupon.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Models;
+
+class Coupon extends Model
+{
+    protected $connection = "default";
+    protected $table = "coupon";
+    
+    public function expire()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['expire']);
+    }
+
+    public function order($shop)
+    {
+        if ($this->attributes['shop']=="") {
+            return true;
+        }
+        
+        $shop_array=explode(",", $this->attributes['shop']);
+        
+        foreach ($shop_array as $shopid) {
+            if ($shopid==$shop) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+}

+ 48 - 0
app/Models/DetectLog.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * DetectLog Model
+ */
+
+use App\Utils\Tools;
+
+class DetectLog extends Model
+{
+    protected $connection = "default";
+    protected $table = "detect_log";
+
+    public function DetectRule()
+    {
+        $rule = DetectRule::where("id", $this->attributes['list_id'])->first();
+        if ($rule == null) {
+            DetectLog::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $rule;
+        }
+    }
+
+    public function User()
+    {
+        $user = User::where("id", $this->attributes['user_id'])->first();
+        if ($user == null) {
+            DetectLog::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+
+    public function Node()
+    {
+        $node = Node::where("id", $this->attributes['node_id'])->first();
+        if ($node == null) {
+            DetectLog::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $node;
+        }
+    }
+}

+ 13 - 0
app/Models/DetectRule.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * DetectLog Model
+ */
+
+class DetectRule extends Model
+{
+    protected $connection = "default";
+    protected $table = "detect_list";
+}

+ 13 - 0
app/Models/Disconnect.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Disconnect Model
+ */
+
+class Disconnect extends Model
+{
+    protected $connection = "default";
+    protected $table = "disconnect_ip";
+}

+ 13 - 0
app/Models/EmailVerify.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * EmailVerify Model
+ */
+
+class EmailVerify extends Model
+{
+    protected $connection = "default";
+    protected $table = "email_verify";
+}

+ 13 - 0
app/Models/InviteCode.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * InviteCode Model
+ */
+
+class InviteCode extends Model
+{
+    protected $connection = "default";
+    protected $table = "ss_invite_code";
+}

+ 43 - 0
app/Models/Ip.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Ip Model
+ */
+
+use App\Utils\Tools;
+
+class Ip extends Model
+{
+    protected $connection = "default";
+    protected $table = "alive_ip";
+    
+    public function user()
+    {
+        $user = User::where("id", $this->attributes['userid'])->first();
+        if ($user == null) {
+            Ip::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+
+    public function Node()
+    {
+        $node = Node::where("id", $this->attributes['nodeid'])->first();
+        if ($node == null) {
+            Ip::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $node;
+        }
+    }
+    
+    
+    public function ip()
+    {
+        return str_replace("::ffff:", "", $this->attributes['ip']);
+    }
+}

+ 9 - 0
app/Models/Link.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class Link extends Model
+{
+    protected $connection = "default";
+    protected $table = "link";
+}

+ 31 - 0
app/Models/LoginIp.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Ip Model
+ */
+
+use App\Utils\Tools;
+
+class LoginIp extends Model
+{
+    protected $connection = "default";
+    protected $table = "login_ip";
+    
+    public function user()
+    {
+        $user = User::where("id", $this->attributes['userid'])->first();
+        if ($user == null) {
+            LoginIp::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+    
+    public function datetime()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+}

+ 14 - 0
app/Models/Model.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model as EloquentMedel;
+
+/**
+ * Base Model
+ */
+
+class Model extends EloquentMedel
+{
+    public $timestamps = false;
+}

+ 180 - 0
app/Models/Node.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Node Model
+ */
+
+use App\Utils\Tools;
+
+class Node extends Model
+{
+    protected $connection = "default";
+    protected $table = "ss_node";
+
+    protected $casts = [
+        'node_speedlimit' => 'float',
+        'traffic_rate' => 'float',
+        'mu_only' => 'int',
+        'sort' => 'int',
+    ];
+
+    public function getLastNodeInfoLog()
+    {
+        $id = $this->attributes['id'];
+        $log = NodeInfoLog::where('node_id', $id)->orderBy('id', 'desc')->first();
+        if ($log == null) {
+            return null;
+        }
+        return $log;
+    }
+
+    public function getNodeUptime()
+    {
+        $log = $this->getLastNodeInfoLog();
+        if ($log == null) {
+            return "暂无数据";
+        }
+        return Tools::secondsToTime((int)$log->uptime);
+    }
+
+
+    public function getNodeUpRate()
+    {
+        $id = $this->attributes['id'];
+        $log = NodeOnlineLog::where('node_id', $id)->where('log_time', '>=', time()-86400)->count();
+
+        return $log/1440;
+    }
+
+    public function getNodeLoad()
+    {
+        $id = $this->attributes['id'];
+        $log = NodeInfoLog::where('node_id', $id)->orderBy('id', 'desc')->whereRaw('`log_time`%1800<60')->limit(48)->get();
+        return $log;
+    }
+
+    public function getNodeAlive()
+    {
+        $id = $this->attributes['id'];
+        $log = NodeOnlineLog::where('node_id', $id)->orderBy('id', 'desc')->whereRaw('`log_time`%1800<60')->limit(48)->get();
+        return $log;
+    }
+
+    public function getOnlineUserCount()
+    {
+        $id = $this->attributes['id'];
+        $log = NodeOnlineLog::where('node_id', $id)->orderBy('id', 'desc')->first();
+        if ($log == null) {
+            return "暂无数据";
+        }
+        return $log->online_user;
+    }
+
+    public function getSpeedtest()
+    {
+        $id = $this->attributes['id'];
+        $log = Speedtest::where('nodeid', $id)->orderBy('datetime', 'desc')->first();
+        if ($log == null) {
+            return "暂无数据";
+        }
+
+
+        return "电信延迟:".$log->telecomping." 下载:".$log->telecomeupload." 上传:".$log->telecomedownload."<br>
+		联通延迟:".$log->unicomping." 下载:".$log->unicomupload." 上传:".$log->unicomdownload."<br>
+		移动延迟:".$log->cmccping." 下载:".$log->cmccupload." 上传:".$log->cmccdownload."<br>定时测试,仅供参考";
+    }
+
+    public function getSpeedtestResult()
+    {
+        $id = $this->attributes['id'];
+        $log = Speedtest::where('nodeid', $id)->orderBy('id', 'desc')->limit(48)->get();
+        if ($log == null) {
+            return "暂无数据";
+        }
+
+
+        return $log;
+    }
+
+    public function getTrafficFromLogs()
+    {
+        $id = $this->attributes['id'];
+
+        $traffic = TrafficLog::where('node_id', $id)->sum('u') + TrafficLog::where('node_id', $id)->sum('d');
+
+        if ($traffic == 0) {
+            return "暂无数据";
+        }
+
+        return Tools::flowAutoShow($traffic);
+    }
+
+    public function isNodeOnline()
+    {
+        $node_heartbeat = $this->attributes['node_heartbeat'];
+        $sort = $this->attributes['sort'];
+
+        if (!($sort == 0 || $sort == 7 || $sort == 8 || $sort==10)) {
+            return null;
+        }
+
+        if ($node_heartbeat == 0) {
+            return null;
+        }
+
+        if (time() - $node_heartbeat > 300) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public function isNodeTrafficOut()
+    {
+        $node_bandwidth = $this->attributes['node_bandwidth'];
+        $node_bandwidth_limit = $this->attributes['node_bandwidth_limit'];
+
+        if ($node_bandwidth_limit == 0 || $node_bandwidth < $node_bandwidth_limit) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public function isNodeAccessable()
+    {
+        if ($this->isNodeTrafficOut() == false && $this->isNodeOnline() == true) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function changeNodeIp($server_name)
+    {
+        $ip = gethostbyname($server_name);
+        $node_id = $this->attributes['id'];
+
+        if ($ip == "") {
+            return false;
+        }
+
+        $relay_rules = Relay::where('dist_node_id', $node_id)->get();
+        foreach ($relay_rules as $relay_rule) {
+            $relay_rule->dist_ip = $ip;
+            $relay_rule->save();
+        }
+
+        $this->attributes['node_ip'] = $ip;
+        return true;
+    }
+
+    public function getNodeIp()
+    {
+        $node_ip_str = $this->attributes['node_ip'];
+        $node_ip_array = explode(',', $node_ip_str);
+        return $node_ip_array[0];
+    }
+}

+ 21 - 0
app/Models/NodeInfoLog.php

@@ -0,0 +1,21 @@
+<?php
+namespace App\Models;
+
+class NodeInfoLog extends Model
+{
+    protected $connection = "default";
+    protected $table = "ss_node_info";
+    
+    public function getNodeLoad()
+    {
+        $load = $this->attributes['load'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getTime()
+    {
+        $time = $this->attributes['log_time'];
+        return date('Y-m-d H:i:s', $time);
+    }
+}

+ 10 - 0
app/Models/NodeOnlineLog.php

@@ -0,0 +1,10 @@
+<?php
+
+
+namespace App\Models;
+
+class NodeOnlineLog extends Model
+{
+    protected $connection = "default";
+    protected $table = "ss_node_online_log";
+}

+ 9 - 0
app/Models/PasswordReset.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class PasswordReset extends Model
+{
+    protected $connection = "default";
+    protected $table = 'ss_password_reset';
+}

+ 20 - 0
app/Models/Payback.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Models;
+
+class Payback extends Model
+{
+    protected $connection = "default";
+    protected $table = 'payback';
+    
+    public function user()
+    {
+        $user = User::where("id", $this->attributes['userid'])->first();
+        if ($user == null) {
+            Bought::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+}

+ 9 - 0
app/Models/Paylist.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class Paylist extends Model
+{
+    protected $connection = "default";
+    protected $table = 'paylist';
+}

+ 9 - 0
app/Models/RadiusBan.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class RadiusBan extends Model
+{
+    protected $connection = "default";
+    protected $table = "radius_ban";
+}

+ 9 - 0
app/Models/RadiusNas.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class RadiusNas extends Model
+{
+    protected $connection = "radius";
+    protected $table = "nas";
+}

+ 9 - 0
app/Models/RadiusRadAcct.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class RadiusRadAcct extends Model
+{
+    protected $connection = "radius";
+    protected $table = "radacct";
+}

+ 9 - 0
app/Models/RadiusRadCheck.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class RadiusRadCheck extends Model
+{
+    protected $connection = "radius";
+    protected $table = "radcheck";
+}

+ 9 - 0
app/Models/RadiusRadPostauth.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class RadiusRadPostauth extends Model
+{
+    protected $connection = "radius";
+    protected $table = "radpostauth";
+}

+ 9 - 0
app/Models/RadiusRadUserGroup.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class RadiusRadUserGroup extends Model
+{
+    protected $connection = "radius";
+    protected $table = "radusergroup";
+}

+ 50 - 0
app/Models/Relay.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * DetectLog Model
+ */
+
+class Relay extends Model
+{
+    protected $connection = "default";
+    protected $table = "relay";
+
+    public function User()
+    {
+        $user = User::where("id", $this->attributes['user_id'])->first();
+        if ($user == null) {
+            Relay::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+
+    public function Source_Node()
+    {
+        $node = Node::where("id", $this->attributes['source_node_id'])->first();
+        if ($node == null && $this->attributes['source_node_id'] != 0) {
+            Relay::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $node;
+        }
+    }
+
+    public function Dist_Node()
+    {
+        if ($this->attributes['dist_node_id'] == -1) {
+            return null;
+        }
+
+        $node = Node::where("id", $this->attributes['dist_node_id'])->first();
+        if ($node == null) {
+            Relay::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $node;
+        }
+    }
+}

+ 13 - 0
app/Models/Role.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Node Model
+ */
+
+class Role extends Model
+{
+    protected $connection = "default";
+    protected $table = "user_role";
+}

+ 166 - 0
app/Models/Shop.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace App\Models;
+
+use App\Services\Config;
+
+class Shop extends Model
+{
+    protected $connection = "default";
+    protected $table = "shop";
+
+    public function content()
+    {
+        $content = json_decode($this->attributes['content'], true);
+        $content_text="";
+        $i = 0;
+        foreach ($content as $key=>$value) {
+            switch ($key) {
+                case "bandwidth":
+                    $content_text .= "添加流量 ".$value." G ";
+                    break;
+                case "expire":
+                    $content_text .= "为账号的有效期添加 ".$value." 天 ";
+                    break;
+                case "class":
+                    $content_text .= "为账号升级为等级 ".$value." ,有效期 ".$content["class_expire"]." 天";
+                    break;
+                case "reset":
+                    $content_text .= " 在 ".$content["reset_exp"]." 天内,每 ".$value." 天重置流量为 ".$content["reset_value"]." G ";
+                    break;
+                default:
+            }
+
+            if ($i<count($content)&&$key!="reset_exp") {
+                $content_text .= ",";
+            }
+
+            $i++;
+        }
+
+        if (substr($content_text, -1, 1)==",") {
+            $content_text=substr($content_text, 0, -1);
+        }
+
+        return $content_text;
+    }
+
+    public function bandwidth()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->bandwidth)) {
+            return $content->bandwidth;
+        } else {
+            return 0;
+        }
+    }
+
+    public function expire()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->expire)) {
+            return $content->expire;
+        } else {
+            return 0;
+        }
+    }
+
+    public function reset()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->reset)) {
+            return $content->reset;
+        } else {
+            return 0;
+        }
+    }
+
+    public function reset_value()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->reset_value)) {
+            return $content->reset_value;
+        } else {
+            return 0;
+        }
+    }
+
+    public function reset_exp()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->reset_exp)) {
+            return $content->reset_exp;
+        } else {
+            return 0;
+        }
+    }
+
+    public function user_class()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->class)) {
+            return $content->class;
+        } else {
+            return 0;
+        }
+    }
+
+    public function class_expire()
+    {
+        $content =  json_decode($this->attributes['content']);
+        if (isset($content->class_expire)) {
+            return $content->class_expire;
+        } else {
+            return 0;
+        }
+    }
+
+    public function buy($user, $is_renew = 0)
+    {
+        $content = json_decode($this->attributes['content'], true);
+        $content_text="";
+
+        foreach ($content as $key=>$value) {
+            switch ($key) {
+                case "bandwidth":
+                    if ($is_renew == 0) {
+                        if (Config::get('enable_bought_reset') == 'true') {
+                            $user->transfer_enable=$value*1024*1024*1024;
+                            $user->u = 0;
+                            $user->d = 0;
+                            $user->last_day_t = 0;
+                        } else {
+                            $user->transfer_enable=$user->transfer_enable+$value*1024*1024*1024;
+                        }
+                    } else {
+                        if ($this->attributes['auto_reset_bandwidth'] == 1) {
+                            $user->transfer_enable=$value*1024*1024*1024;
+                            $user->u = 0;
+                            $user->d = 0;
+                            $user->last_day_t = 0;
+                        } else {
+                            $user->transfer_enable=$user->transfer_enable+$value*1024*1024*1024;
+                        }
+                    }
+                    break;
+                case "expire":
+                    if (time()>strtotime($user->expire_in)) {
+                        $user->expire_in=date("Y-m-d H:i:s", time()+$value*86400);
+                    } else {
+                        $user->expire_in=date("Y-m-d H:i:s", strtotime($user->expire_in)+$value*86400);
+                    }
+                    break;
+                case "class":
+                    if ($user->class==0||$user->class!=$value) {
+                        $user->class_expire=date("Y-m-d H:i:s", time());
+                    }
+                    $user->class_expire=date("Y-m-d H:i:s", strtotime($user->class_expire)+$content["class_expire"]*86400);
+                    $user->class=$value;
+                    break;
+                default:
+            }
+        }
+
+        $user->save();
+    }
+}

+ 85 - 0
app/Models/Speedtest.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Node Model
+ */
+
+use App\Utils\Tools;
+
+class Speedtest extends Model
+{
+    protected $connection = "default";
+    protected $table = "speedtest";
+    
+    public function node()
+    {
+        return Node::find($this->attributes['nodeid']);
+    }
+
+    
+    public function getTelecomPing()
+    {
+        $load = $this->attributes['telecomping'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getUnicomPing()
+    {
+        $load = $this->attributes['unicomping'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getCmccPing()
+    {
+        $load = $this->attributes['cmccping'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+
+    public function getTelecomUpload()
+    {
+        $load = $this->attributes['telecomeupload'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getTelecomDownload()
+    {
+        $load = $this->attributes['telecomedownload'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getUnicomUpload()
+    {
+        $load = $this->attributes['unicomupload'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getUnicomDownload()
+    {
+        $load = $this->attributes['unicomdownload'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getCmccDownload()
+    {
+        $load = $this->attributes['cmccdownload'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+    
+    public function getCmccUpload()
+    {
+        $load = $this->attributes['cmccupload'];
+        $exp = explode(" ", $load);
+        return $exp[0];
+    }
+}

+ 29 - 0
app/Models/TelegramSession.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * TelegramSession Model
+ */
+
+class TelegramSession extends Model
+{
+    protected $connection = "default";
+    protected $table = "telegram_session";
+
+    public function datetime()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+    
+    public function User()
+    {
+        $user = User::where("id", $this->attributes['user_id'])->first();
+        if ($user == null) {
+            Ticket::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+}

+ 29 - 0
app/Models/Ticket.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * Ticket Model
+ */
+
+class Ticket extends Model
+{
+    protected $connection = "default";
+    protected $table = "ticket";
+
+    public function datetime()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+    
+    public function User()
+    {
+        $user = User::where("id", $this->attributes['userid'])->first();
+        if ($user == null) {
+            Ticket::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+}

+ 9 - 0
app/Models/Token.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Models;
+
+class Token extends Model
+{
+    protected $connection = "default";
+    protected $table = "user_token";
+}

+ 49 - 0
app/Models/TrafficLog.php

@@ -0,0 +1,49 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Utils\Tools;
+
+class TrafficLog extends Model
+{
+    protected $connection = "default";
+    protected $table = "user_traffic_log";
+
+    public function node()
+    {
+        $node = Node::where("id", $this->attributes['node_id'])->first();
+        if ($node == null) {
+            TrafficLog::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $node;
+        }
+    }
+    
+    public function user()
+    {
+        $user = User::where("id", $this->attributes['user_id'])->first();
+        if ($user == null) {
+            TrafficLog::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+
+    public function totalUsed()
+    {
+        return Tools::flowAutoShow($this->attributes['u'] + $this->attributes['d']);
+    }
+    
+    public function totalUsedRaw()
+    {
+        return number_format(($this->attributes['u'] + $this->attributes['d'])/1024, 2, ".", "");
+    }
+
+    public function logTime()
+    {
+        return Tools::toDateTime($this->attributes['log_time']);
+    }
+}

+ 28 - 0
app/Models/UnblockIp.php

@@ -0,0 +1,28 @@
+<?php
+
+
+namespace App\Models;
+
+use App\Utils\Tools;
+
+class UnblockIp extends Model
+{
+    protected $connection = "default";
+    protected $table = "unblockip";
+
+    public function user()
+    {
+        $user = User::where("id", $this->attributes['userid'])->first();
+        if ($user == null) {
+            UnblockIp::where('id', '=', $this->attributes['id'])->delete();
+            return null;
+        } else {
+            return $user;
+        }
+    }
+    
+    public function time()
+    {
+        return date("Y-m-d H:i:s", $this->attributes['datetime']);
+    }
+}

+ 337 - 0
app/Models/User.php

@@ -0,0 +1,337 @@
+<?php
+
+namespace App\Models;
+
+/**
+ * User Model
+ */
+
+use App\Utils\Tools;
+use App\Utils\Hash;
+use App\Models\InviteCode;
+use App\Services\Config;
+use App\Utils\GA;
+use App\Utils\QQWry;
+use App\Models\Link;
+use App\Utils\Wecenter;
+use App\Utils\Radius;
+
+class User extends Model
+{
+    protected $connection = "default";
+    protected $table = "user";
+
+    public $isLogin;
+
+    public $isAdmin;
+
+    protected $casts = [
+        "t" => 'float',
+        "u" => 'float',
+        "d" => 'float',
+        "port" => 'int',
+        "transfer_enable" => 'float',
+        "enable" => 'int',
+        'is_admin' => 'boolean',
+        'is_multi_user' => 'int',
+        'node_speedlimit' => 'float',
+    ];
+
+    public function getGravatarAttribute()
+    {
+      //  $hash = md5(strtolower(trim($this->attributes['email'])));
+        return "/images/Avatar.jpg";//.$hash;
+    }
+
+    public function isAdmin()
+    {
+        return $this->attributes['is_admin'];
+    }
+
+    public function lastSsTime()
+    {
+        if ($this->attributes['t'] == 0) {
+            return "从未使用喵";
+        }
+        return Tools::toDateTime($this->attributes['t']);
+    }
+
+    public function getMuMd5()
+    {
+        $str = str_replace("%id", $this->attributes['id'], Config::get('mu_regex'));
+        $str = str_replace("%suffix", Config::get('mu_suffix'), $str);
+        preg_match_all("|%-?[1-9]\d*m|U", $str, $matches, PREG_PATTERN_ORDER);
+        foreach ($matches[0] as $key) {
+            $key_match = str_replace("%", "", $key);
+            $key_match = str_replace("m", "", $key_match);
+            $md5 = substr(MD5($this->attributes['id'].$this->attributes['passwd'].$this->attributes['method'].$this->attributes['obfs'].$this->attributes['protocol']),
+            ($key_match < 0 ? $key_match : 0),
+            abs($key_match));
+            $str = str_replace($key, $md5, $str);
+        }
+        return $str;
+    }
+
+    public function lastCheckInTime()
+    {
+        if ($this->attributes['last_check_in_time'] == 0) {
+            return "从未签到";
+        }
+        return Tools::toDateTime($this->attributes['last_check_in_time']);
+    }
+
+    public function regDate()
+    {
+        return $this->attributes['reg_date'];
+    }
+
+    public function updatePassword($pwd)
+    {
+        $this->pass = Hash::passwordHash($pwd);
+        $this->save();
+    }
+
+    public function get_forbidden_ip()
+    {
+        return str_replace(",", PHP_EOL, $this->attributes['forbidden_ip']);
+    }
+
+    public function get_forbidden_port()
+    {
+        return str_replace(",", PHP_EOL, $this->attributes['forbidden_port']);
+    }
+
+    public function updateSsPwd($pwd)
+    {
+        $this->passwd = $pwd;
+        $this->save();
+    }
+
+    public function updateMethod($method)
+    {
+        $this->method = $method;
+        $this->save();
+    }
+
+    public function addInviteCode()
+    {
+        $uid = $this->attributes['id'];
+        $code = new InviteCode();
+        $code->code = Tools::genRandomChar(32);
+        $code->user = $uid;
+        $code->save();
+    }
+
+    public function addManyInviteCodes($num)
+    {
+        for ($i = 0; $i < $num; $i++) {
+            $this->addInviteCode();
+        }
+    }
+
+    public function trafficUsagePercent()
+    {
+        $total = $this->attributes['u'] + $this->attributes['d'];
+        $transferEnable = $this->attributes['transfer_enable'];
+        if ($transferEnable == 0) {
+            return 0;
+        }
+        $percent = $total / $transferEnable;
+        $percent = round($percent, 2);
+        $percent = $percent * 100;
+        return $percent;
+    }
+
+    public function enableTraffic()
+    {
+        $transfer_enable = $this->attributes['transfer_enable'];
+        return Tools::flowAutoShow($transfer_enable);
+    }
+    public function enableTrafficInGB()
+    {
+        $transfer_enable = $this->attributes['transfer_enable'];
+        return Tools::flowToGB($transfer_enable);
+    }
+    public function usedTraffic()
+    {
+        $total = $this->attributes['u'] + $this->attributes['d'];
+        return Tools::flowAutoShow($total);
+    }
+    public function unusedTraffic()
+    {
+        $total = $this->attributes['u'] + $this->attributes['d'];
+        $transfer_enable = $this->attributes['transfer_enable'];
+        return Tools::flowAutoShow($transfer_enable - $total);
+    }
+
+    public function TodayusedTraffic()
+    {
+        $total = $this->attributes['u'] + $this->attributes['d']-$this->attributes['last_day_t'];
+        return Tools::flowAutoShow($total);
+    }
+
+    public function LastusedTraffic()
+    {
+        $total = $this->attributes['last_day_t'];
+        return Tools::flowAutoShow($total);
+    }
+
+    public function isAbleToCheckin()
+    {
+        $last = $this->attributes['last_check_in_time'];
+
+        $now = time();
+        if (date("Ymd", $now)!= date("Ymd", $last)) {
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * @param traffic 单位 MB
+     */
+    public function addTraffic($traffic)
+    {
+    }
+
+    public function getGAurl()
+    {
+        $ga = new GA();
+        $url = $ga->getUrl(urlencode(Config::get('appName')."-".$this->attributes['user_name']."-两步验证码"), $this->attributes['ga_token']);
+        return $url;
+    }
+
+    public function inviteCodes()
+    {
+        $uid = $this->attributes['id'];
+        return InviteCode::where('user_id', $uid)->get();
+    }
+
+    public function ref_by_user()
+    {
+        $uid = $this->attributes['ref_by'];
+        return User::where('id', $uid)->first();
+    }
+
+    public function clean_link()
+    {
+        $uid = $this->attributes['id'];
+        Link::where('userid', $uid)->delete();
+    }
+
+    public function online_ip_count()
+    {
+        $uid = $this->attributes['id'];
+        $total = Ip::where("datetime", ">=", time()-90)->where('userid', $uid)->orderBy('userid', 'desc')->get();
+        $unique_ip_list = array();
+        foreach ($total as $single_record) {
+            $single_record->ip = Tools::getRealIp($single_record->ip);
+            $is_node = Node::where("node_ip", $single_record->ip)->first();
+            if($is_node) {
+                continue;
+            }
+
+            if (!in_array($single_record->ip, $unique_ip_list)) {
+                array_push($unique_ip_list, $single_record->ip);
+            }
+        }
+
+        return count($unique_ip_list);
+    }
+
+    public function kill_user()
+    {
+        $uid = $this->attributes['id'];
+        $email = $this->attributes['email'];
+
+        Radius::Delete($email);
+
+        RadiusBan::where('userid', '=', $uid)->delete();
+        Disconnect::where('userid', '=', $uid)->delete();
+        Bought::where('userid', '=', $uid)->delete();
+        Bought::where('userid', '=', $uid)->delete();
+        Ip::where('userid', '=', $uid)->delete();
+        Code::where('userid', '=', $uid)->delete();
+        DetectLog::where('user_id', '=', $uid)->delete();
+        Link::where('userid', '=', $uid)->delete();
+        LoginIp::where('userid', '=', $uid)->delete();
+        InviteCode::where('user_id', '=', $uid)->delete();
+        TelegramSession::where('user_id', '=', $uid)->delete();
+        UnblockIp::where('userid', '=', $uid)->delete();
+        TrafficLog::where('user_id', '=', $uid)->delete();
+        Token::where('user_id', '=', $uid)->delete();
+        PasswordReset::where('email', '=', $email)->delete();
+
+        Wecenter::Delete($email);
+
+        $this->delete();
+
+        return true;
+    }
+
+    public function get_table_json_array()
+    {
+        $id = $this->attributes['id'];
+        $today_traffic = Tools::flowToMB($this->attributes['u'] + $this->attributes['d'] - $this->attributes['last_day_t']);
+        $is_enable = $this->attributes['enable'] == 1 ? "可用" : "禁用";
+        $reg_location = $this->attributes['reg_ip'];
+        $account_expire_in = $this->attributes['expire_in'];
+        $class_expire_in = $this->attributes['class_expire'];
+        $used_traffic = Tools::flowToGB($this->attributes['u'] + $this->attributes['d']);
+        $enable_traffic = Tools::flowToGB($this->attributes['transfer_enable']);
+
+        $im_type = '';
+        $im_value = $this->attributes['im_value'];
+        switch($this->attributes['im_type']) {
+            case 1:
+              $im_type = '微信';
+              break;
+            case 2:
+              $im_type = 'QQ';
+              break;
+            case 3:
+              $im_type = 'Google+';
+              break;
+            default:
+              $im_type = 'Telegram';
+              $im_value = '<a href="https://telegram.me/'.$im_value.'">'.$im_value.'</a>';
+        }
+
+        $ref_user = User::find($this->attributes['ref_by']);
+
+        if ($this->attributes['ref_by'] == 0) {
+            $ref_user_id = 0;
+            $ref_user_name = "系统邀请";
+        } else {
+            if ($ref_user == null) {
+                $ref_user_id = $this->attributes['ref_by'];
+                $ref_user_name = "邀请人已经被删除";
+            } else {
+                $ref_user_id = $this->attributes['ref_by'];
+                $ref_user_name = $ref_user->user_name;
+            }
+        }
+
+        $iplocation = new QQWry();
+        $location=$iplocation->getlocation($reg_location);
+        $reg_location .= "\n".iconv('gbk', 'utf-8//IGNORE', $location['country'].$location['area']);
+
+        $return_array = Array('DT_RowId' => 'row_1_'.$id, $id, $id,
+                              $this->attributes['user_name'], $this->attributes['remark'],
+                              $this->attributes['email'], $this->attributes['money'],
+                              $im_type, $im_value,
+                              $this->attributes['node_group'], $account_expire_in,
+                              $this->attributes['class'], $class_expire_in,
+                              $this->attributes['passwd'], $this->attributes['port'],
+                              $this->attributes['method'],
+                              $this->attributes['protocol'], $this->attributes['obfs'],
+                              $this->online_ip_count(), $this->lastSsTime(),
+                              $used_traffic, $enable_traffic,
+                              $this->lastCheckInTime(), $today_traffic,
+                              $is_enable, $this->attributes['reg_date'],
+                              $reg_location,
+                              $this->attributes['auto_reset_day'], $this->attributes['auto_reset_bandwidth'],
+                              $ref_user_id, $ref_user_name);
+        return $return_array;
+    }
+}

+ 10 - 0
app/Models/WecenterUser.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace App\Models;
+
+class WecenterUser extends Model
+{
+    protected $connection = "wecenter";
+    protected $table = "aws_users";
+    protected $primaryKey = 'uid';
+}

+ 31 - 0
app/Services/Analytic.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\User;
+use App\Utils\Tools;
+
+class Analytic
+{
+    public function userCount()
+    {
+        return  User::all()->count();
+    }
+
+    public function checkinUserCount()
+    {
+        return User::where('last_checkin_time', '>', 1)->count();
+    }
+
+    public function activedUserCount()
+    {
+        return User::where('t', '>', 1)->count();
+    }
+
+    public function totalTraffic()
+    {
+        $u = User::all()->sum('u');
+        $d = User::all()->sum('d');
+        return Tools::flowAutoShow($u + $d);
+    }
+}

+ 118 - 0
app/Services/Analytics.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\User;
+use App\Models\Node;
+use App\Utils\Tools;
+
+class Analytics
+{
+    public function getTotalUser()
+    {
+        return User::count();
+    }
+
+    public function getCheckinUser()
+    {
+        return User::where('last_check_in_time', '>', 0)->count();
+    }
+    
+    public function getTodayCheckinUser()
+    {
+        return User::where('last_check_in_time', '>', strtotime('today'))->count();
+    }
+
+    public function getTrafficUsage()
+    {
+        $total = User::sum('u') + User::sum('d');
+        return Tools::flowAutoShow($total);
+    }
+    
+    public function getTodayTrafficUsage()
+    {
+        $total = User::sum('u') + User::sum('d') - User::sum('last_day_t');
+        return Tools::flowAutoShow($total);
+    }
+    
+    
+    public function getRawTodayTrafficUsage()
+    {
+        $total = User::sum('u') + User::sum('d') - User::sum('last_day_t');
+        return $total;
+    }
+    
+    public function getLastTrafficUsage()
+    {
+        $total = User::sum('last_day_t');
+        return Tools::flowAutoShow($total);
+    }
+    
+    
+    public function getRawLastTrafficUsage()
+    {
+        $total = User::sum('last_day_t');
+        return $total;
+    }
+    
+    public function getUnusedTrafficUsage()
+    {
+        $total = User::sum('transfer_enable') - User::sum('u') - User::sum('d');
+        return Tools::flowAutoShow($total);
+    }
+    
+    public function getRawUnusedTrafficUsage()
+    {
+        $total = User::sum('transfer_enable') - User::sum('u') - User::sum('d');
+        return $total;
+    }
+    
+    
+    public function getTotalTraffic()
+    {
+        $total = User::sum('transfer_enable');
+        return Tools::flowAutoShow($total);
+    }
+    
+    public function getRawTotalTraffic()
+    {
+        $total = User::sum('transfer_enable');
+        return $total;
+    }
+
+    public function getOnlineUser($time)
+    {
+        $time = time() - $time;
+        return User::where('t', '>', $time)->count();
+    }
+    
+    public function getUnusedUser()
+    {
+        return User::where('t', '=', 0)->count();
+    }
+
+    public function getTotalNode()
+    {
+        return Node::count();
+    }
+    
+    public function getTotalSSNode()
+    {
+        return Node::where('node_heartbeat', '>', 0)->where(
+                    function ($query) {
+                        $query->Where('sort', '=', 0)
+                            ->orWhere('sort', '=', 10);
+                    }
+                )->count();
+    }
+    
+    public function getAliveSSNode()
+    {
+        return Node::where(
+            function ($query) {
+                $query->Where('sort', '=', 0)
+                    ->orWhere('sort', '=', 10);
+            }
+        )->where('node_heartbeat', '>', time()-90)->count();
+    }
+}

+ 35 - 0
app/Services/Auth.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Services;
+
+use App\Utils\Wecenter;
+
+class Auth
+{
+    protected $driver;
+
+    public function __construct()
+    {
+    }
+
+    private static function getDriver()
+    {
+        return Factory::createAuth();
+    }
+
+    public static function login($uid, $time)
+    {
+        self::getDriver()->login($uid, $time);
+    }
+
+    public static function getUser()
+    {
+        return self::getDriver()->getUser();
+    }
+
+    public static function logout()
+    {
+        self::getDriver()->logout();
+        Wecenter::Loginout();
+    }
+}

+ 10 - 0
app/Services/Auth/Base.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace App\Services\Auth;
+
+abstract class Base
+{
+    abstract public function login($uid, $time);
+    abstract public function logout();
+    abstract public function getUser();
+}

+ 78 - 0
app/Services/Auth/Cookie.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Services\Auth;
+
+use App\Models\User;
+use App\Models\Node;
+use App\Utils;
+use App\Utils\Hash;
+use App\Services\Config;
+
+class Cookie extends Base
+{
+    public function login($uid, $time)
+    {
+        $user = User::find($uid);
+        $key = Hash::cookieHash($user->pass);
+        $expire_in = $time+time();
+        Utils\Cookie::set([
+            "uid" => $uid,
+            "email" => $user->email,
+            "key" => $key,
+            "ip" => md5($_SERVER["REMOTE_ADDR"].Config::get('key').$uid.$expire_in),
+            "expire_in" => $expire_in
+        ], $expire_in);
+    }
+
+    public function getUser()
+    {
+        $uid = Utils\Cookie::get('uid');
+        $key = Utils\Cookie::get('key');
+        $ip = Utils\Cookie::get('ip');
+        
+        $expire_in = Utils\Cookie::get('expire_in');
+        
+        if ($uid == null) {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+        
+        $nodes=Node::where("node_ip", "=", $_SERVER["REMOTE_ADDR"])->first();
+        if ($ip != md5($_SERVER["REMOTE_ADDR"].Config::get('key').$uid.$expire_in) && $nodes==null && Config::get('enable_login_bind_ip')=='true') {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+        
+        if ($expire_in<time()) {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+
+        $user = User::find($uid);
+        if ($user == null) {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+
+        if (Hash::cookieHash($user->pass) != $key) {
+            $user->isLogin = false;
+            return $user;
+        }
+        $user->isLogin = true;
+        return $user;
+    }
+
+    public function logout()
+    {
+        $time = time() - 1000;
+        Utils\Cookie::set([
+            "uid" => null,
+            "email" => null,
+            "key" => null
+        ], $time);
+    }
+}

+ 7 - 0
app/Services/Auth/File.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Services\Auth;
+
+class File
+{
+}

+ 37 - 0
app/Services/Auth/JwtToken.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Services\Auth;
+
+use App\Utils;
+use App\Services\Jwt;
+
+class JwtToken extends Base
+{
+    public function login($uid, $time)
+    {
+        $expireTime = time() + $time;
+        $ary = [
+          "uid" => $uid,
+          "expire_time" => $expireTime
+        ];
+        $decode = Jwt::encode($ary);
+        Utils\Cookie::set([
+            //"uid" => $uid,
+            "token" => $decode
+        ], $expireTime);
+    }
+
+    public function logout()
+    {
+        Utils\Cookie::set([
+            //"uid" => $uid,
+            "token" => ""
+        ], time()-3600);
+    }
+
+    public function getUser()
+    {
+        $token = Utils\Cookie::get('token');
+        $tokenInfo = Jwt::decodeArray($token);
+    }
+}

+ 73 - 0
app/Services/Auth/Redis.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Services\Auth;
+
+use App\Models\User;
+use App\Models\Node;
+use App\Services\RedisClient;
+use App\Utils\Tools;
+use App\Utils\Cookie;
+use App\Services\Config;
+
+class Redis extends Base
+{
+    private $client;
+
+    public function __construct()
+    {
+        $client = new RedisClient();
+        $this->client = $client;
+    }
+
+    public function getClient()
+    {
+        $client = new RedisClient();
+        return $client;
+    }
+
+    public function login($uid, $time)
+    {
+        $sid = Tools::genSID();
+        Cookie::set([
+            'sid' => $sid
+        ], $time+time());
+        $value = $uid;
+        $this->client->setex($sid, $time, $value);
+        $this->client->setex($sid."ip", $time, $_SERVER["REMOTE_ADDR"]);
+    }
+
+    public function logout()
+    {
+        $sid = Cookie::get('sid');
+        $this->client->del($sid);
+    }
+
+    public function getUser()
+    {
+        $sid = Cookie::get('sid');
+        $value = $this->client->get($sid);
+        
+        $ip = $this->client->get($sid."ip");
+        $nodes=Node::where("node_ip", "=", $_SERVER["REMOTE_ADDR"])->first();
+        if ($ip != $_SERVER["REMOTE_ADDR"] && $nodes==null && Config::get('enable_login_bind_ip')=='true') {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+        
+        if ($value == null) {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+        $uid = $value;
+        $user =  User::find($uid);
+        if ($user == null) {
+            $user = new User();
+            $user->isLogin = false;
+            return $user;
+        }
+        $user->isLogin = true;
+        return $user;
+    }
+}

+ 31 - 0
app/Services/Auth/Token.php

@@ -0,0 +1,31 @@
+<?php
+
+
+namespace App\Services\Auth;
+
+use App\Services\Factory;
+
+class Token extends Base
+{
+    protected $storage;
+
+    public function __construct()
+    {
+        $this->storage = Factory::createTokenStorage();
+    }
+
+    public function login($uid, $time)
+    {
+        // TODO: Implement getUser() method.
+    }
+
+    public function logout()
+    {
+        // TODO: Implement logout() method.
+    }
+
+    public function getUser()
+    {
+        $token = Cookie::get('token');
+    }
+}

+ 19 - 0
app/Services/Aws/Client.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Services\Aws;
+
+use Aws\Sdk;
+
+class Client
+{
+    public function getClient()
+    {
+        $sdk = new Sdk([
+            'region'   => 'us-west-2',
+            'version'  => 'latest',
+            'DynamoDb' => [
+                'region' => 'eu-central-1'
+            ]
+        ]);
+    }
+}

+ 34 - 0
app/Services/Aws/Factory.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Services\Aws;
+
+use Aws\Sdk;
+use App\Services\Config;
+
+class Factory
+{
+    public static function createAwsClient()
+    {
+        $sdk = new Sdk([
+            'credentials' => array(
+                'key'    => Config::get('aws_access_key_id'),
+                'secret' => Config::get('aws_secret_access_key'),
+            ),
+            'region'   => Config::get('aws_region'),
+            'version'  => 'latest',
+            'DynamoDb' => [
+                'region' => Config::get('aws_region')
+            ]
+        ]);
+        return $sdk;
+    }
+
+    public static function createDynamodb()
+    {
+        return self::createAwsClient()->createDynamoDb();
+    }
+
+    public static function createSes()
+    {
+    }
+}

+ 50 - 0
app/Services/Boot.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Services;
+
+use Dotenv\Dotenv;
+use Illuminate\Database\Capsule\Manager as Capsule;
+
+class Boot
+{
+    public static function loadEnv()
+    {
+        // Env
+        $env = new Dotenv(BASE_PATH);
+        $env->load();
+    }
+
+    public static function setDebug()
+    {
+        // debug
+        if (Config::get('debug') == "true") {
+            define("DEBUG", true);
+        }
+    }
+
+    public static function setVersion($version)
+    {
+        $System_Config['version'] = $version;
+    }
+
+    public static function setTimezone()
+    {
+        // config time zone
+        date_default_timezone_set(Config::get('timeZone'));
+    }
+
+    public static function bootDb()
+    {
+        // Init Eloquent ORM Connection
+        $capsule = new Capsule;
+        $capsule->addConnection(Config::getDbConfig(), 'default');
+        if (Config::get('enable_radius')=='true') {
+            $capsule->addConnection(Config::getRadiusDbConfig(), 'radius');
+        }
+
+        if (Config::get('enable_wecenter')=='true') {
+            $capsule->addConnection(Config::getWecenterDbConfig(), 'wecenter');
+        }
+        $capsule->bootEloquent();
+    }
+}

+ 7 - 0
app/Services/Cache.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Services;
+
+class Cache
+{
+}

+ 111 - 0
app/Services/Config.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Services;
+
+class Config
+{
+    public static function get($key)
+    {
+        global $System_Config;
+        return $System_Config[$key];
+    }
+
+    public static function set($key, $value)
+    {
+        global $System_Config;
+        $System_Config[$key] = $value;
+    }
+
+    public static function getPublicConfig()
+    {
+        return [
+            "appName" => self::get("appName"),
+            "version" => VERSION,
+            "baseUrl" => self::get("baseUrl"),
+            "checkinMin" => self::get("checkinMin"),
+            "code_payback" => self::get("code_payback"),
+            "checkinMax" => self::get("checkinMax"),
+            "wecenter_url" => self::get("wecenter_url"),
+            "enable_wecenter" => self::get("enable_wecenter"),
+            "jump_delay" => self::get("jump_delay"),
+            "enable_analytics_code" => self::get("enable_analytics_code"),
+            "enable_donate" => self::get("enable_donate"),
+            "enable_telegram" => self::get("enable_telegram")
+         ];
+    }
+
+    public static function getDbConfig()
+    {
+        return [
+            'driver'    => self::get('db_driver'),
+            'host'      => self::get('db_host'),
+            'database'  => self::get('db_database'),
+            'username'  => self::get('db_username'),
+            'password'  => self::get('db_password'),
+            'charset'   => self::get('db_charset'),
+            'collation' => self::get('db_collation'),
+            'prefix'    => self::get('db_prefix')
+        ];
+    }
+
+    public static function getRadiusDbConfig()
+    {
+        return [
+            'driver'    => self::get('db_driver'),
+            'host'      => self::get('radius_db_host'),
+            'database'  => self::get('radius_db_database'),
+            'username'  => self::get('radius_db_user'),
+            'password'  => self::get('radius_db_password'),
+            'charset'   => self::get('db_charset'),
+            'collation' => self::get('db_collation')
+        ];
+    }
+
+    public static function getWecenterDbConfig()
+    {
+        return [
+            'driver'  => self::get('db_driver'),
+            'host'  => self::get('wecenter_db_host'),
+            'database'  => self::get('wecenter_db_database'),
+            'username'  => self::get('wecenter_db_user'),
+            'password'  => self::get('wecenter_db_password'),
+            'charset'   => self::get('db_charset'),
+            'collation' => self::get('db_collation')
+        ];
+    }
+
+    public static function getSupportParam($type)
+    {
+        switch ($type) {
+            case 'obfs':
+                $list = array('plain', 'http_simple', 'http_simple_compatible', 'http_post', 'http_post_compatible',
+                            'tls1.2_ticket_auth', 'tls1.2_ticket_auth_compatible', 'tls1.2_ticket_fastauth', 'tls1.2_ticket_fastauth_compatible',
+                            'simple_obfs_http', 'simple_obfs_http_compatible', 'simple_obfs_tls', 'simple_obfs_tls_compatible');
+                return $list;
+            case 'protocol':
+                $list = array('origin', 'verify_deflate',
+                            'auth_sha1_v4', 'auth_sha1_v4_compatible', 'auth_aes128_sha1', 'auth_aes128_md5', 'auth_chain_a', 'auth_chain_b');
+                return $list;
+            case 'allow_none_protocol':
+                $list = array('auth_chain_a', 'auth_chain_b');
+                return $list;
+            case 'relay_able_protocol':
+                $list = array('auth_aes128_md5', 'auth_aes128_sha1', 'auth_chain_a', 'auth_chain_b');
+                return $list;
+            case 'ss_aead_method':
+                $list = array('aes-128-gcm', 'aes-192-gcm',
+                'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-ietf-poly1305');
+                return $list;
+            case 'ss_obfs':
+                $list = array('simple_obfs_http', 'simple_obfs_http_compatible', 'simple_obfs_tls', 'simple_obfs_tls_compatible');
+                return $list;
+            default:
+                $list = array('rc4-md5', 'rc4-md5-6', 'aes-128-cfb', 'aes-192-cfb', 'aes-256-cfb',
+                            'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', 'camellia-128-cfb', 'camellia-192-cfb', 'camellia-256-cfb',
+                            'bf-cfb', 'cast5-cfb', 'des-cfb', 'des-ede3-cfb', 'idea-cfb',
+                            'rc2-cfb', 'seed-cfb', 'salsa20', 'chacha20', 'chacha20-ietf', 'none', 'aes-128-gcm', 'aes-192-gcm',
+                            'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-ietf-poly1305');
+                return $list;
+        }
+    }
+}

+ 46 - 0
app/Services/Factory.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Services;
+
+use App\Services\Auth\Cookie;
+use App\Services\Auth\Redis;
+use App\Services\Auth\JwtToken;
+use App\Services\Token\DB;
+use App\Services\Token\Dynamodb;
+
+class Factory
+{
+    public static function createAuth()
+    {
+        $method = Config::get('authDriver');
+        switch ($method) {
+            case 'cookie':
+                return new Cookie();
+            case 'redis':
+                return new Redis();
+            case 'jwt':
+                return new JwtToken();
+        }
+        return new Redis();
+    }
+
+    public static function createCache()
+    {
+    }
+
+    public static function createMail()
+    {
+    }
+
+    public static function createTokenStorage()
+    {
+        switch (Config::get('tokenDriver')) {
+            case 'db':
+                return new DB();
+            case 'dynamodb':
+                return new Dynamodb();
+            default:
+                return new DB();
+        }
+    }
+}

+ 29 - 0
app/Services/Jwt.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Services;
+
+use Firebase\JWT\JWT as JwtClient;
+
+class Jwt
+{
+    private static function getKey()
+    {
+        return Config::get('key');
+    }
+
+    public static function encode($input)
+    {
+        return JwtClient::encode($input, self::getKey());
+    }
+    
+    public static function encode_withkey($input, $key)
+    {
+        return JwtClient::encode($input, $key);
+    }
+
+    public static function decodeArray($input)
+    {
+        $decoded = JWT::decode($input, self::getKey(), array('HS256'));
+        return $decoded;
+    }
+}

+ 71 - 0
app/Services/Mail.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Services;
+
+/***
+ * Mail Service
+ */
+
+use App\Services\Mail\Mailgun;
+use App\Services\Mail\Ses;
+use App\Services\Mail\Smtp;
+use App\Services\Mail\SendGrid;
+use App\Services\Mail\NullMail;
+use Smarty;
+
+class Mail
+{
+    /**
+     * @return Mailgun|Ses|Smtp|null
+     */
+    public static function getClient()
+    {
+        $driver = Config::get("mailDriver");
+        switch ($driver) {
+            case "mailgun":
+                return new Mailgun();
+            case "ses":
+                return new Ses();
+            case "smtp":
+                return new Smtp();
+            case "sendgrid":
+                return new SendGrid();
+            default:
+                return new NullMail();
+        }
+        return null;
+    }
+
+    /**
+     * @param $template
+     * @param $ary
+     * @return mixed
+     */
+    public static function genHtml($template, $ary)
+    {
+        $smarty = new smarty();
+        $smarty->settemplatedir(BASE_PATH . '/resources/email/');
+        $smarty->setcompiledir(BASE_PATH . '/storage/framework/smarty/compile/');
+        $smarty->setcachedir(BASE_PATH . '/storage/framework/smarty/cache/');
+        // add config
+        $smarty->assign('config', Config::getPublicConfig());
+        foreach ($ary as $key => $value) {
+            $smarty->assign($key, $value);
+        }
+        return $smarty->fetch($template);
+    }
+
+    /**
+     * @param $to
+     * @param $subject
+     * @param $template
+     * @param $ary
+     * @param $file
+     * @return bool|void
+     */
+    public static function send($to, $subject, $template, $ary = [], $file = [])
+    {
+        $text = self::genHtml($template, $ary);
+        return self::getClient()->send($to, $subject, $text, $file);
+    }
+}

+ 9 - 0
app/Services/Mail/Base.php

@@ -0,0 +1,9 @@
+<?php
+
+
+namespace App\Services\Mail;
+
+abstract class Base
+{
+    abstract public function send($to, $subject, $text, $file);
+}

+ 46 - 0
app/Services/Mail/Mailgun.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Services\Mail;
+
+use App\Services\Config;
+use Mailgun\Mailgun as MailgunService;
+
+class Mailgun extends Base
+{
+    private $config;
+    private $mg;
+    private $domain;
+    private $sender;
+
+    public function __construct()
+    {
+        $this->config = $this->getConfig();
+        $this->mg = new MailgunService($this->config["key"]);
+        $this->domain = $this->config["domain"];
+        $this->sender = $this->config["sender"];
+    }
+
+    public function getConfig()
+    {
+        return [
+            "key" => Config::get('mailgun_key'),
+            "domain" => Config::get('mailgun_domain'),
+            "sender" => Config::get('mailgun_sender')
+        ];
+    }
+
+    public function send($to, $subject, $text, $file)
+    {
+        $this->mg->sendMessage($this->domain,
+            [
+                'from' => $this->sender,
+                'to' => $to,
+                'subject' => $subject,
+                'html' => $text
+            ],
+            [
+                'inline' => $file
+            ]
+        );
+    }
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません