Procházet zdrojové kódy

Merge pull request #356 from v2board/dev

1.4.1
tokumeikoi před 4 roky
rodič
revize
c72e6cf4ea
59 změnil soubory, kde provedl 836 přidání a 737 odebrání
  1. 9 10
      app/Console/Commands/CheckCommission.php
  2. 9 1
      app/Http/Controllers/Admin/ConfigController.php
  3. 4 4
      app/Http/Controllers/Admin/CouponController.php
  4. 1 1
      app/Http/Controllers/Admin/OrderController.php
  5. 64 0
      app/Http/Controllers/Admin/Server/ManageController.php
  6. 0 35
      app/Http/Controllers/Admin/Server/ShadowsocksController.php
  7. 0 36
      app/Http/Controllers/Admin/Server/TrojanController.php
  8. 0 35
      app/Http/Controllers/Admin/Server/V2rayController.php
  9. 3 3
      app/Http/Controllers/Admin/StatController.php
  10. 42 18
      app/Http/Controllers/Client/AppController.php
  11. 123 92
      app/Http/Controllers/Client/ClientController.php
  12. 2 1
      app/Http/Controllers/Passport/CommController.php
  13. 19 23
      app/Http/Controllers/Server/DeepbworkController.php
  14. 19 16
      app/Http/Controllers/Server/PoseidonController.php
  15. 13 37
      app/Http/Controllers/Server/ShadowsocksTidalabController.php
  16. 14 21
      app/Http/Controllers/Server/TrojanTidalabController.php
  17. 0 1
      app/Http/Controllers/User/CouponController.php
  18. 5 1
      app/Http/Controllers/User/OrderController.php
  19. 1 2
      app/Http/Controllers/User/ServerController.php
  20. 0 1
      app/Http/Kernel.php
  21. 8 1
      app/Http/Requests/Admin/ConfigSave.php
  22. 1 1
      app/Http/Requests/Admin/OrderAssign.php
  23. 1 1
      app/Http/Requests/Admin/OrderUpdate.php
  24. 0 28
      app/Http/Requests/Admin/ServerShadowsocksSort.php
  25. 0 28
      app/Http/Requests/Admin/ServerTrojanSort.php
  26. 1 0
      app/Http/Requests/Admin/ServerV2raySave.php
  27. 0 28
      app/Http/Requests/Admin/ServerV2raySort.php
  28. 2 0
      app/Http/Routes/AdminRoute.php
  29. 1 1
      app/Http/Routes/GuestRoute.php
  30. 3 2
      app/Services/MailService.php
  31. 8 1
      app/Services/OrderService.php
  32. 114 47
      app/Services/ServerService.php
  33. 17 3
      app/Services/UserService.php
  34. 1 1
      app/Utils/CacheKey.php
  35. 31 25
      app/Utils/Clash.php
  36. 31 32
      app/Utils/QuantumultX.php
  37. 29 19
      app/Utils/Shadowrocket.php
  38. 23 27
      app/Utils/Surfboard.php
  39. 29 36
      app/Utils/Surge.php
  40. 34 24
      app/Utils/URLSchemes.php
  41. 1 1
      config/app.php
  42. 4 4
      database/install.sql
  43. 10 0
      database/update.sql
  44. 0 0
      public/assets/admin/components.async.js
  45. 0 0
      public/assets/admin/components.chunk.css
  46. 0 0
      public/assets/admin/umi.css
  47. 0 0
      public/assets/admin/umi.js
  48. 0 0
      public/assets/admin/vendors.async.js
  49. 0 0
      public/assets/user/components.async.js
  50. 0 0
      public/assets/user/umi.js
  51. 0 0
      public/assets/user/vendors.async.js
  52. 15 3
      resources/rules/app.clash.yaml
  53. 58 28
      resources/rules/default.clash.yaml
  54. 55 36
      resources/rules/default.surfboard.conf
  55. 24 15
      resources/rules/default.surge.conf
  56. 1 1
      resources/views/mail/classic/notify.blade.php
  57. 2 2
      resources/views/mail/classic/remindExpire.blade.php
  58. 2 2
      resources/views/mail/classic/remindTraffic.blade.php
  59. 2 2
      resources/views/mail/classic/verify.blade.php

+ 9 - 10
app/Console/Commands/CheckCommission.php

@@ -47,7 +47,8 @@ class CheckCommission extends Command
     {
         if ((int)config('v2board.commission_auto_check_enable', 1)) {
             Order::where('commission_status', 0)
-                ->where('status', 3)
+                ->where('invite_user_id', '!=', NULL)
+                ->whereIn('status', [3, 4])
                 ->where('updated_at', '<=', strtotime('-3 day', time()))
                 ->update([
                     'commission_status' => 1
@@ -58,17 +59,15 @@ class CheckCommission extends Command
     public function autoPayCommission()
     {
         $order = Order::where('commission_status', 1)
-            ->where('status', 3)
+            ->where('invite_user_id', '!=', NULL)
             ->get();
         foreach ($order as $item) {
-            if ($item->invite_user_id) {
-                $inviter = User::find($item->invite_user_id);
-                if (!$inviter) continue;
-                $inviter->commission_balance = $inviter->commission_balance + $item->commission_balance;
-                if ($inviter->save()) {
-                    $item->commission_status = 2;
-                    $item->save();
-                }
+            $inviter = User::find($item->invite_user_id);
+            if (!$inviter) continue;
+            $inviter->commission_balance = $inviter->commission_balance + $item->commission_balance;
+            if ($inviter->save()) {
+                $item->commission_status = 2;
+                $item->save();
             }
         }
     }

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

@@ -132,6 +132,14 @@ class ConfigController extends Controller
                 'telegram' => [
                     'telegram_bot_enable' => config('v2board.telegram_bot_enable', 0),
                     'telegram_bot_token' => config('v2board.telegram_bot_token')
+                ],
+                'app' => [
+                    'windows_version' => config('v2board.windows_version'),
+                    'windows_download_url' => config('v2board.windows_download_url'),
+                    'macos_version' => config('v2board.macos_version'),
+                    'macos_download_url' => config('v2board.macos_download_url'),
+                    'android_version' => config('v2board.android_version'),
+                    'android_download_url' => config('v2board.android_download_url')
                 ]
             ]
         ]);
@@ -152,7 +160,7 @@ class ConfigController extends Controller
             abort(500, '修改失败');
         }
         if (function_exists('opcache_reset')) {
-            if (!opcache_reset()) {
+            if (opcache_reset() === false) {
                 abort(500, '缓存清除失败,请卸载或检查opcache配置状态');
             }
         }

+ 4 - 4
app/Http/Controllers/Admin/CouponController.php

@@ -94,12 +94,12 @@ class CouponController extends Controller
     private function multiGenerate(CouponGenerate $request)
     {
         $coupons = [];
+        $coupon = $request->validated();
+        $coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
+        $coupon['created_at'] = $coupon['updated_at'] = time();
+        unset($coupon['generate_count']);
         for ($i = 0;$i < $request->input('generate_count');$i++) {
-            $coupon = $request->validated();
-            $coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
             $coupon['code'] = Helper::randomChar(8);
-            $coupon['created_at'] = $coupon['updated_at'] = time();
-            unset($coupon['generate_count']);
             array_push($coupons, $coupon);
         }
         DB::beginTransaction();

+ 1 - 1
app/Http/Controllers/Admin/OrderController.php

@@ -25,7 +25,7 @@ class OrderController extends Controller
         }
         if ($request->input('is_commission')) {
             $orderModel->where('invite_user_id', '!=', NULL);
-            $orderModel->where('status', 3);
+            $orderModel->whereIn('status', [3, 4]);
             $orderModel->where('commission_balance', '>', 0);
         }
         if ($request->input('id')) {

+ 64 - 0
app/Http/Controllers/Admin/Server/ManageController.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Server;
+
+use App\Http\Requests\Admin\ServerTrojanSort;
+use App\Models\Plan;
+use App\Models\Server;
+use App\Models\ServerGroup;
+use App\Models\ServerShadowsocks;
+use App\Models\ServerTrojan;
+use App\Models\User;
+use App\Services\ServerService;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+
+class ManageController extends Controller
+{
+    public function getNodes(Request $request)
+    {
+        $serverService = new ServerService();
+        $servers = array_merge(
+            $serverService->getShadowsocksServers(),
+            $serverService->getV2rayServers(),
+            $serverService->getTrojanServers()
+        );
+        $tmp = array_column($servers, 'sort');
+        array_multisort($tmp, SORT_ASC, $servers);
+        return response([
+            'data' => $servers
+        ]);
+    }
+
+    public function sort(Request $request)
+    {
+        DB::beginTransaction();
+        foreach ($request->input('sorts') as $k => $v) {
+            switch ($v['key']) {
+                case 'shadowsocks':
+                    if (!ServerShadowsocks::find($v['value'])->update(['sort' => $k + 1])) {
+                        DB::rollBack();
+                        abort(500, '保存失败');
+                    }
+                    break;
+                case 'v2ray':
+                    if (!Server::find($v['value'])->update(['sort' => $k + 1])) {
+                        DB::rollBack();
+                        abort(500, '保存失败');
+                    }
+                    break;
+                case 'trojan':
+                    if (!ServerTrojan::find($v['value'])->update(['sort' => $k + 1])) {
+                        DB::rollBack();
+                        abort(500, '保存失败');
+                    }
+                    break;
+            }
+        }
+        DB::commit();
+        return response([
+            'data' => true
+        ]);
+    }
+}

+ 0 - 35
app/Http/Controllers/Admin/Server/ShadowsocksController.php

@@ -15,26 +15,6 @@ use Illuminate\Support\Facades\DB;
 
 class ShadowsocksController extends Controller
 {
-    public function fetch(Request $request)
-    {
-        $server = ServerShadowsocks::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_SHADOWSOCKS_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(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server[$i]['parent_id']));
-            } else {
-                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server[$i]['id']));
-            }
-        }
-        return response([
-            'data' => $server
-        ]);
-    }
-
     public function save(ServerShadowsocksSave $request)
     {
         $params = $request->validated();
@@ -117,19 +97,4 @@ class ShadowsocksController extends Controller
             'data' => true
         ]);
     }
-
-    public function sort(ServerShadowsocksSort $request)
-    {
-        DB::beginTransaction();
-        foreach ($request->input('server_ids') as $k => $v) {
-            if (!ServerShadowsocks::find($v)->update(['sort' => $k + 1])) {
-                DB::rollBack();
-                abort(500, '保存失败');
-            }
-        }
-        DB::commit();
-        return response([
-            'data' => true
-        ]);
-    }
 }

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

@@ -15,26 +15,6 @@ 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]['parent_id'] ? $server[$i]['parent_id'] : $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->validated();
@@ -117,22 +97,6 @@ class TrojanController extends Controller
             '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();

+ 0 - 35
app/Http/Controllers/Admin/Server/V2rayController.php

@@ -15,26 +15,6 @@ use Illuminate\Support\Facades\DB;
 
 class V2rayController extends Controller
 {
-    public function fetch(Request $request)
-    {
-        $server = Server::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_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(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['parent_id']));
-            } else {
-                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['id']));
-            }
-        }
-        return response([
-            'data' => $server
-        ]);
-    }
-
     public function save(ServerV2raySave $request)
     {
         $params = $request->validated();
@@ -150,19 +130,4 @@ class V2rayController extends Controller
             'data' => $config
         ]);
     }
