浏览代码

Merge pull request #270 from v2board/dev

1.3.1
tokumeikoi 4 年之前
父节点
当前提交
b5376c9c1e
共有 59 个文件被更改,包括 1737 次插入527 次删除
  1. 2 1
      app/Console/Commands/CheckOrder.php
  2. 25 4
      app/Console/Commands/V2boardCache.php
  3. 1 1
      app/Console/Commands/V2boardInstall.php
  4. 1 0
      app/Http/Controllers/Admin/ConfigController.php
  5. 12 11
      app/Http/Controllers/Admin/CouponController.php
  6. 20 1
      app/Http/Controllers/Admin/PlanController.php
  7. 71 0
      app/Http/Controllers/Admin/Server/GroupController.php
  8. 144 0
      app/Http/Controllers/Admin/Server/TrojanController.php
  9. 15 73
      app/Http/Controllers/Admin/Server/V2rayController.php
  10. 35 33
      app/Http/Controllers/Client/AppController.php
  11. 75 135
      app/Http/Controllers/Client/ClientController.php
  12. 19 18
      app/Http/Controllers/Guest/OrderController.php
  13. 17 1
      app/Http/Controllers/Guest/TelegramController.php
  14. 1 1
      app/Http/Controllers/Passport/AuthController.php
  15. 13 8
      app/Http/Controllers/Server/DeepbworkController.php
  16. 13 10
      app/Http/Controllers/Server/PoseidonController.php
  17. 120 0
      app/Http/Controllers/Server/TrojanTidalabController.php
  18. 2 1
      app/Http/Controllers/User/CommController.php
  19. 7 0
      app/Http/Controllers/User/CouponController.php
  20. 50 14
      app/Http/Controllers/User/OrderController.php
  21. 7 19
      app/Http/Controllers/User/ServerController.php
  22. 1 1
      app/Http/Controllers/User/UserController.php
  23. 1 0
      app/Http/Requests/Admin/ConfigSave.php
  24. 13 9
      app/Http/Requests/Admin/CouponSave.php
  25. 48 0
      app/Http/Requests/Admin/ServerTrojanSave.php
  26. 28 0
      app/Http/Requests/Admin/ServerTrojanSort.php
  27. 28 0
      app/Http/Requests/Admin/ServerTrojanUpdate.php
  28. 1 1
      app/Http/Requests/Admin/ServerV2raySave.php
  29. 1 1
      app/Http/Requests/Admin/ServerV2raySort.php
  30. 1 1
      app/Http/Requests/Admin/ServerV2rayUpdate.php
  31. 25 10
      app/Http/Routes/AdminRoute.php
  32. 2 1
      app/Http/Routes/ClientRoute.php
  33. 0 2
      app/Models/ServerLog.php
  34. 12 0
      app/Models/ServerStat.php
  35. 12 0
      app/Models/ServerTrojan.php
  36. 6 0
      app/Services/CouponService.php
  37. 29 11
      app/Services/OrderService.php
  38. 86 7
      app/Services/ServerService.php
  39. 5 1
      app/Utils/CacheKey.php
  40. 52 0
      app/Utils/Clash.php
  41. 14 1
      app/Utils/Helper.php
  42. 51 0
      app/Utils/QuantumultX.php
  43. 46 0
      app/Utils/Surge.php
  44. 1 1
      composer.json
  45. 1 1
      config/app.php
  46. 29 4
      database/install.sql
  47. 0 36
      database/migrations/2014_10_12_000000_create_users_table.php
  48. 0 32
      database/migrations/2014_10_12_100000_create_password_resets_table.php
  49. 48 8
      database/update.sql
  50. 3 0
      library/PayTaro.php
  51. 0 63
      library/TomatoPay.php
  52. 14 0
      library/V2ray.php
  53. 0 0
      public/assets/admin/umi.css
  54. 0 0
      public/assets/admin/umi.js
  55. 0 0
      public/assets/user/umi.css
  56. 0 0
      public/assets/user/umi.js
  57. 524 0
      resources/rules/app.clash.yaml
  58. 4 4
      resources/rules/default.clash.yaml
  59. 1 1
      resources/rules/default.surge.conf

+ 2 - 1
app/Console/Commands/CheckOrder.php

@@ -117,7 +117,8 @@ class CheckOrder extends Command
             $user->expired_at = time();
         }
         $user->transfer_enable = $plan->transfer_enable * 1073741824;
-        if ((int)config('v2board.renew_reset_traffic_enable', 1)) {
+        // 当续费清空流量或用户先前是一次性订阅
+        if ((int)config('v2board.renew_reset_traffic_enable', 1) || $user->expired_at === NULL) {
             $user->u = 0;
             $user->d = 0;
         }

+ 25 - 4
app/Console/Commands/V2boardCache.php

@@ -2,13 +2,12 @@
 
 namespace App\Console\Commands;
 
+use App\Utils\CacheKey;
 use Illuminate\Console\Command;
-use App\Models\User;
-use App\Models\Order;
-use App\Models\Server;
 use App\Models\ServerLog;
-use App\Utils\Helper;
+use App\Models\ServerStat;
 use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
 
 class V2boardCache extends Command
 {
@@ -44,4 +43,26 @@ class V2boardCache extends Command
     public function handle()
     {
     }
+
+    private function cacheServerStat()
+    {
+        $serverLogs = ServerLog::select(
+            'server_id',
+            DB::raw("sum(u) as u"),
+            DB::raw("sum(d) as d"),
+            DB::raw("count(*) as online")
+        )
+            ->where('updated_at', '>=', time() - 3600)
+            ->groupBy('server_id')
+            ->get();
+        foreach ($serverLogs as $serverLog) {
+            $data = [
+                'server_id' => $serverLog->server_id,
+                'u' => $serverLog->u,
+                'd' => $serverLog->d,
+                'online' => $serverLog->online
+            ];
+//            ServerStat::create($data);
+        }
+    }
 }

+ 1 - 1
app/Console/Commands/V2boardInstall.php

@@ -114,7 +114,7 @@ class V2boardInstall extends Command
             abort(500, '管理员密码长度最小为8位字符');
         }
         $user->password = password_hash($password, PASSWORD_DEFAULT);
-        $user->v2ray_uuid = Helper::guid(true);
+        $user->uuid = Helper::guid(true);
         $user->token = Helper::guid();
         $user->is_admin = 1;
         return $user->save();

+ 1 - 0
app/Http/Controllers/Admin/ConfigController.php

@@ -76,6 +76,7 @@ class ConfigController extends Controller
                     // stripe
                     'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0),
                     'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0),
+                    'stripe_card_enable' => (int)config('v2board.stripe_card_enable', 0),
                     'stripe_sk_live' => config('v2board.stripe_sk_live'),
                     'stripe_pk_live' => config('v2board.stripe_pk_live'),
                     'stripe_webhook_key' => config('v2board.stripe_webhook_key'),

+ 12 - 11
app/Http/Controllers/Admin/CouponController.php

@@ -12,24 +12,25 @@ class CouponController extends Controller
 {
     public function fetch(Request $request)
     {
+        $coupons = Coupon::all();
+        foreach ($coupons as $k => $v) {
+            if ($coupons[$k]['limit_plan_ids']) $coupons[$k]['limit_plan_ids'] = json_decode($coupons[$k]['limit_plan_ids']);
+        }
         return response([
-            'data' => Coupon::all()
+            'data' => $coupons
         ]);
     }
 
     public function save(CouponSave $request)
     {
-        $params = $request->only([
-            'name',
-            'type',
-            'value',
-            'started_at',
-            'ended_at',
-            'limit_use'
-        ]);
-
+        $params = $request->only(array_keys(CouponSave::RULES));
+        if (isset($params['limit_plan_ids'])) {
+            $params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
+        }
         if (!$request->input('id')) {
-            $params['code'] = Helper::randomChar(8);
+            if (!$params['code']) {
+                $params['code'] = Helper::randomChar(8);
+            }
             if (!Coupon::create($params)) {
                 abort(500, '创建失败');
             }

+ 20 - 1
app/Http/Controllers/Admin/PlanController.php

@@ -16,8 +16,27 @@ class PlanController extends Controller
 {
     public function fetch(Request $request)
     {
+
+        $counts = User::select(
+            DB::raw("plan_id"),
+            DB::raw("count(*) as count")
+        )
+            ->where('plan_id', '!=', NULL)
+            ->where(function ($query) {
+                $query->where('expired_at', '>=', time())
+                    ->orWhere('expired_at', NULL);
+            })
+            ->groupBy("plan_id")
+            ->get();
+        $plans = Plan::orderBy('sort', 'ASC')->get();
+        foreach ($plans as $k => $v) {
+            $plans[$k]->count = 0;
+            foreach ($counts as $kk => $vv) {
+                if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
+            }
+        }
         return response([
-            'data' => Plan::orderBy('sort', 'ASC')->get()
+            'data' => $plans
         ]);
     }
 

+ 71 - 0
app/Http/Controllers/Admin/Server/GroupController.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Server;
+
+use App\Models\Plan;
+use App\Models\Server;
+use App\Models\ServerGroup;
+use App\Models\User;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class GroupController extends Controller
+{
+    public function fetch(Request $request)
+    {
+        if ($request->input('group_id')) {
+            return response([
+                'data' => [ServerGroup::find($request->input('group_id'))]
+            ]);
+        }
+        return response([
+            'data' => ServerGroup::get()
+        ]);
+    }
+
+    public function save(Request $request)
+    {
+        if (empty($request->input('name'))) {
+            abort(500, '组名不能为空');
+        }
+
+        if ($request->input('id')) {
+            $serverGroup = ServerGroup::find($request->input('id'));
+        } else {
+            $serverGroup = new ServerGroup();
+        }
+
+        $serverGroup->name = $request->input('name');
+        return response([
+            'data' => $serverGroup->save()
+        ]);
+    }
+
+    public function drop(Request $request)
+    {
+        if ($request->input('id')) {
+            $serverGroup = ServerGroup::find($request->input('id'));
+            if (!$serverGroup) {
+                abort(500, '组不存在');
+            }
+        }
+
+        $servers = Server::all();
+        foreach ($servers as $server) {
+            $groupId = json_decode($server->group_id);
+            if (in_array($request->input('id'), $groupId)) {
+                abort(500, '该组已被节点所使用,无法删除');
+            }
+        }
+
+        if (Plan::where('group_id', $request->input('id'))->first()) {
+            abort(500, '该组已被订阅所使用,无法删除');
+        }
+        if (User::where('group_id', $request->input('id'))->first()) {
+            abort(500, '该组已被用户所使用,无法删除');
+        }
+        return response([
+            'data' => $serverGroup->delete()
+        ]);
+    }
+}

+ 144 - 0
app/Http/Controllers/Admin/Server/TrojanController.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Server;
+
+use App\Http\Requests\Admin\ServerTrojanSave;
+use App\Http\Requests\Admin\ServerTrojanSort;
+use App\Http\Requests\Admin\ServerTrojanUpdate;
+use App\Services\ServerService;
+use App\Utils\CacheKey;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use App\Models\ServerTrojan;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+
+class TrojanController extends Controller
+{
+    public function fetch(Request $request)
+    {
+        $server = ServerTrojan::orderBy('sort', 'ASC')->get();
+        for ($i = 0; $i < count($server); $i++) {
+            if (!empty($server[$i]['tags'])) {
+                $server[$i]['tags'] = json_decode($server[$i]['tags']);
+            }
+            $server[$i]['group_id'] = json_decode($server[$i]['group_id']);
+            $server[$i]['online'] = Cache::get(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server[$i]['id']));
+            if ($server[$i]['parent_id']) {
+                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server[$i]['parent_id']));
+            } else {
+                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server[$i]['id']));
+            }
+        }
+        return response([
+            'data' => $server
+        ]);
+    }
+
+    public function save(ServerTrojanSave $request)
+    {
+        $params = $request->only(array_keys(ServerTrojanSave::RULES));
+        $params['group_id'] = json_encode($params['group_id']);
+        if (isset($params['tags'])) {
+            $params['tags'] = json_encode($params['tags']);
+        }
+
+        if ($request->input('id')) {
+            $server = ServerTrojan::find($request->input('id'));
+            if (!$server) {
+                abort(500, '服务器不存在');
+            }
+            try {
+                $server->update($params);
+            } catch (\Exception $e) {
+                abort(500, '保存失败');
+            }
+            return response([
+                'data' => true
+            ]);
+        }
+
+        if (!ServerTrojan::create($params)) {
+            abort(500, '创建失败');
+        }
+
+        return response([
+            'data' => true
+        ]);
+    }
+
+    public function drop(Request $request)
+    {
+        if ($request->input('id')) {
+            $server = ServerTrojan::find($request->input('id'));
+            if (!$server) {
+                abort(500, '节点ID不存在');
+            }
+        }
+        return response([
+            'data' => $server->delete()
+        ]);
+    }
+
+    public function update(ServerTrojanUpdate $request)
+    {
+        $params = $request->only([
+            'show',
+        ]);
+
+        $server = ServerTrojan::find($request->input('id'));
+
+        if (!$server) {
+            abort(500, '该服务器不存在');
+        }
+        try {
+            $server->update($params);
+        } catch (\Exception $e) {
+            abort(500, '保存失败');
+        }
+
+        return response([
+            'data' => true
+        ]);
+    }
+
+    public function copy(Request $request)
+    {
+        $server = ServerTrojan::find($request->input('id'));
+        $server->show = 0;
+        if (!$server) {
+            abort(500, '服务器不存在');
+        }
+        if (!ServerTrojan::create($server->toArray())) {
+            abort(500, '复制失败');
+        }
+
+        return response([
+            'data' => true
+        ]);
+    }
+
+    public function sort(ServerTrojanSort $request)
+    {
+        DB::beginTransaction();
+        foreach ($request->input('server_ids') as $k => $v) {
+            if (!ServerTrojan::find($v)->update(['sort' => $k + 1])) {
+                DB::rollBack();
+                abort(500, '保存失败');
+            }
+        }
+        DB::commit();
+        return response([
+            'data' => true
+        ]);
+    }
+
+    public function viewConfig(Request $request)
+    {
+        $serverService = new ServerService();
+        $config = $serverService->getTrojanConfig($request->input('node_id'), 23333);
+        return response([
+            'data' => $config
+        ]);
+    }
+}

+ 15 - 73
app/Http/Controllers/Admin/ServerController.php → app/Http/Controllers/Admin/Server/V2rayController.php

@@ -1,21 +1,19 @@
 <?php
 
-namespace App\Http\Controllers\Admin;
+namespace App\Http\Controllers\Admin\Server;
 
-use App\Http\Requests\Admin\ServerSave;
-use App\Http\Requests\Admin\ServerSort;
-use App\Http\Requests\Admin\ServerUpdate;
+use App\Http\Requests\Admin\ServerV2raySave;
+use App\Http\Requests\Admin\ServerV2raySort;
+use App\Http\Requests\Admin\ServerV2rayUpdate;
 use App\Services\ServerService;
+use App\Utils\CacheKey;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
-use App\Models\ServerGroup;
 use App\Models\Server;
-use App\Models\Plan;
-use App\Models\User;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\DB;
 
