Browse Source

feature: shadowsocks and more

Tokumeikoi 4 years ago
parent
commit
ba2e0a6b66

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

@@ -44,117 +44,19 @@ class CheckOrder extends Command
     {
         $orders = Order::get();
         foreach ($orders as $item) {
+            $orderService = new OrderService($item);
             switch ($item->status) {
                 // cancel
                 case 0:
                     if (strtotime($item->created_at) <= (time() - 1800)) {
-                        $orderService = new OrderService($item);
                         $orderService->cancel();
                     }
                     break;
                 case 1:
-                    $this->orderHandle($item);
+                    $orderService->open();
                     break;
             }
 
         }
     }
-
-    private function orderHandle(Order $order)
-    {
-        $user = User::find($order->user_id);
-        $plan = Plan::find($order->plan_id);
-
-        if ($order->refund_amount) {
-            $user->balance = $user->balance + $order->refund_amount;
-        }
-        DB::beginTransaction();
-        if ($order->surplus_order_ids) {
-            try {
-                Order::whereIn('id', json_decode($order->surplus_order_ids))->update([
-                    'status' => 4
-                ]);
-            } catch (\Exception $e) {
-                DB::rollback();
-                abort(500, '开通失败');
-            }
-        }
-        switch ((string)$order->cycle) {
-            case 'onetime_price':
-                $this->buyByOneTime($user, $plan);
-                break;
-            case 'reset_price':
-                $this->buyByResetTraffic($user);
-                break;
-            default:
-                $this->buyByCycle($order, $user, $plan);
-        }
-        if (!$user->save()) {
-            DB::rollBack();
-            abort(500, '开通失败');
-        }
-        $order->status = 3;
-        if (!$order->save()) {
-            DB::rollBack();
-            abort(500, '开通失败');
-        }
-
-        DB::commit();
-    }
-
-    private function buyByResetTraffic(User $user)
-    {
-        $user->u = 0;
-        $user->d = 0;
-    }
-
-    private function buyByCycle(Order $order, User $user, Plan $plan)
-    {
-        // change plan process
-        if ((int)$order->type === 3) {
-            $user->expired_at = time();
-        }
-        $user->transfer_enable = $plan->transfer_enable * 1073741824;
-
-        // 续费重置&类型=续费
-        if ((int)config('v2board.renew_reset_traffic_enable', 1) && $order->type === 2) $this->buyByResetTraffic($user);
-        // 购买前用户过期为NULL(一次性)
-        if ($user->expired_at === NULL) $this->buyByResetTraffic($user);
-        // 新购
-        if ($order->type === 1) $this->buyByResetTraffic($user);
-        $user->plan_id = $plan->id;
-        $user->group_id = $plan->group_id;
-        $user->expired_at = $this->getTime($order->cycle, $user->expired_at);
-    }
-
-    private function buyByOneTime(User $user, Plan $plan)
-    {
-        $user->transfer_enable = $plan->transfer_enable * 1073741824;
-        $user->u = 0;
-        $user->d = 0;
-        $user->plan_id = $plan->id;
-        $user->group_id = $plan->group_id;
-        $user->expired_at = NULL;
-    }
-
-    private function getTime($str, $timestamp)
-    {
-        if ($timestamp < time()) {
-            $timestamp = time();
-        }
-        switch ($str) {
-            case 'month_price':
-                return strtotime('+1 month', $timestamp);
-            case 'quarter_price':
-                return strtotime('+3 month', $timestamp);
-            case 'half_year_price':
-                return strtotime('+6 month', $timestamp);
-            case 'year_price':
-                return strtotime('+12 month', $timestamp);
-            case 'two_year_price':
-                return strtotime('+24 month', $timestamp);
-            case 'three_year_price':
-                return strtotime('+36 month', $timestamp);
-        }
-    }
 }

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

