Browse Source

Fix & Improvement

Fix #98 ;
Fix some missing class errors;
Finished the validation for configs & simplify the store/update process;
Combined the User node info & subscribe node info as one unit;
兔姬桑 4 years ago
parent
commit
265fabac86
32 changed files with 449 additions and 623 deletions
  1. 26 0
      app/Components/Client/Text.php
  2. 1 8
      app/Http/Controllers/Admin/AffiliateController.php
  3. 1 0
      app/Http/Controllers/Admin/ArticleController.php
  4. 4 4
      app/Http/Controllers/Admin/CertController.php
  5. 18 46
      app/Http/Controllers/Admin/Config/CountryController.php
  6. 6 11
      app/Http/Controllers/Admin/Config/EmailFilterController.php
  7. 23 9
      app/Http/Controllers/Admin/Config/LabelController.php
  8. 12 33
      app/Http/Controllers/Admin/Config/LevelController.php
  9. 16 34
      app/Http/Controllers/Admin/Config/SsConfigController.php
  10. 3 9
      app/Http/Controllers/Admin/CouponController.php
  11. 2 1
      app/Http/Controllers/Admin/LogsController.php
  12. 2 8
      app/Http/Controllers/Admin/MarketingController.php
  13. 9 15
      app/Http/Controllers/Admin/NodeAuthController.php
  14. 3 5
      app/Http/Controllers/Admin/NodeController.php
  15. 1 1
      app/Http/Controllers/Admin/RoleController.php
  16. 16 17
      app/Http/Controllers/Admin/ShopController.php
  17. 1 1
      app/Http/Controllers/Admin/SystemController.php
  18. 29 41
      app/Http/Controllers/Admin/UserController.php
  19. 4 3
      app/Http/Controllers/Api/Client/V1Controller.php
  20. 2 1
      app/Http/Controllers/Api/WebApi/BaseController.php
  21. 225 0
      app/Http/Controllers/ClientController.php
  22. 15 98
      app/Http/Controllers/Controller.php
  23. 3 250
      app/Http/Controllers/User/SubscribeController.php
  24. 2 11
      app/Http/Controllers/UserController.php
  25. 5 0
      app/Models/Country.php
  26. 5 0
      app/Models/Level.php
  27. 1 1
      app/Models/Node.php
  28. 4 4
      app/Models/ReferralLog.php
  29. 1 1
      config/version.php
  30. 2 2
      resources/views/admin/config/config.blade.php
  31. 7 7
      resources/views/admin/config/emailFilter.blade.php
  32. 0 2
      resources/views/admin/node/auth.blade.php

+ 26 - 0
app/Components/Client/Text.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Components\Client;
+
+class Text
+{
+    public static function buildShadowsocks($server)
+    {
+        return '服务器:'.$server['host'].PHP_EOL.'服务器端口:'.$server['port'].PHP_EOL.'密码:'.$server['passwd'].PHP_EOL.'加密:'.$server['method'].PHP_EOL;
+    }
+
+    public static function buildShadowsocksr($server)
+    {
+        return '服务器:'.$server['host'].PHP_EOL.'服务器端口:'.$server['port'].PHP_EOL.'密码:'.$server['passwd'].PHP_EOL.'加密:'.$server['method'].PHP_EOL.'协议:'.$server['protocol'].PHP_EOL.'协议参数:'.$server['protocol_param'].PHP_EOL.'混淆:'.$server['obfs'].PHP_EOL.'混淆参数:'.$server['obfs_param'].PHP_EOL.'UDP:'.$server['udp'].PHP_EOL;
+    }
+
+    public static function buildVmess($server)
+    {
+        return '服务器:'.$server['host'].PHP_EOL.'端口:'.$server['port'].PHP_EOL.'加密方式:'.$server['method'].PHP_EOL.'用户ID:'.$server['uuid'].PHP_EOL.'额外ID:'.$server['v2_alter_id'].PHP_EOL.'传输协议:'.$server['v2_net'].PHP_EOL.'伪装类型:'.$server['v2_type'].PHP_EOL.'伪装域名:'.$server['v2_host'].PHP_EOL.'路径:'.$server['v2_path'].PHP_EOL.'TLS:'.$server['v2_tls'].PHP_EOL.'UDP:'.$server['udp'].PHP_EOL;
+    }
+
+    public static function buildTrojan($server)
+    {
+        return '服务器:'.$server['host'].PHP_EOL.'端口:'.$server['port'].PHP_EOL.'密码:'.$server['passwd'].PHP_EOL.'SNI:'.$server['sni'].PHP_EOL.'UDP:'.$server['udp'].PHP_EOL;
+    }
+}

+ 1 - 8
app/Http/Controllers/Admin/AffiliateController.php

@@ -32,16 +32,9 @@ class AffiliateController extends Controller
     // 提现申请详情
     public function detail(Request $request, ReferralApply $aff)
     {
-        if ($aff->link_logs) {
-            $commissions = ReferralLog::with(['invitee:id,email', 'order.goods:id,name'])
-                ->whereIn('id', $aff->link_logs)
-                ->paginate(15)
-                ->appends($request->except('page'));
-        }
-
         return view('admin.aff.detail', [
             'referral' => $aff->load('user:id,email'),
-            'commissions' => $commissions ?? null,
+            'commissions' => $aff->referral_logs()->with(['invitee:id,email', 'order.goods:id,name'])->paginate()->appends($request->except('page')),
         ]);
     }
 

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

@@ -8,6 +8,7 @@ use App\Models\Article;
 use Exception;
 use Illuminate\Http\UploadedFile;
 use Log;