-class ServerController extends Controller
+class V2rayController extends Controller
 {
     public function fetch(Request $request)
     {
@@ -25,10 +23,11 @@ class ServerController extends Controller
                 $server[$i]['tags'] = json_decode($server[$i]['tags']);
             }
             $server[$i]['group_id'] = json_decode($server[$i]['group_id']);
+            $server[$i]['online'] = Cache::get(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server[$i]['parent_id'] ? $server[$i]['parent_id'] : $server[$i]['id']));
             if ($server[$i]['parent_id']) {
-                $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
+                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['parent_id']));
             } else {
-                $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
+                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['id']));
             }
         }
         return response([
@@ -36,9 +35,9 @@ class ServerController extends Controller
         ]);
     }
 
-    public function save(ServerSave $request)
+    public function save(ServerV2raySave $request)
     {
-        $params = $request->only(array_keys(ServerSave::RULES));
+        $params = $request->only(array_keys(ServerV2raySave::RULES));
         $params['group_id'] = json_encode($params['group_id']);
         if (isset($params['tags'])) {
             $params['tags'] = json_encode($params['tags']);
@@ -92,64 +91,6 @@ class ServerController extends Controller
         ]);
     }
 
-    public function groupFetch(Request $request)
-    {
-        if ($request->input('group_id')) {
-            return response([
-                'data' => [ServerGroup::find($request->input('group_id'))]
-            ]);
-        }
-        return response([
-            'data' => ServerGroup::get()
-        ]);
-    }
-
-    public function groupSave(Request $request)
-    {
-        if (empty($request->input('name'))) {
-            abort(500, '组名不能为空');
-        }
-
-        if ($request->input('id')) {
-            $serverGroup = ServerGroup::find($request->input('id'));
-        } else {
-            $serverGroup = new ServerGroup();
-        }
-
-        $serverGroup->name = $request->input('name');
-        return response([
-            'data' => $serverGroup->save()
-        ]);
-    }
-
-    public function groupDrop(Request $request)
-    {
-        if ($request->input('id')) {
-            $serverGroup = ServerGroup::find($request->input('id'));
-            if (!$serverGroup) {
-                abort(500, '组不存在');
-            }
-        }
-
-        $servers = Server::all();
-        foreach ($servers as $server) {
-            $groupId = json_decode($server->group_id);
-            if (in_array($request->input('id'), $groupId)) {
-                abort(500, '该组已被节点所使用,无法删除');
-            }
-        }
-
-        if (Plan::where('group_id', $request->input('id'))->first()) {
-            abort(500, '该组已被订阅所使用,无法删除');
-        }
-        if (User::where('group_id', $request->input('id'))->first()) {
-            abort(500, '该组已被用户所使用,无法删除');
-        }
-        return response([
-            'data' => $serverGroup->delete()
-        ]);
-    }
-
     public function drop(Request $request)
     {
         if ($request->input('id')) {
@@ -163,7 +104,7 @@ class ServerController extends Controller
         ]);
     }
 