@@ -0,0 +1,139 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Server;
+
+use App\Http\Requests\Admin\ServerShadowsocksSave;
+use App\Http\Requests\Admin\ServerShadowsocksSort;
+use App\Http\Requests\Admin\ServerShadowsocksUpdate;
+use App\Http\Requests\Admin\ServerV2raySave;
+use App\Http\Requests\Admin\ServerV2raySort;
+use App\Http\Requests\Admin\ServerV2rayUpdate;
+use App\Models\ServerShadowsocks;
+use App\Services\ServerService;
+use App\Utils\CacheKey;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use App\Models\Server;
+use Illuminate\Support\Facades\Cache;
+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();
+        $params['group_id'] = json_encode($params['group_id']);
+        if (isset($params['tags'])) {
+            $params['tags'] = json_encode($params['tags']);
+        }
+
+        if ($request->input('id')) {
+            $server = ServerShadowsocks::find($request->input('id'));
+            if (!$server) {
+                abort(500, '服务器不存在');
+            }
+            try {
+                $server->update($params);
+            } catch (\Exception $e) {
+                abort(500, '保存失败');
+            }
+            return response([
+                'data' => true
+            ]);
+        }
+
+        if (!ServerShadowsocks::create($params)) {
+            abort(500, '创建失败');
+        }
+
+        return response([
+            'data' => true
+        ]);
+    }
+
+    public function drop(Request $request)
+    {
+        if ($request->input('id')) {
+            $server = ServerShadowsocks::find($request->input('id'));
+            if (!$server) {
+                abort(500, '节点ID不存在');
+            }
+        }
+        return response([
+            'data' => $server->delete()
+        ]);
+    }
+
+    public function update(ServerShadowsocksUpdate $request)
+    {
+        $params = $request->only([
+            'show',
+        ]);
+
+        $server = ServerShadowsocks::find($request->input('id'));
+
+        if (!$server) {
+            abort(500, '该服务器不存在');
+        }
+        try {
+            $server->update($params);
+        } catch (\Exception $e) {
+            abort(500, '保存失败');
+        }
+
+        return response([
+            'data' => true
+        ]);
+    }
+
+    public function copy(Request $request)
+    {
+        $server = ServerShadowsocks::find($request->input('id'));
+        $server->show = 0;
+        if (!$server) {
+            abort(500, '服务器不存在');
+        }
+        if (!Server::create($server->toArray())) {
+            abort(500, '复制失败');
+        }
+
+        return response([
+            '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
+        ]);
+    }
+}

+ 5 - 0
app/Http/Controllers/Client/AppController.php

@@ -29,6 +29,11 @@ class AppController extends Controller
         $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);

+ 16 - 6
app/Http/Controllers/Client/ClientController.php

@@ -8,6 +8,7 @@ use App\Utils\Clash;
 use App\Utils\QuantumultX;
 use App\Utils\Shadowrocket;
 use App\Utils\Surge;
+use App\Utils\URLSchemes;
 use Illuminate\Http\Request;
 use App\Models\Server;
 use App\Utils\Helper;
@@ -34,7 +35,7 @@ class ClientController extends Controller
                     die($this->quantumult($user, $servers['vmess']));
                 }
                 if (strpos($_SERVER['HTTP_USER_AGENT'], 'clash') !== false) {
-                    die($this->clash($user, $servers['vmess'], $servers['trojan']));
+                    die($this->clash($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
                 }
                 if (strpos($_SERVER['HTTP_USER_AGENT'], 'surfboard') !== false) {
                     die($this->surfboard($user, $servers['vmess']));
@@ -46,7 +47,7 @@ class ClientController extends Controller
                     die($this->shadowrocket($user, $servers['vmess'], $servers['trojan']));
                 }
             }
-            die($this->origin($user, $servers['vmess'], $servers['trojan']));
+            die($this->origin($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
         }
     }
     // TODO: Ready to stop support
@@ -101,14 +102,17 @@ class ClientController extends Controller
         return base64_encode($uri);
     }
 
-    private function origin($user, $vmess = [], $trojan = [])
+    private function origin($user, $shadowsocks = [], $vmess = [], $trojan = [])
     {
         $uri = '';
+        foreach ($shadowsocks as $item) {
+            $uri .= URLSchemes::buildShadowsocks($item, $user);
+        }
         foreach ($vmess as $item) {
-            $uri .= Helper::buildVmessLink($item, $user);
+            $uri .= URLSchemes::buildVmess($item, $user);
         }
         foreach ($trojan as $item) {
-            $uri .= Helper::buildTrojanLink($item, $user);
+            $uri .= URLSchemes::buildTrojan($item, $user);
         }
         return base64_encode($uri);
     }
@@ -192,7 +196,7 @@ class ClientController extends Controller
         return $config;
     }
 
-    private function clash($user, $vmess = [], $trojan = [])
+    private function clash($user, $shadowsocks = [], $vmess = [], $trojan = [])
     {
         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
         $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
@@ -203,6 +207,12 @@ 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);

+ 119 - 0
app/Http/Controllers/Server/ShadowsocksTidalabController.php

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

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