-
-    public function sort(ServerV2raySort $request)
-    {
-        DB::beginTransaction();
-        foreach ($request->input('server_ids') as $k => $v) {
-            if (!Server::find($v)->update(['sort' => $k + 1])) {
-                DB::rollBack();
-                abort(500, '保存失败');
-            }
-        }
-        DB::commit();
-        return response([
-            'data' => true
-        ]);
-    }
 }

+ 3 - 3
app/Http/Controllers/Admin/StatController.php

@@ -29,16 +29,16 @@ class StatController extends Controller
                     ->count(),
                 'commission_pendding_total' => Order::where('commission_status', 0)
                     ->where('invite_user_id', '!=', NULL)
-                    ->where('status', 3)
+                    ->where('status', [3, 4])
                     ->where('commission_balance', '>', 0)
                     ->count(),
                 'day_income' => Order::where('created_at', '>=', strtotime(date('Y-m-d')))
                     ->where('created_at', '<', time())
-                    ->where('status', 3)
+                    ->whereIn('status', [3, 4])
                     ->sum('total_amount'),
                 'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
                     ->where('created_at', '<', strtotime(date('Y-m-1')))
-                    ->where('status', 3)
+                    ->whereIn('status', [3, 4])
                     ->sum('total_amount')
             ]
         ]);

+ 42 - 18
app/Http/Controllers/Client/AppController.php

@@ -23,25 +23,25 @@ class AppController extends Controller
         $userService = new UserService();
         if ($userService->isAvailable($user)) {
             $serverService = new ServerService();
-            $servers = $serverService->getAllServers($user);
+            $servers = $serverService->getAvailableServers($user);
         }
         $config = Yaml::parseFile(base_path() . '/resources/rules/app.clash.yaml');
         $proxy = [];
         $proxies = [];
 