-    public function update(ServerUpdate $request)
+    public function update(ServerV2rayUpdate $request)
     {
         $params = $request->only([
             'show',
@@ -188,6 +129,7 @@ class ServerController extends Controller
     public function copy(Request $request)
     {
         $server = Server::find($request->input('id'));
+        $server->show = 0;
         if (!$server) {
             abort(500, '服务器不存在');
         }
@@ -203,13 +145,13 @@ class ServerController extends Controller
     public function viewConfig(Request $request)
     {
         $serverService = new ServerService();
-        $config = $serverService->getConfig($request->input('node_id'), 23333);
+        $config = $serverService->getVmessConfig($request->input('node_id'), 23333);
         return response([
             'data' => $config
         ]);
     }
 
-    public function sort(ServerSort $request)
+    public function sort(ServerV2raySort $request)
     {
         DB::beginTransaction();
         foreach ($request->input('server_ids') as $k => $v) {

+ 35 - 33
app/Http/Controllers/Client/AppController.php

@@ -3,12 +3,12 @@
 namespace App\Http\Controllers\Client;
 
 use App\Http\Controllers\Controller;
+use App\Services\ServerService;
+use App\Services\UserService;
+use App\Utils\Clash;
 use Illuminate\Http\Request;
-use App\Models\User;
-use App\Models\Plan;
 use App\Models\Server;
-use App\Models\Notice;
-use App\Utils\Helper;
+use Symfony\Component\Yaml\Yaml;
 
 class AppController extends Controller
 {
@@ -16,38 +16,40 @@ class AppController extends Controller
     CONST SOCKS_PORT = 10010;
     CONST HTTP_PORT = 10011;
 
-    // TODO: 1.1.1 abolish
-    public function data(Request $request)
+    public function getConfig(Request $request)
     {
+        $server = [];
         $user = $request->user;
-        $nodes = [];
-        if ($user->plan_id) {
-            $user['plan'] = Plan::find($user->plan_id);
-            if (!$user['plan']) {
-                abort(500, '订阅计划不存在');
-            }
-            if ($user->expired_at > time()) {
-                $servers = Server::where('show', 1)
-                    ->orderBy('name')
-                    ->get();
-                foreach ($servers as $item) {
-                    $groupId = json_decode($item['group_id']);
-                    if (in_array($user->group_id, $groupId)) {
-                        array_push($nodes, $item);
-                    }
-                }
-            }
+        $userService = new UserService();
+        if ($userService->isAvailable($user)) {
+            $serverService = new ServerService();
+            $servers = $serverService->getAllServers($user);
+        }
+        $config = Yaml::parseFile(base_path() . '/resources/rules/app.clash.yaml');
+        $proxy = [];
+        $proxies = [];
+
+        foreach ($servers['vmess'] as $item) {
+            array_push($proxy, Clash::buildVmess($user->uuid, $item));
+            array_push($proxies, $item->name);
+        }
+
+        foreach ($servers['trojan'] as $item) {
+            array_push($proxy, Clash::buildTrojan($user->uuid, $item));
+            array_push($proxies, $item->name);
         }
+
+        $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
+        foreach ($config['proxy-groups'] as $k => $v) {
+            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
+        }
+        die(Yaml::dump($config));
+    }
+
+    public function getVersion()
+    {
         return response([
-            'data' => [
-                'nodes' => $nodes,
-                'u' => $user->u,
-                'd' => $user->d,
-                'transfer_enable' => $user->transfer_enable,
-                'expired_at' => $user->expired_at,
-                'plan' => isset($user['plan']) ? $user['plan'] : false,
-                'notice' => Notice::orderBy('created_at', 'DESC')->first()
-            ]
+            'data' => '4.0.0'
         ]);
     }
 
@@ -74,7 +76,7 @@ class AppController extends Controller
         //other
         $json->outbound->settings->vnext[0]->address = (string)$server->host;
         $json->outbound->settings->vnext[0]->port = (int)$server->port;
-        $json->outbound->settings->vnext[0]->users[0]->id = (string)$user->v2ray_uuid;
+        $json->outbound->settings->vnext[0]->users[0]->id = (string)$user->uuid;
         $json->outbound->settings->vnext[0]->users[0]->alterId = (int)$user->v2ray_alter_id;
         $json->outbound->settings->vnext[0]->remark = (string)$server->name;
         $json->outbound->streamSettings->network = $server->network;

+ 75 - 135
app/Http/Controllers/Client/ClientController.php

@@ -3,6 +3,10 @@
 namespace App\Http\Controllers\Client;
 
 use App\Http\Controllers\Controller;
+use App\Services\ServerService;
+use App\Utils\Clash;
+use App\Utils\QuantumultX;
+use App\Utils\Surge;
 use Illuminate\Http\Request;
 use App\Models\Server;
 use App\Utils\Helper;
@@ -14,76 +18,41 @@ class ClientController extends Controller
     public function subscribe(Request $request)
     {
         $user = $request->user;
-        $server = [];
         // account not expired and is not banned.
         $userService = new UserService();
         if ($userService->isAvailable($user)) {
-            $servers = Server::where('show', 1)
-                ->orderBy('sort', 'ASC')
-                ->get();
-            foreach ($servers as $item) {
-                $groupId = json_decode($item['group_id']);
-                if (in_array($user->group_id, $groupId)) {
-                    array_push($server, $item);
-                }
-            }
-        }
-        if (isset($_SERVER['HTTP_USER_AGENT'])) {
-            if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult%20X') !== false) {
-                die($this->quantumultX($user, $server));
-            }
-            if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult') !== false) {
-                die($this->quantumult($user, $server));
-            }
-            if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'clash') !== false) {
-                die($this->clash($user, $server));
-            }
-            if (strpos($_SERVER['HTTP_USER_AGENT'], 'Surfboard') !== false) {
-                die($this->surfboard($user, $server));
-            }
-            if (strpos($_SERVER['HTTP_USER_AGENT'], 'Surge') !== false) {
-                die($this->surge($user, $server));
-            }
-        }
-        die($this->origin($user, $server));
-    }
+            $serverService = new ServerService();
+            $servers = $serverService->getAllServers($user);
 
-    private function quantumultX($user, $server)
-    {
-        $uri = '';
-        foreach ($server as $item) {
-            $uri .= "vmess=" . $item->host . ":" . $item->port . ", method=none, password=" . $user->v2ray_uuid . ", fast-open=false, udp-relay=false, tag=" . $item->name;
-            if ($item->tls) {
-                $tlsSettings = json_decode($item->tlsSettings);
-                if ($item->network === 'tcp') $uri .= ', obfs=over-tls';
-                if (isset($tlsSettings->allowInsecure)) {
-                    // Default: tls-verification=true
-                    $uri .= ', tls-verification=' . ($tlsSettings->allowInsecure ? "false" : "true");
+            if (isset($_SERVER['HTTP_USER_AGENT'])) {
+                $_SERVER['HTTP_USER_AGENT'] = strtolower($_SERVER['HTTP_USER_AGENT']);
+                if (strpos($_SERVER['HTTP_USER_AGENT'], 'quantumult%20x') !== false) {
+                    die($this->quantumultX($user, $servers['vmess'], $servers['trojan']));
                 }
-                if (isset($tlsSettings->serverName)) {
-                    $uri .= ', obfs-host=' . $tlsSettings->serverName;
+                if (strpos($_SERVER['HTTP_USER_AGENT'], 'quantumult') !== false) {
+                    die($this->quantumult($user, $servers['vmess']));
                 }
-            }
-            if ($item->network === 'ws') {
-                $uri .= ', obfs=' . ($item->tls ? 'wss' : 'ws');
-                if ($item->networkSettings) {
-                    $wsSettings = json_decode($item->networkSettings);
-                    if (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
-                    if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
+                if (strpos($_SERVER['HTTP_USER_AGENT'], 'clash') !== false) {
+                    die($this->clash($user, $servers['vmess'], $servers['trojan']));
+                }
+                if (strpos($_SERVER['HTTP_USER_AGENT'], 'surfboard') !== false) {
+                    die($this->surfboard($user, $servers['vmess']));
+                }
+                if (strpos($_SERVER['HTTP_USER_AGENT'], 'surge') !== false) {
+                    die($this->surge($user, $servers['vmess'], $servers['trojan']));
                 }
             }
-            $uri .= "\r\n";
+            die($this->origin($user, $servers['vmess'], $servers['trojan']));
         }
-        return base64_encode($uri);
     }
-
-    private function quantumult($user, $server)
+    // TODO: Ready to stop support
+    private function quantumult($user, $vmess = [])
     {
         $uri = '';
         header('subscription-userinfo: upload=' . $user->u . '; download=' . $user->d . ';total=' . $user->transfer_enable);
-        foreach ($server as $item) {
+        foreach ($vmess as $item) {
             $str = '';
-            $str .= $item->name . '= vmess, ' . $item->host . ', ' . $item->port . ', chacha20-ietf-poly1305, "' . $user->v2ray_uuid . '", over-tls=' . ($item->tls ? "true" : "false") . ', certificate=0, group=' . config('v2board.app_name', 'V2Board');
+            $str .= $item->name . '= vmess, ' . $item->host . ', ' . $item->port . ', chacha20-ietf-poly1305, "' . $user->uuid . '", over-tls=' . ($item->tls ? "true" : "false") . ', certificate=0, group=' . config('v2board.app_name', 'V2Board');
             if ($item->network === 'ws') {
                 $str .= ', obfs=ws';
                 if ($item->networkSettings) {
@@ -97,38 +66,44 @@ class ClientController extends Controller
         return base64_encode($uri);
     }
 
-    private function origin($user, $server)
+    private function quantumultX($user, $vmess = [], $trojan = [])
     {
         $uri = '';
-        foreach ($server as $item) {
+        foreach ($vmess as $item) {
+            $uri .= QuantumultX::buildVmess($user->uuid, $item);
+        }
+        foreach ($trojan as $item) {
+            $uri .= QuantumultX::buildTrojan($user->uuid, $item);
+        }
+        return base64_encode($uri);
+    }
+
+    private function origin($user, $vmess = [], $trojan = [])
+    {
+        $uri = '';
+        foreach ($vmess as $item) {
             $uri .= Helper::buildVmessLink($item, $user);
         }
+        foreach ($trojan as $item) {
+            $uri .= Helper::buildTrojanLink($item, $user);
+        }
         return base64_encode($uri);
     }
 
-    private function surge($user, $server)
+    private function surge($user, $vmess = [], $trojan = [])
     {
         $proxies = '';
         $proxyGroup = '';
-        foreach ($server as $item) {
+        foreach ($vmess as $item) {
             // [Proxy]
-            $proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->v2ray_uuid . ', tfo=true';
-            if ($item->tls) {
-                $tlsSettings = json_decode($item->tlsSettings);
-                $proxies .= ', tls=' . ($item->tls ? "true" : "false");
-                if (isset($tlsSettings->allowInsecure)) {
-                  $proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
-                }
-            }
-            if ($item->network == 'ws') {
-                $proxies .= ', ws=true';
-                if ($item->networkSettings) {
-                    $wsSettings = json_decode($item->networkSettings);
-                    if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
-                    if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
-                }
-            }
-            $proxies .= "\r\n";
+            $proxies .= Surge::buildVmess($user->uuid, $item);
+            // [Proxy Group]
+            $proxyGroup .= $item->name . ', ';
+        }
+
+        foreach ($trojan as $item) {
+            // [Proxy]
+            $proxies .= Surge::buildTrojan($user->uuid, $item);
             // [Proxy Group]
             $proxyGroup .= $item->name . ', ';
         }
@@ -142,30 +117,21 @@ class ClientController extends Controller
         }
 
         // Subscription link
-        $subsURL = 'http';
-        if (isset( $_SERVER['HTTPS'] ) && strtolower( $_SERVER['HTTPS'] ) == 'on') {
-            $subsURL .= 's';
-        }
-        $subsURL .= '://';
-        if ($_SERVER['SERVER_PORT'] != ('80' || '443')) {
-            $subsURL .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
-        } else {
-            $subsURL .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
-        }
+        $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
 
-        $config = str_replace('$subs_link',$subsURL,$config);
-        $config = str_replace('$proxies',$proxies,$config);
-        $config = str_replace('$proxy_group',rtrim($proxyGroup, ', '),$config);
+        $config = str_replace('$subs_link', $subsURL, $config);
+        $config = str_replace('$proxies', $proxies, $config);
+        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
         return $config;
     }
 
-    private function surfboard($user, $server)
+    private function surfboard($user, $vmess = [])
     {
         $proxies = '';
         $proxyGroup = '';
-        foreach ($server as $item) {
+        foreach ($vmess as $item) {
             // [Proxy]
-            $proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->v2ray_uuid;
+            $proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->uuid;
             if ($item->tls) {
                 $tlsSettings = json_decode($item->tlsSettings);
                 $proxies .= ', tls=' . ($item->tls ? "true" : "false");
@@ -195,24 +161,15 @@ class ClientController extends Controller
         }
 
         // Subscription link
-        $subsURL = 'http';
-        if (isset( $_SERVER['HTTPS'] ) && strtolower( $_SERVER['HTTPS'] ) == 'on') {
-            $subsURL .= 's';
-        }
-        $subsURL .= '://';
-        if ($_SERVER['SERVER_PORT'] != ('80' || '443')) {
-            $subsURL .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
-        } else {
-            $subsURL .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
-        }
+        $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
 
-        $config = str_replace('$subs_link',$subsURL,$config);
-        $config = str_replace('$proxies',$proxies,$config);
-        $config = str_replace('$proxy_group',rtrim($proxyGroup, ', '),$config);
+        $config = str_replace('$subs_link', $subsURL, $config);
+        $config = str_replace('$proxies', $proxies, $config);
+        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
         return $config;
     }
 
-    private function clash($user, $server)
+    private function clash($user, $vmess = [], $trojan = [])
     {
         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
         $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
@@ -223,37 +180,20 @@ class ClientController extends Controller
         }
         $proxy = [];
         $proxies = [];
-        foreach ($server as $item) {
-            $array = [];
-            $array['name'] = $item->name;
-            $array['type'] = 'vmess';
-            $array['server'] = $item->host;
-            $array['port'] = $item->port;
-            $array['uuid'] = $user->v2ray_uuid;
-            $array['alterId'] = $user->v2ray_alter_id;
-            $array['cipher'] = 'auto';
-            if ($item->tls) {
-                $tlsSettings = json_decode($item->tlsSettings);
-                $array['tls'] = true;
-                if (isset($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false );
-            }
-            if ($item->network == 'ws') {
-                $array['network'] = $item->network;
-                if ($item->networkSettings) {
-                    $wsSettings = json_decode($item->networkSettings);
-                    if (isset($wsSettings->path)) $array['ws-path'] = $wsSettings->path;
-                    if (isset($wsSettings->headers->Host)) $array['ws-headers'] = [
-                        'Host' => $wsSettings->headers->Host
-                    ];
-                }
-            }
-            array_push($proxy, $array);
+        foreach ($vmess as $item) {
+            array_push($proxy, Clash::buildVmess($user->uuid, $item));
+            array_push($proxies, $item->name);
+        }
+
+
+        foreach ($trojan as $item) {
+            array_push($proxy, Clash::buildTrojan($user->uuid, $item));
             array_push($proxies, $item->name);
         }
 
-        $config['Proxy'] = array_merge($config['Proxy'] ? $config['Proxy'] : [], $proxy);
-        foreach ($config['Proxy Group'] as $k => $v) {
-            $config['Proxy Group'][$k]['proxies'] = array_merge($config['Proxy Group'][$k]['proxies'], $proxies);
+        $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
+        foreach ($config['proxy-groups'] as $k => $v) {
+            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
         }
         $yaml = Yaml::dump($config);
         $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);

+ 19 - 18
app/Http/Controllers/Guest/OrderController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers\Guest;
 
+use App\Services\OrderService;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\Order;
@@ -66,22 +67,26 @@ class OrderController extends Controller
         }
         switch ($event->type) {
             case 'source.chargeable':
-                $source = $event->data->object;
-                $charge = \Stripe\Charge::create([
-                    'amount' => $source['amount'],
-                    'currency' => $source['currency'],
-                    'source' => $source['id'],
-                    'description' => config('v2board.app_name', 'V2Board') . $source['metadata']['invoice_id'],
+                $object = $event->data->object;
+                \Stripe\Charge::create([
+                    'amount' => $object->amount,
+                    'currency' => $object->currency,
+                    'source' => $object->id,
+                    'metadata' => json_decode($object->metadata, true)
                 ]);
-                if ($charge['status'] == 'succeeded') {
-                    $trade_no = Cache::get($source['id']);
-                    if (!$trade_no) {
-                        abort(500, 'redis is not found trade no by stripe source id');
+                die('success');
+                break;
+            case 'charge.succeeded':
+                $object = $event->data->object;
+                if ($object->status === 'succeeded') {
+                    $metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
+                    $tradeNo = $metaData->out_trade_no;
+                    if (!$tradeNo) {
+                        abort(500, 'trade no is not found in metadata');
                     }
-                    if (!$this->handle($trade_no, $source['id'])) {
+                    if (!$this->handle($tradeNo, $object->balance_transaction)) {
                         abort(500, 'fail');
                     }
-                    Cache::forget($source['id']);
                     die('success');
                 }
                 break;
@@ -143,11 +148,7 @@ class OrderController extends Controller
         if (!$order) {
             abort(500, 'order is not found');
         }
-        if ($order->status !== 0) {
-            return true;
-        }
-        $order->status = 1;
-        $order->callback_no = $callbackNo;
-        return $order->save();
+        $orderService = new OrderService($order);
+        return $orderService->success($callbackNo);
     }
 }

+ 17 - 1
app/Http/Controllers/Guest/TelegramController.php

@@ -29,6 +29,8 @@ class TelegramController extends Controller
                     break;
                 case '/traffic': $this->traffic();
                     break;
+                case '/getlatesturl': $this->getLatestUrl();
+                    break;
                 default: $this->help();
             }
         } catch (\Exception $e) {
@@ -84,7 +86,8 @@ class TelegramController extends Controller
         $telegramService = new TelegramService();
         $commands = [
             '/bind 订阅地址 - 绑定你的' . config('v2board.app_name', 'V2Board') . '账号',
-            '/traffic - 查询流量信息'
+            '/traffic - 查询流量信息',
+            '/getlatesturl - 获取最新的' . config('v2board.app_name', 'V2Board') . '网址'
         ];
         $text = implode(PHP_EOL, $commands);
         $telegramService->sendMessage($msg->chat_id, "你可以使用以下命令进行操作:\n\n$text", 'markdown');
@@ -108,4 +111,17 @@ class TelegramController extends Controller
         $text = "🚥流量查询\n———————————————\n计划流量:`{$transferEnable}`\n已用上行:`{$up}`\n已用下行:`{$down}`\n剩余流量:`{$remaining}`";
         $telegramService->sendMessage($msg->chat_id, $text, 'markdown');
     }
+
+    private function getLatestUrl()
+    {
+        $msg = $this->msg;
+        $user = User::where('telegram_id', $msg->chat_id)->first();
+        $telegramService = new TelegramService();
+        $text = sprintf(
+            "%s的最新网址是:%s",
+            config('v2board.app_name', 'V2Board'),
+            config('v2board.app_url')
+        );
+        $telegramService->sendMessage($msg->chat_id, $text, 'markdown');
+    }
 }

+ 1 - 1
app/Http/Controllers/Passport/AuthController.php

@@ -58,7 +58,7 @@ class AuthController extends Controller
         $user = new User();
         $user->email = $email;
         $user->password = password_hash($password, PASSWORD_DEFAULT);
-        $user->v2ray_uuid = Helper::guid(true);
+        $user->uuid = Helper::guid(true);
         $user->token = Helper::guid();
         if ($request->input('invite_code')) {
             $inviteCode = InviteCode::where('code', $request->input('invite_code'))

+ 13 - 8
app/Http/Controllers/Server/DeepbworkController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Server;
 
 use App\Services\ServerService;
 use App\Services\UserService;
+use App\Utils\CacheKey;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\User;
@@ -13,10 +14,12 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
 
+/*
+ * V2ray Aurora
+ * Github: https://github.com/tokumeikoi/aurora
+ */
 class DeepbworkController extends Controller
 {
-    CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled": true,"destOverride": ["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
-
     public function __construct(Request $request)
     {
         $token = $request->input('token');
@@ -36,18 +39,18 @@ class DeepbworkController extends Controller
         if (!$server) {
             abort(500, 'fail');
         }
-        Cache::put('server_last_check_at_' . $server->id, time());
+        Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
         $serverService = new ServerService();
         $users = $serverService->getAvailableUsers(json_decode($server->group_id));
         $result = [];
         foreach ($users as $user) {
             $user->v2ray_user = [
-                "uuid" => $user->v2ray_uuid,
-                "email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
+                "uuid" => $user->uuid,
+                "email" => sprintf("%s@v2board.user", $user->uuid),
                 "alter_id" => $user->v2ray_alter_id,
                 "level" => $user->v2ray_level,
             ];
-            unset($user['v2ray_uuid']);
+            unset($user['uuid']);
             unset($user['v2ray_alter_id']);
             unset($user['v2ray_level']);
             array_push($result, $user);
@@ -71,6 +74,7 @@ class DeepbworkController extends Controller
         }
         $data = file_get_contents('php://input');
         $data = json_decode($data, true);
+        Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
         $serverService = new ServerService();
         $userService = new UserService();
         foreach ($data as $item) {
@@ -88,7 +92,8 @@ class DeepbworkController extends Controller
                 $request->input('node_id'),
                 $item['u'],
                 $item['d'],
-                $server->rate
+                $server->rate,
+                'vmess'
             );
         }
 
@@ -108,7 +113,7 @@ class DeepbworkController extends Controller
         }
         $serverService = new ServerService();
         try {
-            $json = $serverService->getConfig($nodeId, $localPort);
+            $json = $serverService->getVmessConfig($nodeId, $localPort);
         } catch (\Exception $e) {
             abort(500, $e->getMessage());
         }

+ 13 - 10
app/Http/Controllers/Server/PoseidonController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Server;
 
 use App\Services\ServerService;
 use App\Services\UserService;
+use App\Utils\CacheKey;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\User;
@@ -13,10 +14,12 @@ use App\Models\ServerLog;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
 
+/*
+ * V2ray Poseidon
+ * Github: https://github.com/ColetteContreras/trojan-poseidon
+ */
 class PoseidonController extends Controller
 {
-    CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled": true,"destOverride": ["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
-
     public $poseidonVersion;
 
     public function __construct(Request $request)
@@ -34,18 +37,18 @@ class PoseidonController extends Controller
         if (!$server) {
             return $this->error("server could not be found", 404);
         }
-        Cache::put('server_last_check_at_' . $server->id, time());
+        Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
         $serverService = new ServerService();
         $users = $serverService->getAvailableUsers(json_decode($server->group_id));
         $result = [];
         foreach ($users as $user) {
             $user->v2ray_user = [
-                "uuid" => $user->v2ray_uuid,
-                "email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
+                "uuid" => $user->uuid,
+                "email" => sprintf("%s@v2board.user", $user->uuid),
                 "alter_id" => $user->v2ray_alter_id,
                 "level" => $user->v2ray_level,
             ];
-            unset($user['v2ray_uuid']);
+            unset($user['uuid']);
             unset($user['v2ray_alter_id']);
             unset($user['v2ray_level']);
             array_push($result, $user);
@@ -58,14 +61,13 @@ class PoseidonController extends Controller
     public function submit(Request $request)
     {
         if ($r = $this->verifyToken($request)) { return $r; }
-
-        // Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
         $server = Server::find($request->input('node_id'));
         if (!$server) {
             return $this->error("server could not be found", 404);
         }
         $data = file_get_contents('php://input');
         $data = json_decode($data, true);
+        Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
         $serverService = new ServerService();
         $userService = new UserService();
         foreach ($data as $item) {
@@ -80,7 +82,8 @@ class PoseidonController extends Controller
                 $request->input('node_id'),
                 $item['u'],
                 $item['d'],
-                $server->rate
+                $server->rate,
+                'vmess'
             );
         }
 
@@ -100,7 +103,7 @@ class PoseidonController extends Controller
 
         $serverService = new ServerService();
         try {
-            $json = $serverService->getConfig($nodeId, $localPort);
+            $json = $serverService->getVmessConfig($nodeId, $localPort);
             $json->poseidon = [
               'license_key' => (string)config('v2board.server_license'),
             ];

+ 120 - 0
app/Http/Controllers/Server/TrojanTidalabController.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace App\Http\Controllers\Server;
+
+use App\Services\ServerService;
+use App\Services\UserService;
+use App\Utils\CacheKey;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use App\Models\User;
+use App\Models\ServerTrojan;
+use App\Models\ServerLog;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Cache;
+
+/*
+ * Tidal Lab Trojan
+ * Github: https://github.com/tokumeikoi/tidalab-trojan
+ */
+class TrojanTidalabController extends Controller
+{
+    public function __construct(Request $request)
+    {
+        $token = $request->input('token');
+        if (empty($token)) {
+            abort(500, 'token is null');
+        }
+        if ($token !== config('v2board.server_token')) {
+            abort(500, 'token is error');
+        }
+    }
+
+    // 后端获取用户
+    public function user(Request $request)
+    {
+        $nodeId = $request->input('node_id');
+        $server = ServerTrojan::find($nodeId);
+        if (!$server) {
+            abort(500, 'fail');
+        }
+        Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
+        $serverService = new ServerService();
+        $users = $serverService->getAvailableUsers(json_decode($server->group_id));
+        $result = [];
+        foreach ($users as $user) {
+            $user->trojan_user = [
+                "password" => $user->uuid,
+            ];
+            unset($user['uuid']);
+            unset($user['v2ray_alter_id']);
+            unset($user['v2ray_level']);
+            array_push($result, $user);
+        }
+        return response([
+            'msg' => 'ok',
+            'data' => $result,
+        ]);
+    }
+
+    // 后端提交数据
+    public function submit(Request $request)
+    {
+        // Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
+        $server = ServerTrojan::find($request->input('node_id'));
+        if (!$server) {
+            return response([
+                'ret' => 0,
+                'msg' => 'server is not found'
+            ]);
+        }
+        $data = file_get_contents('php://input');
+        $data = json_decode($data, true);
+        Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
+        $serverService = new ServerService();
+        $userService = new UserService();
+        foreach ($data as $item) {
+            $u = $item['u'] * $server->rate;
+            $d = $item['d'] * $server->rate;
+            if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
+                return response([
+                    'ret' => 0,
+                    'msg' => 'user fetch fail'
+                ]);
+            }
+
+            $serverService->log(
+                $item['user_id'],
+                $request->input('node_id'),
+                $item['u'],
+                $item['d'],
+                $server->rate,
+                'trojan'
+            );
+        }
+
+        return response([
+            'ret' => 1,
+            'msg' => 'ok'
+        ]);
+    }
+
+    // 后端获取配置
+    public function config(Request $request)
+    {
+        $nodeId = $request->input('node_id');
+        $localPort = $request->input('local_port');
+        if (empty($nodeId) || empty($localPort)) {
+            abort(500, '参数错误');
+        }
+        $serverService = new ServerService();
+        try {
+            $json = $serverService->getTrojanConfig($nodeId, $localPort);
+        } catch (\Exception $e) {
+            abort(500, $e->getMessage());
+        }
+
+        die(json_encode($json, JSON_UNESCAPED_UNICODE));
+    }
+}

+ 2 - 1
app/Http/Controllers/User/CommController.php

@@ -11,7 +11,8 @@ class CommController extends Controller
     {
         return response([
             'data' => [
-                'isTelegram' => (int)config('v2board.telegram_bot_enable', 0)
+                'isTelegram' => (int)config('v2board.telegram_bot_enable', 0),
+                'stripePk' => config('v2board.stripe_pk_live')
             ]
         ]);
     }

+ 7 - 0
app/Http/Controllers/User/CouponController.php

@@ -26,6 +26,13 @@ class CouponController extends Controller
         if (time() > $coupon->ended_at) {
             abort(500, '优惠券已过期');
         }
+        if ($coupon->limit_plan_ids) {
+            $limitPlanIds = json_decode($coupon->limit_plan_ids);
+            info($limitPlanIds);
+            if (!in_array($request->input('plan_id'), $limitPlanIds)) {
+                abort(500, '这个计划无法使用该优惠码');
+            }
+        }
         return response([
             'data' => $coupon
         ]);

+ 50 - 14
app/Http/Controllers/User/OrderController.php

@@ -9,12 +9,10 @@ use App\Services\OrderService;
 use App\Services\UserService;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\DB;
 use App\Models\Order;
 use App\Models\Plan;
 use App\Models\User;
-use App\Models\Coupon;
 use App\Utils\Helper;
 use Omnipay\Omnipay;
 use Stripe\Stripe;
@@ -173,7 +171,7 @@ class OrderController extends Controller
             ]);
         }
         switch ($method) {
-            // return type => 0: QRCode / 1: URL
+            // return type => 0: QRCode / 1: URL / 2: No action
             case 0:
                 // alipayF2F
                 if (!(int)config('v2board.alipay_enable')) {
@@ -218,6 +216,14 @@ class OrderController extends Controller
                     'type' => 1,
                     'data' => $this->payTaro($order)
                 ]);
+            case 6:
+                if (!(int)config('v2board.stripe_card_enable')) {
+                    abort(500, '支付方式不可用');
+                }
+                return response([
+                    'type' => 2,
+                    'data' => $this->stripeCard($order, $request->input('token'))
+                ]);
             default:
                 abort(500, '支付方式不存在');
         }
@@ -266,7 +272,7 @@ class OrderController extends Controller
 
         if ((int)config('v2board.bitpayx_enable')) {
             $bitpayX = new \StdClass();
-            $bitpayX->name = config('v2board.bitpayx_name', '聚合支付');
+            $bitpayX->name = config('v2board.bitpayx_name', '在线支付');
             $bitpayX->method = 4;
             $bitpayX->icon = 'wallet';
             array_push($data, $bitpayX);
@@ -274,12 +280,20 @@ class OrderController extends Controller
 
         if ((int)config('v2board.paytaro_enable')) {
             $obj = new \StdClass();
-            $obj->name = config('v2board.paytaro_name', '聚合支付');
+            $obj->name = config('v2board.paytaro_name', '在线支付');
             $obj->method = 5;
             $obj->icon = 'wallet';
             array_push($data, $obj);
         }
 
+        if ((int)config('v2board.stripe_card_enable')) {
+            $obj = new \StdClass();
+            $obj->name = '信用卡';
+            $obj->method = 6;
+            $obj->icon = 'card';
+            array_push($data, $obj);
+        }
+
         return response([
             'data' => $data
         ]);
@@ -347,7 +361,7 @@ class OrderController extends Controller
             'statement_descriptor' => $order->trade_no,
             'metadata' => [
                 'user_id' => $order->user_id,
-                'invoice_id' => $order->trade_no,
+                'out_trade_no' => $order->trade_no,
                 'identifier' => ''
             ],
             'redirect' => [
@@ -357,10 +371,6 @@ class OrderController extends Controller
         if (!$source['redirect']['url']) {
             abort(500, '支付网关请求失败');
         }
-
-        if (!Cache::put($source['id'], $order->trade_no, 3600)) {
-            abort(500, '订单创建失败');
-        }
         return $source['redirect']['url'];
     }
 
@@ -378,7 +388,7 @@ class OrderController extends Controller
             'type' => 'wechat',
             'metadata' => [
                 'user_id' => $order->user_id,
-                'invoice_id' => $order->trade_no,
+                'out_trade_no' => $order->trade_no,
                 'identifier' => ''
             ],
             'redirect' => [
@@ -388,12 +398,38 @@ class OrderController extends Controller
         if (!$source['wechat']['qr_code_url']) {
             abort(500, '支付网关请求失败');
         }
-        if (!Cache::put($source['id'], $order->trade_no, 3600)) {
-            abort(500, '订单创建失败');
-        }
         return $source['wechat']['qr_code_url'];
     }
 
+    private function stripeCard($order, string $token)
+    {
+        $currency = config('v2board.stripe_currency', 'hkd');
+        $exchange = Helper::exchange('CNY', strtoupper($currency));
+        if (!$exchange) {
+            abort(500, '货币转换超时,请稍后再试');
+        }
+        Stripe::setApiKey(config('v2board.stripe_sk_live'));
+        try {
+            $charge = \Stripe\Charge::create([
+                'amount' => floor($order->total_amount * $exchange),
+                'currency' => $currency,
+                'source' => $token,
+                'metadata' => [
+                    'user_id' => $order->user_id,
+                    'out_trade_no' => $order->trade_no,
+                    'identifier' => ''
+                ]
+            ]);
+        } catch (\Exception $e) {
+            abort(500, '遇到了点问题,请刷新页面稍后再试');
+        }
+        info($charge);
+        if (!$charge->paid) {
+            abort(500, '扣款失败,请检查信用卡信息');
+        }
+        return $charge->paid;
+    }
+
     private function bitpayX($order)
     {
         $bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));

+ 7 - 19
app/Http/Controllers/User/ServerController.php

@@ -3,7 +3,9 @@
 namespace App\Http\Controllers\User;
 
 use App\Http\Controllers\Controller;
+use App\Services\ServerService;
 use App\Services\UserService;
+use App\Utils\CacheKey;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
 use App\Models\Server;
@@ -17,29 +19,15 @@ class ServerController extends Controller
     public function fetch(Request $request)
     {
         $user = User::find($request->session()->get('id'));
-        $server = [];
+        $servers = [];
         $userService = new UserService();
         if ($userService->isAvailable($user)) {
-            $servers = Server::where('show', 1)
-                ->orderBy('sort', 'ASC')
-                ->get();
-            foreach ($servers as $item) {
-                $groupId = json_decode($item['group_id']);
-                if (in_array($user->group_id, $groupId)) {
-                    array_push($server, $item);
-                }
-            }
-        }
-        for ($i = 0; $i < count($server); $i++) {
-            $server[$i]['link'] = Helper::buildVmessLink($server[$i], $user);
-            if ($server[$i]['parent_id']) {
-                $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
-            } else {
-                $server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
-            }
+            $serverService = new ServerService();
+            $servers = $serverService->getAllServers($user);
+            $servers = array_merge($servers['vmess'], $servers['trojan']);
         }
         return response([
-            'data' => $server
+            'data' => $servers
         ]);
     }
 

+ 1 - 1
app/Http/Controllers/User/UserController.php

@@ -107,7 +107,7 @@ class UserController extends Controller
     public function resetSecurity(Request $request)
     {
         $user = User::find($request->session()->get('id'));
-        $user->v2ray_uuid = Helper::guid(true);
+        $user->uuid = Helper::guid(true);
         $user->token = Helper::guid();
         if (!$user->save()) {
             abort(500, '重置失败');

+ 1 - 0
app/Http/Requests/Admin/ConfigSave.php

@@ -44,6 +44,7 @@ class ConfigSave extends FormRequest
         // stripe
         'stripe_alipay_enable' => 'in:0,1',
         'stripe_wepay_enable' => 'in:0,1',
+        'stripe_card_enable' => 'in:0,1',
         'stripe_sk_live' => '',
         'stripe_pk_live' => '',
         'stripe_webhook_key' => '',

+ 13 - 9
app/Http/Requests/Admin/CouponSave.php

@@ -6,6 +6,16 @@ use Illuminate\Foundation\Http\FormRequest;
 
 class CouponSave extends FormRequest
 {
+    const RULES = [
+        'name' => 'required',
+        'type' => 'required|in:1,2',
+        'value' => 'required|integer',
+        'started_at' => 'required|integer',
+        'ended_at' => 'required|integer',
+        'limit_use' => 'nullable|integer',
+        'limit_plan_ids' => 'nullable|array',
+        'code' => ''
+    ];
     /**
      * Get the validation rules that apply to the request.
      *
@@ -13,14 +23,7 @@ class CouponSave extends FormRequest
      */
     public function rules()
     {
-        return [
-            'name' => 'required',
-            'type' => 'required|in:1,2',
-            'value' => 'required|integer',
-            'started_at' => 'required|integer',
-            'ended_at' => 'required|integer',
-            'limit_use' => 'nullable|integer'
-        ];
+        return self::RULES;
     }
 
     public function messages()
@@ -35,7 +38,8 @@ class CouponSave extends FormRequest
             'started_at.integer' => '开始时间格式有误',
             'ended_at.required' => '结束时间不能为空',
             'ended_at.integer' => '结束时间格式有误',
-            'limit_use.integer' => '使用次数格式有误'
+            'limit_use.integer' => '使用次数格式有误',
+            'limit_plan_ids.array' => '指定订阅格式有误'
         ];
     }
 }

+ 48 - 0
app/Http/Requests/Admin/ServerTrojanSave.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Http\Requests\Admin;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class ServerTrojanSave extends FormRequest
+{
+    CONST RULES = [
+        'show' => '',
+        'name' => 'required',
+        'group_id' => 'required|array',
+        'parent_id' => 'nullable|integer',
+        'host' => 'required',
+        'port' => 'required',
+        'server_port' => 'required',
+        'allow_insecure' => 'nullable|in:0,1',
+        'server_name' => 'nullable',
+        'tags' => 'nullable|array',
+        'rate' => 'required|numeric'
+    ];
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return self::RULES;
+    }
+
+    public function messages()
+    {
+        return [
+            'name.required' => '节点名称不能为空',
+            'group_id.required' => '权限组不能为空',
+            'group_id.array' => '权限组格式不正确',
+            'parent_id.integer' => '父节点格式不正确',
+            'host.required' => '节点地址不能为空',
+            'port.required' => '连接端口不能为空',
+            'server_port.required' => '后端服务端口不能为空',
+            'allow_insecure.in' => '允许不安全格式不正确',
+            'tags.array' => '标签格式不正确',
+            'rate.required' => '倍率不能为空',
+            'rate.numeric' => '倍率格式不正确'
+        ];
+    }
+}

+ 28 - 0
app/Http/Requests/Admin/ServerTrojanSort.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Requests\Admin;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class ServerTrojanSort extends FormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'server_ids' => 'required|array'
+        ];
+    }
+
+    public function messages()
+    {
+        return [
+            'server_ids.required' => '服务器ID不能为空',
+            'server_ids.array' => '服务器ID格式有误'
+        ];
+    }
+}

+ 28 - 0
app/Http/Requests/Admin/ServerTrojanUpdate.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Requests\Admin;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class ServerTrojanUpdate extends FormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+
+    public function rules()
+    {
+        return [
+            'show' => 'in:0,1'
+        ];
+    }
+
+    public function messages()
+    {
+        return [
+            'show.in' => '显示状态格式不正确'
+        ];
+    }
+}

+ 1 - 1
app/Http/Requests/Admin/ServerSave.php → app/Http/Requests/Admin/ServerV2raySave.php

@@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class ServerSave extends FormRequest
+class ServerV2raySave extends FormRequest
 {
     CONST RULES = [
         'show' => '',

+ 1 - 1
app/Http/Requests/Admin/ServerSort.php → app/Http/Requests/Admin/ServerV2raySort.php

@@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class ServerSort extends FormRequest
+class ServerV2raySort extends FormRequest
 {
     /**
      * Get the validation rules that apply to the request.

+ 1 - 1
app/Http/Requests/Admin/ServerUpdate.php → app/Http/Requests/Admin/ServerV2rayUpdate.php

@@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class ServerUpdate extends FormRequest
+class ServerV2rayUpdate extends FormRequest
 {
     /**
      * Get the validation rules that apply to the request.

+ 25 - 10
app/Http/Routes/AdminRoute.php

@@ -23,16 +23,31 @@ class AdminRoute
             $router->post('/plan/update', 'Admin\\PlanController@update');
             $router->post('/plan/sort', 'Admin\\PlanController@sort');
             // Server
-            $router->get ('/server/fetch', 'Admin\\ServerController@fetch');
-            $router->post('/server/save', 'Admin\\ServerController@save');
-            $router->get ('/server/group/fetch', 'Admin\\ServerController@groupFetch');
-            $router->post('/server/group/save', 'Admin\\ServerController@groupSave');
-            $router->post('/server/group/drop', 'Admin\\ServerController@groupDrop');
-            $router->post('/server/drop', 'Admin\\ServerController@drop');
-            $router->post('/server/update', 'Admin\\ServerController@update');
-            $router->post('/server/copy', 'Admin\\ServerController@copy');
-            $router->post('/server/viewConfig', 'Admin\\ServerController@viewConfig');
-            $router->post('/server/sort', 'Admin\\ServerController@sort');
+            $router->get ('/server/group/fetch', 'Admin\\Server\\GroupController@fetch');
+            $router->post('/server/group/save', 'Admin\\Server\\GroupController@save');
+            $router->post('/server/group/drop', 'Admin\\Server\\GroupController@drop');
+            $router->group([
+                'prefix' => 'server/trojan'
+            ], function ($router) {
+                $router->get ('fetch', 'Admin\\Server\\TrojanController@fetch');
+                $router->post('save', 'Admin\\Server\\TrojanController@save');
+                $router->post('drop', 'Admin\\Server\\TrojanController@drop');
+                $router->post('update', 'Admin\\Server\\TrojanController@update');
+                $router->post('copy', 'Admin\\Server\\TrojanController@copy');
+                $router->post('sort', 'Admin\\Server\\TrojanController@sort');
+                $router->post('viewConfig', 'Admin\\Server\\TrojanController@viewConfig');
+            });
+            $router->group([
+                'prefix' => 'server/v2ray'
+            ], function ($router) {
+                $router->get ('fetch', 'Admin\\Server\\V2rayController@fetch');
+                $router->post('save', 'Admin\\Server\\V2rayController@save');
+                $router->post('drop', 'Admin\\Server\\V2rayController@drop');
+                $router->post('update', 'Admin\\Server\\V2rayController@update');
+                $router->post('copy', 'Admin\\Server\\V2rayController@copy');
+                $router->post('sort', 'Admin\\Server\\V2rayController@sort');
+                $router->post('viewConfig', 'Admin\\Server\\V2rayController@viewConfig');
+            });
             // Order
             $router->get ('/order/fetch', 'Admin\\OrderController@fetch');
             $router->post('/order/repair', 'Admin\\OrderController@repair');

+ 2 - 1
app/Http/Routes/ClientRoute.php

@@ -14,8 +14,9 @@ class ClientRoute
             // Client
             $router->get('/subscribe', 'Client\\ClientController@subscribe');
             // App
-            $router->get('/app/data', 'Client\\AppController@data');
             $router->get('/app/config', 'Client\\AppController@config');
+            $router->get('/app/getConfig', 'Client\\AppController@getConfig');
+            $router->get('/app/getVersion', 'Client\\AppController@getVersion');
         });
     }
 }

+ 0 - 2
app/Models/ServerLog.php

@@ -9,6 +9,4 @@ class ServerLog extends Model
 {
     protected $table = 'v2_server_log';
     protected $dateFormat = 'U';
-    protected $dispatchesEvents = [
-    ];
 }

+ 12 - 0
app/Models/ServerStat.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class ServerStat extends Model
+{
+    protected $table = 'v2_server_stat';
+    protected $dateFormat = 'U';
+    protected $guarded = ['id'];
+}

+ 12 - 0
app/Models/ServerTrojan.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class ServerTrojan extends Model
+{
+    protected $table = 'v2_server_trojan';
+    protected $dateFormat = 'U';
+    protected $guarded = ['id'];
+}

+ 6 - 0
app/Services/CouponService.php

@@ -43,6 +43,12 @@ class CouponService
                 return false;
             }
         }
+        if ($this->coupon->limit_plan_ids) {
+            $limitPlanIds = json_decode($this->coupon->limit_plan_ids);
+            if (!in_array($order->plan_id, $limitPlanIds)) {
+                return false;
+            }
+        }
         return true;
     }
 }

+ 29 - 11
app/Services/OrderService.php

@@ -119,25 +119,43 @@ class OrderService
             'month_price' => 1,
             'quarter_price' => 3,
             'half_year_price' => 6,
-            'year_price' => 12,
-            'onetime_price' => 0
+            'year_price' => 12
         ];
         $orderModel = Order::where('user_id', $user->id)
             ->where('cycle', '!=', 'reset_price')
             ->where('status', 3);
-        $surplusAmount = 0;
+        $orderSurplusMonth = 0;
+        $orderSurplusAmount = 0;
+        $userSurplusMonth = ($user->expired_at - time()) / 2678400;
         foreach ($orderModel->get() as $item) {
-            $surplusMonth = strtotime("+ {$strToMonth[$item->cycle]}month", $item->created_at->format('U'));
-            if (!$surplusMonth) continue;
-            $surplusMonth = ($surplusMonth - time()) / 2678400 / $strToMonth[$item->cycle];
-            if ($surplusMonth > 0) {
-                $surplusAmount = $surplusAmount + ($item['total_amount'] + $item['balance_amount']) * $surplusMonth;
-            }
+            // 兼容历史余留问题
+            if ($item->cycle === 'onetime_price') continue;
+            $orderSurplusMonth = $orderSurplusMonth + $strToMonth[$item->cycle];
+            $orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount']);
+        }
+        if (!$orderSurplusMonth || !$orderSurplusAmount) return;
+        $monthUnitPrice = $orderSurplusAmount / $orderSurplusMonth;
+        // 如果用户过期月大于订单过期月
+        if ($userSurplusMonth > $orderSurplusMonth) {
+            $orderSurplusAmount = $orderSurplusMonth * $monthUnitPrice;
+        } else {
+            $orderSurplusAmount = $userSurplusMonth * $monthUnitPrice;
         }
-        if (!$surplusAmount) {
+        if (!$orderSurplusAmount) {
             return;
         }
-        $order->surplus_amount = $surplusAmount > 0 ? $surplusAmount : 0;
+        $order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0;
         $order->surplus_order_ids = json_encode(array_map(function ($v) { return $v['id'];}, $orderModel->get()->toArray()));
     }
+
+    public function success(string $callbackNo)
+    {
+        $order = $this->order;
+        if ($order->status !== 0) {
+            return true;
+        }
+        $order->status = 1;
+        $order->callback_no = $callbackNo;
+        return $order->save();
+    }
 }

+ 86 - 7
app/Services/ServerService.php

@@ -5,11 +5,72 @@ namespace App\Services;
 use App\Models\ServerLog;
 use App\Models\User;
 use App\Models\Server;
+use App\Models\ServerTrojan;
+use App\Utils\CacheKey;
+use App\Utils\Helper;
+use Illuminate\Support\Facades\Cache;
 
 class ServerService
 {
 
-    CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
+    CONST V2RAY_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
+    CONST TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}';
+    public function getVmess(User $user, $all = false):array
+    {
+        $vmess = [];
+        $model = Server::orderBy('sort', 'ASC');
+        if (!$all) {
+            $model->where('show', 1);
+        }
+        $vmesss = $model->get();
+        foreach ($vmesss as $k => $v) {
+            $groupId = json_decode($vmesss[$k]['group_id']);
+            if (in_array($user->group_id, $groupId)) {
+                $vmesss[$k]['link'] = Helper::buildVmessLink($vmesss[$k], $user);
+                if ($vmesss[$k]['parent_id']) {
+                    $vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['parent_id']));
+                } else {
+                    $vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['id']));
+                }
+                array_push($vmess, $vmesss[$k]);
+            }
+        }
+
+
+        return $vmess;
+    }
+
+    public function getTrojan(User $user, $all = false)
+    {
+        $trojan = [];
+        $model = ServerTrojan::orderBy('sort', 'ASC');
+        if (!$all) {
+            $model->where('show', 1);
+        }
+        $trojans = $model->get();
+        foreach ($trojans as $k => $v) {
+            $groupId = json_decode($trojans[$k]['group_id']);
+            if (in_array($user->group_id, $groupId)) {
+                if ($trojans[$k]['parent_id']) {
+                    $trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['parent_id']));
+                } else {
+                    $trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['id']));
+                }
+                array_push($trojan, $trojans[$k]);
+            }
+
+        }
+        return $trojan;
+    }
+
+    public function getAllServers(User $user, $all = false)
+    {
+        return [
+            'vmess' => $this->getVmess($user, $all),
+            'trojan' => $this->getTrojan($user, $all)
+        ];
+    }
+
 
     public function getAvailableUsers($groupId)
     {
@@ -27,20 +88,20 @@ class ServerService
                 'u',
                 'd',
                 'transfer_enable',
-                'v2ray_uuid',
+                'uuid',
                 'v2ray_alter_id',
                 'v2ray_level'
             ])
             ->get();
     }
 
-    public function getConfig(int $nodeId, int $localPort)
+    public function getVmessConfig(int $nodeId, int $localPort)
     {
         $server = Server::find($nodeId);
         if (!$server) {
             abort(500, '节点不存在');
         }
-        $json = json_decode(self::SERVER_CONFIG);
+        $json = json_decode(self::V2RAY_CONFIG);
         $json->log->loglevel = config('v2board.server_log_level', 'none');
         $json->inboundDetour[0]->port = (int)$localPort;
         $json->inbound->port = (int)$server->server_port;
@@ -53,6 +114,22 @@ class ServerService
         return $json;
     }
 
+    public function getTrojanConfig(int $nodeId, int $localPort)
+    {
+        $server = ServerTrojan::find($nodeId);
+        if (!$server) {
+            abort(500, '节点不存在');
+        }
+
+        $json = json_decode(self::TROJAN_CONFIG);
+        $json->local_port = $server->server_port;
+        $json->ssl->sni = $server->server_name ? $server->server_name : $server->host;
+        $json->ssl->cert = "/root/.cert/server.crt";
+        $json->ssl->key = "/root/.cert/server.key";
+        $json->api->api_port = $localPort;
+        return $json;
+    }
+
     private function setDns(Server $server, object $json)
     {
         if ($server->dnsSettings) {
@@ -123,8 +200,8 @@ class ServerService
             $tlsSettings = json_decode($server->tlsSettings);
             $json->inbound->streamSettings->security = 'tls';
             $tls = (object)[
-                'certificateFile' => '/home/v2ray.crt',
-                'keyFile' => '/home/v2ray.key'
+                'certificateFile' => '/root/.cert/server.crt',
+                'keyFile' => '/root/.cert/server.key'
             ];
             $json->inbound->streamSettings->tlsSettings = new \StdClass();
             if (isset($tlsSettings->serverName)) {
@@ -137,7 +214,7 @@ class ServerService
         }
     }
 
-    public function log(int $userId, int $serverId, int $u, int $d, float $rate)
+    public function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method)
     {
         if (($u + $d) <= 10240) return;
         $timestamp = strtotime(date('Y-m-d H:0'));
@@ -146,6 +223,7 @@ class ServerService
             ->where('server_id', $serverId)
             ->where('user_id', $userId)
             ->where('rate', $rate)
+            ->where('method', $method)
             ->first();
         if ($serverLog) {
             $serverLog->u = $serverLog->u + $u;
@@ -159,6 +237,7 @@ class ServerService
             $serverLog->d = $d;
             $serverLog->rate = $rate;
             $serverLog->log_at = $timestamp;
+            $serverLog->method = $method;
             $serverLog->save();
         }
     }

+ 5 - 1
app/Utils/CacheKey.php

@@ -6,7 +6,11 @@ class CacheKey
 {
     CONST KEYS = [
         'EMAIL_VERIFY_CODE' => '邮箱验证吗',
-        'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间'
+        'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间',
+        'SERVER_V2RAY_ONLINE_USER' => '节点在线用户',
+        'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间',
+        'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户',
+        'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间'
     ];
 
     public static function get(string $key, $uniqueValue)

+ 52 - 0
app/Utils/Clash.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Utils;
+
+
+class Clash
+{
+    public static function buildVmess($uuid, $server)
+    {
+        $array = [];
+        $array['name'] = $server->name;
+        $array['type'] = 'vmess';
+        $array['server'] = $server->host;
+        $array['port'] = $server->port;
+        $array['uuid'] = $uuid;
+        $array['alterId'] = 2;
+        $array['cipher'] = 'auto';
+        if ($server->tls) {
+            $tlsSettings = json_decode($server->tlsSettings);
+            $array['tls'] = true;
+            if (isset($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false );
+        }
+        if ($server->network == 'ws') {
+            $array['network'] = $server->network;
+            if ($server->networkSettings) {
+                $wsSettings = json_decode($server->networkSettings);
+                if (isset($wsSettings->path)) $array['ws-path'] = $wsSettings->path;
+                if (isset($wsSettings->headers->Host)) $array['ws-headers'] = [
+                    'Host' => $wsSettings->headers->Host
+                ];
+            }
+        }
+        return $array;
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $array = [];
+        $array['name'] = $server->name;
+        $array['type'] = 'trojan';
+        $array['server'] = $server->host;
+        $array['port'] = $server->port;
+        $array['password'] = $password;
+        $array['sni'] = $server->server_name;
+        if ($server->allow_insecure) {
+            $array['skip-cert-verify'] = true;
+        } else {
+            $array['skip-cert-verify'] = false;
+        }
+        return $array;
+    }
+}

+ 14 - 1
app/Utils/Helper.php

@@ -3,6 +3,7 @@
 namespace App\Utils;
 
 use App\Models\Server;
+use App\Models\ServerTrojan;
 use App\Models\User;
 
 class Helper
@@ -56,6 +57,18 @@ class Helper
         return $str;
     }
 
+    public static function buildTrojanLink(ServerTrojan $server, User $user)
+    {
+        $server->name = rawurlencode($server->name);
+        $query = http_build_query([
+            'allowInsecure' => $server->allow_insecure,
+            'peer' => $server->server_name
+        ]);
+        $uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$server->name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+
     public static function buildVmessLink(Server $server, User $user)
     {
         $config = [
@@ -63,7 +76,7 @@ class Helper
             "ps" => $server->name,
             "add" => $server->host,
             "port" => $server->port,
-            "id" => $user->v2ray_uuid,
+            "id" => $user->uuid,
             "aid" => "2",
             "net" => $server->network,
             "type" => "none",

+ 51 - 0
app/Utils/QuantumultX.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace App\Utils;
+
+
+class QuantumultX
+{
+    public static function buildVmess($uuid, $server)
+    {
+        $uri = "vmess=" . $server->host . ":" . $server->port . ", method=none, password=" . $uuid . ", fast-open=false, udp-relay=false, tag=" . $server->name;
+        if ($server->tls) {
+            $tlsSettings = json_decode($server->tlsSettings);
+            if ($server->network === 'tcp') $uri .= ', obfs=over-tls';
+            if (isset($tlsSettings->allowInsecure)) {
+                // Default: tls-verification=true
+                $uri .= ', tls-verification=' . ($tlsSettings->allowInsecure ? "false" : "true");
+            }
+            if (isset($tlsSettings->serverName)) {
+                $uri .= ', obfs-host=' . $tlsSettings->serverName;
+            }
+        }
+        if ($server->network === 'ws') {
+            $uri .= ', obfs=' . ($server->tls ? 'wss' : 'ws');
+            if ($server->networkSettings) {
+                $wsSettings = json_decode($server->networkSettings);
+                if (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
+                if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
+            }
+        }
+        $uri .= "\r\n";
+        return $uri;
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $config = [
+            "trojan={$server->host}:{$server->port}",
+            "password={$password}",
+            "over-tls=true",
+            $server->server_name ? "tls-host={$server->server_name}" : "",
+            $server->allow_insecure ? 'tls-verification=true' : 'tls-verification=false',
+            "fast-open=false",
+            "udp-relay=false",
+            "tag={$server->name}"
+        ];
+        $config = array_filter($config);
+        $uri = implode($config, ',');
+        $uri .= "\r\n";
+        return $uri;
+    }
+}

+ 46 - 0
app/Utils/Surge.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Utils;
+
+
+class Surge
+{
+    public static function buildVmess($uuid, $server)
+    {
+        $proxies = $server->name . ' = vmess, ' . $server->host . ', ' . $server->port . ', username=' . $uuid . ', tfo=true';
+        if ($server->tls) {
+            $tlsSettings = json_decode($server->tlsSettings);
+            $proxies .= ', tls=' . ($server->tls ? "true" : "false");
+            if (isset($tlsSettings->allowInsecure)) {
+                $proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
+            }
+        }
+        if ($server->network == 'ws') {
+            $proxies .= ', ws=true';
+            if ($server->networkSettings) {
+                $wsSettings = json_decode($server->networkSettings);
+                if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
+                if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
+            }
+        }
+        $proxies .= "\r\n";
+        return $proxies;
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $config = [
+            "{$server->name}=trojan",
+            "{$server->host}",
+            "{$server->port}",
+            "password={$password}",
+            $server->allow_insecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false',
+            $server->server_name ? "sni={$server->server_name}" : "",
+            "tfo=true"
+        ];
+        $config = array_filter($config);
+        $uri = implode($config, ',');
+        $uri .= "\r\n";
+        return $uri;
+    }
+}

+ 1 - 1
composer.json

@@ -15,7 +15,7 @@
         "laravel/tinker": "^1.0",
         "lokielse/omnipay-alipay": "3.0.6",
         "php-curl-class/php-curl-class": "^8.6",
-        "stripe/stripe-php": "^7.5",
+        "stripe/stripe-php": "^7.36.1",
         "symfony/yaml": "^4.3"
     },
     "require-dev": {

+ 1 - 1
config/app.php

@@ -236,5 +236,5 @@ return [
     | The only modification by laravel config
     |
     */
-    'version' => '1.3'
+    'version' => '1.3.1-r.1'
 ];

+ 29 - 4
database/install.sql

@@ -27,6 +27,7 @@ CREATE TABLE `v2_coupon` (
   `type` tinyint(1) NOT NULL,
   `value` int(11) NOT NULL,
   `limit_use` int(11) DEFAULT NULL,
+  `limit_plan_ids` varchar(255) DEFAULT NULL,
   `started_at` int(11) NOT NULL,
   `ended_at` int(11) NOT NULL,
   `created_at` int(11) NOT NULL,
@@ -165,6 +166,7 @@ CREATE TABLE `v2_server_log` (
   `u` varchar(255) NOT NULL,
   `d` varchar(255) NOT NULL,
   `rate` decimal(10,2) NOT NULL,
+  `method` varchar(255) NOT NULL,
   `log_at` int(11) NOT NULL,
   `created_at` int(11) NOT NULL,
   `updated_at` int(11) NOT NULL,
@@ -178,11 +180,33 @@ CREATE TABLE `v2_server_stat` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `server_id` int(11) NOT NULL,
   `u` varchar(255) NOT NULL,
-  `d` varchar(25) NOT NULL,
+  `d` varchar(255) NOT NULL,
+  `created_at` int(11) NOT NULL,
+  `updated_at` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `created_at` (`created_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `v2_server_trojan`;
+CREATE TABLE `v2_server_trojan` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '节点ID',
+  `group_id` varchar(255) NOT NULL COMMENT '节点组',
+  `parent_id` int(11) DEFAULT NULL COMMENT '父节点',
+  `tags` varchar(255) DEFAULT NULL COMMENT '节点标签',
+  `name` varchar(255) NOT NULL COMMENT '节点名称',
+  `rate` varchar(11) NOT NULL COMMENT '倍率',
+  `host` varchar(255) NOT NULL COMMENT '主机名',
+  `port` int(11) NOT NULL COMMENT '连接端口',
+  `server_port` int(11) NOT NULL COMMENT '服务端口',
+  `allow_insecure` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否允许不安全',
+  `server_name` varchar(255) DEFAULT NULL,
+  `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否显示',
+  `sort` int(11) DEFAULT NULL,
   `created_at` int(11) NOT NULL,
   `updated_at` int(11) NOT NULL,
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='trojan伺服器表';
 
 
 DROP TABLE IF EXISTS `v2_ticket`;
@@ -241,11 +265,12 @@ CREATE TABLE `v2_user` (
   `u` bigint(20) NOT NULL DEFAULT '0',
   `d` bigint(20) NOT NULL DEFAULT '0',
   `transfer_enable` bigint(20) NOT NULL DEFAULT '0',
+  `enable` tinyint(1) NOT NULL DEFAULT '1',
   `banned` tinyint(1) NOT NULL DEFAULT '0',
   `is_admin` tinyint(1) NOT NULL DEFAULT '0',
   `last_login_at` int(11) DEFAULT NULL,
   `last_login_ip` int(11) DEFAULT NULL,
-  `v2ray_uuid` varchar(36) NOT NULL,
+  `uuid` varchar(36) NOT NULL,
   `v2ray_alter_id` tinyint(4) NOT NULL DEFAULT '2',
   `v2ray_level` tinyint(4) NOT NULL DEFAULT '0',
   `group_id` int(11) DEFAULT NULL,
@@ -261,4 +286,4 @@ CREATE TABLE `v2_user` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--- 2020-05-12 12:31:04
+-- 2020-07-01 07:01:59

+ 0 - 36
database/migrations/2014_10_12_000000_create_users_table.php

@@ -1,36 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-class CreateUsersTable extends Migration
-{
-    /**
-     * Run the migrations.
-     *
-     * @return void
-     */
-    public function up()
-    {
-        Schema::create('users', function (Blueprint $table) {
-            $table->bigIncrements('id');
-            $table->string('name');
-            $table->string('email')->unique();
-            $table->timestamp('email_verified_at')->nullable();
-            $table->string('password');
-            $table->rememberToken();
-            $table->timestamps();
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     *
-     * @return void
-     */
-    public function down()
-    {
-        Schema::dropIfExists('users');
-    }
-}

+ 0 - 32
database/migrations/2014_10_12_100000_create_password_resets_table.php

@@ -1,32 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-class CreatePasswordResetsTable extends Migration
-{
-    /**
-     * Run the migrations.
-     *
-     * @return void
-     */
-    public function up()
-    {
-        Schema::create('password_resets', function (Blueprint $table) {
-            $table->string('email')->index();
-            $table->string('token');
-            $table->timestamp('created_at')->nullable();
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     *
-     * @return void
-     */
-    public function down()
-    {
-        Schema::dropIfExists('password_resets');
-    }
-}

+ 48 - 8
database/update.sql

@@ -118,14 +118,6 @@ CREATE TABLE `v2_tutorial` (
   `updated_at` int(11) NOT NULL
 );
 
-SET NAMES utf8mb4;
-
-INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
-(1,	'Windows',	'兼容 Windows 7 以上的版本',	'fab fa-2x fa-windows',	'[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压,解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url):<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置,找到Http代理,选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]',	1,	1577972408,	1577980882),
-(2,	'Android',	'兼容 Android 6 以上的版本',	'fab fa-2x fa-android',	'[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url):<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点,点击右下角的V字按钮即可连接。</div>\"}]',	1,	1577972534,	1577981610),
-(3,	'macOS',	'兼容 Yosemite 以上的版本',	'fab fa-2x fa-apple',	'[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url:<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\\n<div>Config Name:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]',	1,	1577979855,	1577981646),
-(4,	'iOS',	'兼容 iOS 9 以上的版本',	'fab fa-2x fa-apple',	'[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID:<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]',	1,	1577982016,	1577983283);
-
 ALTER TABLE `v2_server_log`
 CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`;
 
@@ -256,3 +248,51 @@ ADD INDEX log_at (`log_at`);
 
 ALTER TABLE `v2_user`
 ADD `telegram_id` bigint NULL AFTER `invite_user_id`;
+
+ALTER TABLE `v2_server_stat`
+ADD `online` int(11) NOT NULL AFTER `d`;
+
+ALTER TABLE `v2_server_stat`
+ADD INDEX `created_at` (`created_at`);
+
+CREATE TABLE `v2_server_trojan` (
+  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  `group_id` varchar(255) NOT NULL,
+  `tags` varchar(255) NULL,
+  `name` varchar(255) NOT NULL,
+  `host` varchar(255) NOT NULL,
+  `port` int(11) NOT NULL,
+  `show` tinyint(1) NOT NULL DEFAULT '0',
+  `sort` int(11) NULL,
+  `created_at` int(11) NOT NULL,
+  `updated_at` int(11) NOT NULL
+) COMMENT='trojan伺服器表' COLLATE 'utf8mb4_general_ci';
+
+ALTER TABLE `v2_server_stat`
+CHANGE `d` `d` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `u`,
+DROP `online`;
+
+ALTER TABLE `v2_user`
+CHANGE `v2ray_uuid` `uuid` varchar(36) COLLATE 'utf8_general_ci' NOT NULL AFTER `last_login_ip`;
+
+ALTER TABLE `v2_server_trojan`
+ADD `rate` varchar(11) COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `name`;
+
+ALTER TABLE `v2_server_log`
+ADD `method` varchar(255) NOT NULL AFTER `rate`;
+
+ALTER TABLE `v2_coupon`
+ADD `limit_plan_ids` varchar(255) NULL AFTER `limit_use`;
+
+ALTER TABLE `v2_server_trojan`
+ADD `server_port` int(11) NOT NULL AFTER `port`;
+
+ALTER TABLE `v2_server_trojan`
+ADD `parent_id` int(11) NULL AFTER `group_id`;
+
+ALTER TABLE `v2_server_trojan`
+ADD `allow_insecure` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否允许不安全' AFTER `server_port`,
+CHANGE `show` `show` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否显示' AFTER `allow_insecure`;
+
+ALTER TABLE `v2_server_trojan`
+ADD `server_name` varchar(255) NULL AFTER `allow_insecure`;

+ 3 - 0
library/PayTaro.php

@@ -23,6 +23,9 @@ class PayTaro
         $curl = new Curl();
         $curl->post('https://api.paytaro.com/v1/gateway/fetch', http_build_query($params));
         $result = $curl->response;
+        if (!$result) {
+            abort(500, '网络异常');
+        }
         if ($curl->error) {
             $errors = (array)$result->errors;
             abort(500, $errors[array_keys($errors)[0]][0]);

+ 0 - 63
library/TomatoPay.php

@@ -1,63 +0,0 @@
-<?php
-
-namespace Library;
-
-class TomatoPay
-{
-    private $mchid;
-    private $account;
-    private $key;
-
-    public function __construct($mchid, $account, $key)
-    {
-        $this->mchid = $mchid;
-        $this->account = $account;
-        $this->key = $key;
-    }
-
-    public function alipay($cny, $trade)
-    {
-        $params = [
-            'mchid' => $this->mchid,
-            'account' => $this->account,
-            'cny' => $cny,
-            'type' => '1',
-            'trade' => $trade
-        ];
-        $params['signs'] = $this->sign($params);
-        return $this->buildHtml('https://b.fanqieui.com/gateways/alipay.php', $params);
-    }
-
-    public function wxpay($cny, $trade)
-    {
-        $params = [
-            'mchid' => $this->mchid,
-            'account' => $this->account,
-            'cny' => $cny,
-            'type' => '1',
-            'trade' => $trade
-        ];
-        $params['signs'] = $this->sign($params);
-        return $this->buildHtml('https://b.fanqieui.com/gateways/wxpay.php', $params);
-    }
-
-    public function sign($params)
-    {
-        $o = '';
-        foreach ($params as $k => $v) {
-            $o .= "$k=" . ($v) . "&";
-        }
-        return md5(substr($o, 0, -1) . $this->key);
-    }
-
-    public function buildHtml($url, $params, $method = 'post', $target = '_self')
-    {
-        // return var_dump($params);
-        $html = "<form id='submit' name='submit' action='" . $url . "' method='$method' target='$target'>";
-        foreach ($params as $key => $value) {
-            $html .= "<input type='hidden' name='$key' value='$value'/>";
-        }
-        $html .= "</form><script>document.forms['submit'].submit();</script>";
-        return $html;
-    }
-}

+ 14 - 0
library/V2ray.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Library;
+
+
+class V2ray
+{
+    protected $config;
+
+    public function __construct()
+    {
+        $this->config = new \StdClass();
+    }
+}

文件差异内容过多而无法显示
+ 0 - 0
public/assets/admin/umi.css


文件差异内容过多而无法显示
+ 0 - 0
public/assets/admin/umi.js


文件差异内容过多而无法显示
+ 0 - 0
public/assets/user/umi.css


文件差异内容过多而无法显示
+ 0 - 0
public/assets/user/umi.js


+ 524 - 0
resources/rules/app.clash.yaml

@@ -0,0 +1,524 @@
+port: 8890
+socks-port: 8891
+allow-lan: false
+mode: rule
+log-level: info
+external-controller: 127.0.0.1:9091
+experimental:
+  ignore-resolve-fail: true
+dns:
+  enable: true
+  ipv6: false
+  enhanced-mode: redir-host
+  nameserver:
+    - 1.2.4.8
+    - 223.5.5.5
+  fallback:
+    - tls://1.0.0.1:853
+    - tls://dns.google:853
+proxies:
+
+proxy-groups:
+  - { name: "SELECT", type: select, proxies: ["自动选择", "故障转移"] }
+  - { name: "自动选择", type: url-test, proxies: [], url: "http://www.gstatic.com/generate_204", interval: 86400 }
+  - { name: "故障转移", type: fallback, proxies: [], url: "http://www.gstatic.com/generate_204", interval: 7200 }
+
+rules:
+  # Apple
+  - DOMAIN,safebrowsing.urlsec.qq.com,DIRECT # 如果您并不信任此服务提供商或防止其下载消耗过多带宽资源,可以进入 Safari 设置,关闭 Fraudulent Website Warning 功能,并使用 REJECT 策略。
+  - DOMAIN,safebrowsing.googleapis.com,DIRECT # 如果您并不信任此服务提供商或防止其下载消耗过多带宽资源,可以进入 Safari 设置,关闭 Fraudulent Website Warning 功能,并使用 REJECT 策略。
+  - DOMAIN,ocsp.apple.com,SELECT
+  - DOMAIN-SUFFIX,digicert.com,SELECT
+  - DOMAIN-SUFFIX,entrust.net,SELECT
+  - DOMAIN,ocsp.verisign.net,SELECT
+  - DOMAIN-SUFFIX,apps.apple.com,SELECT
+  - DOMAIN,itunes.apple.com,SELECT
+  - DOMAIN-SUFFIX,blobstore.apple.com,SELECT
+  - DOMAIN-SUFFIX,music.apple.com,DIRECT
+  - DOMAIN-SUFFIX,mzstatic.com,DIRECT
+  - DOMAIN-SUFFIX,itunes.apple.com,DIRECT
+  - DOMAIN-SUFFIX,icloud.com,DIRECT
+  - DOMAIN-SUFFIX,icloud-content.com,DIRECT
+  - DOMAIN-SUFFIX,me.com,DIRECT
+  - DOMAIN-SUFFIX,mzstatic.com,DIRECT
+  - DOMAIN-SUFFIX,akadns.net,DIRECT
+  - DOMAIN-SUFFIX,aaplimg.com,DIRECT
+  - DOMAIN-SUFFIX,cdn-apple.com,DIRECT
+  - DOMAIN-SUFFIX,apple.com,DIRECT
+  - DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT
+  # - DOMAIN,e.crashlytics.com,REJECT //注释此选项有助于大多数App开发者分析崩溃信息;如果您拒绝一切崩溃数据统计、搜集,请取消 # 注释。
+
+
+  # 自定义规则
+  ## 您可以在此处插入您补充的自定义规则(请注意保持缩进)
+
+  # 国内网站
+  - DOMAIN-SUFFIX,cn,DIRECT
+  - DOMAIN-KEYWORD,-cn,DIRECT
+
+  - DOMAIN-SUFFIX,126.com,DIRECT
+  - DOMAIN-SUFFIX,126.net,DIRECT
+  - DOMAIN-SUFFIX,127.net,DIRECT
+  - DOMAIN-SUFFIX,163.com,DIRECT
+  - DOMAIN-SUFFIX,360buyimg.com,DIRECT
+  - DOMAIN-SUFFIX,36kr.com,DIRECT
+  - DOMAIN-SUFFIX,acfun.tv,DIRECT
+  - DOMAIN-SUFFIX,air-matters.com,DIRECT
+  - DOMAIN-SUFFIX,aixifan.com,DIRECT
+  - DOMAIN-SUFFIX,akamaized.net,DIRECT
+  - DOMAIN-KEYWORD,alicdn,DIRECT
+  - DOMAIN-KEYWORD,alipay,DIRECT
+  - DOMAIN-KEYWORD,taobao,DIRECT
+  - DOMAIN-SUFFIX,amap.com,DIRECT
+  - DOMAIN-SUFFIX,autonavi.com,DIRECT
+  - DOMAIN-KEYWORD,baidu,DIRECT
+  - DOMAIN-SUFFIX,bdimg.com,DIRECT
+  - DOMAIN-SUFFIX,bdstatic.com,DIRECT
+  - DOMAIN-SUFFIX,bilibili.com,DIRECT
+  - DOMAIN-SUFFIX,bilivideo.com,DIRECT
+  - DOMAIN-SUFFIX,caiyunapp.com,DIRECT
+  - DOMAIN-SUFFIX,clouddn.com,DIRECT
+  - DOMAIN-SUFFIX,cnbeta.com,DIRECT
+  - DOMAIN-SUFFIX,cnbetacdn.com,DIRECT
+  - DOMAIN-SUFFIX,cootekservice.com,DIRECT
+  - DOMAIN-SUFFIX,csdn.net,DIRECT
+  - DOMAIN-SUFFIX,ctrip.com,DIRECT
+  - DOMAIN-SUFFIX,dgtle.com,DIRECT
+  - DOMAIN-SUFFIX,dianping.com,DIRECT
+  - DOMAIN-SUFFIX,douban.com,DIRECT
+  - DOMAIN-SUFFIX,doubanio.com,DIRECT
+  - DOMAIN-SUFFIX,duokan.com,DIRECT
+  - DOMAIN-SUFFIX,easou.com,DIRECT
+  - DOMAIN-SUFFIX,ele.me,DIRECT
+  - DOMAIN-SUFFIX,feng.com,DIRECT
+  - DOMAIN-SUFFIX,fir.im,DIRECT
+  - DOMAIN-SUFFIX,frdic.com,DIRECT
+  - DOMAIN-SUFFIX,g-cores.com,DIRECT
+  - DOMAIN-SUFFIX,godic.net,DIRECT
+  - DOMAIN-SUFFIX,gtimg.com,DIRECT
+  - DOMAIN,cdn.hockeyapp.net,DIRECT
+  - DOMAIN-SUFFIX,hdslb.com,DIRECT
+  - DOMAIN-SUFFIX,hongxiu.com,DIRECT
+  - DOMAIN-SUFFIX,hxcdn.net,DIRECT
+  - DOMAIN-SUFFIX,iciba.com,DIRECT
+  - DOMAIN-SUFFIX,ifeng.com,DIRECT
+  - DOMAIN-SUFFIX,ifengimg.com,DIRECT
+  - DOMAIN-SUFFIX,ipip.net,DIRECT
+  - DOMAIN-SUFFIX,iqiyi.com,DIRECT
+  - DOMAIN-SUFFIX,jd.com,DIRECT
+  - DOMAIN-SUFFIX,jianshu.com,DIRECT
+  - DOMAIN-SUFFIX,knewone.com,DIRECT
+  - DOMAIN-SUFFIX,le.com,DIRECT
+  - DOMAIN-SUFFIX,lecloud.com,DIRECT
+  - DOMAIN-SUFFIX,lemicp.com,DIRECT
+  - DOMAIN-SUFFIX,licdn.com,DIRECT
+  - DOMAIN-SUFFIX,linkedin.com,DIRECT
+  - DOMAIN-SUFFIX,luoo.net,DIRECT
+  - DOMAIN-SUFFIX,meituan.com,DIRECT
+  - DOMAIN-SUFFIX,meituan.net,DIRECT
+  - DOMAIN-SUFFIX,mi.com,DIRECT
+  - DOMAIN-SUFFIX,miaopai.com,DIRECT
+  - DOMAIN-SUFFIX,microsoft.com,DIRECT
+  - DOMAIN-SUFFIX,microsoftonline.com,DIRECT
+  - DOMAIN-SUFFIX,miui.com,DIRECT
+  - DOMAIN-SUFFIX,miwifi.com,DIRECT
+  - DOMAIN-SUFFIX,mob.com,DIRECT
+  - DOMAIN-SUFFIX,netease.com,DIRECT
+  - DOMAIN-SUFFIX,office.com,DIRECT
+  - DOMAIN-SUFFIX,office365.com,DIRECT
+  - DOMAIN-KEYWORD,officecdn,DIRECT
+  - DOMAIN-SUFFIX,oschina.net,DIRECT
+  - DOMAIN-SUFFIX,ppsimg.com,DIRECT
+  - DOMAIN-SUFFIX,pstatp.com,DIRECT
+  - DOMAIN-SUFFIX,qcloud.com,DIRECT
+  - DOMAIN-SUFFIX,qdaily.com,DIRECT
+  - DOMAIN-SUFFIX,qdmm.com,DIRECT
+  - DOMAIN-SUFFIX,qhimg.com,DIRECT
+  - DOMAIN-SUFFIX,qhres.com,DIRECT
+  - DOMAIN-SUFFIX,qidian.com,DIRECT
+  - DOMAIN-SUFFIX,qihucdn.com,DIRECT
+  - DOMAIN-SUFFIX,qiniu.com,DIRECT
+  - DOMAIN-SUFFIX,qiniucdn.com,DIRECT
+  - DOMAIN-SUFFIX,qiyipic.com,DIRECT
+  - DOMAIN-SUFFIX,qq.com,DIRECT
+  - DOMAIN-SUFFIX,qqurl.com,DIRECT
+  - DOMAIN-SUFFIX,rarbg.to,DIRECT
+  - DOMAIN-SUFFIX,ruguoapp.com,DIRECT
+  - DOMAIN-SUFFIX,segmentfault.com,DIRECT
+  - DOMAIN-SUFFIX,sinaapp.com,DIRECT
+  - DOMAIN-SUFFIX,smzdm.com,DIRECT
+  - DOMAIN-SUFFIX,snapdrop.net,DIRECT
+  - DOMAIN-SUFFIX,sogou.com,DIRECT
+  - DOMAIN-SUFFIX,sogoucdn.com,DIRECT
+  - DOMAIN-SUFFIX,sohu.com,DIRECT
+  - DOMAIN-SUFFIX,soku.com,DIRECT
+  - DOMAIN-SUFFIX,speedtest.net,DIRECT
+  - DOMAIN-SUFFIX,sspai.com,DIRECT
+  - DOMAIN-SUFFIX,suning.com,DIRECT
+  - DOMAIN-SUFFIX,taobao.com,DIRECT
+  - DOMAIN-SUFFIX,tencent.com,DIRECT
+  - DOMAIN-SUFFIX,tenpay.com,DIRECT
+  - DOMAIN-SUFFIX,tianyancha.com,DIRECT
+  - DOMAIN-SUFFIX,tmall.com,DIRECT
+  - DOMAIN-SUFFIX,tudou.com,DIRECT
+  - DOMAIN-SUFFIX,umetrip.com,DIRECT
+  - DOMAIN-SUFFIX,upaiyun.com,DIRECT
+  - DOMAIN-SUFFIX,upyun.com,DIRECT
+  - DOMAIN-SUFFIX,veryzhun.com,DIRECT
+  - DOMAIN-SUFFIX,weather.com,DIRECT
+  - DOMAIN-SUFFIX,weibo.com,DIRECT
+  - DOMAIN-SUFFIX,xiami.com,DIRECT
+  - DOMAIN-SUFFIX,xiami.net,DIRECT
+  - DOMAIN-SUFFIX,xiaomicp.com,DIRECT
+  - DOMAIN-SUFFIX,ximalaya.com,DIRECT
+  - DOMAIN-SUFFIX,xmcdn.com,DIRECT
+  - DOMAIN-SUFFIX,xunlei.com,DIRECT
+  - DOMAIN-SUFFIX,yhd.com,DIRECT
+  - DOMAIN-SUFFIX,yihaodianimg.com,DIRECT
+  - DOMAIN-SUFFIX,yinxiang.com,DIRECT
+  - DOMAIN-SUFFIX,ykimg.com,DIRECT
+  - DOMAIN-SUFFIX,youdao.com,DIRECT
+  - DOMAIN-SUFFIX,youku.com,DIRECT
+  - DOMAIN-SUFFIX,zealer.com,DIRECT
+  - DOMAIN-SUFFIX,zhihu.com,DIRECT
+  - DOMAIN-SUFFIX,zhimg.com,DIRECT
+  - DOMAIN-SUFFIX,zimuzu.tv,DIRECT
+  - DOMAIN-SUFFIX,zoho.com,DIRECT
+
+  # 抗 DNS 污染
+  - DOMAIN-KEYWORD,amazon,SELECT
+  - DOMAIN-KEYWORD,google,SELECT
+  - DOMAIN-KEYWORD,gmail,SELECT
+  - DOMAIN-KEYWORD,youtube,SELECT
+  - DOMAIN-KEYWORD,facebook,SELECT
+  - DOMAIN-SUFFIX,fb.me,SELECT
+  - DOMAIN-SUFFIX,fbcdn.net,SELECT
+  - DOMAIN-KEYWORD,twitter,SELECT
+  - DOMAIN-KEYWORD,instagram,SELECT
+  - DOMAIN-KEYWORD,dropbox,SELECT
+  - DOMAIN-SUFFIX,twimg.com,SELECT
+  - DOMAIN-KEYWORD,blogspot,SELECT
+  - DOMAIN-SUFFIX,youtu.be,SELECT
+  - DOMAIN-KEYWORD,whatsapp,SELECT
+
+  # 常见广告域名屏蔽
+  - DOMAIN-KEYWORD,admarvel,REJECT
+  - DOMAIN-KEYWORD,admaster,REJECT
+  - DOMAIN-KEYWORD,adsage,REJECT
+  - DOMAIN-KEYWORD,adsmogo,REJECT
+  - DOMAIN-KEYWORD,adsrvmedia,REJECT
+  - DOMAIN-KEYWORD,adwords,REJECT
+  - DOMAIN-KEYWORD,adservice,REJECT
+  - DOMAIN-KEYWORD,domob,REJECT
+  - DOMAIN-KEYWORD,duomeng,REJECT
+  - DOMAIN-KEYWORD,dwtrack,REJECT
+  - DOMAIN-KEYWORD,guanggao,REJECT
+  - DOMAIN-KEYWORD,lianmeng,REJECT
+  - DOMAIN-SUFFIX,mmstat.com,REJECT
+  - DOMAIN-KEYWORD,omgmta,REJECT
+  - DOMAIN-KEYWORD,openx,REJECT
+  - DOMAIN-KEYWORD,partnerad,REJECT
+  - DOMAIN-KEYWORD,pingfore,REJECT
+  - DOMAIN-KEYWORD,supersonicads,REJECT
+  - DOMAIN-KEYWORD,tracking,REJECT
+  - DOMAIN-KEYWORD,uedas,REJECT
+  - DOMAIN-KEYWORD,umeng,REJECT
+  - DOMAIN-KEYWORD,usage,REJECT
+  - DOMAIN-KEYWORD,wlmonitor,REJECT
+  - DOMAIN-KEYWORD,zjtoolbar,REJECT
+
+  # 国外网站
+  - DOMAIN-SUFFIX,9to5mac.com,SELECT
+  - DOMAIN-SUFFIX,abpchina.org,SELECT
+  - DOMAIN-SUFFIX,adblockplus.org,SELECT
+  - DOMAIN-SUFFIX,adobe.com,SELECT
+  - DOMAIN-SUFFIX,alfredapp.com,SELECT
+  - DOMAIN-SUFFIX,amplitude.com,SELECT
+  - DOMAIN-SUFFIX,ampproject.org,SELECT
+  - DOMAIN-SUFFIX,android.com,SELECT
+  - DOMAIN-SUFFIX,angularjs.org,SELECT
+  - DOMAIN-SUFFIX,aolcdn.com,SELECT
+  - DOMAIN-SUFFIX,apkpure.com,SELECT
+  - DOMAIN-SUFFIX,appledaily.com,SELECT
+  - DOMAIN-SUFFIX,appshopper.com,SELECT
+  - DOMAIN-SUFFIX,appspot.com,SELECT
+  - DOMAIN-SUFFIX,arcgis.com,SELECT
+  - DOMAIN-SUFFIX,archive.org,SELECT
+  - DOMAIN-SUFFIX,armorgames.com,SELECT
+  - DOMAIN-SUFFIX,aspnetcdn.com,SELECT
+  - DOMAIN-SUFFIX,att.com,SELECT
+  - DOMAIN-SUFFIX,awsstatic.com,SELECT
+  - DOMAIN-SUFFIX,azureedge.net,SELECT
+  - DOMAIN-SUFFIX,azurewebsites.net,SELECT
+  - DOMAIN-SUFFIX,bing.com,SELECT
+  - DOMAIN-SUFFIX,bintray.com,SELECT
+  - DOMAIN-SUFFIX,bit.com,SELECT
+  - DOMAIN-SUFFIX,bit.ly,SELECT
+  - DOMAIN-SUFFIX,bitbucket.org,SELECT
+  - DOMAIN-SUFFIX,bjango.com,SELECT
+  - DOMAIN-SUFFIX,bkrtx.com,SELECT
+  - DOMAIN-SUFFIX,blog.com,SELECT
+  - DOMAIN-SUFFIX,blogcdn.com,SELECT
+  - DOMAIN-SUFFIX,blogger.com,SELECT
+  - DOMAIN-SUFFIX,blogsmithmedia.com,SELECT
+  - DOMAIN-SUFFIX,blogspot.com,SELECT
+  - DOMAIN-SUFFIX,blogspot.hk,SELECT
+  - DOMAIN-SUFFIX,bloomberg.com,SELECT
+  - DOMAIN-SUFFIX,box.com,SELECT
+  - DOMAIN-SUFFIX,box.net,SELECT
+  - DOMAIN-SUFFIX,cachefly.net,SELECT
+  - DOMAIN-SUFFIX,chromium.org,SELECT
+  - DOMAIN-SUFFIX,cl.ly,SELECT
+  - DOMAIN-SUFFIX,cloudflare.com,SELECT
+  - DOMAIN-SUFFIX,cloudfront.net,SELECT
+  - DOMAIN-SUFFIX,cloudmagic.com,SELECT
+  - DOMAIN-SUFFIX,cmail19.com,SELECT
+  - DOMAIN-SUFFIX,cnet.com,SELECT
+  - DOMAIN-SUFFIX,cocoapods.org,SELECT
+  - DOMAIN-SUFFIX,comodoca.com,SELECT
+  - DOMAIN-SUFFIX,crashlytics.com,SELECT
+  - DOMAIN-SUFFIX,culturedcode.com,SELECT
+  - DOMAIN-SUFFIX,d.pr,SELECT
+  - DOMAIN-SUFFIX,danilo.to,SELECT
+  - DOMAIN-SUFFIX,dayone.me,SELECT
+  - DOMAIN-SUFFIX,db.tt,SELECT
+  - DOMAIN-SUFFIX,deskconnect.com,SELECT
+  - DOMAIN-SUFFIX,disq.us,SELECT
+  - DOMAIN-SUFFIX,disqus.com,SELECT
+  - DOMAIN-SUFFIX,disquscdn.com,SELECT
+  - DOMAIN-SUFFIX,dnsimple.com,SELECT
+  - DOMAIN-SUFFIX,docker.com,SELECT
+  - DOMAIN-SUFFIX,dribbble.com,SELECT
+  - DOMAIN-SUFFIX,droplr.com,SELECT
+  - DOMAIN-SUFFIX,duckduckgo.com,SELECT
+  - DOMAIN-SUFFIX,dueapp.com,SELECT
+  - DOMAIN-SUFFIX,dytt8.net,SELECT
+  - DOMAIN-SUFFIX,edgecastcdn.net,SELECT
+  - DOMAIN-SUFFIX,edgekey.net,SELECT
+  - DOMAIN-SUFFIX,edgesuite.net,SELECT
+  - DOMAIN-SUFFIX,engadget.com,SELECT
+  - DOMAIN-SUFFIX,entrust.net,SELECT
+  - DOMAIN-SUFFIX,eurekavpt.com,SELECT
+  - DOMAIN-SUFFIX,evernote.com,SELECT
+  - DOMAIN-SUFFIX,fabric.io,SELECT
+  - DOMAIN-SUFFIX,fast.com,SELECT
+  - DOMAIN-SUFFIX,fastly.net,SELECT
+  - DOMAIN-SUFFIX,fc2.com,SELECT
+  - DOMAIN-SUFFIX,feedburner.com,SELECT
+  - DOMAIN-SUFFIX,feedly.com,SELECT
+  - DOMAIN-SUFFIX,feedsportal.com,SELECT
+  - DOMAIN-SUFFIX,fiftythree.com,SELECT
+  - DOMAIN-SUFFIX,firebaseio.com,SELECT
+  - DOMAIN-SUFFIX,flexibits.com,SELECT
+  - DOMAIN-SUFFIX,flickr.com,SELECT
+  - DOMAIN-SUFFIX,flipboard.com,SELECT
+  - DOMAIN-SUFFIX,g.co,SELECT
+  - DOMAIN-SUFFIX,gabia.net,SELECT
+  - DOMAIN-SUFFIX,geni.us,SELECT
+  - DOMAIN-SUFFIX,gfx.ms,SELECT
+  - DOMAIN-SUFFIX,ggpht.com,SELECT
+  - DOMAIN-SUFFIX,ghostnoteapp.com,SELECT
+  - DOMAIN-SUFFIX,git.io,SELECT
+  - DOMAIN-KEYWORD,github,SELECT
+  - DOMAIN-SUFFIX,globalsign.com,SELECT
+  - DOMAIN-SUFFIX,gmodules.com,SELECT
+  - DOMAIN-SUFFIX,godaddy.com,SELECT
+  - DOMAIN-SUFFIX,golang.org,SELECT
+  - DOMAIN-SUFFIX,gongm.in,SELECT
+  - DOMAIN-SUFFIX,goo.gl,SELECT
+  - DOMAIN-SUFFIX,goodreaders.com,SELECT
+  - DOMAIN-SUFFIX,goodreads.com,SELECT
+  - DOMAIN-SUFFIX,gravatar.com,SELECT
+  - DOMAIN-SUFFIX,gstatic.com,SELECT
+  - DOMAIN-SUFFIX,gvt0.com,SELECT
+  - DOMAIN-SUFFIX,hockeyapp.net,SELECT
+  - DOMAIN-SUFFIX,hotmail.com,SELECT
+  - DOMAIN-SUFFIX,icons8.com,SELECT
+  - DOMAIN-SUFFIX,ifixit.com,SELECT
+  - DOMAIN-SUFFIX,ift.tt,SELECT
+  - DOMAIN-SUFFIX,ifttt.com,SELECT
+  - DOMAIN-SUFFIX,iherb.com,SELECT
+  - DOMAIN-SUFFIX,imageshack.us,SELECT
+  - DOMAIN-SUFFIX,img.ly,SELECT
+  - DOMAIN-SUFFIX,imgur.com,SELECT
+  - DOMAIN-SUFFIX,imore.com,SELECT
+  - DOMAIN-SUFFIX,instapaper.com,SELECT
+  - DOMAIN-SUFFIX,ipn.li,SELECT
+  - DOMAIN-SUFFIX,is.gd,SELECT
+  - DOMAIN-SUFFIX,issuu.com,SELECT
+  - DOMAIN-SUFFIX,itgonglun.com,SELECT
+  - DOMAIN-SUFFIX,itun.es,SELECT
+  - DOMAIN-SUFFIX,ixquick.com,SELECT
+  - DOMAIN-SUFFIX,j.mp,SELECT
+  - DOMAIN-SUFFIX,js.revsci.net,SELECT
+  - DOMAIN-SUFFIX,jshint.com,SELECT
+  - DOMAIN-SUFFIX,jtvnw.net,SELECT
+  - DOMAIN-SUFFIX,justgetflux.com,SELECT
+  - DOMAIN-SUFFIX,kat.cr,SELECT
+  - DOMAIN-SUFFIX,klip.me,SELECT
+  - DOMAIN-SUFFIX,libsyn.com,SELECT
+  - DOMAIN-SUFFIX,linode.com,SELECT
+  - DOMAIN-SUFFIX,lithium.com,SELECT
+  - DOMAIN-SUFFIX,littlehj.com,SELECT
+  - DOMAIN-SUFFIX,live.com,SELECT
+  - DOMAIN-SUFFIX,live.net,SELECT
+  - DOMAIN-SUFFIX,livefilestore.com,SELECT
+  - DOMAIN-SUFFIX,llnwd.net,SELECT
+  - DOMAIN-SUFFIX,macid.co,SELECT
+  - DOMAIN-SUFFIX,macromedia.com,SELECT
+  - DOMAIN-SUFFIX,macrumors.com,SELECT
+  - DOMAIN-SUFFIX,mashable.com,SELECT
+  - DOMAIN-SUFFIX,mathjax.org,SELECT
+  - DOMAIN-SUFFIX,medium.com,SELECT
+  - DOMAIN-SUFFIX,mega.co.nz,SELECT
+  - DOMAIN-SUFFIX,mega.nz,SELECT
+  - DOMAIN-SUFFIX,megaupload.com,SELECT
+  - DOMAIN-SUFFIX,microsofttranslator.com,SELECT
+  - DOMAIN-SUFFIX,mindnode.com,SELECT
+  - DOMAIN-SUFFIX,mobile01.com,SELECT
+  - DOMAIN-SUFFIX,modmyi.com,SELECT
+  - DOMAIN-SUFFIX,msedge.net,SELECT
+  - DOMAIN-SUFFIX,myfontastic.com,SELECT
+  - DOMAIN-SUFFIX,name.com,SELECT
+  - DOMAIN-SUFFIX,nextmedia.com,SELECT
+  - DOMAIN-SUFFIX,nsstatic.net,SELECT
+  - DOMAIN-SUFFIX,nssurge.com,SELECT
+  - DOMAIN-SUFFIX,nyt.com,SELECT
+  - DOMAIN-SUFFIX,nytimes.com,SELECT
+  - DOMAIN-SUFFIX,omnigroup.com,SELECT
+  - DOMAIN-SUFFIX,onedrive.com,SELECT
+  - DOMAIN-SUFFIX,onenote.com,SELECT
+  - DOMAIN-SUFFIX,ooyala.com,SELECT
+  - DOMAIN-SUFFIX,openvpn.net,SELECT
+  - DOMAIN-SUFFIX,openwrt.org,SELECT
+  - DOMAIN-SUFFIX,orkut.com,SELECT
+  - DOMAIN-SUFFIX,osxdaily.com,SELECT
+  - DOMAIN-SUFFIX,outlook.com,SELECT
+  - DOMAIN-SUFFIX,ow.ly,SELECT
+  - DOMAIN-SUFFIX,paddleapi.com,SELECT
+  - DOMAIN-SUFFIX,parallels.com,SELECT
+  - DOMAIN-SUFFIX,parse.com,SELECT
+  - DOMAIN-SUFFIX,pdfexpert.com,SELECT
+  - DOMAIN-SUFFIX,periscope.tv,SELECT
+  - DOMAIN-SUFFIX,pinboard.in,SELECT
+  - DOMAIN-SUFFIX,pinterest.com,SELECT
+  - DOMAIN-SUFFIX,pixelmator.com,SELECT
+  - DOMAIN-SUFFIX,pixiv.net,SELECT
+  - DOMAIN-SUFFIX,playpcesor.com,SELECT
+  - DOMAIN-SUFFIX,playstation.com,SELECT
+  - DOMAIN-SUFFIX,playstation.com.hk,SELECT
+  - DOMAIN-SUFFIX,playstation.net,SELECT
+  - DOMAIN-SUFFIX,playstationnetwork.com,SELECT
+  - DOMAIN-SUFFIX,pushwoosh.com,SELECT
+  - DOMAIN-SUFFIX,rime.im,SELECT
+  - DOMAIN-SUFFIX,servebom.com,SELECT
+  - DOMAIN-SUFFIX,sfx.ms,SELECT
+  - DOMAIN-SUFFIX,shadowsocks.org,SELECT
+  - DOMAIN-SUFFIX,sharethis.com,SELECT
+  - DOMAIN-SUFFIX,shazam.com,SELECT
+  - DOMAIN-SUFFIX,skype.com,SELECT
+  - DOMAIN-SUFFIX,smartdnsSELECT.com,SELECT
+  - DOMAIN-SUFFIX,smartmailcloud.com,SELECT
+  - DOMAIN-SUFFIX,sndcdn.com,SELECT
+  - DOMAIN-SUFFIX,sony.com,SELECT
+  - DOMAIN-SUFFIX,soundcloud.com,SELECT
+  - DOMAIN-SUFFIX,sourceforge.net,SELECT
+  - DOMAIN-SUFFIX,spotify.com,SELECT
+  - DOMAIN-SUFFIX,squarespace.com,SELECT
+  - DOMAIN-SUFFIX,sstatic.net,SELECT
+  - DOMAIN-SUFFIX,st.luluku.pw,SELECT
+  - DOMAIN-SUFFIX,stackoverflow.com,SELECT
+  - DOMAIN-SUFFIX,startpage.com,SELECT
+  - DOMAIN-SUFFIX,staticflickr.com,SELECT
+  - DOMAIN-SUFFIX,steamcommunity.com,SELECT
+  - DOMAIN-SUFFIX,symauth.com,SELECT
+  - DOMAIN-SUFFIX,symcb.com,SELECT
+  - DOMAIN-SUFFIX,symcd.com,SELECT
+  - DOMAIN-SUFFIX,tapbots.com,SELECT
+  - DOMAIN-SUFFIX,tapbots.net,SELECT
+  - DOMAIN-SUFFIX,tdesktop.com,SELECT
+  - DOMAIN-SUFFIX,techcrunch.com,SELECT
+  - DOMAIN-SUFFIX,techsmith.com,SELECT
+  - DOMAIN-SUFFIX,thepiratebay.org,SELECT
+  - DOMAIN-SUFFIX,theverge.com,SELECT
+  - DOMAIN-SUFFIX,time.com,SELECT
+  - DOMAIN-SUFFIX,timeinc.net,SELECT
+  - DOMAIN-SUFFIX,tiny.cc,SELECT
+  - DOMAIN-SUFFIX,tinypic.com,SELECT
+  - DOMAIN-SUFFIX,tmblr.co,SELECT
+  - DOMAIN-SUFFIX,todoist.com,SELECT
+  - DOMAIN-SUFFIX,trello.com,SELECT
+  - DOMAIN-SUFFIX,trustasiassl.com,SELECT
+  - DOMAIN-SUFFIX,tumblr.co,SELECT
+  - DOMAIN-SUFFIX,tumblr.com,SELECT
+  - DOMAIN-SUFFIX,tweetdeck.com,SELECT
+  - DOMAIN-SUFFIX,tweetmarker.net,SELECT
+  - DOMAIN-SUFFIX,twitch.tv,SELECT
+  - DOMAIN-SUFFIX,txmblr.com,SELECT
+  - DOMAIN-SUFFIX,typekit.net,SELECT
+  - DOMAIN-SUFFIX,ubertags.com,SELECT
+  - DOMAIN-SUFFIX,ublock.org,SELECT
+  - DOMAIN-SUFFIX,ubnt.com,SELECT
+  - DOMAIN-SUFFIX,ulyssesapp.com,SELECT
+  - DOMAIN-SUFFIX,urchin.com,SELECT
+  - DOMAIN-SUFFIX,usertrust.com,SELECT
+  - DOMAIN-SUFFIX,v.gd,SELECT
+  - DOMAIN-SUFFIX,v2ex.com,SELECT
+  - DOMAIN-SUFFIX,vimeo.com,SELECT
+  - DOMAIN-SUFFIX,vimeocdn.com,SELECT
+  - DOMAIN-SUFFIX,vine.co,SELECT
+  - DOMAIN-SUFFIX,vivaldi.com,SELECT
+  - DOMAIN-SUFFIX,vox-cdn.com,SELECT
+  - DOMAIN-SUFFIX,vsco.co,SELECT
+  - DOMAIN-SUFFIX,vultr.com,SELECT
+  - DOMAIN-SUFFIX,w.org,SELECT
+  - DOMAIN-SUFFIX,w3schools.com,SELECT
+  - DOMAIN-SUFFIX,webtype.com,SELECT
+  - DOMAIN-SUFFIX,wikiwand.com,SELECT
+  - DOMAIN-SUFFIX,wikileaks.org,SELECT
+  - DOMAIN-SUFFIX,wikimedia.org,SELECT
+  - DOMAIN-SUFFIX,wikipedia.com,SELECT
+  - DOMAIN-SUFFIX,wikipedia.org,SELECT
+  - DOMAIN-SUFFIX,windows.com,SELECT
+  - DOMAIN-SUFFIX,windows.net,SELECT
+  - DOMAIN-SUFFIX,wire.com,SELECT
+  - DOMAIN-SUFFIX,wordpress.com,SELECT
+  - DOMAIN-SUFFIX,workflowy.com,SELECT
+  - DOMAIN-SUFFIX,wp.com,SELECT
+  - DOMAIN-SUFFIX,wsj.com,SELECT
+  - DOMAIN-SUFFIX,wsj.net,SELECT
+  - DOMAIN-SUFFIX,xda-developers.com,SELECT
+  - DOMAIN-SUFFIX,xeeno.com,SELECT
+  - DOMAIN-SUFFIX,xiti.com,SELECT
+  - DOMAIN-SUFFIX,yahoo.com,SELECT
+  - DOMAIN-SUFFIX,yimg.com,SELECT
+  - DOMAIN-SUFFIX,ying.com,SELECT
+  - DOMAIN-SUFFIX,yoyo.org,SELECT
+  - DOMAIN-SUFFIX,ytimg.com,SELECT
+
+  # Telegram
+  - DOMAIN-SUFFIX,telegra.ph,SELECT
+  - DOMAIN-SUFFIX,telegram.org,SELECT
+
+  - IP-CIDR,91.108.4.0/22,SELECT,no-resolve
+  - IP-CIDR,91.108.8.0/22,SELECT,no-resolve
+  - IP-CIDR,91.108.12.0/22,SELECT,no-resolve
+  - IP-CIDR,91.108.16.0/22,SELECT,no-resolve
+  - IP-CIDR,91.108.56.0/22,SELECT,no-resolve
+  - IP-CIDR,149.154.160.0/22,SELECT,no-resolve
+  - IP-CIDR,149.154.164.0/22,SELECT,no-resolve
+  - IP-CIDR,149.154.168.0/22,SELECT,no-resolve
+  - IP-CIDR,149.154.172.0/22,SELECT,no-resolve
+
+  # LAN
+  - DOMAIN-SUFFIX,local,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
+  - IP-CIDR,10.0.0.0/8,DIRECT
+  - IP-CIDR,17.0.0.0/8,DIRECT
+  - IP-CIDR,100.64.0.0/10,DIRECT
+
+  # 最终规则
+  - GEOIP,CN,DIRECT
+  - MATCH,SELECT

+ 4 - 4
resources/rules/default.clash.yaml

@@ -1,7 +1,7 @@
 port: 7890
 socks-port: 7891
 allow-lan: false
-mode: Rule
+mode: rule
 log-level: info
 external-controller: 127.0.0.1:9090
 experimental:
@@ -16,14 +16,14 @@ dns:
   fallback:
     - tls://1.0.0.1:853
     - tls://dns.google:853
-Proxy:
+proxies:
 
-Proxy Group:
+proxy-groups:
   - { name: "$app_name", type: select, proxies: ["自动选择", "故障转移"] }
   - { name: "自动选择", type: url-test, proxies: [], url: "http://www.gstatic.com/generate_204", interval: 86400 }
   - { name: "故障转移", type: fallback, proxies: [], url: "http://www.gstatic.com/generate_204", interval: 7200 }
 
-Rule:
+rules:
   # Apple
   - DOMAIN,safebrowsing.urlsec.qq.com,DIRECT # 如果您并不信任此服务提供商或防止其下载消耗过多带宽资源,可以进入 Safari 设置,关闭 Fraudulent Website Warning 功能,并使用 REJECT 策略。
   - DOMAIN,safebrowsing.googleapis.com,DIRECT # 如果您并不信任此服务提供商或防止其下载消耗过多带宽资源,可以进入 Safari 设置,关闭 Fraudulent Website Warning 功能,并使用 REJECT 策略。

+ 1 - 1
resources/rules/default.surge.conf

@@ -13,7 +13,7 @@ http-listen = 0.0.0.0:6152
 socks5-listen = 0.0.0.0:6153
 
 test-timeout = 4
-network-framework = true
+network-framework = false
 proxy-test-url = http://www.gstatic.com/generate_204
 
 external-controller-access = surgepasswd@0.0.0.0:6170

部分文件因为文件数量过多而无法显示