@@ -82,7 +82,7 @@ class OrderController extends Controller
             }
         }
 
-        if (!$plan->renew && $user->plan_id == $plan->id) {
+        if (!$plan->renew && $user->plan_id == $plan->id && $request->input('cycle') !== 'reset_price') {
             abort(500, '该订阅无法续费,请更换其他订阅');
         }
 

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

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

+ 46 - 0
app/Http/Requests/Admin/ServerShadowsocksSave.php

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

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

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

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

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

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

@@ -48,6 +48,16 @@ class AdminRoute
                 $router->post('sort', 'Admin\\Server\\V2rayController@sort');
                 $router->post('viewConfig', 'Admin\\Server\\V2rayController@viewConfig');
             });
+            $router->group([
+                'prefix' => 'server/shadowsocks'
+            ], function ($router) {
+                $router->get ('fetch', 'Admin\\Server\\ShadowsocksController@fetch');
+                $router->post('save', 'Admin\\Server\\ShadowsocksController@save');
+                $router->post('drop', 'Admin\\Server\\ShadowsocksController@drop');
+                $router->post('update', 'Admin\\Server\\ShadowsocksController@update');
+                $router->post('copy', 'Admin\\Server\\ShadowsocksController@copy');
+                $router->post('sort', 'Admin\\Server\\ShadowsocksController@sort');
+            });
             // Order
             $router->get ('/order/fetch', 'Admin\\OrderController@fetch');
             $router->post('/order/repair', 'Admin\\OrderController@repair');

+ 12 - 0
app/Models/ServerShadowsocks.php

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

+ 100 - 0
app/Services/OrderService.php

@@ -24,6 +24,49 @@ class OrderService
         $this->order = $order;
     }
 
+    public function open()
+    {
+        $order = $this->order;
+        $user = User::find($order->user_id);
+        $plan = Plan::find($order->plan_id);
+
+        if ($order->refund_amount) {
+            $user->balance = $user->balance + $order->refund_amount;
+        }
+        DB::beginTransaction();
+        if ($order->surplus_order_ids) {
+            try {
+                Order::whereIn('id', json_decode($order->surplus_order_ids))->update([
+                    'status' => 4
+                ]);
+            } catch (\Exception $e) {
+                DB::rollback();
+                abort(500, '开通失败');
+            }
+        }
+        switch ((string)$order->cycle) {
+            case 'onetime_price':
+                $this->buyByOneTime($user, $plan);
+                break;
+            case 'reset_price':
+                $this->buyByResetTraffic($user);
+                break;
+            default:
+                $this->buyByCycle($order, $user, $plan);
+        }
+        if (!$user->save()) {
+            DB::rollBack();
+            abort(500, '开通失败');
+        }
+        $order->status = 3;
+        if (!$order->save()) {
+            DB::rollBack();
+            abort(500, '开通失败');
+        }
+
+        DB::commit();
+    }
+
     public function cancel():bool
     {
         $order = $this->order;
@@ -165,4 +208,61 @@ class OrderService
         $order->callback_no = $callbackNo;
         return $order->save();
     }
+
+
+    private function buyByResetTraffic(User $user)
+    {
+        $user->u = 0;
+        $user->d = 0;
+    }
+
+    private function buyByCycle(Order $order, User $user, Plan $plan)
+    {
+        // change plan process
+        if ((int)$order->type === 3) {
+            $user->expired_at = time();
+        }
+        $user->transfer_enable = $plan->transfer_enable * 1073741824;
+
+        // 续费重置&类型=续费
+        if ((int)config('v2board.renew_reset_traffic_enable', 1) && $order->type === 2) $this->buyByResetTraffic($user);
+        // 购买前用户过期为NULL(一次性)
+        if ($user->expired_at === NULL) $this->buyByResetTraffic($user);
+        // 新购
+        if ($order->type === 1) $this->buyByResetTraffic($user);
+        $user->plan_id = $plan->id;
+        $user->group_id = $plan->group_id;
+        $user->expired_at = $this->getTime($order->cycle, $user->expired_at);
+    }
+
+    private function buyByOneTime(User $user, Plan $plan)
+    {
+        $user->transfer_enable = $plan->transfer_enable * 1073741824;
+        $user->u = 0;
+        $user->d = 0;
+        $user->plan_id = $plan->id;
+        $user->group_id = $plan->group_id;
+        $user->expired_at = NULL;
+    }
+
+    private function getTime($str, $timestamp)
+    {
+        if ($timestamp < time()) {
+            $timestamp = time();
+        }
+        switch ($str) {
+            case 'month_price':
+                return strtotime('+1 month', $timestamp);
+            case 'quarter_price':
+                return strtotime('+3 month', $timestamp);
+            case 'half_year_price':
+                return strtotime('+6 month', $timestamp);
+            case 'year_price':
+                return strtotime('+12 month', $timestamp);
+            case 'two_year_price':
+                return strtotime('+24 month', $timestamp);
+            case 'three_year_price':
+                return strtotime('+36 month', $timestamp);
+        }
+    }
 }

+ 32 - 1
app/Services/ServerService.php

@@ -3,11 +3,13 @@
 namespace App\Services;
 
 use App\Models\ServerLog;
+use App\Models\ServerShadowsocks;
 use App\Models\User;
 use App\Models\Server;
 use App\Models\ServerTrojan;
 use App\Utils\CacheKey;
 use App\Utils\Helper;
+use App\Utils\URLSchemes;
 use Illuminate\Support\Facades\Cache;
 
 class ServerService
@@ -24,9 +26,10 @@ class ServerService
         }
         $vmesss = $model->get();
         foreach ($vmesss as $k => $v) {
+            $vmesss[$k]['protocol_type'] = 'vmess';
             $groupId = json_decode($vmesss[$k]['group_id']);
             if (in_array($user->group_id, $groupId)) {
-                $vmesss[$k]['link'] = Helper::buildVmessLink($vmesss[$k], $user);
+                $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']));
                 } else {
@@ -49,7 +52,9 @@ class ServerService
         }
         $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);
             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']));
@@ -63,9 +68,35 @@ class ServerService
         return $trojan;
     }
 
+    public function getShadowsocks(User $user, $all = false)
+    {
+        $shadowsocks = [];
+        $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);
+            if (in_array($user->group_id, $groupId)) {
+                if ($shadowsockss[$k]['parent_id']) {
+                    $shadowsockss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $shadowsockss[$k]['parent_id']));
+                } else {
+                    $shadowsockss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $shadowsockss[$k]['id']));
+                }
+                array_push($shadowsocks, $shadowsockss[$k]);
+            }
+
+        }
+        return $shadowsocks;
+    }
+
     public function getAllServers(User $user, $all = false)
     {
         return [
+            'shadowsocks' => $this->getShadowsocks($user, $all),
             'vmess' => $this->getVmess($user, $all),
             'trojan' => $this->getTrojan($user, $all)
         ];

+ 2 - 0
app/Utils/CacheKey.php

@@ -11,6 +11,8 @@ class CacheKey
         'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间',
         'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户',
         'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间',
+        'SERVER_SHADOWSOCKS_ONLINE_USER' => 'ss节点在线用户',
+        'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间',
         'TEMP_TOKEN' => '临时令牌',
         'LAST_SEND_EMAIL_REMIND_TRAFFIC'
     ];

+ 13 - 0
app/Utils/Clash.php