+use Str;
 
 class ArticleController extends Controller
 {

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

@@ -13,7 +13,7 @@ class CertController extends Controller
     // 域名证书列表
     public function index()
     {
-        $certs = NodeCertificate::orderBy('id')->paginate(15)->appends(request('page'));
+        $certs = NodeCertificate::orderBy('id')->paginate()->appends(request('page'));
         foreach ($certs as $cert) {
             if ($cert->pem) {
                 $certInfo = openssl_x509_parse($cert->pem);
@@ -63,14 +63,14 @@ class CertController extends Controller
     {
         try {
             if ($cert->delete()) {
-                return response()->json(['status' => 'success', 'message' => '操作成功']);
+                return response()->json(['status' => 'success', 'message' => '删除成功']);
             }
         } catch (Exception $e) {
             Log::error('删除域名证书失败:'.$e->getMessage());
 
-            return response()->json(['status' => 'fail', 'message' => '删除域名证书错误:'.$e->getMessage()]);
+            return response()->json(['status' => 'fail', 'message' => '删除错误:'.$e->getMessage()]);
         }
 
-        return response()->json(['status' => 'fail', 'message' => '删除域名证书失败']);
+        return response()->json(['status' => 'fail', 'message' => '删除失败']);
     }
 }

+ 18 - 46
app/Http/Controllers/Admin/Config/CountryController.php

@@ -4,67 +4,44 @@ namespace App\Http\Controllers\Admin\Config;
 
 use App\Http\Controllers\Controller;
 use App\Models\Country;
-use App\Models\Node;
 use Exception;
-use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Log;
 use Response;
+use Validator;
 
 class CountryController extends Controller
 {
     // 添加国家/地区
-    public function store(Request $request): JsonResponse
+    public function store(Request $request)
     {
-        $code = $request->input('code');
-        $name = $request->input('name');
+        $validator = Validator::make($request->all(), [
+            'code' => 'required|string|unique:country,code',
+            'name' => 'required',
+        ]);
 
-        if (empty($code)) {
-            return Response::json(['status' => 'fail', 'message' => '国家/地区代码不能为空']);
+        if ($validator->fails()) {
+            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
         }
 
-        if (empty($name)) {
-            return Response::json(['status' => 'fail', 'message' => '国家/地区名称不能为空']);
+        if (Country::create($validator->validated())) {
+            return Response::json(['status' => 'success', 'message' => '生成成功']);
         }
 
-        $exists = Country::find($code);
-        if ($exists) {
-            return Response::json(['status' => 'fail', 'message' => '该国家/地区名称已存在,请勿重复添加']);
-        }
-
-        $obj = new Country();
-        $obj->code = $code;
-        $obj->name = $name;
-
-        if ($obj->save()) {
-            return Response::json(['status' => 'success', 'message' => '提交成功']);
-        }
-
-        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+        return Response::json(['status' => 'fail', 'message' => '生成失败']);
     }
 
     // 编辑国家/地区
-    public function update(Request $request, $code): JsonResponse
+    public function update(Request $request, Country $country)
     {
-        $name = $request->input('name');
-
-        if (empty($name)) {
-            return Response::json(['status' => 'fail', 'message' => '国家/地区名称不能为空']);
-        }
+        $validator = Validator::make($request->all(), ['name' => 'required']);
 
-        $country = Country::find($code);
-        if (! $country) {
-            return Response::json(['status' => 'fail', 'message' => '国家/地区不存在']);
-        }
-
-        // 校验该国家/地区下是否存在关联节点
-        if (Node::whereCountryCode($country->code)->exists()) {
-            return Response::json(['status' => 'fail', 'message' => '该国家/地区下存在关联节点,请先取消关联']);
+        if ($validator->fails()) {
+            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
         }
 
         try {
-            $country->name = $name;
-            if ($country->save()) {
+            if ($country->update($validator->validated())) {
                 return Response::json(['status' => 'success', 'message' => '编辑成功']);
             }
         } catch (Exception $e) {
@@ -77,15 +54,10 @@ class CountryController extends Controller
     }
 
     // 删除国家/地区
-    public function destroy($code): ?JsonResponse
+    public function destroy(Country $country)
     {
-        $country = Country::find($code);
-        if (! $country) {
-            return Response::json(['status' => 'fail', 'message' => '国家/地区不存在']);
-        }
-
         // 校验该国家/地区下是否存在关联节点
-        if (Node::whereCountryCode($country->code)->exists()) {
+        if ($country->nodes()->exists()) {
             return Response::json(['status' => 'fail', 'message' => '该国家/地区下存在关联节点,请先取消关联']);
         }
 

+ 6 - 11
app/Http/Controllers/Admin/Config/EmailFilterController.php

@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Admin\Config;
 use App\Http\Controllers\Controller;
 use App\Models\EmailFilter;
 use Exception;
-use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Log;
 use Response;
@@ -16,13 +15,14 @@ class EmailFilterController extends Controller
     // 邮箱过滤列表
     public function index()
     {
-        return view('admin.config.emailFilter', ['list' => EmailFilter::orderByDesc('id')->paginate()]);
+        return view('admin.config.emailFilter', ['filters' => EmailFilter::orderByDesc('id')->paginate()]);
     }
 
     // 添加邮箱后缀
-    public function store(Request $request): JsonResponse
+    public function store(Request $request)
     {
         $validator = Validator::make($request->all(), [
+            'type' => 'required|numeric|between:1,2',
             'words' => 'required|unique:email_filter',
         ]);
 
@@ -31,11 +31,7 @@ class EmailFilterController extends Controller
         }
 
         try {
-            $obj = new EmailFilter();
-            $obj->type = $request->input('type');
-            $obj->words = strtolower($request->input('words'));
-
-            if ($obj->save()) {
+            if (EmailFilter::create($validator->validated())) {
                 return Response::json(['status' => 'success', 'message' => '添加成功']);
             }
         } catch (Exception $e) {
@@ -48,11 +44,10 @@ class EmailFilterController extends Controller
     }
 
     // 删除邮箱后缀
-    public function destroy($id): JsonResponse
+    public function destroy(EmailFilter $filter)
     {
         try {
-            $result = EmailFilter::whereId($id)->delete();
-            if ($result) {
+            if ($filter->delete()) {
                 return Response::json(['status' => 'success', 'message' => '删除成功']);
             }
         } catch (Exception $e) {

+ 23 - 9
app/Http/Controllers/Admin/Config/LabelController.php

@@ -5,21 +5,26 @@ namespace App\Http\Controllers\Admin\Config;
 use App\Http\Controllers\Controller;
 use App\Models\Label;
 use Exception;
-use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Log;
 use Response;
+use Validator;
 
 class LabelController extends Controller
 {
     // 添加标签
-    public function store(Request $request): JsonResponse
+    public function store(Request $request)
     {
-        $label = new Label();
-        $label->name = $request->input('name');
-        $label->sort = $request->input('sort');
+        $validator = Validator::make($request->all(), [
+            'name' => 'required|string|unique:label,name',
+            'sort' => 'required|numeric',
+        ]);
 
-        if ($label->save()) {
+        if ($validator->fails()) {
+            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
+        }
+
+        if (Label::create($validator->validated())) {
             return Response::json(['status' => 'success', 'message' => '添加成功']);
         }
 
@@ -27,9 +32,18 @@ class LabelController extends Controller
     }
 
     // 编辑标签
-    public function update(Request $request, Label $label): JsonResponse
+    public function update(Request $request, Label $label)
     {
-        if ($label->update(['name' => $request->input('name'), 'sort' => $request->input('sort')])) {
+        $validator = Validator::make($request->all(), [
+            'name' => 'required|string|unique:label,name,'.$label->id,
+            'sort' => 'required|numeric',
+        ]);
+
+        if ($validator->fails()) {
+            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
+        }
+
+        if ($label->update($validator->validated())) {
             return Response::json(['status' => 'success', 'message' => '编辑成功']);
         }
 
@@ -37,7 +51,7 @@ class LabelController extends Controller
     }
 
     // 删除标签
-    public function destroy(Label $label): ?JsonResponse
+    public function destroy(Label $label)
     {
         try {
             $label->delete();

+ 12 - 33
app/Http/Controllers/Admin/Config/LevelController.php

@@ -4,9 +4,7 @@ namespace App\Http\Controllers\Admin\Config;
 
 use App\Http\Controllers\Controller;
 use App\Models\Level;
-use App\Models\User;
 use Exception;
-use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Log;
 use Response;
@@ -15,23 +13,18 @@ use Validator;
 class LevelController extends Controller
 {
     // 添加等级
-    public function store(Request $request): JsonResponse
+    public function store(Request $request)
     {
         $validator = Validator::make($request->all(), [
             'level' => 'required|numeric|unique:level,level',
-            'level_name' => 'required',
+            'name' => 'required',
         ]);
 
         if ($validator->fails()) {
             return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
         }
 
-        $obj = new Level();
-        $obj->level = $request->input('level');
-        $obj->name = $request->input('level_name');
-        $obj->save();
-
-        if ($obj->id) {
+        if (Level::create($validator->validated())) {
             return Response::json(['status' => 'success', 'message' => '提交成功']);
         }
 
@@ -39,48 +32,34 @@ class LevelController extends Controller
     }
 
     // 编辑等级
-    public function update(Request $request, $id): JsonResponse
+    public function update(Request $request, Level $level)
     {
-        $level = $request->input('level');
-
         $validator = Validator::make($request->all(), [
-            'level' => 'required|numeric',
-            'level_name' => 'required',
+            'level' => 'required|numeric|unique:level,level,'.$level->id,
+            'name' => 'required',
         ]);
 
         if ($validator->fails()) {
             return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
         }
-        // 校验该等级下是否存在关联账号
-        $levelCheck = Level::where('id', '<>', $id)->whereLevel($level)->exists();
-        if ($levelCheck) {
-            return Response::json(['status' => 'fail', 'message' => '该等级已存在!']);
-        }
 
-        // 校验该等级下是否存在关联账号
-        $userCount = User::whereLevel($level)->count();
-        if ($userCount) {
-            return Response::json(['status' => 'fail', 'message' => '该等级下存在关联账号,请先取消关联!']);
+        if ($level->update($validator->validated())) {
+            return Response::json(['status' => 'success', 'message' => '操作成功']);
         }
 
-        Level::whereId($id)->update(['level' => $level, 'name' => $request->input('level_name')]);
-
-        return Response::json(['status' => 'success', 'message' => '操作成功']);
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
     }
 
     // 删除等级
-    public function destroy($id): JsonResponse
+    public function destroy(Level $level)
     {
-        $level = Level::find($id);
-
         // 校验该等级下是否存在关联账号
-        $userCount = User::whereLevel($level->level)->count();
-        if ($userCount) {
+        if ($level->users()->exists()) {
             return Response::json(['status' => 'fail', 'message' => '该等级下存在关联账号,请先取消关联']);
         }
 
         try {
-            if (Level::whereId($id)->delete()) {
+            if ($level->delete()) {
                 return Response::json(['status' => 'success', 'message' => '删除成功']);
             }
         } catch (Exception $e) {

+ 16 - 34
app/Http/Controllers/Admin/Config/SsConfigController.php

@@ -5,67 +5,49 @@ namespace App\Http\Controllers\Admin\Config;
 use App\Http\Controllers\Controller;
 use App\Models\SsConfig;
 use Exception;
-use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Log;
 use Response;
+use Validator;
 
 class SsConfigController extends Controller
 {
     // 添加SS配置
-    public function store(Request $request): JsonResponse
+    public function store(Request $request)
     {
-        $name = $request->input('name');
-        $type = $request->input('type', 1); // 类型:1-加密方式(method)、2-协议(protocol)、3-混淆(obfs)
-        $is_default = $request->input('is_default', 0);
-        $sort = $request->input('sort', 0);
+        $validator = Validator::make($request->all(), [
+            'name' => 'required|string|unique:ss_config,name',
+            'type' => 'required|numeric|between:1,3',
+        ]);
 
-        if (empty($name)) {
-            return Response::json(['status' => 'fail', 'message' => '配置名称不能为空']);
+        if ($validator->fails()) {
+            return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
         }
 
-        // 校验是否已存在
-        $config = SsConfig::type($type)->whereName($name)->first();
-        if ($config) {
-            return Response::json(['status' => 'fail', 'message' => '配置已经存在,请勿重复添加']);
+        if (SsConfig::create($validator->validated())) {
+            return Response::json(['status' => 'success', 'message' => '添加成功']);
         }
 
-        $ssConfig = new SsConfig();
-        $ssConfig->name = $name;
-        $ssConfig->type = $type;
-        $ssConfig->is_default = $is_default;
-        $ssConfig->sort = $sort;
-        $ssConfig->save();
-
-        return Response::json(['status' => 'success', 'message' => '添加成功']);
+        return Response::json(['status' => 'fail', 'message' => '添加失败']);
     }
 
     // 设置SS默认配置
-    public function update($id): JsonResponse
+    public function update(SsConfig $ss)
     {
-        if (empty($id)) {
-            return Response::json(['status' => 'fail', 'message' => '非法请求']);
-        }
-
-        $config = SsConfig::find($id);
-        if (! $config) {
-            return Response::json(['status' => 'fail', 'message' => '配置不存在']);
-        }
-
         // 去除该配置所属类型的默认值
-        SsConfig::default()->type($config->type)->update(['is_default' => 0]);
+        SsConfig::default()->type($ss->type)->update(['is_default' => 0]);
 
         // 将该ID对应记录值置为默认值
-        SsConfig::whereId($id)->update(['is_default' => 1]);
+        $ss->update(['is_default' => 1]);
 
         return Response::json(['status' => 'success', 'message' => '操作成功']);
     }
 
     // 删除SS配置
-    public function destroy($id): JsonResponse
+    public function destroy(SsConfig $ss)
     {
         try {
-            if (SsConfig::whereId($id)->delete()) {
+            if ($ss->delete()) {
                 return Response::json(['status' => 'success', 'message' => '删除成功']);
             }
         } catch (Exception $e) {

+ 3 - 9
app/Http/Controllers/Admin/CouponController.php

@@ -15,11 +15,6 @@ use Redirect;
 use Response;
 use Str;
 
-/**
- * 优惠券控制器.
- *
- * Class CouponController
- */
 class CouponController extends Controller
 {
     // 优惠券列表
@@ -67,11 +62,10 @@ class CouponController extends Controller
             }
             $logo = 'upload/'.$fileName;
         }
+        $num = (int) $request->input('num');
+        $data = $request->only(['name', 'type', 'usable_times', 'value', 'rule', 'start_time', 'end_time']);
+        $data['logo'] = $logo;
         try {
-            $num = (int) $request->input('num');
-            $data = $request->only(['name', 'type', 'usable_times', 'value', 'rule', 'start_time', 'end_time']);
-            $data['logo'] = $logo;
-
             for ($i = 0; $i < $num; $i++) {
                 $data['sn'] = $num === 1 && $request->input('sn') ? $request->input('sn') : Str::random(8);
                 Coupon::create($data);

+ 2 - 1
app/Http/Controllers/Admin/LogsController.php

@@ -286,8 +286,9 @@ class LogsController extends Controller
 
         $nodeOnlineIPs = NodeOnlineIp::with('node:id,name')->where('created_at', '>=', strtotime('-10 minutes'))->latest()->distinct()->get();
         foreach ($userList as $user) {
+            //Todo node_online_ip表 api可以用user_id
             // 最近5条在线IP记录,如果后端设置为60秒上报一次,则为10分钟内的在线IP
-            $user->onlineIPList = $nodeOnlineIPs->where('port', '==', $user->port)->chunk(5);
+            $user->onlineIPList = $nodeOnlineIPs->where('port', $user->port)->chunk(5);
         }
 
         return view('admin.logs.userOnlineIP', ['userList' => $userList]);

+ 2 - 8
app/Http/Controllers/Admin/MarketingController.php

@@ -7,17 +7,11 @@ use App\Models\Marketing;
 use DB;
 use Exception;
 use Http;
-use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Log;
 use Response;
 use RuntimeException;
 
-/**
- * 促销控制器.
- *
- * Class MarketingController
- */
 class MarketingController extends Controller
 {
     // 邮件群发消息列表
@@ -49,7 +43,7 @@ class MarketingController extends Controller
     }
 
     // 添加推送消息
-    public function addPushMarketing(Request $request): ?JsonResponse
+    public function addPushMarketing(Request $request)
     {
         $title = $request->input('title');
         $content = $request->input('content');
@@ -74,7 +68,7 @@ class MarketingController extends Controller
                 throw new RuntimeException($message['message']);
             }
 
-            $this->addMarketing(2, $title, $content, 1);
+            $this->addMarketing(2, $title, $content);
 
             DB::commit();
 

+ 9 - 15
app/Http/Controllers/Admin/NodeAuthController.php

@@ -3,9 +3,11 @@
 namespace App\Http\Controllers\Admin;
 
 use App\Http\Controllers\Controller;
+use App\Models\Node;
 use App\Models\NodeAuth;
 use Exception;
-use Illuminate\Http\Request;
+use Response;
+use Str;
 
 class NodeAuthController extends Controller
 {
@@ -16,24 +18,16 @@ class NodeAuthController extends Controller
     }
 
     // 添加节点授权
-    public function store(Request $request)
+    public function store()
     {
-        $nodeArray = Node::whereStatus(1)->orderBy('id')->pluck('id')->toArray();
-        $authArray = NodeAuth::orderBy('node_id')->pluck('node_id')->toArray();
+        $nodes = Node::whereStatus(1)->doesntHave('auth')->orderBy('id')->get();
 
-        $arrayDifferent = array_diff($nodeArray, $authArray);
-
-        if (empty($arrayDifferent)) {
+        if ($nodes->isEmpty()) {
             return Response::json(['status' => 'success', 'message' => '没有需要生成授权的节点']);
         }
-
-        foreach ($arrayDifferent as $nodeId) {
-            $obj = new NodeAuth();
-            $obj->node_id = $nodeId;
-            $obj->key = Str::random();
-            $obj->secret = Str::random(8);
-            $obj->save();
-        }
+        $nodes->each(static function ($node) {
+            $node->auth()->create(['key' => Str::random(), 'secret' => Str::random(8)]);
+        });
 
         return Response::json(['status' => 'success', 'message' => '生成成功']);
     }

+ 3 - 5
app/Http/Controllers/Admin/NodeController.php

@@ -68,12 +68,10 @@ class NodeController extends Controller
     // 添加节点
     public function store(NodeRequest $request): JsonResponse
     {
+        $array = $request->validated();
+        Arr::forget($array, ['labels']);
         try {
-            $array = $request->validated();
-            Arr::forget($array, ['labels']);
-            $node = Node::create($array);
-
-            if ($node) {
+            if ($node = Node::create($array)) {
                 // 生成节点标签
                 if ($request->has('labels')) {
                     $node->labels()->attach($request->input('labels'));

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

@@ -23,7 +23,7 @@ class RoleController extends Controller
     public function store(RoleRequest $request)
     {
         if ($role = Role::create($request->only(['name', 'description']))) {
-            $role->givePermissionTo($request->input('permissions') ?: []);
+            $role->givePermissionTo($request->input('permissions') ?? []);
 
             return redirect()->route('admin.role.edit', $role)->with('successMsg', '操作成功');
         }

+ 16 - 17
app/Http/Controllers/Admin/ShopController.php

@@ -48,25 +48,24 @@ class ShopController extends Controller
     // 添加商品
     public function store(ShopStoreRequest $request): RedirectResponse
     {
-        try {
-            $data = $request->validated();
-            if (array_key_exists('traffic_unit', $data)) {
-                $data['traffic'] *= $data['traffic_unit'];
-                Arr::forget($data, 'traffic_unit');
-            }
-            $data['is_hot'] = array_key_exists('is_hot', $data) ? 1 : 0;
-            $data['status'] = array_key_exists('status', $data) ? 1 : 0;
+        $data = $request->validated();
+        if (array_key_exists('traffic_unit', $data)) {
+            $data['traffic'] *= $data['traffic_unit'];
+            Arr::forget($data, 'traffic_unit');
+        }
+        $data['is_hot'] = array_key_exists('is_hot', $data) ? 1 : 0;
+        $data['status'] = array_key_exists('status', $data) ? 1 : 0;
 
-            // 商品LOGO
-            if ($request->hasFile('logo')) {
-                $path = $this->fileUpload($request->file('logo'));
-                if (is_string($path)) {
-                    $data['logo'] = $path;
-                } else {
-                    return $path;
-                }
+        // 商品LOGO
+        if ($request->hasFile('logo')) {
+            $path = $this->fileUpload($request->file('logo'));
+            if (is_string($path)) {
+                $data['logo'] = $path;
+            } else {
+                return $path;
             }
-
+        }
+        try {
             if ($good = Goods::create($data)) {
                 return Redirect::route('admin.goods.edit', $good)->with('successMsg', '添加成功');
             }

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

@@ -44,7 +44,7 @@ class SystemController extends Controller
             }
             $file = $request->file('website_logo');
             $ret = $file->move('uploads/logo', $file->getClientOriginalName());
-            if ($ret && Config::find('website_logo')->update(['value' => 'uploads/logo/'.$file->getClientOriginalName()])) {
+            if ($ret && Config::findOrFail('website_logo')->update(['value' => 'uploads/logo/'.$file->getClientOriginalName()])) {
                 return redirect()->route('admin.system.index', '#other')->with('successMsg', '更新成功');
             }
         }

+ 29 - 41
app/Http/Controllers/Admin/UserController.php

@@ -137,22 +137,22 @@ class UserController extends Controller
     // 添加账号
     public function store(UserStoreRequest $request): JsonResponse
     {
+        $data = $request->validated();
+        Arr::forget($data, 'roles');
+        $data['password'] = $data['password'] ?? Str::random();
+        $data['port'] = $data['port'] ?? Helpers::getPort();
+        $data['passwd'] = $data['passwd'] ?? Str::random();
+        $data['vmess_id'] = $data['uuid'] ?? Str::uuid();
+        Arr::forget($data, 'uuid');
+        $data['transfer_enable'] *= GB;
+        $data['expired_at'] = $data['expired_at'] ?? date('Y-m-d', strtotime('+365 days'));
+        $data['remark'] = str_replace(['atob', 'eval'], '', $data['remark']);
+        $data['reg_ip'] = IP::getClientIp();
+        $data['reset_time'] = $data['reset_time'] > date('Y-m-d') ? $data['reset_time'] : null;
+        $user = User::create($data);
+
+        $roles = $request->input('roles');
         try {
-            $data = $request->validated();
-            Arr::forget($data, 'roles');
-            $data['password'] = $data['password'] ?? Str::random();
-            $data['port'] = $data['port'] ?? Helpers::getPort();
-            $data['passwd'] = $data['passwd'] ?? Str::random();
-            $data['vmess_id'] = $data['uuid'] ?? Str::uuid();
-            Arr::forget($data, 'uuid');
-            $data['transfer_enable'] *= GB;
-            $data['expired_at'] = $data['expired_at'] ?? date('Y-m-d', strtotime('+365 days'));
-            $data['remark'] = str_replace(['atob', 'eval'], '', $data['remark']);
-            $data['reg_ip'] = IP::getClientIp();
-            $data['reset_time'] = $data['reset_time'] > date('Y-m-d') ? $data['reset_time'] : null;
-            $user = User::create($data);
-
-            $roles = $request->input('roles');
             if ($roles && (Auth::getUser()->hasPermissionTo('give roles') || (in_array('Super Admin', $roles, true)
                         && Auth::getUser()->hasRole('Super Admin')) || Auth::getUser()->hasRole('Super Admin'))) {
                 $user->assignRole($roles);
@@ -193,20 +193,19 @@ class UserController extends Controller
     // 编辑账号
     public function update(UserUpdateRequest $request, User $user)
     {
+        $data = $request->validated();
+        Arr::forget($data, 'roles');
+        $data['passwd'] = $request->input('passwd') ?? Str::random();
+        $data['vmess_id'] = $data['uuid'] ?? Str::uuid();
+        Arr::forget($data, 'uuid');
+        $data['transfer_enable'] *= GB;
+        $data['enable'] = $data['status'] < 0 ? 0 : $data['enable'];
+        $data['expired_at'] = $data['expired_at'] ?? date('Y-m-d', strtotime('+365 days'));
+        $data['remark'] = str_replace(['atob', 'eval'], '', $data['remark']);
+
+        // 只有超级管理员才能赋予超级管理员
+        $roles = $request->input('roles');
         try {
-            $data = $request->validated();
-            Arr::forget($data, 'roles');
-            $data['passwd'] = $request->input('passwd') ?? Str::random();
-            $data['vmess_id'] = $data['uuid'] ?? Str::uuid();
-            Arr::forget($data, 'uuid');
-            $data['transfer_enable'] *= GB;
-            $data['enable'] = $data['status'] < 0 ? 0 : $data['enable'];
-            $data['expired_at'] = $data['expired_at'] ?? date('Y-m-d', strtotime('+365 days'));
-            $data['remark'] = str_replace(['atob', 'eval'], '', $data['remark']);
-
-            // 只有超级管理员才能赋予超级管理员
-            $roles = $request->input('roles');
-
             if ($roles && (Auth::getUser()->hasPermissionTo('give roles') || (in_array('Super Admin', $roles, true)
                         && Auth::getUser()->hasRole('Super Admin')) || Auth::getUser()->hasRole('Super Admin'))) {
                 $user->syncRoles($roles);
@@ -332,19 +331,8 @@ class UserController extends Controller
 
     public function exportProxyConfig(Request $request, User $user): JsonResponse
     {
-        $node = Node::find($request->input('id'));
-        if ($node->type === 1) {
-            if ($node->compatible) {
-                $proxyType = 'SS';
-            } else {
-                $proxyType = 'SSR';
-            }
-        } else {
-            $proxyType = 'V2Ray';
-        }
-
-        $data = $this->getUserNodeInfo($user->id, $node->id, $request->input('type') !== 'text' ? 0 : 1);
+        $server = Node::findOrFail($request->input('id'))->config($user); // 提取节点信息
 
-        return Response::json(['status' => 'success', 'data' => $data, 'title' => $proxyType]);
+        return Response::json(['status' => 'success', 'data' => $this->getUserNodeInfo($server, $request->input('type') !== 'text'), 'title' => $server['type']]);
     }
 }

+ 4 - 3
app/Http/Controllers/Api/Client/V1Controller.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Client;
 
 use App\Http\Controllers\Controller;
 use App\Models\Goods;
+use App\Models\User;
 use Illuminate\Http\Request;
 use Validator;
 
@@ -26,11 +27,11 @@ class V1Controller extends Controller
             return response()->json(['ret' => 0, 'msg' => $validator->errors()->all()], 422);
         }
 
-        if (! $token = auth()->attempt($validator->validated())) {
-            return response()->json(['ret' => 0, 'msg' => '登录信息错误'], 401);
+        if ($token = auth()->attempt($validator->validated())) {
+            return $this->createNewToken($token);
         }
 
-        return $this->createNewToken($token);
+        return response()->json(['ret' => 0, 'msg' => '登录信息错误'], 401);
     }
 
     protected function createNewToken($token)

+ 2 - 1
app/Http/Controllers/Api/WebApi/BaseController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\Api\WebApi;
 
 use App\Models\Node;
+use App\Models\User;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Response;
@@ -73,7 +74,7 @@ class BaseController
 
         $onlineCount = 0;
         foreach ($validator->validated() as $input) { // 处理节点在线IP数据
-            $formattedData[] = ['user_id' => $input['uid'], 'ip' => $input['ip'], 'created_at' => time()];
+            $formattedData[] = ['user_id' => $input['uid'], 'ip' => $input['ip'], 'port' => User::find($input['uid'])->port, 'created_at' => time()];
             $onlineCount++;
         }
 

+ 225 - 0
app/Http/Controllers/ClientController.php

@@ -0,0 +1,225 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Components\Client\Clash;
+use App\Components\Client\QuantumultX;
+use App\Components\Client\Surfboard;
+use App\Components\Client\Surge;
+use App\Components\Client\URLSchemes;
+use App\Models\User;
+use File;
+use Symfony\Component\Yaml\Yaml;
+
+class ClientController extends Controller
+{
+    public function config(string $target, User $user, array $servers)
+    {
+        if (strpos($target, 'quantumult%20x') !== false) {
+            return $this->quantumultX($user, $servers);
+        }
+        if (strpos($target, 'quantumult') !== false) {
+            return $this->quantumult($user, $servers);
+        }
+        if (strpos($target, 'clash') !== false) {
+            return $this->clash($servers);
+        }
+        if (strpos($target, 'surfboard') !== false) {
+            return $this->surfboard($user, $servers);
+        }
+        if (strpos($target, 'surge') !== false) {
+            return $this->surge($user, $servers);
+        }
+        if (strpos($target, 'shadowrocket') !== false) {
+            return $this->shadowrocket($user, $servers);
+        }
+//            if (strpos($target, 'shadowsocks') !== false) {
+//                exit($this->shaodowsocksSIP008($servers));
+//            }
+        return $this->origin($servers);
+    }
+
+    private function quantumultX(User $user, array $servers = []): string
+    {
+        $uri = '';
+        if (sysConfig('is_custom_subscribe')) {
+            header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}");
+        }
+        foreach ($servers as $server) {
+            if ($server['type'] === 'shadowsocks') {
+                $uri .= QuantumultX::buildShadowsocks($server);
+            }
+            if ($server['type'] === 'shadowsocksr') {
+                $uri .= QuantumultX::buildShadowsocksr($server);
+            }
+            if ($server['type'] === 'v2ray') {
+                $uri .= QuantumultX::buildVmess($server);
+            }
+            if ($server['type'] === 'trojan') {
+                $uri .= QuantumultX::buildTrojan($server);
+            }
+        }
+
+        return base64_encode($uri);
+    }
+
+    private function quantumult(User $user, array $servers = []): string
+    {
+        if (sysConfig('is_custom_subscribe')) {
+            header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}");
+        }
+
+        return $this->origin($servers);
+    }
+
+    private function origin(array $servers = [], bool $encode = true): string
+    {
+        $uri = '';
+        foreach ($servers as $server) {
+            if ($server['type'] === 'shadowsocks') {
+                $uri .= URLSchemes::buildShadowsocks($server);
+            }
+            if ($server['type'] === 'shadowsocksr') {
+                $uri .= URLSchemes::buildShadowsocksr($server);
+            }
+            if ($server['type'] === 'v2ray') {
+                $uri .= URLSchemes::buildVmess($server);
+            }
+            if ($server['type'] === 'trojan') {
+                $uri .= URLSchemes::buildTrojan($server);
+            }
+        }
+
+        return $encode ? base64_encode($uri) : $uri;
+    }
+
+    private function clash($servers)
+    {
+        $defaultConfig = base_path().'/resources/rules/default.clash.yaml';
+        $customConfig = base_path().'/resources/rules/custom.clash.yaml';
+        if (File::exists($customConfig)) {
+            $config = Yaml::parseFile($customConfig);
+        } else {
+            $config = Yaml::parseFile($defaultConfig);
+        }
+
+        foreach ($servers as $server) {
+            if ($server['type'] === 'shadowsocks') {
+                $proxy[] = Clash::buildShadowsocks($server);
+                $proxies[] = $server['name'];
+            }
+            if ($server['type'] === 'shadowsocksr') {
+                $proxy[] = Clash::buildShadowsocksr($server);
+                $proxies[] = $server['name'];
+            }
+            if ($server['type'] === 'v2ray') {
+                $proxy[] = Clash::buildVmess($server);
+                $proxies[] = $server['name'];
+            }
+            if ($server['type'] === 'trojan') {
+                $proxy[] = Clash::buildTrojan($server);
+                $proxies[] = $server['name'];
+            }
+        }
+
+        $config['proxies'] = array_merge($config['proxies'] ?: [], $proxy ?? []);
+        foreach ($config['proxy-groups'] as $k => $v) {
+            if (! is_array($config['proxy-groups'][$k]['proxies'])) {
+                continue;
+            }
+            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies ?? []);
+        }
+
+        return str_replace('$app_name', sysConfig('website_name'), Yaml::dump($config));
+    }
+
+    private function surfboard(User $user, array $servers = [])
+    {
+        $proxies = '';
+        $proxyGroup = '';
+
+        foreach ($servers as $server) {
+            if ($server['type'] === 'shadowsocks') {
+                $proxies .= Surfboard::buildShadowsocks($server);
+                $proxyGroup .= $server['name'].', ';
+            }
+            if ($server['type'] === 'v2ray') {
+                $proxies .= Surfboard::buildVmess($server);
+                $proxyGroup .= $server['name'].', ';
+            }
+        }
+
+        $defaultConfig = base_path().'/resources/rules/default.surfboard.conf';
+        $customConfig = base_path().'/resources/rules/custom.surfboard.conf';
+        if (File::exists($customConfig)) {
+            $config = file_get_contents($customConfig);
+        } else {
+            $config = file_get_contents($defaultConfig);
+        }
+
+        // Subscription link
+        $subsURL = route('sub', $user->subscribe->code);
+
+        return str_replace(['$subs_link', '$proxies', '$proxy_group'], [$subsURL, $proxies, rtrim($proxyGroup, ', ')], $config);
+    }
+
+    private function surge(User $user, array $servers = [])
+    {
+        $proxies = '';
+        $proxyGroup = '';
+
+        foreach ($servers as $server) {
+            if ($server['type'] === 'shadowsocks') {
+                $proxies .= Surge::buildShadowsocks($server);
+                $proxyGroup .= $server['name'].', ';
+            }
+            if ($server['type'] === 'v2ray') {
+                $proxies .= Surge::buildVmess($server);
+                $proxyGroup .= $server['name'].', ';
+            }
+            if ($server['type'] === 'trojan') {
+                $proxies .= Surge::buildTrojan($server);
+                $proxyGroup .= $server['name'].', ';
+            }
+        }
+
+        $defaultConfig = base_path().'/resources/rules/default.surge.conf';
+        $customConfig = base_path().'/resources/rules/custom.surge.conf';
+        if (File::exists($customConfig)) {
+            $config = file_get_contents($customConfig);
+        } else {
+            $config = file_get_contents($defaultConfig);
+        }
+
+        // Subscription link
+        $subsURL = route('sub', $user->subscribe->code);
+
+        return str_replace(['$subs_link', '$proxies', '$proxy_group'], [$subsURL, $proxies, rtrim($proxyGroup, ', ')], $config);
+    }
+
+    private function shadowrocket(User $user, array $servers = []): string
+    {
+        //display remaining traffic and expire date
+        $uri = '';
+        if (sysConfig('is_custom_subscribe')) {
+            $upload = flowAutoShow($user->u);
+            $download = flowAutoShow($user->d);
+            $totalTraffic = flowAutoShow($user->transfer_enable);
+            $uri = "STATUS=📤:{$upload}📥:{$download}⏳:{$totalTraffic}📅:{$user->expired_at}\r\n";
+        }
+        $uri .= $this->origin($servers, false);
+
+        return base64_encode($uri);
+    }
+
+    private function shaodowsocksSIP008(array $servers = []): string
+    {
+        foreach ($servers as $server) {
+            if ($server['type'] === 'shadowsocks') {
+                $configs[] = URLSchemes::buildShadowsocksSIP008($server);
+            }
+        }
+
+        return json_encode(['version' => 1, 'remark' => sysConfig('website_name'), 'servers' => $configs ?? []], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+    }
+}

+ 15 - 98
app/Http/Controllers/Controller.php

@@ -2,11 +2,10 @@
 
 namespace App\Http\Controllers;
 
-use App\Models\EmailFilter;
-use App\Models\Node;
+use App\Components\Client\Text;
+use App\Components\Client\URLSchemes;
 use App\Models\NodeDailyDataFlow;
 use App\Models\NodeHourlyDataFlow;
-use App\Models\User;
 use App\Models\UserDailyDataFlow;
 use App\Models\UserDataFlowLog;
 use App\Models\UserHourlyDataFlow;
@@ -105,108 +104,26 @@ class Controller extends BaseController
         return '';
     }
 
-    /**
-     * 节点信息.
-     *
-     * @param  int  $uid  用户ID
-     * @param  int  $nodeId  节点ID
-     * @param  int  $infoType  信息类型:0为链接,1为文字
-     *
-     * @return string
-     */
-    public function getUserNodeInfo(int $uid, int $nodeId, int $infoType): string
+    // 节点信息
+    public function getUserNodeInfo(array $server, bool $is_url): ?string
     {
-        $user = User::find($uid);
-        $node = Node::find($nodeId);
-        $scheme = null;
-        $group = sysConfig('website_name'); // 分组名称
-        $host = $node->is_relay ? $node->relay_server : ($node->server ?: $node->ip);
-        $data = null;
-        switch ($node->type) {
-            case 2:
-                // 生成v2ray scheme
-                if ($infoType !== 1) {
-                    // 生成v2ray scheme
-                    $data = $this->v2raySubUrl(
-                        $node->name,
-                        $host,
-                        $node->v2_port,
-                        $user->vmess_id,
-                        $node->v2_alter_id,
-                        $node->v2_net,
-                        $node->v2_type,
-                        $node->v2_host,
-                        $node->v2_path,
-                        $node->v2_tls ? 'tls' : ''
-                    );
-                } else {
-                    $data = '服务器:'.$host.PHP_EOL.'IPv6:'.($node->ipv6 ?: '').PHP_EOL.'端口:'.$node->v2_port.PHP_EOL.'加密方式:'.$node->v2_method.PHP_EOL.'用户ID:'.$user->vmess_id.PHP_EOL.'额外ID:'.$node->v2_alter_id.PHP_EOL.'传输协议:'.$node->v2_net.PHP_EOL.'伪装类型:'.$node->v2_type.PHP_EOL.'伪装域名:'.($node->v2_host ?: '').PHP_EOL.'路径:'.($node->v2_path ?: '').PHP_EOL.'TLS:'.($node->v2_tls ? 'tls' : '').PHP_EOL;
-                }
+        switch ($server['type']) {
+            case'shadowsocks':
+                $data = $is_url ? URLSchemes::buildShadowsocks($server) : Text::buildShadowsocks($server);
+                break;
+            case 'shadowsocksr':
+                $data = $is_url ? URLSchemes::buildShadowsocksr($server) : Text::buildShadowsocksr($server);
                 break;
-            case 3:
-                if ($infoType !== 1) {
-                    $data = $this->trojanSubUrl($user->passwd, $host, $node->port, $node->name);
-                } else {
-                    $data = '备注:'.$node->name.PHP_EOL.'服务器:'.$host.PHP_EOL.'密码:'.$user->passwd.PHP_EOL.'端口:'.$node->port.PHP_EOL;
-                }
+            case 'v2ray':
+                $data = $is_url ? URLSchemes::buildVmess($server) : Text::buildVmess($server);
                 break;
-            case 1:
-            case 4:
-                $protocol = $node->protocol;
-                $method = $node->method;
-                $obfs = $node->obfs;
-                if ($node->single) {
-                    //单端口使用中转的端口
-                    $port = $node->is_relay ? $node->relay_port : $node->port;
-                    $passwd = $node->passwd;
-                    $protocol_param = $user->port.':'.$user->passwd;
-                } else {
-                    $port = $user->port;
-                    $passwd = $user->passwd;
-                    $protocol_param = $node->protocol_param;
-                    if ($node->type === 1) {
-                        $protocol = $user->protocol;
-                        $method = $user->method;
-                        $obfs = $user->obfs;
-                    }
-                }
-
-                if ($infoType !== 1) {
-                    // 生成ss/ssr scheme
-                    $data = $node->compatible ? $this->ssSubUrl($host, $port, $method, $passwd, $group) :
-                        $this->ssrSubUrl($host, $port, $protocol, $method, $obfs, $passwd, $node->obfs_param, $protocol_param, $node->name, $group, $node->is_udp);
-                } else {
-                    // 生成文本配置信息
-                    $data = '服务器:'.$host.PHP_EOL.'IPv6:'.$node->ipv6.PHP_EOL.'服务器端口:'.$port.PHP_EOL.'密码:'.$passwd.PHP_EOL.'加密:'.$method.PHP_EOL.($node->compatible ? '' : '协议:'.$protocol.PHP_EOL.'协议参数:'.$protocol_param.PHP_EOL.'混淆:'.$obfs.PHP_EOL.'混淆参数:'.$node->obfs_param.PHP_EOL);
-                }
+            case 'trojan':
+                $data = $is_url ? URLSchemes::buildTrojan($server) : Text::buildTrojan($server);
                 break;
             default:
         }
 
-        return $data;
-    }
-
-    public function v2raySubUrl($name, $host, $port, $uuid, $alter_id, $net, $type, $domain, $path, $tls): string
-    {
-        return 'vmess://'.base64url_encode(json_encode([
-            'v' => '2', 'ps' => $name, 'add' => $host, 'port' => $port, 'id' => $uuid, 'aid' => $alter_id, 'net' => $net,
-            'type' => $type, 'host' => $domain, 'path' => $path, 'tls' => $tls ? 'tls' : '',
-        ], JSON_PRETTY_PRINT));
-    }
-
-    public function trojanSubUrl($password, $domain, $port, $remark): string
-    {
-        return 'trojan://'.urlencode($password).'@'.$domain.':'.$port.'#'.urlencode($remark);
-    }
-
-    public function ssSubUrl($host, $port, $method, $passwd, $group): string
-    {
-        return 'ss://'.base64url_encode($method.':'.$passwd.'@'.$host.':'.$port).'#'.$group;
-    }
-
-    public function ssrSubUrl($host, $port, $protocol, $method, $obfs, $passwd, $obfs_param, $protocol_param, $name, $group, $is_udp): string
-    {
-        return 'ssr://'.base64url_encode($host.':'.$port.':'.$protocol.':'.$method.':'.$obfs.':'.base64url_encode($passwd).'/?obfsparam='.base64url_encode($obfs_param).'&protoparam='.base64url_encode($protocol_param).'&remarks='.base64url_encode($name).'&group='.base64url_encode($group).'&udpport='.$is_udp.'&uot=0');
+        return $data ?? null;
     }
 
     // 流量使用图表

+ 3 - 250
app/Http/Controllers/User/SubscribeController.php

@@ -2,23 +2,15 @@
 
 namespace App\Http\Controllers\User;
 
-use App\Components\Client\Clash;
-use App\Components\Client\QuantumultX;
-use App\Components\Client\Shadowrocket;
-use App\Components\Client\Surfboard;
-use App\Components\Client\Surge;
-use App\Components\Client\URLSchemes;
 use App\Components\IP;
+use App\Http\Controllers\ClientController;
 use App\Http\Controllers\Controller;
-use App\Models\User;
 use App\Models\UserSubscribe;
 use App\Models\UserSubscribeLog;
 use Arr;
-use File;
 use Illuminate\Http\Request;
 use Redirect;
 use Response;
-use Symfony\Component\Yaml\Yaml;
 
 class SubscribeController extends Controller
 {
@@ -31,7 +23,7 @@ class SubscribeController extends Controller
             return Redirect::route('login');
         }
         $this->subType = $request->input('type');
-        $target = strtolower($request->input('target') ?? (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''));
+        $target = strtolower($request->input('target') ?? ($request->userAgent() ?? ''));
 
         // 检查订阅码是否有效
         $subscribe = UserSubscribe::whereCode($code)->first();
@@ -104,34 +96,9 @@ class SubscribeController extends Controller
             $servers = array_slice($servers, 0, (int) sysConfig('subscribe_max'));
         }
 
-        if ($target) {
-            if (strpos($target, 'quantumult%20x') !== false) {
-                exit($this->quantumultX($user, $servers));
-            }
-            if (strpos($target, 'quantumult') !== false) {
-                exit($this->quantumult($user, $servers));
-            }
-            if (strpos($target, 'clash') !== false) {
-                exit($this->clash($servers));
-            }
-            if (strpos($target, 'surfboard') !== false) {
-                exit($this->surfboard($user, $servers));
-            }
-            if (strpos($target, 'surge') !== false) {
-                exit($this->surge($user, $servers));
-            }
-            if (strpos($target, 'shadowrocket') !== false) {
-                exit($this->shadowrocket($user, $servers));
-            }
-//            if (strpos($target, 'shadowsocks') !== false) {
-//                exit($this->shaodowsocksSIP008($servers));
-//            }
-        }
-        exit($this->origin($servers));
+        return (new ClientController)->config($target, $user, $servers);
     }
 
-    // TODO 通过Token获取订阅信息
-
     // 抛出错误的节点信息,用于兼容防止客户端订阅失败
     private function failed($text)
     {
@@ -171,218 +138,4 @@ class SubscribeController extends Controller
         $log->request_header = $headers;
         $log->save();
     }
-
-    private function quantumultX(User $user, array $servers = []): string
-    {
-        $uri = '';
-        if (sysConfig('is_custom_subscribe')) {
-            header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}");
-        }
-        foreach ($servers as $server) {
-            if ($server['type'] === 'shadowsocks') {
-                $uri .= QuantumultX::buildShadowsocks($server);
-            }
-            if ($server['type'] === 'shadowsocksr') {
-                $uri .= QuantumultX::buildShadowsocksr($server);
-            }
-            if ($server['type'] === 'v2ray') {
-                $uri .= QuantumultX::buildVmess($server);
-            }
-            if ($server['type'] === 'trojan') {
-                $uri .= QuantumultX::buildTrojan($server);
-            }
-        }
-
-        return base64_encode($uri);
-    }
-
-    private function quantumult(User $user, array $servers = []): string
-    {
-        if (sysConfig('is_custom_subscribe')) {
-            header('subscription-userinfo: upload='.$user->u.'; download='.$user->d.';total='.$user->transfer_enable.'; expire='.strtotime($user->expired_at));
-        }
-
-        return $this->origin($servers);
-    }
-
-    private function origin(array $servers = [], bool $encode = true): string
-    {
-        $uri = '';
-        foreach ($servers as $server) {
-            if ($server['type'] === 'shadowsocks') {
-                $uri .= URLSchemes::buildShadowsocks($server);
-            }
-            if ($server['type'] === 'shadowsocksr') {
-                $uri .= URLSchemes::buildShadowsocksr($server);
-            }
-            if ($server['type'] === 'v2ray') {
-                $uri .= URLSchemes::buildVmess($server);
-            }
-            if ($server['type'] === 'trojan') {
-                $uri .= URLSchemes::buildTrojan($server);
-            }
-        }
-
-        return $encode ? base64_encode($uri) : $uri;
-    }
-
-    private function clash($servers)
-    {
-        $defaultConfig = base_path().'/resources/rules/default.clash.yaml';
-        $customConfig = base_path().'/resources/rules/custom.clash.yaml';
-        if (File::exists($customConfig)) {
-            $config = Yaml::parseFile($customConfig);
-        } else {
-            $config = Yaml::parseFile($defaultConfig);
-        }
-        $proxy = [];
-        $proxies = [];
-
-        foreach ($servers as $server) {
-            if ($server['type'] === 'shadowsocks') {
-                $proxy[] = Clash::buildShadowsocks($server);
-                $proxies[] = $server['name'];
-            }
-            if ($server['type'] === 'shadowsocksr') {
-                $proxy[] = Clash::buildShadowsocksr($server);
-                $proxies[] = $server['name'];
-            }
-            if ($server['type'] === 'v2ray') {
-                $proxy[] = Clash::buildVmess($server);
-                $proxies[] = $server['name'];
-            }
-            if ($server['type'] === 'trojan') {
-                $proxy[] = Clash::buildTrojan($server);
-                $proxies[] = $server['name'];
-            }
-        }
-
-        $config['proxies'] = array_merge($config['proxies'] ?: [], $proxy);
-        foreach ($config['proxy-groups'] as $k => $v) {
-            if (! is_array($config['proxy-groups'][$k]['proxies'])) {
-                continue;
-            }
-            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
-        }
-        $yaml = Yaml::dump($config);
-        $yaml = str_replace('$app_name', sysConfig('website_name'), $yaml);
-
-        return $yaml;
-    }
-
-    private function surfboard(User $user, array $servers = [])
-    {
-        $proxies = '';
-        $proxyGroup = '';
-
-        foreach ($servers as $server) {
-            if ($server['type'] === 'shadowsocks') {
-                // [Proxy]
-                $proxies .= Surfboard::buildShadowsocks($server);
-                // [Proxy Group]
-                $proxyGroup .= $server['name'].', ';
-            }
-            if ($server['type'] === 'v2ray') {
-                // [Proxy]
-                $proxies .= Surfboard::buildVmess($server);
-                // [Proxy Group]
-                $proxyGroup .= $server['name'].', ';
-            }
-        }
-
-        $defaultConfig = base_path().'/resources/rules/default.surfboard.conf';
-        $customConfig = base_path().'/resources/rules/custom.surfboard.conf';
-        if (File::exists($customConfig)) {
-            $config = file_get_contents("$customConfig");
-        } else {
-            $config = file_get_contents("$defaultConfig");
-        }
-
-        // Subscription link
-        $subsURL = route('sub', $user->subscribe->code);
-
-        $config = str_replace('$subs_link', $subsURL, $config);
-        $config = str_replace('$proxies', $proxies, $config);
-        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
-
-        return $config;
-    }
-
-    private function surge(User $user, array $servers = [])
-    {
-        $proxies = '';
-        $proxyGroup = '';
-
-        foreach ($servers as $server) {
-            if ($server['type'] === 'shadowsocks') {
-                // [Proxy]
-                $proxies .= Surge::buildShadowsocks($server);
-                // [Proxy Group]
-                $proxyGroup .= $server['name'].', ';
-            }
-            if ($server['type'] === 'v2ray') {
-                // [Proxy]
-                $proxies .= Surge::buildVmess($server);
-                // [Proxy Group]
-                $proxyGroup .= $server['name'].', ';
-            }
-            if ($server['type'] === 'trojan') {
-                // [Proxy]
-                $proxies .= Surge::buildTrojan($server);
-                // [Proxy Group]
-                $proxyGroup .= $server['name'].', ';
-            }
-        }
-
-        $defaultConfig = base_path().'/resources/rules/default.surge.conf';
-        $customConfig = base_path().'/resources/rules/custom.surge.conf';
-        if (File::exists($customConfig)) {
-            $config = file_get_contents("$customConfig");
-        } else {
-            $config = file_get_contents("$defaultConfig");
-        }
-
-        // Subscription link
-        $subsURL = route('sub', $user->subscribe->code);
-
-        $config = str_replace('$subs_link', $subsURL, $config);
-        $config = str_replace('$proxies', $proxies, $config);
-        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
-
-        return $config;
-    }
-
-    private function shadowrocket(User $user, array $servers = []): string
-    {
-        //display remaining traffic and expire date
-        $uri = '';
-        if (sysConfig('is_custom_subscribe')) {
-            $upload = flowAutoShow($user->u);
-            $download = flowAutoShow($user->d);
-            $totalTraffic = flowAutoShow($user->transfer_enable);
-            $uri = "STATUS=📤:{$upload}📥:{$download}⏳:{$totalTraffic}📅:{$user->expired_at}\r\n";
-        }
-        $uri .= $this->origin($servers, false);
-
-        return base64_encode($uri);
-    }
-
-    private function shaodowsocksSIP008(array $servers = []): string
-    {
-        $configs = [];
-        $subs = [];
-        $subs['servers'] = [];
-
-        foreach ($servers as $server) {
-            if ($server['type'] === 'shadowsocks') {
-                $configs[] = URLSchemes::buildShadowsocksSIP008($server);
-            }
-        }
-
-        $subs['version'] = 1;
-        $subs['remark'] = sysConfig('website_name');
-        $subs['servers'] = array_merge($subs['servers'] ?: [], $configs);
-
-        return json_encode($subs, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
-    }
 }

+ 2 - 11
app/Http/Controllers/UserController.php

@@ -110,18 +110,9 @@ class UserController extends Controller
     {
         $user = auth()->user();
         if ($request->isMethod('POST')) {
-            $infoType = $request->input('type');
-
-            $node = Node::find($request->input('id'));
-            // 生成节点信息
-            if ($node->type === 1) {
-                $proxyType = $node->compatible ? 'SS' : 'SSR';
-            } else {
-                $proxyType = 'V2Ray';
-            }
-            $data = $this->getUserNodeInfo($user->id, $node->id, $infoType !== 'text' ? 0 : 1);
+            $server = Node::findOrFail($request->input('id'))->config($user); // 提取节点信息
 
-            return Response::json(['status' => 'success', 'data' => $data, 'title' => $proxyType]);
+            return Response::json(['status' => 'success', 'data' => $this->getUserNodeInfo($server, $request->input('type') !== 'text'), 'title' => $server['type']]);
         }
 
         // 获取当前用户可用节点

+ 5 - 0
app/Models/Country.php

@@ -15,4 +15,9 @@ class Country extends Model
     protected $primaryKey = 'code';
     protected $keyType = 'string';
     protected $guarded = [];
+
+    public function nodes()
+    {
+        return $this->hasMany(Node::class, 'country_code');
+    }
 }

+ 5 - 0
app/Models/Level.php

@@ -12,4 +12,9 @@ class Level extends Model
     public $timestamps = false;
     protected $table = 'level';
     protected $guarded = [];
+
+    public function users()
+    {
+        return $this->hasMany(User::class, 'level');
+    }
 }

+ 1 - 1
app/Models/Node.php

@@ -90,7 +90,7 @@ class Node extends Model
             ->get();
     }
 
-    public function config($user)
+    public function config(User $user)
     {
         $config = [
             'id' => $this->id,

+ 4 - 4
app/Models/ReferralLog.php

@@ -38,14 +38,14 @@ class ReferralLog extends Model
         return $value / 100;
     }
 
-    public function setAmountAttribute($value): void
+    public function getCommissionAttribute($value)
     {
-        $this->attributes['amount'] = $value * 100;
+        return $value / 100;
     }
 
-    public function getCommissionAttribute($value)
+    public function setAmountAttribute($value): void
     {
-        return $value / 100;
+        $this->attributes['amount'] = $value * 100;
     }
 
     public function setCommissionAttribute($value): void

+ 1 - 1
config/version.php

@@ -2,5 +2,5 @@
 
 return [
     'name' => 'ProxyPanel',
-    'number' => '2.6.a',
+    'number' => '2.6.b',
 ];

+ 2 - 2
resources/views/admin/config/config.blade.php

@@ -392,7 +392,7 @@
           $.ajax({
             url: '{{route('admin.config.level.store')}}',
             method: 'POST',
-            data: {_token: '{{csrf_token()}}', level: level, level_name: level_name},
+            data: {_token: '{{csrf_token()}}', level: level, name: level_name},
             beforeSend: function() {
               $('#level_msg').show().html('正在添加');
             },
@@ -426,7 +426,7 @@
             data: {
               _token: '{{csrf_token()}}',
               level: $('#level_' + id).val(),
-              level_name: $('#level_name_' + id).val(),
+              name: $('#level_name_' + id).val(),
             },
             dataType: 'json',
             success: function(ret) {

+ 7 - 7
resources/views/admin/config/emailFilter.blade.php

@@ -27,14 +27,14 @@
                     </tr>
                     </thead>
                     <tbody>
-                    @foreach($list as $vo)
+                    @foreach($filters as $filter)
                         <tr>
-                            <td> {{$vo->id}} </td>
-                            <td> {{$vo->type==1? '黑':'白'}} </td>
-                            <td> {{$vo->words}} </td>
+                            <td> {{$filter->id}} </td>
+                            <td> {{$filter->type === 1? '黑':'白'}} </td>
+                            <td> {{$filter->words}} </td>
                             <td>
                                 @can('admin.config.filter.destroy')
-                                    <button class="btn btn-danger" onclick="delSuffix('{{$vo->id}}','{{$vo->words}}')">
+                                    <button class="btn btn-danger" onclick="delSuffix('{{$filter->id}}','{{$filter->words}}')">
                                         <i class="icon wb-trash"></i>
                                     </button>
                                 @endcan
@@ -47,11 +47,11 @@
             <div class="panel-footer">
                 <div class="row">
                     <div class="col-sm-4">
-                        共 <code>{{$list->total()}}</code> 条记录
+                        共 <code>{{$filters->total()}}</code> 条记录
                     </div>
                     <div class="col-sm-8">
                         <nav class="Page navigation float-right">
-                            {{$list->links()}}
+                            {{$filters->links()}}
                         </nav>
                     </div>
                 </div>

+ 0 - 2
resources/views/admin/node/auth.blade.php

@@ -19,7 +19,6 @@
                 <table class="text-md-center" data-toggle="table" data-mobile-responsive="true">
                     <thead class="thead-default">
                     <tr>
-                        <th> #</th>
                         <th> 节点ID</th>
                         <th> 节点类型</th>
                         <th> 节点名称</th>
@@ -33,7 +32,6 @@
                     <tbody>
                     @foreach ($authorizations as $auth)
                         <tr>
-                            <td> {{$auth->id}} </td>
                             <td> {{$auth->node_id}} </td>
                             <td> {{$auth->node->type_label}} </td>
                             <td> {{Str::limit($auth->node->name, 20) ?? '【节点已删除】'}} </td>