-        foreach ($servers['shadowsocks'] as $item) {
-            array_push($proxy, Clash::buildShadowsocks($user->uuid, $item));
-            array_push($proxies, $item->name);
-        }
-
-        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);
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                array_push($proxy, Clash::buildShadowsocks($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+            if ($item['type'] === 'v2ray') {
+                array_push($proxy, Clash::buildVmess($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+            if ($item['type'] === 'trojan') {
+                array_push($proxy, Clash::buildTrojan($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
         }
 
         $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
@@ -51,12 +51,36 @@ class AppController extends Controller
         die(Yaml::dump($config));
     }
 
-    public function getVersion()
+    public function getVersion(Request $request)
     {
+        if (strpos($request->header('user-agent'), 'tidalab/4.0.0') !== false
+            || strpos($request->header('user-agent'), 'tunnelab/4.0.0') !== false
+        ) {
+            if (strpos($request->header('user-agent'), 'Win64') !== false) {
+                return response([
+                    'data' => [
+                        'version' => config('v2board.windows_version'),
+                        'download_url' => config('v2board.windows_download_url')
+                    ]
+                ]);
+            } else {
+                return response([
+                    'data' => [
+                        'version' => config('v2board.macos_version'),
+                        'download_url' => config('v2board.macos_download_url')
+                    ]
+                ]);
+            }
+            return;
+        }
         return response([
             'data' => [
-                'version' => '4.0.0',
-                'download_url' => ''
+                'windows_version' => config('v2board.windows_version'),
+                'windows_download_url' => config('v2board.windows_download_url'),
+                'macos_version' => config('v2board.macos_version'),
+                'macos_download_url' => config('v2board.macos_download_url'),
+                'android_version' => config('v2board.android_version'),
+                'android_download_url' => config('v2board.android_download_url')
             ]
         ]);
     }
@@ -85,7 +109,7 @@ class AppController extends Controller
         $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->uuid;
-        $json->outbound->settings->vnext[0]->users[0]->alterId = (int)$user->v2ray_alter_id;
+        $json->outbound->settings->vnext[0]->users[0]->alterId = (int)$server->alter_id;
         $json->outbound->settings->vnext[0]->remark = (string)$server->name;
         $json->outbound->streamSettings->network = $server->network;
         if ($server->networkSettings) {

+ 123 - 92
app/Http/Controllers/Client/ClientController.php

@@ -30,52 +30,57 @@ class ClientController extends Controller
         $userService = new UserService();
         if ($userService->isAvailable($user)) {
             $serverService = new ServerService();
-            $servers = $serverService->getAllServers($user);
+            $servers = $serverService->getAvailableServers($user);
             if ($flag) {
                 if (strpos($flag, 'quantumult%20x') !== false) {
-                    die($this->quantumultX($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
+                    die($this->quantumultX($user, $servers));
                 }
                 if (strpos($flag, 'quantumult') !== false) {
-                    die($this->quantumult($user, $servers['vmess']));
+                    die($this->quantumult($user, $servers));
                 }
                 if (strpos($flag, 'clash') !== false) {
-                    die($this->clash($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
+                    die($this->clash($user, $servers));
                 }
                 if (strpos($flag, 'surfboard') !== false) {
-                    die($this->surfboard($user, $servers['shadowsocks'], $servers['vmess']));
+                    die($this->surfboard($user, $servers));
                 }
                 if (strpos($flag, 'surge') !== false) {
-                    die($this->surge($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
+                    die($this->surge($user, $servers));
                 }
                 if (strpos($flag, 'shadowrocket') !== false) {
-                    die($this->shadowrocket($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
+                    die($this->shadowrocket($user, $servers));
+                }
+                if (strpos($flag, 'shadowsocks') !== false) {
+                    die($this->shaodowsocksSIP008($user, $servers));
                 }
             }
-            die($this->origin($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
+            die($this->origin($user, $servers));
         }
     }
     // TODO: Ready to stop support
-    private function quantumult($user, $vmess = [])
+    private function quantumult($user, $servers = [])
     {
         $uri = '';
         header('subscription-userinfo: upload=' . $user->u . '; download=' . $user->d . ';total=' . $user->transfer_enable);
-        foreach ($vmess as $item) {
-            $str = '';
-            $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) {
-                    $wsSettings = json_decode($item->networkSettings);
-                    if (isset($wsSettings->path)) $str .= ', obfs-path="' . $wsSettings->path . '"';
-                    if (isset($wsSettings->headers->Host)) $str .= ', obfs-header="Host:' . $wsSettings->headers->Host . '"';
+        foreach ($servers as $item) {
+            if ($item['type'] === 'v2ray') {
+                $str = '';
+                $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']) {
+                        $wsSettings = json_decode($item['networkSettings'], true);
+                        if (isset($wsSettings['path'])) $str .= ', obfs-path="' . $wsSettings['path'] . '"';
+                        if (isset($wsSettings['headers']['Host'])) $str .= ', obfs-header="Host:' . $wsSettings['headers']['Host'] . '"';
+                    }
                 }
+                $uri .= "vmess://" . base64_encode($str) . "\r\n";
             }
-            $uri .= "vmess://" . base64_encode($str) . "\r\n";
         }
         return base64_encode($uri);
     }
 
-    private function shadowrocket($user, $shadowsocks = [], $vmess = [], $trojan = [])
+    private function shadowrocket($user, $servers = [])
     {
         $uri = '';
         //display remaining traffic and expire date
@@ -84,73 +89,98 @@ class ClientController extends Controller
         $totalTraffic = round($user->transfer_enable / (1024*1024*1024), 2);
         $expiredDate = date('Y-m-d', $user->expired_at);
         $uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
-        foreach ($shadowsocks as $item) {
-            $uri .= Shadowrocket::buildShadowsocks($user->uuid, $item);
-        }
-        foreach ($vmess as $item) {
-            $uri .= Shadowrocket::buildVmess($user->uuid, $item);
-        }
-        foreach ($trojan as $item) {
-            $uri .= Shadowrocket::buildTrojan($user->uuid, $item);
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= Shadowrocket::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'v2ray') {
+                $uri .= Shadowrocket::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= Shadowrocket::buildTrojan($user['uuid'], $item);
+            }
         }
         return base64_encode($uri);
     }
 
-    private function quantumultX($user, $shadowsocks = [], $vmess = [], $trojan = [])
+    private function quantumultX($user, $servers = [])
     {
         $uri = '';
         header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}");
-        foreach ($shadowsocks as $item) {
-            $uri .= QuantumultX::buildShadowsocks($user->uuid, $item);
-        }
-        foreach ($vmess as $item) {
-            $uri .= QuantumultX::buildVmess($user->uuid, $item);
-        }
-        foreach ($trojan as $item) {
-            $uri .= QuantumultX::buildTrojan($user->uuid, $item);
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= QuantumultX::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'v2ray') {
+                $uri .= QuantumultX::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= QuantumultX::buildTrojan($user['uuid'], $item);
+            }
         }
         return base64_encode($uri);
     }
 
-    private function origin($user, $shadowsocks = [], $vmess = [], $trojan = [])
+    private function origin($user, $servers = [])
     {
         $uri = '';
-        foreach ($shadowsocks as $item) {
-            $uri .= URLSchemes::buildShadowsocks($item, $user);
-        }
-        foreach ($vmess as $item) {
-            $uri .= URLSchemes::buildVmess($item, $user);
-        }
-        foreach ($trojan as $item) {
-            $uri .= URLSchemes::buildTrojan($item, $user);
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= URLSchemes::buildShadowsocks($item, $user);
+            }
+            if ($item['type'] === 'v2ray') {
+                $uri .= URLSchemes::buildVmess($item, $user);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= URLSchemes::buildTrojan($item, $user);
+            }
         }
         return base64_encode($uri);
     }
 
-    private function surge($user, $shadowsocks = [], $vmess = [], $trojan = [])
+    private function shaodowsocksSIP008($user, $servers = [])
     {
-        $proxies = '';
-        $proxyGroup = '';
+        $configs = [];
+        $subs = [];
+        $subs['servers'] = [];
 
-        foreach ($shadowsocks as $item) {
-            // [Proxy]
-            $proxies .= Surge::buildShadowsocks($user->uuid, $item);
-            // [Proxy Group]
-            $proxyGroup .= $item->name . ', ';
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                array_push($configs, URLSchemes::buildShadowsocksSIP008($item, $user));
+            }
         }
 
-        foreach ($vmess as $item) {
-            // [Proxy]
-            $proxies .= Surge::buildVmess($user->uuid, $item);
-            // [Proxy Group]
-            $proxyGroup .= $item->name . ', ';
-        }
+        $subs['version'] = 1;
+        $subs['remark'] = config('v2board.app_name', 'V2Board');
+        $subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs);
 
-        foreach ($trojan as $item) {
-            // [Proxy]
-            $proxies .= Surge::buildTrojan($user->uuid, $item);
-            // [Proxy Group]
-            $proxyGroup .= $item->name . ', ';
+        return json_encode($subs, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
+    }
+
+    private function surge($user, $servers = [])
+    {
+        $proxies = '';
+        $proxyGroup = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                // [Proxy]
+                $proxies .= Surge::buildShadowsocks($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+            if ($item['type'] === 'v2ray') {
+                // [Proxy]
+                $proxies .= Surge::buildVmess($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+            if ($item['type'] === 'trojan') {
+                // [Proxy]
+                $proxies .= Surge::buildTrojan($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
         }
 
         $defaultConfig = base_path() . '/resources/rules/default.surge.conf';
@@ -170,23 +200,24 @@ class ClientController extends Controller
         return $config;
     }
 
-    private function surfboard($user, $shadowsocks = [], $vmess = [])
+    private function surfboard($user, $servers = [])
     {
         $proxies = '';
         $proxyGroup = '';
 
-        foreach ($shadowsocks as $item) {
-            // [Proxy]
-            $proxies .= Surfboard::buildShadowsocks($user->uuid, $item);
-            // [Proxy Group]
-            $proxyGroup .= $item->name . ', ';
-        }
-
-        foreach ($vmess as $item) {
-            // [Proxy]
-            $proxies .= Surfboard::buildVmess($user->uuid, $item);
-            // [Proxy Group]
-            $proxyGroup .= $item->name . ', ';
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                // [Proxy]
+                $proxies .= Surfboard::buildShadowsocks($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+            if ($item['type'] === 'v2ray') {
+                // [Proxy]
+                $proxies .= Surfboard::buildVmess($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
         }
 
         $defaultConfig = base_path() . '/resources/rules/default.surfboard.conf';
@@ -206,7 +237,7 @@ class ClientController extends Controller
         return $config;
     }
 
-    private function clash($user, $shadowsocks = [], $vmess = [], $trojan = [])
+    private function clash($user, $servers = [])
     {
         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
         $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
@@ -218,19 +249,19 @@ class ClientController extends Controller
         $proxy = [];
         $proxies = [];
 
-        foreach ($shadowsocks as $item) {
-            array_push($proxy, Clash::buildShadowsocks($user->uuid, $item));
-            array_push($proxies, $item->name);
-        }
-
-        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);
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                array_push($proxy, Clash::buildShadowsocks($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+            if ($item['type'] === 'v2ray') {
+                array_push($proxy, Clash::buildVmess($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+            if ($item['type'] === 'trojan') {
+                array_push($proxy, Clash::buildTrojan($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
         }
 
         $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);

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

@@ -28,7 +28,8 @@ class CommController extends Controller
                     : 0,
                 'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
                 'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
-                'appDescription' => config('v2board.app_description')
+                'appDescription' => config('v2board.app_description'),
+                'appUrl' => config('v2board.app_url')
             ]
         ]);
     }

+ 19 - 23
app/Http/Controllers/Server/DeepbworkController.php

@@ -47,12 +47,11 @@ class DeepbworkController extends Controller
             $user->v2ray_user = [
                 "uuid" => $user->uuid,
                 "email" => sprintf("%s@v2board.user", $user->uuid),
-                "alter_id" => $user->v2ray_alter_id,
-                "level" => $user->v2ray_level,
+                "alter_id" => $server->alter_id,
+                "level" => 0,
             ];
             unset($user['uuid']);
-            unset($user['v2ray_alter_id']);
-            unset($user['v2ray_level']);
+            unset($user['email']);
             array_push($result, $user);
         }
         return response([
@@ -64,7 +63,7 @@ class DeepbworkController extends Controller
     // 后端提交数据
     public function submit(Request $request)
     {
-        // Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
+//         Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
         $server = Server::find($request->input('node_id'));
         if (!$server) {
             return response([
@@ -75,27 +74,24 @@ 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) {
-            $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'
-                ]);
+        DB::beginTransaction();
+        try {
+            foreach ($data as $item) {
+                $u = $item['u'] * $server->rate;
+                $d = $item['d'] * $server->rate;
+                if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
+                    continue;
+                }
             }
-
-            $serverService->log(
-                $item['user_id'],
-                $request->input('node_id'),
-                $item['u'],
-                $item['d'],
-                $server->rate,
-                'vmess'
-            );
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return response([
+                'ret' => 0,
+                'msg' => 'user fetch fail'
+            ]);
         }
+        DB::commit();
 
         return response([
             'ret' => 1,

+ 19 - 16
app/Http/Controllers/Server/PoseidonController.php

@@ -45,12 +45,11 @@ class PoseidonController extends Controller
             $user->v2ray_user = [
                 "uuid" => $user->uuid,
                 "email" => sprintf("%s@v2board.user", $user->uuid),
-                "alter_id" => $user->v2ray_alter_id,
-                "level" => $user->v2ray_level,
+                "alter_id" => $server->alter_id,
+                "level" => 0,
             ];
             unset($user['uuid']);
-            unset($user['v2ray_alter_id']);
-            unset($user['v2ray_level']);
+            unset($user['email']);
             array_push($result, $user);
         }
 
@@ -68,23 +67,13 @@ class PoseidonController 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) {
             $u = $item['u'] * $server->rate;
             $d = $item['d'] * $server->rate;
-            if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
+            if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
                 return $this->error("user fetch fail", 500);
             }
-
-            $serverService->log(
-                $item['user_id'],
-                $request->input('node_id'),
-                $item['u'],
-                $item['d'],
-                $server->rate,
-                'vmess'
-            );
         }
 
         return $this->success('');
@@ -146,9 +135,23 @@ class PoseidonController extends Controller
     }
 
     protected function success($data) {
+         $req = request();
+        // Only for "GET" method
+        if (!$req->isMethod('GET') || !$data) {
+            return response([
+                'msg' => 'ok',
+                'data' => $data,
+            ]);
+        }
+
+        $etag = sha1(json_encode($data));
+        if ($etag == $req->header("IF-NONE-MATCH")) {
+            return response(null, 304);
+        }
+
         return response([
             'msg' => 'ok',
             'data' => $data,
-        ]);
+        ])->header('ETAG', $etag);
     }
 }

+ 13 - 37
app/Http/Controllers/Server/ShadowsocksTidalabController.php

@@ -70,28 +70,22 @@ class ShadowsocksTidalabController extends Controller
         $data = file_get_contents('php://input');
         $data = json_decode($data, true);
         Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
-        $serverService = new ServerService();
         $userService = new UserService();
         DB::beginTransaction();
-        foreach ($data as $item) {
-            $u = $item['u'] * $server->rate;
-            $d = $item['d'] * $server->rate;
-            if (!$userService->trafficFetch((float)$u, (float)$d, (int)$item['user_id'])) {
-                DB::rollBack();
-                return response([
-                    'ret' => 0,
-                    'msg' => 'user fetch fail'
-                ]);
+        try {
+            foreach ($data as $item) {
+                $u = $item['u'] * $server->rate;
+                $d = $item['d'] * $server->rate;
+                if (!$userService->trafficFetch((float)$u, (float)$d, (int)$item['user_id'], $server, 'shadowsocks')) {
+                    continue;
+                }
             }
-
-            $serverService->log(
-                $item['user_id'],
-                $request->input('node_id'),
-                $item['u'],
-                $item['d'],
-                $server->rate,
-                'shadowsocks'
-            );
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return response([
+                'ret' => 0,
+                'msg' => 'user fetch fail'
+            ]);
         }
         DB::commit();
 
@@ -100,22 +94,4 @@ class ShadowsocksTidalabController extends Controller
             '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));
-    }
 }

+ 14 - 21
app/Http/Controllers/Server/TrojanTidalabController.php

@@ -48,8 +48,7 @@ class TrojanTidalabController extends Controller
                 "password" => $user->uuid,
             ];
             unset($user['uuid']);
-            unset($user['v2ray_alter_id']);
-            unset($user['v2ray_level']);
+            unset($user['email']);
             array_push($result, $user);
         }
         return response([
@@ -72,28 +71,22 @@ class TrojanTidalabController extends Controller
         $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();
         DB::beginTransaction();
-        foreach ($data as $item) {
-            $u = $item['u'] * $server->rate;
-            $d = $item['d'] * $server->rate;
-            if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
-                DB::rollBack();
-                return response([
-                    'ret' => 0,
-                    'msg' => 'user fetch fail'
-                ]);
+        try {
+            foreach ($data as $item) {
+                $u = $item['u'] * $server->rate;
+                $d = $item['d'] * $server->rate;
+                if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan')) {
+                    continue;
+                }
             }
-
-            $serverService->log(
-                $item['user_id'],
-                $request->input('node_id'),
-                $item['u'],
-                $item['d'],
-                $server->rate,
-                'trojan'
-            );
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return response([
+                'ret' => 0,
+                'msg' => 'user fetch fail'
+            ]);
         }
         DB::commit();
 

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

@@ -28,7 +28,6 @@ class CouponController extends Controller
         }
         if ($coupon->limit_plan_ids) {
             $limitPlanIds = json_decode($coupon->limit_plan_ids);
-            info($limitPlanIds);
             if (!in_array($request->input('plan_id'), $limitPlanIds)) {
                 abort(500, '这个计划无法使用该优惠码');
             }

+ 5 - 1
app/Http/Controllers/User/OrderController.php

@@ -78,7 +78,7 @@ class OrderController extends Controller
 
         if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
             if ($request->input('cycle') !== 'reset_price') {
-                abort(500, '该订阅已售罄');
+                abort(500, '该订阅已售罄,请更换其他订阅');
             }
         }
 
@@ -96,6 +96,10 @@ class OrderController extends Controller
             }
         }
 
+        if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) {
+            abort(500, '订阅已过期,请更换其他订阅');
+        }
+
         DB::beginTransaction();
         $order = new Order();
         $orderService = new OrderService($order);

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

@@ -23,8 +23,7 @@ class ServerController extends Controller
         $userService = new UserService();
         if ($userService->isAvailable($user)) {
             $serverService = new ServerService();
-            $servers = $serverService->getAllServers($user);
-            $servers = array_merge($servers['shadowsocks'], $servers['vmess'], $servers['trojan']);
+            $servers = $serverService->getAvailableServers($user);
         }
         return response([
             'data' => $servers

+ 0 - 1
app/Http/Kernel.php

@@ -43,7 +43,6 @@ class Kernel extends HttpKernel
             \Illuminate\Session\Middleware\StartSession::class,
             \App\Http\Middleware\ForceJson::class,
             \App\Http\Middleware\CORS::class,
-            'throttle:120,1',
             'bindings',
         ],
     ];

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

@@ -100,7 +100,14 @@ class ConfigSave extends FormRequest
             'telegram_bot_enable' => 'in:0,1',
             'telegram_bot_token' => '',
             'telegram_discuss_id' => '',
-            'telegram_channel_id' => ''
+            'telegram_channel_id' => '',
+            // app
+            'windows_version' => '',
+            'windows_download_url' => '',
+            'macos_version' => '',
+            'macos_download_url' => '',
+            'android_version' => '',
+            'android_download_url' => ''
         ];
     }
 

+ 1 - 1
app/Http/Requests/Admin/OrderAssign.php

@@ -17,7 +17,7 @@ class OrderAssign extends FormRequest
             'plan_id' => 'required',
             'email' => 'required',
             'total_amount' => 'required',
-            'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price,reset_price'
+            'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price'
         ];
     }
 

+ 1 - 1
app/Http/Requests/Admin/OrderUpdate.php

@@ -15,7 +15,7 @@ class OrderUpdate extends FormRequest
     {
         return [
             'status' => 'in:0,1,2,3',
-            'commission_status' => 'in:0,1,2'
+            'commission_status' => 'in:0,1,3'
         ];
     }
 

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

@@ -1,28 +0,0 @@
-<?php
-
-namespace App\Http\Requests\Admin;
-
-use Illuminate\Foundation\Http\FormRequest;
-
-class ServerShadowsocksSort 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格式有误'
-        ];
-    }
-}

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

@@ -1,28 +0,0 @@
-<?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格式有误'
-        ];
-    }
-}

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

@@ -24,6 +24,7 @@ class ServerV2raySave extends FormRequest
             'tls' => 'required',
             'tags' => 'nullable|array',
             'rate' => 'required|numeric',
+            'alter_id' => 'required|integer',
             'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
             'networkSettings' => '',
             'ruleSettings' => '',

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

@@ -1,28 +0,0 @@
-<?php
-
-namespace App\Http\Requests\Admin;
-
-use Illuminate\Foundation\Http\FormRequest;
-
-class ServerV2raySort 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格式有误'
-        ];
-    }
-}