@@ -5,6 +5,19 @@ namespace App\Utils;
 
 class Clash
 {
+    public static function buildShadowsocks($uuid, $server)
+    {
+        $array = [];
+        $array['name'] = $server->name;
+        $array['type'] = 'ss';
+        $array['server'] = $server->host;
+        $array['port'] = $server->port;
+        $array['cipher'] = $server->cipher;
+        $array['password'] = $uuid;
+        $array['udp'] = true;
+        return $array;
+    }
+
     public static function buildVmess($uuid, $server)
     {
         $array = [];

+ 1 - 36
app/Utils/Helper.php

@@ -3,6 +3,7 @@
 namespace App\Utils;
 
 use App\Models\Server;
+use App\Models\ServerShadowsocks;
 use App\Models\ServerTrojan;
 use App\Models\User;
 
@@ -57,42 +58,6 @@ class Helper
         return $str;
     }
 
-    public static function buildTrojanLink(ServerTrojan $server, User $user)
-    {
-        $server->name = rawurlencode($server->name);
-        $query = http_build_query([
-            'allowInsecure' => $server->allow_insecure,
-            'peer' => $server->server_name,
-            'sni' => $server->server_name
-        ]);
-        $uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$server->name}";
-        $uri .= "\r\n";
-        return $uri;
-    }
-
-    public static function buildVmessLink(Server $server, User $user)
-    {
-        $config = [
-            "v" => "2",
-            "ps" => $server->name,
-            "add" => $server->host,
-            "port" => $server->port,
-            "id" => $user->uuid,
-            "aid" => "2",
-            "net" => $server->network,
-            "type" => "none",
-            "host" => "",
-            "path" => "",
-            "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;
-        }
-        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
-    }
-
     public static function multiPasswordVerify($algo, $password, $hash)
     {
         switch($algo) {

+ 57 - 0
app/Utils/URLSchemes.php

@@ -0,0 +1,57 @@
+<?php
+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)
+    {
+        $str = str_replace(
+            ['+', '/', '='],
+            ['-', '_', ''],
+            base64_encode("{$server->cipher}:{$user->uuid}")
+        );
+        return "ss://{$str}@{$server->host}:{$server->port}#{$server->name}\r\n";
+    }
+
+
+    public static function buildVmess(Server $server, User $user)
+    {
+        $config = [
+            "v" => "2",
+            "ps" => $server->name,
+            "add" => $server->host,
+            "port" => $server->port,
+            "id" => $user->uuid,
+            "aid" => "2",
+            "net" => $server->network,
+            "type" => "none",
+            "host" => "",
+            "path" => "",
+            "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;
+        }
+        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
+    }
+
+    public static function buildTrojan(ServerTrojan $server, User $user)
+    {
+        $server->name = rawurlencode($server->name);
+        $query = http_build_query([
+            'allowInsecure' => $server->allow_insecure,
+            'peer' => $server->server_name,
+            'sni' => $server->server_name
+        ]);
+        $uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$server->name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+}

+ 1 - 1
config/app.php

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

+ 22 - 2
database/install.sql

@@ -22,7 +22,7 @@ CREATE TABLE `failed_jobs` (
 DROP TABLE IF EXISTS `v2_coupon`;
 CREATE TABLE `v2_coupon` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
-  `code` char(8) NOT NULL,
+  `code` varchar(255) NOT NULL,
   `name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
   `type` tinyint(1) NOT NULL,
   `value` int(11) NOT NULL,
@@ -177,6 +177,26 @@ CREATE TABLE `v2_server_log` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
+DROP TABLE IF EXISTS `v2_server_shadowsocks`;
+CREATE TABLE `v2_server_shadowsocks` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `group_id` varchar(255) NOT NULL,
+  `parent_id` int(11) DEFAULT NULL,
+  `tags` varchar(255) DEFAULT NULL,
+  `name` varchar(255) NOT NULL,
+  `rate` varchar(11) NOT NULL,
+  `host` varchar(255) NOT NULL,
+  `port` int(11) NOT NULL,
+  `server_port` int(11) NOT NULL,
+  `cipher` varchar(255) NOT NULL,
+  `show` tinyint(4) NOT NULL DEFAULT '0',
+  `sort` int(11) DEFAULT NULL,
+  `created_at` int(11) NOT NULL,
+  `updated_at` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+
 DROP TABLE IF EXISTS `v2_server_stat`;
 CREATE TABLE `v2_server_stat` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
@@ -289,4 +309,4 @@ CREATE TABLE `v2_user` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--- 2020-09-19 14:39:28
+-- 2020-09-29 09:05:00

+ 20 - 0
database/update.sql

@@ -307,3 +307,23 @@ ADD `three_year_price` int(11) NULL AFTER `two_year_price`;
 
 ALTER TABLE `v2_user`
 ADD `is_staff` tinyint(1) NOT NULL DEFAULT '0' AFTER `is_admin`;
+
+CREATE TABLE `v2_server_shadowsocks` (
+  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  `group_id` varchar(255) NOT NULL,
+  `parent_id` int(11) NULL,
+  `tags` varchar(255) NULL,
+  `name` varchar(255) NOT NULL,
+  `rate` varchar(11) NOT NULL,
+  `host` varchar(255) NOT NULL,
+  `port` int(11) NOT NULL,
+  `server_port` int(11) NOT NULL,
+  `cipher` varchar(255) NOT NULL,
+  `show` tinyint NOT NULL DEFAULT '0',
+  `sort` int(11) NULL,
+  `created_at` int(11) NOT NULL,
+  `updated_at` int(11) NOT NULL
+) COLLATE 'utf8mb4_general_ci';
+
+ALTER TABLE `v2_coupon`
+CHANGE `code` `code` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `id`;

File diff suppressed because it is too large
+ 0 - 0
public/assets/admin/umi.js


File diff suppressed because it is too large
+ 0 - 0
public/assets/user/umi.js


Some files were not shown because too many files changed in this diff