mirror of
				https://github.com/v2board/v2board.git
				synced 2025-10-31 01:11:46 +08:00 
			
		
		
		
	feature: shadowsocks and more
This commit is contained in:
		| @@ -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
									
								
								app/Http/Controllers/Admin/Server/ShadowsocksController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								app/Http/Controllers/Admin/Server/ShadowsocksController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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
									
								
								app/Http/Controllers/Server/ShadowsocksTidalabController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								app/Http/Controllers/Server/ShadowsocksTidalabController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -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)); | ||||
|     } | ||||
| } | ||||
| @@ -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, '该订阅无法续费,请更换其他订阅'); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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
									
								
								app/Http/Requests/Admin/ServerShadowsocksSave.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/Http/Requests/Admin/ServerShadowsocksSave.php
									
									
									
									
									
										Normal file
									
								
							| @@ -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
									
								
								app/Http/Requests/Admin/ServerShadowsocksSort.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/Http/Requests/Admin/ServerShadowsocksSort.php
									
									
									
									
									
										Normal file
									
								
							| @@ -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
									
								
								app/Http/Requests/Admin/ServerShadowsocksUpdate.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										28
									
								
								app/Http/Requests/Admin/ServerShadowsocksUpdate.php
									
									
									
									
									
										Executable file
									
								
							| @@ -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' => '显示状态格式不正确' | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -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
									
								
								app/Models/ServerShadowsocks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/Models/ServerShadowsocks.php
									
									
									
									
									
										Normal file
									
								
							| @@ -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']; | ||||
| } | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|         ]; | ||||
|   | ||||
| @@ -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' | ||||
|     ]; | ||||
|   | ||||
| @@ -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 = []; | ||||
|   | ||||
| @@ -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
									
								
								app/Utils/URLSchemes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/Utils/URLSchemes.php
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user