+ 2 - 0
app/Http/Routes/AdminRoute.php

@@ -26,6 +26,8 @@ class AdminRoute
             $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->get ('/server/manage/getNodes', 'Admin\\Server\\ManageController@getNodes');
+            $router->post('/server/manage/sort', 'Admin\\Server\\ManageController@sort');
             $router->group([
                 'prefix' => 'server/trojan'
             ], function ($router) {

+ 1 - 1
app/Http/Routes/GuestRoute.php

@@ -17,7 +17,7 @@ class GuestRoute
             $router->post('/order/stripeNotify', 'Guest\\OrderController@stripeNotify');
             $router->post('/order/bitpayXNotify', 'Guest\\OrderController@bitpayXNotify');
             $router->post('/order/mgateNotify', 'Guest\\OrderController@mgateNotify');
-            $router->post('/order/epayNotify', 'Guset\\OrderController@epayNotify');
+            $router->post('/order/epayNotify', 'Guest\\OrderController@epayNotify');
             // Telegram
             $router->post('/telegram/webhook', 'Guest\\TelegramController@webhook');
         });

+ 3 - 2
app/Services/MailService.php

@@ -29,8 +29,9 @@ class MailService
 
     private function remindTrafficIsWarnValue($ud, $transfer_enable)
     {
-        if ($ud <= 0) return false;
-        $percentage = $ud / $transfer_enable * 100;
+        if (!$ud) return false;
+        if (!$transfer_enable) return false;
+        $percentage = ($ud / $transfer_enable) * 100;
         if ($percentage < 80) return false;
         if ($percentage >= 100) return false;
         return true;

+ 8 - 1
app/Services/OrderService.php

@@ -127,7 +127,7 @@ class OrderService
         if ($user->invite_user_id && $order->total_amount > 0) {
             $order->invite_user_id = $user->invite_user_id;
             $commissionFirstTime = (int)config('v2board.commission_first_time_enable', 1);
-            if (!$commissionFirstTime || ($commissionFirstTime && !Order::where('user_id', $user->id)->where('status', 3)->first())) {
+            if (!$commissionFirstTime || ($commissionFirstTime && !$this->haveValidOrder($user))) {
                 $inviter = User::find($user->invite_user_id);
                 if ($inviter && $inviter->commission_rate) {
                     $order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
@@ -138,6 +138,13 @@ class OrderService
         }
     }
 
+    private function haveValidOrder(User $user)
+    {
+        return Order::where('user_id', $user->id)
+            ->whereIn('status', [3, 4])
+            ->first();
+    }
+
     private function getSurplusValue(User $user, Order $order)
     {
         if ($user->expired_at === NULL) {

+ 114 - 47
app/Services/ServerService.php

@@ -15,91 +15,93 @@ use Illuminate\Support\Facades\Cache;
 class ServerService
 {
 
-    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 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":"127.0.0.1","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
+    public function getV2ray(User $user, $all = false):array
     {
-        $vmess = [];
+        $servers = [];
         $model = Server::orderBy('sort', 'ASC');
         if (!$all) {
             $model->where('show', 1);
         }
-        $vmesss = $model->get();
-        foreach ($vmesss as $k => $v) {
-            $vmesss[$k]['protocol_type'] = 'vmess';
-            $groupId = json_decode($vmesss[$k]['group_id']);
+        $v2ray = $model->get();
+        for ($i = 0; $i < count($v2ray); $i++) {
+            $v2ray[$i]['type'] = 'v2ray';
+            $groupId = json_decode($v2ray[$i]['group_id']);
             if (in_array($user->group_id, $groupId)) {
-                $vmesss[$k]['link'] = URLSchemes::buildVmess($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']));
+                $v2ray[$i]['link'] = URLSchemes::buildVmess($v2ray[$i], $user);
+                if ($v2ray[$i]['parent_id']) {
+                    $v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id']));
                 } else {
-                    $vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['id']));
+                    $v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['id']));
                 }
-                array_push($vmess, $vmesss[$k]);
+                array_push($servers, $v2ray[$i]->toArray());
             }
         }
 
 
-        return $vmess;
+        return $servers;
     }
 
-    public function getTrojan(User $user, $all = false)
+    public function getTrojan(User $user, $all = false):array
     {
-        $trojan = [];
+        $servers = [];
         $model = ServerTrojan::orderBy('sort', 'ASC');
         if (!$all) {
             $model->where('show', 1);
         }
-        $trojans = $model->get();
-        foreach ($trojans as $k => $v) {
-            $trojans[$k]['protocol_type'] = 'trojan';
-            $groupId = json_decode($trojans[$k]['group_id']);
-            $trojans[$k]['link'] = URLSchemes::buildTrojan($trojans[$k], $user);
+        $trojan = $model->get();
+        for ($i = 0; $i < count($trojan); $i++) {
+            $trojan[$i]['type'] = 'trojan';
+            $groupId = json_decode($trojan[$i]['group_id']);
+            $trojan[$i]['link'] = URLSchemes::buildTrojan($trojan[$i], $user);
             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']));
+                if ($trojan[$i]['parent_id']) {
+                    $trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['parent_id']));
                 } else {
-                    $trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['id']));
+                    $trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['id']));
                 }
-                array_push($trojan, $trojans[$k]);
+                array_push($servers, $trojan[$i]->toArray());
             }
-
         }
-        return $trojan;
+        return $servers;
     }
 
     public function getShadowsocks(User $user, $all = false)
     {
-        $shadowsocks = [];
+        $servers = [];
         $model = ServerShadowsocks::orderBy('sort', 'ASC');
         if (!$all) {
             $model->where('show', 1);
         }
-        $shadowsockss = $model->get();
-        foreach ($shadowsockss as $k => $v) {
-            $shadowsockss[$k]['protocol_type'] = 'shadowsocks';
-            $groupId = json_decode($shadowsockss[$k]['group_id']);
-            $shadowsockss[$k]['link'] = URLSchemes::buildShadowsocks($shadowsockss[$k], $user);
+        $shadowsocks = $model->get();
+        for ($i = 0; $i < count($shadowsocks); $i++) {
+            $shadowsocks[$i]['type'] = 'shadowsocks';
+            $groupId = json_decode($shadowsocks[$i]['group_id']);
+            $shadowsocks[$i]['link'] = URLSchemes::buildShadowsocks($shadowsocks[$i], $user);
             if (in_array($user->group_id, $groupId)) {
-                if ($shadowsockss[$k]['parent_id']) {
-                    $shadowsockss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsockss[$k]['parent_id']));
+                if ($shadowsocks[$i]['parent_id']) {
+                    $shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['parent_id']));
                 } else {
-                    $shadowsockss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsockss[$k]['id']));
+                    $shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['id']));
                 }
-                array_push($shadowsocks, $shadowsockss[$k]);
+                array_push($servers, $shadowsocks[$i]->toArray());
             }
 
         }
-        return $shadowsocks;
+        return $servers;
     }
 
-    public function getAllServers(User $user, $all = false)
+    public function getAvailableServers(User $user, $all = false)
     {
-        return [
-            'shadowsocks' => $this->getShadowsocks($user, $all),
-            'vmess' => $this->getVmess($user, $all),
-            'trojan' => $this->getTrojan($user, $all)
-        ];
+        $servers = array_merge(
+            $this->getShadowsocks($user, $all),
+            $this->getV2ray($user, $all),
+            $this->getTrojan($user, $all)
+        );
+        $tmp = array_column($servers, 'sort');
+        array_multisort($tmp, SORT_ASC, $servers);
+        return $servers;
     }
 
 
@@ -119,9 +121,7 @@ class ServerService
                 'u',
                 'd',
                 'transfer_enable',
-                'uuid',
-                'v2ray_alter_id',
-                'v2ray_level'
+                'uuid'
             ])
             ->get();
     }
@@ -133,7 +133,7 @@ class ServerService
             abort(500, '节点不存在');
         }
         $json = json_decode(self::V2RAY_CONFIG);
-        $json->log->loglevel = config('v2board.server_log_level', 'none');
+        $json->log->loglevel = (int)config('v2board.server_log_enable') ? 'debug' : 'none';
         $json->inboundDetour[0]->port = (int)$localPort;
         $json->inbound->port = (int)$server->server_port;
         $json->inbound->streamSettings->network = $server->network;
@@ -270,6 +270,7 @@ class ServerService
             ->where('user_id', $userId)
             ->where('rate', $rate)
             ->where('method', $method)
+            ->lockForUpdate()
             ->first();
         if ($serverLog) {
             $serverLog->u = $serverLog->u + $u;
@@ -287,4 +288,70 @@ class ServerService
             $serverLog->save();
         }
     }
+
+    public function getShadowsocksServers()
+    {
+        $server = ServerShadowsocks::orderBy('sort', 'ASC')->get();
+        for ($i = 0; $i < count($server); $i++) {
+            $server[$i]['type'] = 'shadowsocks';
+            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_SHADOWSOCKS_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(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server[$i]['parent_id']));
+            } else {
+                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server[$i]['id']));
+            }
+        }
+        return $server->toArray();
+    }
+
+    public function getV2rayServers()
+    {
+        $server = Server::orderBy('sort', 'ASC')->get();
+        for ($i = 0; $i < count($server); $i++) {
+            $server[$i]['type'] = 'v2ray';
+            if (!empty($server[$i]['tags'])) {
+                $server[$i]['tags'] = json_decode($server[$i]['tags']);
+            }
+            if (!empty($server[$i]['dnsSettings'])) {
+                $server[$i]['dnsSettings'] = json_decode($server[$i]['dnsSettings']);
+            }
+            if (!empty($server[$i]['tlsSettings'])) {
+                $server[$i]['tlsSettings'] = json_decode($server[$i]['tlsSettings']);
+            }
+            if (!empty($server[$i]['ruleSettings'])) {
+                $server[$i]['ruleSettings'] = json_decode($server[$i]['ruleSettings']);
+            }
+            $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(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['parent_id']));
+            } else {
+                $server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server[$i]['id']));
+            }
+        }
+        return $server->toArray();
+    }
+
+    public function getTrojanServers()
+    {
+        $server = ServerTrojan::orderBy('sort', 'ASC')->get();
+        for ($i = 0; $i < count($server); $i++) {
+            $server[$i]['type'] = 'trojan';
+            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]['parent_id'] ? $server[$i]['parent_id'] : $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 $server->toArray();
+    }
 }

+ 17 - 3
app/Services/UserService.php

@@ -3,6 +3,7 @@
 namespace App\Services;
 
 use App\Models\Order;
+use App\Models\Server;
 use App\Models\User;
 
 class UserService
@@ -76,9 +77,10 @@ class UserService
         return true;
     }
 
-    public function trafficFetch(int $u, int $d, int $userId):bool
+    public function trafficFetch(int $u, int $d, int $userId, object $server, string $protocol):bool
     {
-        $user = User::find($userId);
+        $user = User::lockForUpdate()
+            ->find($userId);
         if (!$user) {
             return true;
         }
@@ -89,7 +91,19 @@ class UserService
             return false;
         }
         $mailService = new MailService();
-        $mailService->remindTraffic($user);
+        $serverService = new ServerService();
+        try {
+            $mailService->remindTraffic($user);
+            $serverService->log(
+                $userId,
+                $server->id,
+                $u,
+                $d,
+                $server->rate,
+                $protocol
+            );
+        } catch (\Exception $e) {
+        }
         return true;
     }
 }

+ 1 - 1
app/Utils/CacheKey.php

@@ -5,7 +5,7 @@ namespace App\Utils;
 class CacheKey
 {
     CONST KEYS = [
-        'EMAIL_VERIFY_CODE' => '邮箱验证',
+        'EMAIL_VERIFY_CODE' => '邮箱验证',
         'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间',
         'SERVER_V2RAY_ONLINE_USER' => '节点在线用户',
         'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间',

+ 31 - 25
app/Utils/Clash.php

@@ -8,11 +8,11 @@ class Clash
     public static function buildShadowsocks($uuid, $server)
     {
         $array = [];
-        $array['name'] = $server->name;
+        $array['name'] = $server['name'];
         $array['type'] = 'ss';
-        $array['server'] = $server->host;
-        $array['port'] = $server->port;
-        $array['cipher'] = $server->cipher;
+        $array['server'] = $server['host'];
+        $array['port'] = $server['port'];
+        $array['cipher'] = $server['cipher'];
         $array['password'] = $uuid;
         $array['udp'] = true;
         return $array;
@@ -21,44 +21,50 @@ class Clash
     public static function buildVmess($uuid, $server)
     {
         $array = [];
-        $array['name'] = $server->name;
+        $array['name'] = $server['name'];
         $array['type'] = 'vmess';
-        $array['server'] = $server->host;
-        $array['port'] = $server->port;
+        $array['server'] = $server['host'];
+        $array['port'] = $server['port'];
         $array['uuid'] = $uuid;
-        $array['alterId'] = 2;
+        $array['alterId'] = $server['alter_id'];
         $array['cipher'] = 'auto';
         $array['udp'] = true;
-        if ($server->tls) {
-            $tlsSettings = json_decode($server->tlsSettings);
+
+        if ($server['tls']) {
             $array['tls'] = true;
-            if (!empty($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false );
-            if (!empty($tlsSettings->serverName)) $array['servername'] = $tlsSettings->serverName;
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
+                    $array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    $array['servername'] = $tlsSettings['serverName'];
+            }
         }
-        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
-                ];
+        if ($server['network'] === 'ws') {
+            $array['network'] = 'ws';
+            if ($server['networkSettings']) {
+                $wsSettings = json_decode($server['networkSettings'], true);
+                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
+                    $array['ws-path'] = $wsSettings['path'];
+                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
+                    $array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
             }
         }
+
         return $array;
     }
 
     public static function buildTrojan($password, $server)
     {
         $array = [];
-        $array['name'] = $server->name;
+        $array['name'] = $server['name'];
         $array['type'] = 'trojan';
-        $array['server'] = $server->host;
-        $array['port'] = $server->port;
+        $array['server'] = $server['host'];
+        $array['port'] = $server['port'];
         $array['password'] = $password;
         $array['udp'] = true;
-        if (!empty($server->server_name)) $array['sni'] = $server->server_name;
-        if (!empty($server->allow_insecure)) $array['skip-cert-verify'] = ($server->allow_insecure ? true : false );
+        if (!empty($server['server_name'])) $array['sni'] = $server['server_name'];
+        if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false);
         return $array;
     }
 }

+ 31 - 32
app/Utils/QuantumultX.php

@@ -8,12 +8,12 @@ class QuantumultX
     public static function buildShadowsocks($password, $server)
     {
         $config = [
-            "shadowsocks={$server->host}:{$server->port}",
-            "method={$server->cipher}",
+            "shadowsocks={$server['host']}:{$server['port']}",
+            "method={$server['cipher']}",
             "password={$password}",
             'fast-open=true',
             'udp-relay=true',
-            "tag={$server->name}"
+            "tag={$server['name']}"
         ];
         $config = array_filter($config);
         $uri = implode(',', $config);
@@ -24,41 +24,40 @@ class QuantumultX
     public static function buildVmess($uuid, $server)
     {
         $config = [
-            "vmess={$server->host}:{$server->port}",
+            "vmess={$server['host']}:{$server['port']}",
             'method=chacha20-poly1305',
             "password={$uuid}",
             'fast-open=true',
             'udp-relay=true',
-            "tag={$server->name}"
+            "tag={$server['name']}"
         ];
-        if ($server->network === 'tcp') {
-            if ($server->tls) {
-                $tlsSettings = json_decode($server->tlsSettings);
+
+        if ($server['tls']) {
+            if ($server['network'] === 'tcp') {
                 array_push($config, 'obfs=over-tls');
-                if (isset($tlsSettings->allowInsecure)) {
-                    // Tips: allowInsecure=false = tls-verification=true
-                    array_push($config, $tlsSettings->allowInsecure ? 'tls-verification=false' : 'tls-verification=true');
-                }
-                if (!empty($tlsSettings->serverName)) {
-                    array_push($config, "obfs-host={$tlsSettings->serverName}");
-                }
+            } else {
+                array_push($config, 'obfs=wss');
             }
+        } else if ($server['network'] === 'ws') {
+            array_push($config, 'obfs=ws');
         }
 
-        if ($server->network === 'ws') {
-            if ($server->tls) {
-                $tlsSettings = json_decode($server->tlsSettings);
-                array_push($config, 'obfs=wss');
-                if (isset($tlsSettings->allowInsecure)) {
-                    array_push($config, $tlsSettings->allowInsecure ? 'tls-verification=false' : 'tls-verification=true');
-                }
-            } else {
-                array_push($config, 'obfs=ws');
+        if ($server['tls']) {
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
+                    array_push($config, 'tls-verification=' . ($tlsSettings['allowInsecure'] ? 'false' : 'true'));
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    array_push($config, "tls-host={$tlsSettings['serverName']}");
             }
-            if ($server->networkSettings) {
-                $wsSettings = json_decode($server->networkSettings);
-                if (isset($wsSettings->path)) array_push($config, "obfs-uri={$wsSettings->path}");
-                if (isset($wsSettings->headers->Host)) array_push($config, "obfs-host={$wsSettings->headers->Host}");
+        }
+        if ($server['network'] === 'ws') {
+            if ($server['networkSettings']) {
+                $wsSettings = json_decode($server['networkSettings'], true);
+                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
+                    array_push($config, "obfs-uri={$wsSettings['path']}");
+                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
+                    array_push($config, "obfs-host={$wsSettings['headers']['Host']}");
             }
         }
 
@@ -70,15 +69,15 @@ class QuantumultX
     public static function buildTrojan($password, $server)
     {
         $config = [
-            "trojan={$server->host}:{$server->port}",
+            "trojan={$server['host']}:{$server['port']}",
             "password={$password}",
             'over-tls=true',
-            $server->server_name ? "tls-host={$server->server_name}" : "",
+            $server['server_name'] ? "tls-host={$server['server_name']}" : "",
             // Tips: allowInsecure=false = tls-verification=true
-            $server->allow_insecure ? 'tls-verification=false' : 'tls-verification=true',
+            $server['allow_insecure'] ? 'tls-verification=false' : 'tls-verification=true',
             'fast-open=true',
             'udp-relay=true',
-            "tag={$server->name}"
+            "tag={$server['name']}"
         ];
         $config = array_filter($config);
         $uri = implode(',', $config);

+ 29 - 19
app/Utils/Shadowrocket.php

@@ -7,47 +7,57 @@ class Shadowrocket
 {
     public static function buildShadowsocks($password, $server)
     {
-        $name = rawurlencode($server->name);
+        $name = rawurlencode($server['name']);
         $str = str_replace(
             ['+', '/', '='],
             ['-', '_', ''],
-            base64_encode("{$server->cipher}:{$password}")
+            base64_encode("{$server['cipher']}:{$password}")
         );
-        return "ss://{$str}@{$server->host}:{$server->port}#{$name}\r\n";
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
     }
 
     public static function buildVmess($uuid, $server)
     {
-        $userinfo = base64_encode('auto:' . $uuid . '@' . $server->host . ':' . $server->port);
+        $userinfo = base64_encode('auto:' . $uuid . '@' . $server['host'] . ':' . $server['port']);
         $config = [
-            'remark' => $server->name
+            'tfo' => 1,
+            'remark' => $server['name'],
+            'alterId' => $server['alter_id']
         ];
-        if ($server->tls) {
-            $tlsSettings = json_decode($server->tlsSettings);
+        if ($server['tls']) {
             $config['tls'] = 1;
-            if (isset($tlsSettings->serverName)) $config['peer'] = $tlsSettings->serverName;
-            if (isset($tlsSettings->allowInsecure)) $config['allowInsecure'] = 1;
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
+                    $config['allowInsecure'] = (int)$tlsSettings['allowInsecure'];
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    $config['peer'] = $tlsSettings['serverName'];
+            }
         }
-        if ($server->network === 'ws') {
-            $wsSettings = json_decode($server->networkSettings);
+        if ($server['network'] === 'ws') {
             $config['obfs'] = "websocket";
-            if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
-            if (isset($wsSettings->headers->Host)) $config['obfsParam'] = $wsSettings->headers->Host;
+            if ($server['networkSettings']) {
+                $wsSettings = json_decode($server['networkSettings'], true);
+                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
+                    $config['path'] = $wsSettings['path'];
+                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
+                    $config['obfsParam'] = $wsSettings['headers']['Host'];
+            }
         }
-        $query = http_build_query($config, null, '&', PHP_QUERY_RFC3986);
-        $uri = "vmess://{$userinfo}?{$query}&tfo=1";
+        $query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
+        $uri = "vmess://{$userinfo}?{$query}";
         $uri .= "\r\n";
         return $uri;
     }
 
     public static function buildTrojan($password, $server)
     {
-        $name = rawurlencode($server->name);
+        $name = rawurlencode($server['name']);
         $query = http_build_query([
-            'allowInsecure' => $server->allow_insecure,
-            'peer' => $server->server_name
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name']
         ]);
-        $uri = "trojan://{$password}@{$server->host}:{$server->port}?{$query}&tfo=1#{$name}";
+        $uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}&tfo=1#{$name}";
         $uri .= "\r\n";
         return $uri;
     }

+ 23 - 27
app/Utils/Surfboard.php

@@ -8,10 +8,10 @@ class Surfboard
     public static function buildShadowsocks($password, $server)
     {
         $config = [
-            "{$server->name}=custom",
-            "{$server->host}",
-            "{$server->port}",
-            "{$server->cipher}",
+            "{$server['name']}=custom",
+            "{$server['host']}",
+            "{$server['port']}",
+            "{$server['cipher']}",
             "{$password}",
             'https://raw.githubusercontent.com/Hackl0us/proxy-tool-backup/master/SSEncrypt.module',
             'tfo=true',
@@ -26,36 +26,32 @@ class Surfboard
     public static function buildVmess($uuid, $server)
     {
         $config = [
-            "{$server->name}=vmess",
-            "{$server->host}",
-            "{$server->port}",
+            "{$server['name']}=vmess",
+            "{$server['host']}",
+            "{$server['port']}",
             "username={$uuid}",
             'tfo=true',
             'udp-relay=true'
         ];
-        if ($server->network === 'tcp') {
-            if ($server->tls) {
-                $tlsSettings = json_decode($server->tlsSettings);
-                array_push($config, $server->tls ? 'tls=true' : 'tls=false');
-                if (!empty($tlsSettings->allowInsecure)) {
-                    array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
-                }
+
+        if ($server['tls']) {
+            array_push($config, 'tls=true');
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
+                    array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    array_push($config, "sni={$tlsSettings['serverName']}");
             }
         }
-
-        if ($server->network === 'ws') {
+        if ($server['network'] === 'ws') {
             array_push($config, 'ws=true');
-            if ($server->tls) {
-                $tlsSettings = json_decode($server->tlsSettings);
-                array_push($config, $server->tls ? 'tls=true' : 'tls=false');
-                if (!empty($tlsSettings->allowInsecure)) {
-                    array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
-                }
-            }
-            if ($server->networkSettings) {
-                $wsSettings = json_decode($server->networkSettings);
-                if (isset($wsSettings->path)) array_push($config, "ws-path={$wsSettings->path}");
-                if (isset($wsSettings->headers->Host)) array_push($config, "ws-headers=host:{$wsSettings->headers->Host}");
+            if ($server['networkSettings']) {
+                $wsSettings = json_decode($server['networkSettings'], true);
+                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
+                    array_push($config, "ws-path={$wsSettings['path']}");
+                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
+                    array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
             }
         }
 

+ 29 - 36
app/Utils/Surge.php

@@ -8,10 +8,10 @@ class Surge
     public static function buildShadowsocks($password, $server)
     {
         $config = [
-            "{$server->name}=ss",
-            "{$server->host}",
-            "{$server->port}",
-            "encrypt-method={$server->cipher}",
+            "{$server['name']}=ss",
+            "{$server['host']}",
+            "{$server['port']}",
+            "encrypt-method={$server['cipher']}",
             "password={$password}",
             'tfo=true',
             'udp-relay=true'
@@ -25,39 +25,32 @@ class Surge
     public static function buildVmess($uuid, $server)
     {
         $config = [
-            "{$server->name}=vmess",
-            "{$server->host}",
-            "{$server->port}",
+            "{$server['name']}=vmess",
+            "{$server['host']}",
+            "{$server['port']}",
             "username={$uuid}",
             'tfo=true',
             'udp-relay=true'
         ];
-        if ($server->network === 'tcp') {
-            if ($server->tls) {
-                $tlsSettings = json_decode($server->tlsSettings);
-                array_push($config, $server->tls ? 'tls=true' : 'tls=false');
-                if (!empty($tlsSettings->allowInsecure)) {
-                    array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
-                }
-                if (!empty($tlsSettings->serverName)) {
-                    array_push($config, "sni={$tlsSettings->serverName}");
-                }
+
+        if ($server['tls']) {
+            array_push($config, 'tls=true');
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
+                    array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    array_push($config, "sni={$tlsSettings['serverName']}");
             }
         }
-
-        if ($server->network === 'ws') {
+        if ($server['network'] === 'ws') {
             array_push($config, 'ws=true');
-            if ($server->tls) {
-                $tlsSettings = json_decode($server->tlsSettings);
-                array_push($config, $server->tls ? 'tls=true' : 'tls=false');
-                if (!empty($tlsSettings->allowInsecure)) {
-                    array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
-                }
-            }
-            if ($server->networkSettings) {
-                $wsSettings = json_decode($server->networkSettings);
-                if (isset($wsSettings->path)) array_push($config, "ws-path={$wsSettings->path}");
-                if (isset($wsSettings->headers->Host)) array_push($config, "ws-headers=host:{$wsSettings->headers->Host}");
+            if ($server['networkSettings']) {
+                $wsSettings = json_decode($server['networkSettings'], true);
+                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
+                    array_push($config, "ws-path={$wsSettings['path']}");
+                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
+                    array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
             }
         }
 
@@ -69,16 +62,16 @@ class Surge
     public static function buildTrojan($password, $server)
     {
         $config = [
-            "{$server->name}=trojan",
-            "{$server->host}",
-            "{$server->port}",
+            "{$server['name']}=trojan",
+            "{$server['host']}",
+            "{$server['port']}",
             "password={$password}",
-            $server->server_name ? "sni={$server->server_name}" : "",
+            $server['server_name'] ? "sni={$server['server_name']}" : "",
             'tfo=true',
             'udp-relay=true'
         ];
-        if (!empty($server->allow_insecure)) {
-            array_push($config, $server->allow_insecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
+        if (!empty($server['allow_insecure'])) {
+            array_push($config, $server['allow_insecure'] ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
         }
         $config = array_filter($config);
         $uri = implode(',', $config);

+ 34 - 24
app/Utils/URLSchemes.php

@@ -2,56 +2,66 @@
 namespace App\Utils;
 
 use App\Models\Server;
-use App\Models\ServerShadowsocks;
-use App\Models\ServerTrojan;
 use App\Models\User;
 
 class URLSchemes
 {
-    public static function buildShadowsocks(ServerShadowsocks $server, User $user)
+    public static function buildShadowsocks($server, User $user)
     {
-        $name = rawurlencode($server->name);
+        $name = rawurlencode($server['name']);
         $str = str_replace(
             ['+', '/', '='],
             ['-', '_', ''],
-            base64_encode("{$server->cipher}:{$user->uuid}")
+            base64_encode("{$server['cipher']}:{$user['uuid']}")
         );
-        return "ss://{$str}@{$server->host}:{$server->port}#{$name}\r\n";
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
     }
 
+    public static function buildShadowsocksSIP008($server, User $user)
+    {
+        $config = [
+            "id" => $server['id'],
+            "remark" => $server['name'],
+            "server" => $server['host'],
+            "server_port" => $server['port'],
+            "password" => $user['uuid'],
+            "method" => $server['cipher']
+        ];
+        return $config;
+    }
 
-    public static function buildVmess(Server $server, User $user)
+    public static function buildVmess($server, User $user)
     {
         $config = [
             "v" => "2",
-            "ps" => $server->name,
-            "add" => $server->host,
-            "port" => $server->port,
-            "id" => $user->uuid,
-            "aid" => "2",
-            "net" => $server->network,
+            "ps" => $server['name'],
+            "add" => $server['host'],
+            "port" => $server['port'],
+            "id" => $user['uuid'],
+            "aid" => $server['alter_id'],
+            "net" => $server['network'],
             "type" => "none",
             "host" => "",
             "path" => "",
-            "tls" => $server->tls ? "tls" : ""
+            "tls" => $server['tls'] ? "tls" : ""
         ];
-        if ((string)$server->network === 'ws') {
-            $wsSettings = json_decode($server->networkSettings);
-            if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
-            if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
+        if ((string)$server['network'] === 'ws') {
+            $wsSettings = json_decode($server['networkSettings'], true);
+            if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
+            if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
         }
         return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
     }
 
-    public static function buildTrojan(ServerTrojan $server, User $user)
+    public static function buildTrojan($server, User $user)
     {
-        $name = rawurlencode($server->name);
+        $name = rawurlencode($server['name']);
         $query = http_build_query([
-            'allowInsecure' => $server->allow_insecure,
-            'peer' => $server->server_name,
-            'sni' => $server->server_name
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name'],
+            'sni' => $server['server_name']
         ]);
-        $uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$name}";
+        $uri = "trojan://{$user['uuid']}@{$server['host']}:{$server['port']}?{$query}#{$name}";
         $uri .= "\r\n";
         return $uri;
     }

+ 1 - 1
config/app.php

@@ -236,5 +236,5 @@ return [
     | The only modification by laravel config
     |
     */
-    'version' => '1.4'
+    'version' => '1.4.1.1605605380'
 ];

+ 4 - 4
database/install.sql

@@ -107,7 +107,7 @@ CREATE TABLE `v2_order` (
   `balance_amount` int(11) DEFAULT NULL COMMENT '使用余额',
   `surplus_order_ids` text COMMENT '折抵订单',
   `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待支付1开通中2已取消3已完成4已折抵',
-  `commission_status` tinyint(1) NOT NULL DEFAULT '0',
+  `commission_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待确认1发放中2有效3无效',
   `commission_balance` int(11) NOT NULL DEFAULT '0',
   `created_at` int(11) NOT NULL,
   `updated_at` int(11) NOT NULL,
@@ -152,6 +152,7 @@ CREATE TABLE `v2_server` (
   `tags` varchar(255) DEFAULT NULL,
   `rate` varchar(11) NOT NULL,
   `network` text NOT NULL,
+  `alter_id` int(11) NOT NULL DEFAULT '1',
   `settings` text,
   `rules` text,
   `networkSettings` text,
@@ -217,6 +218,7 @@ DROP TABLE IF EXISTS `v2_server_stat`;
 CREATE TABLE `v2_server_stat` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `server_id` int(11) NOT NULL,
+  `method` varchar(255) NOT NULL,
   `u` varchar(255) NOT NULL,
   `d` varchar(255) NOT NULL,
   `created_at` int(11) NOT NULL,
@@ -295,8 +297,6 @@ CREATE TABLE `v2_user` (
   `last_login_at` int(11) DEFAULT NULL,
   `last_login_ip` int(11) DEFAULT 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,
   `plan_id` int(11) DEFAULT NULL,
   `remind_expire` tinyint(4) DEFAULT '1',
@@ -310,4 +310,4 @@ CREATE TABLE `v2_user` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--- 2020-11-03 18:08:25
+-- 2020-11-17 10:46:49

+ 10 - 0
database/update.sql

@@ -342,3 +342,13 @@ CREATE TABLE `v2_knowledge` (
 
 ALTER TABLE `v2_order`
 ADD `coupon_id` int(11) NULL AFTER `plan_id`;
+
+ALTER TABLE `v2_server_stat`
+ADD `method` varchar(255) NOT NULL AFTER `server_id`;
+
+ALTER TABLE `v2_server`
+ADD `alter_id` int(11) NOT NULL DEFAULT '1' AFTER `network`;
+
+ALTER TABLE `v2_user`
+DROP `v2ray_alter_id`,
+DROP `v2ray_level`;

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/admin/components.async.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/admin/components.chunk.css


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/admin/umi.css


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/admin/umi.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/admin/vendors.async.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/user/components.async.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/user/umi.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/assets/user/vendors.async.js


+ 15 - 3
resources/rules/app.clash.yaml

@@ -8,14 +8,26 @@ experimental:
   ignore-resolve-fail: true
 dns:
   enable: true
+  # listen: 0.0.0.0:53
   ipv6: false
+
+  default-nameserver:
+    - 223.5.5.5
+    - 119.29.29.29
   enhanced-mode: redir-host
+  fake-ip-range: 198.18.0.1/16
+  use-hosts: true
   nameserver:
-    - 1.2.4.8
-    - 223.5.5.5
+    - https://dns.alidns.com/dns-query
+    - https://doh.pub/dns-query
   fallback:
     - tls://1.0.0.1:853
-    - tls://dns.google:853
+    - https://dns.google/dns-query
+  fallback-filter:
+    geoip: true
+    ipcidr:
+      - 240.0.0.0/4
+
 proxies:
 
 proxy-groups:

+ 58 - 28
resources/rules/default.clash.yaml

@@ -1,21 +1,37 @@
-port: 7890
-socks-port: 7891
-allow-lan: false
+# port: 7890
+# socks-port: 7891
+# redir-port: 7892
+# tproxy-port: 7893
+mixed-port: 7890
+allow-lan: true
+bind-address: "*"
 mode: rule
 log-level: info
 external-controller: 127.0.0.1:9090
-experimental:
-  ignore-resolve-fail: true
+
 dns:
   enable: true
+  # listen: 0.0.0.0:53
   ipv6: false
+
+  default-nameserver:
+    - 223.5.5.5
+    - 119.29.29.29
   enhanced-mode: redir-host
+  fake-ip-range: 198.18.0.1/16
+  use-hosts: true
   nameserver:
-    - 1.2.4.8
-    - 223.5.5.5
+    - https://doh.pub/dns-query
   fallback:
     - tls://1.0.0.1:853
-    - tls://dns.google:853
+    - https://cloudflare-dns.com/dns-query
+    - https://dns.google/dns-query
+  fallback-filter:
+    geoip: true
+    ipcidr:
+      - 240.0.0.0/4
+      - 0.0.0.0/32
+
 proxies:
 
 proxy-groups:
@@ -24,34 +40,44 @@ proxy-groups:
   - { 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,$app_name
+  - DOMAIN,developer.apple.com,$app_name
   - DOMAIN-SUFFIX,digicert.com,$app_name
-  - DOMAIN-SUFFIX,entrust.net,$app_name
+  - DOMAIN,ocsp.apple.com,$app_name
+  - DOMAIN,ocsp.comodoca.com,$app_name
+  - DOMAIN,ocsp.usertrust.com,$app_name
+  - DOMAIN,ocsp.sectigo.com,$app_name
   - DOMAIN,ocsp.verisign.net,$app_name
-  - DOMAIN-SUFFIX,apps.apple.com,$app_name
+  - DOMAIN-SUFFIX,apple-dns.net,$app_name
+  - DOMAIN,testflight.apple.com,$app_name
+  - DOMAIN,sandbox.itunes.apple.com,$app_name
   - DOMAIN,itunes.apple.com,$app_name
+  - DOMAIN-SUFFIX,apps.apple.com,$app_name
   - DOMAIN-SUFFIX,blobstore.apple.com,$app_name
-  - DOMAIN-SUFFIX,music.apple.com,DIRECT
+  - DOMAIN,cvws.icloud-content.com,$app_name
   - 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,cdn20.com,DIRECT
   - DOMAIN-SUFFIX,cdn-apple.com,DIRECT
+  - DOMAIN-SUFFIX,akadns.net,DIRECT
+  - DOMAIN-SUFFIX,akamaiedge.net,DIRECT
+  - DOMAIN-SUFFIX,edgekey.net,DIRECT
+  - DOMAIN-SUFFIX,mwcloudcdn.com,DIRECT
+  - DOMAIN-SUFFIX,mwcname.com,DIRECT
   - DOMAIN-SUFFIX,apple.com,DIRECT
   - DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT
+  - DOMAIN-SUFFIX,apple-mapkit.com,DIRECT
   # - DOMAIN,e.crashlytics.com,REJECT //注释此选项有助于大多数App开发者分析崩溃信息;如果您拒绝一切崩溃数据统计、搜集,请取消 # 注释。
 
-
-  # 自定义规则
-  ## 您可以在此处插入您补充的自定义规则(请注意保持缩进)
-
   # 国内网站
   - DOMAIN-SUFFIX,cn,DIRECT
   - DOMAIN-KEYWORD,-cn,DIRECT
@@ -65,7 +91,6 @@ rules:
   - 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
@@ -97,7 +122,6 @@ rules:
   - 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
@@ -209,21 +233,24 @@ rules:
   - DOMAIN-KEYWORD,adsrvmedia,REJECT
   - DOMAIN-KEYWORD,adwords,REJECT
   - DOMAIN-KEYWORD,adservice,REJECT
+  - DOMAIN-SUFFIX,appsflyer.com,REJECT
   - DOMAIN-KEYWORD,domob,REJECT
+  - DOMAIN-SUFFIX,doubleclick.net,REJECT
   - DOMAIN-KEYWORD,duomeng,REJECT
   - DOMAIN-KEYWORD,dwtrack,REJECT
   - DOMAIN-KEYWORD,guanggao,REJECT
   - DOMAIN-KEYWORD,lianmeng,REJECT
   - DOMAIN-SUFFIX,mmstat.com,REJECT
+  - DOMAIN-KEYWORD,mopub,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-SUFFIX,vungle.com,REJECT
   - DOMAIN-KEYWORD,wlmonitor,REJECT
   - DOMAIN-KEYWORD,zjtoolbar,REJECT
 
@@ -232,6 +259,7 @@ rules:
   - DOMAIN-SUFFIX,abpchina.org,$app_name
   - DOMAIN-SUFFIX,adblockplus.org,$app_name
   - DOMAIN-SUFFIX,adobe.com,$app_name
+  - DOMAIN-SUFFIX,akamaized.net,$app_name
   - DOMAIN-SUFFIX,alfredapp.com,$app_name
   - DOMAIN-SUFFIX,amplitude.com,$app_name
   - DOMAIN-SUFFIX,ampproject.org,$app_name
@@ -499,18 +527,18 @@ rules:
   # Telegram
   - DOMAIN-SUFFIX,telegra.ph,$app_name
   - DOMAIN-SUFFIX,telegram.org,$app_name
-
   - IP-CIDR,91.108.4.0/22,$app_name,no-resolve
-  - IP-CIDR,91.108.8.0/22,$app_name,no-resolve
-  - IP-CIDR,91.108.12.0/22,$app_name,no-resolve
+  - IP-CIDR,91.108.8.0/21,$app_name,no-resolve
   - IP-CIDR,91.108.16.0/22,$app_name,no-resolve
   - IP-CIDR,91.108.56.0/22,$app_name,no-resolve
-  - IP-CIDR,149.154.160.0/22,$app_name,no-resolve
-  - IP-CIDR,149.154.164.0/22,$app_name,no-resolve
-  - IP-CIDR,149.154.168.0/22,$app_name,no-resolve
-  - IP-CIDR,149.154.172.0/22,$app_name,no-resolve
+  - IP-CIDR,149.154.160.0/20,$app_name,no-resolve
+  - IP-CIDR6,2001:67c:4e8::/48,$app_name,no-resolve
+  - IP-CIDR6,2001:b28:f23d::/48,$app_name,no-resolve
+  - IP-CIDR6,2001:b28:f23f::/48,$app_name,no-resolve
 
   # LAN
+  - DOMAIN,injections.adguard.org,DIRECT
+  - DOMAIN,local.adguard.org,DIRECT
   - DOMAIN-SUFFIX,local,DIRECT
   - IP-CIDR,127.0.0.0/8,DIRECT
   - IP-CIDR,172.16.0.0/12,DIRECT
@@ -518,6 +546,8 @@ rules:
   - IP-CIDR,10.0.0.0/8,DIRECT
   - IP-CIDR,17.0.0.0/8,DIRECT
   - IP-CIDR,100.64.0.0/10,DIRECT
+  - IP-CIDR,224.0.0.0/4,DIRECT
+  - IP-CIDR6,fe80::/10,DIRECT
 
   # 最终规则
   - GEOIP,CN,DIRECT

+ 55 - 36
resources/rules/default.surfboard.conf

@@ -3,9 +3,9 @@
 [General]
 loglevel = notify
 interface = 127.0.0.1
-skip-proxy = localhost, *.local, 0.0.0.0/8, 10.0.0.0/8, 17.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32
-ipv6 = false
-dns-server = 1.2.4.8, 114.114.114.114, 223.5.5.5, 8.8.8.8, system
+skip-proxy = localhost, *.local, injections.adguard.org, local.adguard.org, 0.0.0.0/8, 10.0.0.0/8, 17.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32
+ipv6 = true
+dns-server = 1.2.4.8, 114.114.114.114, 223.5.5.5, 119.29.29.29
 exclude-simple-hostnames = true
 enhanced-mode-by-rule = true
 
@@ -39,24 +39,38 @@ DOMAIN-SUFFIX,appspot.com,Proxy,force-remote-dns
 DOMAIN-KEYWORD,google,Proxy,force-remote-dns
 
 # Apple
-DOMAIN,ocsp.apple.com,Proxy
+DOMAIN,safebrowsing.urlsec.qq.com,DIRECT
+DOMAIN,safebrowsing.googleapis.com,DIRECT
+DOMAIN,developer.apple.com,Proxy
 DOMAIN-SUFFIX,digicert.com,Proxy
-DOMAIN-SUFFIX,entrust.net,Proxy
+DOMAIN,ocsp.apple.com,Proxy
+DOMAIN,ocsp.comodoca.com,Proxy
+DOMAIN,ocsp.usertrust.com,Proxy
+DOMAIN,ocsp.sectigo.com,Proxy
 DOMAIN,ocsp.verisign.net,Proxy
-DOMAIN,itunes.apple.com,Proxy,force-remote-dns
-DOMAIN-SUFFIX,apps.apple.com,Proxy,force-remote-dns
+DOMAIN-SUFFIX,apple-dns.net,Proxy
+DOMAIN,testflight.apple.com,Proxy
+DOMAIN,sandbox.itunes.apple.com,Proxy
+DOMAIN,itunes.apple.com,Proxy
+DOMAIN-SUFFIX,apps.apple.com,Proxy
 DOMAIN-SUFFIX,blobstore.apple.com,Proxy
-DOMAIN-SUFFIX,music.apple.com,DIRECT,force-remote-dns
+DOMAIN,cvws.icloud-content.com,Proxy
 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,akadns.net,DIRECT
 DOMAIN-SUFFIX,aaplimg.com,DIRECT
+DOMAIN-SUFFIX,cdn20.com,DIRECT
 DOMAIN-SUFFIX,cdn-apple.com,DIRECT
+DOMAIN-SUFFIX,akadns.net,DIRECT
+DOMAIN-SUFFIX,akamaiedge.net,DIRECT
+DOMAIN-SUFFIX,edgekey.net,DIRECT
+DOMAIN-SUFFIX,mwcloudcdn.com,DIRECT
+DOMAIN-SUFFIX,mwcname.com,DIRECT
 DOMAIN-SUFFIX,apple.com,DIRECT
 DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT
+DOMAIN-SUFFIX,apple-mapkit.com,DIRECT
 
 # 国内网站
 DOMAIN-SUFFIX,cn,DIRECT
@@ -71,7 +85,6 @@ 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
@@ -190,22 +203,7 @@ DOMAIN-SUFFIX,zhimg.com,DIRECT
 DOMAIN-SUFFIX,zimuzu.tv,DIRECT
 DOMAIN-SUFFIX,zoho.com,DIRECT
 
-# 抗 DNS 污染 
-DOMAIN-KEYWORD,amazon,Proxy
-DOMAIN-KEYWORD,gmail,Proxy
-DOMAIN-KEYWORD,youtube,Proxy
-DOMAIN-KEYWORD,facebook,Proxy
-DOMAIN-SUFFIX,fb.me,Proxy
-DOMAIN-SUFFIX,fbcdn.net,Proxy
-DOMAIN-KEYWORD,twitter,Proxy
-DOMAIN-KEYWORD,instagram,Proxy
-DOMAIN-KEYWORD,dropbox,Proxy
-DOMAIN-SUFFIX,twimg.com,Proxy
-DOMAIN-KEYWORD,blogspot,Proxy
-DOMAIN-SUFFIX,youtu.be,Proxy
-DOMAIN-KEYWORD,whatsapp,Proxy
-
-# 常见广告域名关键词屏蔽
+# 常见广告域名屏蔽
 DOMAIN-KEYWORD,admarvel,REJECT
 DOMAIN-KEYWORD,admaster,REJECT
 DOMAIN-KEYWORD,adsage,REJECT
@@ -213,12 +211,15 @@ DOMAIN-KEYWORD,adsmogo,REJECT
 DOMAIN-KEYWORD,adsrvmedia,REJECT
 DOMAIN-KEYWORD,adwords,REJECT
 DOMAIN-KEYWORD,adservice,REJECT
+DOMAIN-SUFFIX,appsflyer.com,REJECT
 DOMAIN-KEYWORD,domob,REJECT
+DOMAIN-SUFFIX,doubleclick.net,REJECT
 DOMAIN-KEYWORD,duomeng,REJECT
 DOMAIN-KEYWORD,dwtrack,REJECT
 DOMAIN-KEYWORD,guanggao,REJECT
 DOMAIN-KEYWORD,lianmeng,REJECT
 DOMAIN-SUFFIX,mmstat.com,REJECT
+DOMAIN-KEYWORD,mopub,REJECT
 DOMAIN-KEYWORD,omgmta,REJECT
 DOMAIN-KEYWORD,openx,REJECT
 DOMAIN-KEYWORD,partnerad,REJECT
@@ -227,14 +228,32 @@ DOMAIN-KEYWORD,supersonicads,REJECT
 DOMAIN-KEYWORD,uedas,REJECT
 DOMAIN-KEYWORD,umeng,REJECT
 DOMAIN-KEYWORD,usage,REJECT
+DOMAIN-SUFFIX,vungle.com,REJECT
 DOMAIN-KEYWORD,wlmonitor,REJECT
 DOMAIN-KEYWORD,zjtoolbar,REJECT
 
+# 抗 DNS 污染
+DOMAIN-KEYWORD,amazon,Proxy
+DOMAIN-KEYWORD,google,Proxy
+DOMAIN-KEYWORD,gmail,Proxy
+DOMAIN-KEYWORD,youtube,Proxy
+DOMAIN-KEYWORD,facebook,Proxy
+DOMAIN-SUFFIX,fb.me,Proxy
+DOMAIN-SUFFIX,fbcdn.net,Proxy
+DOMAIN-KEYWORD,twitter,Proxy
+DOMAIN-KEYWORD,instagram,Proxy
+DOMAIN-KEYWORD,dropbox,Proxy
+DOMAIN-SUFFIX,twimg.com,Proxy
+DOMAIN-KEYWORD,blogspot,Proxy
+DOMAIN-SUFFIX,youtu.be,Proxy
+DOMAIN-KEYWORD,whatsapp,Proxy
+
 # 国外网站
 DOMAIN-SUFFIX,9to5mac.com,Proxy
 DOMAIN-SUFFIX,abpchina.org,Proxy
 DOMAIN-SUFFIX,adblockplus.org,Proxy
 DOMAIN-SUFFIX,adobe.com,Proxy
+DOMAIN-SUFFIX,akamaized.net,Proxy
 DOMAIN-SUFFIX,alfredapp.com,Proxy
 DOMAIN-SUFFIX,amplitude.com,Proxy
 DOMAIN-SUFFIX,ampproject.org,Proxy
@@ -499,16 +518,14 @@ DOMAIN-SUFFIX,ytimg.com,Proxy
 # Telegram
 DOMAIN-SUFFIX,telegra.ph,Proxy
 DOMAIN-SUFFIX,telegram.org,Proxy
-
-IP-CIDR,91.108.4.0/22,Proxy
-IP-CIDR,91.108.8.0/22,Proxy
-IP-CIDR,91.108.12.0/22,Proxy
-IP-CIDR,91.108.16.0/22,Proxy
-IP-CIDR,91.108.56.0/22,Proxy
-IP-CIDR,149.154.160.0/22,Proxy
-IP-CIDR,149.154.164.0/22,Proxy
-IP-CIDR,149.154.168.0/22,Proxy
-IP-CIDR,149.154.172.0/22,Proxy
+IP-CIDR,91.108.4.0/22,Proxy,no-resolve
+IP-CIDR,91.108.8.0/21,Proxy,no-resolve
+IP-CIDR,91.108.16.0/22,Proxy,no-resolve
+IP-CIDR,91.108.56.0/22,Proxy,no-resolve
+IP-CIDR,149.154.160.0/20,Proxy,no-resolve
+IP-CIDR6,2001:67c:4e8::/48,Proxy,no-resolve
+IP-CIDR6,2001:b28:f23d::/48,Proxy,no-resolve
+IP-CIDR6,2001:b28:f23f::/48,Proxy,no-resolve
 
 # LAN
 DOMAIN-SUFFIX,local,DIRECT
@@ -518,6 +535,8 @@ 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
+IP-CIDR,224.0.0.0/4,DIRECT
+IP-CIDR6,fe80::/10,DIRECT
 
 # 最终规则
 GEOIP,CN,DIRECT

+ 24 - 15
resources/rules/default.surge.conf

@@ -1,26 +1,33 @@
 #!MANAGED-CONFIG $subs_link interval=43200 strict=true
+# Surge 的规则配置手册: https://manual.nssurge.com/
 
 [General]
 loglevel = notify
-dns-server = 1.2.4.8, 114.114.114.114, 223.5.5.5, 8.8.8.8, system
+# 从 Surge iOS 4 / Surge Mac 3.3.0 起,工具开始支持 DoH
+doh-server = https://doh.pub/dns-query
+# https://dns.alidns.com/dns-query, https://13800000000.rubyfish.cn/, https://dns.google/dns-query
+dns-server = 223.5.5.5, 114.114.114.114
 tun-excluded-routes = 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 255.255.255.255/32
-skip-proxy = localhost, *.local, captive.apple.com, 0.0.0.0/8, 10.0.0.0/8, 17.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32
+skip-proxy = localhost, *.local, injections.adguard.org, local.adguard.org, captive.apple.com, guzzoni.apple.com, 0.0.0.0/8, 10.0.0.0/8, 17.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32
 
+wifi-assist = true
 allow-wifi-access = true
 wifi-access-http-port = 6152
 wifi-access-socks5-port = 6153
 http-listen = 0.0.0.0:6152
 socks5-listen = 0.0.0.0:6153
 
-test-timeout = 4
-network-framework = false
-proxy-test-url = http://www.gstatic.com/generate_204
-
 external-controller-access = surgepasswd@0.0.0.0:6170
-exclude-simple-hostnames = true
-ipv6 = false
 replica = false
 
+tls-provider = openssl
+network-framework = false
+exclude-simple-hostnames = true
+ipv6 = true
+
+test-timeout = 4
+proxy-test-url = http://www.gstatic.com/generate_204
+
 [Replica]
 hide-apple-request = true
 hide-crashlytics-request = true
@@ -29,6 +36,8 @@ hide-udp = false
 
 # -----------------------------
 # Surge 的几种策略配置规范,请参考 https://manual.nssurge.com/policy/proxy.html
+# 不同的代理策略有*很多*可选参数,请参考上方连接的 Parameters 一段,根据需求自行添加参数。
+#
 # Surge 现已支持 UDP 转发功能,请参考: https://trello.com/c/ugOMxD3u/53-udp-%E8%BD%AC%E5%8F%91
 # Surge 现已支持 TCP-Fast-Open 技术,请参考: https://trello.com/c/ij65BU6Q/48-tcp-fast-open-troubleshooting-guide
 # Surge 现已支持 ss-libev 的全部加密方式和混淆,请参考: https://trello.com/c/BTr0vG1O/47-ss-libev-%E7%9A%84%E6%94%AF%E6%8C%81%E6%83%85%E5%86%B5
@@ -47,13 +56,13 @@ fallback = fallback, $proxy_group, url=http://www.gstatic.com/generate_204, inte
 ## 您可以在此处插入自定义规则
 
 # 实用规则片段集
-# RULE-SET,SYSTEM,DIRECT
-RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Basic/Apple-proxy.list,Proxy
-RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Basic/Apple-direct.list,DIRECT
-RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Basic/CN.list,DIRECT
-RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Basic/common-ad-keyword.list,REJECT-TINYGIF
-RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Basic/foreign.list,Proxy
-RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/App/social/Telegram.list,Proxy
+# RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/Basic/Apple-News.list,Proxy
+RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/Basic/Apple-proxy.list,Proxy
+RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/Basic/Apple-direct.list,DIRECT
+RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/Basic/CN.list,DIRECT
+RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/Basic/common-ad-keyword.list,REJECT-TINYGIF
+RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/Basic/foreign.list,Proxy
+RULE-SET,https://cdn.jsdelivr.net/gh/Hackl0us/SS-Rule-Snippet@master/Rulesets/Surge/App/social/Telegram.list,Proxy
 RULE-SET,LAN,DIRECT
 
 # 最终规则

+ 1 - 1
resources/views/mail/classic/notify.blade.php

@@ -169,7 +169,7 @@
                                     align="center" valign="top">
                                     <a href="{{$url}}/#/subscribe"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">我的订阅</a> |
-                                    <a href="{{$url}}/#/tutorial"
+                                    <a href="{{$url}}/#/knowledge"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">使用教程</a>
                                 </td>
                             </tr>

+ 2 - 2
resources/views/mail/classic/remindExpire.blade.php

@@ -169,7 +169,7 @@
                                     align="center" valign="top">
                                     <a href="{{$url}}/#/subscribe"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">我的订阅</a> |
-                                    <a href="{{$url}}/#/tutorial"
+                                    <a href="{{$url}}/#/knowledge"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">使用教程</a>
                                 </td>
                             </tr>
@@ -184,4 +184,4 @@
     </table>
 </body>
 
-</html>
+</html>

+ 2 - 2
resources/views/mail/classic/remindTraffic.blade.php

@@ -169,7 +169,7 @@
                                     align="center" valign="top">
                                     <a href="{{$url}}/#/subscribe"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">我的订阅</a> |
-                                    <a href="{{$url}}/#/tutorial"
+                                    <a href="{{$url}}/#/knowledge"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">使用教程</a>
                                 </td>
                             </tr>
@@ -184,4 +184,4 @@
     </table>
 </body>
 
-</html>
+</html>

+ 2 - 2
resources/views/mail/classic/verify.blade.php

@@ -177,7 +177,7 @@
                                     align="center" valign="top">
                                     <a href="{{$url}}/#/subscribe"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">我的订阅</a> |
-                                    <a href="{{$url}}/#/tutorial"
+                                    <a href="{{$url}}/#/knowledge"
                                         style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: none; margin: 0;">使用教程</a>
                                 </td>
                             </tr>
@@ -192,4 +192,4 @@
     </table>
 </body>
 
-</html>
+</html>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů