Explorar el Código

Reformat Code to follow in PSR-12

兔姬桑 hace 4 años
padre
commit
b037d9c4f2
Se han modificado 100 ficheros con 11624 adiciones y 8604 borrados
  1. 1 0
      .gitignore
  2. 29 25
      app/Components/CaptchaVerify.php
  3. 304 249
      app/Components/Helpers.php
  4. 17 12
      app/Components/IPIP.php
  5. 124 86
      app/Components/Namesilo.php
  6. 94 76
      app/Components/NetworkDetection.php
  7. 96 62
      app/Components/PushNotification.php
  8. 53 45
      app/Components/QQInfo.php
  9. 16 12
      app/Components/QQWry.php
  10. 105 61
      app/Console/Commands/AutoClearLog.php
  11. 266 216
      app/Console/Commands/AutoJob.php
  12. 43 34
      app/Console/Commands/AutoPingNode.php
  13. 50 32
      app/Console/Commands/AutoReportNode.php
  14. 44 34
      app/Console/Commands/AutoStatisticsNodeDailyTraffic.php
  15. 44 34
      app/Console/Commands/AutoStatisticsNodeHourlyTraffic.php
  16. 55 45
      app/Console/Commands/AutoStatisticsUserDailyTraffic.php
  17. 55 45
      app/Console/Commands/AutoStatisticsUserHourlyTraffic.php
  18. 183 128
      app/Console/Commands/DailyJob.php
  19. 133 97
      app/Console/Commands/NodeBlockedDetection.php
  20. 56 36
      app/Console/Commands/ServiceTimer.php
  21. 65 44
      app/Console/Commands/UserExpireAutoWarning.php
  22. 65 40
      app/Console/Commands/UserTrafficAbnormalAutoWarning.php
  23. 53 34
      app/Console/Commands/UserTrafficAutoWarning.php
  24. 130 77
      app/Console/Commands/fixDailyTrafficLogError.php
  25. 29 21
      app/Console/Commands/updateCoupon.php
  26. 43 26
      app/Console/Commands/updateTextToJson.php
  27. 37 21
      app/Console/Commands/updateTicket.php
  28. 40 24
      app/Console/Commands/updateUserLevel.php
  29. 59 46
      app/Console/Commands/updateUserName.php
  30. 66 47
      app/Console/Commands/upgradeUserResetTime.php
  31. 65 60
      app/Console/Kernel.php
  32. 175 113
      app/Exceptions/Handler.php
  33. 116 84
      app/Http/Controllers/Admin/AffiliateController.php
  34. 230 170
      app/Http/Controllers/Admin/CouponController.php
  35. 56 41
      app/Http/Controllers/Admin/EmailFilterController.php
  36. 102 83
      app/Http/Controllers/Admin/MarketingController.php
  37. 143 111
      app/Http/Controllers/Admin/RuleController.php
  38. 147 123
      app/Http/Controllers/Admin/RuleGroupController.php
  39. 180 164
      app/Http/Controllers/Admin/ShopController.php
  40. 75 58
      app/Http/Controllers/Admin/SubscribeController.php
  41. 168 125
      app/Http/Controllers/Admin/TicketController.php
  42. 256 221
      app/Http/Controllers/Admin/ToolsController.php
  43. 92 69
      app/Http/Controllers/Admin/UserGroupController.php
  44. 1913 1462
      app/Http/Controllers/AdminController.php
  45. 190 156
      app/Http/Controllers/Api/WebApi/BaseController.php
  46. 49 33
      app/Http/Controllers/Api/WebApi/TrojanController.php
  47. 83 62
      app/Http/Controllers/Api/WebApi/V2RayController.php
  48. 65 49
      app/Http/Controllers/Api/WebApi/VNetController.php
  49. 995 751
      app/Http/Controllers/AuthController.php
  50. 378 253
      app/Http/Controllers/Controller.php
  51. 66 47
      app/Http/Controllers/Gateway/AbstractPayment.php
  52. 107 74
      app/Http/Controllers/Gateway/BitpayX.php
  53. 55 36
      app/Http/Controllers/Gateway/CodePay.php
  54. 87 56
      app/Http/Controllers/Gateway/EPay.php
  55. 104 75
      app/Http/Controllers/Gateway/F2Fpay.php
  56. 30 18
      app/Http/Controllers/Gateway/Local.php
  57. 65 41
      app/Http/Controllers/Gateway/PayJs.php
  58. 173 122
      app/Http/Controllers/Gateway/PayPal.php
  59. 623 473
      app/Http/Controllers/NodeController.php
  60. 263 201
      app/Http/Controllers/PaymentController.php
  61. 99 57
      app/Http/Controllers/User/AffiliateController.php
  62. 189 144
      app/Http/Controllers/User/SubscribeController.php
  63. 819 558
      app/Http/Controllers/UserController.php
  64. 68 65
      app/Http/Kernel.php
  65. 20 16
      app/Http/Middleware/Affiliate.php
  66. 18 12
      app/Http/Middleware/Authenticate.php
  67. 11 8
      app/Http/Middleware/CheckForMaintenanceMode.php
  68. 11 8
      app/Http/Middleware/EncryptCookies.php
  69. 21 15
      app/Http/Middleware/RedirectIfAuthenticated.php
  70. 22 19
      app/Http/Middleware/SetLocale.php
  71. 13 10
      app/Http/Middleware/TrimStrings.php
  72. 15 11
      app/Http/Middleware/TrustHosts.php
  73. 16 13
      app/Http/Middleware/TrustProxies.php
  74. 12 9
      app/Http/Middleware/VerifyCsrfToken.php
  75. 50 43
      app/Http/Middleware/WebApi.php
  76. 19 15
      app/Http/Middleware/isAdmin.php
  77. 19 15
      app/Http/Middleware/isAdminlogin.php
  78. 108 72
      app/Http/Middleware/isForbidden.php
  79. 19 15
      app/Http/Middleware/isLogin.php
  80. 25 18
      app/Http/Middleware/isMaintenance.php
  81. 41 29
      app/Http/Middleware/isSecurity.php
  82. 51 37
      app/Jobs/VNet/addUser.php
  83. 44 30
      app/Jobs/VNet/delUser.php
  84. 46 32
      app/Jobs/VNet/editUser.php
  85. 70 51
      app/Jobs/VNet/reloadNode.php
  86. 30 21
      app/Mail/activeUser.php
  87. 35 24
      app/Mail/closeTicket.php
  88. 35 24
      app/Mail/newTicket.php
  89. 29 19
      app/Mail/nodeCrashWarning.php
  90. 35 24
      app/Mail/replyTicket.php
  91. 32 21
      app/Mail/resetPassword.php
  92. 32 21
      app/Mail/sendUserInfo.php
  93. 32 21
      app/Mail/sendVerifyCode.php
  94. 32 21
      app/Mail/userExpireWarning.php
  95. 22 13
      app/Mail/userExpireWarningToday.php
  96. 32 21
      app/Mail/userTrafficWarning.php
  97. 13 9
      app/Models/Article.php
  98. 10 7
      app/Models/Config.php
  99. 10 7
      app/Models/Country.php
  100. 13 9
      app/Models/Coupon.php

+ 1 - 0
.gitignore

@@ -21,3 +21,4 @@ yarn-error.log
 .phpunit.result.cache
 _ide_helper_models.php
 _ide_helper.php
+.php_cs.cache

+ 29 - 25
app/Components/CaptchaVerify.php

@@ -7,32 +7,36 @@ namespace App\Components;
  *
  * @package App\Components
  */
-class CaptchaVerify {
-	//从后台获取 hcaptcha_sitekey 和 hcaptcha_secret
-	public static function hCaptchaGetConfig(): array {
-		return [
-			"sitekey" => sysConfig('hcaptcha_sitekey'),
-			"secret"  => sysConfig('hcaptcha_secret'),
-			"options" => []
-		];
-	}
+class CaptchaVerify
+{
 
-	//从后台获取 Geetest_id 和 Geetest_key
-	public static function geetestCaptchaGetConfig(): array {
-		return [
-			"geetest_id"  => sysConfig('geetest_id'),
-			"geetest_key" => sysConfig('geetest_key')
-		];
-	}
+    //从后台获取 hcaptcha_sitekey 和 hcaptcha_secret
+    public static function hCaptchaGetConfig(): array
+    {
+        return [
+            "sitekey" => sysConfig('hcaptcha_sitekey'),
+            "secret"  => sysConfig('hcaptcha_secret'),
+            "options" => [],
+        ];
+    }
 
-	//从后台获取 google_captcha_sitekey 和 google_captcha_secret
-	public static function googleCaptchaGetConfig(): array {
-		return [
-			"sitekey" => sysConfig('google_captcha_sitekey'),
-			"secret"  => sysConfig('google_captcha_secret'),
-			"options" => []
-		];
-	}
-}
+    //从后台获取 Geetest_id 和 Geetest_key
+    public static function geetestCaptchaGetConfig(): array
+    {
+        return [
+            "geetest_id"  => sysConfig('geetest_id'),
+            "geetest_key" => sysConfig('geetest_key'),
+        ];
+    }
 
+    //从后台获取 google_captcha_sitekey 和 google_captcha_secret
+    public static function googleCaptchaGetConfig(): array
+    {
+        return [
+            "sitekey" => sysConfig('google_captcha_sitekey'),
+            "secret"  => sysConfig('google_captcha_secret'),
+            "options" => [],
+        ];
+    }
 
+}

+ 304 - 249
app/Components/Helpers.php

@@ -14,253 +14,308 @@ use Cache;
 use DateTime;
 use Str;
 
-class Helpers {
-	// 不生成的端口
-	private static $denyPorts = [
-		1068,
-		1109,
-		1434,
-		3127,
-		3128,
-		3129,
-		3130,
-		3332,
-		4444,
-		5554,
-		6669,
-		8080,
-		8081,
-		8082,
-		8181,
-		8282,
-		9996,
-		17185,
-		24554,
-		35601,
-		60177,
-		60179
-	];
-
-	// 加密方式
-	public static function methodList() {
-		return SsConfig::type(1)->get();
-	}
-
-	// 协议
-	public static function protocolList() {
-		return SsConfig::type(2)->get();
-	}
-
-	// 混淆
-	public static function obfsList() {
-		return SsConfig::type(3)->get();
-	}
-
-	// 生成用户的订阅码
-	public static function makeSubscribeCode(): string {
-		$code = Str::random();
-		if(UserSubscribe::whereCode($code)->exists()){
-			$code = self::makeSubscribeCode();
-		}
-
-		return $code;
-	}
-
-	/**
-	 * 添加用户
-	 *
-	 * @param  string    $email            用户邮箱
-	 * @param  string    $password         用户密码
-	 * @param  string    $transfer_enable  可用流量
-	 * @param  int       $data             可使用天数
-	 * @param  int|null  $inviter_id       邀请人
-	 *
-	 * @return int
-	 */
-	public static function addUser($email, $password, $transfer_enable, $data, $inviter_id = null): int {
-		$user = new User();
-		$user->username = $email;
-		$user->email = $email;
-		$user->password = $password;
-		// 生成一个可用端口
-		$user->port = self::getPort();
-		$user->passwd = Str::random();
-		$user->vmess_id = Str::uuid();
-		$user->enable = 1;
-		$user->method = self::getDefaultMethod();
-		$user->protocol = self::getDefaultProtocol();
-		$user->obfs = self::getDefaultObfs();
-		$user->transfer_enable = $transfer_enable;
-		$user->expired_at = date('Y-m-d', strtotime("+".$data." days"));
-		$user->reg_ip = getClientIp();
-		$user->inviter_id = $inviter_id;
-		$user->reset_time = null;
-		$user->status = 0;
-		$user->save();
-
-		return $user->id;
-	}
-
-	// 获取一个有效端口
-	public static function getPort(): int {
-		if(sysConfig('is_rand_port')){
-			$port = self::getRandPort();
-		}else{
-			$port = (int) sysConfig('min_port');
-			$exists_port = array_merge(User::where('port', '>=', $port)->pluck('port')->toArray(), self::$denyPorts);
-
-			while(in_array($port, $exists_port, true)){
-				++$port;
-			}
-		}
-		return $port;
-	}
-
-	// 获取一个随机端口
-	private static function getRandPort(): int {
-		$port = random_int(sysConfig('min_port'), sysConfig('max_port'));
-		$exists_port = array_merge(User::where('port', '<>', 0)->pluck('port')->toArray(), self::$denyPorts);
-
-		while(in_array($port, $exists_port, true)){
-			$port = random_int(sysConfig('min_port'), sysConfig('max_port'));
-		}
-
-		return $port;
-	}
-
-	// 获取默认加密方式
-	public static function getDefaultMethod(): string {
-		$config = SsConfig::default()->type(1)->first();
-
-		return $config? $config->name : 'aes-256-cfb';
-	}
-
-	// 获取默认协议
-	public static function getDefaultProtocol(): string {
-		$config = SsConfig::default()->type(2)->first();
-
-		return $config? $config->name : 'origin';
-	}
-
-	// 获取默认混淆
-	public static function getDefaultObfs(): string {
-		$config = SsConfig::default()->type(3)->first();
-
-		return $config? $config->name : 'plain';
-	}
-
-	// 获取系统配置
-	public static function cacheSysConfig($name) {
-		if($name === 'is_onlinePay'){
-			$value = !empty(array_filter(Cache::many([
-				'is_AliPay',
-				'is_QQPay',
-				'is_WeChatPay',
-				'is_otherPay'
-			])));
-			Cache::tags('sysConfig')->put('is_onlinePay', $value);
-		}else{
-			$value = Config::find($name)->value;
-			Cache::tags('sysConfig')->put($name, $value?: false);
-		}
-
-		return $value;
-	}
-
-	public static function daysToNow($date): int {
-		return (new DateTime())->diff(new DateTime($date))->days;
-	}
-
-	/**
-	 * 添加通知推送日志
-	 *
-	 * @param  string  $title    标题
-	 * @param  string  $content  内容
-	 * @param  int     $type     发送类型
-	 * @param  string  $address  收信方
-	 * @param  int     $status   投递状态
-	 * @param  string  $error    投递失败时记录的异常信息
-	 *
-	 * @return int
-	 */
-	public static function addNotificationLog($title, $content, $type, $address = 'admin', $status = 1, $error = ''
-	): int {
-		$log = new NotificationLog();
-		$log->type = $type;
-		$log->address = $address;
-		$log->title = $title;
-		$log->content = $content;
-		$log->status = $status;
-		$log->error = $error;
-		$log->save();
-
-		return $log->id;
-	}
-
-	/**
-	 * 添加优惠券操作日志
-	 *
-	 * @param  string  $description  备注
-	 * @param  int     $couponId     优惠券ID
-	 * @param  int     $goodsId      商品ID
-	 * @param  int     $orderId      订单ID
-	 *
-	 * @return boolean
-	 */
-	public static function addCouponLog($description, $couponId, $goodsId = 0, $orderId = 0): bool {
-		$log = new CouponLog();
-		$log->coupon_id = $couponId;
-		$log->goods_id = $goodsId;
-		$log->order_id = $orderId;
-		$log->description = $description;
-
-		return $log->save();
-	}
-
-	/**
-	 * 记录余额操作日志
-	 *
-	 * @param  int     $userId       用户ID
-	 * @param  int     $orderId      订单ID
-	 * @param  int     $before       记录前余额
-	 * @param  int     $after        记录后余额
-	 * @param  int     $amount       发生金额
-	 * @param  string  $description  描述
-	 *
-	 * @return boolean
-	 */
-	public static function addUserCreditLog($userId, $orderId, $before, $after, $amount, $description = ''): bool {
-		$log = new UserCreditLog();
-		$log->user_id = $userId;
-		$log->order_id = $orderId;
-		$log->before = $before;
-		$log->after = $after;
-		$log->amount = $amount;
-		$log->description = $description;
-		$log->created_at = date('Y-m-d H:i:s');
-
-		return $log->save();
-	}
-
-	/**
-	 * 记录流量变动日志
-	 *
-	 * @param  int     $userId       用户ID
-	 * @param  int     $orderId      订单ID
-	 * @param  int     $before       记录前的值
-	 * @param  int     $after        记录后的值
-	 * @param  string  $description  描述
-	 *
-	 * @return bool
-	 */
-	public static function addUserTrafficModifyLog($userId, $orderId, $before, $after, $description = ''): bool {
-		$log = new UserDataModifyLog();
-		$log->user_id = $userId;
-		$log->order_id = $orderId;
-		$log->before = $before;
-		$log->after = $after;
-		$log->description = $description;
-
-		return $log->save();
-	}
+class Helpers
+{
+
+    // 不生成的端口
+    private static $denyPorts = [
+        1068,
+        1109,
+        1434,
+        3127,
+        3128,
+        3129,
+        3130,
+        3332,
+        4444,
+        5554,
+        6669,
+        8080,
+        8081,
+        8082,
+        8181,
+        8282,
+        9996,
+        17185,
+        24554,
+        35601,
+        60177,
+        60179,
+    ];
+
+    // 加密方式
+    public static function methodList()
+    {
+        return SsConfig::type(1)->get();
+    }
+
+    // 协议
+    public static function protocolList()
+    {
+        return SsConfig::type(2)->get();
+    }
+
+    // 混淆
+    public static function obfsList()
+    {
+        return SsConfig::type(3)->get();
+    }
+
+    // 生成用户的订阅码
+    public static function makeSubscribeCode(): string
+    {
+        $code = Str::random();
+        if (UserSubscribe::whereCode($code)->exists()) {
+            $code = self::makeSubscribeCode();
+        }
+
+        return $code;
+    }
+
+    /**
+     * 添加用户
+     *
+     * @param  string  $email  用户邮箱
+     * @param  string  $password  用户密码
+     * @param  string  $transfer_enable  可用流量
+     * @param  int  $data  可使用天数
+     * @param  int|null  $inviter_id  邀请人
+     *
+     * @return int
+     */
+    public static function addUser(
+        string $email,
+        string $password,
+        string $transfer_enable,
+        int $data,
+        $inviter_id = null
+    ): int {
+        $user           = new User();
+        $user->username = $email;
+        $user->email    = $email;
+        $user->password = $password;
+        // 生成一个可用端口
+        $user->port            = self::getPort();
+        $user->passwd          = Str::random();
+        $user->vmess_id        = Str::uuid();
+        $user->enable          = 1;
+        $user->method          = self::getDefaultMethod();
+        $user->protocol        = self::getDefaultProtocol();
+        $user->obfs            = self::getDefaultObfs();
+        $user->transfer_enable = $transfer_enable;
+        $user->expired_at      = date(
+            'Y-m-d',
+            strtotime("+" . $data . " days")
+        );
+        $user->reg_ip          = getClientIp();
+        $user->inviter_id      = $inviter_id;
+        $user->reset_time      = null;
+        $user->status          = 0;
+        $user->save();
+
+        return $user->id;
+    }
+
+    // 获取一个有效端口
+    public static function getPort(): int
+    {
+        if (sysConfig('is_rand_port')) {
+            $port = self::getRandPort();
+        } else {
+            $port        = (int)sysConfig('min_port');
+            $exists_port = array_merge(
+                User::where('port', '>=', $port)->pluck('port')->toArray(),
+                self::$denyPorts
+            );
+
+            while (in_array($port, $exists_port, true)) {
+                ++$port;
+            }
+        }
+
+        return $port;
+    }
+
+    // 获取一个随机端口
+    private static function getRandPort(): int
+    {
+        $port        = random_int(sysConfig('min_port'), sysConfig('max_port'));
+        $exists_port = array_merge(
+            User::where('port', '<>', 0)->pluck('port')->toArray(),
+            self::$denyPorts
+        );
+
+        while (in_array($port, $exists_port, true)) {
+            $port = random_int(sysConfig('min_port'), sysConfig('max_port'));
+        }
+
+        return $port;
+    }
+
+    // 获取默认加密方式
+    public static function getDefaultMethod(): string
+    {
+        $config = SsConfig::default()->type(1)->first();
+
+        return $config ? $config->name : 'aes-256-cfb';
+    }
+
+    // 获取默认协议
+    public static function getDefaultProtocol(): string
+    {
+        $config = SsConfig::default()->type(2)->first();
+
+        return $config ? $config->name : 'origin';
+    }
+
+    // 获取默认混淆
+    public static function getDefaultObfs(): string
+    {
+        $config = SsConfig::default()->type(3)->first();
+
+        return $config ? $config->name : 'plain';
+    }
+
+    // 获取系统配置
+    public static function cacheSysConfig($name)
+    {
+        if ($name === 'is_onlinePay') {
+            $value = ! empty(
+            array_filter(
+                Cache::many(
+                    ['is_AliPay', 'is_QQPay', 'is_WeChatPay', 'is_otherPay']
+                )
+            )
+            );
+            Cache::tags('sysConfig')->put('is_onlinePay', $value);
+        } else {
+            $value = Config::find($name)->value;
+            Cache::tags('sysConfig')->put($name, $value ?: false);
+        }
+
+        return $value;
+    }
+
+    public static function daysToNow($date): int
+    {
+        return (new DateTime())->diff(new DateTime($date))->days;
+    }
+
+    /**
+     * 添加通知推送日志
+     *
+     * @param  string  $title  标题
+     * @param  string  $content  内容
+     * @param  int  $type  发送类型
+     * @param  string  $address  收信方
+     * @param  int  $status  投递状态
+     * @param  string  $error  投递失败时记录的异常信息
+     *
+     * @return int
+     */
+    public static function addNotificationLog(
+        string $title,
+        string $content,
+        int $type,
+        string $address = 'admin',
+        int $status = 1,
+        string $error = ''
+    ): int {
+        $log          = new NotificationLog();
+        $log->type    = $type;
+        $log->address = $address;
+        $log->title   = $title;
+        $log->content = $content;
+        $log->status  = $status;
+        $log->error   = $error;
+        $log->save();
+
+        return $log->id;
+    }
+
+    /**
+     * 添加优惠券操作日志
+     *
+     * @param  string  $description  备注
+     * @param  int  $couponId  优惠券ID
+     * @param  int  $goodsId  商品ID
+     * @param  int  $orderId  订单ID
+     *
+     * @return bool
+     */
+    public static function addCouponLog(
+        string $description,
+        int $couponId,
+        $goodsId = 0,
+        $orderId = 0
+    ): bool {
+        $log              = new CouponLog();
+        $log->coupon_id   = $couponId;
+        $log->goods_id    = $goodsId;
+        $log->order_id    = $orderId;
+        $log->description = $description;
+
+        return $log->save();
+    }
+
+    /**
+     * 记录余额操作日志
+     *
+     * @param  int  $userId  用户ID
+     * @param  int  $orderId  订单ID
+     * @param  int  $before  记录前余额
+     * @param  int  $after  记录后余额
+     * @param  int  $amount  发生金额
+     * @param  string  $description  描述
+     *
+     * @return bool
+     */
+    public static function addUserCreditLog(
+        int $userId,
+        int $orderId,
+        int $before,
+        int $after,
+        int $amount,
+        $description = ''
+    ): bool {
+        $log              = new UserCreditLog();
+        $log->user_id     = $userId;
+        $log->order_id    = $orderId;
+        $log->before      = $before;
+        $log->after       = $after;
+        $log->amount      = $amount;
+        $log->description = $description;
+        $log->created_at  = date('Y-m-d H:i:s');
+
+        return $log->save();
+    }
+
+    /**
+     * 记录流量变动日志
+     *
+     * @param  int  $userId  用户ID
+     * @param  int  $orderId  订单ID
+     * @param  int  $before  记录前的值
+     * @param  int  $after  记录后的值
+     * @param  string  $description  描述
+     *
+     * @return bool
+     */
+    public static function addUserTrafficModifyLog(
+        int $userId,
+        int $orderId,
+        int $before,
+        int $after,
+        $description = ''
+    ): bool {
+        $log              = new UserDataModifyLog();
+        $log->user_id     = $userId;
+        $log->order_id    = $orderId;
+        $log->before      = $before;
+        $log->after       = $after;
+        $log->description = $description;
+
+        return $log->save();
+    }
+
 }

+ 17 - 12
app/Components/IPIP.php

@@ -4,16 +4,21 @@ namespace App\Components;
 
 use ipip\db\City;
 
-class IPIP {
-	/**
-	 * 查询IP地址的详细信息
-	 *
-	 * @param  string  $ip  IPv4
-	 *
-	 * @return array|null
-	 */
-	public static function ip($ip): ?array {
-		$filePath = database_path('ipip.ipdb');
-		return (new City($filePath))->findMap($ip, 'CN');
-	}
+class IPIP
+{
+
+    /**
+     * 查询IP地址的详细信息
+     *
+     * @param  string  $ip  IPv4
+     *
+     * @return array|null
+     */
+    public static function ip($ip): ?array
+    {
+        $filePath = database_path('ipip.ipdb');
+
+        return (new City($filePath))->findMap($ip, 'CN');
+    }
+
 }

+ 124 - 86
app/Components/Namesilo.php

@@ -6,90 +6,128 @@ use GuzzleHttp\Client;
 use Log;
 use LSS\XML2Array;
 
-class Namesilo {
-	private static $host = 'https://www.namesilo.com/api/';
-
-	// 列出账号下所有域名 Todo Debug测试
-	public function listDomains() {
-		return $this->send('listDomains');
-	}
-
-	// 发送请求
-	private function send($operation, $data = []) {
-		$params = [
-			'version' => 1,
-			'type'    => 'xml',
-			'key'     => sysConfig('namesilo_key')
-		];
-		$query = array_merge($params, $data);
-
-		$content = '请求操作:['.$operation.'] --- 请求数据:['.http_build_query($query).']';
-
-		$request = (new Client(['timeout' => 15]))->get(self::$host.$operation.'?'.http_build_query($query));
-		$result = XML2Array::createArray(json_decode($request->getBody(), true));
-
-		if($request->getStatusCode() != 200){
-			Log::error('请求失败:'.var_export($request, true));
-			Helpers::addNotificationLog('[Namesilo API] - ['.$operation.']', $content, 1, sysConfig('webmaster_email'),
-				0, var_export($request, true));
-
-			return false;
-		}
-
-		// 出错
-		if(empty($result['namesilo']) || $result['namesilo']['reply']['code'] != 300 || $result['namesilo']['reply']['detail'] !== 'success'){
-			Helpers::addNotificationLog('[Namesilo API] - ['.$operation.']', $content, 1, sysConfig('webmaster_email'),
-				0, $result['namesilo']['reply']['detail']);
-		}else{
-			Helpers::addNotificationLog('[Namesilo API] - ['.$operation.']', $content, 1, sysConfig('webmaster_email'),
-				1, $result['namesilo']['reply']['detail']);
-		}
-
-		return $result['namesilo']['reply'];
-	}
-
-	// 列出指定域名的所有DNS记录
-	public function dnsListRecords($domain) {
-		$query = [
-			'domain' => $domain
-		];
-
-		return $this->send('dnsListRecords', $query);
-	}
-
-	// 为指定域名添加DNS记录
-	public function dnsAddRecord($domain, $host, $value, $type = 'A', $ttl = 7207) {
-		$query = [
-			'domain'  => $domain,
-			'rrtype'  => $type,
-			'rrhost'  => $host,
-			'rrvalue' => $value,
-			'rrttl'   => $ttl
-		];
-
-		return $this->send('dnsAddRecord', $query);
-	}
-
-	// 更新DNS记录
-	public function dnsUpdateRecord($domain, $id, $host, $value, $ttl = 7207) {
-		$query = [
-			'domain'  => $domain,
-			'rrid'    => $id,
-			'rrhost'  => $host,
-			'rrvalue' => $value,
-			'rrttl'   => $ttl
-		];
-
-		return $this->send('dnsUpdateRecord', $query);
-	}
-
-	// 删除DNS记录
-	public function dnsDeleteRecord($domain, $id) {
-		$data = [
-			'domain' => $domain,
-			'rrid'   => $id
-		];
-
-		return $this->send('dnsDeleteRecord', $data);
-	}
+class Namesilo
+{
+
+    private static $host = 'https://www.namesilo.com/api/';
+
+    // 列出账号下所有域名 Todo Debug测试
+    public function listDomains()
+    {
+        return $this->send('listDomains');
+    }
+
+    // 发送请求
+    private function send($operation, $data = [])
+    {
+        $params = [
+            'version' => 1,
+            'type'    => 'xml',
+            'key'     => sysConfig('namesilo_key'),
+        ];
+        $query  = array_merge($params, $data);
+
+        $content = '请求操作:[' . $operation . '] --- 请求数据:[' . http_build_query(
+                $query
+            ) . ']';
+
+        $request = (new Client(['timeout' => 15]))->get(
+            self::$host . $operation . '?' . http_build_query($query)
+        );
+        $result  = XML2Array::createArray(
+            json_decode($request->getBody(), true)
+        );
+
+        if ($request->getStatusCode() != 200) {
+            Log::error('请求失败:' . var_export($request, true));
+            Helpers::addNotificationLog(
+                '[Namesilo API] - [' . $operation . ']',
+                $content,
+                1,
+                sysConfig('webmaster_email'),
+                0,
+                var_export($request, true)
+            );
+
+            return false;
+        }
+
+        // 出错
+        if (empty($result['namesilo']) || $result['namesilo']['reply']['code'] != 300 || $result['namesilo']['reply']['detail'] !== 'success') {
+            Helpers::addNotificationLog(
+                '[Namesilo API] - [' . $operation . ']',
+                $content,
+                1,
+                sysConfig('webmaster_email'),
+                0,
+                $result['namesilo']['reply']['detail']
+            );
+        } else {
+            Helpers::addNotificationLog(
+                '[Namesilo API] - [' . $operation . ']',
+                $content,
+                1,
+                sysConfig('webmaster_email'),
+                1,
+                $result['namesilo']['reply']['detail']
+            );
+        }
+
+        return $result['namesilo']['reply'];
+    }
+
+    // 列出指定域名的所有DNS记录
+    public function dnsListRecords($domain)
+    {
+        $query = [
+            'domain' => $domain,
+        ];
+
+        return $this->send('dnsListRecords', $query);
+    }
+
+    // 为指定域名添加DNS记录
+    public function dnsAddRecord(
+        $domain,
+        $host,
+        $value,
+        $type = 'A',
+        $ttl = 7207
+    ) {
+        $query = [
+            'domain'  => $domain,
+            'rrtype'  => $type,
+            'rrhost'  => $host,
+            'rrvalue' => $value,
+            'rrttl'   => $ttl,
+        ];
+
+        return $this->send('dnsAddRecord', $query);
+    }
+
+    // 更新DNS记录
+    public function dnsUpdateRecord($domain, $id, $host, $value, $ttl = 7207)
+    {
+        $query = [
+            'domain'  => $domain,
+            'rrid'    => $id,
+            'rrhost'  => $host,
+            'rrvalue' => $value,
+            'rrttl'   => $ttl,
+        ];
+
+        return $this->send('dnsUpdateRecord', $query);
+    }
+
+    // 删除DNS记录
+    public function dnsDeleteRecord($domain, $id)
+    {
+        $data = [
+            'domain' => $domain,
+            'rrid'   => $id,
+        ];
+
+        return $this->send('dnsDeleteRecord', $data);
+    }
+
 }

+ 94 - 76
app/Components/NetworkDetection.php

@@ -5,80 +5,98 @@ namespace App\Components;
 use GuzzleHttp\Client;
 use Log;
 
-class NetworkDetection {
-	/**
-	 * 用api.50network.com进行节点阻断检测
-	 *
-	 * @param  string    $ip    被检测的IP
-	 * @param  boolean   $type  TRUE 为ICMP,FALSE 为tcp
-	 * @param  int|null  $port  检测端口,默认为空
-	 *
-	 * @return bool|string
-	 */
-	public static function networkCheck($ip, $type, $port = null) {
-		$url = 'https://api.50network.com/china-firewall/check/ip/'.($type? 'icmp/' : ($port? 'tcp_port/' : 'tcp_ack/')).$ip.($port? '/'.$port : '');
-		$checkName = $type? 'ICMP' : 'TCP';
-		$request = (new Client(['timeout' => 15]))->get($url);
-		$result = json_decode($request->getBody(), true);
-
-		if($request->getStatusCode() == 200){
-			if(!$result){
-				Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口返回异常访问链接:".$url);
-
-				return false;
-			}
-
-			if(!$result['success']){
-				if($result['error'] === "execute timeout (3s)"){
-					sleep(10);
-
-					return self::networkCheck($ip, $type, $port);
-				}
-
-				Log::warning("【".$checkName."阻断检测】检测".$ip.($port?: '')."时,返回".var_export($result, true));
-				return false;
-			}
-
-			if($result['firewall-enable'] && $result['firewall-disable']){
-				return "通讯正常"; // 正常
-			}
-
-			if($result['firewall-enable'] && !$result['firewall-disable']){
-				return "海外阻断"; // 国外访问异常
-			}
-
-			if(!$result['firewall-enable'] && $result['firewall-disable']){
-				return "国内阻断"; // 被墙
-			}
-
-			return "机器宕机"; // 服务器宕机
-		}
-		return false;
-	}
-
-	/**
-	 * 用外部API进行Ping检测
-	 *
-	 * @param  string  $ip  被检测的IP或者域名
-	 *
-	 * @return bool|array
-	 */
-	public static function ping($ip) {
-		$url = 'https://api.oioweb.cn/api/hostping.php?host='.$ip;//https://api.iiwl.cc/api/ping.php?host=
-		$request = (new Client(['timeout' => 15]))->get($url);
-		$message = json_decode($request->getBody(), true);
-
-		// 发送成功
-		if($request->getStatusCode() == 200){
-			if($message && $message['code']){
-				return $message['data'];
-			}
-			// 发送失败
-			Log::warning("【PING】检测".$ip."时,返回".var_export($message, true));
-			return false;
-		}
-		Log::warning("【PING】检测".$ip."时,接口返回异常访问链接:".$url);
-		// 发送错误
-		return false;
-	}
+class NetworkDetection
+{
+
+    /**
+     * 用api.50network.com进行节点阻断检测
+     *
+     * @param  string  $ip  被检测的IP
+     * @param  bool  $type  TRUE 为ICMP,FALSE 为tcp
+     * @param  int|null  $port  检测端口,默认为空
+     *
+     * @return bool|string
+     */
+    public static function networkCheck(string $ip, bool $type, $port = null)
+    {
+        $url       = 'https://api.50network.com/china-firewall/check/ip/' . ($type ? 'icmp/' : ($port ? 'tcp_port/' : 'tcp_ack/')) . $ip . ($port ? '/' . $port : '');
+        $checkName = $type ? 'ICMP' : 'TCP';
+        $request   = (new Client(['timeout' => 15]))->get($url);
+        $result    = json_decode($request->getBody(), true);
+
+        if ($request->getStatusCode() == 200) {
+            if ( ! $result) {
+                Log::warning(
+                    "【" . $checkName . "阻断检测】检测" . $ip . "时,接口返回异常访问链接:" . $url
+                );
+
+                return false;
+            }
+
+            if ( ! $result['success']) {
+                if ($result['error'] === "execute timeout (3s)") {
+                    sleep(10);
+
+                    return self::networkCheck($ip, $type, $port);
+                }
+
+                Log::warning(
+                    "【" . $checkName . "阻断检测】检测" . $ip . ($port ?: '') . "时,返回" . var_export(
+                        $result,
+                        true
+                    )
+                );
+
+                return false;
+            }
+
+            if ($result['firewall-enable'] && $result['firewall-disable']) {
+                return "通讯正常"; // 正常
+            }
+
+            if ($result['firewall-enable'] && ! $result['firewall-disable']) {
+                return "海外阻断"; // 国外访问异常
+            }
+
+            if ( ! $result['firewall-enable'] && $result['firewall-disable']) {
+                return "国内阻断"; // 被墙
+            }
+
+            return "机器宕机"; // 服务器宕机
+        }
+
+        return false;
+    }
+
+    /**
+     * 用外部API进行Ping检测
+     *
+     * @param  string  $ip  被检测的IP或者域名
+     *
+     * @return bool|array
+     */
+    public static function ping(string $ip)
+    {
+        $url     = 'https://api.oioweb.cn/api/hostping.php?host=' . $ip;//https://api.iiwl.cc/api/ping.php?host=
+        $request = (new Client(['timeout' => 15]))->get($url);
+        $message = json_decode($request->getBody(), true);
+
+        // 发送成功
+        if ($request->getStatusCode() == 200) {
+            if ($message && $message['code']) {
+                return $message['data'];
+            }
+            // 发送失败
+            Log::warning(
+                "【PING】检测" . $ip . "时,返回" . var_export($message, true)
+            );
+
+            return false;
+        }
+        Log::warning("【PING】检测" . $ip . "时,接口返回异常访问链接:" . $url);
+
+        // 发送错误
+        return false;
+    }
+
 }

+ 96 - 62
app/Components/PushNotification.php

@@ -6,69 +6,103 @@ namespace App\Components;
 use GuzzleHttp\Client;
 use Log;
 
-class PushNotification {
-	public static function send($title, $content) {
-		switch(sysConfig('is_notification')){
-			case 'serverChan':
-				return self::ServerChan($title, $content);
-			case 'bark':
-				return self::Bark($title, $content);
-			default:
-				return false;
-		}
-	}
+class PushNotification
+{
 
-	/**
-	 * ServerChan推送消息
-	 *
-	 * @param  string  $title    消息标题
-	 * @param  string  $content  消息内容
-	 *
-	 * @return mixed
-	 */
-	private static function ServerChan($title, $content) {
-		// TODO:一天仅可发送不超过500条
-		$request = (new Client(['timeout' => 15]))->get('https://sc.ftqq.com/'.sysConfig('server_chan_key').'.send?text='.$title.'&desp='.urlencode($content));
-		$message = json_decode($request->getBody(), true);
-		// 发送成功
-		if($request->getStatusCode() == 200){
-			if(!$message['errno']){
-				Helpers::addNotificationLog($title, $content, 2);
-				return $message;
-			}
-			// 发送失败
-			Helpers::addNotificationLog($title, $content, 2, 'admin', -1, $message? $message['errmsg'] : '未知');
-			return false;
-		}
-		// 发送错误
-		Log::error('ServerChan消息推送异常:'.var_export($request, true));
-		return false;
-	}
+    public static function send($title, $content)
+    {
+        switch (sysConfig('is_notification')) {
+            case 'serverChan':
+                return self::ServerChan($title, $content);
+            case 'bark':
+                return self::Bark($title, $content);
+            default:
+                return false;
+        }
+    }
 
-	/**
-	 * Bark推送消息
-	 *
-	 * @param  string  $title    消息标题
-	 * @param  string  $content  消息内容
-	 *
-	 * @return mixed
-	 */
-	private static function Bark($title, $content) {
-		$request = (new Client(['timeout' => 15]))->get('https://api.day.app/'.sysConfig('bark_key').'/'.$title.'/'.$content);
-		$message = json_decode($request->getBody(), true);
+    /**
+     * ServerChan推送消息
+     *
+     * @param  string  $title  消息标题
+     * @param  string  $content  消息内容
+     *
+     * @return mixed
+     */
+    private static function ServerChan(string $title, string $content)
+    {
+        // TODO:一天仅可发送不超过500条
+        $request = (new Client(['timeout' => 15]))->get(
+            'https://sc.ftqq.com/' . sysConfig(
+                'server_chan_key'
+            ) . '.send?text=' . $title . '&desp=' . urlencode($content)
+        );
+        $message = json_decode($request->getBody(), true);
+        // 发送成功
+        if ($request->getStatusCode() == 200) {
+            if ( ! $message['errno']) {
+                Helpers::addNotificationLog($title, $content, 2);
+
+                return $message;
+            }
+            // 发送失败
+            Helpers::addNotificationLog(
+                $title,
+                $content,
+                2,
+                'admin',
+                -1,
+                $message ? $message['errmsg'] : '未知'
+            );
+
+            return false;
+        }
+        // 发送错误
+        Log::error('ServerChan消息推送异常:' . var_export($request, true));
+
+        return false;
+    }
+
+    /**
+     * Bark推送消息
+     *
+     * @param  string  $title  消息标题
+     * @param  string  $content  消息内容
+     *
+     * @return mixed
+     */
+    private static function Bark(string $title, string $content)
+    {
+        $request = (new Client(['timeout' => 15]))->get(
+            'https://api.day.app/' . sysConfig(
+                'bark_key'
+            ) . '/' . $title . '/' . $content
+        );
+        $message = json_decode($request->getBody(), true);
+
+        if ($request->getStatusCode() == 200) {
+            // 发送成功
+            if ($message['code'] == 200) {
+                Helpers::addNotificationLog($title, $content, 3);
+
+                return $message;
+            }
+            // 发送失败
+            Helpers::addNotificationLog(
+                $title,
+                $content,
+                3,
+                'admin',
+                -1,
+                $message
+            );
+
+            return false;
+        }
+        // 发送错误
+        Log::error('Bark消息推送异常:' . var_export($request, true));
+
+        return false;
+    }
 
-		if($request->getStatusCode() == 200){
-			// 发送成功
-			if($message['code'] == 200){
-				Helpers::addNotificationLog($title, $content, 3);
-				return $message;
-			}
-			// 发送失败
-			Helpers::addNotificationLog($title, $content, 3, 'admin', -1, $message);
-			return false;
-		}
-		// 发送错误
-		Log::error('Bark消息推送异常:'.var_export($request, true));
-		return false;
-	}
 }

+ 53 - 45
app/Components/QQInfo.php

@@ -4,49 +4,57 @@ namespace App\Components;
 
 use GuzzleHttp\Client;
 
-class QQInfo {
-	public static function getName(string $qq): string {
-		//向接口发起请求获取json数据
-		$url = 'https://r.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?get_nick=1&uins='.$qq;
-		$request = (new Client(['timeout' => 15]))->get($url);
-		$message = mb_convert_encoding($request->getBody(), "UTF-8", "GBK");
-
-		// 接口是否异常
-		if($request->getStatusCode() == 200 && str_contains($message, $qq)){
-			//对获取的json数据进行截取并解析成数组
-			$message = json_decode(substr($message, 17, -1), true);
-
-			return stripslashes($message[$qq][6]);
-		}
-
-		return $qq;
-	}
-
-	public static function getName2(string $qq): string {
-		//向接口发起请求获取json数据
-		$url = 'https://api.toubiec.cn/qq?qq='.$qq.'&size=100';
-		$request = (new Client(['timeout' => 15]))->get($url);
-		$message = json_decode($request->getBody(), true);
-
-		// 接口是否异常
-		if($message && $message['code'] == 200 && $request->getStatusCode() == 200){
-			return $message['name'];
-		}
-
-		return $qq;
-	}
-
-	public static function getName3(string $qq): string {
-		//向接口发起请求获取json数据
-		$url = 'https://api.unipay.qq.com/v1/r/1450000186/wechat_query?cmd=1&pf=mds_storeopen_qb-__mds_qqclub_tab_-html5&pfkey=pfkey&from_h5=1&from_https=1&openid=openid&openkey=openkey&session_id=hy_gameid&session_type=st_dummy&qq_appid=&offerId=1450000186&sandbox=&provide_uin='.$qq;
-		$request = (new Client(['timeout' => 15]))->get($url);
-		$message = json_decode($request->getBody(), true);
-
-		// 接口是否异常
-		if($message && $message['ret'] == 0 && $request->getStatusCode() == 200){
-			return urldecode($message['nick']);
-		}
-
-		return $qq;
-	}
+class QQInfo
+{
+
+    public static function getName(string $qq): string
+    {
+        //向接口发起请求获取json数据
+        $url     = 'https://r.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?get_nick=1&uins=' . $qq;
+        $request = (new Client(['timeout' => 15]))->get($url);
+        $message = mb_convert_encoding($request->getBody(), "UTF-8", "GBK");
+
+        // 接口是否异常
+        if ($request->getStatusCode() == 200 && str_contains($message, $qq)) {
+            //对获取的json数据进行截取并解析成数组
+            $message = json_decode(substr($message, 17, -1), true);
+
+            return stripslashes($message[$qq][6]);
+        }
+
+        return $qq;
+    }
+
+    public static function getName2(string $qq): string
+    {
+        //向接口发起请求获取json数据
+        $url     = 'https://api.toubiec.cn/qq?qq=' . $qq . '&size=100';
+        $request = (new Client(['timeout' => 15]))->get($url);
+        $message = json_decode($request->getBody(), true);
+
+        // 接口是否异常
+        if ($message && $message['code'] == 200
+            && $request->getStatusCode() == 200) {
+            return $message['name'];
+        }
+
+        return $qq;
+    }
+
+    public static function getName3(string $qq): string
+    {
+        //向接口发起请求获取json数据
+        $url     = 'https://api.unipay.qq.com/v1/r/1450000186/wechat_query?cmd=1&pf=mds_storeopen_qb-__mds_qqclub_tab_-html5&pfkey=pfkey&from_h5=1&from_https=1&openid=openid&openkey=openkey&session_id=hy_gameid&session_type=st_dummy&qq_appid=&offerId=1450000186&sandbox=&provide_uin=' . $qq;
+        $request = (new Client(['timeout' => 15]))->get($url);
+        $message = json_decode($request->getBody(), true);
+
+        // 接口是否异常
+        if ($message && $message['ret'] == 0
+            && $request->getStatusCode() == 200) {
+            return urldecode($message['nick']);
+        }
+
+        return $qq;
+    }
+
 }

+ 16 - 12
app/Components/QQWry.php

@@ -4,17 +4,21 @@ namespace App\Components;
 
 use itbdw\Ip\IpLocation;
 
-class QQWry {
-	/**
-	 * 查询IP地址的详细信息
-	 *
-	 * @param  string  $ip  IPv4
-	 *
-	 * @return array
-	 */
-	public static function ip($ip): array {
-		$filePath = database_path('qqwry.dat');
+class QQWry
+{
+
+    /**
+     * 查询IP地址的详细信息
+     *
+     * @param  string  $ip  IPv4
+     *
+     * @return array
+     */
+    public static function ip($ip): array
+    {
+        $filePath = database_path('qqwry.dat');
+
+        return IpLocation::getLocation($ip, $filePath);
+    }
 
-		return IpLocation::getLocation($ip, $filePath);
-	}
 }

+ 105 - 61
app/Console/Commands/AutoClearLog.php

@@ -17,66 +17,110 @@ use Exception;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoClearLog extends Command {
-	protected $signature = 'autoClearLog';
-	protected $description = '自动清除日志';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 清除日志
-		if(sysConfig('is_clear_log')){
-			$this->clearLog();
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	// 清除日志
-	private function clearLog(): void {
-		try{
-			// 清除节点负载信息日志
-			NodeHeartBeat::where('log_time', '<=', strtotime("-30 minutes"))->delete();
-
-			// 清除节点在线用户数日志
-			NodeOnlineLog::where('log_time', '<=', strtotime("-1 hour"))->delete();
-
-			// 清除用户流量日志
-			UserDataFlowLog::where('log_time', '<=', strtotime("-3 days"))->delete();
-
-			// 清除用户每时各流量数据日志
-			UserHourlyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime('-3 days')))->delete();
-
-			// 清除用户各节点 / 节点总计的每天流量数据日志
-			UserDailyDataFlow::where('node_id', '<>', 0)
-			                 ->where('created_at', '<=', date('Y-m-d H:i:s', strtotime('-1 month')))
-			                 ->orWhere('created_at', '<=', date('Y-m-d H:i:s', strtotime('-3 month')))
-			                 ->delete();
-
-			// 清除节点每小时流量数据日志
-			NodeHourlyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime('-3 days')))->delete();
-
-			// 清除节点每天流量数据日志
-			NodeDailyDataFlow::where('created_at', '<=', date('Y-m-d H:i:s', strtotime('-2 month')))->delete();
-
-			// 清除用户封禁日志
-			UserBanedLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime("-3 month")))->delete();
-
-			// 清除用户连接IP
-			NodeOnlineUserIp::where('created_at', '<=', strtotime("-1 month"))->delete();
-
-			// 清除用户登陆日志
-			UserLoginLog::where('created_at', '<=', date('Y-m-d H:i:s', strtotime("-3 month")))->delete();
-
-			// 清除用户订阅记录
-			UserSubscribeLog::where('request_time', '<=', date('Y-m-d H:i:s', strtotime("-1 month")))->delete();
-		}catch(Exception $e){
-			Log::error('【清理日志】错误: '.$e->getMessage());
-		}
-
-	}
+class AutoClearLog extends Command
+{
+
+    protected $signature = 'autoClearLog';
+    protected $description = '自动清除日志';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 清除日志
+        if (sysConfig('is_clear_log')) {
+            $this->clearLog();
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    // 清除日志
+    private function clearLog(): void
+    {
+        try {
+            // 清除节点负载信息日志
+            NodeHeartBeat::where('log_time', '<=', strtotime("-30 minutes"))
+                         ->delete();
+
+            // 清除节点在线用户数日志
+            NodeOnlineLog::where('log_time', '<=', strtotime("-1 hour"))
+                         ->delete();
+
+            // 清除用户流量日志
+            UserDataFlowLog::where('log_time', '<=', strtotime("-3 days"))
+                           ->delete();
+
+            // 清除用户每时各流量数据日志
+            UserHourlyDataFlow::where(
+                'created_at',
+                '<=',
+                date('Y-m-d H:i:s', strtotime('-3 days'))
+            )->delete();
+
+            // 清除用户各节点 / 节点总计的每天流量数据日志
+            UserDailyDataFlow::where('node_id', '<>', 0)
+                             ->where(
+                                 'created_at',
+                                 '<=',
+                                 date('Y-m-d H:i:s', strtotime('-1 month'))
+                             )
+                             ->orWhere(
+                                 'created_at',
+                                 '<=',
+                                 date(
+                                     'Y-m-d H:i:s',
+                                     strtotime('-3 month')
+                                 )
+                             )
+                             ->delete();
+
+            // 清除节点每小时流量数据日志
+            NodeHourlyDataFlow::where(
+                'created_at',
+                '<=',
+                date('Y-m-d H:i:s', strtotime('-3 days'))
+            )->delete();
+
+            // 清除节点每天流量数据日志
+            NodeDailyDataFlow::where(
+                'created_at',
+                '<=',
+                date('Y-m-d H:i:s', strtotime('-2 month'))
+            )->delete();
+
+            // 清除用户封禁日志
+            UserBanedLog::where(
+                'created_at',
+                '<=',
+                date('Y-m-d H:i:s', strtotime("-3 month"))
+            )->delete();
+
+            // 清除用户连接IP
+            NodeOnlineUserIp::where('created_at', '<=', strtotime("-1 month"))
+                            ->delete();
+
+            // 清除用户登陆日志
+            UserLoginLog::where(
+                'created_at',
+                '<=',
+                date('Y-m-d H:i:s', strtotime("-3 month"))
+            )->delete();
+
+            // 清除用户订阅记录
+            UserSubscribeLog::where(
+                'request_time',
+                '<=',
+                date('Y-m-d H:i:s', strtotime("-1 month"))
+            )->delete();
+        } catch (Exception $e) {
+            Log::error('【清理日志】错误: ' . $e->getMessage());
+        }
+    }
 
 }

+ 266 - 216
app/Console/Commands/AutoJob.php

@@ -18,220 +18,270 @@ use Cache;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoJob extends Command {
-	protected $signature = 'autoJob';
-	protected $description = '自动化任务';
-
-	/*
-	 * 警告:除非熟悉业务流程,否则不推荐更改以下执行顺序,随意变更以下顺序可能导致系统异常
-	 */
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 关闭超时未支付本地订单
-		$this->closeOrders();
-
-		//过期验证码、优惠券、邀请码无效化
-		$this->expireCode();
-
-		// 封禁访问异常的订阅链接
-		$this->blockSubscribe();
-
-		// 封禁账号
-		$this->blockUsers();
-
-		// 解封被封禁的账号
-		$this->unblockUsers();
-
-		// 端口回收与分配
-		if(sysConfig('auto_release_port')){
-			$this->dispatchPort();
-		}
-
-		// 检测节点是否离线
-		$this->checkNodeStatus();
-
-		// 检查维护模式
-		if(sysConfig('maintenance_mode')){
-			Config::whereIn('name', ['maintenance_mode', 'maintenance_time'])->update(['value' => null]);
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	// 关闭超时未支付本地订单
-	private function closeOrders(): void {
-		// 关闭超时未支付的本地支付订单
-		foreach(Order::recentUnPay()->get() as $order){
-			// 关闭订单
-			$order->update(['status' => -1]);
-		}
-	}
-
-	// 注册验证码自动置无效 & 优惠券无效化
-	private function expireCode(): void {
-		// 注册验证码自动置无效
-		VerifyCode::recentUnused()->update(['status' => 2]);
-
-		// 优惠券到期 / 用尽的 自动置无效
-		Coupon::whereStatus(0)
-		      ->where('end_time', '<=', time())
-		      ->orWhereIn('type', [1, 2])
-		      ->whereUsableTimes(0)
-		      ->update(['status' => 2]);
-
-		// 邀请码到期自动置无效
-		Invite::whereStatus(0)->where('dateline', '<=', date('Y-m-d H:i:s'))->update(['status' => 2]);
-	}
-
-	// 封禁访问异常的订阅链接
-	private function blockSubscribe(): void {
-		if(sysConfig('is_subscribe_ban')){
-			$subscribe_ban_times = sysConfig('subscribe_ban_times');
-			foreach(User::activeUser()->with('subscribe')->get() as $user){
-				if(!$user->subscribe || $user->subscribe->status === 0){ // 无订阅链接 或 已封
-					continue;
-				}
-				// 24小时内不同IP的请求次数
-				$request_times = $user->subscribeLogs()
-				                      ->where('request_time', '>=', date("Y-m-d H:i:s", strtotime("-1 days")))
-				                      ->distinct()
-				                      ->count('request_ip');
-				if($request_times >= $subscribe_ban_times){
-					$user->subscribe->update([
-						'status'   => 0,
-						'ban_time' => strtotime("+".sysConfig('traffic_ban_time')." minutes"),
-						'ban_desc' => '存在异常,自动封禁'
-					]);
-
-					// 记录封禁日志
-					$this->addUserBanLog($user->id, 0, '【完全封禁订阅】-订阅24小时内请求异常');
-				}
-			}
-		}
-	}
-
-	/**
-	 * 添加用户封禁日志
-	 *
-	 * @param  int     $userId       用户ID
-	 * @param  int     $time         封禁时长,单位分钟
-	 * @param  string  $description  封禁理由
-	 */
-	private function addUserBanLog($userId, $time, $description): void {
-		$log = new UserBanedLog();
-		$log->user_id = $userId;
-		$log->time = $time;
-		$log->description = $description;
-		$log->save();
-	}
-
-	// 封禁账号
-	private function blockUsers(): void {
-		// 封禁1小时内流量异常账号
-		$userList = User::activeUser()->whereBanTime(null);
-		if(sysConfig('is_traffic_ban')){
-			$trafficBanValue = sysConfig('traffic_ban_value');
-			$trafficBanTime = sysConfig('traffic_ban_time');
-			foreach($userList->get() as $user){
-				// 对管理员豁免
-				if($user->is_admin){
-					continue;
-				}
-
-				// 多往前取5分钟,防止数据统计任务执行时间过长导致没有数据
-				$totalTraffic = UserHourlyDataFlow::userRecentUsed($user->id)->sum('total');
-				if($totalTraffic >= $trafficBanValue * GB){
-					$user->update([
-						'enable'   => 0,
-						'ban_time' => strtotime("+".$trafficBanTime." minutes")
-					]);
-
-					// 写入日志
-					$this->addUserBanLog($user->id, $trafficBanTime, '【临时封禁代理】-1小时内流量异常');
-				}
-			}
-		}
-
-		// 禁用流量超限用户
-		foreach($userList->whereRaw("u + d >= transfer_enable")->get() as $user){
-			$user->update(['enable' => 0]);
-
-			// 写入日志
-			$this->addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
-		}
-	}
-
-	// 解封被临时封禁的账号
-	private function unblockUsers(): void {
-		// 解封被临时封禁的账号
-		$userList = User::whereEnable(0)->where('status', '>=', 0)->whereNotNull('ban_time')->get();
-		foreach($userList as $user){
-			if($user->ban_time < time()){
-				$user->update(['enable' => 1, 'ban_time' => null]);
-
-				// 写入操作日志
-				$this->addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
-			}
-		}
-
-		// 可用流量大于已用流量也解封(比如:邀请返利自动加了流量)
-		$userList = User::whereEnable(0)
-		                ->where('status', '>=', 0)
-		                ->whereBanTime(null)
-		                ->where('expired_at', '>=', date('Y-m-d'))
-		                ->whereRaw("u + d < transfer_enable")
-		                ->get();
-		foreach($userList as $user){
-			$user->update(['enable' => 1]);
-
-			// 写入操作日志
-			$this->addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
-		}
-	}
-
-	// 端口回收与分配
-	private function dispatchPort(): void {
-		## 自动分配端口
-		foreach(User::activeUser()->wherePort(0)->get() as $user){
-			$user->update(['port' => Helpers::getPort()]);
-		}
-
-		// 被封禁 / 过期一个月 的账号自动释放端口
-		User::where('port', '<>', 0)
-		    ->whereStatus(-1)
-		    ->orWhere('expired_at', '<=', date("Y-m-d", strtotime("-1 months")))
-		    ->update(['port' => 0]);
-	}
-
-	// 检测节点是否离线
-	private function checkNodeStatus(): void {
-		if(sysConfig('is_node_offline')){
-			$offlineCheckTimes = sysConfig('offline_check_times');
-			$onlineNode = NodeHeartBeat::recently()->distinct()->pluck('node_id')->toArray();
-			foreach(Node::whereIsRelay(0)->whereStatus(1)->get() as $node){
-				// 10分钟内无节点负载信息则认为是后端炸了
-				$nodeTTL = !in_array($node->id, $onlineNode);
-				if($nodeTTL && $offlineCheckTimes){
-					// 已通知次数
-					$cacheKey = 'offline_check_times'.$node->id;
-					if(Cache::has($cacheKey)){
-						$times = Cache::get($cacheKey);
-					}else{
-						// 键将保留24小时
-						Cache::put($cacheKey, 1, Day);
-						$times = 1;
-					}
-
-					if($times < $offlineCheckTimes){
-						Cache::increment($cacheKey);
-						PushNotification::send('节点异常警告', "节点**{$node->name}【{$node->ip}】**异常:**心跳异常,可能离线了**");
-					}
-				}
-			}
-		}
-	}
+class AutoJob extends Command
+{
+
+    protected $signature = 'autoJob';
+    protected $description = '自动化任务';
+
+    /*
+     * 警告:除非熟悉业务流程,否则不推荐更改以下执行顺序,随意变更以下顺序可能导致系统异常
+     */
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 关闭超时未支付本地订单
+        $this->closeOrders();
+
+        //过期验证码、优惠券、邀请码无效化
+        $this->expireCode();
+
+        // 封禁访问异常的订阅链接
+        $this->blockSubscribe();
+
+        // 封禁账号
+        $this->blockUsers();
+
+        // 解封被封禁的账号
+        $this->unblockUsers();
+
+        // 端口回收与分配
+        if (sysConfig('auto_release_port')) {
+            $this->dispatchPort();
+        }
+
+        // 检测节点是否离线
+        $this->checkNodeStatus();
+
+        // 检查维护模式
+        if (sysConfig('maintenance_mode')) {
+            Config::whereIn('name', ['maintenance_mode', 'maintenance_time'])
+                  ->update(['value' => null]);
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    // 关闭超时未支付本地订单
+    private function closeOrders(): void
+    {
+        // 关闭超时未支付的本地支付订单
+        foreach (Order::recentUnPay()->get() as $order) {
+            // 关闭订单
+            $order->update(['status' => -1]);
+        }
+    }
+
+    // 注册验证码自动置无效 & 优惠券无效化
+    private function expireCode(): void
+    {
+        // 注册验证码自动置无效
+        VerifyCode::recentUnused()->update(['status' => 2]);
+
+        // 优惠券到期 / 用尽的 自动置无效
+        Coupon::whereStatus(0)
+              ->where('end_time', '<=', time())
+              ->orWhereIn('type', [1, 2])
+              ->whereUsableTimes(0)
+              ->update(['status' => 2]);
+
+        // 邀请码到期自动置无效
+        Invite::whereStatus(0)
+              ->where('dateline', '<=', date('Y-m-d H:i:s'))
+              ->update(['status' => 2]);
+    }
+
+    // 封禁访问异常的订阅链接
+    private function blockSubscribe(): void
+    {
+        if (sysConfig('is_subscribe_ban')) {
+            $subscribe_ban_times = sysConfig('subscribe_ban_times');
+            foreach (User::activeUser()->with('subscribe')->get() as $user) {
+                if ( ! $user->subscribe || $user->subscribe->status === 0) { // 无订阅链接 或 已封
+                    continue;
+                }
+                // 24小时内不同IP的请求次数
+                $request_times = $user->subscribeLogs()
+                                      ->where(
+                                          'request_time',
+                                          '>=',
+                                          date(
+                                              "Y-m-d H:i:s",
+                                              strtotime("-1 days")
+                                          )
+                                      )
+                                      ->distinct()
+                                      ->count('request_ip');
+                if ($request_times >= $subscribe_ban_times) {
+                    $user->subscribe->update(
+                        [
+                            'status'   => 0,
+                            'ban_time' => strtotime(
+                                "+" . sysConfig('traffic_ban_time') . " minutes"
+                            ),
+                            'ban_desc' => '存在异常,自动封禁',
+                        ]
+                    );
+
+                    // 记录封禁日志
+                    $this->addUserBanLog($user->id, 0, '【完全封禁订阅】-订阅24小时内请求异常');
+                }
+            }
+        }
+    }
+
+    /**
+     * 添加用户封禁日志
+     *
+     * @param  int  $userId  用户ID
+     * @param  int  $time  封禁时长,单位分钟
+     * @param  string  $description  封禁理由
+     */
+    private function addUserBanLog(
+        int $userId,
+        int $time,
+        string $description
+    ): void {
+        $log              = new UserBanedLog();
+        $log->user_id     = $userId;
+        $log->time        = $time;
+        $log->description = $description;
+        $log->save();
+    }
+
+    // 封禁账号
+    private function blockUsers(): void
+    {
+        // 封禁1小时内流量异常账号
+        $userList = User::activeUser()->whereBanTime(null);
+        if (sysConfig('is_traffic_ban')) {
+            $trafficBanValue = sysConfig('traffic_ban_value');
+            $trafficBanTime  = sysConfig('traffic_ban_time');
+            foreach ($userList->get() as $user) {
+                // 对管理员豁免
+                if ($user->is_admin) {
+                    continue;
+                }
+
+                // 多往前取5分钟,防止数据统计任务执行时间过长导致没有数据
+                $totalTraffic = UserHourlyDataFlow::userRecentUsed($user->id)
+                                                  ->sum('total');
+                if ($totalTraffic >= $trafficBanValue * GB) {
+                    $user->update(
+                        [
+                            'enable'   => 0,
+                            'ban_time' => strtotime(
+                                "+" . $trafficBanTime . " minutes"
+                            ),
+                        ]
+                    );
+
+                    // 写入日志
+                    $this->addUserBanLog(
+                        $user->id,
+                        $trafficBanTime,
+                        '【临时封禁代理】-1小时内流量异常'
+                    );
+                }
+            }
+        }
+
+        // 禁用流量超限用户
+        foreach (
+            $userList->whereRaw("u + d >= transfer_enable")->get() as $user
+        ) {
+            $user->update(['enable' => 0]);
+
+            // 写入日志
+            $this->addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
+        }
+    }
+
+    // 解封被临时封禁的账号
+    private function unblockUsers(): void
+    {
+        // 解封被临时封禁的账号
+        $userList = User::whereEnable(0)
+                        ->where('status', '>=', 0)
+                        ->whereNotNull('ban_time')
+                        ->get();
+        foreach ($userList as $user) {
+            if ($user->ban_time < time()) {
+                $user->update(['enable' => 1, 'ban_time' => null]);
+
+                // 写入操作日志
+                $this->addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
+            }
+        }
+
+        // 可用流量大于已用流量也解封(比如:邀请返利自动加了流量)
+        $userList = User::whereEnable(0)
+                        ->where('status', '>=', 0)
+                        ->whereBanTime(null)
+                        ->where('expired_at', '>=', date('Y-m-d'))
+                        ->whereRaw("u + d < transfer_enable")
+                        ->get();
+        foreach ($userList as $user) {
+            $user->update(['enable' => 1]);
+
+            // 写入操作日志
+            $this->addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
+        }
+    }
+
+    // 端口回收与分配
+    private function dispatchPort(): void
+    {
+        ## 自动分配端口
+        foreach (User::activeUser()->wherePort(0)->get() as $user) {
+            $user->update(['port' => Helpers::getPort()]);
+        }
+
+        // 被封禁 / 过期一个月 的账号自动释放端口
+        User::where('port', '<>', 0)
+            ->whereStatus(-1)
+            ->orWhere('expired_at', '<=', date("Y-m-d", strtotime("-1 months")))
+            ->update(['port' => 0]);
+    }
+
+    // 检测节点是否离线
+    private function checkNodeStatus(): void
+    {
+        if (sysConfig('is_node_offline')) {
+            $offlineCheckTimes = sysConfig('offline_check_times');
+            $onlineNode        = NodeHeartBeat::recently()->distinct()->pluck(
+                'node_id'
+            )->toArray();
+            foreach (Node::whereIsRelay(0)->whereStatus(1)->get() as $node) {
+                // 10分钟内无节点负载信息则认为是后端炸了
+                $nodeTTL = ! in_array($node->id, $onlineNode);
+                if ($nodeTTL && $offlineCheckTimes) {
+                    // 已通知次数
+                    $cacheKey = 'offline_check_times' . $node->id;
+                    if (Cache::has($cacheKey)) {
+                        $times = Cache::get($cacheKey);
+                    } else {
+                        // 键将保留24小时
+                        Cache::put($cacheKey, 1, Day);
+                        $times = 1;
+                    }
+
+                    if ($times < $offlineCheckTimes) {
+                        Cache::increment($cacheKey);
+                        PushNotification::send(
+                            '节点异常警告',
+                            "节点**{$node->name}【{$node->ip}】**异常:**心跳异常,可能离线了**"
+                        );
+                    }
+                }
+            }
+        }
+    }
+
 }

+ 43 - 34
app/Console/Commands/AutoPingNode.php

@@ -8,38 +8,47 @@ use App\Models\NodePing;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoPingNode extends Command {
-	protected $signature = 'autoPingNode';
-	protected $description = '节点定时Ping测速';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		foreach(Node::whereIsRelay(0)->whereStatus(1)->get() as $node){
-			$this->pingNode($node->id, $node->is_ddns? $node->server : $node->ip);
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	// 节点Ping测速
-	private function pingNode($nodeId, $ip): void {
-		$result = NetworkDetection::ping($ip);
-
-		if($result){
-			$obj = new NodePing();
-			$obj->node_id = $nodeId;
-			$obj->ct = (int) $result['telecom']['time'];//电信
-			$obj->cu = (int) $result['Unicom']['time'];// 联通
-			$obj->cm = (int) $result['move']['time'];// 移动
-			$obj->hk = (int) $result['HongKong']['time'];// 香港
-			$obj->save();
-		}else{
-			Log::error("【".$ip."】Ping测速获取失败");
-		}
-
-	}
+class AutoPingNode extends Command
+{
+
+    protected $signature = 'autoPingNode';
+    protected $description = '节点定时Ping测速';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        foreach (Node::whereIsRelay(0)->whereStatus(1)->get() as $node) {
+            $this->pingNode(
+                $node->id,
+                $node->is_ddns ? $node->server : $node->ip
+            );
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    // 节点Ping测速
+    private function pingNode($nodeId, $ip): void
+    {
+        $result = NetworkDetection::ping($ip);
+
+        if ($result) {
+            $obj          = new NodePing();
+            $obj->node_id = $nodeId;
+            $obj->ct      = (int)$result['telecom']['time'];//电信
+            $obj->cu      = (int)$result['Unicom']['time'];// 联通
+            $obj->cm      = (int)$result['move']['time'];// 移动
+            $obj->hk      = (int)$result['HongKong']['time'];// 香港
+            $obj->save();
+        } else {
+            Log::error("【" . $ip . "】Ping测速获取失败");
+        }
+    }
+
 }

+ 50 - 32
app/Console/Commands/AutoReportNode.php

@@ -8,36 +8,54 @@ use App\Models\NodeDailyDataFlow;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoReportNode extends Command {
-	protected $signature = 'autoReportNode';
-	protected $description = '自动报告节点昨日使用情况';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		if(sysConfig('node_daily_report')){
-			$nodeList = Node::whereStatus(1)->get();
-			if($nodeList->isNotEmpty()){
-				$msg = "|节点|上行流量|下行流量|合计|\r\n| :------ | :------ | :------ |\r\n";
-				foreach($nodeList as $node){
-					$log = NodeDailyDataFlow::whereNodeId($node->id)
-					                        ->whereDate('created_at', date("Y-m-d", strtotime('-1 days')))
-					                        ->first();
-
-					if($log){
-						$msg .= '|'.$node->name.'|'.flowAutoShow($log->u).'|'.flowAutoShow($log->d).'|'.$log->traffic."\r\n";
-					}else{
-						$msg .= '|'.$node->name.'|'.flowAutoShow(0).'|'.flowAutoShow(0)."|0B\r\n";
-					}
-				}
-
-				PushNotification::send('节点昨日使用情况', $msg);
-			}
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
+class AutoReportNode extends Command
+{
+
+    protected $signature = 'autoReportNode';
+    protected $description = '自动报告节点昨日使用情况';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        if (sysConfig('node_daily_report')) {
+            $nodeList = Node::whereStatus(1)->get();
+            if ($nodeList->isNotEmpty()) {
+                $msg = "|节点|上行流量|下行流量|合计|\r\n| :------ | :------ | :------ |\r\n";
+                foreach ($nodeList as $node) {
+                    $log = NodeDailyDataFlow::whereNodeId($node->id)
+                                            ->whereDate(
+                                                'created_at',
+                                                date(
+                                                    "Y-m-d",
+                                                    strtotime('-1 days')
+                                                )
+                                            )
+                                            ->first();
+
+                    if ($log) {
+                        $msg .= '|' . $node->name . '|' . flowAutoShow(
+                                $log->u
+                            ) . '|' . flowAutoShow(
+                                    $log->d
+                                ) . '|' . $log->traffic . "\r\n";
+                    } else {
+                        $msg .= '|' . $node->name . '|' . flowAutoShow(
+                                0
+                            ) . '|' . flowAutoShow(0) . "|0B\r\n";
+                    }
+                }
+
+                PushNotification::send('节点昨日使用情况', $msg);
+            }
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
 }

+ 44 - 34
app/Console/Commands/AutoStatisticsNodeDailyTraffic.php

@@ -8,38 +8,48 @@ use App\Models\UserDataFlowLog;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoStatisticsNodeDailyTraffic extends Command {
-	protected $signature = 'autoStatisticsNodeDailyTraffic';
-	protected $description = '自动统计节点每日流量';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		foreach(Node::whereStatus(1)->orderBy('id')->get() as $node){
-			$this->statisticsByNode($node->id);
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	private function statisticsByNode($node_id): void {
-		$query = UserDataFlowLog::whereNodeId($node_id)->whereBetween('log_time', [strtotime(date('Y-m-d')), time()]);
-
-		$u = $query->sum('u');
-		$d = $query->sum('d');
-		$total = $u + $d;
-
-		if($total){ // 有数据才记录
-			$obj = new NodeDailyDataFlow();
-			$obj->node_id = $node_id;
-			$obj->u = $u;
-			$obj->d = $d;
-			$obj->total = $total;
-			$obj->traffic = flowAutoShow($total);
-			$obj->save();
-		}
-	}
+class AutoStatisticsNodeDailyTraffic extends Command
+{
+
+    protected $signature = 'autoStatisticsNodeDailyTraffic';
+    protected $description = '自动统计节点每日流量';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        foreach (Node::whereStatus(1)->orderBy('id')->get() as $node) {
+            $this->statisticsByNode($node->id);
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    private function statisticsByNode($node_id): void
+    {
+        $query = UserDataFlowLog::whereNodeId($node_id)->whereBetween(
+            'log_time',
+            [strtotime(date('Y-m-d')), time()]
+        );
+
+        $u     = $query->sum('u');
+        $d     = $query->sum('d');
+        $total = $u + $d;
+
+        if ($total) { // 有数据才记录
+            $obj          = new NodeDailyDataFlow();
+            $obj->node_id = $node_id;
+            $obj->u       = $u;
+            $obj->d       = $d;
+            $obj->total   = $total;
+            $obj->traffic = flowAutoShow($total);
+            $obj->save();
+        }
+    }
+
 }

+ 44 - 34
app/Console/Commands/AutoStatisticsNodeHourlyTraffic.php

@@ -8,38 +8,48 @@ use App\Models\UserDataFlowLog;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoStatisticsNodeHourlyTraffic extends Command {
-	protected $signature = 'autoStatisticsNodeHourlyTraffic';
-	protected $description = '自动统计节点每小时流量';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		foreach(Node::whereStatus(1)->orderBy('id')->get() as $node){
-			$this->statisticsByNode($node->id);
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	private function statisticsByNode($node_id): void {
-		$query = UserDataFlowLog::whereNodeId($node_id)->whereBetween('log_time', [strtotime("-1 hour"), time()]);
-
-		$u = $query->sum('u');
-		$d = $query->sum('d');
-		$total = $u + $d;
-
-		if($total){ // 有数据才记录
-			$obj = new NodeHourlyDataFlow();
-			$obj->node_id = $node_id;
-			$obj->u = $u;
-			$obj->d = $d;
-			$obj->total = $total;
-			$obj->traffic = flowAutoShow($total);
-			$obj->save();
-		}
-	}
+class AutoStatisticsNodeHourlyTraffic extends Command
+{
+
+    protected $signature = 'autoStatisticsNodeHourlyTraffic';
+    protected $description = '自动统计节点每小时流量';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        foreach (Node::whereStatus(1)->orderBy('id')->get() as $node) {
+            $this->statisticsByNode($node->id);
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    private function statisticsByNode($node_id): void
+    {
+        $query = UserDataFlowLog::whereNodeId($node_id)->whereBetween(
+            'log_time',
+            [strtotime("-1 hour"), time()]
+        );
+
+        $u     = $query->sum('u');
+        $d     = $query->sum('d');
+        $total = $u + $d;
+
+        if ($total) { // 有数据才记录
+            $obj          = new NodeHourlyDataFlow();
+            $obj->node_id = $node_id;
+            $obj->u       = $u;
+            $obj->d       = $d;
+            $obj->total   = $total;
+            $obj->traffic = flowAutoShow($total);
+            $obj->save();
+        }
+    }
+
 }

+ 55 - 45
app/Console/Commands/AutoStatisticsUserDailyTraffic.php

@@ -9,49 +9,59 @@ use App\Models\UserDataFlowLog;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoStatisticsUserDailyTraffic extends Command {
-	protected $signature = 'autoStatisticsUserDailyTraffic';
-	protected $description = '自动统计用户每日流量';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		foreach(User::activeUser()->get() as $user){
-			// 统计一次所有节点的总和
-			$this->statisticsByUser($user->id);
-
-			// 统计每个节点产生的流量
-			foreach(Node::whereStatus(1)->orderBy('id')->get() as $node){
-				$this->statisticsByUser($user->id, $node->id);
-			}
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	private function statisticsByUser($user_id, $node_id = 0): void {
-		$query = UserDataFlowLog::whereUserId($user_id)->whereBetween('log_time', [strtotime(date('Y-m-d')), time()]);
-
-		if($node_id){
-			$query->whereNodeId($node_id);
-		}
-
-		$u = $query->sum('u');
-		$d = $query->sum('d');
-		$total = $u + $d;
-
-		if($total){ // 有数据才记录
-			$obj = new UserDailyDataFlow();
-			$obj->user_id = $user_id;
-			$obj->node_id = $node_id;
-			$obj->u = $u;
-			$obj->d = $d;
-			$obj->total = $total;
-			$obj->traffic = flowAutoShow($total);
-			$obj->save();
-		}
-	}
+class AutoStatisticsUserDailyTraffic extends Command
+{
+
+    protected $signature = 'autoStatisticsUserDailyTraffic';
+    protected $description = '自动统计用户每日流量';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        foreach (User::activeUser()->get() as $user) {
+            // 统计一次所有节点的总和
+            $this->statisticsByUser($user->id);
+
+            // 统计每个节点产生的流量
+            foreach (Node::whereStatus(1)->orderBy('id')->get() as $node) {
+                $this->statisticsByUser($user->id, $node->id);
+            }
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    private function statisticsByUser($user_id, $node_id = 0): void
+    {
+        $query = UserDataFlowLog::whereUserId($user_id)->whereBetween(
+            'log_time',
+            [strtotime(date('Y-m-d')), time()]
+        );
+
+        if ($node_id) {
+            $query->whereNodeId($node_id);
+        }
+
+        $u     = $query->sum('u');
+        $d     = $query->sum('d');
+        $total = $u + $d;
+
+        if ($total) { // 有数据才记录
+            $obj          = new UserDailyDataFlow();
+            $obj->user_id = $user_id;
+            $obj->node_id = $node_id;
+            $obj->u       = $u;
+            $obj->d       = $d;
+            $obj->total   = $total;
+            $obj->traffic = flowAutoShow($total);
+            $obj->save();
+        }
+    }
+
 }

+ 55 - 45
app/Console/Commands/AutoStatisticsUserHourlyTraffic.php

@@ -9,49 +9,59 @@ use App\Models\UserHourlyDataFlow;
 use Illuminate\Console\Command;
 use Log;
 
-class AutoStatisticsUserHourlyTraffic extends Command {
-	protected $signature = 'autoStatisticsUserHourlyTraffic';
-	protected $description = '自动统计用户每小时流量';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		foreach(User::activeUser()->get() as $user){
-			// 统计一次所有节点的总和
-			$this->statisticsByNode($user->id);
-
-			// 统计每个节点产生的流量
-			foreach(Node::whereStatus(1)->orderBy('id')->get() as $node){
-				$this->statisticsByNode($user->id, $node->id);
-			}
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	private function statisticsByNode($user_id, $node_id = 0): void {
-		$query = UserDataFlowLog::whereUserId($user_id)->whereBetween('log_time', [strtotime("-1 hour"), time()]);
-
-		if($node_id){
-			$query->whereNodeId($node_id);
-		}
-
-		$u = $query->sum('u');
-		$d = $query->sum('d');
-		$total = $u + $d;
-
-		if($total){ // 有数据才记录
-			$obj = new UserHourlyDataFlow();
-			$obj->user_id = $user_id;
-			$obj->node_id = $node_id;
-			$obj->u = $u;
-			$obj->d = $d;
-			$obj->total = $total;
-			$obj->traffic = flowAutoShow($total);
-			$obj->save();
-		}
-	}
+class AutoStatisticsUserHourlyTraffic extends Command
+{
+
+    protected $signature = 'autoStatisticsUserHourlyTraffic';
+    protected $description = '自动统计用户每小时流量';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        foreach (User::activeUser()->get() as $user) {
+            // 统计一次所有节点的总和
+            $this->statisticsByNode($user->id);
+
+            // 统计每个节点产生的流量
+            foreach (Node::whereStatus(1)->orderBy('id')->get() as $node) {
+                $this->statisticsByNode($user->id, $node->id);
+            }
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    private function statisticsByNode($user_id, $node_id = 0): void
+    {
+        $query = UserDataFlowLog::whereUserId($user_id)->whereBetween(
+            'log_time',
+            [strtotime("-1 hour"), time()]
+        );
+
+        if ($node_id) {
+            $query->whereNodeId($node_id);
+        }
+
+        $u     = $query->sum('u');
+        $d     = $query->sum('d');
+        $total = $u + $d;
+
+        if ($total) { // 有数据才记录
+            $obj          = new UserHourlyDataFlow();
+            $obj->user_id = $user_id;
+            $obj->node_id = $node_id;
+            $obj->u       = $u;
+            $obj->d       = $d;
+            $obj->total   = $total;
+            $obj->traffic = flowAutoShow($total);
+            $obj->save();
+        }
+    }
+
 }

+ 183 - 128
app/Console/Commands/DailyJob.php

@@ -13,132 +13,187 @@ use App\Services\OrderService;
 use Illuminate\Console\Command;
 use Log;
 
-class DailyJob extends Command {
-	protected $signature = 'dailyJob';
-	protected $description = '每日任务';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 过期用户处理
-		$this->expireUser();
-
-		// 关闭超过72小时未处理的工单
-		$this->closeTickets();
-
-		// 重置用户流量
-		if(sysConfig('reset_traffic')){
-			$this->resetUserTraffic();
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	private function expireUser(): void {
-		// 过期用户处理
-		$userList = User::activeUser()->where('expired_at', '<', date('Y-m-d'))->get();
-		$isBanStatus = sysConfig('is_ban_status');
-		foreach($userList as $user){
-			if($isBanStatus){
-				$user->update([
-					'u'               => 0,
-					'd'               => 0,
-					'transfer_enable' => 0,
-					'enable'          => 0,
-					'reset_time'      => null,
-					'ban_time'        => null,
-					'status'          => -1
-				]);
-
-				$this->addUserBanLog($user->id, 0, '【禁止登录,清空账户】-账号已过期');
-
-				// 废除其名下邀请码
-				Invite::whereInviterId($user->id)->whereStatus(0)->update(['status' => 2]);
-
-				// 写入用户流量变动记录
-				Helpers::addUserTrafficModifyLog($user->id, 0, $user->transfer_enable, 0, '[定时任务]账号已过期(禁止登录,清空账户)');
-			}else{
-				$user->update([
-					'u'               => 0,
-					'd'               => 0,
-					'transfer_enable' => 0,
-					'enable'          => 0,
-					'reset_time'      => null,
-					'ban_time'        => null
-				]);
-
-				$this->addUserBanLog($user->id, 0, '【封禁代理,清空账户】-账号已过期');
-
-				// 写入用户流量变动记录
-				Helpers::addUserTrafficModifyLog($user->id, 0, $user->transfer_enable, 0, '[定时任务]账号已过期(封禁代理,清空账户)');
-			}
-		}
-	}
-
-	/**
-	 * 添加用户封禁日志
-	 *
-	 * @param  int     $userId       用户ID
-	 * @param  int     $time         封禁时长,单位分钟
-	 * @param  string  $description  封禁理由
-	 * @return bool
-	 */
-	private function addUserBanLog($userId, $time, $description): bool {
-		$log = new UserBanedLog();
-		$log->user_id = $userId;
-		$log->time = $time;
-		$log->description = $description;
-		return $log->save();
-	}
-
-	// 关闭超过72小时未处理的工单
-	private function closeTickets(): void {
-		$ticketList = Ticket::where('updated_at', '<=', date('Y-m-d', strtotime("-3 days")))->whereStatus(1)->get();
-		foreach($ticketList as $ticket){
-			$ret = Ticket::whereId($ticket->id)->update(['status' => 2]);
-			if($ret){
-				PushNotification::send('工单关闭提醒', '工单:ID'.$ticket->id.'超过72小时未处理,系统已自动关闭');
-			}
-		}
-	}
-
-	// 重置用户流量
-	private function resetUserTraffic(): void {
-		$userList = User::where('status', '<>', -1)
-		                ->where('expired_at', '>', date('Y-m-d'))
-		                ->where('reset_time', '<=', date('Y-m-d'))
-		                ->get();
-		foreach($userList as $user){
-			// 跳过 没有重置日期的账号
-			if(!$user->reset_time){
-				continue;
-			}
-
-			// 取出用户正在使用的套餐
-			$order = Order::userActivePlan($user->id)->first();
-
-			// 无订单用户跳过
-			if(!$order){
-				continue;
-			}
-
-			// 过期生效中的加油包
-			Order::userActivePackage($user->id)->update(['is_expire' => 1]);
-
-			$oldData = $user->transfer_enable;
-			// 重置流量与重置日期 TODO 可用流量变动日志加入至UserObserver
-			$ret = $user->update((new OrderService($order))->resetTimeAndData($user->expired_at));
-			if($ret){
-				// 可用流量变动日志
-				Helpers::addUserTrafficModifyLog($order->user_id, $order->id, $oldData, $user->transfer_enable,
-					'【流量重置】重置可用流量');
-				Log::info('用户[ID:'.$user->id.'  昵称: '.$user->username.'  邮箱: '.$user->email.'] 流量重置为 '.flowAutoShow($user->transfer_enable).'. 重置日期为 '.($user->reset_time?: '【无】'));
-			}else{
-				Log::error('用户[ID:'.$user->id.'  昵称: '.$user->username.'  邮箱: '.$user->email.'] 流量重置失败');
-			}
-		}
-	}
+class DailyJob extends Command
+{
+
+    protected $signature = 'dailyJob';
+    protected $description = '每日任务';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 过期用户处理
+        $this->expireUser();
+
+        // 关闭超过72小时未处理的工单
+        $this->closeTickets();
+
+        // 重置用户流量
+        if (sysConfig('reset_traffic')) {
+            $this->resetUserTraffic();
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    private function expireUser(): void
+    {
+        // 过期用户处理
+        $userList    = User::activeUser()
+                           ->where('expired_at', '<', date('Y-m-d'))
+                           ->get();
+        $isBanStatus = sysConfig('is_ban_status');
+        foreach ($userList as $user) {
+            if ($isBanStatus) {
+                $user->update(
+                    [
+                        'u'               => 0,
+                        'd'               => 0,
+                        'transfer_enable' => 0,
+                        'enable'          => 0,
+                        'reset_time'      => null,
+                        'ban_time'        => null,
+                        'status'          => -1,
+                    ]
+                );
+
+                $this->addUserBanLog($user->id, 0, '【禁止登录,清空账户】-账号已过期');
+
+                // 废除其名下邀请码
+                Invite::whereInviterId($user->id)->whereStatus(0)->update(
+                    ['status' => 2]
+                );
+
+                // 写入用户流量变动记录
+                Helpers::addUserTrafficModifyLog(
+                    $user->id,
+                    0,
+                    $user->transfer_enable,
+                    0,
+                    '[定时任务]账号已过期(禁止登录,清空账户)'
+                );
+            } else {
+                $user->update(
+                    [
+                        'u'               => 0,
+                        'd'               => 0,
+                        'transfer_enable' => 0,
+                        'enable'          => 0,
+                        'reset_time'      => null,
+                        'ban_time'        => null,
+                    ]
+                );
+
+                $this->addUserBanLog($user->id, 0, '【封禁代理,清空账户】-账号已过期');
+
+                // 写入用户流量变动记录
+                Helpers::addUserTrafficModifyLog(
+                    $user->id,
+                    0,
+                    $user->transfer_enable,
+                    0,
+                    '[定时任务]账号已过期(封禁代理,清空账户)'
+                );
+            }
+        }
+    }
+
+    /**
+     * 添加用户封禁日志
+     *
+     * @param  int  $userId  用户ID
+     * @param  int  $time  封禁时长,单位分钟
+     * @param  string  $description  封禁理由
+     *
+     * @return bool
+     */
+    private function addUserBanLog(
+        int $userId,
+        int $time,
+        string $description
+    ): bool {
+        $log              = new UserBanedLog();
+        $log->user_id     = $userId;
+        $log->time        = $time;
+        $log->description = $description;
+
+        return $log->save();
+    }
+
+    // 关闭超过72小时未处理的工单
+    private function closeTickets(): void
+    {
+        $ticketList = Ticket::where(
+            'updated_at',
+            '<=',
+            date('Y-m-d', strtotime("-3 days"))
+        )->whereStatus(1)->get();
+        foreach ($ticketList as $ticket) {
+            $ret = Ticket::whereId($ticket->id)->update(['status' => 2]);
+            if ($ret) {
+                PushNotification::send(
+                    '工单关闭提醒',
+                    '工单:ID' . $ticket->id . '超过72小时未处理,系统已自动关闭'
+                );
+            }
+        }
+    }
+
+    // 重置用户流量
+    private function resetUserTraffic(): void
+    {
+        $userList = User::where('status', '<>', -1)
+                        ->where('expired_at', '>', date('Y-m-d'))
+                        ->where('reset_time', '<=', date('Y-m-d'))
+                        ->get();
+        foreach ($userList as $user) {
+            // 跳过 没有重置日期的账号
+            if ( ! $user->reset_time) {
+                continue;
+            }
+
+            // 取出用户正在使用的套餐
+            $order = Order::userActivePlan($user->id)->first();
+
+            // 无订单用户跳过
+            if ( ! $order) {
+                continue;
+            }
+
+            // 过期生效中的加油包
+            Order::userActivePackage($user->id)->update(['is_expire' => 1]);
+
+            $oldData = $user->transfer_enable;
+            // 重置流量与重置日期 TODO 可用流量变动日志加入至UserObserver
+            $ret = $user->update(
+                (new OrderService($order))->resetTimeAndData($user->expired_at)
+            );
+            if ($ret) {
+                // 可用流量变动日志
+                Helpers::addUserTrafficModifyLog(
+                    $order->user_id,
+                    $order->id,
+                    $oldData,
+                    $user->transfer_enable,
+                    '【流量重置】重置可用流量'
+                );
+                Log::info(
+                    '用户[ID:' . $user->id . '  昵称: ' . $user->username . '  邮箱: ' . $user->email . '] 流量重置为 ' . flowAutoShow(
+                        $user->transfer_enable
+                    ) . '. 重置日期为 ' . ($user->reset_time ?: '【无】')
+                );
+            } else {
+                Log::error(
+                    '用户[ID:' . $user->id . '  昵称: ' . $user->username . '  邮箱: ' . $user->email . '] 流量重置失败'
+                );
+            }
+        }
+    }
+
 }

+ 133 - 97
app/Console/Commands/NodeBlockedDetection.php

@@ -12,110 +12,146 @@ use Illuminate\Console\Command;
 use Log;
 use Mail;
 
-class NodeBlockedDetection extends Command {
-	protected $signature = 'nodeBlockedDetection';
-	protected $description = '节点阻断检测';
+class NodeBlockedDetection extends Command
+{
 
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-		if(sysConfig('nodes_detection')){
-			if(!Cache::has('LastCheckTime')){
-				$this->checkNodes();
-			}elseif(Cache::get('LastCheckTime') <= time()){
-				$this->checkNodes();
-			}else{
-				Log::info('下次节点阻断检测时间:'.date('Y-m-d H:i:s', Cache::get('LastCheckTime')));
-			}
-		}
+    protected $signature = 'nodeBlockedDetection';
+    protected $description = '节点阻断检测';
 
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+        if (sysConfig('nodes_detection')) {
+            if ( ! Cache::has('LastCheckTime')) {
+                $this->checkNodes();
+            } elseif (Cache::get('LastCheckTime') <= time()) {
+                $this->checkNodes();
+            } else {
+                Log::info(
+                    '下次节点阻断检测时间:' . date(
+                        'Y-m-d H:i:s',
+                        Cache::get('LastCheckTime')
+                    )
+                );
+            }
+        }
 
-		Log::info("---【{$this->description}】完成---,耗时 {$jobUsedTime} 秒");
-	}
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
 
-	// 监测节点状态
-	private function checkNodes(): void {
-		$detectionCheckTimes = sysConfig('detection_check_times');
-		$sendText = false;
-		$message = "| 线路 | 协议 | 状态 |\r\n| ------ | ------ | ------ |\r\n";
-		$additionalMessage = '';
-		foreach(Node::whereIsRelay(0)->whereStatus(1)->where('detection_type', '>', 0)->get() as $node){
-			$info = false;
-			if($node->detection_type == 0){
-				continue;
-			}
-			// 使用DDNS的node先通过gethostbyname获取ipv4地址
-			if($node->is_ddns){
-				$ip = gethostbyname($node->server);
-				if(strcmp($ip, $node->server) != 0){
-					$node->ip = $ip;
-				}else{
-					Log::warning("【节点阻断检测】检测".$node->server."时,IP获取失败".$ip." | ".$node->server);
-					$this->notifyMaster("{$node->name}动态IP获取失败", "节点 {$node->name} : IP获取失败 ");
-				}
-			}
-			if($node->detection_type != 1){
-				$icmpCheck = NetworkDetection::networkCheck($node->ip, true);
-				if($icmpCheck != false && $icmpCheck !== "通讯正常"){
-					$message .= "| ".$node->name." | ICMP | ".$icmpCheck." |\r\n";
-					$sendText = true;
-					$info = true;
-				}
-			}
-			if($node->detection_type != 2){
-				$tcpCheck = NetworkDetection::networkCheck($node->ip, false, $node->single? $node->port : null);
-				if($tcpCheck != false && $tcpCheck !== "通讯正常"){
-					$message .= "| ".$node->name." | TCP | ".$tcpCheck." |\r\n";
-					$sendText = true;
-					$info = true;
-				}
-			}
+        Log::info("---【{$this->description}】完成---,耗时 {$jobUsedTime} 秒");
+    }
 
-			// 节点检测次数
-			if($info && $detectionCheckTimes){
-				// 已通知次数
-				$cacheKey = 'detection_check_times'.$node->id;
-				if(Cache::has($cacheKey)){
-					$times = Cache::get($cacheKey);
-				}else{
-					// 键将保留12小时,多10分钟防意外
-					Cache::put($cacheKey, 1, 43800);
-					$times = 1;
-				}
+    // 监测节点状态
+    private function checkNodes(): void
+    {
+        $detectionCheckTimes = sysConfig('detection_check_times');
+        $sendText            = false;
+        $message             = "| 线路 | 协议 | 状态 |\r\n| ------ | ------ | ------ |\r\n";
+        $additionalMessage   = '';
+        foreach (
+            Node::whereIsRelay(0)->whereStatus(1)->where(
+                'detection_type',
+                '>',
+                0
+            )->get() as $node
+        ) {
+            $info = false;
+            if ($node->detection_type == 0) {
+                continue;
+            }
+            // 使用DDNS的node先通过gethostbyname获取ipv4地址
+            if ($node->is_ddns) {
+                $ip = gethostbyname($node->server);
+                if (strcmp($ip, $node->server) != 0) {
+                    $node->ip = $ip;
+                } else {
+                    Log::warning(
+                        "【节点阻断检测】检测" . $node->server . "时,IP获取失败" . $ip . " | " . $node->server
+                    );
+                    $this->notifyMaster(
+                        "{$node->name}动态IP获取失败",
+                        "节点 {$node->name} : IP获取失败 "
+                    );
+                }
+            }
+            if ($node->detection_type != 1) {
+                $icmpCheck = NetworkDetection::networkCheck($node->ip, true);
+                if ($icmpCheck != false && $icmpCheck !== "通讯正常") {
+                    $message  .= "| " . $node->name . " | ICMP | " . $icmpCheck . " |\r\n";
+                    $sendText = true;
+                    $info     = true;
+                }
+            }
+            if ($node->detection_type != 2) {
+                $tcpCheck = NetworkDetection::networkCheck(
+                    $node->ip,
+                    false,
+                    $node->single ? $node->port : null
+                );
+                if ($tcpCheck != false && $tcpCheck !== "通讯正常") {
+                    $message  .= "| " . $node->name . " | TCP | " . $tcpCheck . " |\r\n";
+                    $sendText = true;
+                    $info     = true;
+                }
+            }
 
-				if($times < $detectionCheckTimes){
-					Cache::increment($cacheKey);
-				}else{
-					Cache::forget($cacheKey);
-					Node::find($node->id)->update(['status' => 0]);
-					$additionalMessage .= "\r\n节点【{$node->name}】自动进入维护状态\r\n";
-				}
-			}
-		}
+            // 节点检测次数
+            if ($info && $detectionCheckTimes) {
+                // 已通知次数
+                $cacheKey = 'detection_check_times' . $node->id;
+                if (Cache::has($cacheKey)) {
+                    $times = Cache::get($cacheKey);
+                } else {
+                    // 键将保留12小时,多10分钟防意外
+                    Cache::put($cacheKey, 1, 43800);
+                    $times = 1;
+                }
 
-		//只有在出现阻断线路时,才会发出警报
-		if($sendText){
-			$this->notifyMaster("节点阻断警告", "阻断日志: \r\n\r\n".$message.$additionalMessage);
-			Log::info("阻断日志: \r\n".$message.$additionalMessage);
-		}
+                if ($times < $detectionCheckTimes) {
+                    Cache::increment($cacheKey);
+                } else {
+                    Cache::forget($cacheKey);
+                    Node::find($node->id)->update(['status' => 0]);
+                    $additionalMessage .= "\r\n节点【{$node->name}】自动进入维护状态\r\n";
+                }
+            }
+        }
 
-		// 随机生成下次检测时间
-		Cache::put('LastCheckTime', time() + random_int(3000, Hour), 3700);
-	}
+        //只有在出现阻断线路时,才会发出警报
+        if ($sendText) {
+            $this->notifyMaster(
+                "节点阻断警告",
+                "阻断日志: \r\n\r\n" . $message . $additionalMessage
+            );
+            Log::info("阻断日志: \r\n" . $message . $additionalMessage);
+        }
+
+        // 随机生成下次检测时间
+        Cache::put('LastCheckTime', time() + random_int(3000, Hour), 3700);
+    }
+
+    /**
+     * 通知管理员
+     *
+     * @param  string  $title  消息标题
+     * @param  string  $content  消息内容
+     *
+     */
+    private function notifyMaster(string $title, string $content): void
+    {
+        $result = PushNotification::send($title, $content);
+        if ( ! $result && sysConfig('webmaster_email')) {
+            $logId = Helpers::addNotificationLog(
+                $title,
+                $content,
+                1,
+                sysConfig('webmaster_email')
+            );
+            Mail::to(sysConfig('webmaster_email'))->send(
+                new nodeCrashWarning($logId)
+            );
+        }
+    }
 
-	/**
-	 * 通知管理员
-	 *
-	 * @param  string  $title    消息标题
-	 * @param  string  $content  消息内容
-	 *
-	 */
-	private function notifyMaster($title, $content): void {
-		$result = PushNotification::send($title, $content);
-		if(!$result && sysConfig('webmaster_email')){
-			$logId = Helpers::addNotificationLog($title, $content, 1, sysConfig('webmaster_email'));
-			Mail::to(sysConfig('webmaster_email'))->send(new nodeCrashWarning($logId));
-		}
-	}
 }

+ 56 - 36
app/Console/Commands/ServiceTimer.php

@@ -7,40 +7,60 @@ use App\Models\Order;
 use Illuminate\Console\Command;
 use Log;
 
-class ServiceTimer extends Command {
-	protected $signature = 'serviceTimer';
-	protected $description = '服务计时器';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 扣减用户到期商品的流量
-		$this->decGoodsTraffic();
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	// 扣减用户到期商品的流量
-	private function decGoodsTraffic(): void {
-		//获取失效的套餐
-		foreach(Order::activePlan()->where('expired_at', '<=', date('Y-m-d H:i:s'))->with('user')->get() as $order){
-			// 清理全部流量,重置重置日期和等级 TODO 可用流量变动日志加入至UserObserver
-			$user = $order->user;
-			$user->update([
-				'u'               => 0,
-				'd'               => 0,
-				'transfer_enable' => 0,
-				'reset_time'      => null,
-				'level'           => 0
-			]);
-			Helpers::addUserTrafficModifyLog($user->id, $order->id, $user->transfer_enable, 0,
-				'[定时任务]用户所购商品到期,扣减商品对应的流量');
-
-			// 过期本订单
-			$order->update(['is_expire' => 1]);
-		}
-	}
+class ServiceTimer extends Command
+{
+
+    protected $signature = 'serviceTimer';
+    protected $description = '服务计时器';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 扣减用户到期商品的流量
+        $this->decGoodsTraffic();
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    // 扣减用户到期商品的流量
+    private function decGoodsTraffic(): void
+    {
+        //获取失效的套餐
+        foreach (
+            Order::activePlan()->where(
+                'expired_at',
+                '<=',
+                date('Y-m-d H:i:s')
+            )->with('user')->get() as $order
+        ) {
+            // 清理全部流量,重置重置日期和等级 TODO 可用流量变动日志加入至UserObserver
+            $user = $order->user;
+            $user->update(
+                [
+                    'u'               => 0,
+                    'd'               => 0,
+                    'transfer_enable' => 0,
+                    'reset_time'      => null,
+                    'level'           => 0,
+                ]
+            );
+            Helpers::addUserTrafficModifyLog(
+                $user->id,
+                $order->id,
+                $user->transfer_enable,
+                0,
+                '[定时任务]用户所购商品到期,扣减商品对应的流量'
+            );
+
+            // 过期本订单
+            $order->update(['is_expire' => 1]);
+        }
+    }
+
 }

+ 65 - 44
app/Console/Commands/UserExpireAutoWarning.php

@@ -10,48 +10,69 @@ use Illuminate\Console\Command;
 use Log;
 use Mail;
 
-class UserExpireAutoWarning extends Command {
-	protected $signature = 'userExpireAutoWarning';
-	protected $description = '用户临近到期自动发邮件提醒';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 用户临近到期自动发邮件提醒
-		if(sysConfig('expire_warning')){
-			$this->userExpireWarning();
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	private function userExpireWarning(): void {
-		$expireDays = sysConfig('expire_days');
-		// 只取SSR没被禁用的用户,其他不用管
-		foreach(User::whereEnable(1)->get() as $user){
-			// 用户名不是邮箱的跳过
-			if(false === filter_var($user->email, FILTER_VALIDATE_EMAIL)){
-				continue;
-			}
-
-			// 计算剩余可用时间
-			$lastCanUseDays = Helpers::daysToNow($user->expired_at);
-			if($lastCanUseDays == 0){
-				$title = '账号过期提醒';
-				$content = '您的账号将于今天晚上【24:00】过期。';
-
-				$logId = Helpers::addNotificationLog($title, $content, 1, $user->email);
-				Mail::to($user->email)->send(new userExpireWarningToday($logId));
-			}elseif($lastCanUseDays > 0 && $lastCanUseDays <= $expireDays){
-				$title = '账号过期提醒';
-				$content = '您的账号还剩'.$lastCanUseDays.'天即将过期。';
-
-				$logId = Helpers::addNotificationLog($title, $content, 1, $user->email);
-				Mail::to($user->email)->send(new userExpireWarning($logId, $lastCanUseDays));
-			}
-		}
-	}
+class UserExpireAutoWarning extends Command
+{
+
+    protected $signature = 'userExpireAutoWarning';
+    protected $description = '用户临近到期自动发邮件提醒';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 用户临近到期自动发邮件提醒
+        if (sysConfig('expire_warning')) {
+            $this->userExpireWarning();
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    private function userExpireWarning(): void
+    {
+        $expireDays = sysConfig('expire_days');
+        // 只取SSR没被禁用的用户,其他不用管
+        foreach (User::whereEnable(1)->get() as $user) {
+            // 用户名不是邮箱的跳过
+            if (false === filter_var($user->email, FILTER_VALIDATE_EMAIL)) {
+                continue;
+            }
+
+            // 计算剩余可用时间
+            $lastCanUseDays = Helpers::daysToNow($user->expired_at);
+            if ($lastCanUseDays == 0) {
+                $title   = '账号过期提醒';
+                $content = '您的账号将于今天晚上【24:00】过期。';
+
+                $logId = Helpers::addNotificationLog(
+                    $title,
+                    $content,
+                    1,
+                    $user->email
+                );
+                Mail::to($user->email)->send(
+                    new userExpireWarningToday($logId)
+                );
+            } elseif ($lastCanUseDays > 0 && $lastCanUseDays <= $expireDays) {
+                $title   = '账号过期提醒';
+                $content = '您的账号还剩' . $lastCanUseDays . '天即将过期。';
+
+                $logId = Helpers::addNotificationLog(
+                    $title,
+                    $content,
+                    1,
+                    $user->email
+                );
+                Mail::to($user->email)->send(
+                    new userExpireWarning($logId, $lastCanUseDays)
+                );
+            }
+        }
+    }
+
 }

+ 65 - 40
app/Console/Commands/UserTrafficAbnormalAutoWarning.php

@@ -7,44 +7,69 @@ use App\Models\UserHourlyDataFlow;
 use Illuminate\Console\Command;
 use Log;
 
-class UserTrafficAbnormalAutoWarning extends Command {
-	protected $signature = 'userTrafficAbnormalAutoWarning';
-	protected $description = '用户流量异常警告';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 用户流量异常警告
-		$this->userTrafficAbnormalWarning();
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	// 用户流量异常警告
-	private function userTrafficAbnormalWarning(): void {
-		// 1小时内流量异常用户(多往前取5分钟,防止数据统计任务执行时间过长导致没有数据)
-		$userTotalTrafficLogs = UserHourlyDataFlow::whereNodeId(0)
-		                                          ->where('total', '>', MB * 50)
-		                                          ->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))
-		                                          ->groupBy('user_id')
-		                                          ->selectRaw("user_id, sum(total) as totalTraffic")
-		                                          ->get(); // 只统计100M以上的记录,加快查询速度
-		$trafficBanValue = sysConfig('traffic_ban_value');
-
-		foreach($userTotalTrafficLogs->load('user') as $log){
-			// 推送通知管理员
-			if($log->totalTraffic > $trafficBanValue * GB){
-				$user = $log->user;
-				$traffic = UserHourlyDataFlow::userRecentUsed($user->id)
-				                             ->selectRaw("user_id, sum(`u`) as totalU, sum(`d`) as totalD, sum(total) as totalTraffic")
-				                             ->first();
-
-				PushNotification::send("流量异常用户提醒",
-					"用户**{$user->email}(ID:{$user->id})**,最近1小时**上行流量:".flowAutoShow($traffic->totalU).",下行流量:".flowAutoShow($traffic->totalD).",共计:".flowAutoShow($traffic->totalTraffic)."**。");
-			}
-		}
-	}
+class UserTrafficAbnormalAutoWarning extends Command
+{
+
+    protected $signature = 'userTrafficAbnormalAutoWarning';
+    protected $description = '用户流量异常警告';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 用户流量异常警告
+        $this->userTrafficAbnormalWarning();
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    // 用户流量异常警告
+    private function userTrafficAbnormalWarning(): void
+    {
+        // 1小时内流量异常用户(多往前取5分钟,防止数据统计任务执行时间过长导致没有数据)
+        $userTotalTrafficLogs = UserHourlyDataFlow::whereNodeId(0)
+                                                  ->where('total', '>', MB * 50)
+                                                  ->where(
+                                                      'created_at',
+                                                      '>=',
+                                                      date(
+                                                          'Y-m-d H:i:s',
+                                                          time() - 3900
+                                                      )
+                                                  )
+                                                  ->groupBy('user_id')
+                                                  ->selectRaw(
+                                                      "user_id, sum(total) as totalTraffic"
+                                                  )
+                                                  ->get(
+                                                  ); // 只统计100M以上的记录,加快查询速度
+        $trafficBanValue      = sysConfig('traffic_ban_value');
+
+        foreach ($userTotalTrafficLogs->load('user') as $log) {
+            // 推送通知管理员
+            if ($log->totalTraffic > $trafficBanValue * GB) {
+                $user    = $log->user;
+                $traffic = UserHourlyDataFlow::userRecentUsed($user->id)
+                                             ->selectRaw(
+                                                 "user_id, sum(`u`) as totalU, sum(`d`) as totalD, sum(total) as totalTraffic"
+                                             )
+                                             ->first();
+
+                PushNotification::send(
+                    "流量异常用户提醒",
+                    "用户**{$user->email}(ID:{$user->id})**,最近1小时**上行流量:" . flowAutoShow(
+                        $traffic->totalU
+                    ) . ",下行流量:" . flowAutoShow(
+                        $traffic->totalD
+                    ) . ",共计:" . flowAutoShow($traffic->totalTraffic) . "**。"
+                );
+            }
+        }
+    }
+
 }

+ 53 - 34
app/Console/Commands/UserTrafficAutoWarning.php

@@ -9,38 +9,57 @@ use Illuminate\Console\Command;
 use Log;
 use Mail;
 
-class UserTrafficAutoWarning extends Command {
-	protected $signature = 'userTrafficAutoWarning';
-	protected $description = '用户流量超过警告阈值自动发邮件提醒';
-
-	public function handle(): void {
-		$jobStartTime = microtime(true);
-
-		// 用户流量超过警告阈值自动发邮件提醒
-		if(sysConfig('traffic_warning')){
-			$this->userTrafficWarning();
-		}
-
-		$jobEndTime = microtime(true);
-		$jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
-
-		Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
-	}
-
-	// 用户流量超过警告阈值自动发邮件提醒
-	private function userTrafficWarning(): void {
-		$trafficWarningPercent = sysConfig('traffic_warning_percent');
-		foreach(User::activeUser()->where('transfer_enable', '>', 0)->get() as $user){
-			// 用户名不是邮箱的跳过
-			if(false === filter_var($user->email, FILTER_VALIDATE_EMAIL)){
-				continue;
-			}
-
-			$usedPercent = round(($user->d + $user->u) / $user->transfer_enable, 2) * 100; // 已使用流量百分比
-			if($usedPercent >= $trafficWarningPercent){
-				$logId = Helpers::addNotificationLog("流量提醒", '流量已使用:'.$usedPercent.'%,请保持关注。', 1, $user->email);
-				Mail::to($user->email)->send(new userTrafficWarning($logId, $usedPercent));
-			}
-		}
-	}
+class UserTrafficAutoWarning extends Command
+{
+
+    protected $signature = 'userTrafficAutoWarning';
+    protected $description = '用户流量超过警告阈值自动发邮件提醒';
+
+    public function handle(): void
+    {
+        $jobStartTime = microtime(true);
+
+        // 用户流量超过警告阈值自动发邮件提醒
+        if (sysConfig('traffic_warning')) {
+            $this->userTrafficWarning();
+        }
+
+        $jobEndTime  = microtime(true);
+        $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
+
+        Log::info(
+            '---【' . $this->description . '】完成---,耗时' . $jobUsedTime . '秒'
+        );
+    }
+
+    // 用户流量超过警告阈值自动发邮件提醒
+    private function userTrafficWarning(): void
+    {
+        $trafficWarningPercent = sysConfig('traffic_warning_percent');
+        foreach (
+            User::activeUser()->where('transfer_enable', '>', 0)->get() as $user
+        ) {
+            // 用户名不是邮箱的跳过
+            if (false === filter_var($user->email, FILTER_VALIDATE_EMAIL)) {
+                continue;
+            }
+
+            $usedPercent = round(
+                               ($user->d + $user->u) / $user->transfer_enable,
+                               2
+                           ) * 100; // 已使用流量百分比
+            if ($usedPercent >= $trafficWarningPercent) {
+                $logId = Helpers::addNotificationLog(
+                    "流量提醒",
+                    '流量已使用:' . $usedPercent . '%,请保持关注。',
+                    1,
+                    $user->email
+                );
+                Mail::to($user->email)->send(
+                    new userTrafficWarning($logId, $usedPercent)
+                );
+            }
+        }
+    }
+
 }

+ 130 - 77
app/Console/Commands/fixDailyTrafficLogError.php

@@ -8,92 +8,145 @@ use App\Models\UserDataFlowLog;
 use Illuminate\Console\Command;
 use Log;
 
-class fixDailyTrafficLogError extends Command {
-	protected $signature = 'fixDailyTrafficLogError';
-	protected $description = '修复原版本的每日流量计算错误';
+class fixDailyTrafficLogError extends Command
+{
 
-	private $end;
+    protected $signature = 'fixDailyTrafficLogError';
+    protected $description = '修复原版本的每日流量计算错误';
 
-	public function handle(): void {
-		// set value
-		$this->end = date('Y-m-d 23:59:59', strtotime("-1 days"));
-		$nodeArray = UserDataFlowLog::distinct()->pluck('node_id')->toArray();
+    private $end;
 
-		Log::info('----------------------------【修复原版本的每日流量计算错误】开始----------------------------');
-		Log::info('----------------------------【节点流量日志修正】开始----------------------------');
-		foreach(NodeDailyDataFlow::all() as $log){
-			NodeDailyDataFlow::whereId($log->id)->update([
-				'created_at' => date('Y-m-d H:i:s', strtotime("$log->created_at -1 days"))
-			]);
-		}
+    public function handle(): void
+    {
+        // set value
+        $this->end = date('Y-m-d 23:59:59', strtotime("-1 days"));
+        $nodeArray = UserDataFlowLog::distinct()->pluck('node_id')->toArray();
 
-		Log::info('----------------------------【添加节点流量日志】开始----------------------------');
-		foreach($nodeArray as $nodeId){
-			$query = UserDataFlowLog::whereNodeId($nodeId)
-			                        ->whereBetween('log_time',
-				                        [strtotime(date('Y-m-d', strtotime("-1 days"))), strtotime($this->end)]);
+        Log::info(
+            '----------------------------【修复原版本的每日流量计算错误】开始----------------------------'
+        );
+        Log::info(
+            '----------------------------【节点流量日志修正】开始----------------------------'
+        );
+        foreach (NodeDailyDataFlow::all() as $log) {
+            NodeDailyDataFlow::whereId($log->id)->update(
+                [
+                    'created_at' => date(
+                        'Y-m-d H:i:s',
+                        strtotime("$log->created_at -1 days")
+                    ),
+                ]
+            );
+        }
 
-			$u = $query->sum('u');
-			$d = $query->sum('d');
-			$total = $u + $d;
+        Log::info(
+            '----------------------------【添加节点流量日志】开始----------------------------'
+        );
+        foreach ($nodeArray as $nodeId) {
+            $query = UserDataFlowLog::whereNodeId($nodeId)
+                                    ->whereBetween(
+                                        'log_time',
+                                        [
+                                            strtotime(
+                                                date(
+                                                    'Y-m-d',
+                                                    strtotime("-1 days")
+                                                )
+                                            ),
+                                            strtotime($this->end),
+                                        ]
+                                    );
 
-			if($total){ // 有数据才记录
-				$obj = new NodeDailyDataFlow();
-				$obj->node_id = $nodeId;
-				$obj->u = $u;
-				$obj->d = $d;
-				$obj->total = $total;
-				$obj->traffic = flowAutoShow($total);
-				$obj->created_at = $this->end;
-				$obj->save();
-			}
-		}
-		Log::info('----------------------------【添加节点流量日志】结束----------------------------');
-		Log::info('----------------------------【节点流量日志修正】结束----------------------------');
-		Log::info('----------------------------【用户流量日志修正】开始----------------------------');
-		foreach(UserDailyDataFlow::all() as $log){
-			UserDailyDataFlow::whereId($log->id)->update([
-				'created_at' => date('Y-m-d H:i:s', strtotime("$log->created_at -1 days"))
-			]);
-		}
-		Log::info('----------------------------【用户个人流量日志修正】开始----------------------------');
-		foreach(UserDataFlowLog::distinct()->pluck('user_id')->toArray() as $userId){
-			// 统计一次所有节点的总和
-			$this->statisticsByUser($userId);
-			// 统计每个节点产生的流量
-			foreach($nodeArray as $nodeId){
-				$this->statisticsByUser($userId, $nodeId);
-			}
-		}
-		Log::info('----------------------------【用户个人流量日志修正】结束----------------------------');
-		Log::info('----------------------------【用户流量日志修正】结束----------------------------');
-		Log::info('----------------------------【修复原版本的每日流量计算错误】结束----------------------------');
-	}
+            $u     = $query->sum('u');
+            $d     = $query->sum('d');
+            $total = $u + $d;
 
-	private function statisticsByUser($user_id, $node_id = 0): void {
-		$query = UserDataFlowLog::whereUserId($user_id)
-		                        ->whereBetween('log_time',
-			                        [strtotime(date('Y-m-d', strtotime("-1 days"))), strtotime($this->end)]);
+            if ($total) { // 有数据才记录
+                $obj             = new NodeDailyDataFlow();
+                $obj->node_id    = $nodeId;
+                $obj->u          = $u;
+                $obj->d          = $d;
+                $obj->total      = $total;
+                $obj->traffic    = flowAutoShow($total);
+                $obj->created_at = $this->end;
+                $obj->save();
+            }
+        }
+        Log::info(
+            '----------------------------【添加节点流量日志】结束----------------------------'
+        );
+        Log::info(
+            '----------------------------【节点流量日志修正】结束----------------------------'
+        );
+        Log::info(
+            '----------------------------【用户流量日志修正】开始----------------------------'
+        );
+        foreach (UserDailyDataFlow::all() as $log) {
+            UserDailyDataFlow::whereId($log->id)->update(
+                [
+                    'created_at' => date(
+                        'Y-m-d H:i:s',
+                        strtotime("$log->created_at -1 days")
+                    ),
+                ]
+            );
+        }
+        Log::info(
+            '----------------------------【用户个人流量日志修正】开始----------------------------'
+        );
+        foreach (
+            UserDataFlowLog::distinct()->pluck('user_id')->toArray() as $userId
+        ) {
+            // 统计一次所有节点的总和
+            $this->statisticsByUser($userId);
+            // 统计每个节点产生的流量
+            foreach ($nodeArray as $nodeId) {
+                $this->statisticsByUser($userId, $nodeId);
+            }
+        }
+        Log::info(
+            '----------------------------【用户个人流量日志修正】结束----------------------------'
+        );
+        Log::info(
+            '----------------------------【用户流量日志修正】结束----------------------------'
+        );
+        Log::info(
+            '----------------------------【修复原版本的每日流量计算错误】结束----------------------------'
+        );
+    }
 
-		if($node_id){
-			$query->whereNodeId($node_id);
-		}
+    private function statisticsByUser($user_id, $node_id = 0): void
+    {
+        $query = UserDataFlowLog::whereUserId($user_id)
+                                ->whereBetween(
+                                    'log_time',
+                                    [
+                                        strtotime(
+                                            date('Y-m-d', strtotime("-1 days"))
+                                        ),
+                                        strtotime($this->end),
+                                    ]
+                                );
 
-		$u = $query->sum('u');
-		$d = $query->sum('d');
-		$total = $u + $d;
+        if ($node_id) {
+            $query->whereNodeId($node_id);
+        }
 
-		if($total){ // 有数据才记录
-			$obj = new UserDailyDataFlow();
-			$obj->user_id = $user_id;
-			$obj->node_id = $node_id;
-			$obj->u = $u;
-			$obj->d = $d;
-			$obj->total = $total;
-			$obj->traffic = flowAutoShow($total);
-			$obj->created_at = $this->end;
-			$obj->save();
-		}
-	}
+        $u     = $query->sum('u');
+        $d     = $query->sum('d');
+        $total = $u + $d;
+
+        if ($total) { // 有数据才记录
+            $obj             = new UserDailyDataFlow();
+            $obj->user_id    = $user_id;
+            $obj->node_id    = $node_id;
+            $obj->u          = $u;
+            $obj->d          = $d;
+            $obj->total      = $total;
+            $obj->traffic    = flowAutoShow($total);
+            $obj->created_at = $this->end;
+            $obj->save();
+        }
+    }
 
 }

+ 29 - 21
app/Console/Commands/updateCoupon.php

@@ -6,27 +6,35 @@ use App\Models\Coupon;
 use Illuminate\Console\Command;
 use Log;
 
-class updateCoupon extends Command {
-	protected $signature = 'updateCoupon';
-	protected $description = '修改原版Coupon至新版';
+class updateCoupon extends Command
+{
 
-	public function handle(): void {
-		Log::info('----------------------------【优惠券转换】开始----------------------------');
-		$coupons = Coupon::withTrashed()->get();
-		foreach($coupons as $coupon){
-			if($coupon->amount){
-				$coupon->value = $coupon->amount / 100;
-			}elseif($coupon->discount){
-				$coupon->value = $coupon->discount * 100;
-			}
+    protected $signature = 'updateCoupon';
+    protected $description = '修改原版Coupon至新版';
+
+    public function handle(): void
+    {
+        Log::info(
+            '----------------------------【优惠券转换】开始----------------------------'
+        );
+        $coupons = Coupon::withTrashed()->get();
+        foreach ($coupons as $coupon) {
+            if ($coupon->amount) {
+                $coupon->value = $coupon->amount / 100;
+            } elseif ($coupon->discount) {
+                $coupon->value = $coupon->discount * 100;
+            }
+
+            if ($coupon->rule === 0) {
+                $coupon->rule = null;
+            } else {
+                $coupon->rule /= 100;
+            }
+            $coupon->save();
+        }
+        Log::info(
+            '----------------------------【优惠券转换】结束----------------------------'
+        );
+    }
 
-			if($coupon->rule === 0){
-				$coupon->rule = null;
-			}else{
-				$coupon->rule /= 100;
-			}
-			$coupon->save();
-		}
-		Log::info('----------------------------【优惠券转换】结束----------------------------');
-	}
 }

+ 43 - 26
app/Console/Commands/updateTextToJson.php

@@ -8,32 +8,49 @@ use App\Models\UserGroup;
 use Illuminate\Console\Command;
 use Log;
 
-class updateTextToJson extends Command {
-	protected $signature = 'updateTextToJson';
-	protected $description = '转换原有数列至新数列';
+class updateTextToJson extends Command
+{
 
-	public function handle(): void {
-		Log::info('----------------------------【数据转换】开始----------------------------');
-		foreach(ReferralApply::all() as $referralApply){
-			$referralApply->link_logs = $this->convertToJson($referralApply->getRawOriginal('link_logs'));
-			$referralApply->save();
-		}
-		Log::info('转换返利表完成');
-		foreach(UserGroup::all() as $userGroup){
-			$userGroup->nodes = $this->convertToJson($userGroup->getRawOriginal('nodes'));
-			$userGroup->save();
-		}
-		Log::info('转换用户分组表完成');
-		foreach(RuleGroup::all() as $ruleGroup){
-			$ruleGroup->rules = $this->convertToJson($ruleGroup->getRawOriginal('rules'));
-			$ruleGroup->nodes = $this->convertToJson($ruleGroup->getRawOriginal('nodes'));
-			$ruleGroup->save();
-		}
-		Log::info('转换审核规则表完成');
-		Log::info('----------------------------【数据转换】结束----------------------------');
-	}
+    protected $signature = 'updateTextToJson';
+    protected $description = '转换原有数列至新数列';
+
+    public function handle(): void
+    {
+        Log::info(
+            '----------------------------【数据转换】开始----------------------------'
+        );
+        foreach (ReferralApply::all() as $referralApply) {
+            $referralApply->link_logs = $this->convertToJson(
+                $referralApply->getRawOriginal('link_logs')
+            );
+            $referralApply->save();
+        }
+        Log::info('转换返利表完成');
+        foreach (UserGroup::all() as $userGroup) {
+            $userGroup->nodes = $this->convertToJson(
+                $userGroup->getRawOriginal('nodes')
+            );
+            $userGroup->save();
+        }
+        Log::info('转换用户分组表完成');
+        foreach (RuleGroup::all() as $ruleGroup) {
+            $ruleGroup->rules = $this->convertToJson(
+                $ruleGroup->getRawOriginal('rules')
+            );
+            $ruleGroup->nodes = $this->convertToJson(
+                $ruleGroup->getRawOriginal('nodes')
+            );
+            $ruleGroup->save();
+        }
+        Log::info('转换审核规则表完成');
+        Log::info(
+            '----------------------------【数据转换】结束----------------------------'
+        );
+    }
+
+    private function convertToJson($string): array
+    {
+        return explode(',', $string);
+    }
 
-	private function convertToJson($string): array {
-		return explode(',', $string);
-	}
 }

+ 37 - 21
app/Console/Commands/updateTicket.php

@@ -7,26 +7,42 @@ use App\Models\User;
 use Illuminate\Console\Command;
 use Log;
 
-class updateTicket extends Command {
-	protected $signature = 'updateTicket';
-	protected $description = '更新工单';
+class updateTicket extends Command
+{
+
+    protected $signature = 'updateTicket';
+    protected $description = '更新工单';
+
+    public function handle(): void
+    {
+        Log::info(
+            '----------------------------【更新工单】开始----------------------------'
+        );
+        // 获取管理员
+        foreach (User::whereIsAdmin(1)->get() as $admin) {
+            Log::info(
+                '----------------------------【更新管理员' . $admin->id . '回复工单】开始----------------------------'
+            );
+            // 获取该管理回复过的工单, 更新工单
+            foreach (TicketReply::whereUserId($admin->id)->get() as $reply) {
+                $ret = TicketReply::whereId($reply->id)->update(
+                    ['user_id' => 0, 'admin_id' => $admin->id]
+                );
+                if ($ret) {
+                    Log::info(
+                        '--- 管理员:' . $admin->email . '回复子单ID:' . $reply->id . ' ---'
+                    );
+                } else {
+                    Log::error('更新回复子单ID:【' . $reply->id . '】 失败!');
+                }
+            }
+            Log::info(
+                '----------------------------【更新管理员' . $admin->id . '回复工单】完成----------------------------'
+            );
+        }
+        Log::info(
+            '----------------------------【更新工单】结束----------------------------'
+        );
+    }
 
-	public function handle(): void {
-		Log::info('----------------------------【更新工单】开始----------------------------');
-		// 获取管理员
-		foreach(User::whereIsAdmin(1)->get() as $admin){
-			Log::info('----------------------------【更新管理员'.$admin->id.'回复工单】开始----------------------------');
-			// 获取该管理回复过的工单, 更新工单
-			foreach(TicketReply::whereUserId($admin->id)->get() as $reply){
-				$ret = TicketReply::whereId($reply->id)->update(['user_id' => 0, 'admin_id' => $admin->id]);
-				if($ret){
-					Log::info('--- 管理员:'.$admin->email.'回复子单ID:'.$reply->id.' ---');
-				}else{
-					Log::error('更新回复子单ID:【'.$reply->id.'】 失败!');
-				}
-			}
-			Log::info('----------------------------【更新管理员'.$admin->id.'回复工单】完成----------------------------');
-		}
-		Log::info('----------------------------【更新工单】结束----------------------------');
-	}
 }

+ 40 - 24
app/Console/Commands/updateUserLevel.php

@@ -8,28 +8,44 @@ use App\Models\User;
 use Illuminate\Console\Command;
 use Log;
 
-class updateUserLevel extends Command {
-	protected $signature = 'updateUserLevel';
-	protected $description = '更新用户等级';
-
-	public function handle(): void {
-		Log::info('----------------------------【用户等级升级】开始----------------------------');
-		// 预设level 0
-		User::where('level', '<>', 0)->update(['level' => 0]);
-
-		// 获取商品列表,取新等级
-		$goodsLevel = Goods::type(2)->where('level', '<>', 0)->pluck('id')->toArray();
-		// 取生效的套餐
-		$orderList = Order::active()->with('goods')->whereIn('goods_id', $goodsLevel)->get();
-		foreach($orderList as $order){
-			$ret = $order->user->update(['level' => $order->goods->level]);
-
-			if($ret){
-				Log::info('用户: '.$order->user_id.', 按照订单'.$order->id.' 等级为'.$order->goods->level);
-			}else{
-				Log::error('用户: '.$order->user_id.' 等级更新失败!');
-			}
-		}
-		Log::info('----------------------------【用户等级升级】结束----------------------------');
-	}
+class updateUserLevel extends Command
+{
+
+    protected $signature = 'updateUserLevel';
+    protected $description = '更新用户等级';
+
+    public function handle(): void
+    {
+        Log::info(
+            '----------------------------【用户等级升级】开始----------------------------'
+        );
+        // 预设level 0
+        User::where('level', '<>', 0)->update(['level' => 0]);
+
+        // 获取商品列表,取新等级
+        $goodsLevel = Goods::type(2)
+                           ->where('level', '<>', 0)
+                           ->pluck('id')
+                           ->toArray();
+        // 取生效的套餐
+        $orderList = Order::active()->with('goods')->whereIn(
+            'goods_id',
+            $goodsLevel
+        )->get();
+        foreach ($orderList as $order) {
+            $ret = $order->user->update(['level' => $order->goods->level]);
+
+            if ($ret) {
+                Log::info(
+                    '用户: ' . $order->user_id . ', 按照订单' . $order->id . ' 等级为' . $order->goods->level
+                );
+            } else {
+                Log::error('用户: ' . $order->user_id . ' 等级更新失败!');
+            }
+        }
+        Log::info(
+            '----------------------------【用户等级升级】结束----------------------------'
+        );
+    }
+
 }

+ 59 - 46
app/Console/Commands/updateUserName.php

@@ -7,60 +7,73 @@ use App\Models\User;
 use Illuminate\Console\Command;
 use Log;
 
-class updateUserName extends Command {
-	protected $signature = 'updateUserName';
-	protected $description = '升级用户昵称';
+class updateUserName extends Command
+{
 
-	public function handle(): void {
-		Log::info('----------------------------【升级用户昵称】开始----------------------------');
+    protected $signature = 'updateUserName';
+    protected $description = '升级用户昵称';
 
-		$userList = User::all();
-		foreach($userList as $user){
-			$name = process($user->id);
-			$user->update(['username' => $name]);
+    public function handle(): void
+    {
+        Log::info(
+            '----------------------------【升级用户昵称】开始----------------------------'
+        );
 
-			Log::info('---用户[ID:'.$user->id.' - '.$user->email.'] :'.$user->username.'---');
-		}
+        $userList = User::all();
+        foreach ($userList as $user) {
+            $name = process($user->id);
+            $user->update(['username' => $name]);
 
-		foreach($userList as $user){
-			if($user->email == $user->username){
-				$name = process($user->id);
+            Log::info(
+                '---用户[ID:' . $user->id . ' - ' . $user->email . '] :' . $user->username . '---'
+            );
+        }
 
-				$user->update(['username' => $name]);
+        foreach ($userList as $user) {
+            if ($user->email == $user->username) {
+                $name = process($user->id);
 
-				Log::info('---用户[ID:'.$user->id.' - '.$user->email.'] :'.$user->username.'---');
-			}
-		}
+                $user->update(['username' => $name]);
+
+                Log::info(
+                    '---用户[ID:' . $user->id . ' - ' . $user->email . '] :' . $user->username . '---'
+                );
+            }
+        }
+
+        Log::info(
+            '----------------------------【升级用户昵称】结束----------------------------'
+        );
+    }
 
-		Log::info('----------------------------【升级用户昵称】结束----------------------------');
-	}
 }
 
-function process($id) {
-	$user = User::find($id);
-	// 先设个默认值
-	$name = $user->email;
-	// 用户是否设置了QQ号
-	if($user->qq){
-		$name = QQInfo::getName3($user->qq);
-		// 检测用户注册是否为QQ邮箱
-	}elseif(stripos($user->email, '@qq') !== false){
-		// 分离QQ邮箱后缀
-		$email = explode('@', $user->email, 2);
-		if(is_numeric($email[0])){
-			$name = QQInfo::getName3($email[0]);
-		}elseif(str_contains($email[0], '.')){
-			$temp = explode('.', $email[0]);
-			if(is_numeric($temp[1])){
-				$name = QQInfo::getName3($temp[1]);
-			}else{
-				echo $user->email.PHP_EOL;
-			}
-		}
-	}
-	if($name == false){
-		$name = $user->email;
-	}
+function process($id)
+{
+    $user = User::find($id);
+    // 先设个默认值
+    $name = $user->email;
+    // 用户是否设置了QQ号
+    if ($user->qq) {
+        $name = QQInfo::getName3($user->qq);
+        // 检测用户注册是否为QQ邮箱
+    } elseif (stripos($user->email, '@qq') !== false) {
+        // 分离QQ邮箱后缀
+        $email = explode('@', $user->email, 2);
+        if (is_numeric($email[0])) {
+            $name = QQInfo::getName3($email[0]);
+        } elseif (str_contains($email[0], '.')) {
+            $temp = explode('.', $email[0]);
+            if (is_numeric($temp[1])) {
+                $name = QQInfo::getName3($temp[1]);
+            } else {
+                echo $user->email . PHP_EOL;
+            }
+        }
+    }
+    if ($name == false) {
+        $name = $user->email;
+    }
 
-	return $name;
+    return $name;
 }

+ 66 - 47
app/Console/Commands/upgradeUserResetTime.php

@@ -6,51 +6,70 @@ use App\Models\User;
 use Illuminate\Console\Command;
 use Log;
 
-class upgradeUserResetTime extends Command {
-	protected $signature = 'upgradeUserResetTime';
-	protected $description = '升级用户重置日期';
-
-	public function handle(): void {
-		Log::info('----------------------------【升级用户重置日期】开始----------------------------');
-
-		foreach(User::all() as $user){
-			$reset_time = null;
-			if($user->traffic_reset_day){
-				$today = date('d');// 今天 日期
-				$last_day = date('t'); //本月最后一天
-				$next_last_day = date('t', strtotime("+1 month"));//下个月最后一天
-				$resetDay = $user->traffic_reset_day;// 用户原本的重置日期
-				// 案例:31 29,重置日 大于 本月最后一天
-				if($resetDay > $last_day){
-					//往后推一个月
-					$resetDay -= $last_day;
-					$reset_time = date('Y-m-'.$resetDay, strtotime("+1 month"));
-					//案例:20<30<31
-				}elseif($resetDay < $last_day && $resetDay > $today){
-					$reset_time = date('Y-m-'.$resetDay);
-					// 本日为重置日
-				}elseif($resetDay == $today){
-					$reset_time = date('Y-m-d', strtotime("+1 month"));
-					//本月已经重置过了
-				}elseif($resetDay < $today){
-					//类似第一种情况,向后推一月
-					if($resetDay > $next_last_day){
-						$resetDay -= $next_last_day;
-						$reset_time = date('Y-m-'.$resetDay, strtotime("+1 month"));
-					}else{
-						$reset_time = date('Y-m-'.$resetDay, strtotime("+1 month"));
-					}
-				}
-				// 用户账号有效期大于重置日期
-				if($reset_time > $user->expired_at){
-					$reset_time = null;
-				}
-				$user->update(['reset_time' => $reset_time]);
-			}
-
-			Log::info('---用户[ID:'.$user->id.' - '.$user->username.' ('.$user->email.')]的新重置日期为'.($reset_time != null? '【'.$reset_time.'】' : '【无】').'---');
-		}
-
-		Log::info('----------------------------【升级用户重置日期】结束----------------------------');
-	}
+class upgradeUserResetTime extends Command
+{
+
+    protected $signature = 'upgradeUserResetTime';
+    protected $description = '升级用户重置日期';
+
+    public function handle(): void
+    {
+        Log::info(
+            '----------------------------【升级用户重置日期】开始----------------------------'
+        );
+
+        foreach (User::all() as $user) {
+            $reset_time = null;
+            if ($user->traffic_reset_day) {
+                $today         = date('d');// 今天 日期
+                $last_day      = date('t'); //本月最后一天
+                $next_last_day = date('t', strtotime("+1 month"));//下个月最后一天
+                $resetDay      = $user->traffic_reset_day;// 用户原本的重置日期
+                // 案例:31 29,重置日 大于 本月最后一天
+                if ($resetDay > $last_day) {
+                    //往后推一个月
+                    $resetDay   -= $last_day;
+                    $reset_time = date(
+                        'Y-m-' . $resetDay,
+                        strtotime("+1 month")
+                    );
+                    //案例:20<30<31
+                } elseif ($resetDay < $last_day && $resetDay > $today) {
+                    $reset_time = date('Y-m-' . $resetDay);
+                    // 本日为重置日
+                } elseif ($resetDay == $today) {
+                    $reset_time = date('Y-m-d', strtotime("+1 month"));
+                    //本月已经重置过了
+                } elseif ($resetDay < $today) {
+                    //类似第一种情况,向后推一月
+                    if ($resetDay > $next_last_day) {
+                        $resetDay   -= $next_last_day;
+                        $reset_time = date(
+                            'Y-m-' . $resetDay,
+                            strtotime("+1 month")
+                        );
+                    } else {
+                        $reset_time = date(
+                            'Y-m-' . $resetDay,
+                            strtotime("+1 month")
+                        );
+                    }
+                }
+                // 用户账号有效期大于重置日期
+                if ($reset_time > $user->expired_at) {
+                    $reset_time = null;
+                }
+                $user->update(['reset_time' => $reset_time]);
+            }
+
+            Log::info(
+                '---用户[ID:' . $user->id . ' - ' . $user->username . ' (' . $user->email . ')]的新重置日期为' . ($reset_time != null ? '【' . $reset_time . '】' : '【无】') . '---'
+            );
+        }
+
+        Log::info(
+            '----------------------------【升级用户重置日期】结束----------------------------'
+        );
+    }
+
 }

+ 65 - 60
app/Console/Kernel.php

@@ -25,67 +25,72 @@ use App\Console\Commands\UserTrafficAutoWarning;
 use Illuminate\Console\Scheduling\Schedule;
 use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
 
-class Kernel extends ConsoleKernel {
-	/**
-	 * The Artisan commands provided by your application.
-	 *
-	 * @var array
-	 */
-	protected $commands = [
-		AutoClearLog::class,
-		AutoJob::class,
-		AutoPingNode::class,
-		AutoReportNode::class,
-		AutoStatisticsNodeDailyTraffic::class,
-		AutoStatisticsNodeHourlyTraffic::class,
-		AutoStatisticsUserDailyTraffic::class,
-		AutoStatisticsUserHourlyTraffic::class,
-		DailyJob::class,
-		fixDailyTrafficLogError::class,
-		NodeBlockedDetection::class,
-		ServiceTimer::class,
-		updateTextToJson::class,
-		updateTicket::class,
-		updateUserLevel::class,
-		updateUserName::class,
-		upgradeUserResetTime::class,
-		UserExpireAutoWarning::class,
-		UserTrafficAbnormalAutoWarning::class,
-		UserTrafficAutoWarning::class,
-	];
+class Kernel extends ConsoleKernel
+{
 
-	/**
-	 * Define the application's command schedule.
-	 *
-	 * @param  Schedule  $schedule
-	 *
-	 * @return void
-	 */
-	protected function schedule(Schedule $schedule) {
-		$schedule->command('autoJob')->everyMinute();
-		$schedule->command('serviceTimer')->everyTenMinutes();
-		$schedule->command('autoClearLog')->everyThirtyMinutes();
-		$schedule->command('nodeBlockedDetection')->everyTenMinutes();
-		$schedule->command('autoStatisticsNodeHourlyTraffic')->hourly();
-		$schedule->command('autoStatisticsUserHourlyTraffic')->hourly();
-		$schedule->command('userTrafficAbnormalAutoWarning')->hourly();
-		$schedule->command('autoPingNode')->twiceDaily();
-		$schedule->command('dailyJob')->daily();
-		$schedule->command('autoReportNode')->dailyAt('09:00');
-		$schedule->command('userTrafficAutoWarning')->dailyAt('10:30');
-		$schedule->command('userExpireAutoWarning')->dailyAt('20:00');
-		$schedule->command('autoStatisticsUserDailyTraffic')->dailyAt('23:55');
-		$schedule->command('autoStatisticsNodeDailyTraffic')->dailyAt('23:57');
-	}
+    /**
+     * The Artisan commands provided by your application.
+     *
+     * @var array
+     */
+    protected $commands = [
+        AutoClearLog::class,
+        AutoJob::class,
+        AutoPingNode::class,
+        AutoReportNode::class,
+        AutoStatisticsNodeDailyTraffic::class,
+        AutoStatisticsNodeHourlyTraffic::class,
+        AutoStatisticsUserDailyTraffic::class,
+        AutoStatisticsUserHourlyTraffic::class,
+        DailyJob::class,
+        fixDailyTrafficLogError::class,
+        NodeBlockedDetection::class,
+        ServiceTimer::class,
+        updateTextToJson::class,
+        updateTicket::class,
+        updateUserLevel::class,
+        updateUserName::class,
+        upgradeUserResetTime::class,
+        UserExpireAutoWarning::class,
+        UserTrafficAbnormalAutoWarning::class,
+        UserTrafficAutoWarning::class,
+    ];
 
-	/**
-	 * Register the commands for the application.
-	 *
-	 * @return void
-	 */
-	protected function commands() {
-		$this->load(__DIR__.'/Commands');
+    /**
+     * Define the application's command schedule.
+     *
+     * @param  Schedule  $schedule
+     *
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        $schedule->command('autoJob')->everyMinute();
+        $schedule->command('serviceTimer')->everyTenMinutes();
+        $schedule->command('autoClearLog')->everyThirtyMinutes();
+        $schedule->command('nodeBlockedDetection')->everyTenMinutes();
+        $schedule->command('autoStatisticsNodeHourlyTraffic')->hourly();
+        $schedule->command('autoStatisticsUserHourlyTraffic')->hourly();
+        $schedule->command('userTrafficAbnormalAutoWarning')->hourly();
+        $schedule->command('autoPingNode')->twiceDaily();
+        $schedule->command('dailyJob')->daily();
+        $schedule->command('autoReportNode')->dailyAt('09:00');
+        $schedule->command('userTrafficAutoWarning')->dailyAt('10:30');
+        $schedule->command('userExpireAutoWarning')->dailyAt('20:00');
+        $schedule->command('autoStatisticsUserDailyTraffic')->dailyAt('23:55');
+        $schedule->command('autoStatisticsNodeDailyTraffic')->dailyAt('23:57');
+    }
+
+    /**
+     * Register the commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        $this->load(__DIR__ . '/Commands');
+
+        require base_path('routes/console.php');
+    }
 
-		require base_path('routes/console.php');
-	}
 }

+ 175 - 113
app/Exceptions/Handler.php

@@ -12,117 +12,179 @@ use Response;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Throwable;
 
-class Handler extends ExceptionHandler {
-	/**
-	 * A list of the exception types that are not reported.
-	 *
-	 * @var array
-	 */
-	protected $dontReport = [];
-
-	/**
-	 * A list of the inputs that are never flashed for validation exceptions.
-	 *
-	 * @var array
-	 */
-	protected $dontFlash = [
-		'password',
-		'password_confirmation',
-	];
-
-	/**
-	 * Report or log an exception.
-	 *
-	 * @param  \Throwable  $exception
-	 * @return void
-	 *
-	 * @throws \Exception
-	 */
-	public function report(Throwable $exception) {
-		// 记录异常来源
-		Log::info('异常来源:'.get_class($exception));
-
-		// 调试模式下记录错误详情
-		if(config('app.debug')){
-			Log::debug('来自链接:'.url()->full());
-			Log::debug($exception);
-		}
-
-		parent::report($exception);
-	}
-
-	/**
-	 * Render an exception into an HTTP response.
-	 *
-	 * @param  \Illuminate\Http\Request  $request
-	 * @param  \Throwable                $exception
-	 * @return \Symfony\Component\HttpFoundation\Response
-	 *
-	 * @throws \Throwable
-	 */
-	public function render($request, Throwable $exception) {
-		// 调试模式下直接返回错误信息
-		if(config('app.debug')){
-			return parent::render($request, $exception);
-		}
-
-		// 捕获访问异常
-		if($exception instanceof NotFoundHttpException){
-			Log::info("异常请求:".$request->fullUrl().",IP:".getClientIp());
-
-			if($request->ajax()){
-				return Response::json(['status' => 'fail', 'message' => trans('error.MissingPage')]);
-			}
-
-			return Response::view('auth.error', ['message' => trans('error.MissingPage')], 404);
-		}
-
-		// 捕获身份校验异常
-		if($exception instanceof AuthenticationException){
-			if($request->ajax()){
-				return Response::json(['status' => 'fail', 'message' => trans('error.Unauthorized')]);
-			}
-
-			return Response::view('auth.error', ['message' => trans('error.Unauthorized')], 401);
-		}
-
-		// 捕获CSRF异常
-		if($exception instanceof TokenMismatchException){
-			if($request->ajax()){
-				return Response::json([
-					'status'  => 'fail',
-					'message' => trans('error.RefreshPage').'<a href="/login" target="_blank">'.trans('error.Refresh').'</a>'
-				]);
-			}
-
-			return Response::view('auth.error',
-				['message' => trans('error.RefreshPage').'<a href="/login" target="_blank">'.trans('error.Refresh').'</a>'],
-				419);
-		}
-
-		// 捕获反射异常
-		if($exception instanceof ReflectionException){
-			if($request->ajax()){
-				return Response::json(['status' => 'fail', 'message' => trans('error.SystemError')]);
-			}
-
-			return Response::view('auth.error', ['message' => trans('error.SystemError')], 500);
-		}
-
-		// 捕获系统错误异常
-		if($exception instanceof ErrorException){
-			if($request->ajax()){
-				return Response::json([
-					'status'  => 'fail',
-					'message' => trans('error.SystemError').', '.trans('error.Visit').'<a href="/logs" target="_blank">'.trans('error.log').'</a>'
-				]);
-			}
-
-			return Response::view('auth.error',
-				['message' => trans('error.SystemError').', '.trans('error.Visit').'<a href="/logs" target="_blank">'.trans('error.log').'</a>'],
-				500);
-		}
-
-		return parent::render($request, $exception);
-	}
+class Handler extends ExceptionHandler
+{
+
+    /**
+     * A list of the exception types that are not reported.
+     *
+     * @var array
+     */
+    protected $dontReport = [];
+
+    /**
+     * A list of the inputs that are never flashed for validation exceptions.
+     *
+     * @var array
+     */
+    protected $dontFlash = [
+        'password',
+        'password_confirmation',
+    ];
+
+    /**
+     * Report or log an exception.
+     *
+     * @param  \Throwable  $exception
+     *
+     * @return void
+     *
+     * @throws \Exception|\Throwable
+     */
+    public function report(Throwable $exception)
+    {
+        // 记录异常来源
+        Log::info('异常来源:' . get_class($exception));
+
+        // 调试模式下记录错误详情
+        if (env('APP_DEBUG')) {
+            Log::debug('来自链接:' . url()->full());
+            Log::debug($exception);
+        }
+
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Throwable  $exception
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \Throwable
+     */
+    public function render($request, Throwable $exception)
+    {
+        // 调试模式下直接返回错误信息
+        if (env('APP_DEBUG')) {
+            return parent::render($request, $exception);
+        }
+
+        // 捕获访问异常
+        if ($exception instanceof NotFoundHttpException) {
+            Log::info("异常请求:" . $request->fullUrl() . ",IP:" . getClientIp());
+
+            if ($request->ajax()) {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => trans('error.MissingPage'),
+                    ]
+                );
+            }
+
+            return Response::view(
+                'auth.error',
+                ['message' => trans('error.MissingPage')],
+                404
+            );
+        }
+
+        // 捕获身份校验异常
+        if ($exception instanceof AuthenticationException) {
+            if ($request->ajax()) {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => trans('error.Unauthorized'),
+                    ]
+                );
+            }
+
+            return Response::view(
+                'auth.error',
+                ['message' => trans('error.Unauthorized')],
+                401
+            );
+        }
+
+        // 捕获CSRF异常
+        if ($exception instanceof TokenMismatchException) {
+            if ($request->ajax()) {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => trans(
+                                         'error.RefreshPage'
+                                     ) . '<a href="/login" target="_blank">' . trans(
+                                         'error.Refresh'
+                                     ) . '</a>',
+                    ]
+                );
+            }
+
+            return Response::view(
+                'auth.error',
+                [
+                    'message' => trans(
+                                     'error.RefreshPage'
+                                 ) . '<a href="/login" target="_blank">' . trans(
+                                     'error.Refresh'
+                                 ) . '</a>',
+                ],
+                419
+            );
+        }
+
+        // 捕获反射异常
+        if ($exception instanceof ReflectionException) {
+            if ($request->ajax()) {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => trans('error.SystemError'),
+                    ]
+                );
+            }
+
+            return Response::view(
+                'auth.error',
+                ['message' => trans('error.SystemError')],
+                500
+            );
+        }
+
+        // 捕获系统错误异常
+        if ($exception instanceof ErrorException) {
+            if ($request->ajax()) {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => trans('error.SystemError') . ', ' . trans(
+                                'error.Visit'
+                            ) . '<a href="/logs" target="_blank">' . trans(
+                                         'error.log'
+                                     ) . '</a>',
+                    ]
+                );
+            }
+
+            return Response::view(
+                'auth.error',
+                [
+                    'message' => trans('error.SystemError') . ', ' . trans(
+                            'error.Visit'
+                        ) . '<a href="/logs" target="_blank">' . trans(
+                                     'error.log'
+                                 ) . '</a>',
+                ],
+                500
+            );
+        }
+
+        return parent::render($request, $exception);
+    }
+
 }

+ 116 - 84
app/Http/Controllers/Admin/AffiliateController.php

@@ -16,88 +16,120 @@ use Response;
  *
  * @package App\Http\Controllers\Controller
  */
-class AffiliateController extends Controller {
-
-	// 提现申请列表
-	public function affiliateList(Request $request) {
-		$email = $request->input('email');
-		$status = $request->input('status');
-
-		$query = ReferralApply::with('user:id,email');
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		if($status){
-			$query->whereStatus($status);
-		}
-
-		$view['applyList'] = $query->latest()->paginate(15)->appends($request->except('page'));
-
-		return view('admin.affiliate.affiliateList', $view);
-	}
-
-	// 提现申请详情
-	public function affiliateDetail(Request $request) {
-		$view['basic'] = ReferralApply::with('user:id,email')->find($request->input('id'));
-		$view['commissions'] = [];
-		if($view['basic'] && $view['basic']->link_logs){
-			$view['commissions'] = ReferralLog::with(['invitee:id,email', 'order.goods:id,name'])
-			                                  ->whereIn('id', $view['basic']->link_logs)
-			                                  ->paginate(15)
-			                                  ->appends($request->except('page'));
-		}
-
-		return view('admin.affiliate.affiliateDetail', $view);
-	}
-
-	// 设置提现申请状态
-	public function setAffiliateStatus(Request $request): JsonResponse {
-		$id = $request->input('id');
-		$status = $request->input('status');
-
-		$ret = ReferralApply::whereId($id)->update(['status' => $status]);
-		if($ret){
-			// 审核申请的时候将关联的
-			$referralApply = ReferralApply::findOrFail($id);
-			if($referralApply && $status == 1){
-				ReferralLog::whereIn('id', $referralApply->link_logs)->update(['status' => 1]);
-			}elseif($referralApply && $status == 2){
-				ReferralLog::whereIn('id', $referralApply->link_logs)->update(['status' => 2]);
-			}
-		}
-
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 用户返利流水记录
-	public function userRebateList(Request $request) {
-		$invitee_email = $request->input('invitee_email');
-		$inviter_email = $request->input('inviter_email');
-		$status = $request->input('status');
-
-		$query = ReferralLog::with(['invitee:id,email', 'inviter:id,email'])->orderBy('status')->latest();
-
-		if(isset($invitee_email)){
-			$query->whereHas('invitee', static function($q) use ($invitee_email) {
-				$q->where('email', 'like', '%'.$invitee_email.'%');
-			});
-		}
-
-		if(isset($inviter_email)){
-			$query->whereHas('inviter', static function($q) use ($inviter_email) {
-				$q->where('email', 'like', '%'.$inviter_email.'%');
-			});
-		}
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['list'] = $query->paginate(15)->appends($request->except('page'));
-
-		return view('admin.affiliate.userRebateList', $view);
-	}
+class AffiliateController extends Controller
+{
+
+    // 提现申请列表
+    public function affiliateList(Request $request)
+    {
+        $email  = $request->input('email');
+        $status = $request->input('status');
+
+        $query = ReferralApply::with('user:id,email');
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        if ($status) {
+            $query->whereStatus($status);
+        }
+
+        $view['applyList'] = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.affiliate.affiliateList', $view);
+    }
+
+    // 提现申请详情
+    public function affiliateDetail(Request $request)
+    {
+        $view['basic']       = ReferralApply::with('user:id,email')->find(
+            $request->input('id')
+        );
+        $view['commissions'] = [];
+        if ($view['basic'] && $view['basic']->link_logs) {
+            $view['commissions'] = ReferralLog::with(
+                ['invitee:id,email', 'order.goods:id,name']
+            )
+                                              ->whereIn(
+                                                  'id',
+                                                  $view['basic']->link_logs
+                                              )
+                                              ->paginate(15)
+                                              ->appends(
+                                                  $request->except('page')
+                                              );
+        }
+
+        return view('admin.affiliate.affiliateDetail', $view);
+    }
+
+    // 设置提现申请状态
+    public function setAffiliateStatus(Request $request): JsonResponse
+    {
+        $id     = $request->input('id');
+        $status = $request->input('status');
+
+        $ret = ReferralApply::whereId($id)->update(['status' => $status]);
+        if ($ret) {
+            // 审核申请的时候将关联的
+            $referralApply = ReferralApply::findOrFail($id);
+            if ($referralApply && $status == 1) {
+                ReferralLog::whereIn('id', $referralApply->link_logs)->update(
+                    ['status' => 1]
+                );
+            } elseif ($referralApply && $status == 2) {
+                ReferralLog::whereIn('id', $referralApply->link_logs)->update(
+                    ['status' => 2]
+                );
+            }
+        }
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 用户返利流水记录
+    public function userRebateList(Request $request)
+    {
+        $invitee_email = $request->input('invitee_email');
+        $inviter_email = $request->input('inviter_email');
+        $status        = $request->input('status');
+
+        $query = ReferralLog::with(['invitee:id,email', 'inviter:id,email'])
+                            ->orderBy('status')
+                            ->latest();
+
+        if (isset($invitee_email)) {
+            $query->whereHas(
+                'invitee',
+                static function ($q) use ($invitee_email) {
+                    $q->where('email', 'like', '%' . $invitee_email . '%');
+                }
+            );
+        }
+
+        if (isset($inviter_email)) {
+            $query->whereHas(
+                'inviter',
+                static function ($q) use ($inviter_email) {
+                    $q->where('email', 'like', '%' . $inviter_email . '%');
+                }
+            );
+        }
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['list'] = $query->paginate(15)->appends($request->except('page'));
+
+        return view('admin.affiliate.userRebateList', $view);
+    }
+
 }

+ 230 - 170
app/Http/Controllers/Admin/CouponController.php

@@ -23,174 +23,234 @@ use Validator;
  *
  * @package App\Http\Controllers\Controller
  */
-class CouponController extends Controller {
-	// 优惠券列表
-	public function couponList(Request $request) {
-		$sn = $request->input('sn');
-		$type = $request->input('type');
-		$status = $request->input('status');
-
-		$query = Coupon::query();
-
-		if(isset($sn)){
-			$query->where('sn', 'like', '%'.$sn.'%');
-		}
-
-		if(isset($type)){
-			$query->whereType($type);
-		}
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['couponList'] = $query->latest()->paginate(15)->appends($request->except('page'));
-
-		return view('admin.coupon.couponList', $view);
-	}
-
-	// 添加优惠券
-	public function addCoupon(Request $request) {
-		if($request->isMethod('POST')){
-			Validator::make($request->all(), [
-				'name'         => 'required',
-				'sn'           => 'unique:coupon',
-				'type'         => 'required|integer|between:1,3',
-				'usable_times' => 'integer|nullable',
-				'num'          => 'required|integer|min:1',
-				'value'        => 'required|numeric|min:0',
-				'start_time'   => 'required|date|before_or_equal:end_time',
-				'end_time'     => 'required|date|after_or_equal:start_time',
-			], [
-				'name.required'              => '请填入卡券名称',
-				'type.required'              => '请选择卡券类型',
-				'type.integer'               => '卡券类型不合法,请重选',
-				'type.between'               => '卡券类型不合法,请重选',
-				'num.required'               => '请填写卡券数量',
-				'num.integer'                => '卡券数量不合法',
-				'num.min'                    => '卡券数量不合法,最小1',
-				'value.required_unless'      => '请填入优惠值',
-				'value.numeric'              => '优惠值金额不合法',
-				'value.min'                  => '优惠值不合法,最小0',
-				'start_time.required'        => '请填入有效期',
-				'start_time.date'            => '有效期不合法',
-				'start_time.before_or_equal' => '有效期不合法',
-				'end_time.required'          => '请填入有效期',
-				'end_time.date'              => '有效期不合法',
-				'end_time.after_or_equal'    => '有效期不合法'
-			]);
-
-			$type = $request->input('type');
-
-			// 优惠卷LOGO
-			$logo = '';
-			if($request->hasFile('logo')){
-				$logo = $this->uploadFile($request->file('logo'));
-
-				if(!$logo){
-					return Redirect::back()->withInput()->withErrors('LOGO不合法');
-				}
-			}
-
-			try{
-				DB::beginTransaction();
-				$num = $request->input('num');
-				for($i = 0; $i < $num; $i++){
-					$obj = new Coupon();
-					$obj->name = $request->input('name');
-					$obj->logo = $logo;
-					$obj->sn = $num == 1 && $request->input('sn')? $request->input('sn') : Str::random(8);
-					$obj->type = $type;
-					$obj->usable_times = $request->input('usable_times');
-					$obj->value = $request->input('value');
-					$obj->rule = $request->input('rule');
-					$obj->start_time = strtotime($request->input('start_time'));
-					$obj->end_time = strtotime($request->input('end_time'));
-					$obj->status = 0;
-					$obj->save();
-				}
-
-				DB::commit();
-
-				return Redirect::back()->with('successMsg', '生成成功');
-			}catch(Exception $e){
-				DB::rollBack();
-
-				Log::error('生成优惠券失败:'.$e->getMessage());
-
-				return Redirect::back()->withInput()->withErrors('生成失败:'.$e->getMessage());
-			}
-		}else{
-			return view('admin.coupon.addCoupon');
-		}
-	}
-
-	// 删除优惠券
-	public function delCoupon(Request $request): JsonResponse {
-		Coupon::find($request->input('id'))->delete();
-
-		return Response::json(['status' => 'success', 'message' => '删除成功']);
-	}
-
-	// 导出卡券
-	public function exportCoupon(): void {
-		$voucherList = Coupon::type(1)->whereStatus(0)->get();
-		$discountCouponList = Coupon::type(2)->whereStatus(0)->get();
-		$refillList = Coupon::type(3)->whereStatus(0)->get();
-
-		$filename = '卡券'.date('Ymd').'.xlsx';
-		$spreadsheet = new Spreadsheet();
-		$spreadsheet->getProperties()
-		            ->setCreator('ProxyPanel')
-		            ->setLastModifiedBy('ProxyPanel')
-		            ->setTitle('邀请码')
-		            ->setSubject('邀请码')
-		            ->setDescription('')
-		            ->setKeywords('')
-		            ->setCategory('');
-
-		// 抵用券
-		$spreadsheet->setActiveSheetIndex(0);
-		$sheet = $spreadsheet->getActiveSheet();
-		$sheet->setTitle('抵用券');
-		$sheet->fromArray(['名称', '使用次数', '有效期', '券码', '金额(元)', '使用限制(元)'], null);
-		foreach($voucherList as $k => $vo){
-			$dateRange = date('Y-m-d', $vo->start_time).' ~ '.date('Y-m-d', $vo->end_time);
-			$sheet->fromArray([$vo->name, $vo->usable_times, $dateRange, $vo->sn, $vo->value, $vo->rule], null,
-				'A'.($k + 2));
-		}
-
-		// 折扣券
-		$spreadsheet->createSheet(1);
-		$spreadsheet->setActiveSheetIndex(1);
-		$sheet = $spreadsheet->getActiveSheet();
-		$sheet->setTitle('折扣券');
-		$sheet->fromArray(['名称', '使用次数', '有效期', '券码', '折扣(折)', '使用限制(元)'], null);
-		foreach($discountCouponList as $k => $vo){
-			$dateRange = date('Y-m-d', $vo->start_time).' ~ '.date('Y-m-d', $vo->end_time);
-			$sheet->fromArray([$vo->name, $vo->usable_times, $dateRange, $vo->sn, $vo->value, $vo->rule], null,
-				'A'.($k + 2));
-		}
-
-		// 充值券
-		$spreadsheet->createSheet(2);
-		$spreadsheet->setActiveSheetIndex(2);
-		$sheet = $spreadsheet->getActiveSheet();
-		$sheet->setTitle('充值券');
-		$sheet->fromArray(['名称', '有效期', '券码', '金额(元)'], null);
-		foreach($refillList as $k => $vo){
-			$dateRange = date('Y-m-d', $vo->start_time).' ~ '.date('Y-m-d', $vo->end_time);
-			$sheet->fromArray([$vo->name, $dateRange, $vo->sn, $vo->value], null, 'A'.($k + 2));
-		}
-
-		// 指针切换回第一个sheet
-		$spreadsheet->setActiveSheetIndex(0);
-
-		header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // 输出07Excel文件
-		//header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件
-		header('Content-Disposition: attachment;filename="'.$filename.'"');
-		header('Cache-Control: max-age=0');
-		$writer = new Xlsx($spreadsheet);
-		$writer->save('php://output');
-	}
+class CouponController extends Controller
+{
+
+    // 优惠券列表
+    public function couponList(Request $request)
+    {
+        $sn     = $request->input('sn');
+        $type   = $request->input('type');
+        $status = $request->input('status');
+
+        $query = Coupon::query();
+
+        if (isset($sn)) {
+            $query->where('sn', 'like', '%' . $sn . '%');
+        }
+
+        if (isset($type)) {
+            $query->whereType($type);
+        }
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['couponList'] = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.coupon.couponList', $view);
+    }
+
+    // 添加优惠券
+    public function addCoupon(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            Validator::make(
+                $request->all(),
+                [
+                    'name'         => 'required',
+                    'sn'           => 'unique:coupon',
+                    'type'         => 'required|integer|between:1,3',
+                    'usable_times' => 'integer|nullable',
+                    'num'          => 'required|integer|min:1',
+                    'value'        => 'required|numeric|min:0',
+                    'start_time'   => 'required|date|before_or_equal:end_time',
+                    'end_time'     => 'required|date|after_or_equal:start_time',
+                ],
+                [
+                    'name.required'              => '请填入卡券名称',
+                    'type.required'              => '请选择卡券类型',
+                    'type.integer'               => '卡券类型不合法,请重选',
+                    'type.between'               => '卡券类型不合法,请重选',
+                    'num.required'               => '请填写卡券数量',
+                    'num.integer'                => '卡券数量不合法',
+                    'num.min'                    => '卡券数量不合法,最小1',
+                    'value.required_unless'      => '请填入优惠值',
+                    'value.numeric'              => '优惠值金额不合法',
+                    'value.min'                  => '优惠值不合法,最小0',
+                    'start_time.required'        => '请填入有效期',
+                    'start_time.date'            => '有效期不合法',
+                    'start_time.before_or_equal' => '有效期不合法',
+                    'end_time.required'          => '请填入有效期',
+                    'end_time.date'              => '有效期不合法',
+                    'end_time.after_or_equal'    => '有效期不合法',
+                ]
+            );
+
+            $type = $request->input('type');
+
+            // 优惠卷LOGO
+            $logo = '';
+            if ($request->hasFile('logo')) {
+                $logo = $this->uploadFile($request->file('logo'));
+
+                if ( ! $logo) {
+                    return Redirect::back()->withInput()->withErrors('LOGO不合法');
+                }
+            }
+
+            try {
+                DB::beginTransaction();
+                $num = $request->input('num');
+                for ($i = 0; $i < $num; $i++) {
+                    $obj               = new Coupon();
+                    $obj->name         = $request->input('name');
+                    $obj->logo         = $logo;
+                    $obj->sn           = $num == 1 && $request->input(
+                        'sn'
+                    ) ? $request->input('sn') : Str::random(8);
+                    $obj->type         = $type;
+                    $obj->usable_times = $request->input('usable_times');
+                    $obj->value        = $request->input('value');
+                    $obj->rule         = $request->input('rule');
+                    $obj->start_time   = strtotime(
+                        $request->input('start_time')
+                    );
+                    $obj->end_time     = strtotime($request->input('end_time'));
+                    $obj->status       = 0;
+                    $obj->save();
+                }
+
+                DB::commit();
+
+                return Redirect::back()->with('successMsg', '生成成功');
+            } catch (Exception $e) {
+                DB::rollBack();
+
+                Log::error('生成优惠券失败:' . $e->getMessage());
+
+                return Redirect::back()->withInput()->withErrors(
+                    '生成失败:' . $e->getMessage()
+                );
+            }
+        } else {
+            return view('admin.coupon.addCoupon');
+        }
+    }
+
+    // 删除优惠券
+    public function delCoupon(Request $request): JsonResponse
+    {
+        Coupon::find($request->input('id'))->delete();
+
+        return Response::json(['status' => 'success', 'message' => '删除成功']);
+    }
+
+    // 导出卡券
+    public function exportCoupon(): void
+    {
+        $voucherList        = Coupon::type(1)->whereStatus(0)->get();
+        $discountCouponList = Coupon::type(2)->whereStatus(0)->get();
+        $refillList         = Coupon::type(3)->whereStatus(0)->get();
+
+        $filename    = '卡券' . date('Ymd') . '.xlsx';
+        $spreadsheet = new Spreadsheet();
+        $spreadsheet->getProperties()
+                    ->setCreator('ProxyPanel')
+                    ->setLastModifiedBy('ProxyPanel')
+                    ->setTitle('邀请码')
+                    ->setSubject('邀请码')
+                    ->setDescription('')
+                    ->setKeywords('')
+                    ->setCategory('');
+
+        // 抵用券
+        $spreadsheet->setActiveSheetIndex(0);
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->setTitle('抵用券');
+        $sheet->fromArray(
+            ['名称', '使用次数', '有效期', '券码', '金额(元)', '使用限制(元)'],
+            null
+        );
+        foreach ($voucherList as $k => $vo) {
+            $dateRange = date('Y-m-d', $vo->start_time) . ' ~ ' . date(
+                    'Y-m-d',
+                    $vo->end_time
+                );
+            $sheet->fromArray(
+                [
+                    $vo->name,
+                    $vo->usable_times,
+                    $dateRange,
+                    $vo->sn,
+                    $vo->value,
+                    $vo->rule,
+                ],
+                null,
+                'A' . ($k + 2)
+            );
+        }
+
+        // 折扣券
+        $spreadsheet->createSheet(1);
+        $spreadsheet->setActiveSheetIndex(1);
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->setTitle('折扣券');
+        $sheet->fromArray(
+            ['名称', '使用次数', '有效期', '券码', '折扣(折)', '使用限制(元)'],
+            null
+        );
+        foreach ($discountCouponList as $k => $vo) {
+            $dateRange = date('Y-m-d', $vo->start_time) . ' ~ ' . date(
+                    'Y-m-d',
+                    $vo->end_time
+                );
+            $sheet->fromArray(
+                [
+                    $vo->name,
+                    $vo->usable_times,
+                    $dateRange,
+                    $vo->sn,
+                    $vo->value,
+                    $vo->rule,
+                ],
+                null,
+                'A' . ($k + 2)
+            );
+        }
+
+        // 充值券
+        $spreadsheet->createSheet(2);
+        $spreadsheet->setActiveSheetIndex(2);
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->setTitle('充值券');
+        $sheet->fromArray(['名称', '有效期', '券码', '金额(元)'], null);
+        foreach ($refillList as $k => $vo) {
+            $dateRange = date('Y-m-d', $vo->start_time) . ' ~ ' . date(
+                    'Y-m-d',
+                    $vo->end_time
+                );
+            $sheet->fromArray(
+                [$vo->name, $dateRange, $vo->sn, $vo->value],
+                null,
+                'A' . ($k + 2)
+            );
+        }
+
+        // 指针切换回第一个sheet
+        $spreadsheet->setActiveSheetIndex(0);
+
+        header(
+            'Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+        ); // 输出07Excel文件
+        //header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件
+        header('Content-Disposition: attachment;filename="' . $filename . '"');
+        header('Cache-Control: max-age=0');
+        $writer = new Xlsx($spreadsheet);
+        $writer->save('php://output');
+    }
+
 }

+ 56 - 41
app/Http/Controllers/Admin/EmailFilterController.php

@@ -16,45 +16,60 @@ use Validator;
  *
  * @package App\Http\Controllers\Controller
  */
-class EmailFilterController extends Controller {
-	// 邮箱过滤列表
-	public function filterList() {
-		$view['list'] = EmailFilter::orderByDesc('id')->paginate(15);
-
-		return view('admin.config.emailFilter', $view);
-	}
-
-	// 添加邮箱后缀
-	public function addSuffix(Request $request): ?JsonResponse {
-		$validator = Validator::make($request->all(), [
-			'words' => 'required|unique:sensitive_words'
-		], [
-			'words.required' => '添加失败:请填写邮箱后缀',
-			'words.unique'   => '添加失败:邮箱后缀已存在'
-		]);
-
-		if($validator->fails()){
-			return Response::json(['status' => 'fail', 'message' => $validator->getMessageBag()->first()]);
-		}
-
-		$obj = new EmailFilter();
-		$obj->type = $request->input('type');
-		$obj->words = strtolower($request->input('words'));
-		$obj->save();
-		if($obj->id){
-			return Response::json(['status' => 'success', 'message' => '添加成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '添加失败']);
-	}
-
-	// 删除邮箱后缀
-	public function delSuffix(Request $request): ?JsonResponse {
-		$result = EmailFilter::whereId($request->input('id'))->delete();
-		if($result){
-			return Response::json(['status' => 'success', 'message' => '删除成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '删除失败']);
-	}
+class EmailFilterController extends Controller
+{
+
+    // 邮箱过滤列表
+    public function filterList()
+    {
+        $view['list'] = EmailFilter::orderByDesc('id')->paginate(15);
+
+        return view('admin.config.emailFilter', $view);
+    }
+
+    // 添加邮箱后缀
+    public function addSuffix(Request $request): ?JsonResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'words' => 'required|unique:sensitive_words',
+            ],
+            [
+                'words.required' => '添加失败:请填写邮箱后缀',
+                'words.unique'   => '添加失败:邮箱后缀已存在',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'message' => $validator->getMessageBag()->first(),
+                ]
+            );
+        }
+
+        $obj        = new EmailFilter();
+        $obj->type  = $request->input('type');
+        $obj->words = strtolower($request->input('words'));
+        $obj->save();
+        if ($obj->id) {
+            return Response::json(['status' => 'success', 'message' => '添加成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '添加失败']);
+    }
+
+    // 删除邮箱后缀
+    public function delSuffix(Request $request): ?JsonResponse
+    {
+        $result = EmailFilter::whereId($request->input('id'))->delete();
+        if ($result) {
+            return Response::json(['status' => 'success', 'message' => '删除成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '删除失败']);
+    }
+
 }

+ 102 - 83
app/Http/Controllers/Admin/MarketingController.php

@@ -20,88 +20,107 @@ use RuntimeException;
  *
  * @package App\Http\Controllers\Controller
  */
-class MarketingController extends Controller {
-	// 邮件群发消息列表
-	public function emailList(Request $request) {
-		$status = $request->input('status');
+class MarketingController extends Controller
+{
+
+    // 邮件群发消息列表
+    public function emailList(Request $request)
+    {
+        $status = $request->input('status');
+
+        $query = Marketing::whereType(1);
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['list'] = $query->paginate(15)->appends($request->except('page'));
+
+        return view('admin.marketing.emailList', $view);
+    }
+
+    // 消息通道群发列表
+    public function pushList(Request $request)
+    {
+        $status = $request->input('status');
+
+        $query = Marketing::whereType(2);
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['list'] = $query->paginate(15)->appends($request->except('page'));
+
+        return view('admin.marketing.pushList', $view);
+    }
+
+    // 添加推送消息
+    public function addPushMarketing(Request $request): ?JsonResponse
+    {
+        $title   = $request->input('title');
+        $content = $request->input('content');
+
+        if ( ! sysConfig('is_push_bear')) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '推送失败:请先启用并配置PushBear']
+            );
+        }
+
+        try {
+            DB::beginTransaction();
+
+            $response = (new Client())->get(
+                'https://pushbear.ftqq.com/sub',
+                [
+                    'query' => [
+                        'sendkey' => sysConfig('push_bear_send_key'),
+                        'text'    => $title,
+                        'desp'    => $content,
+                    ],
+                ]
+            );
+
+            $result = json_decode($response->getBody(), true);
+            if ($result->code) { // 失败
+                $this->addMarketing(2, $title, $content, -1, $result->message);
+
+                throw new RuntimeException($result->message);
+            }
+
+            $this->addMarketing(2, $title, $content, 1);
+
+            DB::commit();
+
+            return Response::json(['status' => 'success', 'message' => '推送成功']);
+        } catch (Exception $e) {
+            Log::error('PushBear消息推送失败:' . $e->getMessage());
+
+            DB::rollBack();
+
+            return Response::json(
+                ['status' => 'fail', 'message' => '推送失败:' . $e->getMessage()]
+            );
+        }
+    }
+
+    private function addMarketing(
+        $type = 1,
+        $title = '',
+        $content = '',
+        $status = 1,
+        $error = '',
+        $receiver = ''
+    ): bool {
+        $marketing           = new Marketing();
+        $marketing->type     = $type;
+        $marketing->receiver = $receiver;
+        $marketing->title    = $title;
+        $marketing->content  = $content;
+        $marketing->error    = $error;
+        $marketing->status   = $status;
+
+        return $marketing->save();
+    }
 
-		$query = Marketing::whereType(1);
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['list'] = $query->paginate(15)->appends($request->except('page'));
-
-		return view('admin.marketing.emailList', $view);
-	}
-
-	// 消息通道群发列表
-	public function pushList(Request $request) {
-		$status = $request->input('status');
-
-		$query = Marketing::whereType(2);
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['list'] = $query->paginate(15)->appends($request->except('page'));
-
-		return view('admin.marketing.pushList', $view);
-	}
-
-	// 添加推送消息
-	public function addPushMarketing(Request $request): ?JsonResponse {
-		$title = $request->input('title');
-		$content = $request->input('content');
-
-		if(!sysConfig('is_push_bear')){
-			return Response::json(['status' => 'fail', 'message' => '推送失败:请先启用并配置PushBear']);
-		}
-
-		try{
-			DB::beginTransaction();
-
-			$response = (new Client())->get('https://pushbear.ftqq.com/sub', [
-				'query' => [
-					'sendkey' => sysConfig('push_bear_send_key'),
-					'text'    => $title,
-					'desp'    => $content
-				]
-			]);
-
-			$result = json_decode($response->getBody(), true);
-			if($result->code){ // 失败
-				$this->addMarketing(2, $title, $content, -1, $result->message);
-
-				throw new RuntimeException($result->message);
-			}
-
-			$this->addMarketing(2, $title, $content, 1);
-
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '推送成功']);
-		}catch(Exception $e){
-			Log::error('PushBear消息推送失败:'.$e->getMessage());
-
-			DB::rollBack();
-
-			return Response::json(['status' => 'fail', 'message' => '推送失败:'.$e->getMessage()]);
-		}
-	}
-
-	private function addMarketing($type = 1, $title = '', $content = '', $status = 1, $error = '', $receiver = ''
-	): bool {
-		$marketing = new Marketing();
-		$marketing->type = $type;
-		$marketing->receiver = $receiver;
-		$marketing->title = $title;
-		$marketing->content = $content;
-		$marketing->error = $error;
-		$marketing->status = $status;
-
-		return $marketing->save();
-	}
 }

+ 143 - 111
app/Http/Controllers/Admin/RuleController.php

@@ -13,115 +13,147 @@ use Illuminate\Http\Request;
 use Response;
 use Validator;
 
-class RuleController extends Controller {
-	// 审计规则列表
-	public function index(Request $request) {
-		$type = $request->input('type');
-		$query = Rule::query();
-
-		if($type){
-			$query->whereType($type);
-		}
-
-		$view['rules'] = $query->paginate(15)->appends($request->except('page'));
-		return view('admin.rule.index', $view);
-	}
-
-	// 添加审计规则
-	public function store(Request $request): JsonResponse {
-		$validator = Validator::make($request->all(), [
-			'type'    => 'required|between:1,4',
-			'name'    => 'required',
-			'pattern' => 'required',
-		]);
-
-		if($validator->fails()){
-			return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
-		}
-
-		$obj = new Rule();
-		$obj->type = $request->input('type');
-		$obj->name = $request->input('name');
-		$obj->pattern = $request->input('pattern');
-		$obj->save();
-
-		if($obj->id){
-			return Response::json(['status' => 'success', 'message' => '提交成功']);
-		}
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 编辑审计规则
-	public function update(Request $request, $id): JsonResponse {
-		$ret = Rule::whereId($id)->update([
-			'name'    => $request->input('rule_name'),
-			'pattern' => $request->input('rule_pattern')
-		]);
-		if($ret){
-			return Response::json(['status' => 'success', 'message' => '操作成功']);
-		}
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 删除审计规则
-	public function destroy($id): JsonResponse {
-		try{
-			Rule::whereId($id)->delete();
-
-			foreach(RuleGroup::all() as $ruleGroup){
-				$rules = $ruleGroup->rules;
-				if($rules && in_array($id, $rules, true)){
-					$ruleGroup->rules = array_merge(array_diff($rules, [$id]));
-					$ruleGroup->save();
-				}
-			}
-		}catch(Exception $e){
-			return Response::json(['status' => 'fail', 'message' => '操作失败, '.$e->getMessage()]);
-		}
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 用户触发审计规则日志
-	public function ruleLogList(Request $request) {
-		$uid = $request->input('uid');
-		$email = $request->input('email');
-		$nodeId = $request->input('node_id');
-		$ruleId = $request->input('rule_id');
-		$query = RuleLog::query();
-
-		if($uid){
-			$query->whereUserId($uid);
-		}
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-		if($nodeId){
-			$query->whereNodeId($nodeId);
-		}
-		if($ruleId){
-			$query->whereRuleId($ruleId);
-		}
-
-		$view['nodeList'] = Node::all();
-		$view['ruleList'] = Rule::all();
-		$view['ruleLogs'] = $query->latest()->paginate(15)->appends($request->except('page'));
-		return view('admin.rule.log', $view);
-	}
-
-	// 清除所有审计触发日志
-	public function clearLog(): JsonResponse {
-		try{
-			$ret = RuleLog::query()->delete();
-		}catch(Exception $e){
-			return Response::json(['status' => 'fail', 'message' => '清理失败, '.$e->getMessage()]);
-		}
-		$result = RuleLog::doesntExist();
-		if($ret || $result){
-			return Response::json(['status' => 'success', 'message' => '清理成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '清理失败']);
-	}
+class RuleController extends Controller
+{
+
+    // 审计规则列表
+    public function index(Request $request)
+    {
+        $type  = $request->input('type');
+        $query = Rule::query();
+
+        if ($type) {
+            $query->whereType($type);
+        }
+
+        $view['rules'] = $query->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.rule.index', $view);
+    }
+
+    // 添加审计规则
+    public function store(Request $request): JsonResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'type'    => 'required|between:1,4',
+                'name'    => 'required',
+                'pattern' => 'required',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Response::json(
+                ['status' => 'fail', 'message' => $validator->errors()->all()]
+            );
+        }
+
+        $obj          = new Rule();
+        $obj->type    = $request->input('type');
+        $obj->name    = $request->input('name');
+        $obj->pattern = $request->input('pattern');
+        $obj->save();
+
+        if ($obj->id) {
+            return Response::json(['status' => 'success', 'message' => '提交成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 编辑审计规则
+    public function update(Request $request, $id): JsonResponse
+    {
+        $ret = Rule::whereId($id)->update(
+            [
+                'name'    => $request->input('rule_name'),
+                'pattern' => $request->input('rule_pattern'),
+            ]
+        );
+        if ($ret) {
+            return Response::json(['status' => 'success', 'message' => '操作成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 删除审计规则
+    public function destroy($id): JsonResponse
+    {
+        try {
+            Rule::whereId($id)->delete();
+
+            foreach (RuleGroup::all() as $ruleGroup) {
+                $rules = $ruleGroup->rules;
+                if ($rules && in_array($id, $rules, true)) {
+                    $ruleGroup->rules = array_merge(array_diff($rules, [$id]));
+                    $ruleGroup->save();
+                }
+            }
+        } catch (Exception $e) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '操作失败, ' . $e->getMessage()]
+            );
+        }
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 用户触发审计规则日志
+    public function ruleLogList(Request $request)
+    {
+        $uid    = $request->input('uid');
+        $email  = $request->input('email');
+        $nodeId = $request->input('node_id');
+        $ruleId = $request->input('rule_id');
+        $query  = RuleLog::query();
+
+        if ($uid) {
+            $query->whereUserId($uid);
+        }
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+        if ($nodeId) {
+            $query->whereNodeId($nodeId);
+        }
+        if ($ruleId) {
+            $query->whereRuleId($ruleId);
+        }
+
+        $view['nodeList'] = Node::all();
+        $view['ruleList'] = Rule::all();
+        $view['ruleLogs'] = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.rule.log', $view);
+    }
+
+    // 清除所有审计触发日志
+    public function clearLog(): JsonResponse
+    {
+        try {
+            $ret = RuleLog::query()->delete();
+        } catch (Exception $e) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '清理失败, ' . $e->getMessage()]
+            );
+        }
+        $result = RuleLog::doesntExist();
+        if ($ret || $result) {
+            return Response::json(['status' => 'success', 'message' => '清理成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '清理失败']);
+    }
+
 }

+ 147 - 123
app/Http/Controllers/Admin/RuleGroupController.php

@@ -15,127 +15,151 @@ use Redirect;
 use Response;
 use Validator;
 
-class RuleGroupController extends Controller {
-	// 审计规则分组列表
-	public function index(Request $request) {
-		$view['ruleGroupList'] = RuleGroup::paginate(15)->appends($request->except('page'));
-
-		return view('admin.rule.group.index', $view);
-	}
-
-	// 添加审计规则分组页面
-	public function create() {
-		$view['ruleList'] = Rule::all();
-
-		return view('admin.rule.group.info', $view);
-	}
-
-	// 添加审计规则分组
-	public function store(Request $request): RedirectResponse {
-		$validator = Validator::make($request->all(), [
-			'name'  => 'required',
-			'type'  => 'required|boolean',
-			'rules' => 'required',
-		]);
-
-		if($validator->fails()){
-			return Redirect::back()->withInput()->withErrors($validator->errors());
-		}
-
-		$obj = new RuleGroup();
-		$obj->name = $request->input('name');
-		$obj->type = (int) $request->input('type');
-		$obj->rules = $request->input('rules');
-		$obj->save();
-
-		if($obj->id){
-			return Redirect::back()->with('successMsg', '操作成功');
-		}
-		return Redirect::back()->withInput()->withErrors('操作失败');
-	}
-
-	// 编辑审计规则分组页面
-	public function edit($id) {
-		$view['ruleGroup'] = RuleGroup::findOrFail($id);
-		$view['ruleList'] = Rule::all();
-
-		return view('admin.rule.group.info', $view);
-	}
-
-	// 编辑审计规则分组
-	public function update(Request $request, $id): RedirectResponse {
-		$validator = Validator::make($request->all(), [
-			'name' => 'required',
-			'type' => 'required|boolean'
-		]);
-		if($validator->fails()){
-			return Redirect::back()->withInput()->withErrors($validator->errors());
-		}
-		$name = $request->input('name');
-		$type = (int) $request->input('type');
-		$rules = $request->input('rules');
-		$ruleGroup = RuleGroup::findOrFail($id);
-
-		$ruleGroup->name = $name;
-		$ruleGroup->type = $type;
-		$ruleGroup->rules = $rules;
-		if($ruleGroup->save()){
-			return Redirect::back()->with('successMsg', '操作成功');
-		}
-
-		return Redirect::back()->withInput()->withErrors('操作失败');
-	}
-
-	// 删除审计规则分组
-	public function destroy($id): JsonResponse {
-		try{
-			RuleGroup::whereId($id)->delete();
-			RuleGroupNode::whereRuleGroupId($id)->delete();
-
-		}catch(Exception $e){
-			return Response::json(['status' => 'fail', 'message' => '删除失败,'.$e->getMessage()]);
-		}
-
-		return Response::json(['status' => 'success', 'message' => '清理成功']);
-	}
-
-	// 规则分组关联节点
-	public function assignNode($id) {
-		$view['ruleGroup'] = RuleGroup::find($id);
-		$view['nodeList'] = Node::all();
-
-		return view('admin.rule.group.assign', $view);
-	}
-
-	public function assign(Request $request, $id) {
-		$nodes = $request->input('nodes');
-		$ruleGroup = RuleGroup::findOrFail($id);
-
-		try{
-			if($ruleGroup->nodes === $nodes){
-				return Redirect::back()->with('successMsg', '检测为未修改,无变动!');
-			}
-			RuleGroupNode::whereRuleGroupId($id)->delete();
-			if($nodes){
-				$ruleGroup->nodes = $nodes;
-				if(!$ruleGroup->save()){
-					return Redirect::back()->withErrors("更新错误!");
-				}
-
-				foreach($nodes as $nodeId){
-					$obj = new RuleGroupNode();
-					$obj->rule_group_id = $id;
-					$obj->node_id = $nodeId;
-					$obj->save();
-				}
-			}else{
-				RuleGroup::whereId($id)->update(['nodes' => null]);
-			}
-
-		}catch(Exception $e){
-			return Redirect::back()->withInput()->withErrors($e->getMessage());
-		}
-
-		return Redirect::back()->with('successMsg', '操作成功');
-	}
+class RuleGroupController extends Controller
+{
+
+    // 审计规则分组列表
+    public function index(Request $request)
+    {
+        $view['ruleGroupList'] = RuleGroup::paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.rule.group.index', $view);
+    }
+
+    // 添加审计规则分组页面
+    public function create()
+    {
+        $view['ruleList'] = Rule::all();
+
+        return view('admin.rule.group.info', $view);
+    }
+
+    // 添加审计规则分组
+    public function store(Request $request): RedirectResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'name'  => 'required',
+                'type'  => 'required|boolean',
+                'rules' => 'required',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Redirect::back()->withInput()->withErrors(
+                $validator->errors()
+            );
+        }
+
+        $obj        = new RuleGroup();
+        $obj->name  = $request->input('name');
+        $obj->type  = (int)$request->input('type');
+        $obj->rules = $request->input('rules');
+        $obj->save();
+
+        if ($obj->id) {
+            return Redirect::back()->with('successMsg', '操作成功');
+        }
+
+        return Redirect::back()->withInput()->withErrors('操作失败');
+    }
+
+    // 编辑审计规则分组页面
+    public function edit($id)
+    {
+        $view['ruleGroup'] = RuleGroup::findOrFail($id);
+        $view['ruleList']  = Rule::all();
+
+        return view('admin.rule.group.info', $view);
+    }
+
+    // 编辑审计规则分组
+    public function update(Request $request, $id): RedirectResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'name' => 'required',
+                'type' => 'required|boolean',
+            ]
+        );
+        if ($validator->fails()) {
+            return Redirect::back()->withInput()->withErrors(
+                $validator->errors()
+            );
+        }
+        $name      = $request->input('name');
+        $type      = (int)$request->input('type');
+        $rules     = $request->input('rules');
+        $ruleGroup = RuleGroup::findOrFail($id);
+
+        $ruleGroup->name  = $name;
+        $ruleGroup->type  = $type;
+        $ruleGroup->rules = $rules;
+        if ($ruleGroup->save()) {
+            return Redirect::back()->with('successMsg', '操作成功');
+        }
+
+        return Redirect::back()->withInput()->withErrors('操作失败');
+    }
+
+    // 删除审计规则分组
+    public function destroy($id): JsonResponse
+    {
+        try {
+            RuleGroup::whereId($id)->delete();
+            RuleGroupNode::whereRuleGroupId($id)->delete();
+        } catch (Exception $e) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '删除失败,' . $e->getMessage()]
+            );
+        }
+
+        return Response::json(['status' => 'success', 'message' => '清理成功']);
+    }
+
+    // 规则分组关联节点
+    public function assignNode($id)
+    {
+        $view['ruleGroup'] = RuleGroup::find($id);
+        $view['nodeList']  = Node::all();
+
+        return view('admin.rule.group.assign', $view);
+    }
+
+    public function assign(Request $request, $id)
+    {
+        $nodes     = $request->input('nodes');
+        $ruleGroup = RuleGroup::findOrFail($id);
+
+        try {
+            if ($ruleGroup->nodes === $nodes) {
+                return Redirect::back()->with('successMsg', '检测为未修改,无变动!');
+            }
+            RuleGroupNode::whereRuleGroupId($id)->delete();
+            if ($nodes) {
+                $ruleGroup->nodes = $nodes;
+                if ( ! $ruleGroup->save()) {
+                    return Redirect::back()->withErrors("更新错误!");
+                }
+
+                foreach ($nodes as $nodeId) {
+                    $obj                = new RuleGroupNode();
+                    $obj->rule_group_id = $id;
+                    $obj->node_id       = $nodeId;
+                    $obj->save();
+                }
+            } else {
+                RuleGroup::whereId($id)->update(['nodes' => null]);
+            }
+        } catch (Exception $e) {
+            return Redirect::back()->withInput()->withErrors($e->getMessage());
+        }
+
+        return Redirect::back()->with('successMsg', '操作成功');
+    }
+
 }

+ 180 - 164
app/Http/Controllers/Admin/ShopController.php

@@ -23,168 +23,184 @@ use Validator;
  *
  * @package App\Http\Controllers\Controller
  */
-class ShopController extends Controller {
-	// 商品列表
-	public function index(Request $request) {
-		$type = $request->input('type');
-		$status = $request->input('status');
-
-		$query = Goods::query();
-
-		if(isset($type)){
-			$query->whereType($type);
-		}
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['goodsList'] = $query->orderByDesc('status')->paginate(10)->appends($request->except('page'));
-
-		return view('admin.shop.index', $view);
-	}
-
-	// 添加商品页面
-	public function create() {
-		$view['goods'] = null;
-		$view['levelList'] = Level::orderBy('level')->get();
-
-		return view('admin.shop.info', $view);
-	}
-
-	// 添加商品
-	public function store(Request $request): RedirectResponse {
-		$validator = Validator::make($request->all(), [
-			'name'    => 'required',
-			'traffic' => 'required|integer|min:1|max:10240000|nullable',
-			'price'   => 'required|numeric|min:0',
-			'type'    => 'required',
-			'renew'   => 'required_unless:type,2|min:0',
-			'days'    => 'required|integer',
-		], [
-			'traffic.min' => '内含流量不能低于1MB',
-			'traffic.max' => '内含流量不能超过10TB',
-		]);
-
-		if($validator->fails()){
-			return Redirect::back()->withInput()->withErrors($validator->errors());
-		}
-
-		// 商品LOGO
-		$logo = null;
-		if($request->hasFile('logo')){
-			$logo = $this->uploadFile($request->file('logo'));
-
-			if(!$logo){
-				return Redirect::back()->withInput()->withErrors('LOGO不合法');
-			}
-		}
-
-		try{
-			DB::beginTransaction();
-
-			$obj = new Goods();
-			$obj->name = $request->input('name');
-			$obj->logo = $logo?: null;
-			$obj->traffic = $request->input('traffic');
-			$obj->type = $request->input('type');
-			$obj->price = round($request->input('price'), 2);
-			$obj->level = $request->input('level');
-			$obj->renew = round($request->input('renew'), 2);
-			$obj->period = $request->input('period');
-			$obj->info = $request->input('info');
-			$obj->description = $request->input('description');
-			$obj->days = $request->input('days');
-			$obj->invite_num = $request->input('invite_num');
-			$obj->limit_num = $request->input('limit_num');
-			$obj->color = $request->input('color');
-			$obj->sort = $request->input('sort');
-			$obj->is_hot = $request->input('is_hot')? 1 : 0;
-			$obj->status = $request->input('status')? 1 : 0;
-			$obj->save();
-
-			DB::commit();
-
-			return Redirect::back()->with('successMsg', '添加成功');
-		}catch(Exception $e){
-			DB::rollBack();
-			Log::error('添加商品信息异常:'.$e->getMessage());
-
-			return Redirect::back()->withInput()->withErrors('添加失败');
-		}
-	}
-
-	// 编辑商品页面
-	public function edit($id) {
-		$view['goods'] = Goods::find($id);
-		$view['levelList'] = Level::orderBy('level')->get();
-
-		return view('admin.shop.info', $view);
-	}
-
-	// 编辑商品
-	public function update(Request $request, $id) {
-		$goods = Goods::find($id);
-		if(!$goods){
-			Session::flash('errorMsg', '商品不存在');
-
-			return Redirect::back();
-		}
-
-		// 商品LOGO
-		if($request->hasFile('logo')){
-			$logo = $this->uploadFile($request->file('logo'));
-
-			if(!$logo){
-				Session::flash('errorMsg', 'LOGO不合法');
-
-				return Redirect::back()->withInput();
-			}
-			Goods::whereId($id)->update(['logo' => $logo]);
-		}
-
-		try{
-			DB::beginTransaction();
-
-			$data = [
-				'name'        => $request->input('name'),
-				'price'       => round($request->input('price'), 2) * 100,
-				'level'       => $request->input('level'),
-				'renew'       => round($request->input('renew'), 2) * 100,
-				'period'      => $request->input('period'),
-				'info'        => $request->input('info'),
-				'description' => $request->input('description'),
-				'invite_num'  => $request->input('invite_num'),
-				'limit_num'   => $request->input('limit_num'),
-				'color'       => $request->input('color'),
-				'sort'        => $request->input('sort'),
-				'is_hot'      => $request->input('is_hot')? 1 : 0,
-				'status'      => $request->input('status')? 1 : 0
-			];
-
-			Goods::whereId($id)->update($data);
-
-			Session::flash('successMsg', '编辑成功');
-
-			DB::commit();
-		}catch(Exception $e){
-			Session::flash('errorMsg', '编辑失败');
-
-			DB::rollBack();
-		}
-
-		return Redirect::back();
-	}
-
-	// 删除商品
-	public function destroy($id): JsonResponse {
-		try{
-			$goods = Goods::findOrFail($id)->delete();
-
-		}catch(Exception $e){
-			Session::flash('errorMsg', '编辑失败'.$e);
-		}
-
-		return Response::json(['status' => 'success', 'message' => '删除成功']);
-	}
+class ShopController extends Controller
+{
+
+    // 商品列表
+    public function index(Request $request)
+    {
+        $type   = $request->input('type');
+        $status = $request->input('status');
+
+        $query = Goods::query();
+
+        if (isset($type)) {
+            $query->whereType($type);
+        }
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['goodsList'] = $query->orderByDesc('status')
+                                   ->paginate(10)
+                                   ->appends($request->except('page'));
+
+        return view('admin.shop.index', $view);
+    }
+
+    // 添加商品页面
+    public function create()
+    {
+        $view['goods']     = null;
+        $view['levelList'] = Level::orderBy('level')->get();
+
+        return view('admin.shop.info', $view);
+    }
+
+    // 添加商品
+    public function store(Request $request): RedirectResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'name'    => 'required',
+                'traffic' => 'required|integer|min:1|max:10240000|nullable',
+                'price'   => 'required|numeric|min:0',
+                'type'    => 'required',
+                'renew'   => 'required_unless:type,2|min:0',
+                'days'    => 'required|integer',
+            ],
+            [
+                'traffic.min' => '内含流量不能低于1MB',
+                'traffic.max' => '内含流量不能超过10TB',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Redirect::back()->withInput()->withErrors(
+                $validator->errors()
+            );
+        }
+
+        // 商品LOGO
+        $logo = null;
+        if ($request->hasFile('logo')) {
+            $logo = $this->uploadFile($request->file('logo'));
+
+            if ( ! $logo) {
+                return Redirect::back()->withInput()->withErrors('LOGO不合法');
+            }
+        }
+
+        try {
+            DB::beginTransaction();
+
+            $obj              = new Goods();
+            $obj->name        = $request->input('name');
+            $obj->logo        = $logo ?: null;
+            $obj->traffic     = $request->input('traffic');
+            $obj->type        = $request->input('type');
+            $obj->price       = round($request->input('price'), 2);
+            $obj->level       = $request->input('level');
+            $obj->renew       = round($request->input('renew'), 2);
+            $obj->period      = $request->input('period');
+            $obj->info        = $request->input('info');
+            $obj->description = $request->input('description');
+            $obj->days        = $request->input('days');
+            $obj->invite_num  = $request->input('invite_num');
+            $obj->limit_num   = $request->input('limit_num');
+            $obj->color       = $request->input('color');
+            $obj->sort        = $request->input('sort');
+            $obj->is_hot      = $request->input('is_hot') ? 1 : 0;
+            $obj->status      = $request->input('status') ? 1 : 0;
+            $obj->save();
+
+            DB::commit();
+
+            return Redirect::back()->with('successMsg', '添加成功');
+        } catch (Exception $e) {
+            DB::rollBack();
+            Log::error('添加商品信息异常:' . $e->getMessage());
+
+            return Redirect::back()->withInput()->withErrors('添加失败');
+        }
+    }
+
+    // 编辑商品页面
+    public function edit($id)
+    {
+        $view['goods']     = Goods::find($id);
+        $view['levelList'] = Level::orderBy('level')->get();
+
+        return view('admin.shop.info', $view);
+    }
+
+    // 编辑商品
+    public function update(Request $request, $id)
+    {
+        $goods = Goods::find($id);
+        if ( ! $goods) {
+            Session::flash('errorMsg', '商品不存在');
+
+            return Redirect::back();
+        }
+
+        // 商品LOGO
+        if ($request->hasFile('logo')) {
+            $logo = $this->uploadFile($request->file('logo'));
+
+            if ( ! $logo) {
+                Session::flash('errorMsg', 'LOGO不合法');
+
+                return Redirect::back()->withInput();
+            }
+            Goods::whereId($id)->update(['logo' => $logo]);
+        }
+
+        try {
+            DB::beginTransaction();
+
+            $data = [
+                'name'        => $request->input('name'),
+                'price'       => round($request->input('price'), 2) * 100,
+                'level'       => $request->input('level'),
+                'renew'       => round($request->input('renew'), 2) * 100,
+                'period'      => $request->input('period'),
+                'info'        => $request->input('info'),
+                'description' => $request->input('description'),
+                'invite_num'  => $request->input('invite_num'),
+                'limit_num'   => $request->input('limit_num'),
+                'color'       => $request->input('color'),
+                'sort'        => $request->input('sort'),
+                'is_hot'      => $request->input('is_hot') ? 1 : 0,
+                'status'      => $request->input('status') ? 1 : 0,
+            ];
+
+            Goods::whereId($id)->update($data);
+
+            Session::flash('successMsg', '编辑成功');
+
+            DB::commit();
+        } catch (Exception $e) {
+            Session::flash('errorMsg', '编辑失败');
+
+            DB::rollBack();
+        }
+
+        return Redirect::back();
+    }
+
+    // 删除商品
+    public function destroy($id): JsonResponse
+    {
+        try {
+            Goods::find($id)->delete();
+        } catch (Exception $e) {
+            Session::flash('errorMsg', '编辑失败' . $e);
+        }
+
+        return Response::json(['status' => 'success', 'message' => '删除成功']);
+    }
+
 }

+ 75 - 58
app/Http/Controllers/Admin/SubscribeController.php

@@ -16,63 +16,80 @@ use Response;
  *
  * @package App\Http\Controllers\Controller
  */
-class SubscribeController extends Controller {
-	// 订阅码列表
-	public function subscribeList(Request $request) {
-		$user_id = $request->input('user_id');
-		$email = $request->input('email');
-		$status = $request->input('status');
+class SubscribeController extends Controller
+{
+
+    // 订阅码列表
+    public function subscribeList(Request $request)
+    {
+        $user_id = $request->input('user_id');
+        $email   = $request->input('email');
+        $status  = $request->input('status');
+
+        $query = UserSubscribe::with(['user:id,email']);
+
+        if (isset($user_id)) {
+            $query->whereUserId($user_id);
+        }
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['subscribeList'] = $query->latest()->paginate(20)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.subscribe.subscribeList', $view);
+    }
+
+    //订阅记录
+    public function subscribeLog(Request $request)
+    {
+        $id    = $request->input('id');
+        $query = UserSubscribeLog::with('user:email');
+
+        if (isset($id)) {
+            $query->whereUserSubscribeId($id);
+        }
+
+        $view['subscribeLog'] = $query->latest()->paginate(20)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.subscribe.subscribeLog', $view);
+    }
+
+    // 设置用户的订阅的状态
+    public function setSubscribeStatus(Request $request): JsonResponse
+    {
+        $id     = $request->input('id');
+        $status = $request->input('status', 0);
+
+        if (empty($id)) {
+            return Response::json(['status' => 'fail', 'message' => '操作异常']);
+        }
+
+        if ($status) {
+            UserSubscribe::find($id)->update(
+                ['status' => 1, 'ban_time' => null, 'ban_desc' => '']
+            );
+        } else {
+            UserSubscribe::find($id)->update(
+                ['status' => 0, 'ban_time' => time(), 'ban_desc' => '后台手动封禁']
+            );
+        }
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
 
-		$query = UserSubscribe::with(['user:id,email']);
-
-		if(isset($user_id)){
-			$query->whereUserId($user_id);
-		}
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['subscribeList'] = $query->latest()->paginate(20)->appends($request->except('page'));
-
-		return view('admin.subscribe.subscribeList', $view);
-	}
-
-	//订阅记录
-	public function subscribeLog(Request $request) {
-		$id = $request->input('id');
-		$query = UserSubscribeLog::with('user:email');
-
-		if(isset($id)){
-			$query->whereUserSubscribeId($id);
-		}
-
-		$view['subscribeLog'] = $query->latest()->paginate(20)->appends($request->except('page'));
-
-		return view('admin.subscribe.subscribeLog', $view);
-	}
-
-	// 设置用户的订阅的状态
-	public function setSubscribeStatus(Request $request): JsonResponse {
-		$id = $request->input('id');
-		$status = $request->input('status', 0);
-
-		if(empty($id)){
-			return Response::json(['status' => 'fail', 'message' => '操作异常']);
-		}
-
-		if($status){
-			UserSubscribe::find($id)->update(['status' => 1, 'ban_time' => null, 'ban_desc' => '']);
-		}else{
-			UserSubscribe::find($id)->update(['status' => 0, 'ban_time' => time(), 'ban_desc' => '后台手动封禁']);
-		}
-
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
 }

+ 168 - 125
app/Http/Controllers/Admin/TicketController.php

@@ -23,129 +23,172 @@ use Response;
  *
  * @package App\Http\Controllers\Controller
  */
-class TicketController extends Controller {
-	// 工单列表
-	public function ticketList(Request $request) {
-		$email = $request->input('email');
-
-		$query = Ticket::whereIn('admin_id', [0, Auth::id()]);
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		$view['ticketList'] = $query->latest()->paginate(10)->appends($request->except('page'));
-
-		return view('admin.ticket.ticketList', $view);
-	}
-
-	// 创建工单
-	public function createTicket(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-		$email = $request->input('email');
-		$title = $request->input('title');
-		$content = $request->input('content');
-
-		$user = User::find($id)?: User::whereEmail($email)->first();
-
-		if(!$user){
-			return Response::json(['status' => 'fail', 'message' => '用户不存在']);
-		}
-
-		if($user == Auth::user()){
-			return Response::json(['status' => 'fail', 'message' => '不能对自己发起工单']);
-		}
-
-		if(empty($title) || empty($content)){
-			return Response::json(['status' => 'fail', 'message' => '请输入标题和内容']);
-		}
-
-		$obj = new Ticket();
-		$obj->user_id = $user->id;
-		$obj->admin_id = Auth::id();
-		$obj->title = $title;
-		$obj->content = $content;
-		$obj->status = 0;
-		$obj->save();
-
-		if($obj->id){
-			return Response::json(['status' => 'success', 'message' => '工单创建成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '工单创建失败']);
-	}
-
-	// 回复工单
-	public function replyTicket(Request $request) {
-		$id = $request->input('id');
-
-		if($request->isMethod('POST')){
-			$content = clean($request->input('content'));
-			$content = str_replace(["atob", "eval"], "", $content);
-			$content = substr($content, 0, 300);
-
-			$obj = new TicketReply();
-			$obj->ticket_id = $id;
-			$obj->admin_id = Auth::id();
-			$obj->content = $content;
-			$obj->save();
-
-			if($obj->id){
-				// 将工单置为已回复
-				$ticket = Ticket::with('user')->whereId($id)->firstOrFail();
-				Ticket::whereId($id)->update(['status' => 1]);
-
-				$title = "工单回复提醒";
-				$content = "标题:".$ticket->title."<br>管理员回复:".$content;
-
-				// 发通知邮件
-				if(!Auth::getUser()->is_admin){
-					if(sysConfig('webmaster_email')){
-						$logId = Helpers::addNotificationLog($title, $content, 1, sysConfig('webmaster_email'));
-						Mail::to(sysConfig('webmaster_email'))->send(new replyTicket($logId, $title, $content));
-					}
-					// 推送通知管理员
-					PushNotification::send($title, $content);
-				}else{
-					$logId = Helpers::addNotificationLog($title, $content, 1, $ticket->user->email);
-					Mail::to($ticket->user->email)->send(new replyTicket($logId, $title, $content));
-				}
-
-				return Response::json(['status' => 'success', 'message' => '回复成功']);
-			}
-
-			return Response::json(['status' => 'fail', 'message' => '回复失败']);
-		}
-
-		$view['ticket'] = Ticket::find($id);
-		$view['replyList'] = TicketReply::whereTicketId($id)->oldest()->get();
-
-		return view('admin.ticket.replyTicket', $view);
-	}
-
-	// 关闭工单
-	public function closeTicket(Request $request): JsonResponse {
-		$id = $request->input('id');
-
-		$ticket = Ticket::with('user')->whereId($id)->first();
-		if(!$ticket){
-			return Response::json(['status' => 'fail', 'message' => '关闭失败']);
-		}
-
-		$ret = Ticket::whereId($id)->update(['status' => 2]);
-		if(!$ret){
-			return Response::json(['status' => 'fail', 'message' => '关闭失败']);
-		}
-
-		$title = "工单关闭提醒";
-		$content = "工单【".$ticket->title."】已关闭";
-
-		// 发邮件通知用户
-		$logId = Helpers::addNotificationLog($title, $content, 1, $ticket->user->email);
-		Mail::to($ticket->user->email)->send(new closeTicket($logId, $title, $content));
-
-		return Response::json(['status' => 'success', 'message' => '关闭成功']);
-	}
+class TicketController extends Controller
+{
+
+    // 工单列表
+    public function ticketList(Request $request)
+    {
+        $email = $request->input('email');
+
+        $query = Ticket::whereIn('admin_id', [0, Auth::id()]);
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        $view['ticketList'] = $query->latest()->paginate(10)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.ticket.ticketList', $view);
+    }
+
+    // 创建工单
+    public function createTicket(Request $request): ?JsonResponse
+    {
+        $id      = $request->input('id');
+        $email   = $request->input('email');
+        $title   = $request->input('title');
+        $content = $request->input('content');
+
+        $user = User::find($id) ?: User::whereEmail($email)->first();
+
+        if ( ! $user) {
+            return Response::json(['status' => 'fail', 'message' => '用户不存在']);
+        }
+
+        if ($user == Auth::user()) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '不能对自己发起工单']
+            );
+        }
+
+        if (empty($title) || empty($content)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '请输入标题和内容']
+            );
+        }
+
+        $obj           = new Ticket();
+        $obj->user_id  = $user->id;
+        $obj->admin_id = Auth::id();
+        $obj->title    = $title;
+        $obj->content  = $content;
+        $obj->status   = 0;
+        $obj->save();
+
+        if ($obj->id) {
+            return Response::json(
+                ['status' => 'success', 'message' => '工单创建成功']
+            );
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '工单创建失败']);
+    }
+
+    // 回复工单
+    public function replyTicket(Request $request)
+    {
+        $id = $request->input('id');
+
+        if ($request->isMethod('POST')) {
+            $content = clean($request->input('content'));
+            $content = str_replace(["atob", "eval"], "", $content);
+            $content = substr($content, 0, 300);
+
+            $obj            = new TicketReply();
+            $obj->ticket_id = $id;
+            $obj->admin_id  = Auth::id();
+            $obj->content   = $content;
+            $obj->save();
+
+            if ($obj->id) {
+                // 将工单置为已回复
+                $ticket = Ticket::with('user')->whereId($id)->firstOrFail();
+                Ticket::whereId($id)->update(['status' => 1]);
+
+                $title   = "工单回复提醒";
+                $content = "标题:" . $ticket->title . "<br>管理员回复:" . $content;
+
+                // 发通知邮件
+                if ( ! Auth::getUser()->is_admin) {
+                    if (sysConfig('webmaster_email')) {
+                        $logId = Helpers::addNotificationLog(
+                            $title,
+                            $content,
+                            1,
+                            sysConfig(
+                                'webmaster_email'
+                            )
+                        );
+                        Mail::to(sysConfig('webmaster_email'))->send(
+                            new replyTicket($logId, $title, $content)
+                        );
+                    }
+                    // 推送通知管理员
+                    PushNotification::send($title, $content);
+                } else {
+                    $logId = Helpers::addNotificationLog(
+                        $title,
+                        $content,
+                        1,
+                        $ticket->user->email
+                    );
+                    Mail::to($ticket->user->email)->send(
+                        new replyTicket($logId, $title, $content)
+                    );
+                }
+
+                return Response::json(
+                    ['status' => 'success', 'message' => '回复成功']
+                );
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '回复失败']);
+        }
+
+        $view['ticket']    = Ticket::find($id);
+        $view['replyList'] = TicketReply::whereTicketId($id)->oldest()->get();
+
+        return view('admin.ticket.replyTicket', $view);
+    }
+
+    // 关闭工单
+    public function closeTicket(Request $request): JsonResponse
+    {
+        $id = $request->input('id');
+
+        $ticket = Ticket::with('user')->whereId($id)->first();
+        if ( ! $ticket) {
+            return Response::json(['status' => 'fail', 'message' => '关闭失败']);
+        }
+
+        $ret = Ticket::whereId($id)->update(['status' => 2]);
+        if ( ! $ret) {
+            return Response::json(['status' => 'fail', 'message' => '关闭失败']);
+        }
+
+        $title   = "工单关闭提醒";
+        $content = "工单【" . $ticket->title . "】已关闭";
+
+        // 发邮件通知用户
+        $logId = Helpers::addNotificationLog(
+            $title,
+            $content,
+            1,
+            $ticket->user->email
+        );
+        Mail::to($ticket->user->email)->send(
+            new closeTicket($logId, $title, $content)
+        );
+
+        return Response::json(['status' => 'success', 'message' => '关闭成功']);
+    }
+
 }

+ 256 - 221
app/Http/Controllers/Admin/ToolsController.php

@@ -13,225 +13,260 @@ use Redirect;
 use Response;
 use Session;
 
-class ToolsController extends Controller {
-	// SS(R)链接反解析
-	public function decompile(Request $request) {
-		if($request->isMethod('POST')){
-			$content = $request->input('content');
-
-			if(empty($content)){
-				return Response::json(['status' => 'fail', 'message' => '请在左侧填入要反解析的SS(R)链接']);
-			}
-
-			// 反解析处理
-			$content = str_replace("\n", ",", $content);
-			$content = explode(',', $content);
-			$txt = '';
-			foreach($content as $item){
-				// 判断是SS还是SSR链接
-				$str = '';
-				if(str_contains($item, 'ssr://')){
-					$str = mb_substr($item, 6);
-				}elseif(str_contains($item, 'ss://')){
-					$str = mb_substr($item, 5);
-				}
-
-				$txt .= "\r\n".base64url_decode($str);
-			}
-
-			// 生成转换好的JSON文件
-			file_put_contents(public_path('downloads/decompile.json'), $txt);
-
-			return Response::json(['status' => 'success', 'data' => $txt, 'message' => '反解析成功']);
-		}
-		return view('admin.tools.decompile');
-	}
-
-	// 格式转换(SS转SSR)
-	public function convert(Request $request) {
-		if($request->isMethod('POST')){
-			$method = $request->input('method');
-			$transfer_enable = $request->input('transfer_enable');
-			$protocol = $request->input('protocol');
-			$protocol_param = $request->input('protocol_param');
-			$obfs = $request->input('obfs');
-			$obfs_param = $request->input('obfs_param');
-			$content = $request->input('content');
-
-			if(empty($content)){
-				return Response::json(['status' => 'fail', 'message' => '请在左侧填入要转换的内容']);
-			}
-
-			// 校验格式
-			$content = json_decode($content, true);
-			if(empty($content->port_password)){
-				return Response::json(['status' => 'fail', 'message' => '转换失败:配置信息里缺少【port_password】字段,或者该字段为空']);
-			}
-
-			// 转换成SSR格式JSON
-			$data = [];
-			foreach($content->port_password as $port => $passwd){
-				$data[] = [
-					'u'               => 0,
-					'd'               => 0,
-					'enable'          => 1,
-					'method'          => $method,
-					'obfs'            => $obfs,
-					'obfs_param'      => empty($obfs_param)? "" : $obfs_param,
-					'passwd'          => $passwd,
-					'port'            => $port,
-					'protocol'        => $protocol,
-					'protocol_param'  => empty($protocol_param)? "" : $protocol_param,
-					'transfer_enable' => toGB($transfer_enable),
-					'user'            => date('Ymd').'_IMPORT_'.$port,
-				];
-			}
-
-			$json = json_encode($data);
-
-			// 生成转换好的JSON文件
-			file_put_contents(public_path('downloads/convert.json'), $json);
-
-			return Response::json(['status' => 'success', 'data' => $json, 'message' => '转换成功']);
-		}
-
-		// 加密方式、协议、混淆
-		$view['methodList'] = Helpers::methodList();
-		$view['protocolList'] = Helpers::protocolList();
-		$view['obfsList'] = Helpers::obfsList();
-
-		return view('admin.tools.convert', $view);
-	}
-
-	// 下载转换好的JSON文件
-	public function download(Request $request) {
-		$type = $request->input('type');
-		if(empty($type)){
-			exit('参数异常');
-		}
-
-		if($type == '1'){
-			$filePath = public_path('downloads/convert.json');
-		}else{
-			$filePath = public_path('downloads/decompile.json');
-		}
-
-		if(!file_exists($filePath)){
-			exit('文件不存在,请检查目录权限');
-		}
-
-		return Response::download($filePath);
-	}
-
-	// 数据导入
-	public function import(Request $request) {
-		if($request->isMethod('POST')){
-			if(!$request->hasFile('uploadFile')){
-				Session::flash('errorMsg', '请选择要上传的文件');
-
-				return Redirect::back();
-			}
-
-			$file = $request->file('uploadFile');
-
-			// 只能上传JSON文件
-			if($file->getClientMimeType() !== 'application/json' || $file->getClientOriginalExtension() !== 'json'){
-				Session::flash('errorMsg', '只允许上传JSON文件');
-
-				return Redirect::back();
-			}
-
-			if(!$file->isValid()){
-				Session::flash('errorMsg', '产生未知错误,请重新上传');
-
-				return Redirect::back();
-			}
-
-			$save_path = realpath(storage_path('uploads'));
-			$new_name = md5($file->getClientOriginalExtension()).'.json';
-			$file->move($save_path, $new_name);
-
-			// 读取文件内容
-			$data = file_get_contents($save_path.'/'.$new_name);
-			$data = json_decode($data, true);
-			if(!$data){
-				Session::flash('errorMsg', '内容格式解析异常,请上传符合SSR(R)配置规范的JSON文件');
-
-				return Redirect::back();
-			}
-
-			try{
-				DB::beginTransaction();
-				foreach($data as $user){
-					$obj = new User();
-					$obj->username = $user->user;
-					$obj->email = $user->user;
-					$obj->password = Hash::make('123456');
-					$obj->port = $user->port;
-					$obj->passwd = $user->passwd;
-					$obj->vmess_id = $user->uuid;
-					$obj->transfer_enable = $user->transfer_enable;
-					$obj->method = $user->method;
-					$obj->protocol = $user->protocol;
-					$obj->obfs = $user->obfs;
-					$obj->expired_at = '2099-01-01';
-					$obj->reg_ip = getClientIp();
-					$obj->created_at = date('Y-m-d H:i:s');
-					$obj->updated_at = date('Y-m-d H:i:s');
-					$obj->save();
-				}
-
-				DB::commit();
-			}catch(Exception $e){
-				DB::rollBack();
-
-				Session::flash('errorMsg', '出错了,可能是导入的配置中有端口已经存在了');
-
-				return Redirect::back();
-			}
-
-			Session::flash('successMsg', '导入成功');
-
-			return Redirect::back();
-		}
-
-		return view('admin.tools.import');
-	}
-
-	// 日志分析
-	public function analysis() {
-		$file = storage_path('app/ssserver.log');
-		if(!file_exists($file)){
-			Session::flash('analysisErrorMsg', $file.' 不存在,请先创建文件');
-
-			return view('admin.tools.analysis');
-		}
-
-		$logs = $this->tail($file, 10000);
-		if(false === $logs){
-			$view['urlList'] = [];
-		}else{
-			$url = [];
-			foreach($logs as $log){
-				if(str_contains($log, 'TCP connecting')){
-					continue;
-				}
-
-				preg_match('/TCP request (\w+\.){2}\w+/', $log, $tcp_matches);
-				if(!empty($tcp_matches)){
-					$url[] = str_replace('TCP request ', '[TCP] ', $tcp_matches[0]);
-				}else{
-					preg_match('/UDP data to (25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)/',
-						$log, $udp_matches);
-					if(!empty($udp_matches)){
-						$url[] = str_replace('UDP data to ', '[UDP] ', $udp_matches[0]);
-					}
-				}
-			}
-
-			$view['urlList'] = array_unique($url);
-		}
-
-		return view('admin.tools.analysis', $view);
-	}
+class ToolsController extends Controller
+{
+
+    // SS(R)链接反解析
+    public function decompile(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $content = $request->input('content');
+
+            if (empty($content)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '请在左侧填入要反解析的SS(R)链接']
+                );
+            }
+
+            // 反解析处理
+            $content = str_replace("\n", ",", $content);
+            $content = explode(',', $content);
+            $txt     = '';
+            foreach ($content as $item) {
+                // 判断是SS还是SSR链接
+                $str = '';
+                if (str_contains($item, 'ssr://')) {
+                    $str = mb_substr($item, 6);
+                } elseif (str_contains($item, 'ss://')) {
+                    $str = mb_substr($item, 5);
+                }
+
+                $txt .= "\r\n" . base64url_decode($str);
+            }
+
+            // 生成转换好的JSON文件
+            file_put_contents(public_path('downloads/decompile.json'), $txt);
+
+            return Response::json(
+                ['status' => 'success', 'data' => $txt, 'message' => '反解析成功']
+            );
+        }
+
+        return view('admin.tools.decompile');
+    }
+
+    // 格式转换(SS转SSR)
+    public function convert(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $method          = $request->input('method');
+            $transfer_enable = $request->input('transfer_enable');
+            $protocol        = $request->input('protocol');
+            $protocol_param  = $request->input('protocol_param');
+            $obfs            = $request->input('obfs');
+            $obfs_param      = $request->input('obfs_param');
+            $content         = $request->input('content');
+
+            if (empty($content)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '请在左侧填入要转换的内容']
+                );
+            }
+
+            // 校验格式
+            $content = json_decode($content, true);
+            if (empty($content->port_password)) {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => '转换失败:配置信息里缺少【port_password】字段,或者该字段为空',
+                    ]
+                );
+            }
+
+            // 转换成SSR格式JSON
+            $data = [];
+            foreach ($content->port_password as $port => $passwd) {
+                $data[] = [
+                    'u'               => 0,
+                    'd'               => 0,
+                    'enable'          => 1,
+                    'method'          => $method,
+                    'obfs'            => $obfs,
+                    'obfs_param'      => empty($obfs_param) ? "" : $obfs_param,
+                    'passwd'          => $passwd,
+                    'port'            => $port,
+                    'protocol'        => $protocol,
+                    'protocol_param'  => empty($protocol_param) ? "" : $protocol_param,
+                    'transfer_enable' => toGB($transfer_enable),
+                    'user'            => date('Ymd') . '_IMPORT_' . $port,
+                ];
+            }
+
+            $json = json_encode($data);
+
+            // 生成转换好的JSON文件
+            file_put_contents(public_path('downloads/convert.json'), $json);
+
+            return Response::json(
+                ['status' => 'success', 'data' => $json, 'message' => '转换成功']
+            );
+        }
+
+        // 加密方式、协议、混淆
+        $view['methodList']   = Helpers::methodList();
+        $view['protocolList'] = Helpers::protocolList();
+        $view['obfsList']     = Helpers::obfsList();
+
+        return view('admin.tools.convert', $view);
+    }
+
+    // 下载转换好的JSON文件
+    public function download(Request $request)
+    {
+        $type = $request->input('type');
+        if (empty($type)) {
+            exit('参数异常');
+        }
+
+        if ($type == '1') {
+            $filePath = public_path('downloads/convert.json');
+        } else {
+            $filePath = public_path('downloads/decompile.json');
+        }
+
+        if ( ! file_exists($filePath)) {
+            exit('文件不存在,请检查目录权限');
+        }
+
+        return Response::download($filePath);
+    }
+
+    // 数据导入
+    public function import(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            if ( ! $request->hasFile('uploadFile')) {
+                Session::flash('errorMsg', '请选择要上传的文件');
+
+                return Redirect::back();
+            }
+
+            $file = $request->file('uploadFile');
+
+            // 只能上传JSON文件
+            if ($file->getClientMimeType(
+                ) !== 'application/json' || $file->getClientOriginalExtension(
+                ) !== 'json') {
+                Session::flash('errorMsg', '只允许上传JSON文件');
+
+                return Redirect::back();
+            }
+
+            if ( ! $file->isValid()) {
+                Session::flash('errorMsg', '产生未知错误,请重新上传');
+
+                return Redirect::back();
+            }
+
+            $save_path = realpath(storage_path('uploads'));
+            $new_name  = md5($file->getClientOriginalExtension()) . '.json';
+            $file->move($save_path, $new_name);
+
+            // 读取文件内容
+            $data = file_get_contents($save_path . '/' . $new_name);
+            $data = json_decode($data, true);
+            if ( ! $data) {
+                Session::flash('errorMsg', '内容格式解析异常,请上传符合SSR(R)配置规范的JSON文件');
+
+                return Redirect::back();
+            }
+
+            try {
+                DB::beginTransaction();
+                foreach ($data as $user) {
+                    $obj                  = new User();
+                    $obj->username        = $user->user;
+                    $obj->email           = $user->user;
+                    $obj->password        = Hash::make('123456');
+                    $obj->port            = $user->port;
+                    $obj->passwd          = $user->passwd;
+                    $obj->vmess_id        = $user->uuid;
+                    $obj->transfer_enable = $user->transfer_enable;
+                    $obj->method          = $user->method;
+                    $obj->protocol        = $user->protocol;
+                    $obj->obfs            = $user->obfs;
+                    $obj->expired_at      = '2099-01-01';
+                    $obj->reg_ip          = getClientIp();
+                    $obj->created_at      = date('Y-m-d H:i:s');
+                    $obj->updated_at      = date('Y-m-d H:i:s');
+                    $obj->save();
+                }
+
+                DB::commit();
+            } catch (Exception $e) {
+                DB::rollBack();
+
+                Session::flash('errorMsg', '出错了,可能是导入的配置中有端口已经存在了');
+
+                return Redirect::back();
+            }
+
+            Session::flash('successMsg', '导入成功');
+
+            return Redirect::back();
+        }
+
+        return view('admin.tools.import');
+    }
+
+    // 日志分析
+    public function analysis()
+    {
+        $file = storage_path('app/ssserver.log');
+        if ( ! file_exists($file)) {
+            Session::flash('analysisErrorMsg', $file . ' 不存在,请先创建文件');
+
+            return view('admin.tools.analysis');
+        }
+
+        $logs = $this->tail($file, 10000);
+        if (false === $logs) {
+            $view['urlList'] = [];
+        } else {
+            $url = [];
+            foreach ($logs as $log) {
+                if (str_contains($log, 'TCP connecting')) {
+                    continue;
+                }
+
+                preg_match('/TCP request (\w+\.){2}\w+/', $log, $tcp_matches);
+                if ( ! empty($tcp_matches)) {
+                    $url[] = str_replace(
+                        'TCP request ',
+                        '[TCP] ',
+                        $tcp_matches[0]
+                    );
+                } else {
+                    preg_match(
+                        '/UDP data to (25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)/',
+                        $log,
+                        $udp_matches
+                    );
+                    if ( ! empty($udp_matches)) {
+                        $url[] = str_replace(
+                            'UDP data to ',
+                            '[UDP] ',
+                            $udp_matches[0]
+                        );
+                    }
+                }
+            }
+
+            $view['urlList'] = array_unique($url);
+        }
+
+        return view('admin.tools.analysis', $view);
+    }
+
 }

+ 92 - 69
app/Http/Controllers/Admin/UserGroupController.php

@@ -14,73 +14,96 @@ use Redirect;
 use Response;
 use Validator;
 
-class UserGroupController extends Controller {
-	public function index(Request $request) {
-		$view['list'] = UserGroup::paginate(15)->appends($request->except('page'));
-		return view('admin.user.group.index', $view);
-	}
-
-	// 添加用户分组页面
-	public function create() {
-		$view['nodeList'] = Node::whereStatus(1)->get();
-		return view('admin.user.group.info', $view);
-	}
-
-	// 添加用户分组
-	public function store(Request $request): RedirectResponse {
-		$validator = Validator::make($request->all(), [
-			'name'  => 'required',
-			'nodes' => 'required',
-		]);
-
-		if($validator->fails()){
-			return Redirect::back()->withInput()->withErrors($validator->errors());
-		}
-
-		$obj = new UserGroup();
-		$obj->name = $request->input('name');
-		$obj->nodes = $request->input('nodes');
-		$obj->save();
-
-		if($obj->id){
-			return Redirect::back()->with('successMsg', '操作成功');
-		}
-		return Redirect::back()->withInput()->withErrors('操作失败');
-	}
-
-	// 编辑用户分组页面
-	public function edit($id) {
-		$view['userGroup'] = UserGroup::findOrFail($id);
-		$view['nodeList'] = Node::whereStatus(1)->get();
-
-		return view('admin.user.group.info', $view);
-	}
-
-	// 编辑用户分组
-	public function update(Request $request, $id) {
-		$userGroup = UserGroup::findOrFail($id);
-		$userGroup->name = $request->input('name');
-		$userGroup->nodes = $request->input('nodes');
-		if($userGroup->save()){
-			return Redirect::back()->with('successMsg', '操作成功');
-		}
-
-		return Redirect::back()->withInput()->withErrors('操作失败');
-	}
-
-	// 删除用户分组
-	public function destroy($id): JsonResponse {
-		// 校验该分组下是否存在关联账号
-		if(User::whereGroupId($id)->count()){
-			return Response::json(['status' => 'fail', 'message' => '该分组下存在关联账号,请先取消关联!']);
-		}
-
-		try{
-			UserGroup::whereId($id)->delete();
-		}catch(Exception $e){
-			return Response::json(['status' => 'fail', 'message' => '删除失败,'.$e->getMessage()]);
-		}
-
-		return Response::json(['status' => 'success', 'message' => '清理成功']);
-	}
+class UserGroupController extends Controller
+{
+
+    public function index(Request $request)
+    {
+        $view['list'] = UserGroup::paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.user.group.index', $view);
+    }
+
+    // 添加用户分组页面
+    public function create()
+    {
+        $view['nodeList'] = Node::whereStatus(1)->get();
+
+        return view('admin.user.group.info', $view);
+    }
+
+    // 添加用户分组
+    public function store(Request $request): RedirectResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'name'  => 'required',
+                'nodes' => 'required',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Redirect::back()->withInput()->withErrors(
+                $validator->errors()
+            );
+        }
+
+        $obj        = new UserGroup();
+        $obj->name  = $request->input('name');
+        $obj->nodes = $request->input('nodes');
+        $obj->save();
+
+        if ($obj->id) {
+            return Redirect::back()->with('successMsg', '操作成功');
+        }
+
+        return Redirect::back()->withInput()->withErrors('操作失败');
+    }
+
+    // 编辑用户分组页面
+    public function edit($id)
+    {
+        $view['userGroup'] = UserGroup::findOrFail($id);
+        $view['nodeList']  = Node::whereStatus(1)->get();
+
+        return view('admin.user.group.info', $view);
+    }
+
+    // 编辑用户分组
+    public function update(Request $request, $id)
+    {
+        $userGroup        = UserGroup::findOrFail($id);
+        $userGroup->name  = $request->input('name');
+        $userGroup->nodes = $request->input('nodes');
+        if ($userGroup->save()) {
+            return Redirect::back()->with('successMsg', '操作成功');
+        }
+
+        return Redirect::back()->withInput()->withErrors('操作失败');
+    }
+
+    // 删除用户分组
+    public function destroy($id): JsonResponse
+    {
+        // 校验该分组下是否存在关联账号
+        if (User::whereGroupId($id)->count()) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '该分组下存在关联账号,请先取消关联!']
+            );
+        }
+
+        try {
+            UserGroup::whereId($id)->delete();
+        } catch (Exception $e) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '删除失败,' . $e->getMessage()]
+            );
+        }
+
+        return Response::json(['status' => 'success', 'message' => '清理成功']);
+    }
+
 }

+ 1913 - 1462
app/Http/Controllers/AdminController.php

@@ -52,1467 +52,1918 @@ use Validator;
  *
  * @package App\Http\Controllers
  */
-class AdminController extends Controller {
-	public function index() {
-		$past = strtotime("-".sysConfig('expire_days')." days");
-
-		$view['expireDays'] = sysConfig('expire_days');
-		$view['totalUserCount'] = User::count(); // 总用户数
-		$view['enableUserCount'] = User::whereEnable(1)->count(); // 有效用户数
-		$view['activeUserCount'] = User::where('t', '>=', $past)->count(); // 活跃用户数
-		$view['unActiveUserCount'] = User::whereEnable(1)->whereBetween('t', [1, $past])->count(); // 不活跃用户数
-		$view['onlineUserCount'] = User::where('t', '>=', strtotime("-10 minutes"))->count(); // 10分钟内在线用户数
-		$view['expireWarningUserCount'] = User::whereBetween('expired_at', [
-			date('Y-m-d'),
-			strtotime("+".sysConfig('expire_days')." days")
-		])->count(); // 临近过期用户数
-		$view['largeTrafficUserCount'] = User::whereRaw('(u + d) >= 107374182400')
-		                                     ->where('status', '<>', -1)
-		                                     ->count(); // 流量超过100G的用户
-
-		$view['flowAbnormalUserCount'] = count($this->trafficAbnormal());// 1小时内流量异常用户
-		$view['nodeCount'] = Node::count();
-		$view['unnormalNodeCount'] = Node::whereStatus(0)->count();
-		$view['flowCount'] = flowAutoShow(NodeDailyDataFlow::where('created_at', '>=',
-			date('Y-m-d', strtotime("-30 days")))->sum('total'));
-		$view['totalFlowCount'] = flowAutoShow(NodeDailyDataFlow::sum('total'));
-		$view['totalCredit'] = User::where('credit', '<>', 0)->sum('credit') / 100;
-		$view['totalWaitRefAmount'] = ReferralLog::whereIn('status', [0, 1])->sum('commission') / 100;
-		$view['totalRefAmount'] = ReferralApply::whereStatus(2)->sum('amount') / 100;
-		$view['totalOrder'] = Order::count();
-		$view['totalOnlinePayOrder'] = Order::where('pay_type', '<>', 0)->count();
-		$view['totalSuccessOrder'] = Order::whereStatus(2)->count();
-		$view['todaySuccessOrder'] = Order::whereStatus(2)->whereDate('created_at', date('Y-m-d'))->count();
-		// 今日
-		$view['todayRegister'] = User::whereDate('created_at', date('Y-m-d'))->count();
-
-		return view('admin.index', $view);
-	}
-
-	// 1小时内流量异常用户
-	private function trafficAbnormal(): array {
-		$result = [];
-		$userTotalTrafficList = UserHourlyDataFlow::whereNodeId(0)
-		                                          ->where('total', '>', MB * 50)
-		                                          ->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))
-		                                          ->groupBy('user_id')
-		                                          ->selectRaw("user_id, sum(total) as totalTraffic")
-		                                          ->get(); // 只统计50M以上的记录,加快速度
-		foreach($userTotalTrafficList as $user){
-			if($user->totalTraffic > sysConfig('traffic_ban_value') * GB){
-				$result[] = $user->user_id;
-			}
-		}
-		return $result;
-	}
-
-	// 用户列表
-	public function userList(Request $request) {
-		$id = $request->input('id');
-		$email = $request->input('email');
-		$wechat = $request->input('wechat');
-		$qq = $request->input('qq');
-		$port = $request->input('port');
-		$status = $request->input('status');
-		$enable = $request->input('enable');
-		$online = $request->input('online');
-		$flowAbnormal = $request->input('flowAbnormal');
-		$expireWarning = $request->input('expireWarning');
-		$largeTraffic = $request->input('largeTraffic');
-
-		$query = User::with('subscribe');
-		if(isset($id)){
-			$query->whereId($id);
-		}
-
-		if(isset($email)){
-			$query->where('email', 'like', '%'.$email.'%');
-		}
-
-		if(isset($wechat)){
-			$query->where('wechat', 'like', '%'.$wechat.'%');
-		}
-
-		if(isset($qq)){
-			$query->where('qq', 'like', '%'.$qq.'%');
-		}
-
-		if(isset($port)){
-			$query->wherePort($port);
-		}
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		if(isset($enable)){
-			$query->whereEnable($enable);
-		}
-
-		// 流量超过100G的
-		if($largeTraffic){
-			$query->whereIn('status', [0, 1])->whereRaw('(u + d) >= 107374182400');
-		}
-
-		// 临近过期提醒
-		if($expireWarning){
-			$query->whereBetween('expired_at',
-				[date('Y-m-d'), date('Y-m-d', strtotime("+".sysConfig('expire_days')." days"))]);
-		}
-
-		// 当前在线
-		if($online){
-			$query->where('t', '>=', strtotime("-10 minutes"));
-		}
-
-		// 不活跃用户
-		if($request->input('unActive')){
-			$query->whereBetween('t', [1, strtotime("-".sysConfig('expire_days')." days")])->whereEnable(1);
-		}
-
-		// 1小时内流量异常用户
-		if($flowAbnormal){
-			$query->whereIn('id', $this->trafficAbnormal());
-		}
-
-		$userList = $query->latest()->paginate(15)->appends($request->except('page'));
-		foreach($userList as $user){
-			$user->transfer_enable = flowAutoShow($user->transfer_enable);
-			$user->used_flow = flowAutoShow($user->u + $user->d);
-			if($user->expired_at < date('Y-m-d')){
-				$user->expireWarning = -1; // 已过期
-			}elseif($user->expired_at == date('Y-m-d')){
-				$user->expireWarning = 0; // 今天过期
-			}elseif($user->expired_at > date('Y-m-d') && $user->expired_at <= date('Y-m-d', strtotime("+30 days"))){
-				$user->expireWarning = 1; // 最近一个月过期
-			}else{
-				$user->expireWarning = 2; // 大于一个月过期
-			}
-
-			// 流量异常警告
-			$totalTraffic = UserHourlyDataFlow::userRecentUsed($user->id)->sum('total');
-			$user->trafficWarning = $totalTraffic > (sysConfig('traffic_ban_value') * GB)? 1 : 0;
-
-			// 订阅地址
-			$user->link = (sysConfig('subscribe_domain')?: sysConfig('website_url')).'/s/'.$user->subscribe->code;
-		}
-
-		$view['userList'] = $userList;
-
-		return view('admin.user.userList', $view);
-	}
-
-	// 添加账号
-	public function addUser(Request $request) {
-		if($request->isMethod('POST')){
-			// 校验email是否已存在
-			$exists = User::whereEmail($request->input('email'))->first();
-			if($exists){
-				return Response::json(['status' => 'fail', 'message' => '用户名已存在,请重新输入']);
-			}
-
-			$user = new User();
-			$user->username = $request->input('username');
-			$user->email = $request->input('email');
-			$user->password = Hash::make($request->input('password')?: Str::random());
-			$user->port = $request->input('port')?: $this->makePort();
-			$user->passwd = $request->input('passwd')?: Str::random();
-			$user->vmess_id = $request->input('uuid')?: Str::uuid();
-			$user->transfer_enable = toGB($request->input('transfer_enable')?: 0);
-			$user->enable = $request->input('enable')?: 0;
-			$user->method = $request->input('method');
-			$user->protocol = $request->input('protocol');
-			$user->obfs = $request->input('obfs');
-			$user->speed_limit = $request->input('speed_limit') * Mbps;
-			$user->wechat = $request->input('wechat');
-			$user->qq = $request->input('qq');
-			$user->expired_at = $request->input('expired_at')?: date('Y-m-d', strtotime("+365 days"));
-			$user->remark = str_replace(["atob", "eval"], "", $request->input('remark'));
-			$user->level = $request->input('level')?: 0;
-			$user->group_id = $request->input('group_id')?: 0;
-			$user->reg_ip = getClientIp();
-			$user->reset_time = $request->input('reset_time') > date('Y-m-d')? $request->input('reset_time') : null;
-			$user->invite_num = $request->input('invite_num')?: 0;
-			$user->status = $request->input('status')?: 0;
-			$user->save();
-
-			if($user->id){
-
-				// 写入用户流量变动记录
-				Helpers::addUserTrafficModifyLog($user->id, 0, 0, toGB($request->input('transfer_enable', 0)),
-					'后台手动添加用户');
-
-				return Response::json(['status' => 'success', 'message' => '添加成功']);
-			}
-
-			return Response::json(['status' => 'fail', 'message' => '添加失败']);
-		}
-
-		// 生成一个可用端口
-		$view['methodList'] = Helpers::methodList();
-		$view['protocolList'] = Helpers::protocolList();
-		$view['obfsList'] = Helpers::obfsList();
-		$view['levelList'] = Level::orderBy('level')->get();
-		$view['groupList'] = UserGroup::orderBy('id')->get();
-
-		return view('admin.user.userInfo', $view);
-	}
-
-	// 生成端口
-	public function makePort(): int {
-		return Helpers::getPort();
-	}
-
-	// 批量生成账号
-	public function batchAddUsers(Request $request): ?JsonResponse {
-		$amount = $request->input('amount');
-		try{
-			DB::beginTransaction();
-
-			for($i = 0; $i < $amount; $i++){
-				$uid = Helpers::addUser(Str::random(8).'@auto.generate', Hash::make(Str::random()), toGB(1024), 365);
-				// 生成一个可用端口
-
-				if($uid){
-					// 写入用户流量变动记录
-					Helpers::addUserTrafficModifyLog($uid, 0, 0, toGB(1024), '后台批量生成用户');
-				}
-			}
-
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '批量生成账号成功']);
-		}catch(Exception $e){
-			DB::rollBack();
-
-			return Response::json(['status' => 'fail', 'message' => '批量生成账号失败:'.$e->getMessage()]);
-		}
-	}
-
-	// 编辑账号
-	public function editUser(Request $request, $id) {
-		$user = User::find($id);
-		if($request->isMethod('POST')){
-			$email = $request->input('email');
-			$password = $request->input('password');
-			$port = $request->input('port');
-			$transfer_enable = $request->input('transfer_enable');
-			$is_admin = $request->input('is_admin');
-			$status = $request->input('status');
-
-			// 校验email是否已存在
-			if(User::where('id', '<>', $id)->whereEmail($email)->exists()){
-				return Response::json(['status' => 'fail', 'message' => '用户名已存在,请重新输入']);
-			}
-
-			// 校验端口是否已存在
-			if(User::where('id', '<>', $id)->where('port', '>', 0)->wherePort($port)->exists()){
-				return Response::json(['status' => 'fail', 'message' => '端口已存在,请重新输入']);
-			}
-
-			// 禁止取消默认管理员
-			if($id == 1 && $is_admin == 0){
-				return Response::json(['status' => 'fail', 'message' => '系统默认管理员不可取消']);
-			}
-
-			try{
-				DB::beginTransaction();
-				$data = [
-					'username'        => $request->input('username'),
-					'email'           => $email,
-					'port'            => $port,
-					'passwd'          => $request->input('passwd')?: Str::random(),
-					'vmess_id'        => $request->input('uuid')?: Str::uuid(),
-					'transfer_enable' => toGB($transfer_enable?: 0),
-					'enable'          => $status < 0? 0 : $request->input('enable'),
-					'method'          => $request->input('method'),
-					'protocol'        => $request->input('protocol'),
-					'obfs'            => $request->input('obfs'),
-					'speed_limit'     => $request->input('speed_limit') * Mbps,
-					'wechat'          => $request->input('wechat'),
-					'qq'              => $request->input('qq'),
-					'expired_at'      => $request->input('expired_at')?: date('Y-m-d', strtotime("+365 days")),
-					'remark'          => str_replace("eval", "", str_replace("atob", "", $request->input('remark'))),
-					'level'           => $request->input('level'),
-					'group_id'        => $request->input('group_id'),
-					'reset_time'      => $request->input('reset_time'),
-					'invite_num'      => $request->input('invite_num'),
-					'status'          => $status
-				];
-
-				// 只有admin才有权限操作管理员属性
-				if(Auth::getUser()->is_admin == 1){
-					$data['is_admin'] = (int) $is_admin;
-				}
-
-				// 非演示环境才可以修改管理员密码
-				if(!empty($password) && !(config('app.demo') && $id == 1)){
-					$data['password'] = Hash::make($password);
-				}
-
-				$user->update($data);
-
-				// 写入用户流量变动记录
-				if($user->transfer_enable != toGB($transfer_enable)){
-					Helpers::addUserTrafficModifyLog($id, 0, $user->transfer_enable, toGB($transfer_enable),
-						'后台手动编辑用户');
-				}
-
-				DB::commit();
-
-				return Response::json(['status' => 'success', 'message' => '编辑成功']);
-			}catch(Exception $e){
-				DB::rollBack();
-				Log::error('编辑用户信息异常:'.$e->getMessage());
-
-				return Response::json(['status' => 'fail', 'message' => '编辑失败']);
-			}
-		}else{
-			if($user){
-				$user->transfer_enable = flowToGB($user->transfer_enable);
-			}
-
-			$view['user'] = $user->load('inviter:id,email');
-			$view['methodList'] = Helpers::methodList();
-			$view['protocolList'] = Helpers::protocolList();
-			$view['obfsList'] = Helpers::obfsList();
-			$view['levelList'] = Level::orderBy('level')->get();
-			$view['groupList'] = UserGroup::orderBy('id')->get();
-
-			return view('admin.user.userInfo', $view);
-		}
-	}
-
-	// 删除用户
-	public function delUser(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		if($id <= 1){
-			return Response::json(['status' => 'fail', 'message' => '系统管理员不可删除']);
-		}
-
-		try{
-			DB::beginTransaction();
-
-			User::find($id)->delete();
-
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '删除成功']);
-		}catch(Exception $e){
-			Log::error('删除用户信息异常:'.$e->getMessage());
-			DB::rollBack();
-
-			return Response::json(['status' => 'fail', 'message' => '删除失败']);
-		}
-	}
-
-	// 文章列表
-	public function articleList(Request $request) {
-		$view['list'] = Article::orderByDesc('sort')->paginate(15)->appends($request->except('page'));
-
-		return view('admin.article.articleList', $view);
-	}
-
-	// 添加文章
-	public function addArticle(Request $request) {
-		if($request->isMethod('POST')){
-			$article = new Article();
-			$article->title = $request->input('title');
-			$article->type = $request->input('type', 1);
-			$article->summary = $request->input('summary');
-			// LOGO
-			if($article->type == 4){
-				$article->logo = $request->input('logo');
-			}else{
-				$logo = '';
-				if($request->hasFile('logo')){
-					$logo = $this->uploadFile($request->file('logo'));
-
-					if(!$logo){
-						Session::flash('errorMsg', 'LOGO不合法');
-
-						return Redirect::back()->withInput();
-					}
-				}
-
-				$article = new Article();
-				$article->title = $request->input('title');
-				$article->type = $request->input('type', 1);
-				$article->summary = $request->input('summary');
-				$article->logo = $logo;
-			}
-			$article->content = $request->input('content');
-			$article->sort = $request->input('sort', 0);
-			$article->save();
-
-			if($article->id){
-				Session::flash('successMsg', '添加成功');
-			}else{
-				Session::flash('errorMsg', '添加失败');
-			}
-
-			return Redirect::to('admin/articleList');
-		}
-
-		return view('admin.article.addArticle');
-	}
-
-	// 编辑文章
-	public function editArticle(Request $request) {
-		$id = $request->input('id');
-
-		if($request->isMethod('POST')){
-			$title = $request->input('title');
-			$type = $request->input('type');
-			$summary = $request->input('summary');
-			$content = $request->input('content');
-			$sort = $request->input('sort');
-
-			// 商品LOGO
-			if($type == 4){
-				$logo = $request->input('logo');
-			}else{
-				$logo = '';
-				if($request->hasFile('logo')){
-					$logo = $this->uploadFile($request->file('logo'));
-
-					if(!$logo){
-						Session::flash('errorMsg', 'LOGO不合法');
-
-						return Redirect::back()->withInput();
-					}
-				}
-			}
-
-			$data = ['type' => $type, 'title' => $title, 'summary' => $summary, 'content' => $content, 'sort' => $sort];
-
-			if($logo){
-				$data['logo'] = $logo;
-			}
-
-			$ret = Article::whereId($id)->update($data);
-			if($ret){
-				Session::flash('successMsg', '编辑成功');
-			}else{
-				Session::flash('errorMsg', '编辑失败');
-			}
-
-			return Redirect::to('admin/editArticle?id='.$id);
-		}
-
-		$view['article'] = Article::find($id);
-
-		return view('admin.article.editArticle', $view);
-	}
-
-	// 删除文章
-	public function delArticle(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		$ret = Article::whereId($id)->delete();
-		if($ret){
-			return Response::json(['status' => 'success', 'message' => '删除成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '删除失败']);
-	}
-
-	// 流量日志
-	public function trafficLog(Request $request) {
-		$port = $request->input('port');
-		$user_id = $request->input('user_id');
-		$email = $request->input('email');
-		$nodeId = $request->input('nodeId');
-		$startTime = $request->input('startTime');
-		$endTime = $request->input('endTime');
-
-		$query = UserDataFlowLog::with(['user', 'node']);
-
-		if(isset($port)){
-			$query->whereHas('user', static function($q) use ($port) {
-				$q->wherePort($port);
-			});
-		}
-
-		if(isset($user_id)){
-			$query->whereUserId($user_id);
-		}
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		if(isset($nodeId)){
-			$query->whereNodeId($nodeId);
-		}
-
-		if(isset($startTime)){
-			$query->where('log_time', '>=', strtotime($startTime));
-		}
-
-		if(isset($endTime)){
-			$query->where('log_time', '<=', strtotime($endTime));
-		}
-
-		// 已使用流量
-		$view['totalTraffic'] = flowAutoShow($query->sum('u') + $query->sum('d'));
-
-		$list = $query->latest('log_time')->paginate(20)->appends($request->except('page'));
-		foreach($list as $vo){
-			$vo->u = flowAutoShow($vo->u);
-			$vo->d = flowAutoShow($vo->d);
-			$vo->log_time = date('Y-m-d H:i:s', $vo->log_time);
-		}
-
-		$view['list'] = $list;
-		$view['nodeList'] = Node::whereStatus(1)->orderByDesc('sort')->latest()->get();
-
-		return view('admin.logs.trafficLog', $view);
-	}
-
-	// 导出配置信息
-	public function export(Request $request, $id) {
-		if(empty($id)){
-			return Redirect::to('admin/userList');
-		}
-
-		$user = User::find($id);
-		if(empty($user)){
-			return Redirect::to('admin/userList');
-		}
-
-		if($request->isMethod('POST')){
-			$infoType = $request->input('type');
-
-			$node = Node::find($request->input('id'));
-			if($node->type == 1){
-				if($node->compatible){
-					$proxyType = 'SS';
-				}else{
-					$proxyType = 'SSR';
-				}
-			}else{
-				$proxyType = 'V2Ray';
-			}
-
-			$data = $this->getUserNodeInfo($id, $node->id, $infoType !== 'text'? 0 : 1);
-
-			return Response::json(['status' => 'success', 'data' => $data, 'title' => $proxyType]);
-
-		}
-
-		$view['nodeList'] = Node::whereStatus(1)
-		                        ->orderByDesc('sort')
-		                        ->orderBy('id')
-		                        ->paginate(15)
-		                        ->appends($request->except('page'));
-		$view['user'] = $user;
-
-		return view('admin.user.export', $view);
-	}
-
-	// 导出原版SS用户配置信息
-	public function exportSSJson() {
-		$userList = User::where('port', '>', 0)->get();
-
-		$json = '';
-		if(!$userList->isEmpty()){
-			$userPassword = [];
-			foreach($userList as $user){
-				$userPassword[] = $user->port.":".$user->passwd;
-			}
-
-			$json = json_encode([
-				'server'        => '0.0.0.0',
-				'local_address' => '127.0.0.1',
-				'local_port'    => 1080,
-				'port_password' => $userPassword,
-				'timeout'       => 300,
-				'method'        => Helpers::getDefaultMethod(),
-				'fast_open'     => false,
-			]);
-		}
-
-		// 生成JSON文件
-		$fileName = Str::random().'_shadowsocks.json';
-		$filePath = public_path('downloads/'.$fileName);
-		file_put_contents($filePath, $json);
-
-		if(!is_file($filePath)){
-			exit('文件生成失败,请检查目录权限');
-		}
-
-		return Response::download($filePath);
-	}
-
-	// 修改个人资料
-	public function profile(Request $request) {
-		if($request->isMethod('POST')){
-			$new_password = $request->input('new_password');
-
-			if(!Hash::check($request->input('old_password'), Auth::getUser()->password)){
-				return Redirect::back()->withErrors('旧密码错误,请重新输入');
-			}
-
-			if(Hash::check($new_password, Auth::getUser()->password)){
-				return Redirect::back()->withErrors('新密码不可与旧密码一样,请重新输入');
-			}
-
-			$ret = Auth::getUser()->update(['password' => Hash::make($new_password)]);
-			if(!$ret){
-				return Redirect::back()->withErrors('修改失败');
-			}
-
-			return Redirect::back()->with('successMsg', '修改成功');
-		}
-
-		return view('admin.config.profile');
-	}
-
-	// 用户流量监控
-	public function userMonitor(Request $request) {
-		$id = $request->input('id');
-		if(empty($id)){
-			return Redirect::to('admin/userList');
-		}
-
-		$user = User::find($id);
-		if(empty($user)){
-			return Redirect::to('admin/userList');
-		}
-
-		$view['email'] = $user->email;
-		$view = array_merge($view, $this->dataFlowChart($user->id));
-
-		return view('admin.logs.userMonitor', $view);
-	}
-
-	// 加密方式、混淆、协议、等级、国家地区
-	public function config(Request $request) {
-		if($request->isMethod('POST')){
-			$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);
-
-			if(empty($name)){
-				return Response::json(['status' => 'fail', 'message' => '配置名称不能为空']);
-			}
-
-			// 校验是否已存在
-			$config = SsConfig::type($type)->whereName($name)->first();
-			if($config){
-				return Response::json(['status' => 'fail', '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' => '添加成功']);
-		}
-
-		$view['methodList'] = SsConfig::type(1)->get();
-		$view['protocolList'] = SsConfig::type(2)->get();
-		$view['obfsList'] = SsConfig::type(3)->get();
-		$view['countryList'] = Country::all();
-		$view['levelList'] = Level::all();
-		$view['labelList'] = Label::with('nodes')->get();
-
-		return view('admin.config.config', $view);
-	}
-
-	// 删除配置
-	public function delConfig(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		$ret = SsConfig::whereId($id)->delete();
-		if($ret){
-			return Response::json(['status' => 'success', 'message' => '删除成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '删除失败']);
-	}
-
-	// 设置默认配置
-	public function setDefaultConfig(Request $request): JsonResponse {
-		$id = $request->input('id');
-
-		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]);
-
-		// 将该ID对应记录值置为默认值
-		SsConfig::whereId($id)->update(['is_default' => 1]);
-
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 设置系统扩展信息,例如客服、统计代码
-	public function setExtend(Request $request): ?RedirectResponse {
-		$websiteAnalytics = $request->input('website_analytics');
-		$websiteCustomerService = $request->input('website_customer_service');
-
-		try{
-			DB::beginTransaction();
-
-			// 首页LOGO
-			if($request->hasFile('website_home_logo')){
-				$ret = $this->uploadFile($request->file('website_home_logo'));
-				if(!$ret){
-					Session::flash('errorMsg', 'LOGO不合法');
-					return Redirect::back();
-				}
-				Config::find('website_home_logo')->update(['value' => $ret]);
-			}
-
-			// 站内LOGO
-			if($request->hasFile('website_logo')){
-				$ret = $this->uploadFile($request->file('website_logo'));
-				if(!$ret){
-					Session::flash('errorMsg', 'LOGO不合法');
-					return Redirect::back();
-				}
-				Config::find('website_logo')->update(['value' => $ret]);
-			}
-
-			Config::find('website_analytics')->update(['value' => $websiteAnalytics]);
-			Config::find('website_customer_service')->update(['value' => $websiteCustomerService]);
-
-			Session::flash('successMsg', '更新成功');
-
-			DB::commit();
-
-			return Redirect::back();
-		}catch(Exception $e){
-			DB::rollBack();
-
-			Session::flash('errorMsg', '更新失败');
-
-			return Redirect::back();
-		}
-	}
-
-	// 添加等级
-	public function addLevel(Request $request): ?JsonResponse {
-		$validator = Validator::make($request->all(), [
-			'level'      => 'required|numeric|unique:level,level',
-			'level_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){
-			return Response::json(['status' => 'success', 'message' => '提交成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 编辑等级
-	public function updateLevel(Request $request): JsonResponse {
-		$id = $request->input('id');
-		$level = $request->input('level');
-
-		$validator = Validator::make($request->all(), [
-			'id'         => 'required|numeric',
-			'level'      => 'required|numeric',
-			'level_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' => '该等级下存在关联账号,请先取消关联!']);
-		}
-
-		Level::whereId($id)->update(['level' => $level, 'name' => $request->input('level_name')]);
-
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 删除等级
-	public function delLevel(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		$validator = Validator::make($request->all(), [
-			'id' => 'required|numeric|exists:level,id',
-		]);
-
-		if($validator->fails()){
-			return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
-		}
-
-		$level = Level::find($id);
-
-		// 校验该等级下是否存在关联账号
-		$userCount = User::whereLevel($level->level)->count();
-		if($userCount){
-			return Response::json(['status' => 'fail', 'message' => '该等级下存在关联账号,请先取消关联']);
-		}
-		$ret = false;
-		try{
-			$ret = Level::whereId($id)->delete();
-		}catch(Exception $e){
-			Log::error('删除等级时报错:'.$e->getMessage());
-		}
-		if($ret){
-			return Response::json(['status' => 'success', 'message' => '操作成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 添加国家/地区
-	public function addCountry(Request $request): JsonResponse {
-		$code = $request->input('code');
-		$name = $request->input('name');
-
-		if(empty($code)){
-			return Response::json(['status' => 'fail', 'message' => '国家/地区代码不能为空']);
-		}
-
-		if(empty($name)){
-			return Response::json(['status' => 'fail', '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' => '操作失败']);
-	}
-
-	// 编辑国家/地区
-	public function updateCountry(Request $request): JsonResponse {
-		$code = $request->input('code');
-		$name = $request->input('name');
-
-		if(empty($name)){
-			return Response::json(['status' => 'fail', 'message' => '国家/地区名称不能为空']);
-		}
-
-		if(empty($code)){
-			return Response::json(['status' => 'fail', 'message' => '国家/地区代码不能为空']);
-		}
-
-		$country = Country::find($code);
-		if(!$country){
-			return Response::json(['status' => 'fail', 'message' => '国家/地区不存在']);
-		}
-
-		// 校验该国家/地区下是否存在关联节点
-		if(Node::whereCountryCode($country->code)->exists()){
-			return Response::json(['status' => 'fail', 'message' => '该国家/地区下存在关联节点,请先取消关联']);
-		}
-
-		$ret = $country->update(['name' => $name, 'code' => $code]);
-		if($ret){
-			return Response::json(['status' => 'success', 'message' => '操作成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 删除国家/地区
-	public function delCountry(Request $request): ?JsonResponse {
-		$code = $request->input('code');
-
-		if(empty($id)){
-			return Response::json(['status' => 'fail', 'message' => 'ID不能为空']);
-		}
-
-		$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($country->delete()){
-			return Response::json(['status' => 'success', 'message' => '操作成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 系统设置
-	public function system() {
-		$view = Config::pluck('value', 'name')->toArray();
-		$view['labelList'] = Label::orderByDesc('sort')->orderBy('id')->get();
-
-		return view('admin.config.system', $view);
-	}
-
-	// 设置某个配置项
-	public function setConfig(Request $request): JsonResponse {
-		$name = $request->input('name');
-		$value = $request->input('value');
-
-		if(!$name){
-			return Response::json(['status' => 'fail', 'message' => '设置失败:请求参数异常']);
-		}
-
-		// 屏蔽异常配置
-		if(!in_array($name, Config::pluck('name')->toArray())){
-			return Response::json(['status' => 'fail', 'message' => '设置失败:配置不存在']);
-		}
-
-		// 如果开启用户邮件重置密码,则先设置网站名称和网址
-		if($value !== '0'
-		   && in_array($name, ['is_reset_password', 'is_activate_account', 'expire_warning', 'traffic_warning'], true)){
-			$config = Config::find('website_name');
-			if(!$config->value){
-				return Response::json(['status' => 'fail', 'message' => '设置失败:启用该配置需要先设置【网站名称】']);
-			}
-
-			$config = Config::find('website_url');
-			if(!$config->value){
-				return Response::json(['status' => 'fail', 'message' => '设置失败:启用该配置需要先设置【网站地址】']);
-			}
-		}
-
-		// 支付设置判断
-		if($value !== null && in_array($name, ['is_AliPay', 'is_QQPay', 'is_WeChatPay', 'is_otherPay'], true)){
-			switch($value){
-				case 'f2fpay':
-					if(!sysConfig('f2fpay_app_id') || !sysConfig('f2fpay_private_key') || !sysConfig('f2fpay_public_key')){
-						return Response::json(['status' => 'fail', 'message' => '请先设置【支付宝F2F】必要参数']);
-					}
-					break;
-				case 'codepay':
-					if(!sysConfig('codepay_url') || !sysConfig('codepay_id') || !sysConfig('codepay_key')){
-						return Response::json(['status' => 'fail', 'message' => '请先设置【码支付】必要参数']);
-					}
-					break;
-				case 'epay':
-					if(!sysConfig('epay_url') || !sysConfig('epay_mch_id') || !sysConfig('epay_key')){
-						return Response::json(['status' => 'fail', 'message' => '请先设置【易支付】必要参数']);
-					}
-					break;
-				case 'payjs':
-					if(!sysConfig('payjs_mch_id') || !sysConfig('payjs_key')){
-						return Response::json(['status' => 'fail', 'message' => '请先设置【PayJs】必要参数']);
-					}
-					break;
-				case 'bitpayx':
-					if(!sysConfig('bitpay_secret')){
-						return Response::json(['status' => 'fail', 'message' => '请先设置【麻瓜宝】必要参数']);
-					}
-					break;
-				case 'paypal':
-					if(!sysConfig('paypal_username') || !sysConfig('paypal_password') || !sysConfig('paypal_secret')){
-						return Response::json(['status' => 'fail', 'message' => '请先设置【PayPal】必要参数']);
-					}
-					break;
-				default:
-					return Response::json(['status' => 'fail', 'message' => '未知支付渠道']);
-			}
-		}
-
-		// 演示环境禁止修改特定配置项
-		if(config('app.demo')){
-			$denyConfig = [
-				'website_url',
-				'min_rand_traffic',
-				'max_rand_traffic',
-				'push_bear_send_key',
-				'push_bear_qrcode',
-				'is_forbid_china',
-				'website_security_code'
-			];
-
-			if(in_array($name, $denyConfig, true)){
-				return Response::json(['status' => 'fail', 'message' => '演示环境禁止修改该配置']);
-			}
-		}
-
-		// 如果是返利比例,则需要除100
-		if($name === 'referral_percent'){
-			$value = (int) $value / 100;
-		}
-
-		// 更新配置
-		Config::find($name)->update(['value' => $value]);
-
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 推送通知测试
-	public function sendTestNotification(): JsonResponse {
-		if(sysConfig('is_notification')){
-			$result = PushNotification::send('这是测试的标题', 'ProxyPanel测试内容');
-			if($result === false){
-				return Response::json(['status' => 'fail', 'message' => '发送失败,请重新尝试!']);
-			}
-			switch(sysConfig('is_notification')){
-				case 'serverChan':
-					if(!$result['errno']){
-						return Response::json(['status' => 'success', 'message' => '发送成功,请查看手机是否收到推送消息']);
-					}
-
-					return Response::json(['status' => 'fail', 'message' => $result? $result['errmsg'] : '未知']);
-				case 'bark':
-					if($result['code'] == 200){
-						return Response::json(['status' => 'success', 'message' => '发送成功,请查看手机是否收到推送消息']);
-					}
-
-					return Response::json(['status' => 'fail', 'message' => $result['message']]);
-				default:
-			}
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '请先选择【日志通知】渠道']);
-	}
-
-	// 邀请码列表
-	public function inviteList(Request $request) {
-		$view['inviteList'] = Invite::with(['invitee:id,email', 'inviter:id,email'])
-		                            ->orderBy('status')
-		                            ->latest()
-		                            ->paginate(15)
-		                            ->appends($request->except('page'));
-
-		return view('admin.inviteList', $view);
-	}
-
-	// 生成邀请码
-	public function makeInvite(): JsonResponse {
-		for($i = 0; $i < 10; $i++){
-			$obj = new Invite();
-			$obj->inviter_id = 0;
-			$obj->invitee_id = 0;
-			$obj->code = strtoupper(substr(md5(microtime().Str::random(6)), 8, 12));
-			$obj->status = 0;
-			$obj->dateline = date('Y-m-d H:i:s', strtotime("+".sysConfig('admin_invite_days')." days"));
-			$obj->save();
-		}
-
-		return Response::json(['status' => 'success', 'message' => '生成成功']);
-	}
-
-	// 导出邀请码
-	public function exportInvite(): void {
-		$inviteList = Invite::whereStatus(0)->orderBy('id')->get();
-
-		$filename = '邀请码'.date('Ymd').'.xlsx';
-
-		$spreadsheet = new Spreadsheet();
-		$spreadsheet->getProperties()
-		            ->setCreator('ProxyPanel')
-		            ->setLastModifiedBy('ProxyPanel')
-		            ->setTitle('邀请码')
-		            ->setSubject('邀请码')
-		            ->setDescription('')
-		            ->setKeywords('')
-		            ->setCategory('');
-
-		try{
-			$spreadsheet->setActiveSheetIndex(0);
-			$sheet = $spreadsheet->getActiveSheet();
-			$sheet->setTitle('邀请码');
-			$sheet->fromArray(['邀请码', '有效期'], null);
-
-			foreach($inviteList as $k => $vo){
-				$sheet->fromArray([$vo->code, $vo->dateline], null, 'A'.($k + 2));
-			}
-
-			header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // 输出07Excel文件
-			//header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件
-			header('Content-Disposition: attachment;filename="'.$filename.'"');
-			header('Cache-Control: max-age=0');
-			$writer = new Xlsx($spreadsheet);
-			$writer->save('php://output');
-		}catch(\PhpOffice\PhpSpreadsheet\Exception $e){
-			Log::error('导出优惠券时报错'.$e->getMessage());
-		}
-	}
-
-	// 订单列表
-	public function orderList(Request $request) {
-		$email = $request->input('email');
-		$order_sn = $request->input('order_sn');
-		$is_coupon = $request->input('is_coupon');
-		$is_expire = $request->input('is_expire');
-		$pay_way = $request->input('pay_way');
-		$status = $request->input('status');
-		$range_time = $request->input('range_time');
-		$sort = $request->input('sort'); // 0-按创建时间降序、1-按创建时间升序
-		$order_id = $request->input('id');
-
-		$query = Order::with(['user:id,email', 'goods:id,name', 'coupon:id,name,sn']);
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-		if(isset($order_sn)){
-			$query->where('order_sn', 'like', '%'.$order_sn.'%');
-		}
-
-		if(isset($is_coupon)){
-			if($is_coupon){
-				$query->where('coupon_id', '<>', 0);
-			}else{
-				$query->whereCouponId(0);
-			}
-		}
-
-		if(isset($is_expire)){
-			$query->whereIsExpire($is_expire);
-		}
-
-		if(isset($pay_way)){
-			$query->wherePayWay($pay_way);
-		}
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		if(isset($range_time) && $range_time !== ','){
-			$range_time = explode(',', $range_time);
-			$query->where('created_at', '>=', $range_time[0])->where('created_at', '<=', $range_time[1]);
-		}
-
-		if(isset($order_id)){
-			$query->whereId($order_id);
-		}
-
-		if($sort){
-			$query->orderBy('id');
-		}else{
-			$query->orderByDesc('id');
-		}
-
-		$view['orderList'] = $query->paginate(15)->appends($request->except('page'));
-
-		return view('admin.logs.orderList', $view);
-	}
-
-	// 重置用户流量
-	public function resetUserTraffic(Request $request): JsonResponse {
-		User::find($request->input('id'))->update(['u' => 0, 'd' => 0]);
-
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 操作用户余额
-	public function handleUserCredit(Request $request) {
-		if($request->isMethod('POST')){
-			$userId = $request->input('user_id');
-			$amount = $request->input('amount');
-
-			if(empty($userId) || empty($amount)){
-				return Response::json(['status' => 'fail', 'message' => '充值异常']);
-			}
-			$user = User::find($userId);
-
-			// 写入余额变动日志
-			Helpers::addUserCreditLog($userId, 0, $user->credit, $user->credit + $amount, $amount, '后台手动充值');
-
-			// 加减余额
-			if((new UserService($user))->updateCredit($amount)){
-				return Response::json(['status' => 'success', 'message' => '充值成功']);
-			}
-
-			return Response::json(['status' => 'fail', 'message' => '充值失败']);
-		}
-
-		return view('admin.handleUserCredit');
-	}
-
-	// 用户余额变动记录
-	public function userCreditLogList(Request $request) {
-		$email = $request->input('email');
-
-		$query = UserCreditLog::with('user:id,email')->latest();
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		$view['list'] = $query->paginate(15)->appends($request->except('page'));
-
-		return view('admin.logs.userCreditLogList', $view);
-	}
+class AdminController extends Controller
+{
+
+    public function index()
+    {
+        $past = strtotime("-" . sysConfig('expire_days') . " days");
+
+        $view['expireDays']             = sysConfig('expire_days');
+        $view['totalUserCount']         = User::count(); // 总用户数
+        $view['enableUserCount']        = User::whereEnable(1)->count(
+        ); // 有效用户数
+        $view['activeUserCount']        = User::where('t', '>=', $past)->count(
+        ); // 活跃用户数
+        $view['unActiveUserCount']      = User::whereEnable(1)->whereBetween(
+            't',
+            [
+                1,
+                $past,
+            ]
+        )->count(); // 不活跃用户数
+        $view['onlineUserCount']        = User::where(
+            't',
+            '>=',
+            strtotime("-10 minutes")
+        )->count(); // 10分钟内在线用户数
+        $view['expireWarningUserCount'] = User::whereBetween(
+            'expired_at',
+            [date('Y-m-d'), strtotime("+" . sysConfig('expire_days') . " days")]
+        )->count(); // 临近过期用户数
+        $view['largeTrafficUserCount']  = User::whereRaw(
+            '(u + d) >= 107374182400'
+        )
+                                              ->where('status', '<>', -1)
+                                              ->count(); // 流量超过100G的用户
+
+        $view['flowAbnormalUserCount'] = count(
+            $this->trafficAbnormal()
+        );// 1小时内流量异常用户
+        $view['nodeCount']             = Node::count();
+        $view['unnormalNodeCount']     = Node::whereStatus(0)->count();
+        $view['flowCount']             = flowAutoShow(
+            NodeDailyDataFlow::where(
+                'created_at',
+                '>=',
+                date('Y-m-d', strtotime("-30 days"))
+            )->sum('total')
+        );
+        $view['totalFlowCount']        = flowAutoShow(
+            NodeDailyDataFlow::sum('total')
+        );
+        $view['totalCredit']           = User::where('credit', '<>', 0)->sum(
+                'credit'
+            ) / 100;
+        $view['totalWaitRefAmount']    = ReferralLog::whereIn('status', [0, 1])
+                                                    ->sum('commission') / 100;
+        $view['totalRefAmount']        = ReferralApply::whereStatus(2)->sum(
+                'amount'
+            ) / 100;
+        $view['totalOrder']            = Order::count();
+        $view['totalOnlinePayOrder']   = Order::where('pay_type', '<>', 0)
+                                              ->count();
+        $view['totalSuccessOrder']     = Order::whereStatus(2)->count();
+        $view['todaySuccessOrder']     = Order::whereStatus(2)->whereDate(
+            'created_at',
+            date('Y-m-d')
+        )->count();
+        // 今日
+        $view['todayRegister'] = User::whereDate('created_at', date('Y-m-d'))
+                                     ->count();
+
+        return view('admin.index', $view);
+    }
+
+    // 1小时内流量异常用户
+    private function trafficAbnormal(): array
+    {
+        $result               = [];
+        $userTotalTrafficList = UserHourlyDataFlow::whereNodeId(0)
+                                                  ->where('total', '>', MB * 50)
+                                                  ->where(
+                                                      'created_at',
+                                                      '>=',
+                                                      date(
+                                                          'Y-m-d H:i:s',
+                                                          time() - 3900
+                                                      )
+                                                  )
+                                                  ->groupBy('user_id')
+                                                  ->selectRaw(
+                                                      "user_id, sum(total) as totalTraffic"
+                                                  )
+                                                  ->get(); // 只统计50M以上的记录,加快速度
+        foreach ($userTotalTrafficList as $user) {
+            if ($user->totalTraffic > sysConfig('traffic_ban_value') * GB) {
+                $result[] = $user->user_id;
+            }
+        }
+
+        return $result;
+    }
+
+    // 用户列表
+    public function userList(Request $request)
+    {
+        $id            = $request->input('id');
+        $email         = $request->input('email');
+        $wechat        = $request->input('wechat');
+        $qq            = $request->input('qq');
+        $port          = $request->input('port');
+        $status        = $request->input('status');
+        $enable        = $request->input('enable');
+        $online        = $request->input('online');
+        $flowAbnormal  = $request->input('flowAbnormal');
+        $expireWarning = $request->input('expireWarning');
+        $largeTraffic  = $request->input('largeTraffic');
+
+        $query = User::with('subscribe');
+        if (isset($id)) {
+            $query->whereId($id);
+        }
+
+        if (isset($email)) {
+            $query->where('email', 'like', '%' . $email . '%');
+        }
+
+        if (isset($wechat)) {
+            $query->where('wechat', 'like', '%' . $wechat . '%');
+        }
+
+        if (isset($qq)) {
+            $query->where('qq', 'like', '%' . $qq . '%');
+        }
+
+        if (isset($port)) {
+            $query->wherePort($port);
+        }
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        if (isset($enable)) {
+            $query->whereEnable($enable);
+        }
+
+        // 流量超过100G的
+        if ($largeTraffic) {
+            $query->whereIn('status', [0, 1])->whereRaw(
+                '(u + d) >= 107374182400'
+            );
+        }
+
+        // 临近过期提醒
+        if ($expireWarning) {
+            $query->whereBetween(
+                'expired_at',
+                [
+                    date('Y-m-d'),
+                    date(
+                        'Y-m-d',
+                        strtotime("+" . sysConfig('expire_days') . " days")
+                    ),
+                ]
+            );
+        }
+
+        // 当前在线
+        if ($online) {
+            $query->where('t', '>=', strtotime("-10 minutes"));
+        }
+
+        // 不活跃用户
+        if ($request->input('unActive')) {
+            $query->whereBetween(
+                't',
+                [
+                    1,
+                    strtotime(
+                        "-" . sysConfig(
+                            'expire_days'
+                        ) . " days"
+                    ),
+                ]
+            )->whereEnable(1);
+        }
+
+        // 1小时内流量异常用户
+        if ($flowAbnormal) {
+            $query->whereIn('id', $this->trafficAbnormal());
+        }
+
+        $userList = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+        foreach ($userList as $user) {
+            $user->transfer_enable = flowAutoShow($user->transfer_enable);
+            $user->used_flow       = flowAutoShow($user->u + $user->d);
+            if ($user->expired_at < date('Y-m-d')) {
+                $user->expireWarning = -1; // 已过期
+            } elseif ($user->expired_at == date('Y-m-d')) {
+                $user->expireWarning = 0; // 今天过期
+            } elseif ($user->expired_at > date(
+                    'Y-m-d'
+                ) && $user->expired_at <= date(
+                    'Y-m-d',
+                    strtotime("+30 days")
+                )) {
+                $user->expireWarning = 1; // 最近一个月过期
+            } else {
+                $user->expireWarning = 2; // 大于一个月过期
+            }
+
+            // 流量异常警告
+            $totalTraffic         = UserHourlyDataFlow::userRecentUsed(
+                $user->id
+            )->sum(
+                'total'
+            );
+            $user->trafficWarning = $totalTraffic > (sysConfig(
+                                                         'traffic_ban_value'
+                                                     ) * GB) ? 1 : 0;
+
+            // 订阅地址
+            $user->link = (sysConfig('subscribe_domain') ?: sysConfig(
+                    'website_url'
+                )) . '/s/' . $user->subscribe->code;
+        }
+
+        $view['userList'] = $userList;
+
+        return view('admin.user.userList', $view);
+    }
+
+    // 添加账号
+    public function addUser(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            // 校验email是否已存在
+            $exists = User::whereEmail($request->input('email'))->first();
+            if ($exists) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '用户名已存在,请重新输入']
+                );
+            }
+
+            $user                  = new User();
+            $user->username        = $request->input('username');
+            $user->email           = $request->input('email');
+            $user->password        = Hash::make(
+                $request->input('password') ?: Str::random()
+            );
+            $user->port            = $request->input('port') ?: $this->makePort(
+            );
+            $user->passwd          = $request->input('passwd') ?: Str::random();
+            $user->vmess_id        = $request->input('uuid') ?: Str::uuid();
+            $user->transfer_enable = toGB(
+                $request->input('transfer_enable') ?: 0
+            );
+            $user->enable          = $request->input('enable') ?: 0;
+            $user->method          = $request->input('method');
+            $user->protocol        = $request->input('protocol');
+            $user->obfs            = $request->input('obfs');
+            $user->speed_limit     = $request->input('speed_limit') * Mbps;
+            $user->wechat          = $request->input('wechat');
+            $user->qq              = $request->input('qq');
+            $user->expired_at      = $request->input('expired_at') ?: date(
+                'Y-m-d',
+                strtotime("+365 days")
+            );
+            $user->remark          = str_replace(
+                ["atob", "eval"],
+                "",
+                $request->input('remark')
+            );
+            $user->level           = $request->input('level') ?: 0;
+            $user->group_id        = $request->input('group_id') ?: 0;
+            $user->reg_ip          = getClientIp();
+            $user->reset_time      = $request->input('reset_time') > date(
+                'Y-m-d'
+            ) ? $request->input('reset_time') : null;
+            $user->invite_num      = $request->input('invite_num') ?: 0;
+            $user->status          = $request->input('status') ?: 0;
+            $user->save();
+
+            if ($user->id) {
+                // 写入用户流量变动记录
+                Helpers::addUserTrafficModifyLog(
+                    $user->id,
+                    0,
+                    0,
+                    toGB($request->input('transfer_enable', 0)),
+                    '后台手动添加用户'
+                );
+
+                return Response::json(
+                    ['status' => 'success', 'message' => '添加成功']
+                );
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '添加失败']);
+        }
+
+        // 生成一个可用端口
+        $view['methodList']   = Helpers::methodList();
+        $view['protocolList'] = Helpers::protocolList();
+        $view['obfsList']     = Helpers::obfsList();
+        $view['levelList']    = Level::orderBy('level')->get();
+        $view['groupList']    = UserGroup::orderBy('id')->get();
+
+        return view('admin.user.userInfo', $view);
+    }
+
+    // 生成端口
+    public function makePort(): int
+    {
+        return Helpers::getPort();
+    }
+
+    // 批量生成账号
+    public function batchAddUsers(Request $request): ?JsonResponse
+    {
+        $amount = $request->input('amount');
+        try {
+            DB::beginTransaction();
+
+            for ($i = 0; $i < $amount; $i++) {
+                $uid = Helpers::addUser(
+                    Str::random(8) . '@auto.generate',
+                    Hash::make(Str::random()),
+                    toGB(1024),
+                    365
+                );
+                // 生成一个可用端口
+
+                if ($uid) {
+                    // 写入用户流量变动记录
+                    Helpers::addUserTrafficModifyLog(
+                        $uid,
+                        0,
+                        0,
+                        toGB(1024),
+                        '后台批量生成用户'
+                    );
+                }
+            }
+
+            DB::commit();
+
+            return Response::json(
+                ['status' => 'success', 'message' => '批量生成账号成功']
+            );
+        } catch (Exception $e) {
+            DB::rollBack();
+
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'message' => '批量生成账号失败:' . $e->getMessage(),
+                ]
+            );
+        }
+    }
+
+    // 编辑账号
+    public function editUser(Request $request, $id)
+    {
+        $user = User::find($id);
+        if ($request->isMethod('POST')) {
+            $email           = $request->input('email');
+            $password        = $request->input('password');
+            $port            = $request->input('port');
+            $transfer_enable = $request->input('transfer_enable');
+            $is_admin        = $request->input('is_admin');
+            $status          = $request->input('status');
+
+            // 校验email是否已存在
+            if (User::where('id', '<>', $id)->whereEmail($email)->exists()) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '用户名已存在,请重新输入']
+                );
+            }
+
+            // 校验端口是否已存在
+            if (User::where('id', '<>', $id)->where('port', '>', 0)->wherePort(
+                $port
+            )->exists()) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '端口已存在,请重新输入']
+                );
+            }
+
+            // 禁止取消默认管理员
+            if ($id == 1 && $is_admin == 0) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '系统默认管理员不可取消']
+                );
+            }
+
+            try {
+                DB::beginTransaction();
+                $data = [
+                    'username'        => $request->input('username'),
+                    'email'           => $email,
+                    'port'            => $port,
+                    'passwd'          => $request->input(
+                        'passwd'
+                    ) ?: Str::random(),
+                    'vmess_id'        => $request->input('uuid') ?: Str::uuid(),
+                    'transfer_enable' => toGB($transfer_enable ?: 0),
+                    'enable'          => $status < 0 ? 0 : $request->input(
+                        'enable'
+                    ),
+                    'method'          => $request->input('method'),
+                    'protocol'        => $request->input('protocol'),
+                    'obfs'            => $request->input('obfs'),
+                    'speed_limit'     => $request->input('speed_limit') * Mbps,
+                    'wechat'          => $request->input('wechat'),
+                    'qq'              => $request->input('qq'),
+                    'expired_at'      => $request->input('expired_at') ?: date(
+                        'Y-m-d',
+                        strtotime("+365 days")
+                    ),
+                    'remark'          => str_replace(
+                        "eval",
+                        "",
+                        str_replace(
+                            "atob",
+                            "",
+                            $request->input(
+                                'remark'
+                            )
+                        )
+                    ),
+                    'level'           => $request->input('level'),
+                    'group_id'        => $request->input('group_id'),
+                    'reset_time'      => $request->input('reset_time'),
+                    'invite_num'      => $request->input('invite_num'),
+                    'status'          => $status,
+                ];
+
+                // 只有admin才有权限操作管理员属性
+                if (Auth::getUser()->is_admin == 1) {
+                    $data['is_admin'] = (int)$is_admin;
+                }
+
+                // 非演示环境才可以修改管理员密码
+                if ( ! empty($password) && ! (config('app.demo') && $id == 1)) {
+                    $data['password'] = Hash::make($password);
+                }
+
+                $user->update($data);
+
+                // 写入用户流量变动记录
+                if ($user->transfer_enable != toGB($transfer_enable)) {
+                    Helpers::addUserTrafficModifyLog(
+                        $id,
+                        0,
+                        $user->transfer_enable,
+                        toGB($transfer_enable),
+                        '后台手动编辑用户'
+                    );
+                }
+
+                DB::commit();
+
+                return Response::json(
+                    ['status' => 'success', 'message' => '编辑成功']
+                );
+            } catch (Exception $e) {
+                DB::rollBack();
+                Log::error('编辑用户信息异常:' . $e->getMessage());
+
+                return Response::json(
+                    ['status' => 'fail', 'message' => '编辑失败']
+                );
+            }
+        } else {
+            if ($user) {
+                $user->transfer_enable = flowToGB($user->transfer_enable);
+            }
+
+            $view['user']         = $user->load('inviter:id,email');
+            $view['methodList']   = Helpers::methodList();
+            $view['protocolList'] = Helpers::protocolList();
+            $view['obfsList']     = Helpers::obfsList();
+            $view['levelList']    = Level::orderBy('level')->get();
+            $view['groupList']    = UserGroup::orderBy('id')->get();
+
+            return view('admin.user.userInfo', $view);
+        }
+    }
+
+    // 删除用户
+    public function delUser(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        if ($id <= 1) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '系统管理员不可删除']
+            );
+        }
+
+        try {
+            DB::beginTransaction();
+
+            User::find($id)->delete();
+
+            DB::commit();
+
+            return Response::json(['status' => 'success', 'message' => '删除成功']);
+        } catch (Exception $e) {
+            Log::error('删除用户信息异常:' . $e->getMessage());
+            DB::rollBack();
+
+            return Response::json(['status' => 'fail', 'message' => '删除失败']);
+        }
+    }
+
+    // 文章列表
+    public function articleList(Request $request)
+    {
+        $view['list'] = Article::orderByDesc('sort')->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.article.articleList', $view);
+    }
+
+    // 添加文章
+    public function addArticle(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $article          = new Article();
+            $article->title   = $request->input('title');
+            $article->type    = $request->input('type', 1);
+            $article->summary = $request->input('summary');
+            // LOGO
+            if ($article->type == 4) {
+                $article->logo = $request->input('logo');
+            } else {
+                $logo = '';
+                if ($request->hasFile('logo')) {
+                    $logo = $this->uploadFile($request->file('logo'));
+
+                    if ( ! $logo) {
+                        Session::flash('errorMsg', 'LOGO不合法');
+
+                        return Redirect::back()->withInput();
+                    }
+                }
+
+                $article          = new Article();
+                $article->title   = $request->input('title');
+                $article->type    = $request->input('type', 1);
+                $article->summary = $request->input('summary');
+                $article->logo    = $logo;
+            }
+            $article->content = $request->input('content');
+            $article->sort    = $request->input('sort', 0);
+            $article->save();
+
+            if ($article->id) {
+                Session::flash('successMsg', '添加成功');
+            } else {
+                Session::flash('errorMsg', '添加失败');
+            }
+
+            return Redirect::to('admin/articleList');
+        }
+
+        return view('admin.article.addArticle');
+    }
+
+    // 编辑文章
+    public function editArticle(Request $request)
+    {
+        $id = $request->input('id');
+
+        if ($request->isMethod('POST')) {
+            $title   = $request->input('title');
+            $type    = $request->input('type');
+            $summary = $request->input('summary');
+            $content = $request->input('content');
+            $sort    = $request->input('sort');
+
+            // 商品LOGO
+            if ($type == 4) {
+                $logo = $request->input('logo');
+            } else {
+                $logo = '';
+                if ($request->hasFile('logo')) {
+                    $logo = $this->uploadFile($request->file('logo'));
+
+                    if ( ! $logo) {
+                        Session::flash('errorMsg', 'LOGO不合法');
+
+                        return Redirect::back()->withInput();
+                    }
+                }
+            }
+
+            $data = [
+                'type'    => $type,
+                'title'   => $title,
+                'summary' => $summary,
+                'content' => $content,
+                'sort'    => $sort,
+            ];
+
+            if ($logo) {
+                $data['logo'] = $logo;
+            }
+
+            $ret = Article::whereId($id)->update($data);
+            if ($ret) {
+                Session::flash('successMsg', '编辑成功');
+            } else {
+                Session::flash('errorMsg', '编辑失败');
+            }
+
+            return Redirect::to('admin/editArticle?id=' . $id);
+        }
+
+        $view['article'] = Article::find($id);
+
+        return view('admin.article.editArticle', $view);
+    }
+
+    // 删除文章
+    public function delArticle(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        $ret = Article::whereId($id)->delete();
+        if ($ret) {
+            return Response::json(['status' => 'success', 'message' => '删除成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '删除失败']);
+    }
+
+    // 流量日志
+    public function trafficLog(Request $request)
+    {
+        $port      = $request->input('port');
+        $user_id   = $request->input('user_id');
+        $email     = $request->input('email');
+        $nodeId    = $request->input('nodeId');
+        $startTime = $request->input('startTime');
+        $endTime   = $request->input('endTime');
+
+        $query = UserDataFlowLog::with(['user', 'node']);
+
+        if (isset($port)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($port) {
+                    $q->wherePort($port);
+                }
+            );
+        }
+
+        if (isset($user_id)) {
+            $query->whereUserId($user_id);
+        }
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        if (isset($nodeId)) {
+            $query->whereNodeId($nodeId);
+        }
+
+        if (isset($startTime)) {
+            $query->where('log_time', '>=', strtotime($startTime));
+        }
+
+        if (isset($endTime)) {
+            $query->where('log_time', '<=', strtotime($endTime));
+        }
+
+        // 已使用流量
+        $view['totalTraffic'] = flowAutoShow(
+            $query->sum('u') + $query->sum('d')
+        );
+
+        $list = $query->latest('log_time')->paginate(20)->appends(
+            $request->except('page')
+        );
+        foreach ($list as $vo) {
+            $vo->u        = flowAutoShow($vo->u);
+            $vo->d        = flowAutoShow($vo->d);
+            $vo->log_time = date('Y-m-d H:i:s', $vo->log_time);
+        }
+
+        $view['list']     = $list;
+        $view['nodeList'] = Node::whereStatus(1)
+                                ->orderByDesc('sort')
+                                ->latest()
+                                ->get();
+
+        return view('admin.logs.trafficLog', $view);
+    }
+
+    // 导出配置信息
+    public function export(Request $request, $id)
+    {
+        if (empty($id)) {
+            return Redirect::to('admin/userList');
+        }
+
+        $user = User::find($id);
+        if (empty($user)) {
+            return Redirect::to('admin/userList');
+        }
+
+        if ($request->isMethod('POST')) {
+            $infoType = $request->input('type');
+
+            $node = Node::find($request->input('id'));
+            if ($node->type == 1) {
+                if ($node->compatible) {
+                    $proxyType = 'SS';
+                } else {
+                    $proxyType = 'SSR';
+                }
+            } else {
+                $proxyType = 'V2Ray';
+            }
+
+            $data = $this->getUserNodeInfo(
+                $id,
+                $node->id,
+                $infoType !== 'text' ? 0 : 1
+            );
+
+            return Response::json(
+                ['status' => 'success', 'data' => $data, 'title' => $proxyType]
+            );
+        }
+
+        $view['nodeList'] = Node::whereStatus(1)
+                                ->orderByDesc('sort')
+                                ->orderBy('id')
+                                ->paginate(15)
+                                ->appends($request->except('page'));
+        $view['user']     = $user;
+
+        return view('admin.user.export', $view);
+    }
+
+    // 导出原版SS用户配置信息
+    public function exportSSJson()
+    {
+        $userList = User::where('port', '>', 0)->get();
+
+        $json = '';
+        if ( ! $userList->isEmpty()) {
+            $userPassword = [];
+            foreach ($userList as $user) {
+                $userPassword[] = $user->port . ":" . $user->passwd;
+            }
+
+            $json = json_encode(
+                [
+                    'server'        => '0.0.0.0',
+                    'local_address' => '127.0.0.1',
+                    'local_port'    => 1080,
+                    'port_password' => $userPassword,
+                    'timeout'       => 300,
+                    'method'        => Helpers::getDefaultMethod(),
+                    'fast_open'     => false,
+                ]
+            );
+        }
+
+        // 生成JSON文件
+        $fileName = Str::random() . '_shadowsocks.json';
+        $filePath = public_path('downloads/' . $fileName);
+        file_put_contents($filePath, $json);
+
+        if ( ! is_file($filePath)) {
+            exit('文件生成失败,请检查目录权限');
+        }
+
+        return Response::download($filePath);
+    }
+
+    // 修改个人资料
+    public function profile(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $new_password = $request->input('new_password');
+
+            if ( ! Hash::check(
+                $request->input('old_password'),
+                Auth::getUser()->password
+            )) {
+                return Redirect::back()->withErrors('旧密码错误,请重新输入');
+            }
+
+            if (Hash::check($new_password, Auth::getUser()->password)) {
+                return Redirect::back()->withErrors('新密码不可与旧密码一样,请重新输入');
+            }
+
+            $ret = Auth::getUser()->update(
+                ['password' => Hash::make($new_password)]
+            );
+            if ( ! $ret) {
+                return Redirect::back()->withErrors('修改失败');
+            }
+
+            return Redirect::back()->with('successMsg', '修改成功');
+        }
+
+        return view('admin.config.profile');
+    }
+
+    // 用户流量监控
+    public function userMonitor(Request $request)
+    {
+        $id = $request->input('id');
+        if (empty($id)) {
+            return Redirect::to('admin/userList');
+        }
+
+        $user = User::find($id);
+        if (empty($user)) {
+            return Redirect::to('admin/userList');
+        }
+
+        $view['email'] = $user->email;
+        $view          = array_merge($view, $this->dataFlowChart($user->id));
+
+        return view('admin.logs.userMonitor', $view);
+    }
+
+    // 加密方式、混淆、协议、等级、国家地区
+    public function config(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $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);
+
+            if (empty($name)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '配置名称不能为空']
+                );
+            }
+
+            // 校验是否已存在
+            $config = SsConfig::type($type)->whereName($name)->first();
+            if ($config) {
+                return Response::json(
+                    ['status' => 'fail', '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' => '添加成功']);
+        }
+
+        $view['methodList']   = SsConfig::type(1)->get();
+        $view['protocolList'] = SsConfig::type(2)->get();
+        $view['obfsList']     = SsConfig::type(3)->get();
+        $view['countryList']  = Country::all();
+        $view['levelList']    = Level::all();
+        $view['labelList']    = Label::with('nodes')->get();
+
+        return view('admin.config.config', $view);
+    }
+
+    // 删除配置
+    public function delConfig(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        $ret = SsConfig::whereId($id)->delete();
+        if ($ret) {
+            return Response::json(['status' => 'success', 'message' => '删除成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '删除失败']);
+    }
+
+    // 设置默认配置
+    public function setDefaultConfig(Request $request): JsonResponse
+    {
+        $id = $request->input('id');
+
+        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]);
+
+        // 将该ID对应记录值置为默认值
+        SsConfig::whereId($id)->update(['is_default' => 1]);
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 设置系统扩展信息,例如客服、统计代码
+    public function setExtend(Request $request): ?RedirectResponse
+    {
+        $websiteAnalytics       = $request->input('website_analytics');
+        $websiteCustomerService = $request->input('website_customer_service');
+
+        try {
+            DB::beginTransaction();
+
+            // 首页LOGO
+            if ($request->hasFile('website_home_logo')) {
+                $ret = $this->uploadFile($request->file('website_home_logo'));
+                if ( ! $ret) {
+                    Session::flash('errorMsg', 'LOGO不合法');
+
+                    return Redirect::back();
+                }
+                Config::find('website_home_logo')->update(['value' => $ret]);
+            }
+
+            // 站内LOGO
+            if ($request->hasFile('website_logo')) {
+                $ret = $this->uploadFile($request->file('website_logo'));
+                if ( ! $ret) {
+                    Session::flash('errorMsg', 'LOGO不合法');
+
+                    return Redirect::back();
+                }
+                Config::find('website_logo')->update(['value' => $ret]);
+            }
+
+            Config::find('website_analytics')->update(
+                ['value' => $websiteAnalytics]
+            );
+            Config::find('website_customer_service')->update(
+                ['value' => $websiteCustomerService]
+            );
+
+            Session::flash('successMsg', '更新成功');
+
+            DB::commit();
+
+            return Redirect::back();
+        } catch (Exception $e) {
+            DB::rollBack();
+
+            Session::flash('errorMsg', '更新失败');
+
+            return Redirect::back();
+        }
+    }
+
+    // 添加等级
+    public function addLevel(Request $request): ?JsonResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'level'      => 'required|numeric|unique:level,level',
+                'level_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) {
+            return Response::json(['status' => 'success', 'message' => '提交成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 编辑等级
+    public function updateLevel(Request $request): JsonResponse
+    {
+        $id    = $request->input('id');
+        $level = $request->input('level');
+
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'id'         => 'required|numeric',
+                'level'      => 'required|numeric',
+                'level_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' => '该等级下存在关联账号,请先取消关联!']
+            );
+        }
+
+        Level::whereId($id)->update(
+            ['level' => $level, 'name' => $request->input('level_name')]
+        );
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 删除等级
+    public function delLevel(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'id' => 'required|numeric|exists:level,id',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Response::json(
+                ['status' => 'fail', 'message' => $validator->errors()->all()]
+            );
+        }
+
+        $level = Level::find($id);
+
+        // 校验该等级下是否存在关联账号
+        $userCount = User::whereLevel($level->level)->count();
+        if ($userCount) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '该等级下存在关联账号,请先取消关联']
+            );
+        }
+        $ret = false;
+        try {
+            $ret = Level::whereId($id)->delete();
+        } catch (Exception $e) {
+            Log::error('删除等级时报错:' . $e->getMessage());
+        }
+        if ($ret) {
+            return Response::json(['status' => 'success', 'message' => '操作成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 添加国家/地区
+    public function addCountry(Request $request): JsonResponse
+    {
+        $code = $request->input('code');
+        $name = $request->input('name');
+
+        if (empty($code)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '国家/地区代码不能为空']
+            );
+        }
+
+        if (empty($name)) {
+            return Response::json(
+                ['status' => 'fail', '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' => '操作失败']);
+    }
+
+    // 编辑国家/地区
+    public function updateCountry(Request $request): JsonResponse
+    {
+        $code = $request->input('code');
+        $name = $request->input('name');
+
+        if (empty($name)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '国家/地区名称不能为空']
+            );
+        }
+
+        if (empty($code)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '国家/地区代码不能为空']
+            );
+        }
+
+        $country = Country::find($code);
+        if ( ! $country) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '国家/地区不存在']
+            );
+        }
+
+        // 校验该国家/地区下是否存在关联节点
+        if (Node::whereCountryCode($country->code)->exists()) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '该国家/地区下存在关联节点,请先取消关联']
+            );
+        }
+
+        $ret = $country->update(['name' => $name, 'code' => $code]);
+        if ($ret) {
+            return Response::json(['status' => 'success', 'message' => '操作成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 删除国家/地区
+    public function delCountry(Request $request): ?JsonResponse
+    {
+        $code = $request->input('code');
+
+        if (empty($id)) {
+            return Response::json(['status' => 'fail', 'message' => 'ID不能为空']);
+        }
+
+        $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 ($country->delete()) {
+            return Response::json(['status' => 'success', 'message' => '操作成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 系统设置
+    public function system()
+    {
+        $view              = Config::pluck('value', 'name')->toArray();
+        $view['labelList'] = Label::orderByDesc('sort')->orderBy('id')->get();
+
+        return view('admin.config.system', $view);
+    }
+
+    // 设置某个配置项
+    public function setConfig(Request $request): JsonResponse
+    {
+        $name  = $request->input('name');
+        $value = $request->input('value');
+
+        if ( ! $name) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '设置失败:请求参数异常']
+            );
+        }
+
+        // 屏蔽异常配置
+        if ( ! in_array($name, Config::pluck('name')->toArray())) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '设置失败:配置不存在']
+            );
+        }
+
+        // 如果开启用户邮件重置密码,则先设置网站名称和网址
+        if ($value !== '0'
+            && in_array(
+                $name,
+                [
+                    'is_reset_password',
+                    'is_activate_account',
+                    'expire_warning',
+                    'traffic_warning',
+                ],
+                true
+            )) {
+            $config = Config::find('website_name');
+            if ( ! $config->value) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '设置失败:启用该配置需要先设置【网站名称】']
+                );
+            }
+
+            $config = Config::find('website_url');
+            if ( ! $config->value) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '设置失败:启用该配置需要先设置【网站地址】']
+                );
+            }
+        }
+
+        // 支付设置判断
+        if ($value !== null && in_array(
+                $name,
+                [
+                    'is_AliPay',
+                    'is_QQPay',
+                    'is_WeChatPay',
+                    'is_otherPay',
+                ],
+                true
+            )) {
+            switch ($value) {
+                case 'f2fpay':
+                    if ( ! sysConfig('f2fpay_app_id') || ! sysConfig(
+                            'f2fpay_private_key'
+                        ) || ! sysConfig('f2fpay_public_key')) {
+                        return Response::json(
+                            [
+                                'status'  => 'fail',
+                                'message' => '请先设置【支付宝F2F】必要参数',
+                            ]
+                        );
+                    }
+                    break;
+                case 'codepay':
+                    if ( ! sysConfig('codepay_url') || ! sysConfig(
+                            'codepay_id'
+                        ) || ! sysConfig('codepay_key')) {
+                        return Response::json(
+                            ['status' => 'fail', 'message' => '请先设置【码支付】必要参数']
+                        );
+                    }
+                    break;
+                case 'epay':
+                    if ( ! sysConfig('epay_url') || ! sysConfig(
+                            'epay_mch_id'
+                        ) || ! sysConfig('epay_key')) {
+                        return Response::json(
+                            ['status' => 'fail', 'message' => '请先设置【易支付】必要参数']
+                        );
+                    }
+                    break;
+                case 'payjs':
+                    if ( ! sysConfig('payjs_mch_id') || ! sysConfig(
+                            'payjs_key'
+                        )) {
+                        return Response::json(
+                            ['status' => 'fail', 'message' => '请先设置【PayJs】必要参数']
+                        );
+                    }
+                    break;
+                case 'bitpayx':
+                    if ( ! sysConfig('bitpay_secret')) {
+                        return Response::json(
+                            ['status' => 'fail', 'message' => '请先设置【麻瓜宝】必要参数']
+                        );
+                    }
+                    break;
+                case 'paypal':
+                    if ( ! sysConfig('paypal_username') || ! sysConfig(
+                            'paypal_password'
+                        ) || ! sysConfig('paypal_secret')) {
+                        return Response::json(
+                            [
+                                'status'  => 'fail',
+                                'message' => '请先设置【PayPal】必要参数',
+                            ]
+                        );
+                    }
+                    break;
+                default:
+                    return Response::json(
+                        ['status' => 'fail', 'message' => '未知支付渠道']
+                    );
+            }
+        }
+
+        // 演示环境禁止修改特定配置项
+        if (env('APP_DEMO')) {
+            $denyConfig = [
+                'website_url',
+                'min_rand_traffic',
+                'max_rand_traffic',
+                'push_bear_send_key',
+                'push_bear_qrcode',
+                'is_forbid_china',
+                'website_security_code',
+            ];
+
+            if (in_array($name, $denyConfig, true)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '演示环境禁止修改该配置']
+                );
+            }
+        }
+
+        // 如果是返利比例,则需要除100
+        if ($name === 'referral_percent') {
+            $value = (int)$value / 100;
+        }
+
+        // 更新配置
+        Config::find($name)->update(['value' => $value]);
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 推送通知测试
+    public function sendTestNotification(): JsonResponse
+    {
+        if (sysConfig('is_notification')) {
+            $result = PushNotification::send('这是测试的标题', 'ProxyPanel测试内容');
+            if ($result === false) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '发送失败,请重新尝试!']
+                );
+            }
+            switch (sysConfig('is_notification')) {
+                case 'serverChan':
+                    if ( ! $result['errno']) {
+                        return Response::json(
+                            [
+                                'status'  => 'success',
+                                'message' => '发送成功,请查看手机是否收到推送消息',
+                            ]
+                        );
+                    }
+
+                    return Response::json(
+                        [
+                            'status'  => 'fail',
+                            'message' => $result ? $result['errmsg'] : '未知',
+                        ]
+                    );
+                case 'bark':
+                    if ($result['code'] == 200) {
+                        return Response::json(
+                            [
+                                'status'  => 'success',
+                                'message' => '发送成功,请查看手机是否收到推送消息',
+                            ]
+                        );
+                    }
+
+                    return Response::json(
+                        ['status' => 'fail', 'message' => $result['message']]
+                    );
+                default:
+            }
+        }
+
+        return Response::json(
+            ['status' => 'fail', 'message' => '请先选择【日志通知】渠道']
+        );
+    }
+
+    // 邀请码列表
+    public function inviteList(Request $request)
+    {
+        $view['inviteList'] = Invite::with(
+            ['invitee:id,email', 'inviter:id,email']
+        )
+                                    ->orderBy('status')
+                                    ->latest()
+                                    ->paginate(15)
+                                    ->appends($request->except('page'));
+
+        return view('admin.inviteList', $view);
+    }
+
+    // 生成邀请码
+    public function makeInvite(): JsonResponse
+    {
+        for ($i = 0; $i < 10; $i++) {
+            $obj             = new Invite();
+            $obj->inviter_id = 0;
+            $obj->invitee_id = 0;
+            $obj->code       = strtoupper(
+                substr(md5(microtime() . Str::random(6)), 8, 12)
+            );
+            $obj->status     = 0;
+            $obj->dateline   = date(
+                'Y-m-d H:i:s',
+                strtotime(
+                    "+" . sysConfig(
+                        'admin_invite_days'
+                    ) . " days"
+                )
+            );
+            $obj->save();
+        }
+
+        return Response::json(['status' => 'success', 'message' => '生成成功']);
+    }
+
+    // 导出邀请码
+    public function exportInvite(): void
+    {
+        $inviteList = Invite::whereStatus(0)->orderBy('id')->get();
+
+        $filename = '邀请码' . date('Ymd') . '.xlsx';
+
+        $spreadsheet = new Spreadsheet();
+        $spreadsheet->getProperties()
+                    ->setCreator('ProxyPanel')
+                    ->setLastModifiedBy('ProxyPanel')
+                    ->setTitle('邀请码')
+                    ->setSubject('邀请码')
+                    ->setDescription('')
+                    ->setKeywords('')
+                    ->setCategory('');
+
+        try {
+            $spreadsheet->setActiveSheetIndex(0);
+            $sheet = $spreadsheet->getActiveSheet();
+            $sheet->setTitle('邀请码');
+            $sheet->fromArray(['邀请码', '有效期'], null);
+
+            foreach ($inviteList as $k => $vo) {
+                $sheet->fromArray(
+                    [$vo->code, $vo->dateline],
+                    null,
+                    'A' . ($k + 2)
+                );
+            }
+
+            header(
+                'Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+            ); // 输出07Excel文件
+            //header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件
+            header(
+                'Content-Disposition: attachment;filename="' . $filename . '"'
+            );
+            header('Cache-Control: max-age=0');
+            $writer = new Xlsx($spreadsheet);
+            $writer->save('php://output');
+        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
+            Log::error('导出优惠券时报错' . $e->getMessage());
+        }
+    }
+
+    // 订单列表
+    public function orderList(Request $request)
+    {
+        $email      = $request->input('email');
+        $order_sn   = $request->input('order_sn');
+        $is_coupon  = $request->input('is_coupon');
+        $is_expire  = $request->input('is_expire');
+        $pay_way    = $request->input('pay_way');
+        $status     = $request->input('status');
+        $range_time = $request->input('range_time');
+        $sort       = $request->input('sort'); // 0-按创建时间降序、1-按创建时间升序
+        $order_id   = $request->input('id');
+
+        $query = Order::with(
+            ['user:id,email', 'goods:id,name', 'coupon:id,name,sn']
+        );
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+        if (isset($order_sn)) {
+            $query->where('order_sn', 'like', '%' . $order_sn . '%');
+        }
+
+        if (isset($is_coupon)) {
+            if ($is_coupon) {
+                $query->where('coupon_id', '<>', 0);
+            } else {
+                $query->whereCouponId(0);
+            }
+        }
+
+        if (isset($is_expire)) {
+            $query->whereIsExpire($is_expire);
+        }
+
+        if (isset($pay_way)) {
+            $query->wherePayWay($pay_way);
+        }
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        if (isset($range_time) && $range_time !== ',') {
+            $range_time = explode(',', $range_time);
+            $query->where('created_at', '>=', $range_time[0])->where(
+                'created_at',
+                '<=',
+                $range_time[1]
+            );
+        }
+
+        if (isset($order_id)) {
+            $query->whereId($order_id);
+        }
+
+        if ($sort) {
+            $query->orderBy('id');
+        } else {
+            $query->orderByDesc('id');
+        }
+
+        $view['orderList'] = $query->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.logs.orderList', $view);
+    }
+
+    // 重置用户流量
+    public function resetUserTraffic(Request $request): JsonResponse
+    {
+        User::find($request->input('id'))->update(['u' => 0, 'd' => 0]);
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 操作用户余额
+    public function handleUserCredit(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $userId = $request->input('user_id');
+            $amount = $request->input('amount');
+
+            if (empty($userId) || empty($amount)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '充值异常']
+                );
+            }
+            $user = User::find($userId);
+
+            // 写入余额变动日志
+            Helpers::addUserCreditLog(
+                $userId,
+                0,
+                $user->credit,
+                $user->credit + $amount,
+                $amount,
+                '后台手动充值'
+            );
+
+            // 加减余额
+            if ((new UserService($user))->updateCredit($amount)) {
+                return Response::json(
+                    ['status' => 'success', 'message' => '充值成功']
+                );
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '充值失败']);
+        }
+
+        return view('admin.handleUserCredit');
+    }
+
+    // 用户余额变动记录
+    public function userCreditLogList(Request $request)
+    {
+        $email = $request->input('email');
+
+        $query = UserCreditLog::with('user:id,email')->latest();
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        $view['list'] = $query->paginate(15)->appends($request->except('page'));
+
+        return view('admin.logs.userCreditLogList', $view);
+    }
+
+    // 用户封禁记录
+    public function userBanLogList(Request $request)
+    {
+        $email = $request->input('email');
+
+        $query = UserBanedLog::with('user:id,email,t')->latest();
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        $view['list'] = $query->paginate(15)->appends($request->except('page'));
+
+        return view('admin.logs.userBanLogList', $view);
+    }
+
+    // 用户流量变动记录
+    public function userTrafficLogList(Request $request)
+    {
+        $email = $request->input('email');
+
+        $query = UserDataModifyLog::with(
+            ['user:id,email', 'order.goods:id,name']
+        );
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        $view['list'] = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.logs.userTrafficLogList', $view);
+    }
+
+    // 用户在线IP记录
+    public function userOnlineIPList(Request $request)
+    {
+        $email  = $request->input('email');
+        $port   = $request->input('port');
+        $wechat = $request->input('wechat');
+        $qq     = $request->input('qq');
+
+        $query = User::activeUser();
+
+        if (isset($email)) {
+            $query->where('email', 'like', '%' . $email . '%');
+        }
+
+        if (isset($wechat)) {
+            $query->where('wechat', 'like', '%' . $wechat . '%');
+        }
+
+        if (isset($qq)) {
+            $query->where('qq', 'like', '%' . $qq . '%');
+        }
+
+        if (isset($port)) {
+            $query->wherePort($port);
+        }
+
+        $userList = $query->paginate(15)->appends($request->except('page'));
+
+        $nodeOnlineIPs = NodeOnlineUserIp::with('node:id,name')
+                                         ->where(
+                                             'created_at',
+                                             '>=',
+                                             strtotime("-10 minutes")
+                                         )
+                                         ->latest()
+                                         ->distinct();
+        // Todo 优化查询
+        foreach ($userList as $user) {
+            // 最近5条在线IP记录,如果后端设置为60秒上报一次,则为10分钟内的在线IP
+            $user->onlineIPList = $nodeOnlineIPs->wherePort($user->port)->limit(
+                5
+            )->get();
+        }
+
+        $view['userList'] = $userList;
+
+        return view('admin.logs.userOnlineIPList', $view);
+    }
+
+    // 转换成某个用户的身份
+    public function switchToUser(Request $request): JsonResponse
+    {
+        $id = $request->input('user_id');
+
+        $user = User::find($id);
+        if ( ! $user) {
+            return Response::json(['status' => 'fail', 'message' => "用户不存在"]);
+        }
+
+        // 存储当前管理员ID,并将当前登录信息改成要切换的用户的身份信息
+        Session::put('admin', Auth::id());
+        Auth::login($user);
+
+        return Response::json(['status' => 'success', 'message' => "身份切换成功"]);
+    }
+
+    // 添加标签
+    public function addLabel(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $name = $request->input('name');
+            $sort = $request->input('sort');
+
+            $label       = new Label();
+            $label->name = $name;
+            $label->sort = $sort;
+            $label->save();
+
+            return Response::json(['status' => 'success', 'message' => '添加成功']);
+        }
+
+        return view('admin.label.addLabel');
+    }
+
+    // 编辑标签
+    public function editLabel(Request $request)
+    {
+        $id = $request->input('id');
+        if ($request->isMethod('POST')) {
+            $name = $request->input('name');
+            $sort = $request->input('sort');
+
+            Label::whereId($id)->update(['name' => $name, 'sort' => $sort]);
+
+            return Response::json(['status' => 'success', 'message' => '添加成功']);
+        }
+
+        $view['label'] = Label::find($id);
+
+        return view('admin.label.editLabel', $view);
+    }
+
+    // 删除标签
+    public function delLabel(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        try {
+            DB::beginTransaction();
+
+            Label::whereId($id)->delete();
+            NodeLabel::whereLabelId($id)->delete(); // 删除节点关联
+
+            DB::commit();
+
+            return Response::json(['status' => 'success', 'message' => '删除成功']);
+        } catch (Exception $e) {
+            DB::rollBack();
+
+            return Response::json(
+                ['status' => 'fail', 'message' => '删除失败:' . $e->getMessage()]
+            );
+        }
+    }
+
+    // 邮件发送日志列表
+    public function notificationLog(Request $request)
+    {
+        $email = $request->input('email');
+        $type  = $request->input('type');
+
+        $query = NotificationLog::query();
+
+        if (isset($email)) {
+            $query->where('address', 'like', '%' . $email . '%');
+        }
+
+        if (isset($type)) {
+            $query->whereType($type);
+        }
+
+        $view['list'] = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.logs.notificationLog', $view);
+    }
+
+    // 在线IP监控(实时)
+    public function onlineIPMonitor(Request $request)
+    {
+        $ip     = $request->input('ip');
+        $email  = $request->input('email');
+        $port   = $request->input('port');
+        $nodeId = $request->input('nodeId');
+        $userId = $request->input('id');
+
+        $query = NodeOnlineUserIp::with(['node:id,name', 'user:id,email'])
+                                 ->where(
+                                     'created_at',
+                                     '>=',
+                                     strtotime("-2 minutes")
+                                 );
+
+        if (isset($ip)) {
+            $query->whereIp($ip);
+        }
+
+        if (isset($email)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($email) {
+                    $q->where('email', 'like', '%' . $email . '%');
+                }
+            );
+        }
+
+        if (isset($port)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($port) {
+                    $q->wherePort($port);
+                }
+            );
+        }
+
+        if (isset($nodeId)) {
+            $query->whereHas(
+                'node',
+                static function ($q) use ($nodeId) {
+                    $q->whereId($nodeId);
+                }
+            );
+        }
+
+        if (isset($userId)) {
+            $query->whereHas(
+                'user',
+                static function ($q) use ($userId) {
+                    $q->whereId($userId);
+                }
+            );
+        }
+
+        $onlineIPLogs = $query->groupBy('user_id', 'node_id')
+                              ->latest()
+                              ->paginate(20)
+                              ->appends($request->except('page'));
+        foreach ($onlineIPLogs as $log) {
+            // 跳过上报多IP的
+            if ($log->ip == null || strpos($log->ip, ',') !== false) {
+                continue;
+            }
+            $ipInfo = QQWry::ip($log->ip);
+            if (isset($ipInfo['error'])) {
+                // 用IPIP的库再试一下
+                $ipip   = IPIP::ip($log->ip);
+                $ipInfo = [
+                    'country'  => $ipip['country_name'],
+                    'province' => $ipip['region_name'],
+                    'city'     => $ipip['city_name'],
+                ];
+            }
+
+            $log->ipInfo = $ipInfo['country'] . ' ' . $ipInfo['province'] . ' ' . $ipInfo['city'];
+        }
+
+        $view['list']     = $onlineIPLogs;
+        $view['nodeList'] = Node::whereStatus(1)
+                                ->orderByDesc('sort')
+                                ->latest()
+                                ->get();
+
+        return view('admin.logs.onlineIPMonitor', $view);
+    }
 
-	// 用户封禁记录
-	public function userBanLogList(Request $request) {
-		$email = $request->input('email');
-
-		$query = UserBanedLog::with('user:id,email,t')->latest();
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		$view['list'] = $query->paginate(15)->appends($request->except('page'));
-
-		return view('admin.logs.userBanLogList', $view);
-	}
-
-	// 用户流量变动记录
-	public function userTrafficLogList(Request $request) {
-		$email = $request->input('email');
-
-		$query = UserDataModifyLog::with(['user:id,email', 'order.goods:id,name']);
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		$view['list'] = $query->latest()->paginate(15)->appends($request->except('page'));
-
-		return view('admin.logs.userTrafficLogList', $view);
-	}
-
-	// 用户在线IP记录
-	public function userOnlineIPList(Request $request) {
-		$email = $request->input('email');
-		$port = $request->input('port');
-		$wechat = $request->input('wechat');
-		$qq = $request->input('qq');
-
-		$query = User::activeUser();
-
-		if(isset($email)){
-			$query->where('email', 'like', '%'.$email.'%');
-		}
-
-		if(isset($wechat)){
-			$query->where('wechat', 'like', '%'.$wechat.'%');
-		}
-
-		if(isset($qq)){
-			$query->where('qq', 'like', '%'.$qq.'%');
-		}
-
-		if(isset($port)){
-			$query->wherePort($port);
-		}
-
-		$userList = $query->paginate(15)->appends($request->except('page'));
-
-		$nodeOnlineIPs = NodeOnlineUserIp::with('node:id,name')
-		                                 ->where('created_at', '>=', strtotime("-10 minutes"))
-		                                 ->latest()
-		                                 ->distinct();
-		// Todo 优化查询
-		foreach($userList as $user){
-			// 最近5条在线IP记录,如果后端设置为60秒上报一次,则为10分钟内的在线IP
-			$user->onlineIPList = $nodeOnlineIPs->wherePort($user->port)->limit(5)->get();
-		}
-
-		$view['userList'] = $userList;
-
-		return view('admin.logs.userOnlineIPList', $view);
-	}
-
-	// 转换成某个用户的身份
-	public function switchToUser(Request $request): JsonResponse {
-		$id = $request->input('user_id');
-
-		$user = User::find($id);
-		if(!$user){
-			return Response::json(['status' => 'fail', 'message' => "用户不存在"]);
-		}
-
-		// 存储当前管理员ID,并将当前登录信息改成要切换的用户的身份信息
-		Session::put('admin', Auth::id());
-		Auth::login($user);
-
-		return Response::json(['status' => 'success', 'message' => "身份切换成功"]);
-	}
-
-	// 添加标签
-	public function addLabel(Request $request) {
-		if($request->isMethod('POST')){
-			$name = $request->input('name');
-			$sort = $request->input('sort');
-
-			$label = new Label();
-			$label->name = $name;
-			$label->sort = $sort;
-			$label->save();
-
-			return Response::json(['status' => 'success', 'message' => '添加成功']);
-		}
-
-		return view('admin.label.addLabel');
-	}
-
-	// 编辑标签
-	public function editLabel(Request $request) {
-		$id = $request->input('id');
-		if($request->isMethod('POST')){
-			$name = $request->input('name');
-			$sort = $request->input('sort');
-
-			Label::whereId($id)->update(['name' => $name, 'sort' => $sort]);
-
-			return Response::json(['status' => 'success', 'message' => '添加成功']);
-		}
-
-		$view['label'] = Label::find($id);
-
-		return view('admin.label.editLabel', $view);
-	}
-
-	// 删除标签
-	public function delLabel(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		try{
-			DB::beginTransaction();
-
-			Label::whereId($id)->delete();
-			NodeLabel::whereLabelId($id)->delete(); // 删除节点关联
-
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '删除成功']);
-		}catch(Exception $e){
-			DB::rollBack();
-
-			return Response::json(['status' => 'fail', 'message' => '删除失败:'.$e->getMessage()]);
-		}
-	}
-
-	// 邮件发送日志列表
-	public function notificationLog(Request $request) {
-		$email = $request->input('email');
-		$type = $request->input('type');
-
-		$query = NotificationLog::query();
-
-		if(isset($email)){
-			$query->where('address', 'like', '%'.$email.'%');
-		}
-
-		if(isset($type)){
-			$query->whereType($type);
-		}
-
-		$view['list'] = $query->latest()->paginate(15)->appends($request->except('page'));
-
-		return view('admin.logs.notificationLog', $view);
-	}
-
-	// 在线IP监控(实时)
-	public function onlineIPMonitor(Request $request) {
-		$ip = $request->input('ip');
-		$email = $request->input('email');
-		$port = $request->input('port');
-		$nodeId = $request->input('nodeId');
-		$userId = $request->input('id');
-
-		$query = NodeOnlineUserIp::with(['node:id,name', 'user:id,email'])
-		                         ->where('created_at', '>=', strtotime("-2 minutes"));
-
-		if(isset($ip)){
-			$query->whereIp($ip);
-		}
-
-		if(isset($email)){
-			$query->whereHas('user', static function($q) use ($email) {
-				$q->where('email', 'like', '%'.$email.'%');
-			});
-		}
-
-		if(isset($port)){
-			$query->whereHas('user', static function($q) use ($port) {
-				$q->wherePort($port);
-			});
-		}
-
-		if(isset($nodeId)){
-			$query->whereHas('node', static function($q) use ($nodeId) {
-				$q->whereId($nodeId);
-			});
-		}
-
-		if(isset($userId)){
-			$query->whereHas('user', static function($q) use ($userId) {
-				$q->whereId($userId);
-			});
-		}
-
-		$onlineIPLogs = $query->groupBy('user_id', 'node_id')
-		                      ->latest()
-		                      ->paginate(20)
-		                      ->appends($request->except('page'));
-		foreach($onlineIPLogs as $log){
-			// 跳过上报多IP的
-			if($log->ip == null || strpos($log->ip, ',') !== false){
-				continue;
-			}
-			$ipInfo = QQWry::ip($log->ip);
-			if(isset($ipInfo['error'])){
-				// 用IPIP的库再试一下
-				$ipip = IPIP::ip($log->ip);
-				$ipInfo = [
-					'country'  => $ipip['country_name'],
-					'province' => $ipip['region_name'],
-					'city'     => $ipip['city_name']
-				];
-			}
-
-			$log->ipInfo = $ipInfo['country'].' '.$ipInfo['province'].' '.$ipInfo['city'];
-		}
-
-		$view['list'] = $onlineIPLogs;
-		$view['nodeList'] = Node::whereStatus(1)->orderByDesc('sort')->latest()->get();
-
-		return view('admin.logs.onlineIPMonitor', $view);
-	}
 }

+ 190 - 156
app/Http/Controllers/Api/WebApi/BaseController.php

@@ -16,160 +16,194 @@ use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Response;
 
-class BaseController {
-	// 上报节点心跳信息
-	public function setNodeStatus(Request $request, $id): JsonResponse {
-		$cpu = (int) $request->input('cpu') / 100;
-		$mem = (int) $request->input('mem') / 100;
-		$disk = (int) $request->input('disk') / 100;
-
-		if(is_null($request->input('uptime'))){
-			return $this->returnData('上报节点心跳信息失败,请检查字段');
-		}
-
-		$obj = new NodeHeartBeat();
-		$obj->node_id = $id;
-		$obj->uptime = (int) $request->input('uptime');
-		//$obj->load = $request->input('load');
-		$obj->load = implode(' ', [$cpu, $mem, $disk]);
-		$obj->log_time = time();
-		$obj->save();
-
-		if($obj->id){
-			return $this->returnData('上报节点心跳信息成功', 'success', 200);
-		}
-
-		return $this->returnData('上报节点心跳信息失败,请检查字段');
-	}
-
-	// 返回数据
-	public function returnData($message, $status = 'fail', $code = 400, $data = [], $addition = []): JsonResponse {
-		$data = ['status' => $status, 'code' => $code, 'data' => $data, 'message' => $message];
-
-		if($addition){
-			$data = array_merge($data, $addition);
-		}
-
-		return Response::json($data);
-	}
-
-	// 上报节点在线人数
-	public function setNodeOnline(Request $request, $id): JsonResponse {
-		$inputArray = $request->all();
-		$onlineCount = 0;
-		foreach($inputArray as $input){
-			if(!array_key_exists('ip', $input) || !array_key_exists('uid', $input)){
-				return $this->returnData('上报节点在线情况失败,请检查字段');
-			}
-
-			if(!isset($input['ip'], $input['uid'])){
-				return $this->returnData('上报节点在线情况失败,请检查字段');
-			}
-
-			$obj = new NodeOnlineUserIp();
-			$obj->node_id = $id;
-			$obj->user_id = $input['uid'];
-			$obj->ip = $input['ip'];
-			$obj->port = User::find($input['uid'])->port;
-			$obj->created_at = time();
-			$obj->save();
-
-			if(!$obj->id){
-				return $this->returnData('上报节点在线情况失败,请检查字段');
-			}
-			$onlineCount++;
-		}
-
-		$obj = new NodeOnlineLog();
-		$obj->node_id = $id;
-		$obj->online_user = $onlineCount;
-		$obj->log_time = time();
-		$obj->save();
-
-		if($obj->id){
-			return $this->returnData('上报节点在线情况成功', 'success', 200);
-		}
-
-		return $this->returnData('上报节点在线情况失败,请检查字段');
-	}
-
-	// 上报用户流量日志
-	public function setUserTraffic(Request $request, $id): JsonResponse {
-		foreach($request->all() as $input){
-			if(!array_key_exists('uid', $input)){
-				return $this->returnData('上报用户流量日志失败,请检查字段');
-			}
-
-			$rate = Node::find($id)->traffic_rate;
-
-			$log = new UserDataFlowLog();
-			$log->user_id = (int) $input['uid'];
-			$log->u = (int) $input['upload'] * $rate;
-			$log->d = (int) $input['download'] * $rate;
-			$log->node_id = $id;
-			$log->rate = $rate;
-			$log->traffic = flowAutoShow($log->u + $log->d);
-			$log->log_time = time();
-			$log->save();
-
-			if(!$log->id){
-				return $this->returnData('上报用户流量日志失败,请检查字段');
-			}
-			$user = User::find($log->user_id);
-			if($user){
-				$user->u += $log->u;
-				$user->d += $log->d;
-				$user->t = time();
-				$user->save();
-			}
-		}
-
-		return $this->returnData('上报用户流量日志成功', 'success', 200);
-	}
-
-	// 获取节点的审计规则
-	public function getNodeRule($id): JsonResponse {
-		$nodeRule = RuleGroupNode::whereNodeId($id)->first();
-		$data = [];
-		//节点未设置任何审计规则
-		if($nodeRule){
-			$ruleGroup = RuleGroup::find($nodeRule->rule_group_id);
-			if($ruleGroup && $ruleGroup->rules){
-				foreach($ruleGroup->rules as $ruleId){
-					$rule = Rule::find($ruleId);
-					if($rule){
-						$data[] = [
-							'id'      => $rule->id,
-							'type'    => $rule->type_api_label,
-							'pattern' => $rule->pattern
-						];
-					}
-				}
-
-				return $this->returnData('获取节点审计规则成功', 'success', 200,
-					['mode' => $ruleGroup->type? 'reject' : 'allow', 'rules' => $data]);
-			}
-		}
-
-		//放行
-		return $this->returnData('获取节点审计规则成功', 'success', 200, ['mode' => 'all', 'rules' => $data]);
-	}
-
-	// 上报用户触发的审计规则记录
-	public function addRuleLog(Request $request, $id): JsonResponse {
-		if($request->has(['uid', 'rule_id', 'reason'])){
-			$obj = new RuleLog();
-			$obj->user_id = $request->input('uid');
-			$obj->node_id = $id;
-			$obj->rule_id = $request->input('rule_id');
-			$obj->reason = $request->input('reason');
-			$obj->save();
-
-			if($obj->id){
-				return $this->returnData('上报用户触发审计规则记录成功', 'success', 200);
-			}
-		}
-
-		return $this->returnData('上报用户触发审计规则记录失败');
-	}
+class BaseController
+{
+
+    // 上报节点心跳信息
+    public function setNodeStatus(Request $request, $id): JsonResponse
+    {
+        $cpu  = (int)$request->input('cpu') / 100;
+        $mem  = (int)$request->input('mem') / 100;
+        $disk = (int)$request->input('disk') / 100;
+
+        if (is_null($request->input('uptime'))) {
+            return $this->returnData('上报节点心跳信息失败,请检查字段');
+        }
+
+        $obj          = new NodeHeartBeat();
+        $obj->node_id = $id;
+        $obj->uptime  = (int)$request->input('uptime');
+        //$obj->load = $request->input('load');
+        $obj->load     = implode(' ', [$cpu, $mem, $disk]);
+        $obj->log_time = time();
+        $obj->save();
+
+        if ($obj->id) {
+            return $this->returnData('上报节点心跳信息成功', 'success', 200);
+        }
+
+        return $this->returnData('上报节点心跳信息失败,请检查字段');
+    }
+
+    // 返回数据
+    public function returnData(
+        $message,
+        $status = 'fail',
+        $code = 400,
+        $data = [],
+        $addition = []
+    ): JsonResponse {
+        $data = [
+            'status'  => $status,
+            'code'    => $code,
+            'data'    => $data,
+            'message' => $message,
+        ];
+
+        if ($addition) {
+            $data = array_merge($data, $addition);
+        }
+
+        return Response::json($data);
+    }
+
+    // 上报节点在线人数
+    public function setNodeOnline(Request $request, $id): JsonResponse
+    {
+        $inputArray  = $request->all();
+        $onlineCount = 0;
+        foreach ($inputArray as $input) {
+            if ( ! array_key_exists('ip', $input) || ! array_key_exists(
+                    'uid',
+                    $input
+                )) {
+                return $this->returnData('上报节点在线情况失败,请检查字段');
+            }
+
+            if ( ! isset($input['ip'], $input['uid'])) {
+                return $this->returnData('上报节点在线情况失败,请检查字段');
+            }
+
+            $obj             = new NodeOnlineUserIp();
+            $obj->node_id    = $id;
+            $obj->user_id    = $input['uid'];
+            $obj->ip         = $input['ip'];
+            $obj->port       = User::find($input['uid'])->port;
+            $obj->created_at = time();
+            $obj->save();
+
+            if ( ! $obj->id) {
+                return $this->returnData('上报节点在线情况失败,请检查字段');
+            }
+            $onlineCount++;
+        }
+
+        $obj              = new NodeOnlineLog();
+        $obj->node_id     = $id;
+        $obj->online_user = $onlineCount;
+        $obj->log_time    = time();
+        $obj->save();
+
+        if ($obj->id) {
+            return $this->returnData('上报节点在线情况成功', 'success', 200);
+        }
+
+        return $this->returnData('上报节点在线情况失败,请检查字段');
+    }
+
+    // 上报用户流量日志
+    public function setUserTraffic(Request $request, $id): JsonResponse
+    {
+        foreach ($request->all() as $input) {
+            if ( ! array_key_exists('uid', $input)) {
+                return $this->returnData('上报用户流量日志失败,请检查字段');
+            }
+
+            $rate = Node::find($id)->traffic_rate;
+
+            $log           = new UserDataFlowLog();
+            $log->user_id  = (int)$input['uid'];
+            $log->u        = (int)$input['upload'] * $rate;
+            $log->d        = (int)$input['download'] * $rate;
+            $log->node_id  = $id;
+            $log->rate     = $rate;
+            $log->traffic  = flowAutoShow($log->u + $log->d);
+            $log->log_time = time();
+            $log->save();
+
+            if ( ! $log->id) {
+                return $this->returnData('上报用户流量日志失败,请检查字段');
+            }
+            $user = User::find($log->user_id);
+            if ($user) {
+                $user->u += $log->u;
+                $user->d += $log->d;
+                $user->t = time();
+                $user->save();
+            }
+        }
+
+        return $this->returnData('上报用户流量日志成功', 'success', 200);
+    }
+
+    // 获取节点的审计规则
+    public function getNodeRule($id): JsonResponse
+    {
+        $nodeRule = RuleGroupNode::whereNodeId($id)->first();
+        $data     = [];
+        //节点未设置任何审计规则
+        if ($nodeRule) {
+            $ruleGroup = RuleGroup::find($nodeRule->rule_group_id);
+            if ($ruleGroup && $ruleGroup->rules) {
+                foreach ($ruleGroup->rules as $ruleId) {
+                    $rule = Rule::find($ruleId);
+                    if ($rule) {
+                        $data[] = [
+                            'id'      => $rule->id,
+                            'type'    => $rule->type_api_label,
+                            'pattern' => $rule->pattern,
+                        ];
+                    }
+                }
+
+                return $this->returnData(
+                    '获取节点审计规则成功',
+                    'success',
+                    200,
+                    [
+                        'mode'  => $ruleGroup->type ? 'reject' : 'allow',
+                        'rules' => $data,
+                    ]
+                );
+            }
+        }
+
+        //放行
+        return $this->returnData(
+            '获取节点审计规则成功',
+            'success',
+            200,
+            ['mode' => 'all', 'rules' => $data]
+        );
+    }
+
+    // 上报用户触发的审计规则记录
+    public function addRuleLog(Request $request, $id): JsonResponse
+    {
+        if ($request->has(['uid', 'rule_id', 'reason'])) {
+            $obj          = new RuleLog();
+            $obj->user_id = $request->input('uid');
+            $obj->node_id = $id;
+            $obj->rule_id = $request->input('rule_id');
+            $obj->reason  = $request->input('reason');
+            $obj->save();
+
+            if ($obj->id) {
+                return $this->returnData('上报用户触发审计规则记录成功', 'success', 200);
+            }
+        }
+
+        return $this->returnData('上报用户触发审计规则记录失败');
+    }
+
 }

+ 49 - 33
app/Http/Controllers/Api/WebApi/TrojanController.php

@@ -5,37 +5,53 @@ namespace App\Http\Controllers\Api\WebApi;
 use App\Models\Node;
 use Illuminate\Http\JsonResponse;
 
-class TrojanController extends BaseController {
-	// 获取节点信息
-	public function getNodeInfo($id): JsonResponse {
-		$node = Node::find($id);
-
-		return $this->returnData('获取节点信息成功', 'success', 200, [
-			'id'           => $node->id,
-			'is_udp'       => $node->is_udp? true : false,
-			'speed_limit'  => $node->speed_limit,
-			'client_limit' => $node->client_limit,
-			'push_port'    => $node->push_port,
-			'redirect_url' => sysConfig('redirect_url'),
-			'trojan_port'  => $node->port,
-			'secret'       => $node->auth->secret,
-			'license'      => sysConfig('trojan_license'),
-		]);
-	}
-
-	// 获取节点可用的用户列表
-	public function getUserList($id): JsonResponse {
-		$users = Node::find($id)->node_access_users;
-		$data = [];
-
-		foreach($users as $user){
-			$data[] = [
-				'uid'         => $user->id,
-				'password'    => $user->passwd,
-				'speed_limit' => $user->speed_limit
-			];
-		}
-
-		return $this->returnData('获取用户列表成功', 'success', 200, $data, ['updateTime' => time()]);
-	}
+class TrojanController extends BaseController
+{
+
+    // 获取节点信息
+    public function getNodeInfo($id): JsonResponse
+    {
+        $node = Node::find($id);
+
+        return $this->returnData(
+            '获取节点信息成功',
+            'success',
+            200,
+            [
+                'id'           => $node->id,
+                'is_udp'       => $node->is_udp ? true : false,
+                'speed_limit'  => $node->speed_limit,
+                'client_limit' => $node->client_limit,
+                'push_port'    => $node->push_port,
+                'redirect_url' => sysConfig('redirect_url'),
+                'trojan_port'  => $node->port,
+                'secret'       => $node->auth->secret,
+                'license'      => sysConfig('trojan_license'),
+            ]
+        );
+    }
+
+    // 获取节点可用的用户列表
+    public function getUserList($id): JsonResponse
+    {
+        $users = Node::find($id)->node_access_users;
+        $data  = [];
+
+        foreach ($users as $user) {
+            $data[] = [
+                'uid'         => $user->id,
+                'password'    => $user->passwd,
+                'speed_limit' => $user->speed_limit,
+            ];
+        }
+
+        return $this->returnData(
+            '获取用户列表成功',
+            'success',
+            200,
+            $data,
+            ['updateTime' => time()]
+        );
+    }
+
 }

+ 83 - 62
app/Http/Controllers/Api/WebApi/V2RayController.php

@@ -7,74 +7,95 @@ use App\Models\NodeCertificate;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 
-class V2RayController extends BaseController {
-	// 获取节点信息
-	public function getNodeInfo($id): JsonResponse {
-		$node = Node::find($id);
-		$nodeDv = NodeCertificate::whereDomain($node->v2_host)->first();
+class V2RayController extends BaseController
+{
 
-		return $this->returnData('获取节点信息成功', 'success', 200, [
-			'id'              => $node->id,
-			'is_udp'          => $node->is_udp? true : false,
-			'speed_limit'     => $node->speed_limit,
-			'client_limit'    => $node->client_limit,
-			'push_port'       => $node->push_port,
-			'redirect_url'    => sysConfig('redirect_url'),
-			'secret'          => $node->auth->secret,
-			'key'             => $nodeDv? $nodeDv->key : '',
-			'pem'             => $nodeDv? $nodeDv->pem : '',
-			'v2_license'      => sysConfig('v2ray_license'),
-			'v2_alter_id'     => $node->v2_alter_id,
-			'v2_port'         => $node->v2_port,
-			'v2_method'       => $node->v2_method,
-			'v2_net'          => $node->v2_net,
-			'v2_type'         => $node->v2_type,
-			'v2_host'         => $node->v2_host,
-			'v2_path'         => $node->v2_path,
-			'v2_tls'          => $node->v2_tls? true : false,
-			'v2_tls_provider' => $node->tls_provider?: sysConfig('v2ray_tls_provider'),
-		]);
-	}
+    // 获取节点信息
+    public function getNodeInfo($id): JsonResponse
+    {
+        $node   = Node::find($id);
+        $nodeDv = NodeCertificate::whereDomain($node->v2_host)->first();
 
-	// 获取节点可用的用户列表
-	public function getUserList($id): JsonResponse {
-		$users = Node::find($id)->node_access_users;
-		$data = [];
+        return $this->returnData(
+            '获取节点信息成功',
+            'success',
+            200,
+            [
+                'id'              => $node->id,
+                'is_udp'          => $node->is_udp ? true : false,
+                'speed_limit'     => $node->speed_limit,
+                'client_limit'    => $node->client_limit,
+                'push_port'       => $node->push_port,
+                'redirect_url'    => sysConfig('redirect_url'),
+                'secret'          => $node->auth->secret,
+                'key'             => $nodeDv ? $nodeDv->key : '',
+                'pem'             => $nodeDv ? $nodeDv->pem : '',
+                'v2_license'      => sysConfig('v2ray_license'),
+                'v2_alter_id'     => $node->v2_alter_id,
+                'v2_port'         => $node->v2_port,
+                'v2_method'       => $node->v2_method,
+                'v2_net'          => $node->v2_net,
+                'v2_type'         => $node->v2_type,
+                'v2_host'         => $node->v2_host,
+                'v2_path'         => $node->v2_path,
+                'v2_tls'          => $node->v2_tls ? true : false,
+                'v2_tls_provider' => $node->tls_provider ?: sysConfig(
+                    'v2ray_tls_provider'
+                ),
+            ]
+        );
+    }
 
-		foreach($users as $user){
-			$data[] = [
-				'uid'         => $user->id,
-				'vmess_uid'   => $user->vmess_id,
-				'speed_limit' => $user->speed_limit
-			];
-		}
+    // 获取节点可用的用户列表
+    public function getUserList($id): JsonResponse
+    {
+        $users = Node::find($id)->node_access_users;
+        $data  = [];
 
-		return $this->returnData('获取用户列表成功', 'success', 200, $data, ['updateTime' => time()]);
-	}
+        foreach ($users as $user) {
+            $data[] = [
+                'uid'         => $user->id,
+                'vmess_uid'   => $user->vmess_id,
+                'speed_limit' => $user->speed_limit,
+            ];
+        }
 
-	// 上报节点伪装域名证书信息
-	public function addCertificate(Request $request, $id): JsonResponse {
-		$key = $request->input('key');
-		$pem = $request->input('pem');
+        return $this->returnData(
+            '获取用户列表成功',
+            'success',
+            200,
+            $data,
+            ['updateTime' => time()]
+        );
+    }
 
-		if($request->has(['key', 'pem'])){
-			$node = Node::find($id);
-			$Dv = NodeCertificate::whereDomain($node->v2_host)->first();
-			if($Dv){
-				$ret = NodeCertificate::whereId($Dv->id)->update(['key' => $key, 'pem' => $pem]);
-			}else{
-				$ret = new NodeCertificate();
-				$ret->domain = $node->server;
-				$ret->key = $request->input('key');
-				$ret->pem = $request->input('pem');
-				$ret->save();
-			}
+    // 上报节点伪装域名证书信息
+    public function addCertificate(Request $request, $id): JsonResponse
+    {
+        $key = $request->input('key');
+        $pem = $request->input('pem');
 
-			if($ret){
-				return $this->returnData('上报节点伪装域名证书成功', 'success', 200);
-			}
-		}
+        if ($request->has(['key', 'pem'])) {
+            $node = Node::find($id);
+            $Dv   = NodeCertificate::whereDomain($node->v2_host)->first();
+            if ($Dv) {
+                $ret = NodeCertificate::whereId($Dv->id)->update(
+                    ['key' => $key, 'pem' => $pem]
+                );
+            } else {
+                $ret         = new NodeCertificate();
+                $ret->domain = $node->server;
+                $ret->key    = $request->input('key');
+                $ret->pem    = $request->input('pem');
+                $ret->save();
+            }
+
+            if ($ret) {
+                return $this->returnData('上报节点伪装域名证书成功', 'success', 200);
+            }
+        }
+
+        return $this->returnData('上报节点伪装域名证书失败,请检查字段');
+    }
 
-		return $this->returnData('上报节点伪装域名证书失败,请检查字段');
-	}
 }

+ 65 - 49
app/Http/Controllers/Api/WebApi/VNetController.php

@@ -5,53 +5,69 @@ namespace App\Http\Controllers\Api\WebApi;
 use App\Models\Node;
 use Illuminate\Http\JsonResponse;
 
-class VNetController extends BaseController {
-	// 获取节点信息
-	public function getNodeInfo($id): JsonResponse {
-		$node = Node::find($id);
-
-		return $this->returnData('获取节点信息成功', 'success', 200, [
-			'id'           => $node->id,
-			'method'       => $node->method,
-			'protocol'     => $node->protocol,
-			'obfs'         => $node->obfs,
-			'obfs_param'   => $node->obfs_param?: '',
-			'is_udp'       => $node->is_udp,
-			'speed_limit'  => $node->speed_limit,
-			'client_limit' => $node->client_limit,
-			'single'       => $node->single,
-			'port'         => (string) $node->port,
-			'passwd'       => $node->passwd?: '',
-			'push_port'    => $node->push_port,
-			'secret'       => $node->auth->secret,
-			'redirect_url' => sysConfig('redirect_url')
-		]);
-	}
-
-	// 获取节点可用的用户列表
-	public function getUserList($id): JsonResponse {
-		$node = Node::find($id);
-		$users = $node->node_access_users;
-		$data = [];
-
-		foreach($users as $user){
-			$data[] = [
-				'uid'         => $user->id,
-				'port'        => $user->port,
-				'passwd'      => $user->passwd,
-				'method'      => $user->method,
-				'protocol'    => $user->protocol,
-				'obfs'        => $user->obfs,
-				'obfs_param'  => $node->obfs_param,
-				'speed_limit' => $user->speed_limit,
-				'enable'      => $user->enable
-			];
-		}
-
-		if($data){
-			return $this->returnData('获取用户列表成功', 'success', 200, $data, ['updateTime' => time()]);
-		}
-
-		return $this->returnData('获取用户列表失败');
-	}
+class VNetController extends BaseController
+{
+
+    // 获取节点信息
+    public function getNodeInfo($id): JsonResponse
+    {
+        $node = Node::find($id);
+
+        return $this->returnData(
+            '获取节点信息成功',
+            'success',
+            200,
+            [
+                'id'           => $node->id,
+                'method'       => $node->method,
+                'protocol'     => $node->protocol,
+                'obfs'         => $node->obfs,
+                'obfs_param'   => $node->obfs_param ?: '',
+                'is_udp'       => $node->is_udp,
+                'speed_limit'  => $node->speed_limit,
+                'client_limit' => $node->client_limit,
+                'single'       => $node->single,
+                'port'         => (string)$node->port,
+                'passwd'       => $node->passwd ?: '',
+                'push_port'    => $node->push_port,
+                'secret'       => $node->auth->secret,
+                'redirect_url' => sysConfig('redirect_url'),
+            ]
+        );
+    }
+
+    // 获取节点可用的用户列表
+    public function getUserList($id): JsonResponse
+    {
+        $node  = Node::find($id);
+        $users = $node->node_access_users;
+        $data  = [];
+
+        foreach ($users as $user) {
+            $data[] = [
+                'uid'         => $user->id,
+                'port'        => $user->port,
+                'passwd'      => $user->passwd,
+                'method'      => $user->method,
+                'protocol'    => $user->protocol,
+                'obfs'        => $user->obfs,
+                'obfs_param'  => $node->obfs_param,
+                'speed_limit' => $user->speed_limit,
+                'enable'      => $user->enable,
+            ];
+        }
+
+        if ($data) {
+            return $this->returnData(
+                '获取用户列表成功',
+                'success',
+                200,
+                $data,
+                ['updateTime' => time()]
+            );
+        }
+
+        return $this->returnData('获取用户列表失败');
+    }
+
 }

+ 995 - 751
app/Http/Controllers/AuthController.php

@@ -37,756 +37,1000 @@ use Validator;
  *
  * @package App\Http\Controllers
  */
-class AuthController extends Controller {
-	// 登录
-	public function login(Request $request) {
-		if($request->isMethod('POST')){
-			$validator = Validator::make($request->all(), [
-				'email'    => 'required|email',
-				'password' => 'required'
-			], [
-				'email.required'    => trans('auth.email_null'),
-				'password.required' => trans('auth.password_null')
-			]);
-
-			if($validator->fails()){
-				return Redirect::back()->withInput()->withErrors($validator->errors());
-			}
-
-			$email = $request->input('email');
-			$password = $request->input('password');
-			$remember = $request->input('remember');
-
-			// 是否校验验证码
-			$captcha = $this->check_captcha($request);
-			if($captcha != false){
-				return $captcha;
-			}
-
-			// 验证账号并创建会话
-			if(!Auth::attempt(['email' => $email, 'password' => $password], $remember)){
-				return Redirect::back()->withInput()->withErrors(trans('auth.login_error'));
-			}
-			$user = Auth::getUser();
-
-			if(!$user){
-				return Redirect::back()->withInput()->withErrors(trans('auth.login_error'));
-			}
-
-			// 校验普通用户账号状态
-			if(!$user->is_admin){
-				if($user->status < 0){
-					Auth::logout(); // 强制销毁会话,因为Auth::attempt的时候会产生会话
-
-					return Redirect::back()->withInput()->withErrors(trans('auth.login_ban',
-						['email' => sysConfig('webmaster_email')]));
-				}
-
-				if($user->status == 0 && sysConfig('is_activate_account')){
-					Auth::logout(); // 强制销毁会话,因为Auth::attempt的时候会产生会话
-
-					return Redirect::back()
-					               ->withInput()
-					               ->withErrors(trans('auth.active_tip').'<a href="/activeUser?email='.$email.'" target="_blank"><span style="color:#000">【'.trans('auth.active_account').'】</span></a>');
-				}
-			}
-
-			// 写入登录日志
-			$this->addUserLoginLog($user->id, getClientIp());
-
-			// 更新登录信息
-			Auth::getUser()->update(['last_login' => time()]);
-
-			// 根据权限跳转
-			if($user->is_admin){
-				return Redirect::to('admin');
-			}
-
-			return Redirect::to('/');
-		}
-
-		if(Auth::check()){
-			if(Auth::getUser()->is_admin){
-				return Redirect::to('admin');
-			}
-
-			return Redirect::to('/');
-		}
-
-		return view('auth.login');
-	}
-
-	// 校验验证码
-	private function check_captcha($request) {
-		switch(sysConfig('is_captcha')){
-			case 1: // 默认图形验证码
-				if(!Captcha::check($request->input('captcha'))){
-					return Redirect::back()->withInput()->withErrors(trans('auth.captcha_error'));
-				}
-				break;
-			case 2: // Geetest
-				$validator = Validator::make($request->all(), [
-					'geetest_challenge' => 'required|geetest'
-				], [
-					'geetest' => trans('auth.captcha_fail')
-				]);
-
-				if($validator->fails()){
-					return Redirect::back()->withInput()->withErrors(trans('auth.captcha_fail'));
-				}
-				break;
-			case 3: // Google reCAPTCHA
-				$validator = Validator::make($request->all(), [
-					'g-recaptcha-response' => 'required|NoCaptcha'
-				]);
-
-				if($validator->fails()){
-					return Redirect::back()->withInput()->withErrors(trans('auth.captcha_fail'));
-				}
-				break;
-			case 4: // hCaptcha
-				$validator = Validator::make($request->all(), [
-					'h-captcha-response' => 'required|HCaptcha'
-				]);
-
-				if($validator->fails()){
-					return Redirect::back()->withInput()->withErrors(trans('auth.captcha_fail'));
-				}
-				break;
-			default: // 不启用验证码
-				break;
-		}
-
-		return 0;
-	}
-
-	/**
-	 * 添加用户登录日志
-	 *
-	 * @param  integer  $userId  用户ID
-	 * @param  string   $ip      IP地址
-	 */
-	private function addUserLoginLog($userId, $ip): void {
-		if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
-			Log::info('识别到IPv6,尝试解析:'.$ip);
-			$ipInfo = getIPInfo($ip);
-		}else{
-			$ipInfo = QQWry::ip($ip); // 通过纯真IP库解析IPv4信息
-			if(isset($ipInfo['error'])){
-				Log::info('无法识别IPv4,尝试使用IPIP的IP库解析:'.$ip);
-				$ipip = IPIP::ip($ip);
-				$ipInfo = [
-					'country'  => $ipip['country_name'],
-					'province' => $ipip['region_name'],
-					'city'     => $ipip['city_name']
-				];
-			}else{
-				// 判断纯真IP库获取的国家信息是否与IPIP的IP库获取的信息一致,不一致则用IPIP的(因为纯真IP库的非大陆IP准确率较低)
-				$ipip = IPIP::ip($ip);
-				if($ipInfo['country'] != $ipip['country_name']){
-					$ipInfo['country'] = $ipip['country_name'];
-					$ipInfo['province'] = $ipip['region_name'];
-					$ipInfo['city'] = $ipip['city_name'];
-				}
-			}
-		}
-
-		if(empty($ipInfo) || empty($ipInfo['country'])){
-			Log::warning("获取IP信息异常:".$ip);
-		}
-
-		$log = new UserLoginLog();
-		$log->user_id = $userId;
-		$log->ip = $ip;
-		$log->country = $ipInfo['country'] ?? '';
-		$log->province = $ipInfo['province'] ?? '';
-		$log->city = $ipInfo['city'] ?? '';
-		$log->county = $ipInfo['county'] ?? '';
-		$log->isp = $ipInfo['isp'] ?? ($ipInfo['organization'] ?? '');
-		$log->area = $ipInfo['area'] ?? '';
-		$log->save();
-	}
-
-
-	// 退出
-	public function logout(): RedirectResponse {
-		Auth::logout();
-
-		return Redirect::to('login');
-	}
-
-	// 注册
-	public function register(Request $request) {
-		$cacheKey = 'register_times_'.md5(getClientIp()); // 注册限制缓存key
-
-		if($request->isMethod('POST')){
-			$validator = Validator::make($request->all(), [
-				'username'        => 'required',
-				'email'           => 'required|email|unique:user',
-				'password'        => 'required|min:6',
-				'confirmPassword' => 'required|same:password',
-				'term'            => 'accepted'
-			], [
-				'username.required'        => trans('auth.email_null'),
-				'email.required'           => trans('auth.email_null'),
-				'email.email'              => trans('auth.email_legitimate'),
-				'email.unique'             => trans('auth.email_exist'),
-				'password.required'        => trans('auth.password_null'),
-				'password.min'             => trans('auth.password_limit'),
-				'confirmPassword.required' => trans('auth.confirm_password'),
-				'confirmPassword.same'     => trans('auth.password_same'),
-				'term.accepted'            => trans('auth.unaccepted')
-			]);
-
-			if($validator->fails()){
-				return Redirect::back()->withInput()->withErrors($validator->errors());
-			}
-
-			$username = $request->input('username');
-			$email = $request->input('email');
-			$password = $request->input('password');
-			$register_token = $request->input('register_token');
-			$code = $request->input('code');
-			$verify_code = $request->input('verify_code');
-			$aff = (int) $request->input('aff');
-
-			// 防止重复提交
-			if($register_token !== Session::get('register_token')){
-				return Redirect::back()->withInput()->withErrors(trans('auth.repeat_request'));
-			}
-
-			Session::forget('register_token');
-
-			// 是否开启注册
-			if(!sysConfig('is_register')){
-				return Redirect::back()->withErrors(trans('auth.register_close'));
-			}
-
-			// 校验域名邮箱黑白名单
-			if(sysConfig('is_email_filtering')){
-				$result = $this->emailChecker($email, 1);
-				if($result !== false){
-					return $result;
-				}
-			}
-
-			// 如果需要邀请注册
-			if(sysConfig('is_invite_register')){
-				// 校验邀请码合法性
-				if($code){
-					if(Invite::whereCode($code)->whereStatus(0)->doesntExist()){
-						return Redirect::back()
-						               ->withInput($request->except(['code']))
-						               ->withErrors(trans('auth.code_error'));
-					}
-				}elseif(sysConfig('is_invite_register') == 2){ // 必须使用邀请码
-					return Redirect::back()->withInput()->withErrors(trans('auth.code_null'));
-				}
-			}
-
-			// 注册前发送激活码
-			if(sysConfig('is_activate_account') == 1){
-				if(!$verify_code){
-					return Redirect::back()
-					               ->withInput($request->except(['verify_code']))
-					               ->withErrors(trans('auth.captcha_null'));
-				}
-
-				$verifyCode = VerifyCode::whereAddress($email)->whereCode($verify_code)->whereStatus(0)->first();
-				if(!$verifyCode){
-					return Redirect::back()
-					               ->withInput($request->except(['verify_code']))
-					               ->withErrors(trans('auth.captcha_overtime'));
-				}
-
-				$verifyCode->status = 1;
-				$verifyCode->save();
-			}
-
-			// 是否校验验证码
-			$captcha = $this->check_captcha($request);
-			if($captcha != false){
-				return $captcha;
-			}
-
-			// 24小时内同IP注册限制
-			if(sysConfig('register_ip_limit') && Cache::has($cacheKey)){
-				$registerTimes = Cache::get($cacheKey);
-				if($registerTimes >= sysConfig('register_ip_limit')){
-					return Redirect::back()
-					               ->withInput($request->except(['code']))
-					               ->withErrors(trans('auth.register_anti'));
-				}
-			}
-
-			// 获取可用端口
-			$port = Helpers::getPort();
-			if($port > sysConfig('max_port')){
-				return Redirect::back()->withInput()->withErrors(trans('auth.register_close'));
-			}
-
-			// 获取aff
-			$affArr = $this->getAff($code, $aff);
-			$inviter_id = $affArr['inviter_id'];
-
-			$transfer_enable = MB * ((int) sysConfig('default_traffic') + ($inviter_id? (int) sysConfig('referral_traffic') : 0));
-
-			// 创建新用户
-			$uid = Helpers::addUser($email, Hash::make($password), $transfer_enable, sysConfig('default_days'),
-				$inviter_id);
-
-			// 注册失败,抛出异常
-			if(!$uid){
-				return Redirect::back()->withInput()->withErrors(trans('auth.register_fail'));
-			}
-			// 更新昵称
-			User::find($uid)->update(['username' => $username]);
-
-			// 注册次数+1
-			if(Cache::has($cacheKey)){
-				Cache::increment($cacheKey);
-			}else{
-				Cache::put($cacheKey, 1, Day); // 24小时
-			}
-
-			// 更新邀请码
-			if($affArr['code_id'] && sysConfig('is_invite_register')){
-				Invite::find($affArr['code_id'])->update(['invitee_id' => $uid, 'status' => 1]);
-			}
-
-			// 清除邀请人Cookie
-			Cookie::unqueue('register_aff');
-
-			// 注册后发送激活码
-			if(sysConfig('is_activate_account') == 2){
-				// 生成激活账号的地址
-				$token = $this->addVerifyUrl($uid, $email);
-				$activeUserUrl = sysConfig('website_url').'/active/'.$token;
-
-				$logId = Helpers::addNotificationLog('注册激活', '请求地址:'.$activeUserUrl, 1, $email);
-				Mail::to($email)->send(new activeUser($logId, $activeUserUrl));
-
-				Session::flash('regSuccessMsg', trans('auth.register_active_tip'));
-			}else{
-				// 则直接给推荐人加流量
-				if($inviter_id){
-					$referralUser = User::find($inviter_id);
-					if($referralUser && $referralUser->expired_at >= date('Y-m-d')){
-						(new UserService($referralUser))->incrementData(sysConfig('referral_traffic') * MB);
-					}
-				}
-
-				if(sysConfig('is_activate_account') == 1){
-					User::find($uid)->update(['status' => 1]);
-				}
-
-				Session::flash('regSuccessMsg', trans('auth.register_success'));
-			}
-
-			return Redirect::to('login')->withInput();
-		}
-
-		$view['emailList'] = sysConfig('is_email_filtering') != 2? false : EmailFilter::whereType(2)->get();
-		Session::put('register_token', Str::random());
-
-		return view('auth.register', $view);
-	}
-
-	//邮箱检查
-	private function emailChecker($email, $returnType = 0) {
-		$emailFilterList = $this->emailFilterList(sysConfig('is_email_filtering'));
-		$emailSuffix = explode('@', $email); // 提取邮箱后缀
-		switch(sysConfig('is_email_filtering')){
-			// 黑名单
-			case 1:
-				if(in_array(strtolower($emailSuffix[1]), $emailFilterList, true)){
-					if($returnType){
-						return Redirect::back()->withErrors(trans('auth.email_banned'));
-					}
-					return Response::json(['status' => 'fail', 'message' => trans('auth.email_banned')]);
-				}
-				break;
-			//白名单
-			case 2:
-				if(!in_array(strtolower($emailSuffix[1]), $emailFilterList, true)){
-					if($returnType){
-						return Redirect::back()->withErrors(trans('auth.email_invalid'));
-					}
-					return Response::json(['status' => 'fail', 'message' => trans('auth.email_invalid')]);
-				}
-				break;
-			default:
-				if($returnType){
-					return Redirect::back()->withErrors(trans('auth.email_invalid'));
-				}
-				return Response::json(['status' => 'fail', 'message' => trans('auth.email_invalid')]);
-		}
-
-		return false;
-	}
-
-	/**
-	 * 获取AFF
-	 *
-	 * @param  string|null  $code  邀请码
-	 * @param  int|null     $aff   URL中的aff参数
-	 *
-	 * @return array
-	 */
-	private function getAff($code = null, $aff = null): array {
-		$data = ['inviter_id' => null, 'code_id' => 0];// 邀请人ID 与 邀请码ID
-
-		// 有邀请码先用邀请码,用谁的邀请码就给谁返利
-		if($code){
-			$inviteCode = Invite::whereCode($code)->whereStatus(0)->first();
-			if($inviteCode){
-				$data['inviter_id'] = $inviteCode->inviter_id;
-				$data['code_id'] = $inviteCode->id;
-			}
-		}
-
-		// 没有用邀请码或者邀请码是管理员生成的,则检查cookie或者url链接
-		if(!$data['inviter_id']){
-			// 检查一下cookie里有没有aff
-			$cookieAff = \Request::hasCookie('register_aff')? \Request::cookie('register_aff') : 0;
-			if($cookieAff){
-				$data['inviter_id'] = User::find($cookieAff)? $cookieAff : 0;
-			}elseif($aff){ // 如果cookie里没有aff,就再检查一下请求的url里有没有aff,因为有些人的浏览器会禁用了cookie,比如chrome开了隐私模式
-				$data['inviter_id'] = User::find($aff)? $aff : 0;
-			}
-		}
-
-		return $data;
-	}
-
-	// 生成申请的请求地址
-	private function addVerifyUrl($uid, $email) {
-		$token = md5(sysConfig('website_name').$email.microtime());
-		$verify = new Verify();
-		$verify->type = 1;
-		$verify->user_id = $uid;
-		$verify->token = $token;
-		$verify->status = 0;
-		$verify->save();
-
-		return $token;
-	}
-
-	// 重设密码页
-	public function resetPassword(Request $request) {
-		if($request->isMethod('POST')){
-			// 校验请求
-			$validator = Validator::make($request->all(), [
-				'email' => 'required|email'
-			], [
-				'email.required' => trans('auth.email_null'),
-				'email.email'    => trans('auth.email_legitimate')
-			]);
-
-			if($validator->fails()){
-				return Redirect::back()->withInput()->withErrors($validator->errors());
-			}
-
-			$email = $request->input('email');
-
-			// 是否开启重设密码
-			if(!sysConfig('is_reset_password')){
-				return Redirect::back()->withErrors(trans('auth.reset_password_close',
-					['email' => sysConfig('webmaster_email')]));
-			}
-
-			// 查找账号
-			$user = User::whereEmail($email)->first();
-			if(!$user){
-				return Redirect::back()->withErrors(trans('auth.email_notExist'));
-			}
-
-			// 24小时内重设密码次数限制
-			$resetTimes = 0;
-			if(Cache::has('resetPassword_'.md5($email))){
-				$resetTimes = Cache::get('resetPassword_'.md5($email));
-				if($resetTimes >= sysConfig('reset_password_times')){
-					return Redirect::back()->withErrors(trans('auth.reset_password_limit',
-						['time' => sysConfig('reset_password_times')]));
-				}
-			}
-
-			// 生成取回密码的地址
-			$token = $this->addVerifyUrl($user->id, $email);
-
-			// 发送邮件
-			$resetPasswordUrl = sysConfig('website_url').'/reset/'.$token;
-
-			$logId = Helpers::addNotificationLog('重置密码', '请求地址:'.$resetPasswordUrl, 1, $email);
-			Mail::to($email)->send(new resetPassword($logId, $resetPasswordUrl));
-
-			Cache::put('resetPassword_'.md5($email), $resetTimes + 1, Day);
-
-			return Redirect::back()->with('successMsg', trans('auth.reset_password_success_tip'));
-		}
-
-		return view('auth.resetPassword');
-	}
-
-	// 重设密码
-	public function reset(Request $request, $token) {
-		if(!$token){
-			return Redirect::to('login');
-		}
-
-		if($request->isMethod('POST')){
-			$validator = Validator::make($request->all(), [
-				'password'        => 'required|min:6',
-				'confirmPassword' => 'required|same:password'
-			], [
-				'password.required'        => trans('auth.password_null'),
-				'password.min'             => trans('auth.password_limit'),
-				'confirmPassword.required' => trans('auth.password_null'),
-				'confirmPassword.min'      => trans('auth.password_limit'),
-				'confirmPassword.same'     => trans('auth.password_same'),
-			]);
-
-			if($validator->fails()){
-				return Redirect::back()->withInput()->withErrors($validator->errors());
-			}
-
-			$password = $request->input('password');
-			// 校验账号
-			$verify = Verify::type(1)->whereToken($token)->first();
-			$user = $verify->user;
-			if(!$verify){
-				return Redirect::to('login');
-			}
-
-			if($verify->status == 1){
-				return Redirect::back()->withErrors(trans('auth.overtime'));
-			}
-
-			if($user->status < 0){
-				return Redirect::back()->withErrors(trans('auth.email_banned'));
-			}
-
-			if(Hash::check($password, $verify->user->password)){
-				return Redirect::back()->withErrors(trans('auth.reset_password_same_fail'));
-			}
-
-			// 更新密码
-			if(!$user->update(['password' => Hash::make($password)])){
-				return Redirect::back()->withErrors(trans('auth.reset_password_fail'));
-			}
-
-			// 置为已使用
-			$verify->status = 1;
-			$verify->save();
-
-			return Redirect::to('login')->with('successMsg', trans('auth.reset_password_new'));
-		}
-
-		$verify = Verify::type(1)->whereToken($token)->first();
-		if(!$verify){
-			return Redirect::to('login');
-		}
-
-		if(time() - strtotime($verify->created_at) >= 1800){
-			// 置为已失效
-			$verify->status = 2;
-			$verify->save();
-		}
-
-		// 重新获取一遍verify
-		$view['verify'] = Verify::type(1)->whereToken($token)->first();
-
-		return view('auth.reset', $view);
-	}
-
-	// 激活账号页
-	public function activeUser(Request $request) {
-		if($request->isMethod('POST')){
-			$validator = Validator::make($request->all(), [
-				'email' => 'required|email|exists:user,email'
-			], [
-				'email.required' => trans('auth.email_null'),
-				'email.email'    => trans('auth.email_legitimate'),
-				'email.exists'   => trans('auth.email_notExist')
-			]);
-
-			if($validator->fails()){
-				return Redirect::back()->withInput()->withErrors($validator->errors());
-			}
-
-			$email = $request->input('email');
-
-			// 是否开启账号激活
-			if(sysConfig('is_activate_account') != 2){
-				return Redirect::back()->withInput()->withErrors(trans('auth.active_close',
-					['email' => sysConfig('webmaster_email')]));
-			}
-
-			// 查找账号
-			$user = User::whereEmail($email)->firstOrFail();
-			if($user->status < 0){
-				return Redirect::back()->withErrors(trans('auth.login_ban', ['email' => sysConfig('webmaster_email')]));
-			}
-
-			if($user->status > 0){
-				return Redirect::back()->withErrors(trans('auth.email_normal'));
-			}
-
-			// 24小时内激活次数限制
-			$activeTimes = 0;
-			if(Cache::has('activeUser_'.md5($email))){
-				$activeTimes = Cache::get('activeUser_'.md5($email));
-				if($activeTimes >= sysConfig('active_times')){
-					return Redirect::back()->withErrors(trans('auth.active_limit',
-						['time' => sysConfig('webmaster_email')]));
-				}
-			}
-
-			// 生成激活账号的地址
-			$token = $this->addVerifyUrl($user->id, $email);
-
-			// 发送邮件
-			$activeUserUrl = sysConfig('website_url').'/active/'.$token;
-
-			$logId = Helpers::addNotificationLog('激活账号', '请求地址:'.$activeUserUrl, 1, $email);
-			Mail::to($email)->send(new activeUser($logId, $activeUserUrl));
-
-			Cache::put('activeUser_'.md5($email), $activeTimes + 1, Day);
-
-			return Redirect::back()->with('successMsg', trans('auth.register_active_tip'));
-		}
-
-		return view('auth.activeUser');
-	}
-
-	// 激活账号
-	public function active($token) {
-		if(!$token){
-			return Redirect::to('login');
-		}
-
-		$verify = Verify::type(1)->with('user')->whereToken($token)->first();
-		$user = $verify->user;
-		if(!$verify){
-			return Redirect::to('login');
-		}
-
-		if(empty($user)){
-			Session::flash('errorMsg', trans('auth.overtime'));
-
-			return view('auth.active');
-		}
-
-		if($verify->status > 0){
-			Session::flash('errorMsg', trans('auth.overtime'));
-
-			return view('auth.active');
-		}
-
-		if($user->status != 0){
-			Session::flash('errorMsg', trans('auth.email_normal'));
-
-			return view('auth.active');
-		}
-
-		if(time() - strtotime($verify->created_at) >= 1800){
-			Session::flash('errorMsg', trans('auth.overtime'));
-
-			// 置为已失效
-			$verify->status = 2;
-			$verify->save();
-
-			return view('auth.active');
-		}
-
-		// 更新账号状态
-		if(!$user->update(['status' => 1])){
-			Session::flash('errorMsg', trans('auth.active_fail'));
-
-			return Redirect::back();
-		}
-
-		// 置为已使用
-		$verify->status = 1;
-		$verify->save();
-
-		// 账号激活后给邀请人送流量
-		$inviter = $user->inviter;
-		if($inviter){
-			(new UserService($inviter))->incrementData(sysConfig('referral_traffic') * MB);
-		}
-
-		Session::flash('successMsg', trans('auth.active_success'));
-
-		return view('auth.active');
-	}
-
-	// 发送注册验证码
-	public function sendCode(Request $request) {
-		$validator = Validator::make($request->all(), [
-			'email' => 'required|email|unique:user'
-		], [
-			'email.required' => trans('auth.email_null'),
-			'email.email'    => trans('auth.email_legitimate'),
-			'email.unique'   => trans('auth.email_exist')
-		]);
+class AuthController extends Controller
+{
+
+    // 登录
+    public function login(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $validator = Validator::make(
+                $request->all(),
+                [
+                    'email'    => 'required|email',
+                    'password' => 'required',
+                ],
+                [
+                    'email.required'    => trans('auth.email_null'),
+                    'password.required' => trans('auth.password_null'),
+                ]
+            );
+
+            if ($validator->fails()) {
+                return Redirect::back()->withInput()->withErrors(
+                    $validator->errors()
+                );
+            }
+
+            $email    = $request->input('email');
+            $password = $request->input('password');
+            $remember = $request->input('remember');
+
+            // 是否校验验证码
+            $captcha = $this->check_captcha($request);
+            if ($captcha != false) {
+                return $captcha;
+            }
+
+            // 验证账号并创建会话
+            if ( ! Auth::attempt(
+                ['email' => $email, 'password' => $password],
+                $remember
+            )) {
+                return Redirect::back()->withInput()->withErrors(
+                    trans('auth.login_error')
+                );
+            }
+            $user = Auth::getUser();
+
+            if ( ! $user) {
+                return Redirect::back()->withInput()->withErrors(
+                    trans('auth.login_error')
+                );
+            }
+
+            // 校验普通用户账号状态
+            if ( ! $user->is_admin) {
+                if ($user->status < 0) {
+                    Auth::logout(); // 强制销毁会话,因为Auth::attempt的时候会产生会话
+
+                    return Redirect::back()->withInput()->withErrors(
+                        trans(
+                            'auth.login_ban',
+                            ['email' => sysConfig('webmaster_email')]
+                        )
+                    );
+                }
+
+                if ($user->status == 0 && sysConfig('is_activate_account')) {
+                    Auth::logout(); // 强制销毁会话,因为Auth::attempt的时候会产生会话
+
+                    return Redirect::back()
+                                   ->withInput()
+                                   ->withErrors(
+                                       trans(
+                                           'auth.active_tip'
+                                       ) . '<a href="/activeUser?email=' . $email . '" target="_blank"><span style="color:#000">【' . trans(
+                                           'auth.active_account'
+                                       ) . '】</span></a>'
+                                   );
+                }
+            }
+
+            // 写入登录日志
+            $this->addUserLoginLog($user->id, getClientIp());
+
+            // 更新登录信息
+            Auth::getUser()->update(['last_login' => time()]);
+
+            // 根据权限跳转
+            if ($user->is_admin) {
+                return Redirect::to('admin');
+            }
+
+            return Redirect::to('/');
+        }
+
+        if (Auth::check()) {
+            if (Auth::getUser()->is_admin) {
+                return Redirect::to('admin');
+            }
+
+            return Redirect::to('/');
+        }
+
+        return view('auth.login');
+    }
+
+    // 校验验证码
+    private function check_captcha($request)
+    {
+        switch (sysConfig('is_captcha')) {
+            case 1: // 默认图形验证码
+                if ( ! Captcha::check($request->input('captcha'))) {
+                    return Redirect::back()->withInput()->withErrors(
+                        trans('auth.captcha_error')
+                    );
+                }
+                break;
+            case 2: // Geetest
+                $validator = Validator::make(
+                    $request->all(),
+                    [
+                        'geetest_challenge' => 'required|geetest',
+                    ],
+                    [
+                        'geetest' => trans('auth.captcha_fail'),
+                    ]
+                );
+
+                if ($validator->fails()) {
+                    return Redirect::back()->withInput()->withErrors(
+                        trans('auth.captcha_fail')
+                    );
+                }
+                break;
+            case 3: // Google reCAPTCHA
+                $validator = Validator::make(
+                    $request->all(),
+                    [
+                        'g-recaptcha-response' => 'required|NoCaptcha',
+                    ]
+                );
+
+                if ($validator->fails()) {
+                    return Redirect::back()->withInput()->withErrors(
+                        trans('auth.captcha_fail')
+                    );
+                }
+                break;
+            case 4: // hCaptcha
+                $validator = Validator::make(
+                    $request->all(),
+                    [
+                        'h-captcha-response' => 'required|HCaptcha',
+                    ]
+                );
+
+                if ($validator->fails()) {
+                    return Redirect::back()->withInput()->withErrors(
+                        trans('auth.captcha_fail')
+                    );
+                }
+                break;
+            default: // 不启用验证码
+                break;
+        }
+
+        return 0;
+    }
+
+    /**
+     * 添加用户登录日志
+     *
+     * @param  int  $userId  用户ID
+     * @param  string  $ip  IP地址
+     */
+    private function addUserLoginLog(int $userId, string $ip): void
+    {
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+            Log::info('识别到IPv6,尝试解析:' . $ip);
+            $ipInfo = getIPInfo($ip);
+        } else {
+            $ipInfo = QQWry::ip($ip); // 通过纯真IP库解析IPv4信息
+            if (isset($ipInfo['error'])) {
+                Log::info('无法识别IPv4,尝试使用IPIP的IP库解析:' . $ip);
+                $ipip   = IPIP::ip($ip);
+                $ipInfo = [
+                    'country'  => $ipip['country_name'],
+                    'province' => $ipip['region_name'],
+                    'city'     => $ipip['city_name'],
+                ];
+            } else {
+                // 判断纯真IP库获取的国家信息是否与IPIP的IP库获取的信息一致,不一致则用IPIP的(因为纯真IP库的非大陆IP准确率较低)
+                $ipip = IPIP::ip($ip);
+                if ($ipInfo['country'] != $ipip['country_name']) {
+                    $ipInfo['country']  = $ipip['country_name'];
+                    $ipInfo['province'] = $ipip['region_name'];
+                    $ipInfo['city']     = $ipip['city_name'];
+                }
+            }
+        }
+
+        if (empty($ipInfo) || empty($ipInfo['country'])) {
+            Log::warning("获取IP信息异常:" . $ip);
+        }
+
+        $log           = new UserLoginLog();
+        $log->user_id  = $userId;
+        $log->ip       = $ip;
+        $log->country  = $ipInfo['country'] ?? '';
+        $log->province = $ipInfo['province'] ?? '';
+        $log->city     = $ipInfo['city'] ?? '';
+        $log->county   = $ipInfo['county'] ?? '';
+        $log->isp      = $ipInfo['isp'] ?? ($ipInfo['organization'] ?? '');
+        $log->area     = $ipInfo['area'] ?? '';
+        $log->save();
+    }
+
+
+    // 退出
+    public function logout(): RedirectResponse
+    {
+        Auth::logout();
+
+        return Redirect::to('login');
+    }
+
+    // 注册
+    public function register(Request $request)
+    {
+        $cacheKey = 'register_times_' . md5(getClientIp()); // 注册限制缓存key
+
+        if ($request->isMethod('POST')) {
+            $validator = Validator::make(
+                $request->all(),
+                [
+                    'username'        => 'required',
+                    'email'           => 'required|email|unique:user',
+                    'password'        => 'required|min:6',
+                    'confirmPassword' => 'required|same:password',
+                    'term'            => 'accepted',
+                ],
+                [
+                    'username.required'        => trans('auth.email_null'),
+                    'email.required'           => trans('auth.email_null'),
+                    'email.email'              => trans(
+                        'auth.email_legitimate'
+                    ),
+                    'email.unique'             => trans('auth.email_exist'),
+                    'password.required'        => trans('auth.password_null'),
+                    'password.min'             => trans('auth.password_limit'),
+                    'confirmPassword.required' => trans(
+                        'auth.confirm_password'
+                    ),
+                    'confirmPassword.same'     => trans('auth.password_same'),
+                    'term.accepted'            => trans('auth.unaccepted'),
+                ]
+            );
+
+            if ($validator->fails()) {
+                return Redirect::back()->withInput()->withErrors(
+                    $validator->errors()
+                );
+            }
+
+            $username       = $request->input('username');
+            $email          = $request->input('email');
+            $password       = $request->input('password');
+            $register_token = $request->input('register_token');
+            $code           = $request->input('code');
+            $verify_code    = $request->input('verify_code');
+            $aff            = (int)$request->input('aff');
+
+            // 防止重复提交
+            if ($register_token !== Session::get('register_token')) {
+                return Redirect::back()->withInput()->withErrors(
+                    trans('auth.repeat_request')
+                );
+            }
+
+            Session::forget('register_token');
+
+            // 是否开启注册
+            if ( ! sysConfig('is_register')) {
+                return Redirect::back()->withErrors(
+                    trans('auth.register_close')
+                );
+            }
+
+            // 校验域名邮箱黑白名单
+            if (sysConfig('is_email_filtering')) {
+                $result = $this->emailChecker($email, 1);
+                if ($result !== false) {
+                    return $result;
+                }
+            }
+
+            // 如果需要邀请注册
+            if (sysConfig('is_invite_register')) {
+                // 校验邀请码合法性
+                if ($code) {
+                    if (Invite::whereCode($code)->whereStatus(0)->doesntExist(
+                    )) {
+                        return Redirect::back()
+                                       ->withInput($request->except(['code']))
+                                       ->withErrors(trans('auth.code_error'));
+                    }
+                } elseif (sysConfig('is_invite_register') == 2) { // 必须使用邀请码
+                    return Redirect::back()->withInput()->withErrors(
+                        trans('auth.code_null')
+                    );
+                }
+            }
+
+            // 注册前发送激活码
+            if (sysConfig('is_activate_account') == 1) {
+                if ( ! $verify_code) {
+                    return Redirect::back()
+                                   ->withInput(
+                                       $request->except(['verify_code'])
+                                   )
+                                   ->withErrors(trans('auth.captcha_null'));
+                }
+
+                $verifyCode = VerifyCode::whereAddress($email)->whereCode(
+                    $verify_code
+                )->whereStatus(0)->first();
+                if ( ! $verifyCode) {
+                    return Redirect::back()
+                                   ->withInput(
+                                       $request->except(['verify_code'])
+                                   )
+                                   ->withErrors(trans('auth.captcha_overtime'));
+                }
+
+                $verifyCode->status = 1;
+                $verifyCode->save();
+            }
+
+            // 是否校验验证码
+            $captcha = $this->check_captcha($request);
+            if ($captcha != false) {
+                return $captcha;
+            }
+
+            // 24小时内同IP注册限制
+            if (sysConfig('register_ip_limit') && Cache::has($cacheKey)) {
+                $registerTimes = Cache::get($cacheKey);
+                if ($registerTimes >= sysConfig('register_ip_limit')) {
+                    return Redirect::back()
+                                   ->withInput($request->except(['code']))
+                                   ->withErrors(trans('auth.register_anti'));
+                }
+            }
+
+            // 获取可用端口
+            $port = Helpers::getPort();
+            if ($port > sysConfig('max_port')) {
+                return Redirect::back()->withInput()->withErrors(
+                    trans('auth.register_close')
+                );
+            }
+
+            // 获取aff
+            $affArr     = $this->getAff($code, $aff);
+            $inviter_id = $affArr['inviter_id'];
+
+            $transfer_enable = MB * ((int)sysConfig(
+                        'default_traffic'
+                    ) + ($inviter_id ? (int)sysConfig('referral_traffic') : 0));
+
+            // 创建新用户
+            $uid = Helpers::addUser(
+                $email,
+                Hash::make($password),
+                $transfer_enable,
+                sysConfig('default_days'),
+                $inviter_id
+            );
+
+            // 注册失败,抛出异常
+            if ( ! $uid) {
+                return Redirect::back()->withInput()->withErrors(
+                    trans('auth.register_fail')
+                );
+            }
+            // 更新昵称
+            User::find($uid)->update(['username' => $username]);
+
+            // 注册次数+1
+            if (Cache::has($cacheKey)) {
+                Cache::increment($cacheKey);
+            } else {
+                Cache::put($cacheKey, 1, Day); // 24小时
+            }
+
+            // 更新邀请码
+            if ($affArr['code_id'] && sysConfig('is_invite_register')) {
+                Invite::find($affArr['code_id'])->update(
+                    ['invitee_id' => $uid, 'status' => 1]
+                );
+            }
+
+            // 清除邀请人Cookie
+            Cookie::unqueue('register_aff');
+
+            // 注册后发送激活码
+            if (sysConfig('is_activate_account') == 2) {
+                // 生成激活账号的地址
+                $token         = $this->addVerifyUrl($uid, $email);
+                $activeUserUrl = sysConfig('website_url') . '/active/' . $token;
+
+                $logId = Helpers::addNotificationLog(
+                    '注册激活',
+                    '请求地址:' . $activeUserUrl,
+                    1,
+                    $email
+                );
+                Mail::to($email)->send(new activeUser($logId, $activeUserUrl));
+
+                Session::flash(
+                    'regSuccessMsg',
+                    trans('auth.register_active_tip')
+                );
+            } else {
+                // 则直接给推荐人加流量
+                if ($inviter_id) {
+                    $referralUser = User::find($inviter_id);
+                    if ($referralUser && $referralUser->expired_at >= date(
+                            'Y-m-d'
+                        )) {
+                        (new UserService($referralUser))->incrementData(
+                            sysConfig('referral_traffic') * MB
+                        );
+                    }
+                }
+
+                if (sysConfig('is_activate_account') == 1) {
+                    User::find($uid)->update(['status' => 1]);
+                }
+
+                Session::flash('regSuccessMsg', trans('auth.register_success'));
+            }
+
+            return Redirect::to('login')->withInput();
+        }
+
+        $view['emailList'] = sysConfig(
+                                 'is_email_filtering'
+                             ) != 2 ? false : EmailFilter::whereType(2)->get();
+        Session::put('register_token', Str::random());
+
+        return view('auth.register', $view);
+    }
+
+    //邮箱检查
+    private function emailChecker($email, $returnType = 0)
+    {
+        $emailFilterList = $this->emailFilterList(
+            sysConfig('is_email_filtering')
+        );
+        $emailSuffix     = explode('@', $email); // 提取邮箱后缀
+        switch (sysConfig('is_email_filtering')) {
+            // 黑名单
+            case 1:
+                if (in_array(
+                    strtolower($emailSuffix[1]),
+                    $emailFilterList,
+                    true
+                )) {
+                    if ($returnType) {
+                        return Redirect::back()->withErrors(
+                            trans('auth.email_banned')
+                        );
+                    }
+
+                    return Response::json(
+                        [
+                            'status'  => 'fail',
+                            'message' => trans('auth.email_banned'),
+                        ]
+                    );
+                }
+                break;
+            //白名单
+            case 2:
+                if ( ! in_array(
+                    strtolower($emailSuffix[1]),
+                    $emailFilterList,
+                    true
+                )) {
+                    if ($returnType) {
+                        return Redirect::back()->withErrors(
+                            trans('auth.email_invalid')
+                        );
+                    }
+
+                    return Response::json(
+                        [
+                            'status'  => 'fail',
+                            'message' => trans('auth.email_invalid'),
+                        ]
+                    );
+                }
+                break;
+            default:
+                if ($returnType) {
+                    return Redirect::back()->withErrors(
+                        trans('auth.email_invalid')
+                    );
+                }
+
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => trans('auth.email_invalid'),
+                    ]
+                );
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取AFF
+     *
+     * @param  string|null  $code  邀请码
+     * @param  int|null  $aff  URL中的aff参数
+     *
+     * @return array
+     */
+    private function getAff($code = null, $aff = null): array
+    {
+        $data = ['inviter_id' => null, 'code_id' => 0];// 邀请人ID 与 邀请码ID
+
+        // 有邀请码先用邀请码,用谁的邀请码就给谁返利
+        if ($code) {
+            $inviteCode = Invite::whereCode($code)->whereStatus(0)->first();
+            if ($inviteCode) {
+                $data['inviter_id'] = $inviteCode->inviter_id;
+                $data['code_id']    = $inviteCode->id;
+            }
+        }
+
+        // 没有用邀请码或者邀请码是管理员生成的,则检查cookie或者url链接
+        if ( ! $data['inviter_id']) {
+            // 检查一下cookie里有没有aff
+            $cookieAff = \Request::hasCookie('register_aff') ? \Request::cookie(
+                'register_aff'
+            ) : 0;
+            if ($cookieAff) {
+                $data['inviter_id'] = User::find($cookieAff) ? $cookieAff : 0;
+            } elseif ($aff) { // 如果cookie里没有aff,就再检查一下请求的url里有没有aff,因为有些人的浏览器会禁用了cookie,比如chrome开了隐私模式
+                $data['inviter_id'] = User::find($aff) ? $aff : 0;
+            }
+        }
+
+        return $data;
+    }
+
+    // 生成申请的请求地址
+    private function addVerifyUrl($uid, $email)
+    {
+        $token           = md5(
+            sysConfig('website_name') . $email . microtime()
+        );
+        $verify          = new Verify();
+        $verify->type    = 1;
+        $verify->user_id = $uid;
+        $verify->token   = $token;
+        $verify->status  = 0;
+        $verify->save();
+
+        return $token;
+    }
+
+    // 重设密码页
+    public function resetPassword(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            // 校验请求
+            $validator = Validator::make(
+                $request->all(),
+                [
+                    'email' => 'required|email',
+                ],
+                [
+                    'email.required' => trans('auth.email_null'),
+                    'email.email'    => trans('auth.email_legitimate'),
+                ]
+            );
+
+            if ($validator->fails()) {
+                return Redirect::back()->withInput()->withErrors(
+                    $validator->errors()
+                );
+            }
+
+            $email = $request->input('email');
+
+            // 是否开启重设密码
+            if ( ! sysConfig('is_reset_password')) {
+                return Redirect::back()->withErrors(
+                    trans(
+                        'auth.reset_password_close',
+                        ['email' => sysConfig('webmaster_email')]
+                    )
+                );
+            }
+
+            // 查找账号
+            $user = User::whereEmail($email)->first();
+            if ( ! $user) {
+                return Redirect::back()->withErrors(
+                    trans('auth.email_notExist')
+                );
+            }
+
+            // 24小时内重设密码次数限制
+            $resetTimes = 0;
+            if (Cache::has('resetPassword_' . md5($email))) {
+                $resetTimes = Cache::get('resetPassword_' . md5($email));
+                if ($resetTimes >= sysConfig('reset_password_times')) {
+                    return Redirect::back()->withErrors(
+                        trans(
+                            'auth.reset_password_limit',
+                            ['time' => sysConfig('reset_password_times')]
+                        )
+                    );
+                }
+            }
+
+            // 生成取回密码的地址
+            $token = $this->addVerifyUrl($user->id, $email);
+
+            // 发送邮件
+            $resetPasswordUrl = sysConfig('website_url') . '/reset/' . $token;
+
+            $logId = Helpers::addNotificationLog(
+                '重置密码',
+                '请求地址:' . $resetPasswordUrl,
+                1,
+                $email
+            );
+            Mail::to($email)->send(
+                new resetPassword($logId, $resetPasswordUrl)
+            );
+
+            Cache::put('resetPassword_' . md5($email), $resetTimes + 1, Day);
+
+            return Redirect::back()->with(
+                'successMsg',
+                trans(
+                    'auth.reset_password_success_tip'
+                )
+            );
+        }
+
+        return view('auth.resetPassword');
+    }
+
+    // 重设密码
+    public function reset(Request $request, $token)
+    {
+        if ( ! $token) {
+            return Redirect::to('login');
+        }
+
+        if ($request->isMethod('POST')) {
+            $validator = Validator::make(
+                $request->all(),
+                [
+                    'password'        => 'required|min:6',
+                    'confirmPassword' => 'required|same:password',
+                ],
+                [
+                    'password.required'        => trans('auth.password_null'),
+                    'password.min'             => trans('auth.password_limit'),
+                    'confirmPassword.required' => trans('auth.password_null'),
+                    'confirmPassword.min'      => trans('auth.password_limit'),
+                    'confirmPassword.same'     => trans('auth.password_same'),
+                ]
+            );
+
+            if ($validator->fails()) {
+                return Redirect::back()->withInput()->withErrors(
+                    $validator->errors()
+                );
+            }
+
+            $password = $request->input('password');
+            // 校验账号
+            $verify = Verify::type(1)->whereToken($token)->first();
+            $user   = $verify->user;
+            if ( ! $verify) {
+                return Redirect::to('login');
+            }
+
+            if ($verify->status == 1) {
+                return Redirect::back()->withErrors(trans('auth.overtime'));
+            }
+
+            if ($user->status < 0) {
+                return Redirect::back()->withErrors(trans('auth.email_banned'));
+            }
+
+            if (Hash::check($password, $verify->user->password)) {
+                return Redirect::back()->withErrors(
+                    trans('auth.reset_password_same_fail')
+                );
+            }
+
+            // 更新密码
+            if ( ! $user->update(['password' => Hash::make($password)])) {
+                return Redirect::back()->withErrors(
+                    trans('auth.reset_password_fail')
+                );
+            }
+
+            // 置为已使用
+            $verify->status = 1;
+            $verify->save();
+
+            return Redirect::to('login')->with(
+                'successMsg',
+                trans('auth.reset_password_new')
+            );
+        }
+
+        $verify = Verify::type(1)->whereToken($token)->first();
+        if ( ! $verify) {
+            return Redirect::to('login');
+        }
+
+        if (time() - strtotime($verify->created_at) >= 1800) {
+            // 置为已失效
+            $verify->status = 2;
+            $verify->save();
+        }
+
+        // 重新获取一遍verify
+        $view['verify'] = Verify::type(1)->whereToken($token)->first();
+
+        return view('auth.reset', $view);
+    }
+
+    // 激活账号页
+    public function activeUser(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $validator = Validator::make(
+                $request->all(),
+                [
+                    'email' => 'required|email|exists:user,email',
+                ],
+                [
+                    'email.required' => trans('auth.email_null'),
+                    'email.email'    => trans('auth.email_legitimate'),
+                    'email.exists'   => trans('auth.email_notExist'),
+                ]
+            );
+
+            if ($validator->fails()) {
+                return Redirect::back()->withInput()->withErrors(
+                    $validator->errors()
+                );
+            }
+
+            $email = $request->input('email');
+
+            // 是否开启账号激活
+            if (sysConfig('is_activate_account') != 2) {
+                return Redirect::back()->withInput()->withErrors(
+                    trans(
+                        'auth.active_close',
+                        ['email' => sysConfig('webmaster_email')]
+                    )
+                );
+            }
+
+            // 查找账号
+            $user = User::whereEmail($email)->firstOrFail();
+            if ($user->status < 0) {
+                return Redirect::back()->withErrors(
+                    trans(
+                        'auth.login_ban',
+                        ['email' => sysConfig('webmaster_email')]
+                    )
+                );
+            }
+
+            if ($user->status > 0) {
+                return Redirect::back()->withErrors(trans('auth.email_normal'));
+            }
+
+            // 24小时内激活次数限制
+            $activeTimes = 0;
+            if (Cache::has('activeUser_' . md5($email))) {
+                $activeTimes = Cache::get('activeUser_' . md5($email));
+                if ($activeTimes >= sysConfig('active_times')) {
+                    return Redirect::back()->withErrors(
+                        trans(
+                            'auth.active_limit',
+                            ['time' => sysConfig('webmaster_email')]
+                        )
+                    );
+                }
+            }
+
+            // 生成激活账号的地址
+            $token = $this->addVerifyUrl($user->id, $email);
+
+            // 发送邮件
+            $activeUserUrl = sysConfig('website_url') . '/active/' . $token;
+
+            $logId = Helpers::addNotificationLog(
+                '激活账号',
+                '请求地址:' . $activeUserUrl,
+                1,
+                $email
+            );
+            Mail::to($email)->send(new activeUser($logId, $activeUserUrl));
+
+            Cache::put('activeUser_' . md5($email), $activeTimes + 1, Day);
+
+            return Redirect::back()->with(
+                'successMsg',
+                trans('auth.register_active_tip')
+            );
+        }
+
+        return view('auth.activeUser');
+    }
+
+    // 激活账号
+    public function active($token)
+    {
+        if ( ! $token) {
+            return Redirect::to('login');
+        }
+
+        $verify = Verify::type(1)->with('user')->whereToken($token)->first();
+        $user   = $verify->user;
+        if ( ! $verify) {
+            return Redirect::to('login');
+        }
+
+        if (empty($user)) {
+            Session::flash('errorMsg', trans('auth.overtime'));
+
+            return view('auth.active');
+        }
+
+        if ($verify->status > 0) {
+            Session::flash('errorMsg', trans('auth.overtime'));
+
+            return view('auth.active');
+        }
+
+        if ($user->status != 0) {
+            Session::flash('errorMsg', trans('auth.email_normal'));
+
+            return view('auth.active');
+        }
+
+        if (time() - strtotime($verify->created_at) >= 1800) {
+            Session::flash('errorMsg', trans('auth.overtime'));
+
+            // 置为已失效
+            $verify->status = 2;
+            $verify->save();
+
+            return view('auth.active');
+        }
+
+        // 更新账号状态
+        if ( ! $user->update(['status' => 1])) {
+            Session::flash('errorMsg', trans('auth.active_fail'));
+
+            return Redirect::back();
+        }
+
+        // 置为已使用
+        $verify->status = 1;
+        $verify->save();
+
+        // 账号激活后给邀请人送流量
+        $inviter = $user->inviter;
+        if ($inviter) {
+            (new UserService($inviter))->incrementData(
+                sysConfig('referral_traffic') * MB
+            );
+        }
+
+        Session::flash('successMsg', trans('auth.active_success'));
+
+        return view('auth.active');
+    }
+
+    // 发送注册验证码
+    public function sendCode(Request $request)
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'email' => 'required|email|unique:user',
+            ],
+            [
+                'email.required' => trans('auth.email_null'),
+                'email.email'    => trans('auth.email_legitimate'),
+                'email.unique'   => trans('auth.email_exist'),
+            ]
+        );
+
+        $email = $request->input('email');
+
+        if ($validator->fails()) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'message' => $validator->getMessageBag()->first(),
+                ]
+            );
+        }
+
+        // 校验域名邮箱黑白名单
+        if (sysConfig('is_email_filtering')) {
+            $result = $this->emailChecker($email);
+            if ($result !== false) {
+                return $result;
+            }
+        }
+
+        // 是否开启注册发送验证码
+        if (sysConfig('is_activate_account') != 1) {
+            return Response::json(
+                ['status' => 'fail', 'message' => trans('auth.captcha_close')]
+            );
+        }
+
+        // 防刷机制
+        if (Cache::has('send_verify_code_' . md5(getClientIP()))) {
+            return Response::json(
+                ['status' => 'fail', 'message' => trans('auth.register_anti')]
+            );
+        }
+
+        // 发送邮件
+        $code  = Str::random(6);
+        $logId = Helpers::addNotificationLog(
+            '发送注册验证码',
+            '验证码:' . $code,
+            1,
+            $email
+        );
+        Mail::to($email)->send(new sendVerifyCode($logId, $code));
+
+        $this->addVerifyCode($email, $code);
+
+        Cache::put(
+            'send_verify_code_' . md5(getClientIP()),
+            getClientIP(),
+            Minute
+        );
+
+        return Response::json(
+            ['status' => 'success', 'message' => trans('auth.captcha_send')]
+        );
+    }
+
+    // 生成注册验证码
+    private function addVerifyCode($email, $code): void
+    {
+        $verify          = new VerifyCode();
+        $verify->address = $email;
+        $verify->code    = $code;
+        $verify->status  = 0;
+        $verify->save();
+    }
+
+    // 公开的邀请码列表
+    public function free()
+    {
+        $view['inviteList'] = Invite::whereInviterId(0)
+                                    ->whereStatus(0)
+                                    ->paginate();
+
+        return view('auth.free', $view);
+    }
+
+    // 切换语言
+    public function switchLang($locale): RedirectResponse
+    {
+        Session::put("locale", $locale);
+
+        return Redirect::back();
+    }
 
-		$email = $request->input('email');
-
-		if($validator->fails()){
-			return Response::json(['status' => 'fail', 'message' => $validator->getMessageBag()->first()]);
-		}
-
-		// 校验域名邮箱黑白名单
-		if(sysConfig('is_email_filtering')){
-			$result = $this->emailChecker($email);
-			if($result !== false){
-				return $result;
-			}
-		}
-
-		// 是否开启注册发送验证码
-		if(sysConfig('is_activate_account') != 1){
-			return Response::json(['status' => 'fail', 'message' => trans('auth.captcha_close')]);
-		}
-
-		// 防刷机制
-		if(Cache::has('send_verify_code_'.md5(getClientIP()))){
-			return Response::json(['status' => 'fail', 'message' => trans('auth.register_anti')]);
-		}
-
-		// 发送邮件
-		$code = Str::random(6);
-		$logId = Helpers::addNotificationLog('发送注册验证码', '验证码:'.$code, 1, $email);
-		Mail::to($email)->send(new sendVerifyCode($logId, $code));
-
-		$this->addVerifyCode($email, $code);
-
-		Cache::put('send_verify_code_'.md5(getClientIP()), getClientIP(), Minute);
-
-		return Response::json(['status' => 'success', 'message' => trans('auth.captcha_send')]);
-	}
-
-	// 生成注册验证码
-	private function addVerifyCode($email, $code): void {
-		$verify = new VerifyCode();
-		$verify->address = $email;
-		$verify->code = $code;
-		$verify->status = 0;
-		$verify->save();
-	}
-
-	// 公开的邀请码列表
-	public function free() {
-		$view['inviteList'] = Invite::whereInviterId(0)->whereStatus(0)->paginate();
-
-		return view('auth.free', $view);
-	}
-
-	// 切换语言
-	public function switchLang($locale): RedirectResponse {
-		Session::put("locale", $locale);
-
-		return Redirect::back();
-	}
 }

+ 378 - 253
app/Http/Controllers/Controller.php

@@ -20,257 +20,382 @@ use Illuminate\Routing\Controller as BaseController;
 use RuntimeException;
 use Str;
 
-class Controller extends BaseController {
-	use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
-
-	// 生成随机密码
-	public function makePasswd() {
-		return Str::random();
-	}
-
-	// 生成UUID
-	public function makeUUID() {
-		return Str::uuid();
-	}
-
-	// 生成网站安全码
-	public function makeSecurityCode(): string {
-		return strtolower(Str::random(8));
-	}
-
-	// 类似Linux中的tail命令
-	public function tail($file, $n, $base = 5) {
-		$fileLines = $this->countLine($file);
-		if($fileLines < 15000){
-			return false;
-		}
-
-		$fp = fopen($file, 'rb+');
-		assert($n > 0);
-		$pos = $n + 1;
-		$lines = [];
-		while(count($lines) <= $n){
-			try{
-				fseek($fp, -$pos, SEEK_END);
-			}catch(Exception $e){
-				break;
-			}
-
-			$pos *= $base;
-			while(!feof($fp)){
-				array_unshift($lines, fgets($fp));
-			}
-		}
-
-		return array_slice($lines, 0, $n);
-	}
-
-	/**
-	 * 计算文件行数
-	 *
-	 * @param $file
-	 *
-	 * @return int
-	 */
-	public function countLine($file): int {
-		$fp = fopen($file, 'rb');
-		$i = 0;
-		while(!feof($fp)){
-			//每次读取2M
-			if($data = fread($fp, 1024 * 1024 * 2)){
-				//计算读取到的行数
-				$num = substr_count($data, "\n");
-				$i += $num;
-			}
-		}
-
-		fclose($fp);
-
-		return $i;
-	}
-
-	// 获取邮箱后缀
-	public function emailFilterList($type): array {
-		return EmailFilter::whereType($type)->pluck('words')->toArray();
-	}
-
-	// 将Base64图片转换为本地图片并保存
-	public function base64ImageSaver($base64_image_content): ?string {
-		// 匹配出图片的格式
-		if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)){
-			$type = $result[2];
-
-			$directory = date('Ymd');
-			$path = '/assets/images/qrcode/'.$directory.'/';
-			// 检查是否有该文件夹,如果没有就创建,并给予最高权限
-			if(!file_exists(public_path($path))
-			   && !mkdir($concurrentDirectory = public_path($path), 0755, true)
-			   && !is_dir($concurrentDirectory)){
-				throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
-			}
-
-			$fileName = Str::random(18).".{$type}";
-			if(file_put_contents(public_path($path.$fileName),
-				base64_decode(str_replace($result[1], '', $base64_image_content)))){
-				chmod(public_path($path.$fileName), 0744);
-
-				return $path.$fileName;
-			}
-		}
-
-		return '';
-	}
-
-	// 上传文件处理
-	public function uploadFile(UploadedFile $file): string {
-		$fileType = $file->getClientOriginalExtension();
-
-		// 验证文件合法性
-		if(!in_array($fileType, ['jpg', 'png', 'jpeg', 'bmp'])){
-			return false;
-		}
-
-		$name = date('YmdHis').random_int(1000, 2000).'.'.$fileType;
-		$move = $file->move(base_path().'/public/upload/image/', $name);
-
-		return $move? '/upload/image/'.$name : '';
-	}
-
-	/**
-	 * 节点信息
-	 *
-	 * @param  int  $uid       用户ID
-	 * @param  int  $nodeId    节点ID
-	 * @param  int  $infoType  信息类型:0为链接,1为文字
-	 *
-	 * @return string
-	 */
-	public function getUserNodeInfo($uid, $nodeId, $infoType): 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;
-				}
-				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;
-				}
-				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);
-				}
-				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');
-	}
-
-	// 流量使用图表
-	public function dataFlowChart($id, $is_node = 0): array {
-		if($is_node){
-			$currentFlow = UserDataFlowLog::whereNodeId($id);
-			$hourlyFlow = NodeHourlyDataFlow::whereNodeId($id);
-			$dailyFlow = NodeDailyDataFlow::whereNodeId($id);
-		}else{
-			$currentFlow = UserDataFlowLog::whereUserId($id);
-			$hourlyFlow = UserHourlyDataFlow::userHourly($id);
-			$dailyFlow = UserDailyDataFlow::userDaily($id);
-		}
-		$currentFlow = $currentFlow->where('log_time', '>=', strtotime(date('Y-m-d H:00')))->sum(DB::raw('u + d'));
-		$hourlyFlow = $hourlyFlow->whereDate('created_at', date('Y-m-d'))->pluck('total', 'created_at')->toArray();
-		$dailyFlow = $dailyFlow->whereMonth('created_at', date('n'))->pluck('total', 'created_at')->toArray();
-
-		// 节点一天内的流量
-		$hourlyData = array_fill(0, date('G') + 1, 0);
-		foreach($hourlyFlow as $date => $dataFlow){
-			$hourlyData[date('G', strtotime($date))] = round($dataFlow / GB, 3);
-		}
-		$hourlyData[date('G') + 1] = round($currentFlow / GB, 3);
-
-		// 节点一个月内的流量
-		$dailyData = array_fill(0, date('j'), 0);
-		foreach($dailyFlow as $date => $dataFlow){
-			$dailyData[date('j', strtotime($date)) - 1] = round($dataFlow / GB, 3);
-		}
-		$dailyData[date('j', strtotime(now())) - 1] = round((array_sum($hourlyFlow) + $currentFlow) / GB, 3);
-
-		return [
-			'trafficDaily'  => json_encode($dailyData),
-			'trafficHourly' => json_encode($hourlyData),
-			'monthDays'     => json_encode(range(1, date("j"), 1)),// 本月天数
-			'dayHours'      => json_encode(range(0, date("G") + 1, 1))// 本日小时
-		];
-	}
+class Controller extends BaseController
+{
+
+    use AuthorizesRequests;
+    use DispatchesJobs;
+    use ValidatesRequests;
+
+    // 生成随机密码
+    public function makePasswd(): string
+    {
+        return Str::random();
+    }
+
+    // 生成UUID
+    public function makeUUID()
+    {
+        return Str::uuid();
+    }
+
+    // 生成网站安全码
+    public function makeSecurityCode(): string
+    {
+        return strtolower(Str::random(8));
+    }
+
+    // 类似Linux中的tail命令
+    public function tail($file, $n, $base = 5)
+    {
+        $fileLines = $this->countLine($file);
+        if ($fileLines < 15000) {
+            return false;
+        }
+
+        $fp = fopen($file, 'rb+');
+        assert($n > 0);
+        $pos   = $n + 1;
+        $lines = [];
+        while (count($lines) <= $n) {
+            try {
+                fseek($fp, -$pos, SEEK_END);
+            } catch (Exception $e) {
+                break;
+            }
+
+            $pos *= $base;
+            while ( ! feof($fp)) {
+                array_unshift($lines, fgets($fp));
+            }
+        }
+
+        return array_slice($lines, 0, $n);
+    }
+
+    /**
+     * 计算文件行数
+     *
+     * @param $file
+     *
+     * @return int
+     */
+    public function countLine($file): int
+    {
+        $fp = fopen($file, 'rb');
+        $i  = 0;
+        while ( ! feof($fp)) {
+            //每次读取2M
+            if ($data = fread($fp, 1024 * 1024 * 2)) {
+                //计算读取到的行数
+                $num = substr_count($data, "\n");
+                $i   += $num;
+            }
+        }
+
+        fclose($fp);
+
+        return $i;
+    }
+
+    // 获取邮箱后缀
+    public function emailFilterList($type): array
+    {
+        return EmailFilter::whereType($type)->pluck('words')->toArray();
+    }
+
+    // 将Base64图片转换为本地图片并保存
+    public function base64ImageSaver($base64_image_content): ?string
+    {
+        // 匹配出图片的格式
+        if (preg_match(
+            '/^(data:\s*image\/(\w+);base64,)/',
+            $base64_image_content,
+            $result
+        )) {
+            $type = $result[2];
+
+            $directory = date('Ymd');
+            $path      = '/assets/images/qrcode/' . $directory . '/';
+            // 检查是否有该文件夹,如果没有就创建,并给予最高权限
+            if ( ! file_exists(public_path($path))
+                 && ! mkdir(
+                    $concurrentDirectory = public_path($path),
+                    0755,
+                    true
+                )
+                 && ! is_dir($concurrentDirectory)) {
+                throw new RuntimeException(
+                    sprintf(
+                        'Directory "%s" was not created',
+                        $concurrentDirectory
+                    )
+                );
+            }
+
+            $fileName = Str::random(18) . ".{$type}";
+            if (file_put_contents(
+                public_path($path . $fileName),
+                base64_decode(
+                    str_replace($result[1], '', $base64_image_content)
+                )
+            )) {
+                chmod(public_path($path . $fileName), 0744);
+
+                return $path . $fileName;
+            }
+        }
+
+        return '';
+    }
+
+    // 上传文件处理
+    public function uploadFile(UploadedFile $file): string
+    {
+        $fileType = $file->getClientOriginalExtension();
+
+        // 验证文件合法性
+        if ( ! in_array($fileType, ['jpg', 'png', 'jpeg', 'bmp'])) {
+            return false;
+        }
+
+        $name = date('YmdHis') . random_int(1000, 2000) . '.' . $fileType;
+        $move = $file->move(base_path() . '/public/upload/image/', $name);
+
+        return $move ? '/upload/image/' . $name : '';
+    }
+
+    /**
+     * 节点信息
+     *
+     * @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 {
+        $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;
+                }
+                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;
+                }
+                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);
+                }
+                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'
+            );
+    }
+
+    // 流量使用图表
+    public function dataFlowChart($id, $is_node = 0): array
+    {
+        if ($is_node) {
+            $currentFlow = UserDataFlowLog::whereNodeId($id);
+            $hourlyFlow  = NodeHourlyDataFlow::whereNodeId($id);
+            $dailyFlow   = NodeDailyDataFlow::whereNodeId($id);
+        } else {
+            $currentFlow = UserDataFlowLog::whereUserId($id);
+            $hourlyFlow  = UserHourlyDataFlow::userHourly($id);
+            $dailyFlow   = UserDailyDataFlow::userDaily($id);
+        }
+        $currentFlow = $currentFlow->where(
+            'log_time',
+            '>=',
+            strtotime(date('Y-m-d H:00'))
+        )->sum(DB::raw('u + d'));
+        $hourlyFlow  = $hourlyFlow->whereDate('created_at', date('Y-m-d'))
+                                  ->pluck('total', 'created_at')
+                                  ->toArray();
+        $dailyFlow   = $dailyFlow->whereMonth('created_at', date('n'))->pluck(
+            'total',
+            'created_at'
+        )->toArray();
+
+        // 节点一天内的流量
+        $hourlyData = array_fill(0, date('G') + 1, 0);
+        foreach ($hourlyFlow as $date => $dataFlow) {
+            $hourlyData[date('G', strtotime($date))] = round($dataFlow / GB, 3);
+        }
+        $hourlyData[date('G') + 1] = round($currentFlow / GB, 3);
+
+        // 节点一个月内的流量
+        $dailyData = array_fill(0, date('j'), 0);
+        foreach ($dailyFlow as $date => $dataFlow) {
+            $dailyData[date('j', strtotime($date)) - 1] = round(
+                $dataFlow / GB,
+                3
+            );
+        }
+        $dailyData[date('j', strtotime(now())) - 1] = round(
+            (array_sum($hourlyFlow) + $currentFlow) / GB,
+            3
+        );
+
+        return [
+            'trafficDaily'  => json_encode($dailyData),
+            'trafficHourly' => json_encode($hourlyData),
+            'monthDays'     => json_encode(range(1, date("j"), 1)),// 本月天数
+            'dayHours'      => json_encode(range(0, date("G") + 1, 1))// 本日小时
+        ];
+    }
+
 }

+ 66 - 47
app/Http/Controllers/Gateway/AbstractPayment.php

@@ -8,60 +8,79 @@ use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Str;
 
-abstract class AbstractPayment {
-	abstract public function purchase(Request $request): JsonResponse;
+abstract class AbstractPayment
+{
 
-	abstract public function notify(Request $request): void;
+    abstract public function purchase(Request $request): JsonResponse;
 
-	protected function creatNewPayment($uid, $oid, $amount): Payment {
-		$payment = new Payment();
-		$payment->trade_no = Str::random(8);
-		$payment->user_id = $uid;
-		$payment->order_id = $oid;
-		$payment->amount = $amount;
-		$payment->save();
+    abstract public function notify(Request $request): void;
 
-		return $payment;
-	}
+    protected function creatNewPayment($uid, $oid, $amount): Payment
+    {
+        $payment           = new Payment();
+        $payment->trade_no = Str::random(8);
+        $payment->user_id  = $uid;
+        $payment->order_id = $oid;
+        $payment->amount   = $amount;
+        $payment->save();
 
-	/**
-	 * @param  string  $trade_no      本地订单号
-	 * @param  string  $out_trade_no  外部订单号
-	 * @param  int     $amount        交易金额
-	 * @return int
-	 */
-	protected function addPamentCallback($trade_no, $out_trade_no, $amount): int {
-		$log = new PaymentCallback();
-		$log->trade_no = $trade_no;
-		$log->out_trade_no = $out_trade_no;
-		$log->amount = $amount;
+        return $payment;
+    }
 
-		return $log->save();
-	}
+    /**
+     * @param  string  $trade_no  本地订单号
+     * @param  string  $out_trade_no  外部订单号
+     * @param  int  $amount  交易金额
+     *
+     * @return int
+     */
+    protected function addPamentCallback(
+        string $trade_no,
+        string $out_trade_no,
+        int $amount
+    ): int {
+        $log               = new PaymentCallback();
+        $log->trade_no     = $trade_no;
+        $log->out_trade_no = $out_trade_no;
+        $log->amount       = $amount;
 
-	// MD5验签
-	protected function verify($data, $key, $signature, $filter = true): bool {
-		return hash_equals($this->aliStyleSign($data, $key, $filter), $signature);
-	}
+        return $log->save();
+    }
 
-	/**
-	 *  Alipay式数据MD5签名
-	 * @param  array    $data    需要加密的数组
-	 * @param  string   $key     尾部的密钥
-	 * @param  boolean  $filter  是否清理空值
-	 * @return string md5加密后的数据
-	 */
-	protected function aliStyleSign($data, $key, $filter = true): string {
-		// 剃离sign,sign_type,空值
-		unset($data['sign'], $data['sign_type']);
-		if($filter){
-			$data = array_filter($data);
-		}
+    // MD5验签
+    protected function verify($data, $key, $signature, $filter = true): bool
+    {
+        return hash_equals(
+            $this->aliStyleSign($data, $key, $filter),
+            $signature
+        );
+    }
 
-		// 排序
-		ksort($data, SORT_STRING);
-		reset($data);
+    /**
+     *  Alipay式数据MD5签名
+     *
+     * @param  array  $data  需要加密的数组
+     * @param  string  $key  尾部的密钥
+     * @param  bool  $filter  是否清理空值
+     *
+     * @return string md5加密后的数据
+     */
+    protected function aliStyleSign(
+        array $data,
+        string $key,
+        $filter = true
+    ): string {
+        // 剃离sign,sign_type,空值
+        unset($data['sign'], $data['sign_type']);
+        if ($filter) {
+            $data = array_filter($data);
+        }
+
+        // 排序
+        ksort($data, SORT_STRING);
+        reset($data);
+
+        return md5(urldecode(http_build_query($data)) . $key);
+    }
 
-		return md5(urldecode(http_build_query($data)).$key);
-	}
 }

+ 107 - 74
app/Http/Controllers/Gateway/BitpayX.php

@@ -9,78 +9,111 @@ use Illuminate\Http\JsonResponse;
 use Log;
 use Response;
 
-class BitpayX extends AbstractPayment {
-	public function purchase($request): JsonResponse {
-		$payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
-
-		$data = [
-			'merchant_order_id' => $payment->trade_no,
-			'price_amount'      => $payment->amount,
-			'price_currency'    => 'CNY',
-			'title'             => '支付单号:'.$payment->trade_no,
-			'description'       => sysConfig('subject_name')?: sysConfig('website_name'),
-			'callback_url'      => (sysConfig('website_callback_url')?: sysConfig('website_url')).'/callback/notify?method=bitpayx',
-			'success_url'       => sysConfig('website_url').'/invoices',
-			'cancel_url'        => sysConfig('website_url').'/invoices',
-			'token'             => $this->sign($payment->trade_no),
-		];
-		$result = $this->sendRequest($data);
-
-		if($result['status'] === 200 || $result['status'] === 201){
-			$result['payment_url'] .= '&lang=zh';
-			$payment->update(['url' => $result['payment_url']]);
-
-			return Response::json(['status' => 'success', 'url' => $result['payment_url'], 'message' => '创建订单成功!']);
-		}
-
-		Log::error('创建订单错误:'.var_export($result, true));
-		return Response::json(['status' => 'fail', 'message' => '创建订单失败!'.$result['error']]);
-	}
-
-	private function sign($tradeNo): string {
-		$data = [
-			'merchant_order_id' => $tradeNo,
-			'secret'            => sysConfig('bitpay_secret'),
-			'type'              => 'FIAT'
-		];
-
-		return $this->aliStyleSign($data, sysConfig('bitpay_secret'));
-	}
-
-	private function sendRequest($data, $type = 'createOrder') {
-		$client = new Client([
-			'base_uri' => 'https://api.mugglepay.com/v1/',
-			'timeout'  => 15,
-			'headers'  => [
-				'token'        => sysConfig('bitpay_secret'),
-				'content-type' => 'application/json'
-			]
-		]);
-
-		if($type === 'query'){
-			$request = $client->get('orders/merchant_order_id/status?id='.$data['merchant_order_id']);
-		}else{// Create Order
-			$request = $client->post('orders', ['body' => json_encode($data)]);
-		}
-		if($request->getStatusCode() !== 200){
-			Log::error('BitPayX请求支付错误:'.var_export($request, true));
-		}
-
-		return json_decode($request->getBody(), true);
-	}
-
-	//Todo: Postman虚拟测试通过,需要真实数据参考验证
-	public function notify($request): void {
-		$tradeNo = $request->input(['merchant_order_id']);
-		if($request->input(['status']) === 'PAID' && hash_equals($this->sign($tradeNo), $request->input(['token']))){
-			$payment = Payment::whereTradeNo($tradeNo)->first();
-			if($payment){
-				$ret = $payment->order->update(['status' => 2]);
-				if($ret){
-					exit(json_encode(['status' => 200]));
-				}
-			}
-		}
-		exit(json_encode(['status' => 400]));
-	}
+class BitpayX extends AbstractPayment
+{
+
+    public function purchase($request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(
+            Auth::id(),
+            $request->input('id'),
+            $request->input('amount')
+        );
+
+        $data   = [
+            'merchant_order_id' => $payment->trade_no,
+            'price_amount'      => $payment->amount,
+            'price_currency'    => 'CNY',
+            'title'             => '支付单号:' . $payment->trade_no,
+            'description'       => sysConfig('subject_name') ?: sysConfig(
+                'website_name'
+            ),
+            'callback_url'      => (sysConfig(
+                    'website_callback_url'
+                ) ?: sysConfig(
+                    'website_url'
+                )) . '/callback/notify?method=bitpayx',
+            'success_url'       => sysConfig('website_url') . '/invoices',
+            'cancel_url'        => sysConfig('website_url') . '/invoices',
+            'token'             => $this->sign($payment->trade_no),
+        ];
+        $result = $this->sendRequest($data);
+
+        if ($result['status'] === 200 || $result['status'] === 201) {
+            $result['payment_url'] .= '&lang=zh';
+            $payment->update(['url' => $result['payment_url']]);
+
+            return Response::json(
+                [
+                    'status'  => 'success',
+                    'url'     => $result['payment_url'],
+                    'message' => '创建订单成功!',
+                ]
+            );
+        }
+
+        Log::error('创建订单错误:' . var_export($result, true));
+
+        return Response::json(
+            ['status' => 'fail', 'message' => '创建订单失败!' . $result['error']]
+        );
+    }
+
+    private function sign($tradeNo): string
+    {
+        $data = [
+            'merchant_order_id' => $tradeNo,
+            'secret'            => sysConfig('bitpay_secret'),
+            'type'              => 'FIAT',
+        ];
+
+        return $this->aliStyleSign($data, sysConfig('bitpay_secret'));
+    }
+
+    private function sendRequest($data, $type = 'createOrder')
+    {
+        $client = new Client(
+            [
+                'base_uri' => 'https://api.mugglepay.com/v1/',
+                'timeout'  => 15,
+                'headers'  => [
+                    'token'        => sysConfig('bitpay_secret'),
+                    'content-type' => 'application/json',
+                ],
+            ]
+        );
+
+        if ($type === 'query') {
+            $request = $client->get(
+                'orders/merchant_order_id/status?id=' . $data['merchant_order_id']
+            );
+        } else {// Create Order
+            $request = $client->post('orders', ['body' => json_encode($data)]);
+        }
+        if ($request->getStatusCode() !== 200) {
+            Log::error('BitPayX请求支付错误:' . var_export($request, true));
+        }
+
+        return json_decode($request->getBody(), true);
+    }
+
+    //Todo: Postman虚拟测试通过,需要真实数据参考验证
+    public function notify($request): void
+    {
+        $tradeNo = $request->input(['merchant_order_id']);
+        if ($request->input(['status']) === 'PAID' && hash_equals(
+                $this->sign($tradeNo),
+                $request->input(['token'])
+            )) {
+            $payment = Payment::whereTradeNo($tradeNo)->first();
+            if ($payment) {
+                $ret = $payment->order->update(['status' => 2]);
+                if ($ret) {
+                    exit(json_encode(['status' => 200]));
+                }
+            }
+        }
+        exit(json_encode(['status' => 400]));
+    }
+
 }

+ 55 - 36
app/Http/Controllers/Gateway/CodePay.php

@@ -7,40 +7,59 @@ use Auth;
 use Illuminate\Http\JsonResponse;
 use Response;
 
-class CodePay extends AbstractPayment {
-	public function purchase($request): JsonResponse {
-		$payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
-
-		$data = [
-			'id'         => sysConfig('codepay_id'),
-			'pay_id'     => $payment->trade_no,
-			'type'       => $request->input('type'),            //1支付宝支付 2QQ钱包 3微信支付
-			'price'      => $payment->amount,
-			'page'       => 1,
-			'outTime'    => 900,
-			'notify_url' => (sysConfig('website_callback_url')?: sysConfig('website_url')).'/callback/notify?method=codepay',
-			'return_url' => sysConfig('website_url').'/invoices',
-		];
-		$data['sign'] = $this->aliStyleSign($data, sysConfig('codepay_key'));
-
-		$url = sysConfig('codepay_url').http_build_query($data);
-		$payment->update(['url' => $url]);
-
-		return Response::json(['status' => 'success', 'url' => $url, 'message' => '创建订单成功!']);
-	}
-
-	public function notify($request): void {
-		$trade_no = $request->input('pay_id');
-		if($trade_no && $request->input('pay_no')
-		   && $this->verify($request->except('method'), sysConfig('codepay_key'), $request->input('sign'), false)){
-			$payment = Payment::whereTradeNo($trade_no)->first();
-			if($payment){
-				$ret = $payment->order->update(['status' => 2]);
-				if($ret){
-					exit('success');
-				}
-			}
-		}
-		exit('fail');
-	}
+class CodePay extends AbstractPayment
+{
+
+    public function purchase($request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(
+            Auth::id(),
+            $request->input('id'),
+            $request->input('amount')
+        );
+
+        $data         = [
+            'id'         => sysConfig('codepay_id'),
+            'pay_id'     => $payment->trade_no,
+            'type'       => $request->input('type'),
+            //1支付宝支付 2QQ钱包 3微信支付
+            'price'      => $payment->amount,
+            'page'       => 1,
+            'outTime'    => 900,
+            'notify_url' => (sysConfig('website_callback_url') ?: sysConfig(
+                    'website_url'
+                )) . '/callback/notify?method=codepay',
+            'return_url' => sysConfig('website_url') . '/invoices',
+        ];
+        $data['sign'] = $this->aliStyleSign($data, sysConfig('codepay_key'));
+
+        $url = sysConfig('codepay_url') . http_build_query($data);
+        $payment->update(['url' => $url]);
+
+        return Response::json(
+            ['status' => 'success', 'url' => $url, 'message' => '创建订单成功!']
+        );
+    }
+
+    public function notify($request): void
+    {
+        $trade_no = $request->input('pay_id');
+        if ($trade_no && $request->input('pay_no')
+            && $this->verify(
+                $request->except('method'),
+                sysConfig('codepay_key'),
+                $request->input('sign'),
+                false
+            )) {
+            $payment = Payment::whereTradeNo($trade_no)->first();
+            if ($payment) {
+                $ret = $payment->order->update(['status' => 2]);
+                if ($ret) {
+                    exit('success');
+                }
+            }
+        }
+        exit('fail');
+    }
+
 }

+ 87 - 56
app/Http/Controllers/Gateway/EPay.php

@@ -9,67 +9,98 @@ use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Response;
 
-class EPay extends AbstractPayment {
-	public function purchase(Request $request): JsonResponse {
-		$payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
+class EPay extends AbstractPayment
+{
 
-		switch($request->input('type')){
-			case 2:
-				$type = 'qqpay';
-				break;
-			case 3:
-				$type = 'wxpay';
-				break;
-			case 1:
-			default:
-				$type = 'alipay';
-				break;
-		}
+    public function purchase(Request $request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(
+            Auth::id(),
+            $request->input('id'),
+            $request->input('amount')
+        );
 
-		$data = [
-			'pid'          => sysConfig('epay_mch_id'),
-			'type'         => $type,
-			'notify_url'   => (sysConfig('website_callback_url')?: sysConfig('website_url')).'/callback/notify?method=epay',
-			'return_url'   => sysConfig('website_url').'/invoices',
-			'out_trade_no' => $payment->trade_no,
-			'name'         => sysConfig('subject_name')?: sysConfig('website_name'),
-			'money'        => $payment->amount,
-			'sign_type'    => 'MD5'
-		];
-		$data['sign'] = $this->aliStyleSign($data, sysConfig('epay_key'));
+        switch ($request->input('type')) {
+            case 2:
+                $type = 'qqpay';
+                break;
+            case 3:
+                $type = 'wxpay';
+                break;
+            case 1:
+            default:
+                $type = 'alipay';
+                break;
+        }
 
-		$url = sysConfig('epay_url').'submit.php?'.http_build_query($data);
-		$payment->update(['url' => $url]);
+        $data         = [
+            'pid'          => sysConfig('epay_mch_id'),
+            'type'         => $type,
+            'notify_url'   => (sysConfig('website_callback_url') ?: sysConfig(
+                    'website_url'
+                )) . '/callback/notify?method=epay',
+            'return_url'   => sysConfig('website_url') . '/invoices',
+            'out_trade_no' => $payment->trade_no,
+            'name'         => sysConfig('subject_name') ?: sysConfig(
+                'website_name'
+            ),
+            'money'        => $payment->amount,
+            'sign_type'    => 'MD5',
+        ];
+        $data['sign'] = $this->aliStyleSign($data, sysConfig('epay_key'));
 
-		return Response::json(['status' => 'success', 'url' => $url, 'message' => '创建订单成功!']);
-	}
+        $url = sysConfig('epay_url') . 'submit.php?' . http_build_query($data);
+        $payment->update(['url' => $url]);
 
-	public function notify(Request $request): void {
-		if($request->input('trade_status') === 'TRADE_SUCCESS'
-		   && $this->verify($request->except('method'), sysConfig('epay_key'), $request->input('sign'))){
-			$payment = Payment::whereTradeNo($request->input('out_trade_no'))->first();
-			if($payment){
-				$ret = $payment->order->update(['status' => 2]);
-				if($ret){
-					exit('SUCCESS');
-				}
-			}
-		}
-		exit('FAIL');
-	}
+        return Response::json(
+            ['status' => 'success', 'url' => $url, 'message' => '创建订单成功!']
+        );
+    }
 
-	public function queryInfo(): JsonResponse {
-		$request = (new Client())->get(sysConfig('epay_url').'api.php', [
-			'query' => [
-				'act' => 'query',
-				'pid' => sysConfig('epay_mch_id'),
-				'key' => sysConfig('epay_key')
-			]
-		]);
-		if($request->getStatusCode() == 200){
-			return Response::json(['status' => 'success', 'data' => json_decode($request->getBody(), true)]);
-		}
+    public function notify(Request $request): void
+    {
+        if ($request->input('trade_status') === 'TRADE_SUCCESS'
+            && $this->verify(
+                $request->except('method'),
+                sysConfig('epay_key'),
+                $request->input('sign')
+            )) {
+            $payment = Payment::whereTradeNo($request->input('out_trade_no'))
+                              ->first();
+            if ($payment) {
+                $ret = $payment->order->update(['status' => 2]);
+                if ($ret) {
+                    exit('SUCCESS');
+                }
+            }
+        }
+        exit('FAIL');
+    }
+
+    public function queryInfo(): JsonResponse
+    {
+        $request = (new Client())->get(
+            sysConfig('epay_url') . 'api.php',
+            [
+                'query' => [
+                    'act' => 'query',
+                    'pid' => sysConfig('epay_mch_id'),
+                    'key' => sysConfig('epay_key'),
+                ],
+            ]
+        );
+        if ($request->getStatusCode() == 200) {
+            return Response::json(
+                [
+                    'status' => 'success',
+                    'data'   => json_decode($request->getBody(), true),
+                ]
+            );
+        }
+
+        return Response::json(
+            ['status' => 'fail', 'message' => '获取失败!请检查配置信息']
+        );
+    }
 
-		return Response::json(['status' => 'fail', 'message' => '获取失败!请检查配置信息']);
-	}
 }

+ 104 - 75
app/Http/Controllers/Gateway/F2Fpay.php

@@ -12,89 +12,118 @@ use Payment\Client;
 use Payment\Exceptions\ClassNotFoundException;
 use Response;
 
-class F2Fpay extends AbstractPayment {
-	private static $aliConfig;
+class F2Fpay extends AbstractPayment
+{
 
-	public function __construct() {
-		self::$aliConfig = [
-			'use_sandbox'     => false,
-			'app_id'          => sysConfig('f2fpay_app_id'),
-			'sign_type'       => 'RSA2',
-			'ali_public_key'  => sysConfig('f2fpay_public_key'),
-			'rsa_private_key' => sysConfig('f2fpay_private_key'),
-			'limit_pay'       => [],
-			'notify_url'      => (sysConfig('website_callback_url')?: sysConfig('website_url')).'/callback/notify?method=f2fpay',
-			'return_url'      => sysConfig('website_url').'/invoices',
-			'fee_type'        => 'CNY',
-		];
-	}
+    private static $aliConfig;
 
-	public function purchase($request): JsonResponse {
-		$payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
+    public function __construct()
+    {
+        self::$aliConfig = [
+            'use_sandbox'     => false,
+            'app_id'          => sysConfig('f2fpay_app_id'),
+            'sign_type'       => 'RSA2',
+            'ali_public_key'  => sysConfig('f2fpay_public_key'),
+            'rsa_private_key' => sysConfig('f2fpay_private_key'),
+            'limit_pay'       => [],
+            'notify_url'      => (sysConfig(
+                    'website_callback_url'
+                ) ?: sysConfig(
+                    'website_url'
+                )) . '/callback/notify?method=f2fpay',
+            'return_url'      => sysConfig('website_url') . '/invoices',
+            'fee_type'        => 'CNY',
+        ];
+    }
 
-		$data = [
-			'body'        => '',
-			'subject'     => sysConfig('subject_name')?: sysConfig('website_name'),
-			'trade_no'    => $payment->trade_no,
-			'time_expire' => time() + 900, // 必须 15分钟 内付款
-			'amount'      => $payment->amount,
-		];
+    public function purchase($request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(
+            Auth::id(),
+            $request->input('id'),
+            $request->input('amount')
+        );
 
-		try{
-			$result = (new Client(Client::ALIPAY, self::$aliConfig))->pay(Client::ALI_CHANNEL_QR, $data);
-		}catch(InvalidArgumentException $e){
-			Log::error("【支付宝当面付】输入信息错误: ".$e->getMessage());
-			exit;
-		}catch(ClassNotFoundException $e){
-			Log::error("【支付宝当面付】未知类型: ".$e->getMessage());
-			exit;
-		}catch(Exception $e){
-			Log::error("【支付宝当面付】错误: ".$e->getMessage());
-			exit;
-		}
+        $data = [
+            'body'        => '',
+            'subject'     => sysConfig('subject_name') ?: sysConfig(
+                'website_name'
+            ),
+            'trade_no'    => $payment->trade_no,
+            'time_expire' => time() + 900, // 必须 15分钟 内付款
+            'amount'      => $payment->amount,
+        ];
 
-		$payment->update(['qr_code' => 1, 'url' => $result['qr_code']]);
+        try {
+            $result = (new Client(Client::ALIPAY, self::$aliConfig))->pay(
+                Client::ALI_CHANNEL_QR,
+                $data
+            );
+        } catch (InvalidArgumentException $e) {
+            Log::error("【支付宝当面付】输入信息错误: " . $e->getMessage());
+            exit;
+        } catch (ClassNotFoundException $e) {
+            Log::error("【支付宝当面付】未知类型: " . $e->getMessage());
+            exit;
+        } catch (Exception $e) {
+            Log::error("【支付宝当面付】错误: " . $e->getMessage());
+            exit;
+        }
 
-		return Response::json(['status' => 'success', 'data' => $payment->trade_no, 'message' => '创建订单成功!']);
-	}
+        $payment->update(['qr_code' => 1, 'url' => $result['qr_code']]);
 
-	public function notify($request): void {
-		$data = [
-			'trade_no'       => $request->input('out_trade_no'),
-			'transaction_id' => $request->input('trade_no'),
-		];
+        return Response::json(
+            [
+                'status'  => 'success',
+                'data'    => $payment->trade_no,
+                'message' => '创建订单成功!',
+            ]
+        );
+    }
 
-		try{
-			$result = (new Client(Client::ALIPAY, self::$aliConfig))->tradeQuery($data);
-			Log::info("【支付宝当面付】回调验证查询:".var_export($result, true));
-		}catch(InvalidArgumentException $e){
-			Log::error("【支付宝当面付】回调信息错误: ".$e->getMessage());
-			exit;
-		}catch(ClassNotFoundException $e){
-			Log::error("【支付宝当面付】未知类型: ".$e->getMessage());
-			exit;
-		}catch(Exception $e){
-			Log::error("【支付宝当面付】错误: ".$e->getMessage());
-			exit;
-		}
+    public function notify($request): void
+    {
+        $data = [
+            'trade_no'       => $request->input('out_trade_no'),
+            'transaction_id' => $request->input('trade_no'),
+        ];
 
-		if($result['code'] == 10000 && $result['msg'] === "Success"){
-			if($_POST['trade_status'] === 'TRADE_FINISHED' || $_POST['trade_status'] === 'TRADE_SUCCESS'){
-				$payment = Payment::whereTradeNo($request->input('out_trade_no'))->first();
-				if($payment){
-					$ret = $payment->order->update(['status' => 2]);
-					if($ret){
-						exit('success');
-					}
-				}
-			}else{
-				Log::info('支付宝当面付-POST:交易失败['.getClientIp().']');
-			}
-		}else{
-			Log::info('支付宝当面付-POST:验证失败['.getClientIp().']');
-		}
+        try {
+            $result = (new Client(
+                Client::ALIPAY, self::$aliConfig
+            ))->tradeQuery($data);
+            Log::info("【支付宝当面付】回调验证查询:" . var_export($result, true));
+        } catch (InvalidArgumentException $e) {
+            Log::error("【支付宝当面付】回调信息错误: " . $e->getMessage());
+            exit;
+        } catch (ClassNotFoundException $e) {
+            Log::error("【支付宝当面付】未知类型: " . $e->getMessage());
+            exit;
+        } catch (Exception $e) {
+            Log::error("【支付宝当面付】错误: " . $e->getMessage());
+            exit;
+        }
+
+        if ($result['code'] == 10000 && $result['msg'] === "Success") {
+            if ($_POST['trade_status'] === 'TRADE_FINISHED' || $_POST['trade_status'] === 'TRADE_SUCCESS') {
+                $payment = Payment::whereTradeNo(
+                    $request->input('out_trade_no')
+                )->first();
+                if ($payment) {
+                    $ret = $payment->order->update(['status' => 2]);
+                    if ($ret) {
+                        exit('success');
+                    }
+                }
+            } else {
+                Log::info('支付宝当面付-POST:交易失败[' . getClientIp() . ']');
+            }
+        } else {
+            Log::info('支付宝当面付-POST:验证失败[' . getClientIp() . ']');
+        }
+
+        // 返回验证结果
+        exit('fail');
+    }
 
-		// 返回验证结果
-		exit('fail');
-	}
 }

+ 30 - 18
app/Http/Controllers/Gateway/Local.php

@@ -9,23 +9,35 @@ use App\Models\Order;
 use Illuminate\Http\JsonResponse;
 use Response;
 
-class Local extends AbstractPayment {
-	public function purchase($request): JsonResponse {
-		$order = Order::find($request->input('id'));
-		$goods = Goods::find($request->input('goods_id'));
-		$user = $order->user;
+class Local extends AbstractPayment
+{
+
+    public function purchase($request): JsonResponse
+    {
+        $order = Order::find($request->input('id'));
+        $goods = Goods::find($request->input('goods_id'));
+        $user  = $order->user;
+
+        if ($user && $goods) {
+            $user->update(['credit' => $user->credit - $order->amount]);
+            // 记录余额操作日志
+            Helpers::addUserCreditLog(
+                $user->id,
+                $order->id,
+                $user->credit + $order->amount,
+                $user->credit,
+                -1 * $order->amount,
+                '购买商品' . $goods->name
+            );
+        }
+
+        $order->update(['status' => 2]);
+
+        return Response::json(['status' => 'success', 'message' => '购买完成!']);
+    }
+
+    public function notify($request): void
+    {
+    }
 
-		if($user && $goods){
-			$user->update(['credit' => $user->credit - $order->amount]);
-			// 记录余额操作日志
-			Helpers::addUserCreditLog($user->id, $order->id, $user->credit + $order->amount, $user->credit,
-				-1 * $order->amount, '购买商品'.$goods->name);
-		}
-
-		$order->update(['status' => 2]);
-
-		return Response::json(['status' => 'success', 'message' => '购买完成!']);
-	}
-
-	public function notify($request): void { }
 }

+ 65 - 41
app/Http/Controllers/Gateway/PayJs.php

@@ -8,45 +8,69 @@ use Illuminate\Http\JsonResponse;
 use Response;
 use Xhat\Payjs\Payjs as Pay;
 
-class PayJs extends AbstractPayment {
-	private static $config;
-
-	public function __construct() {
-		self::$config = [
-			'mchid' => sysConfig('payjs_mch_id'),   // 配置商户号
-			'key'   => sysConfig('payjs_key'),   // 配置通信密钥
-		];
-	}
-
-	public function purchase($request): JsonResponse {
-		$payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
-
-		$result = (new Pay($this::$config))->cashier([
-			'body'         => sysConfig('subject_name')?: sysConfig('website_name'),
-			'total_fee'    => $payment->amount * 100,
-			'out_trade_no' => $payment->trade_no,
-			'notify_url'   => (sysConfig('website_callback_url')?: sysConfig('website_url')).'/callback/notify?method=payjs',
-		]);
-
-		// 获取收款二维码内容
-		$payment->update(['qr_code' => 1, 'url' => $result]);
-
-		//$this->addPamentCallback($payment->trade_no, null, $payment->amount * 100);
-		return Response::json(['status' => 'success', 'data' => $payment->trade_no, 'message' => '创建订单成功!']);
-	}
-
-	public function notify($request): void {
-		$data = (new Pay($this::$config))->notify();
-
-		if($data['return_code'] == 1){
-			$payment = Payment::whereTradeNo($data['out_trade_no'])->first();
-			if($payment){
-				$ret = $payment->order->update(['status' => 2]);
-				if($ret){
-					exit('success');
-				}
-			}
-		}
-		exit('fail');
-	}
+class PayJs extends AbstractPayment
+{
+
+    private static $config;
+
+    public function __construct()
+    {
+        self::$config = [
+            'mchid' => sysConfig('payjs_mch_id'),   // 配置商户号
+            'key'   => sysConfig('payjs_key'),   // 配置通信密钥
+        ];
+    }
+
+    public function purchase($request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(
+            Auth::id(),
+            $request->input('id'),
+            $request->input('amount')
+        );
+
+        $result = (new Pay($this::$config))->cashier(
+            [
+                'body'         => sysConfig('subject_name') ?: sysConfig(
+                    'website_name'
+                ),
+                'total_fee'    => $payment->amount * 100,
+                'out_trade_no' => $payment->trade_no,
+                'notify_url'   => (sysConfig(
+                        'website_callback_url'
+                    ) ?: sysConfig(
+                        'website_url'
+                    )) . '/callback/notify?method=payjs',
+            ]
+        );
+
+        // 获取收款二维码内容
+        $payment->update(['qr_code' => 1, 'url' => $result]);
+
+        //$this->addPamentCallback($payment->trade_no, null, $payment->amount * 100);
+        return Response::json(
+            [
+                'status'  => 'success',
+                'data'    => $payment->trade_no,
+                'message' => '创建订单成功!',
+            ]
+        );
+    }
+
+    public function notify($request): void
+    {
+        $data = (new Pay($this::$config))->notify();
+
+        if ($data['return_code'] == 1) {
+            $payment = Payment::whereTradeNo($data['out_trade_no'])->first();
+            if ($payment) {
+                $ret = $payment->order->update(['status' => 2]);
+                if ($ret) {
+                    exit('success');
+                }
+            }
+        }
+        exit('fail');
+    }
+
 }

+ 173 - 122
app/Http/Controllers/Gateway/PayPal.php

@@ -13,126 +13,177 @@ use Log;
 use Response;
 use Srmklive\PayPal\Services\ExpressCheckout;
 
-class PayPal extends AbstractPayment {
-	protected $provider;
-	protected $exChange;
-
-	public function __construct() {
-		$this->provider = new ExpressCheckout();
-		$config = [
-			'mode' => 'live',
-			'live' => [
-				'username'    => sysConfig('paypal_username'),
-				'password'    => sysConfig('paypal_password'),
-				'secret'      => sysConfig('paypal_secret'),
-				'certificate' => sysConfig('paypal_certificate'),
-				'app_id'      => sysConfig('paypal_app_id'),
-			],
-
-			'payment_action' => 'Sale',
-			'currency'       => 'USD',
-			'billing_type'   => 'MerchantInitiatedBilling',
-			'notify_url'     => (sysConfig('website_callback_url')?: sysConfig('website_url')).'/callback/notify?method=paypal',
-			'locale'         => 'zh_CN',
-			'validate_ssl'   => true,
-		];
-		$this->provider->setApiCredentials($config);
-		$this->exChange = 7;
-		$client = new Client(['timeout' => 15]);
-		$exChangeRate = json_decode($client->get('http://api.k780.com/?app=finance.rate&scur=USD&tcur=CNY&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4')
-		                                   ->getBody(), true);
-
-		if($exChangeRate && $exChangeRate['success']){
-			$this->exChange = $exChangeRate['result']['rate'];
-		}
-	}
-
-	public function purchase($request): JsonResponse {
-		$payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
-
-		$data = $this->getCheckoutData($payment->trade_no, $payment->amount);
-
-		try{
-			$response = $this->provider->setExpressCheckout($data);
-			if(!$response['paypal_link']){
-				Log::error('Paypal处理错误:'.var_export($response, true));
-
-				return Response::json(['status' => 'fail', 'message' => '创建订单失败,请使用其他方式或通知管理员!']);
-			}
-			$payment->update(['url' => $response['paypal_link']]);
-
-			return Response::json(['status' => 'success', 'url' => $response['paypal_link'], 'message' => '创建订单成功!']);
-		}catch(Exception $e){
-			Log::error("【PayPal】错误: ".$e->getMessage());
-			exit;
-		}
-	}
-
-	protected function getCheckoutData($trade_no, $amount): array {
-		$amount = 0.3 + ceil($amount / $this->exChange * 100) / 100;
-
-		return [
-			'invoice_id'          => $trade_no,
-			'items'               => [
-				[
-					'name'  => sysConfig('subject_name')?: sysConfig('website_name'),
-					'price' => $amount,
-					'desc'  => 'Description for'.(sysConfig('subject_name')?: sysConfig('website_name')),
-					'qty'   => 1
-				]
-			],
-			'invoice_description' => $trade_no,
-			'return_url'          => sysConfig('website_url').'/callback/checkout',
-			'cancel_url'          => sysConfig('website_url').'/invoices',
-			'total'               => $amount,
-		];
-	}
-
-	public function getCheckout(Request $request) {
-		$token = $request->get('token');
-		$PayerID = $request->get('PayerID');
-
-		// Verify Express Checkout Token
-		$response = $this->provider->getExpressCheckoutDetails($token);
-
-		if(in_array(strtoupper($response['ACK']), ['SUCCESS', 'SUCCESSWITHWARNING'])){
-			$payment = Payment::whereTradeNo($response['INVNUM'])->firstOrFail();
-			$data = $this->getCheckoutData($payment->trade_no, $payment->amount);
-			// Perform transaction on PayPal
-			$payment_status = $this->provider->doExpressCheckoutPayment($data, $token, $PayerID);
-			$status = $payment_status['PAYMENTINFO_0_PAYMENTSTATUS'];
-
-			if(!strcasecmp($status, 'Completed') || !strcasecmp($status, 'Processed')){
-				Log::info("Order $payment->order_id has been paid successfully!");
-				$payment->order->update(['status' => 1]);
-			}else{
-				Log::error("Error processing PayPal payment for Order $payment->id!");
-			}
-		}
-
-		return redirect('/invoices');
-	}
-
-	public function notify($request): void {
-		$request->merge(['cmd' => '_notify-validate']);
-		foreach($request->input() as $key => $value){
-			if($value == null){
-				$request->request->set($key, '');
-			}
-		}
-		$post = $request->all();
-
-		$response = (string) $this->provider->verifyIPN($post);
-
-		if($response === 'VERIFIED' && $request['invoice']){
-			$payment = Payment::whereTradeNo($request['invoice'])->first();
-			if($payment && $payment->status == 0){
-				$ret = $payment->order->update(['status' => 2]);
-				if($ret){
-					exit('success');
-				}
-			}
-		}
-		exit("fail");
-	}
+class PayPal extends AbstractPayment
+{
+
+    protected $provider;
+    protected $exChange;
+
+    public function __construct()
+    {
+        $this->provider = new ExpressCheckout();
+        $config         = [
+            'mode' => 'live',
+            'live' => [
+                'username'    => sysConfig('paypal_username'),
+                'password'    => sysConfig('paypal_password'),
+                'secret'      => sysConfig('paypal_secret'),
+                'certificate' => sysConfig('paypal_certificate'),
+                'app_id'      => sysConfig('paypal_app_id'),
+            ],
+
+            'payment_action' => 'Sale',
+            'currency'       => 'USD',
+            'billing_type'   => 'MerchantInitiatedBilling',
+            'notify_url'     => (sysConfig('website_callback_url') ?: sysConfig(
+                    'website_url'
+                )) . '/callback/notify?method=paypal',
+            'locale'         => 'zh_CN',
+            'validate_ssl'   => true,
+        ];
+        $this->provider->setApiCredentials($config);
+        $this->exChange = 7;
+        $client         = new Client(['timeout' => 15]);
+        $exChangeRate   = json_decode(
+            $client->get(
+                'http://api.k780.com/?app=finance.rate&scur=USD&tcur=CNY&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4'
+            )
+                   ->getBody(),
+            true
+        );
+
+        if ($exChangeRate && $exChangeRate['success']) {
+            $this->exChange = $exChangeRate['result']['rate'];
+        }
+    }
+
+    public function purchase($request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(
+            Auth::id(),
+            $request->input('id'),
+            $request->input('amount')
+        );
+
+        $data = $this->getCheckoutData($payment->trade_no, $payment->amount);
+
+        try {
+            $response = $this->provider->setExpressCheckout($data);
+            if ( ! $response['paypal_link']) {
+                Log::error('Paypal处理错误:' . var_export($response, true));
+
+                return Response::json(
+                    ['status' => 'fail', 'message' => '创建订单失败,请使用其他方式或通知管理员!']
+                );
+            }
+            $payment->update(['url' => $response['paypal_link']]);
+
+            return Response::json(
+                [
+                    'status'  => 'success',
+                    'url'     => $response['paypal_link'],
+                    'message' => '创建订单成功!',
+                ]
+            );
+        } catch (Exception $e) {
+            Log::error("【PayPal】错误: " . $e->getMessage());
+            exit;
+        }
+    }
+
+    protected function getCheckoutData($trade_no, $amount): array
+    {
+        $amount = 0.3 + ceil($amount / $this->exChange * 100) / 100;
+
+        return [
+            'invoice_id'          => $trade_no,
+            'items'               => [
+                [
+                    'name'  => sysConfig('subject_name') ?: sysConfig(
+                        'website_name'
+                    ),
+                    'price' => $amount,
+                    'desc'  => 'Description for' . (sysConfig(
+                            'subject_name'
+                        ) ?: sysConfig('website_name')),
+                    'qty'   => 1,
+                ],
+            ],
+            'invoice_description' => $trade_no,
+            'return_url'          => sysConfig(
+                                         'website_url'
+                                     ) . '/callback/checkout',
+            'cancel_url'          => sysConfig('website_url') . '/invoices',
+            'total'               => $amount,
+        ];
+    }
+
+    public function getCheckout(Request $request)
+    {
+        $token   = $request->get('token');
+        $PayerID = $request->get('PayerID');
+
+        // Verify Express Checkout Token
+        $response = $this->provider->getExpressCheckoutDetails($token);
+
+        if (in_array(
+            strtoupper($response['ACK']),
+            ['SUCCESS', 'SUCCESSWITHWARNING']
+        )) {
+            $payment = Payment::whereTradeNo($response['INVNUM'])->firstOrFail(
+            );
+            $data    = $this->getCheckoutData(
+                $payment->trade_no,
+                $payment->amount
+            );
+            // Perform transaction on PayPal
+            $payment_status = $this->provider->doExpressCheckoutPayment(
+                $data,
+                $token,
+                $PayerID
+            );
+            $status         = $payment_status['PAYMENTINFO_0_PAYMENTSTATUS'];
+
+            if ( ! strcasecmp($status, 'Completed') || ! strcasecmp(
+                    $status,
+                    'Processed'
+                )) {
+                Log::info(
+                    "Order $payment->order_id has been paid successfully!"
+                );
+                $payment->order->update(['status' => 1]);
+            } else {
+                Log::error(
+                    "Error processing PayPal payment for Order $payment->id!"
+                );
+            }
+        }
+
+        return redirect('/invoices');
+    }
+
+    public function notify($request): void
+    {
+        $request->merge(['cmd' => '_notify-validate']);
+        foreach ($request->input() as $key => $value) {
+            if ($value == null) {
+                $request->request->set($key, '');
+            }
+        }
+        $post = $request->all();
+
+        $response = (string)$this->provider->verifyIPN($post);
+
+        if ($response === 'VERIFIED' && $request['invoice']) {
+            $payment = Payment::whereTradeNo($request['invoice'])->first();
+            if ($payment && $payment->status == 0) {
+                $ret = $payment->order->update(['status' => 2]);
+                if ($ret) {
+                    exit('success');
+                }
+            }
+        }
+        exit("fail");
+    }
+
 }

+ 623 - 473
app/Http/Controllers/NodeController.php

@@ -24,477 +24,627 @@ use Session;
 use Str;
 use Validator;
 
-class NodeController extends Controller {
-	// 节点列表
-	public function nodeList(Request $request) {
-		$status = $request->input('status');
-
-		$query = Node::with(['onlineLogs', 'dailyDataFlows']);
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$nodeList = $query->orderByDesc('sort')->orderBy('id')->paginate(15)->appends($request->except('page'));
-		foreach($nodeList as $node){
-			// 在线人数
-			$online_log = $node->onlineLogs()
-			                   ->where('log_time', '>=', strtotime("-5 minutes"))
-			                   ->latest('log_time')
-			                   ->first();
-			$node->online_users = empty($online_log)? 0 : $online_log->online_user;
-
-			// 已产生流量
-			$node->transfer = flowAutoShow($node->dailyDataFlows()->sum('total'));
-
-			// 负载(10分钟以内)
-			$node_info = $node->heartBeats()->recently()->first();
-			$node->isOnline = empty($node_info) || empty($node_info->load)? 0 : 1;
-			$node->load = $node->isOnline? $node_info->load : '离线';
-			$node->uptime = empty($node_info)? 0 : seconds2time($node_info->uptime);
-		}
-
-		$view['nodeList'] = $nodeList;
-
-		return view('admin.node.nodeList', $view);
-	}
-
-	public function checkNode($id): JsonResponse {
-		$node = Node::find($id);
-		// 使用DDNS的node先获取ipv4地址
-		if($node->is_ddns){
-			$ip = gethostbyname($node->server);
-			if(strcmp($ip, $node->server) != 0){
-				$node->ip = $ip;
-			}else{
-				return Response::json(['status' => 'fail', 'title' => 'IP获取错误', 'message' => $node->name.'IP获取失败']);
-			}
-		}
-		$data[0] = NetworkDetection::networkCheck($node->ip, true); //ICMP
-		$data[1] = NetworkDetection::networkCheck($node->ip, false, $node->single? $node->port : null); //TCP
-
-		return Response::json(['status' => 'success', 'title' => '['.$node->name.']阻断信息', 'message' => $data]);
-	}
-
-	// 添加节点
-	public function addNode(Request $request) {
-		if($request->isMethod('POST')){
-			$validator = $this->nodeValidation($request);
-			if($validator){
-				return $validator;
-			}
-
-			// TODO:判断是否已存在绑定了相同域名的节点,提示是否要强制替换,或者不提示之前强制将其他节点的绑定域名置为空,然后发起域名绑定请求,或者请求进入队列
-			try{
-				DB::beginTransaction();
-
-				$node = new Node();
-				$node->type = $request->input('type');
-				$node->name = $request->input('name');
-				$node->country_code = $request->input('country_code');
-				$node->server = $request->input('server');
-				$node->ip = $request->input('ip');
-				$node->ipv6 = $request->input('ipv6');
-				$node->relay_server = $request->input('relay_server');
-				$node->relay_port = $request->input('relay_port');
-				$node->level = $request->input('level');
-				$node->speed_limit = (int) $request->input('speed_limit') * Mbps;
-				$node->client_limit = $request->input('client_limit');
-				$node->description = $request->input('description');
-				$node->method = $request->input('method');
-				$node->protocol = $request->input('protocol');
-				$node->protocol_param = $request->input('protocol_param');
-				$node->obfs = $request->input('obfs');
-				$node->obfs_param = $request->input('obfs_param');
-				$node->traffic_rate = $request->input('traffic_rate');
-				$node->is_subscribe = (int) $request->input('is_subscribe');
-				$node->is_ddns = (int) $request->input('is_ddns');
-				$node->is_relay = (int) $request->input('is_relay');
-				$node->is_udp = (int) $request->input('is_udp');
-				$node->push_port = $request->input('push_port');
-				$node->detection_type = $request->input('detection_type');
-				$node->compatible = (int) $request->input('compatible');
-				$node->single = (int) $request->input('single');
-				$node->port = $request->input('port');
-				$node->passwd = $request->input('passwd');
-				$node->sort = $request->input('sort');
-				$node->status = (int) $request->input('status');
-				$node->v2_alter_id = $request->input('v2_alter_id');
-				$node->v2_port = $request->input('v2_port');
-				$node->v2_method = $request->input('v2_method');
-				$node->v2_net = $request->input('v2_net');
-				$node->v2_type = $request->input('v2_type');
-				$node->v2_host = $request->input('v2_host')?: '';
-				$node->v2_path = $request->input('v2_path');
-				$node->v2_tls = (int) $request->input('v2_tls');
-				$node->tls_provider = $request->input('tls_provider');
-				$node->save();
-
-				DB::commit();
-				// 生成节点标签
-				(new NodeService())->makeLabels($node->id, $request->input('labels'));
-
-				return Response::json(['status' => 'success', 'message' => '添加成功']);
-			}catch(Exception $e){
-				DB::rollBack();
-				Log::error('添加节点信息异常:'.$e->getMessage());
-
-				return Response::json(['status' => 'fail', 'message' => '添加失败:'.$e->getMessage()]);
-			}
-		}else{
-			$view['methodList'] = Helpers::methodList();
-			$view['protocolList'] = Helpers::protocolList();
-			$view['obfsList'] = Helpers::obfsList();
-			$view['countryList'] = Country::orderBy('code')->get();
-			$view['levelList'] = Level::orderBy('level')->get();
-			$view['labelList'] = Label::orderByDesc('sort')->orderBy('id')->get();
-			$view['dvList'] = NodeCertificate::orderBy('id')->get();
-
-			return view('admin.node.nodeInfo', $view);
-		}
-	}
-
-	// 节点信息验证
-	private function nodeValidation(Request $request) {
-		if($request->input('server')){
-			$domain = $request->input('server');
-			$domain = explode('.', $domain);
-			$domainSuffix = end($domain); // 取得域名后缀
-
-			if(!in_array($domainSuffix, config('domains'), true)){
-				return Response::json(['status' => 'fail', 'message' => '绑定域名不合法']);
-			}
-		}
-
-		$validator = Validator::make($request->all(), [
-			'type'           => 'required|between:1,3',
-			'name'           => 'required',
-			'country_code'   => 'required',
-			'server'         => 'required_if:is_ddns,1',
-			'push_port'      => 'numeric|between:0,65535',
-			'traffic_rate'   => 'required|numeric|min:0',
-			'level'          => 'required|numeric|between:0,255',
-			'speed_limit'    => 'required|numeric|min:0',
-			'client_limit'   => 'required|numeric|min:0',
-			'port'           => 'nullable|numeric|between:0,65535',
-			'ip'             => 'ipv4',
-			'ipv6'           => 'nullable|ipv6',
-			'relay_server'   => 'required_if:is_relay,1',
-			'relay_port'     => 'required_if:is_relay,1|numeric|between:0,65535',
-			'method'         => 'required_if:type,1',
-			'protocol'       => 'required_if:type,1',
-			'obfs'           => 'required_if:type,1',
-			'is_subscribe'   => 'boolean',
-			'is_ddns'        => 'boolean',
-			'is_relay'       => 'boolean',
-			'is_udp'         => 'boolean',
-			'detection_type' => 'between:0,3',
-			'compatible'     => 'boolean',
-			'single'         => 'boolean',
-			'sort'           => 'required|numeric|between:0,255',
-			'status'         => 'boolean',
-			'v2_alter_id'    => 'required_if:type,2|numeric|between:0,65535',
-			'v2_port'        => 'required_if:type,2|numeric|between:0,65535',
-			'v2_method'      => 'required_if:type,2',
-			'v2_net'         => 'required_if:type,2',
-			'v2_type'        => 'required_if:type,2',
-			'v2_tls'         => 'boolean'
-		], [
-			'server.required_unless' => '开启DDNS, 域名不能为空',
-		]);
-
-		if($validator->fails()){
-			return Response::json(['status' => 'fail', 'message' => $validator->errors()->all()]);
-		}
-		return false;
-	}
-
-	// 刷新节点地理位置
-	public function refreshGeo(Request $request): JsonResponse {
-		if((new NodeService())->getNodeGeo($request->input('id', 0))){
-			return Response::json(['status' => 'success', 'message' => '获取地理位置更新成功!']);
-		}
-		return Response::json(['status' => 'fail', 'message' => '获取地理位置更新失败!']);
-	}
-
-	// 重载节点
-	public function reload($id): JsonResponse {
-		if(reloadNode::dispatchNow(Node::whereId($id)->get())){
-			return Response::json(['status' => 'success', 'message' => '重载成功!']);
-		}
-		return Response::json(['status' => 'fail', 'message' => '重载失败!']);
-	}
-
-	// 编辑节点
-	public function editNode(Request $request) {
-		$id = $request->input('id');
-
-		if($request->isMethod('POST')){
-			$validator = $this->nodeValidation($request);
-			if($validator){
-				return $validator;
-			}
-
-			$node = Node::find($id);
-
-			try{
-				DB::beginTransaction();
-				// 生成节点标签
-				(new NodeService())->makeLabels($node->id, $request->input('labels'));
-
-				$node->update([
-					'type'           => $request->input('type'),
-					'name'           => $request->input('name'),
-					'country_code'   => $request->input('country_code'),
-					'server'         => $request->input('server'),
-					'ip'             => $request->input('ip'),
-					'ipv6'           => $request->input('ipv6'),
-					'relay_server'   => $request->input('relay_server'),
-					'relay_port'     => $request->input('relay_port'),
-					'level'          => $request->input('level'),
-					'speed_limit'    => (int) $request->input('speed_limit') * Mbps,
-					'client_limit'   => $request->input('client_limit'),
-					'description'    => $request->input('description'),
-					'method'         => $request->input('method'),
-					'protocol'       => $request->input('protocol'),
-					'protocol_param' => $request->input('protocol_param'),
-					'obfs'           => $request->input('obfs'),
-					'obfs_param'     => $request->input('obfs_param'),
-					'traffic_rate'   => $request->input('traffic_rate'),
-					'is_subscribe'   => (int) $request->input('is_subscribe'),
-					'is_ddns'        => (int) $request->input('is_ddns'),
-					'is_relay'       => (int) $request->input('is_relay'),
-					'is_udp'         => (int) $request->input('is_udp'),
-					'push_port'      => $request->input('push_port'),
-					'detection_type' => $request->input('detection_type'),
-					'compatible'     => (int) $request->input('compatible'),
-					'single'         => (int) $request->input('single'),
-					'port'           => $request->input('port'),
-					'passwd'         => $request->input('passwd'),
-					'sort'           => $request->input('sort'),
-					'status'         => (int) $request->input('status'),
-					'v2_alter_id'    => $request->input('v2_alter_id'),
-					'v2_port'        => $request->input('v2_port'),
-					'v2_method'      => $request->input('v2_method'),
-					'v2_net'         => $request->input('v2_net'),
-					'v2_type'        => $request->input('v2_type'),
-					'v2_host'        => $request->input('v2_host')?: '',
-					'v2_path'        => $request->input('v2_path'),
-					'v2_tls'         => (int) $request->input('v2_tls'),
-					'tls_provider'   => $request->input('tls_provider')
-				]);
-				// TODO:更新节点绑定的域名DNS(将节点IP更新到域名DNS 的A记录)
-
-				DB::commit();
-
-				return Response::json(['status' => 'success', 'message' => '编辑成功']);
-			}catch(Exception $e){
-				DB::rollBack();
-				Log::error('编辑节点信息异常:'.$e->getMessage());
-
-				return Response::json(['status' => 'fail', 'message' => '编辑失败:'.$e->getMessage()]);
-			}
-		}
-
-		$view['node'] = Node::with('labels')->find($id);
-		$view['methodList'] = Helpers::methodList();
-		$view['protocolList'] = Helpers::protocolList();
-		$view['obfsList'] = Helpers::obfsList();
-		$view['countryList'] = Country::orderBy('code')->get();
-		$view['levelList'] = Level::orderBy('level')->get();
-		$view['labelList'] = Label::orderByDesc('sort')->orderBy('id')->get();
-		$view['dvList'] = NodeCertificate::orderBy('id')->get();
-
-		return view('admin.node.nodeInfo', $view);
-	}
-
-	// 删除节点
-	public function delNode(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		$node = Node::find($id);
-		if(!$node){
-			return Response::json(['status' => 'fail', 'message' => '节点不存在,请重试']);
-		}
-
-		try{
-			DB::beginTransaction();
-			$node->delete();
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '删除成功']);
-		}catch(Exception $e){
-			DB::rollBack();
-			Log::error('删除节点信息异常:'.$e->getMessage());
-
-			return Response::json(['status' => 'fail', 'message' => '删除失败:'.$e->getMessage()]);
-		}
-	}
-
-	// 节点流量监控
-	public function nodeMonitor(Request $request) {
-		$node = Node::find($request->input('id'));
-		if(!$node){
-			Session::flash('errorMsg', '节点不存在,请重试');
-
-			return Redirect::back();
-		}
-
-		$view['nodeName'] = $node->name;
-		$view['nodeServer'] = $node->server;
-		$view = array_merge($view, $this->DataFlowChart($node->id, 1));
-
-		return view('admin.node.nodeMonitor', $view);
-	}
-
-	// Ping节点延迟
-	public function pingNode($id): ?JsonResponse {
-		$node = Node::find($id);
-		if(!$node){
-			return Response::json(['status' => 'fail', 'message' => '节点不存在,请重试']);
-		}
-
-		$result = NetworkDetection::ping($node->is_ddns? $node->server : $node->ip);
-
-		if($result){
-			return Response::json([
-				'status'  => 'success',
-				'message' => [
-					$result['telecom']['time']?: '无',//电信
-					$result['Unicom']['time']?: '无',// 联通
-					$result['move']['time']?: '无',// 移动
-					$result['HongKong']['time']?: '无'// 香港
-				]
-			]);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => 'Ping访问失败']);
-	}
-
-	// Ping节点延迟日志
-	public function pingLog(Request $request) {
-		$node_id = $request->input('nodeId');
-		$query = NodePing::query();
-		if(isset($node_id)){
-			$query->whereNodeId($node_id);
-		}
-
-		$view['nodeList'] = Node::orderBy('id')->get();
-		$view['pingLogs'] = $query->latest()->paginate(15)->appends($request->except('page'));
-
-		return view('admin.logs.nodePingLog', $view);
-	}
-
-	// 节点授权列表
-	public function authList(Request $request) {
-		$view['list'] = NodeAuth::orderBy('node_id')->paginate(15)->appends($request->except('page'));
-		return view('admin.node.authList', $view);
-	}
-
-	// 添加节点授权
-	public function addAuth(): JsonResponse {
-		$nodeArray = Node::whereStatus(1)->orderBy('id')->pluck('id')->toArray();
-		$authArray = NodeAuth::orderBy('id')->pluck('node_id')->toArray();
-
-		if($nodeArray == $authArray){
-			return Response::json(['status' => 'success', 'message' => '没有需要生成授权的节点']);
-		}
-
-		foreach(array_diff($nodeArray, $authArray) as $nodeId){
-			$obj = new NodeAuth();
-			$obj->node_id = $nodeId;
-			$obj->key = Str::random();
-			$obj->secret = Str::random(8);
-			$obj->save();
-		}
-		return Response::json(['status' => 'success', 'message' => '生成成功']);
-	}
-
-	// 删除节点授权
-	public function delAuth(Request $request): JsonResponse {
-		try{
-			NodeAuth::whereId($request->input('id'))->delete();
-		}catch(Exception $e){
-			return Response::json(['status' => 'fail', 'message' => '错误:'.var_export($e, true)]);
-		}
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
-
-	// 重置节点授权
-	public function refreshAuth(Request $request): ?JsonResponse {
-		$ret = NodeAuth::whereId($request->input('id'))->update([
-			'key'    => Str::random(),
-			'secret' => Str::random(8)
-		]);
-		if($ret){
-			return Response::json(['status' => 'success', 'message' => '操作成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '操作失败']);
-	}
-
-	// 域名证书列表
-	public function certificateList(Request $request) {
-		$DvList = NodeCertificate::orderBy('id')->paginate(15)->appends($request->except('page'));
-		foreach($DvList as $Dv){
-			if($Dv->key && $Dv->pem){
-				$DvInfo = openssl_x509_parse($Dv->pem);
-				$Dv->issuer = $DvInfo['issuer']['O'];
-				$Dv->from = $DvInfo['validFrom_time_t']? date('Y-m-d', $DvInfo['validFrom_time_t']) : null;
-				$Dv->to = $DvInfo['validTo']? date('Y-m-d', $DvInfo['validTo_time_t']) : null;
-			}
-		}
-		$view['list'] = $DvList;
-		return view('admin.node.certificateList', $view);
-	}
-
-	// 添加域名证书
-	public function addCertificate(Request $request) {
-		if($request->isMethod('POST')){
-			$obj = new NodeCertificate();
-			$obj->domain = $request->input('domain');
-			$obj->key = str_replace(["\r", "\n"], '', $request->input('key'));
-			$obj->pem = str_replace(["\r", "\n"], '', $request->input('pem'));
-			$obj->save();
-
-			if($obj->id){
-				return Response::json(['status' => 'success', 'message' => '生成成功']);
-			}
-
-			return Response::json(['status' => 'fail', 'message' => '生成失败']);
-		}
-
-		return view('admin.node.certificateInfo');
-	}
-
-	// 编辑域名证书
-	public function editCertificate(Request $request) {
-		$Dv = NodeCertificate::find($request->input('id'));
-		if($request->isMethod('POST')){
-			if($Dv){
-				$ret = NodeCertificate::whereId($Dv->id)->update([
-					'domain' => $request->input('domain'),
-					'key'    => $request->input('key'),
-					'pem'    => $request->input('pem')
-				]);
-				if($ret){
-					return Response::json(['status' => 'success', 'message' => '修改成功']);
-				}
-			}
-			return Response::json(['status' => 'fail', 'message' => '修改失败']);
-		}
-
-		$view['Dv'] = $Dv;
-		return view('admin.node.certificateInfo', $view);
-	}
-
-	// 删除域名证书
-	public function delCertificate(Request $request): JsonResponse {
-		try{
-			NodeCertificate::whereId($request->input('id'))->delete();
-		}catch(Exception $e){
-			return Response::json(['status' => 'fail', 'message' => '错误:'.var_export($e, true)]);
-		}
-		return Response::json(['status' => 'success', 'message' => '操作成功']);
-	}
+class NodeController extends Controller
+{
+
+    // 节点列表
+    public function nodeList(Request $request)
+    {
+        $status = $request->input('status');
+
+        $query = Node::with(['onlineLogs', 'dailyDataFlows']);
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $nodeList = $query->orderByDesc('sort')
+                          ->orderBy('id')
+                          ->paginate(15)
+                          ->appends($request->except('page'));
+        foreach ($nodeList as $node) {
+            // 在线人数
+            $online_log         = $node->onlineLogs()
+                                       ->where(
+                                           'log_time',
+                                           '>=',
+                                           strtotime("-5 minutes")
+                                       )
+                                       ->latest('log_time')
+                                       ->first();
+            $node->online_users = empty($online_log) ? 0 : $online_log->online_user;
+
+            // 已产生流量
+            $node->transfer = flowAutoShow(
+                $node->dailyDataFlows()->sum('total')
+            );
+
+            // 负载(10分钟以内)
+            $node_info      = $node->heartBeats()->recently()->first();
+            $node->isOnline = empty($node_info) || empty($node_info->load) ? 0 : 1;
+            $node->load     = $node->isOnline ? $node_info->load : '离线';
+            $node->uptime   = empty($node_info) ? 0 : seconds2time(
+                $node_info->uptime
+            );
+        }
+
+        $view['nodeList'] = $nodeList;
+
+        return view('admin.node.nodeList', $view);
+    }
+
+    public function checkNode($id): JsonResponse
+    {
+        $node = Node::find($id);
+        // 使用DDNS的node先获取ipv4地址
+        if ($node->is_ddns) {
+            $ip = gethostbyname($node->server);
+            if (strcmp($ip, $node->server) != 0) {
+                $node->ip = $ip;
+            } else {
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'title'   => 'IP获取错误',
+                        'message' => $node->name . 'IP获取失败',
+                    ]
+                );
+            }
+        }
+        $data[0] = NetworkDetection::networkCheck($node->ip, true); //ICMP
+        $data[1] = NetworkDetection::networkCheck(
+            $node->ip,
+            false,
+            $node->single ? $node->port : null
+        ); //TCP
+
+        return Response::json(
+            [
+                'status'  => 'success',
+                'title'   => '[' . $node->name . ']阻断信息',
+                'message' => $data,
+            ]
+        );
+    }
+
+    // 添加节点
+    public function addNode(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $validator = $this->nodeValidation($request);
+            if ($validator) {
+                return $validator;
+            }
+
+            // TODO:判断是否已存在绑定了相同域名的节点,提示是否要强制替换,或者不提示之前强制将其他节点的绑定域名置为空,然后发起域名绑定请求,或者请求进入队列
+            try {
+                DB::beginTransaction();
+
+                $node                 = new Node();
+                $node->type           = $request->input('type');
+                $node->name           = $request->input('name');
+                $node->country_code   = $request->input('country_code');
+                $node->server         = $request->input('server');
+                $node->ip             = $request->input('ip');
+                $node->ipv6           = $request->input('ipv6');
+                $node->relay_server   = $request->input('relay_server');
+                $node->relay_port     = $request->input('relay_port');
+                $node->level          = $request->input('level');
+                $node->speed_limit    = (int)$request->input(
+                        'speed_limit'
+                    ) * Mbps;
+                $node->client_limit   = $request->input('client_limit');
+                $node->description    = $request->input('description');
+                $node->method         = $request->input('method');
+                $node->protocol       = $request->input('protocol');
+                $node->protocol_param = $request->input('protocol_param');
+                $node->obfs           = $request->input('obfs');
+                $node->obfs_param     = $request->input('obfs_param');
+                $node->traffic_rate   = $request->input('traffic_rate');
+                $node->is_subscribe   = (int)$request->input('is_subscribe');
+                $node->is_ddns        = (int)$request->input('is_ddns');
+                $node->is_relay       = (int)$request->input('is_relay');
+                $node->is_udp         = (int)$request->input('is_udp');
+                $node->push_port      = $request->input('push_port');
+                $node->detection_type = $request->input('detection_type');
+                $node->compatible     = (int)$request->input('compatible');
+                $node->single         = (int)$request->input('single');
+                $node->port           = $request->input('port');
+                $node->passwd         = $request->input('passwd');
+                $node->sort           = $request->input('sort');
+                $node->status         = (int)$request->input('status');
+                $node->v2_alter_id    = $request->input('v2_alter_id');
+                $node->v2_port        = $request->input('v2_port');
+                $node->v2_method      = $request->input('v2_method');
+                $node->v2_net         = $request->input('v2_net');
+                $node->v2_type        = $request->input('v2_type');
+                $node->v2_host        = $request->input('v2_host') ?: '';
+                $node->v2_path        = $request->input('v2_path');
+                $node->v2_tls         = (int)$request->input('v2_tls');
+                $node->tls_provider   = $request->input('tls_provider');
+                $node->save();
+
+                DB::commit();
+                // 生成节点标签
+                (new NodeService())->makeLabels(
+                    $node->id,
+                    $request->input('labels')
+                );
+
+                return Response::json(
+                    ['status' => 'success', 'message' => '添加成功']
+                );
+            } catch (Exception $e) {
+                DB::rollBack();
+                Log::error('添加节点信息异常:' . $e->getMessage());
+
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => '添加失败:' . $e->getMessage(),
+                    ]
+                );
+            }
+        } else {
+            $view['methodList']   = Helpers::methodList();
+            $view['protocolList'] = Helpers::protocolList();
+            $view['obfsList']     = Helpers::obfsList();
+            $view['countryList']  = Country::orderBy('code')->get();
+            $view['levelList']    = Level::orderBy('level')->get();
+            $view['labelList']    = Label::orderByDesc('sort')
+                                         ->orderBy('id')
+                                         ->get();
+            $view['dvList']       = NodeCertificate::orderBy('id')->get();
+
+            return view('admin.node.nodeInfo', $view);
+        }
+    }
+
+    // 节点信息验证
+    private function nodeValidation(Request $request)
+    {
+        if ($request->input('server')) {
+            $domain       = $request->input('server');
+            $domain       = explode('.', $domain);
+            $domainSuffix = end($domain); // 取得域名后缀
+
+            if ( ! in_array($domainSuffix, config('domains'), true)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '绑定域名不合法']
+                );
+            }
+        }
+
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'type'           => 'required|between:1,3',
+                'name'           => 'required',
+                'country_code'   => 'required',
+                'server'         => 'required_if:is_ddns,1',
+                'push_port'      => 'numeric|between:0,65535',
+                'traffic_rate'   => 'required|numeric|min:0',
+                'level'          => 'required|numeric|between:0,255',
+                'speed_limit'    => 'required|numeric|min:0',
+                'client_limit'   => 'required|numeric|min:0',
+                'port'           => 'nullable|numeric|between:0,65535',
+                'ip'             => 'ipv4',
+                'ipv6'           => 'nullable|ipv6',
+                'relay_server'   => 'required_if:is_relay,1',
+                'relay_port'     => 'required_if:is_relay,1|numeric|between:0,65535',
+                'method'         => 'required_if:type,1',
+                'protocol'       => 'required_if:type,1',
+                'obfs'           => 'required_if:type,1',
+                'is_subscribe'   => 'boolean',
+                'is_ddns'        => 'boolean',
+                'is_relay'       => 'boolean',
+                'is_udp'         => 'boolean',
+                'detection_type' => 'between:0,3',
+                'compatible'     => 'boolean',
+                'single'         => 'boolean',
+                'sort'           => 'required|numeric|between:0,255',
+                'status'         => 'boolean',
+                'v2_alter_id'    => 'required_if:type,2|numeric|between:0,65535',
+                'v2_port'        => 'required_if:type,2|numeric|between:0,65535',
+                'v2_method'      => 'required_if:type,2',
+                'v2_net'         => 'required_if:type,2',
+                'v2_type'        => 'required_if:type,2',
+                'v2_tls'         => 'boolean',
+            ],
+            [
+                'server.required_unless' => '开启DDNS, 域名不能为空',
+            ]
+        );
+
+        if ($validator->fails()) {
+            return Response::json(
+                ['status' => 'fail', 'message' => $validator->errors()->all()]
+            );
+        }
+
+        return false;
+    }
+
+    // 刷新节点地理位置
+    public function refreshGeo(Request $request): JsonResponse
+    {
+        if ((new NodeService())->getNodeGeo($request->input('id', 0))) {
+            return Response::json(
+                ['status' => 'success', 'message' => '获取地理位置更新成功!']
+            );
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '获取地理位置更新失败!']);
+    }
+
+    // 重载节点
+    public function reload($id): JsonResponse
+    {
+        if (reloadNode::dispatchNow(Node::whereId($id)->get())) {
+            return Response::json(
+                ['status' => 'success', 'message' => '重载成功!']
+            );
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '重载失败!']);
+    }
+
+    // 编辑节点
+    public function editNode(Request $request)
+    {
+        $id = $request->input('id');
+
+        if ($request->isMethod('POST')) {
+            $validator = $this->nodeValidation($request);
+            if ($validator) {
+                return $validator;
+            }
+
+            $node = Node::find($id);
+
+            try {
+                DB::beginTransaction();
+                // 生成节点标签
+                (new NodeService())->makeLabels(
+                    $node->id,
+                    $request->input('labels')
+                );
+
+                $node->update(
+                    [
+                        'type'           => $request->input('type'),
+                        'name'           => $request->input('name'),
+                        'country_code'   => $request->input('country_code'),
+                        'server'         => $request->input('server'),
+                        'ip'             => $request->input('ip'),
+                        'ipv6'           => $request->input('ipv6'),
+                        'relay_server'   => $request->input('relay_server'),
+                        'relay_port'     => $request->input('relay_port'),
+                        'level'          => $request->input('level'),
+                        'speed_limit'    => (int)$request->input(
+                                'speed_limit'
+                            ) * Mbps,
+                        'client_limit'   => $request->input('client_limit'),
+                        'description'    => $request->input('description'),
+                        'method'         => $request->input('method'),
+                        'protocol'       => $request->input('protocol'),
+                        'protocol_param' => $request->input('protocol_param'),
+                        'obfs'           => $request->input('obfs'),
+                        'obfs_param'     => $request->input('obfs_param'),
+                        'traffic_rate'   => $request->input('traffic_rate'),
+                        'is_subscribe'   => (int)$request->input(
+                            'is_subscribe'
+                        ),
+                        'is_ddns'        => (int)$request->input('is_ddns'),
+                        'is_relay'       => (int)$request->input('is_relay'),
+                        'is_udp'         => (int)$request->input('is_udp'),
+                        'push_port'      => $request->input('push_port'),
+                        'detection_type' => $request->input('detection_type'),
+                        'compatible'     => (int)$request->input('compatible'),
+                        'single'         => (int)$request->input('single'),
+                        'port'           => $request->input('port'),
+                        'passwd'         => $request->input('passwd'),
+                        'sort'           => $request->input('sort'),
+                        'status'         => (int)$request->input('status'),
+                        'v2_alter_id'    => $request->input('v2_alter_id'),
+                        'v2_port'        => $request->input('v2_port'),
+                        'v2_method'      => $request->input('v2_method'),
+                        'v2_net'         => $request->input('v2_net'),
+                        'v2_type'        => $request->input('v2_type'),
+                        'v2_host'        => $request->input('v2_host') ?: '',
+                        'v2_path'        => $request->input('v2_path'),
+                        'v2_tls'         => (int)$request->input('v2_tls'),
+                        'tls_provider'   => $request->input('tls_provider'),
+                    ]
+                );
+                // TODO:更新节点绑定的域名DNS(将节点IP更新到域名DNS 的A记录)
+
+                DB::commit();
+
+                return Response::json(
+                    ['status' => 'success', 'message' => '编辑成功']
+                );
+            } catch (Exception $e) {
+                DB::rollBack();
+                Log::error('编辑节点信息异常:' . $e->getMessage());
+
+                return Response::json(
+                    [
+                        'status'  => 'fail',
+                        'message' => '编辑失败:' . $e->getMessage(),
+                    ]
+                );
+            }
+        }
+
+        $view['node']         = Node::with('labels')->find($id);
+        $view['methodList']   = Helpers::methodList();
+        $view['protocolList'] = Helpers::protocolList();
+        $view['obfsList']     = Helpers::obfsList();
+        $view['countryList']  = Country::orderBy('code')->get();
+        $view['levelList']    = Level::orderBy('level')->get();
+        $view['labelList']    = Label::orderByDesc('sort')->orderBy('id')->get(
+        );
+        $view['dvList']       = NodeCertificate::orderBy('id')->get();
+
+        return view('admin.node.nodeInfo', $view);
+    }
+
+    // 删除节点
+    public function delNode(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        $node = Node::find($id);
+        if ( ! $node) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '节点不存在,请重试']
+            );
+        }
+
+        try {
+            DB::beginTransaction();
+            $node->delete();
+            DB::commit();
+
+            return Response::json(['status' => 'success', 'message' => '删除成功']);
+        } catch (Exception $e) {
+            DB::rollBack();
+            Log::error('删除节点信息异常:' . $e->getMessage());
+
+            return Response::json(
+                ['status' => 'fail', 'message' => '删除失败:' . $e->getMessage()]
+            );
+        }
+    }
+
+    // 节点流量监控
+    public function nodeMonitor(Request $request)
+    {
+        $node = Node::find($request->input('id'));
+        if ( ! $node) {
+            Session::flash('errorMsg', '节点不存在,请重试');
+
+            return Redirect::back();
+        }
+
+        $view['nodeName']   = $node->name;
+        $view['nodeServer'] = $node->server;
+        $view               = array_merge(
+            $view,
+            $this->DataFlowChart($node->id, 1)
+        );
+
+        return view('admin.node.nodeMonitor', $view);
+    }
+
+    // Ping节点延迟
+    public function pingNode($id): ?JsonResponse
+    {
+        $node = Node::find($id);
+        if ( ! $node) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '节点不存在,请重试']
+            );
+        }
+
+        $result = NetworkDetection::ping(
+            $node->is_ddns ? $node->server : $node->ip
+        );
+
+        if ($result) {
+            return Response::json(
+                [
+                    'status'  => 'success',
+                    'message' => [
+                        $result['telecom']['time'] ?: '无',//电信
+                        $result['Unicom']['time'] ?: '无',// 联通
+                        $result['move']['time'] ?: '无',// 移动
+                        $result['HongKong']['time'] ?: '无'// 香港
+                    ],
+                ]
+            );
+        }
+
+        return Response::json(['status' => 'fail', 'message' => 'Ping访问失败']);
+    }
+
+    // Ping节点延迟日志
+    public function pingLog(Request $request)
+    {
+        $node_id = $request->input('nodeId');
+        $query   = NodePing::query();
+        if (isset($node_id)) {
+            $query->whereNodeId($node_id);
+        }
+
+        $view['nodeList'] = Node::orderBy('id')->get();
+        $view['pingLogs'] = $query->latest()->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.logs.nodePingLog', $view);
+    }
+
+    // 节点授权列表
+    public function authList(Request $request)
+    {
+        $view['list'] = NodeAuth::orderBy('node_id')->paginate(15)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.node.authList', $view);
+    }
+
+    // 添加节点授权
+    public function addAuth(): JsonResponse
+    {
+        $nodeArray = Node::whereStatus(1)->orderBy('id')->pluck('id')->toArray(
+        );
+        $authArray = NodeAuth::orderBy('id')->pluck('node_id')->toArray();
+
+        if ($nodeArray == $authArray) {
+            return Response::json(
+                ['status' => 'success', 'message' => '没有需要生成授权的节点']
+            );
+        }
+
+        foreach (array_diff($nodeArray, $authArray) as $nodeId) {
+            $obj          = new NodeAuth();
+            $obj->node_id = $nodeId;
+            $obj->key     = Str::random();
+            $obj->secret  = Str::random(8);
+            $obj->save();
+        }
+
+        return Response::json(['status' => 'success', 'message' => '生成成功']);
+    }
+
+    // 删除节点授权
+    public function delAuth(Request $request): JsonResponse
+    {
+        try {
+            NodeAuth::whereId($request->input('id'))->delete();
+        } catch (Exception $e) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '错误:' . var_export($e, true)]
+            );
+        }
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
+    // 重置节点授权
+    public function refreshAuth(Request $request): ?JsonResponse
+    {
+        $ret = NodeAuth::whereId($request->input('id'))->update(
+            [
+                'key'    => Str::random(),
+                'secret' => Str::random(8),
+            ]
+        );
+        if ($ret) {
+            return Response::json(['status' => 'success', 'message' => '操作成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '操作失败']);
+    }
+
+    // 域名证书列表
+    public function certificateList(Request $request)
+    {
+        $DvList = NodeCertificate::orderBy('id')->paginate(15)->appends(
+            $request->except('page')
+        );
+        foreach ($DvList as $Dv) {
+            if ($Dv->key && $Dv->pem) {
+                $DvInfo     = openssl_x509_parse($Dv->pem);
+                $Dv->issuer = $DvInfo['issuer']['O'];
+                $Dv->from   = $DvInfo['validFrom_time_t'] ? date(
+                    'Y-m-d',
+                    $DvInfo['validFrom_time_t']
+                ) : null;
+                $Dv->to     = $DvInfo['validTo'] ? date(
+                    'Y-m-d',
+                    $DvInfo['validTo_time_t']
+                ) : null;
+            }
+        }
+        $view['list'] = $DvList;
+
+        return view('admin.node.certificateList', $view);
+    }
+
+    // 添加域名证书
+    public function addCertificate(Request $request)
+    {
+        if ($request->isMethod('POST')) {
+            $obj         = new NodeCertificate();
+            $obj->domain = $request->input('domain');
+            $obj->key    = str_replace(
+                ["\r", "\n"],
+                '',
+                $request->input('key')
+            );
+            $obj->pem    = str_replace(
+                ["\r", "\n"],
+                '',
+                $request->input('pem')
+            );
+            $obj->save();
+
+            if ($obj->id) {
+                return Response::json(
+                    ['status' => 'success', 'message' => '生成成功']
+                );
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '生成失败']);
+        }
+
+        return view('admin.node.certificateInfo');
+    }
+
+    // 编辑域名证书
+    public function editCertificate(Request $request)
+    {
+        $Dv = NodeCertificate::find($request->input('id'));
+        if ($request->isMethod('POST')) {
+            if ($Dv) {
+                $ret = NodeCertificate::whereId($Dv->id)->update(
+                    [
+                        'domain' => $request->input('domain'),
+                        'key'    => $request->input('key'),
+                        'pem'    => $request->input('pem'),
+                    ]
+                );
+                if ($ret) {
+                    return Response::json(
+                        ['status' => 'success', 'message' => '修改成功']
+                    );
+                }
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '修改失败']);
+        }
+
+        $view['Dv'] = $Dv;
+
+        return view('admin.node.certificateInfo', $view);
+    }
+
+    // 删除域名证书
+    public function delCertificate(Request $request): JsonResponse
+    {
+        try {
+            NodeCertificate::whereId($request->input('id'))->delete();
+        } catch (Exception $e) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '错误:' . var_export($e, true)]
+            );
+        }
+
+        return Response::json(['status' => 'success', 'message' => '操作成功']);
+    }
+
 }

+ 263 - 201
app/Http/Controllers/PaymentController.php

@@ -28,205 +28,267 @@ use Response;
  *
  * @package App\Http\Controllers
  */
-class PaymentController extends Controller {
-	private static $method;
-
-	public static function notify(Request $request): int {
-		self::$method = $request->input('method');
-
-		Log::info(self::$method."回调接口[POST]:".self::$method.var_export($request->all(), true));
-		self::getClient()->notify($request);
-
-		return 0;
-	}
-
-	public static function getClient() {
-		switch(self::$method){
-			case 'credit':
-				return new Local();
-			case 'f2fpay':
-				return new F2Fpay();
-			case 'codepay':
-				return new Codepay();
-			case 'payjs':
-				return new PayJs();
-			case 'bitpayx':
-				return new BitpayX();
-			case 'paypal':
-				return new PayPal();
-			case 'epay':
-				return new EPay();
-			default:
-				Log::error("未知支付:".self::$method);
-
-				return false;
-		}
-	}
-
-	public static function getStatus(Request $request): JsonResponse {
-		$payment = Payment::whereTradeNo($request->input('trade_no'))->first();
-		if($payment){
-			if($payment->status == 1){
-				return Response::json(['status' => 'success', 'message' => '支付成功']);
-			}
-
-			if($payment->status == -1){
-				return Response::json(['status' => 'error', 'message' => '订单超时未支付,已自动关闭']);
-			}
-
-			return Response::json(['status' => 'fail', 'message' => '等待支付']);
-		}
-
-		return Response::json(['status' => 'error', 'message' => '未知订单']);
-	}
-
-	// 创建支付订单
-	public function purchase(Request $request) {
-		$goods_id = $request->input('goods_id');
-		$coupon_sn = $request->input('coupon_sn');
-		self::$method = $request->input('method');
-		$credit = $request->input('amount');
-		$pay_type = $request->input('pay_type');
-		$amount = 0;
-
-		$goods = Goods::find($goods_id);
-		// 充值余额
-		if($credit){
-			if(!is_numeric($credit) || $credit <= 0){
-				return Response::json(['status' => 'fail', 'message' => '充值余额不合规']);
-			}
-			$amount = $credit;
-			// 购买服务
-		}elseif($goods_id && self::$method){
-			if(!$goods || !$goods->status){
-				return Response::json(['status' => 'fail', 'message' => '订单创建失败:商品已下架']);
-			}
-			$amount = $goods->price;
-
-			// 是否有生效的套餐
-			$activePlan = Order::userActivePlan()->doesntExist();
-
-			// 无生效套餐,禁止购买加油包
-			if($goods->type == 1 && $activePlan){
-				return Response::json(['status' => 'fail', 'message' => '购买加油包前,请先购买套餐']);
-			}
-
-			//非余额付款下,检查在线支付是否开启
-			if(self::$method !== 'credit'){
-				// 判断是否开启在线支付
-				if(!sysConfig('is_onlinePay')){
-					return Response::json(['status' => 'fail', 'message' => '订单创建失败:系统并未开启在线支付功能']);
-				}
-
-				// 判断是否存在同个商品的未支付订单
-				if(Order::uid()->whereStatus(0)->exists()){
-					return Response::json(['status' => 'fail', 'message' => '订单创建失败:尚有未支付的订单,请先去支付']);
-				}
-			}elseif(Auth::getUser()->credit < $amount){ // 验证账号余额是否充足
-				return Response::json(['status' => 'fail', 'message' => '您的余额不足,请先充值']);
-			}
-
-			// 单个商品限购
-			if($goods->limit_num){
-				$count = Order::uid()->where('status', '>=', 0)->whereGoodsId($goods_id)->count();
-				if($count >= $goods->limit_num){
-					return Response::json([
-						'status'  => 'fail',
-						'message' => '此商品限购'.$goods->limit_num.'次,您已购买'.$count.'次'
-					]);
-				}
-			}
-
-			// 使用优惠券 TODO 代码整合至 CouponService
-			if($coupon_sn){
-				$coupon = Coupon::whereStatus(0)->whereIn('type', [1, 2])->whereSn($coupon_sn)->first();
-				if(!$coupon){
-					return Response::json(['status' => 'fail', 'message' => '订单创建失败:优惠券不存在']);
-				}
-
-				// 计算实际应支付总价
-				$amount = $coupon->type == 2? $goods->price * $coupon->value / 100 : $goods->price - $coupon->value;
-				$amount = $amount > 0? round($amount, 2) : 0; // 四舍五入保留2位小数,避免无法正常创建订单
-			}
-
-			// 价格异常判断
-			if($amount < 0){
-				return Response::json(['status' => 'fail', 'message' => '订单创建失败:订单总价异常']);
-			}
-
-			if($amount == 0 && self::$method !== 'credit'){
-				return Response::json(['status' => 'fail', 'message' => '订单创建失败:订单总价为0,无需使用在线支付']);
-			}
-		}
-
-		$orderSn = date('ymdHis').random_int(100000, 999999);
-
-		// 生成订单
-		$order = new Order();
-		$order->order_sn = $orderSn;
-		$order->user_id = Auth::id();
-		$order->goods_id = $credit? 0 : $goods_id;
-		$order->coupon_id = !empty($coupon)? $coupon->id : 0;
-		$order->origin_amount = $credit?: $goods->price;
-		$order->amount = $amount;
-		$order->is_expire = 0;
-		$order->pay_type = $pay_type;
-		$order->pay_way = self::$method;
-		$order->status = 0;
-		$order->save();
-
-		// 使用优惠券,减少可使用次数
-		if(!empty($coupon)){
-			if($coupon->usable_times > 0){
-				Coupon::whereId($coupon->id)->decrement('usable_times', 1);
-			}
-
-			Helpers::addCouponLog('订单支付使用', $coupon->id, $goods_id, $order->id);
-		}
-
-		$request->merge(['id' => $order->id, 'type' => $pay_type, 'amount' => $amount]);
-
-		// 生成支付单
-		return self::getClient()->purchase($request);
-	}
-
-	public function close(Request $request): JsonResponse {
-		$order = Order::find($request->input('id'));
-		if($order){
-			if(!$order->update(['status' => -1])){
-				return Response::json(['status' => 'fail', 'message' => '关闭订单失败']);
-			}
-		}else{
-			return Response::json(['status' => 'fail', 'message' => '未找到订单']);
-		}
-		return Response::json(['status' => 'success', 'message' => '关闭订单成功']);
-	}
-
-	// 支付单详情
-	public function detail($trade_no) {
-		$payment = Payment::uid()->with(['order', 'order.goods'])->whereTradeNo($trade_no)->firstOrFail();
-		$view['payment'] = $payment;
-		$goods = $payment->order->goods;
-		$view['name'] = $goods? $goods->name : '余额充值';
-		$view['days'] = $goods? $goods->days : 0;
-		$view['pay_type'] = $payment->order->pay_type_label?: 0;
-		$view['pay_type_icon'] = $payment->order->pay_type_icon;
-
-		return view('user.payment', $view);
-	}
-
-	// 回调日志
-	public function callbackList(Request $request) {
-		$status = $request->input('status', 0);
-
-		$query = PaymentCallback::query();
-
-		if(isset($status)){
-			$query->whereStatus($status);
-		}
-
-		$view['list'] = $query->latest()->paginate(10)->appends($request->except('page'));
-
-		return view('admin.logs.callbackList', $view);
-	}
+class PaymentController extends Controller
+{
+
+    private static $method;
+
+    public static function notify(Request $request): int
+    {
+        self::$method = $request->input('method');
+
+        Log::info(
+            self::$method . "回调接口[POST]:" . self::$method . var_export(
+                $request->all(),
+                true
+            )
+        );
+        self::getClient()->notify($request);
+
+        return 0;
+    }
+
+    public static function getClient()
+    {
+        switch (self::$method) {
+            case 'credit':
+                return new Local();
+            case 'f2fpay':
+                return new F2Fpay();
+            case 'codepay':
+                return new Codepay();
+            case 'payjs':
+                return new PayJs();
+            case 'bitpayx':
+                return new BitpayX();
+            case 'paypal':
+                return new PayPal();
+            case 'epay':
+                return new EPay();
+            default:
+                Log::error("未知支付:" . self::$method);
+
+                return false;
+        }
+    }
+
+    public static function getStatus(Request $request): JsonResponse
+    {
+        $payment = Payment::whereTradeNo($request->input('trade_no'))->first();
+        if ($payment) {
+            if ($payment->status == 1) {
+                return Response::json(
+                    ['status' => 'success', 'message' => '支付成功']
+                );
+            }
+
+            if ($payment->status == -1) {
+                return Response::json(
+                    ['status' => 'error', 'message' => '订单超时未支付,已自动关闭']
+                );
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '等待支付']);
+        }
+
+        return Response::json(['status' => 'error', 'message' => '未知订单']);
+    }
+
+    // 创建支付订单
+    public function purchase(Request $request)
+    {
+        $goods_id     = $request->input('goods_id');
+        $coupon_sn    = $request->input('coupon_sn');
+        self::$method = $request->input('method');
+        $credit       = $request->input('amount');
+        $pay_type     = $request->input('pay_type');
+        $amount       = 0;
+
+        $goods = Goods::find($goods_id);
+        // 充值余额
+        if ($credit) {
+            if ( ! is_numeric($credit) || $credit <= 0) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '充值余额不合规']
+                );
+            }
+            $amount = $credit;
+            // 购买服务
+        } elseif ($goods_id && self::$method) {
+            if ( ! $goods || ! $goods->status) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '订单创建失败:商品已下架']
+                );
+            }
+            $amount = $goods->price;
+
+            // 是否有生效的套餐
+            $activePlan = Order::userActivePlan()->doesntExist();
+
+            // 无生效套餐,禁止购买加油包
+            if ($goods->type == 1 && $activePlan) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '购买加油包前,请先购买套餐']
+                );
+            }
+
+            //非余额付款下,检查在线支付是否开启
+            if (self::$method !== 'credit') {
+                // 判断是否开启在线支付
+                if ( ! sysConfig('is_onlinePay')) {
+                    return Response::json(
+                        ['status' => 'fail', 'message' => '订单创建失败:系统并未开启在线支付功能']
+                    );
+                }
+
+                // 判断是否存在同个商品的未支付订单
+                if (Order::uid()->whereStatus(0)->exists()) {
+                    return Response::json(
+                        [
+                            'status'  => 'fail',
+                            'message' => '订单创建失败:尚有未支付的订单,请先去支付',
+                        ]
+                    );
+                }
+            } elseif (Auth::getUser()->credit < $amount) { // 验证账号余额是否充足
+                return Response::json(
+                    ['status' => 'fail', 'message' => '您的余额不足,请先充值']
+                );
+            }
+
+            // 单个商品限购
+            if ($goods->limit_num) {
+                $count = Order::uid()->where('status', '>=', 0)->whereGoodsId(
+                    $goods_id
+                )->count();
+                if ($count >= $goods->limit_num) {
+                    return Response::json(
+                        [
+                            'status'  => 'fail',
+                            'message' => '此商品限购' . $goods->limit_num . '次,您已购买' . $count . '次',
+                        ]
+                    );
+                }
+            }
+
+            // 使用优惠券 TODO 代码整合至 CouponService
+            if ($coupon_sn) {
+                $coupon = Coupon::whereStatus(0)
+                                ->whereIn('type', [1, 2])
+                                ->whereSn($coupon_sn)
+                                ->first();
+                if ( ! $coupon) {
+                    return Response::json(
+                        ['status' => 'fail', 'message' => '订单创建失败:优惠券不存在']
+                    );
+                }
+
+                // 计算实际应支付总价
+                $amount = $coupon->type == 2 ? $goods->price * $coupon->value / 100 : $goods->price - $coupon->value;
+                $amount = $amount > 0 ? round(
+                    $amount,
+                    2
+                ) : 0; // 四舍五入保留2位小数,避免无法正常创建订单
+            }
+
+            // 价格异常判断
+            if ($amount < 0) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '订单创建失败:订单总价异常']
+                );
+            }
+
+            if ($amount == 0 && self::$method !== 'credit') {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '订单创建失败:订单总价为0,无需使用在线支付']
+                );
+            }
+        }
+
+        $orderSn = date('ymdHis') . random_int(100000, 999999);
+
+        // 生成订单
+        $order                = new Order();
+        $order->order_sn      = $orderSn;
+        $order->user_id       = Auth::id();
+        $order->goods_id      = $credit ? 0 : $goods_id;
+        $order->coupon_id     = ! empty($coupon) ? $coupon->id : 0;
+        $order->origin_amount = $credit ?: $goods->price;
+        $order->amount        = $amount;
+        $order->is_expire     = 0;
+        $order->pay_type      = $pay_type;
+        $order->pay_way       = self::$method;
+        $order->status        = 0;
+        $order->save();
+
+        // 使用优惠券,减少可使用次数
+        if ( ! empty($coupon)) {
+            if ($coupon->usable_times > 0) {
+                Coupon::whereId($coupon->id)->decrement('usable_times', 1);
+            }
+
+            Helpers::addCouponLog('订单支付使用', $coupon->id, $goods_id, $order->id);
+        }
+
+        $request->merge(
+            ['id' => $order->id, 'type' => $pay_type, 'amount' => $amount]
+        );
+
+        // 生成支付单
+        return self::getClient()->purchase($request);
+    }
+
+    public function close(Request $request): JsonResponse
+    {
+        $order = Order::find($request->input('id'));
+        if ($order) {
+            if ( ! $order->update(['status' => -1])) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '关闭订单失败']
+                );
+            }
+        } else {
+            return Response::json(['status' => 'fail', 'message' => '未找到订单']);
+        }
+
+        return Response::json(['status' => 'success', 'message' => '关闭订单成功']);
+    }
+
+    // 支付单详情
+    public function detail($trade_no)
+    {
+        $payment               = Payment::uid()
+                                        ->with(['order', 'order.goods'])
+                                        ->whereTradeNo(
+                                            $trade_no
+                                        )
+                                        ->firstOrFail();
+        $view['payment']       = $payment;
+        $goods                 = $payment->order->goods;
+        $view['name']          = $goods ? $goods->name : '余额充值';
+        $view['days']          = $goods ? $goods->days : 0;
+        $view['pay_type']      = $payment->order->pay_type_label ?: 0;
+        $view['pay_type_icon'] = $payment->order->pay_type_icon;
+
+        return view('user.payment', $view);
+    }
+
+    // 回调日志
+    public function callbackList(Request $request)
+    {
+        $status = $request->input('status', 0);
+
+        $query = PaymentCallback::query();
+
+        if (isset($status)) {
+            $query->whereStatus($status);
+        }
+
+        $view['list'] = $query->latest()->paginate(10)->appends(
+            $request->except('page')
+        );
+
+        return view('admin.logs.callbackList', $view);
+    }
+
 }

+ 99 - 57
app/Http/Controllers/User/AffiliateController.php

@@ -10,67 +10,109 @@ use Auth;
 use Illuminate\Http\JsonResponse;
 use Response;
 
-class AffiliateController extends Controller {
-	// 推广返利
-	public function referral() {
-		if(ReferralLog::uid()->doesntExist() && Order::uid()->whereStatus(2)->doesntExist()){
-			return Response::view('auth.error',
-				['message' => '本功能对非付费用户禁用!请 <a class="btn btn-sm btn-danger" href="/">返 回</a>'], 402);
-		}
-		$view['referral_traffic'] = flowAutoShow(sysConfig('referral_traffic') * MB);
-		$view['referral_percent'] = sysConfig('referral_percent');
-		$view['referral_money'] = sysConfig('referral_money');
-		$view['totalAmount'] = ReferralLog::uid()->sum('commission') / 100;
-		$view['canAmount'] = ReferralLog::uid()->whereStatus(0)->sum('commission') / 100;
-		$view['aff_link'] = sysConfig('website_url').'/register?aff='.Auth::id();
-		$view['referralLogList'] = ReferralLog::uid()
-		                                      ->with('invitee:id,email')
-		                                      ->latest()
-		                                      ->paginate(10, ['*'], 'log_page');
-		$view['referralApplyList'] = ReferralApply::uid()->latest()->paginate(10, ['*'], 'apply_page');
-		$view['referralUserList'] = Auth::getUser()
-		                                ->invitees()
-		                                ->select(['email', 'created_at'])
-		                                ->latest()
-		                                ->paginate(10, ['*'], 'user_page');
+class AffiliateController extends Controller
+{
 
-		return view('user.referral', $view);
-	}
+    // 推广返利
+    public function referral()
+    {
+        if (ReferralLog::uid()->doesntExist() && Order::uid()
+                                                      ->whereStatus(2)
+                                                      ->doesntExist()) {
+            return Response::view(
+                'auth.error',
+                ['message' => '本功能对非付费用户禁用!请 <a class="btn btn-sm btn-danger" href="/">返 回</a>'],
+                402
+            );
+        }
+        $view['referral_traffic']  = flowAutoShow(
+            sysConfig('referral_traffic') * MB
+        );
+        $view['referral_percent']  = sysConfig('referral_percent');
+        $view['referral_money']    = sysConfig('referral_money');
+        $view['totalAmount']       = ReferralLog::uid()->sum(
+                'commission'
+            ) / 100;
+        $view['canAmount']         = ReferralLog::uid()->whereStatus(0)->sum(
+                'commission'
+            ) / 100;
+        $view['aff_link']          = sysConfig(
+                                         'website_url'
+                                     ) . '/register?aff=' . Auth::id();
+        $view['referralLogList']   = ReferralLog::uid()
+                                                ->with('invitee:id,email')
+                                                ->latest()
+                                                ->paginate(
+                                                    10,
+                                                    ['*'],
+                                                    'log_page'
+                                                );
+        $view['referralApplyList'] = ReferralApply::uid()->latest()->paginate(
+            10,
+            ['*'],
+            'apply_page'
+        );
+        $view['referralUserList']  = Auth::getUser()
+                                         ->invitees()
+                                         ->select(['email', 'created_at'])
+                                         ->latest()
+                                         ->paginate(10, ['*'], 'user_page');
 
-	// 申请提现
-	public function extractMoney(): JsonResponse {
-		// 判断账户是否过期
-		if(Auth::getUser()->expired_at < date('Y-m-d')){
-			return Response::json(['status' => 'fail', 'message' => '申请失败:账号已过期,请先购买服务吧']);
-		}
+        return view('user.referral', $view);
+    }
 
-		// 判断是否已存在申请
-		$referralApply = ReferralApply::uid()->whereIn('status', [0, 1])->first();
-		if($referralApply){
-			return Response::json(['status' => 'fail', 'message' => '申请失败:已存在申请,请等待之前的申请处理完']);
-		}
+    // 申请提现
+    public function extractMoney(): JsonResponse
+    {
+        // 判断账户是否过期
+        if (Auth::getUser()->expired_at < date('Y-m-d')) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '申请失败:账号已过期,请先购买服务吧']
+            );
+        }
 
-		// 校验可以提现金额是否超过系统设置的阀值
-		$commission = ReferralLog::uid()->whereStatus(0)->sum('commission');
-		$commission /= 100;
-		if($commission < sysConfig('referral_money')){
-			return Response::json([
-				'status'  => 'fail',
-				'message' => '申请失败:满'.sysConfig('referral_money').'元才可以提现,继续努力吧'
-			]);
-		}
+        // 判断是否已存在申请
+        $referralApply = ReferralApply::uid()->whereIn('status', [0, 1])->first(
+        );
+        if ($referralApply) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '申请失败:已存在申请,请等待之前的申请处理完']
+            );
+        }
 
-		$ref = new ReferralApply();
-		$ref->user_id = Auth::id();
-		$ref->before = $commission;
-		$ref->after = 0;
-		$ref->amount = $commission;
-		$ref->link_logs = ReferralLog::uid()->whereStatus(0)->pluck('id')->toArray();
-		$ref->status = 0;
-		if($ref->save()){
-			return Response::json(['status' => 'success', 'message' => '申请成功,请等待管理员审核']);
-		}
+        // 校验可以提现金额是否超过系统设置的阀值
+        $commission = ReferralLog::uid()->whereStatus(0)->sum('commission');
+        $commission /= 100;
+        if ($commission < sysConfig('referral_money')) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'message' => '申请失败:满' . sysConfig(
+                            'referral_money'
+                        ) . '元才可以提现,继续努力吧',
+                ]
+            );
+        }
+
+        $ref            = new ReferralApply();
+        $ref->user_id   = Auth::id();
+        $ref->before    = $commission;
+        $ref->after     = 0;
+        $ref->amount    = $commission;
+        $ref->link_logs = ReferralLog::uid()
+                                     ->whereStatus(0)
+                                     ->pluck('id')
+                                     ->toArray();
+        $ref->status    = 0;
+        if ($ref->save()) {
+            return Response::json(
+                ['status' => 'success', 'message' => '申请成功,请等待管理员审核']
+            );
+        }
+
+        return Response::json(
+            ['status' => 'fail', 'message' => '申请失败,返利单建立失败,请稍后尝试或通知管理员']
+        );
+    }
 
-		return Response::json(['status' => 'fail', 'message' => '申请失败,返利单建立失败,请稍后尝试或通知管理员']);
-	}
 }

+ 189 - 144
app/Http/Controllers/User/SubscribeController.php

@@ -10,148 +10,193 @@ use Illuminate\Http\Request;
 use Redirect;
 use Response;
 
-class SubscribeController extends Controller {
-	private $subType;
-
-	// 通过订阅码获取订阅信息
-	public function getSubscribeByCode(Request $request, $code) {
-		if(empty($code)){
-			return Redirect::to('login');
-		}
-		$this->subType = $request->input('type');
-
-		// 检查订阅码是否有效
-		$subscribe = UserSubscribe::whereCode($code)->first();
-		if(!$subscribe){
-			exit($this->infoGenerator('使用的订阅链接错误!请重新从官网获取!'));
-		}
-
-		if($subscribe->status != 1){
-			exit($this->infoGenerator('您的订阅链接已被封禁,请前往官网查询原因!'));
-		}
-
-		// 检查用户是否有效
-		$user = $subscribe->user;
-		if(!$user){
-			exit($this->infoGenerator('错误订阅链接,账号不存在!请前往官网重新获取订阅链接'));
-		}
-
-		if($user->status == -1){
-			exit($this->infoGenerator('您的账号已经被禁止使用,请重新注册'));
-		}
-
-		if($user->enable != 1){
-			$unusedTransfer = $user->transfer_enable - $user->u - $user->d;
-			if($user->ban_time){
-				exit($this->infoGenerator('您的账号处于封禁状态,请在'.date('Y-m-d H:i:s', $user->ban_time).'之后再更新!'));
-			}
-
-			if($unusedTransfer <= 0){
-				exit($this->infoGenerator('账号流量耗尽!请前往官网购买或重置流量!'));
-			}
-
-			if($user->expired_at < date('Y-m-d')){
-				exit($this->infoGenerator('账号过期!请前往官网购买!'));
-			}
-
-			exit($this->infoGenerator('账号存在问题,请前往官网查询!'));
-		}
-
-		// 更新访问次数
-		$subscribe->increment('times', 1);
-
-		// 记录每次请求
-		$this->subscribeLog($subscribe->id, getClientIp(), $request->headers);
-
-		// 获取这个账号可用节点
-		$query = $user->whereIsSubscribe(1)->userAccessNodes();
-
-		if($this->subType === 1){
-			$query = $query->whereIn('type', [1, 4]);
-		}elseif($this->subType){
-			$query = $query->whereType($this->subType);
-		}
-
-		$nodeList = $query->orderByDesc('sort')->orderBy('id')->get()->toArray();
-		if(empty($nodeList)){
-			exit($this->infoGenerator('无可用节点'));
-		}
-
-		// 打乱数组
-		if(sysConfig('rand_subscribe')){
-			$nodeList = Arr::shuffle($nodeList);
-		}
-
-		$scheme = null;
-
-		// 展示到期时间和剩余流量
-		if(sysConfig('is_custom_subscribe')){
-			$scheme .= $this->infoGenerator('到期时间: '.($user->expired_at < date('Y-m-d')? '过期' : $user->expired_at)).$this->infoGenerator('剩余流量: '.flowAutoShow($user->transfer_enable - $user->u - $user->d));
-		}
-
-		// 控制客户端最多获取节点数
-		foreach($nodeList as $key => $node){
-			// 控制显示的节点数
-			if(sysConfig('subscribe_max') && $key >= sysConfig('subscribe_max')){
-				break;
-			}
-
-			$scheme .= $this->getUserNodeInfo($user->id, $node['id'], 0).PHP_EOL;
-		}
-
-		$headers = [
-			'Content-type'  => 'application/octet-stream; charset=utf-8',
-			'Cache-Control' => 'no-store, no-cache, must-revalidate',
-			//'Content-Disposition' => 'attachment; filename='.$filename
-		];
-
-		// 适配Quantumult的自定义订阅头
-		if(sysConfig('is_custom_subscribe')){
-			$headers['Subscription-Userinfo'] = 'upload='.$user->u.'; download='.$user->d.'; total='.$user->transfer_enable.'; expire='.strtotime($user->expired_at);
-		}
-
-		return Response::make(base64url_encode($scheme), 200, $headers);
-	}
-
-	// 抛出错误的节点信息,用于兼容防止客户端订阅失败
-	private function infoGenerator($text): string {
-		$result = null;
-		switch($this->subType){
-			case 2:
-				$result = 'vmess://'.base64url_encode(json_encode([
-						"v"    => "2",
-						"ps"   => $text,
-						"add"  => "0.0.0.0",
-						"port" => 0,
-						"id"   => 0,
-						"aid"  => 0,
-						"net"  => "tcp",
-						"type" => "none",
-						"host" => "",
-						"path" => "/",
-						'tls'  => "tls"
-					], JSON_PRETTY_PRINT));
-				break;
-			case 3:
-				$result = 'trojan://0@0.0.0.0:0?peer=0.0.0.0#'.rawurlencode($text);
-				break;
-			case 1:
-			case 4:
-			default:
-				$result = 'ssr://'.base64url_encode('0.0.0.0:0:origin:none:plain:'.base64url_encode('0000').'/?obfsparam=&protoparam=&remarks='.base64url_encode($text).'&group='.base64url_encode(sysConfig('website_name')).'&udpport=0&uot=0');
-				break;
-
-		}
-		return $result.PHP_EOL;
-	}
-
-	// 写入订阅访问日志
-	private function subscribeLog($subscribeId, $ip, $headers): void {
-		$log = new UserSubscribeLog();
-		$log->user_subscribe_id = $subscribeId;
-		$log->request_ip = $ip;
-		$log->request_time = date('Y-m-d H:i:s');
-		$log->request_header = $headers;
-		$log->save();
-	}
+class SubscribeController extends Controller
+{
+
+    private $subType;
+
+    // 通过订阅码获取订阅信息
+    public function getSubscribeByCode(Request $request, $code)
+    {
+        if (empty($code)) {
+            return Redirect::to('login');
+        }
+        $this->subType = $request->input('type');
+
+        // 检查订阅码是否有效
+        $subscribe = UserSubscribe::whereCode($code)->first();
+        if ( ! $subscribe) {
+            exit($this->infoGenerator('使用的订阅链接错误!请重新从官网获取!'));
+        }
+
+        if ($subscribe->status != 1) {
+            exit($this->infoGenerator('您的订阅链接已被封禁,请前往官网查询原因!'));
+        }
+
+        // 检查用户是否有效
+        $user = $subscribe->user;
+        if ( ! $user) {
+            exit($this->infoGenerator('错误订阅链接,账号不存在!请前往官网重新获取订阅链接'));
+        }
+
+        if ($user->status == -1) {
+            exit($this->infoGenerator('您的账号已经被禁止使用,请重新注册'));
+        }
+
+        if ($user->enable != 1) {
+            $unusedTransfer = $user->transfer_enable - $user->u - $user->d;
+            if ($user->ban_time) {
+                exit(
+                $this->infoGenerator(
+                    '您的账号处于封禁状态,请在' . date(
+                        'Y-m-d H:i:s',
+                        $user->ban_time
+                    ) . '之后再更新!'
+                )
+                );
+            }
+
+            if ($unusedTransfer <= 0) {
+                exit($this->infoGenerator('账号流量耗尽!请前往官网购买或重置流量!'));
+            }
+
+            if ($user->expired_at < date('Y-m-d')) {
+                exit($this->infoGenerator('账号过期!请前往官网购买!'));
+            }
+
+            exit($this->infoGenerator('账号存在问题,请前往官网查询!'));
+        }
+
+        // 更新访问次数
+        $subscribe->increment('times', 1);
+
+        // 记录每次请求
+        $this->subscribeLog($subscribe->id, getClientIp(), $request->headers);
+
+        // 获取这个账号可用节点
+        $query = $user->whereIsSubscribe(1)->userAccessNodes();
+
+        if ($this->subType === 1) {
+            $query = $query->whereIn('type', [1, 4]);
+        } elseif ($this->subType) {
+            $query = $query->whereType($this->subType);
+        }
+
+        $nodeList = $query->orderByDesc('sort')->orderBy('id')->get()->toArray(
+        );
+        if (empty($nodeList)) {
+            exit($this->infoGenerator('无可用节点'));
+        }
+
+        // 打乱数组
+        if (sysConfig('rand_subscribe')) {
+            $nodeList = Arr::shuffle($nodeList);
+        }
+
+        $scheme = null;
+
+        // 展示到期时间和剩余流量
+        if (sysConfig('is_custom_subscribe')) {
+            $scheme .= $this->infoGenerator(
+                    '到期时间: ' . ($user->expired_at < date(
+                        'Y-m-d'
+                    ) ? '过期' : $user->expired_at)
+                ) . $this->infoGenerator(
+                    '剩余流量: ' . flowAutoShow(
+                        $user->transfer_enable - $user->u - $user->d
+                    )
+                );
+        }
+
+        // 控制客户端最多获取节点数
+        foreach ($nodeList as $key => $node) {
+            // 控制显示的节点数
+            if (sysConfig('subscribe_max') && $key >= sysConfig(
+                    'subscribe_max'
+                )) {
+                break;
+            }
+
+            $scheme .= $this->getUserNodeInfo(
+                    $user->id,
+                    $node['id'],
+                    0
+                ) . PHP_EOL;
+        }
+
+        $headers = [
+            'Content-type'  => 'application/octet-stream; charset=utf-8',
+            'Cache-Control' => 'no-store, no-cache, must-revalidate',
+            //'Content-Disposition' => 'attachment; filename='.$filename
+        ];
+
+        // 适配Quantumult的自定义订阅头
+        if (sysConfig('is_custom_subscribe')) {
+            $headers['Subscription-Userinfo'] = 'upload=' . $user->u . '; download=' . $user->d . '; total=' . $user->transfer_enable . '; expire=' . strtotime(
+                    $user->expired_at
+                );
+        }
+
+        return Response::make(base64url_encode($scheme), 200, $headers);
+    }
+
+    // 抛出错误的节点信息,用于兼容防止客户端订阅失败
+    private function infoGenerator($text): string
+    {
+        $result = null;
+        switch ($this->subType) {
+            case 2:
+                $result = 'vmess://' . base64url_encode(
+                        json_encode(
+                            [
+                                "v"    => "2",
+                                "ps"   => $text,
+                                "add"  => "0.0.0.0",
+                                "port" => 0,
+                                "id"   => 0,
+                                "aid"  => 0,
+                                "net"  => "tcp",
+                                "type" => "none",
+                                "host" => "",
+                                "path" => "/",
+                                'tls'  => "tls",
+                            ],
+                            JSON_PRETTY_PRINT
+                        )
+                    );
+                break;
+            case 3:
+                $result = 'trojan://0@0.0.0.0:0?peer=0.0.0.0#' . rawurlencode(
+                        $text
+                    );
+                break;
+            case 1:
+            case 4:
+            default:
+                $result = 'ssr://' . base64url_encode(
+                        '0.0.0.0:0:origin:none:plain:' . base64url_encode(
+                            '0000'
+                        ) . '/?obfsparam=&protoparam=&remarks=' . base64url_encode(
+                            $text
+                        ) . '&group=' . base64url_encode(
+                            sysConfig('website_name')
+                        ) . '&udpport=0&uot=0'
+                    );
+                break;
+        }
+
+        return $result . PHP_EOL;
+    }
+
+    // 写入订阅访问日志
+    private function subscribeLog($subscribeId, $ip, $headers): void
+    {
+        $log                    = new UserSubscribeLog();
+        $log->user_subscribe_id = $subscribeId;
+        $log->request_ip        = $ip;
+        $log->request_time      = date('Y-m-d H:i:s');
+        $log->request_header    = $headers;
+        $log->save();
+    }
+
 }

+ 819 - 558
app/Http/Controllers/UserController.php

@@ -44,562 +44,823 @@ use Validator;
  *
  * @package App\Http\Controllers
  */
-class UserController extends Controller {
-	public function index() {
-		$user = Auth::getUser();
-		$totalTransfer = $user->transfer_enable;
-		$usedTransfer = $user->u + $user->d;
-		$unusedTransfer = $totalTransfer - $usedTransfer > 0? $totalTransfer - $usedTransfer : 0;
-		$expireTime = $user->expired_at;
-		$view['remainDays'] = $expireTime < date('Y-m-d')? -1 : Helpers::daysToNow($expireTime);
-		$view['resetDays'] = $user->reset_time? Helpers::daysToNow($user->reset_time) : 0;
-		$view['unusedTransfer'] = $unusedTransfer;
-		$view['expireTime'] = $expireTime;
-		$view['banedTime'] = $user->ban_time? date('Y-m-d H:i:s', $user->ban_time) : 0;
-		$view['unusedPercent'] = $totalTransfer > 0? round($unusedTransfer / $totalTransfer, 2) : 0;
-		$view['noticeList'] = Article::type(2)->latest()->Paginate(1); // 公告
-		//流量异常判断
-		$hourlyTraffic = UserHourlyDataFlow::userRecentUsed($user->id)->sum('total');
-		$view['isTrafficWarning'] = $hourlyTraffic >= (sysConfig('traffic_ban_value') * GB)?: 0;
-		//付费用户判断
-		$view['not_paying_user'] = Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist();
-		$view['userLoginLog'] = UserLoginLog::whereUserId($user->id)->latest()->first(); // 近期登录日志
-		$view = array_merge($view, $this->dataFlowChart($user->id));
-
-		return view('user.index', $view);
-	}
-
-	// 签到
-	public function checkIn(): JsonResponse {
-		$user = Auth::getUser();
-		// 系统开启登录加积分功能才可以签到
-		if(!sysConfig('is_checkin')){
-			return Response::json(['status' => 'fail', 'message' => '系统未开启签到功能']);
-		}
-
-		// 已签到过,验证是否有效
-		if(Cache::has('userCheckIn_'.$user->id)){
-			return Response::json(['status' => 'fail', 'message' => '已经签到过了,明天再来吧']);
-		}
-
-		$traffic = random_int((int) sysConfig('min_rand_traffic'), (int) sysConfig('max_rand_traffic')) * MB;
-
-		if(!(new UserService())->incrementData($traffic)){
-			return Response::json(['status' => 'fail', 'message' => '签到失败,系统异常']);
-		}
-
-		// 写入用户流量变动记录
-		Helpers::addUserTrafficModifyLog($user->id, 0, $user->transfer_enable, $user->transfer_enable + $traffic,
-			'[签到]');
-
-		// 多久后可以再签到
-		$ttl = sysConfig('traffic_limit_time')? sysConfig('traffic_limit_time') * Minute : Day;
-		Cache::put('userCheckIn_'.$user->id, '1', $ttl);
-
-		return Response::json(['status' => 'success', 'message' => '签到成功,系统送您 '.flowAutoShow($traffic).'流量']);
-	}
-
-	// 节点列表
-	public function nodeList(Request $request) {
-		$user = Auth::getUser();
-		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);
-
-			return Response::json(['status' => 'success', 'data' => $data, 'title' => $proxyType]);
-		}
-
-		// 获取当前用户可用节点
-		$nodeList = $user->userAccessNodes()->with(['labels', 'level_table'])->get();
-
-		$view['nodesGeo'] = $nodeList->pluck('name', 'geo')->toArray();
-		$onlineNode = NodeHeartBeat::recently()->distinct()->pluck('node_id')->toArray();
-		$pingNodeLogs = NodePing::whereMonth('created_at', date('m'))->get(['node_id', 'ct', 'cu', 'cm', 'hk']);
-		foreach($nodeList as $node){
-			$data = $pingNodeLogs->where('node_id', $node->id);
-			$node->ct = round($data->pluck('ct')->filter()->avg(), 2);
-			$node->cu = round($data->pluck('cu')->filter()->avg(), 2);
-			$node->cm = round($data->pluck('cm')->filter()->avg(), 2);
-			$node->hk = round($data->pluck('hk')->filter()->avg(), 2);
-
-			// 节点在线状态
-			$node->offline = !in_array($node->id, $onlineNode);
-		}
-		$view['nodeList'] = $nodeList?: [];
-
-		return view('user.nodeList', $view);
-	}
-
-	// 公告详情
-	public function article(Request $request) {
-		$view['info'] = Article::findOrFail($request->input('id'));
-
-		return view('user.article', $view);
-	}
-
-	// 修改个人资料
-	public function profile(Request $request) {
-		$user = Auth::getUser();
-		if($request->isMethod('POST')){
-			$old_password = $request->input('old_password');
-			$new_password = $request->input('new_password');
-			$username = $request->input('username');
-			$wechat = $request->input('wechat');
-			$qq = $request->input('qq');
-			$passwd = $request->input('passwd');
-
-			// 修改密码
-			if($old_password && $new_password){
-				if(!Hash::check($old_password, $user->password)){
-					return Redirect::to('profile#tab_1')->withErrors('旧密码错误,请重新输入');
-				}
-
-				if(Hash::check($new_password, $user->password)){
-					return Redirect::to('profile#tab_1')->withErrors('新密码不可与旧密码一样,请重新输入');
-				}
-
-				// 演示环境禁止改管理员密码
-				if($user->id === 1 && config('app.demo')){
-					return Redirect::to('profile#tab_1')->withErrors('演示环境禁止修改管理员密码');
-				}
-
-				if(!$user->update(['password' => Hash::make($new_password)])){
-					return Redirect::to('profile#tab_1')->withErrors('修改失败');
-				}
-
-				return Redirect::to('profile#tab_1')->with('successMsg', '修改成功');
-				// 修改代理密码
-			}
-
-			if($passwd){
-				if(!$user->update(['passwd' => $passwd])){
-					return Redirect::to('profile#tab_3')->withErrors('修改失败');
-				}
-
-				return Redirect::to('profile#tab_3')->with('successMsg', '修改成功');
-			}
-
-			// 修改联系方式
-			if(empty($username)){
-				return Redirect::to('profile#tab_2')->withErrors('修改失败,昵称不能为空值');
-			}
-
-			if(!$user->update(['username' => $username, 'wechat' => $wechat, 'qq' => $qq])){
-				return Redirect::to('profile#tab_2')->withErrors('修改失败');
-			}
-
-			return Redirect::to('profile#tab_2')->with('successMsg', '修改成功');
-		}
-
-		return view('user.profile');
-	}
-
-	// 商品列表
-	public function services(Request $request) {
-		$user = Auth::getUser();
-		// 余额充值商品,只取10个
-		$view['chargeGoodsList'] = Goods::type(3)->whereStatus(1)->orderBy('price')->limit(10)->get();
-		$view['goodsList'] = Goods::whereStatus(1)
-		                          ->where('type', '<=', '2')
-		                          ->orderByDesc('type')
-		                          ->orderByDesc('sort')
-		                          ->paginate(10)
-		                          ->appends($request->except('page'));
-		$renewOrder = Order::userActivePlan($user->id)->first();
-		$renewPrice = $renewOrder? $renewOrder->goods : 0;
-		$view['renewTraffic'] = $renewPrice? $renewPrice->renew : 0;
-		// 有重置日时按照重置日为标准,否者就以过期日为标准
-		$dataPlusDays = $user->reset_time?: $user->expired_at;
-		$view['dataPlusDays'] = $dataPlusDays > date('Y-m-d')? Helpers::daysToNow($dataPlusDays) : 0;
-
-		return view('user.services', $view);
-	}
-
-	//重置流量
-	public function resetUserTraffic(): ?JsonResponse {
-		$user = Auth::getUser();
-		$order = Order::userActivePlan()->first();
-		$renewCost = $order->goods->renew;
-		if($user->credit < $renewCost){
-			return Response::json(['status' => 'fail', 'message' => '余额不足,请充值余额']);
-		}
-
-		$user->update(['u' => 0, 'd' => 0]);
-
-		// 扣余额
-		(new UserService($user))->updateCredit(-$renewCost);
-
-		// 记录余额操作日志
-		Helpers::addUserCreditLog($user->id, '', $user->credit, $user->credit - $renewCost, -1 * $renewCost,
-			'用户自行重置流量');
-
-		return Response::json(['status' => 'success', 'message' => '重置成功']);
-	}
-
-	// 工单
-	public function ticketList(Request $request) {
-		$view['ticketList'] = Ticket::uid()->latest()->paginate(10)->appends($request->except('page'));
-
-		return view('user.ticketList', $view);
-	}
-
-	// 订单
-	public function invoices(Request $request) {
-		$view['orderList'] = Order::uid()
-		                          ->with(['goods', 'payment'])
-		                          ->orderByDesc('id')
-		                          ->paginate(10)
-		                          ->appends($request->except('page'));
-		$view['prepaidPlan'] = Order::userPrepay()->exists();
-
-		return view('user.invoices', $view);
-	}
-
-	public function closePlan(): JsonResponse {
-		$activePlan = Order::userActivePlan()->first();
-		$activePlan->is_expire = 1;
-
-		if($activePlan->save()){
-			// 关闭先前套餐后,新套餐自动运行
-			if(Order::userActivePlan()->exists()){
-				return Response::json(['status' => 'success', 'message' => '激活成功']);
-			}
-			return Response::json(['status' => 'success', 'message' => '关闭']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '关闭失败']);
-	}
-
-	// 订单明细
-	public function invoiceDetail($sn) {
-		$view['order'] = Order::uid()->with(['goods', 'coupon', 'payment'])->whereOrderSn($sn)->firstOrFail();
-
-		return view('user.invoiceDetail', $view);
-	}
-
-	// 添加工单
-	public function createTicket(Request $request): ?JsonResponse {
-		$user = Auth::getUser();
-		$title = $request->input('title');
-		$content = clean($request->input('content'));
-		$content = str_replace(["atob", "eval"], "", $content);
-
-		if(empty($title) || empty($content)){
-			return Response::json(['status' => 'fail', 'message' => '请输入标题和内容']);
-		}
-
-		$obj = new Ticket();
-		$obj->user_id = $user->id;
-		$obj->title = $title;
-		$obj->content = $content;
-		$obj->status = 0;
-		$obj->save();
-
-		if($obj->id){
-			$emailTitle = "新工单提醒";
-			$content = "标题:【".$title."】<br>用户:".$user->email."<br>内容:".$content;
-
-			// 发邮件通知管理员
-			if(sysConfig('webmaster_email')){
-				$logId = Helpers::addNotificationLog($emailTitle, $content, 1, sysConfig('webmaster_email'));
-				Mail::to(sysConfig('webmaster_email'))->send(new newTicket($logId, $emailTitle, $content));
-			}
-
-			PushNotification::send($emailTitle, $content);
-
-			return Response::json(['status' => 'success', 'message' => '提交成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '提交失败']);
-	}
-
-	// 回复工单
-	public function replyTicket(Request $request) {
-		$id = $request->input('id');
-
-		$ticket = Ticket::uid()->with('user')->whereId($id)->firstOrFail();
-
-		if($request->isMethod('POST')){
-			$content = clean($request->input('content'));
-			$content = str_replace(["atob", "eval"], "", $content);
-			$content = substr($content, 0, 300);
-
-			if(empty($content)){
-				return Response::json(['status' => 'fail', 'message' => '回复内容不能为空']);
-			}
-
-			if($ticket->status == 2){
-				return Response::json(['status' => 'fail', 'message' => '错误:该工单已关闭']);
-			}
-
-			$obj = new TicketReply();
-			$obj->ticket_id = $id;
-			$obj->user_id = Auth::id();
-			$obj->content = $content;
-			$obj->save();
-
-			if($obj->id){
-				// 重新打开工单
-				$ticket->status = 0;
-				$ticket->save();
-
-				$title = "工单回复提醒";
-				$content = "标题:【".$ticket->title."】<br>用户回复:".$content;
-
-				// 发邮件通知管理员
-				if(sysConfig('webmaster_email')){
-					$logId = Helpers::addNotificationLog($title, $content, 1, sysConfig('webmaster_email'));
-					Mail::to(sysConfig('webmaster_email'))->send(new replyTicket($logId, $title, $content));
-				}
-
-				PushNotification::send($title, $content);
-
-				return Response::json(['status' => 'success', 'message' => '回复成功']);
-			}
-
-			return Response::json(['status' => 'fail', 'message' => '回复失败']);
-		}
-
-		$view['ticket'] = $ticket;
-		$view['replyList'] = TicketReply::whereTicketId($id)->with('user')->oldest()->get();
-
-		return view('user.replyTicket', $view);
-	}
-
-	// 关闭工单
-	public function closeTicket(Request $request): ?JsonResponse {
-		$id = $request->input('id');
-
-		$ret = Ticket::uid()->whereId($id)->update(['status' => 2]);
-		if($ret){
-			PushNotification::send('工单关闭提醒', '工单:ID'.$id.'用户已手动关闭');
-
-			return Response::json(['status' => 'success', 'message' => '关闭成功']);
-		}
-
-		return Response::json(['status' => 'fail', 'message' => '关闭失败']);
-	}
-
-	// 邀请码
-	public function invite() {
-		if(Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist()){
-			return Response::view('auth.error',
-				['message' => '本功能对非付费用户禁用!请 <a class="btn btn-sm btn-danger" href="/">返 回</a>'], 402);
-		}
-
-		$view['num'] = Auth::getUser()->invite_num; // 还可以生成的邀请码数量
-		$view['inviteList'] = Invite::uid()->with(['invitee', 'inviter'])->paginate(10); // 邀请码列表
-		$view['referral_traffic'] = flowAutoShow(sysConfig('referral_traffic') * MB);
-		$view['referral_percent'] = sysConfig('referral_percent');
-
-		return view('user.invite', $view);
-	}
-
-	// 生成邀请码
-	public function makeInvite(): JsonResponse {
-		$user = Auth::getUser();
-		if($user->invite_num <= 0){
-			return Response::json(['status' => 'fail', 'message' => '生成失败:已无邀请码生成名额']);
-		}
-
-		$obj = new Invite();
-		$obj->inviter_id = $user->id;
-		$obj->invitee_id = 0;
-		$obj->code = strtoupper(mb_substr(md5(microtime().Str::random()), 8, 12));
-		$obj->status = 0;
-		$obj->dateline = date('Y-m-d H:i:s', strtotime("+".sysConfig('user_invite_days')." days"));
-		$obj->save();
-
-		User::uid()->decrement('invite_num', 1);
-
-		return Response::json(['status' => 'success', 'message' => '生成成功']);
-	}
-
-	// 使用优惠券
-	public function redeemCoupon(Request $request): JsonResponse {
-		$coupon_sn = $request->input('coupon_sn');
-		$good_price = $request->input('price');
-
-		if(empty($coupon_sn)){
-			return Response::json(['status' => 'fail', 'title' => '使用失败', 'message' => '请输入您的优惠劵!']);
-		}
-
-		$coupon = Coupon::whereSn($coupon_sn)->whereIn('type', [1, 2])->first();
-		if(!$coupon){
-			return Response::json(['status' => 'fail', 'title' => '优惠券不存在', 'message' => '请确认优惠券是否输入正确!']);
-		}
-
-		if($coupon->status == 1){
-			return Response::json(['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已被使用!']);
-		}
-
-		if($coupon->status == 2){
-			return Response::json(['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已失效!']);
-		}
-
-		if($coupon->end_time < time()){
-			$coupon->status = 2;
-			$coupon->save();
-
-			return Response::json(['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已失效!']);
-		}
-
-		if($coupon->start_time > time()){
-			return Response::json(['status' => 'fail', 'title' => '优惠券尚未生效', 'message' => '请等待活动正式开启']);
-		}
-
-		if($good_price < $coupon->rule){
-			return Response::json(['status' => 'fail', 'title' => '使用条件未满足', 'message' => '请购买价格更高的套餐']);
-		}
-
-		$data = [
-			'name'  => $coupon->name,
-			'type'  => $coupon->type,
-			'value' => $coupon->value
-		];
-
-		return Response::json(['status' => 'success', 'data' => $data, 'message' => '优惠券有效']);
-	}
-
-	// 购买服务
-	public function buy($goods_id) {
-		$user = Auth::getUser();
-		$goods = Goods::whereId($goods_id)->whereStatus(1)->first();
-		if(empty($goods)){
-			return Redirect::to('services');
-		}
-		// 有重置日时按照重置日为标准,否者就以过期日为标准
-		$dataPlusDays = $user->reset_time?: $user->expired_at;
-		$view['dataPlusDays'] = $dataPlusDays > date('Y-m-d')? Helpers::daysToNow($dataPlusDays) : 0;
-		$view['activePlan'] = Order::userActivePlan()->exists();
-		$view['goods'] = $goods;
-
-		return view('user.buy', $view);
-	}
-
-	// 帮助中心
-	public function help() {
-		//$view['articleList'] = Article::type(1)->orderByDesc('sort')->latest()->limit(10)->paginate(5);
-		$data = [];
-		if(Node::whereIn('type', [1, 4])->whereStatus(1)->exists()){
-			$data[] = 'ss';
-			//array_push
-		}
-		if(Node::whereType(2)->whereStatus(1)->exists()){
-			$data[] = 'v2';
-		}
-		if(Node::whereType(3)->whereStatus(1)->exists()){
-			$data[] = 'trojan';
-		}
-
-		$view['sub'] = $data;
-
-		//付费用户判断
-		$view['not_paying_user'] = Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist();
-		//客户端安装
-		$view['Shadowrocket_install'] = 'itms-services://?action=download-manifest&url='.sysConfig('website_url').'/clients/Shadowrocket.plist';
-		$view['Quantumult_install'] = 'itms-services://?action=download-manifest&url='.sysConfig('website_url').'/clients/Quantumult.plist';
-		// 订阅连接
-		$subscribe = UserSubscribe::whereUserId(Auth::id())->firstOrFail();
-		$view['subscribe_status'] = $subscribe->status;
-		$subscribe_link = (sysConfig('subscribe_domain')?: sysConfig('website_url')).'/s/'.$subscribe->code;
-		$view['link'] = $subscribe_link;
-		$view['subscribe_link'] = 'sub://'.base64url_encode($subscribe_link);
-		$view['Shadowrocket_link'] = 'shadowrocket://add/sub://'.base64url_encode($subscribe_link).'?remarks='.(sysConfig('website_name').'-'.sysConfig('website_url'));
-		$view['Shadowrocket_linkQrcode'] = 'sub://'.base64url_encode($subscribe_link).'#'.base64url_encode(sysConfig('website_name'));
-		$view['Quantumult_linkOut'] = 'quantumult://configuration?server='.base64url_encode($subscribe_link).'&filter='.base64url_encode('https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Pro.conf').'&rejection='.base64url_encode('https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Rejection.conf');
-		$view['Quantumult_linkIn'] = 'quantumult://configuration?server='.base64url_encode($subscribe_link).'&filter='.base64url_encode('https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/BacktoCN.conf').'&rejection='.base64url_encode('https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Rejection.conf');
-
-		return view('user.help', $view);
-	}
-
-	// 更换订阅地址
-	public function exchangeSubscribe(): ?JsonResponse {
-		try{
-			DB::beginTransaction();
-
-			// 更换订阅码
-			Auth::getUser()->subscribe->update(['code' => Helpers::makeSubscribeCode()]);
-
-			// 更换连接密码
-			Auth::getUser()->update(['passwd' => Str::random()]);
-
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '更换成功']);
-		}catch(Exception $e){
-			DB::rollBack();
-
-			Log::error("更换订阅地址异常:".$e->getMessage());
-
-			return Response::json(['status' => 'fail', 'message' => '更换失败'.$e->getMessage()]);
-		}
-	}
-
-	// 转换成管理员的身份
-	public function switchToAdmin(): JsonResponse {
-		if(!Session::has('admin')){
-			return Response::json(['status' => 'fail', 'message' => '非法请求']);
-		}
-
-		// 管理员信息重新写入user
-		$user = Auth::loginUsingId(Session::get('admin'));
-		Session::forget('admin');
-		if($user){
-			return Response::json(['status' => 'success', 'message' => "身份切换成功"]);
-		}
-		return Response::json(['status' => 'fail', 'message' => '身份切换失败']);
-	}
-
-	// Todo 卡券余额合并至CouponService
-	public function charge(Request $request): ?JsonResponse {
-		$validator = Validator::make($request->all(), [
-			'coupon_sn' => [
-				'required',
-				Rule::exists('coupon', 'sn')->where(static function($query) {
-					$query->whereType(3)->whereStatus(0);
-				}),
-			]
-		], ['coupon_sn.required' => '券码不能为空', 'coupon_sn.exists' => '该券不可用']);
-
-		if($validator->fails()){
-			return Response::json(['status' => 'fail', 'message' => $validator->getMessageBag()->first()]);
-		}
-
-		$coupon = Coupon::whereSn($request->input('coupon_sn'))->firstOrFail();
-
-		try{
-			DB::beginTransaction();
-			// 写入日志
-			$user = Auth::getUser();
-			Helpers::addUserCreditLog($user->id, 0, $user->credit, $user->credit + $coupon->value, $coupon->value,
-				'用户手动充值 - [充值券:'.$request->input('coupon_sn').']');
-
-			// 余额充值
-			(new UserService($user))->updateCredit($coupon->value);
-
-			// 更改卡券状态
-			Coupon::find($coupon->id)->update(['status' => 1]);
-
-			// 写入卡券日志
-			Helpers::addCouponLog('账户余额充值使用', $coupon->id);
-
-			DB::commit();
-
-			return Response::json(['status' => 'success', 'message' => '充值成功']);
-		}catch(Exception $e){
-			Log::error('卡劵充值错误:'.$e->getMessage());
-			DB::rollBack();
-
-			return Response::json(['status' => 'fail', 'message' => '充值失败']);
-		}
-	}
+class UserController extends Controller
+{
+
+    public function index()
+    {
+        $user                   = Auth::getUser();
+        $totalTransfer          = $user->transfer_enable;
+        $usedTransfer           = $user->u + $user->d;
+        $unusedTransfer         = $totalTransfer - $usedTransfer > 0 ? $totalTransfer - $usedTransfer : 0;
+        $expireTime             = $user->expired_at;
+        $view['remainDays']     = $expireTime < date(
+            'Y-m-d'
+        ) ? -1 : Helpers::daysToNow($expireTime);
+        $view['resetDays']      = $user->reset_time ? Helpers::daysToNow(
+            $user->reset_time
+        ) : 0;
+        $view['unusedTransfer'] = $unusedTransfer;
+        $view['expireTime']     = $expireTime;
+        $view['banedTime']      = $user->ban_time ? date(
+            'Y-m-d H:i:s',
+            $user->ban_time
+        ) : 0;
+        $view['unusedPercent']  = $totalTransfer > 0 ? round(
+            $unusedTransfer / $totalTransfer,
+            2
+        ) : 0;
+        $view['noticeList']     = Article::type(2)->latest()->Paginate(1); // 公告
+        //流量异常判断
+        $hourlyTraffic            = UserHourlyDataFlow::userRecentUsed(
+            $user->id
+        )->sum(
+            'total'
+        );
+        $view['isTrafficWarning'] = $hourlyTraffic >= (sysConfig(
+                                                           'traffic_ban_value'
+                                                       ) * GB) ?: 0;
+        //付费用户判断
+        $view['not_paying_user'] = Order::uid()->active()->where(
+            'origin_amount',
+            '>',
+            0
+        )->doesntExist();
+        $view['userLoginLog']    = UserLoginLog::whereUserId($user->id)->latest(
+        )->first(); // 近期登录日志
+        $view                    = array_merge(
+            $view,
+            $this->dataFlowChart($user->id)
+        );
+
+        return view('user.index', $view);
+    }
+
+    // 签到
+    public function checkIn(): JsonResponse
+    {
+        $user = Auth::getUser();
+        // 系统开启登录加积分功能才可以签到
+        if ( ! sysConfig('is_checkin')) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '系统未开启签到功能']
+            );
+        }
+
+        // 已签到过,验证是否有效
+        if (Cache::has('userCheckIn_' . $user->id)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '已经签到过了,明天再来吧']
+            );
+        }
+
+        $traffic = random_int(
+                       (int)sysConfig('min_rand_traffic'),
+                       (int)sysConfig('max_rand_traffic')
+                   ) * MB;
+
+        if ( ! (new UserService())->incrementData($traffic)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '签到失败,系统异常']
+            );
+        }
+
+        // 写入用户流量变动记录
+        Helpers::addUserTrafficModifyLog(
+            $user->id,
+            0,
+            $user->transfer_enable,
+            $user->transfer_enable + $traffic,
+            '[签到]'
+        );
+
+        // 多久后可以再签到
+        $ttl = sysConfig('traffic_limit_time') ? sysConfig(
+                                                     'traffic_limit_time'
+                                                 ) * Minute : Day;
+        Cache::put('userCheckIn_' . $user->id, '1', $ttl);
+
+        return Response::json(
+            [
+                'status'  => 'success',
+                'message' => '签到成功,系统送您 ' . flowAutoShow($traffic) . '流量',
+            ]
+        );
+    }
+
+    // 节点列表
+    public function nodeList(Request $request)
+    {
+        $user = Auth::getUser();
+        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
+            );
+
+            return Response::json(
+                ['status' => 'success', 'data' => $data, 'title' => $proxyType]
+            );
+        }
+
+        // 获取当前用户可用节点
+        $nodeList = $user->userAccessNodes()
+                         ->with(['labels', 'level_table'])
+                         ->get();
+
+        $view['nodesGeo'] = $nodeList->pluck('name', 'geo')->toArray();
+        $onlineNode       = NodeHeartBeat::recently()->distinct()->pluck(
+            'node_id'
+        )->toArray();
+        $pingNodeLogs     = NodePing::whereMonth('created_at', date('m'))->get(
+            ['node_id', 'ct', 'cu', 'cm', 'hk']
+        );
+        foreach ($nodeList as $node) {
+            $data     = $pingNodeLogs->where('node_id', $node->id);
+            $node->ct = round($data->pluck('ct')->filter()->avg(), 2);
+            $node->cu = round($data->pluck('cu')->filter()->avg(), 2);
+            $node->cm = round($data->pluck('cm')->filter()->avg(), 2);
+            $node->hk = round($data->pluck('hk')->filter()->avg(), 2);
+
+            // 节点在线状态
+            $node->offline = ! in_array($node->id, $onlineNode);
+        }
+        $view['nodeList'] = $nodeList ?: [];
+
+        return view('user.nodeList', $view);
+    }
+
+    // 公告详情
+    public function article(Request $request)
+    {
+        $view['info'] = Article::findOrFail($request->input('id'));
+
+        return view('user.article', $view);
+    }
+
+    // 修改个人资料
+    public function profile(Request $request)
+    {
+        $user = Auth::getUser();
+        if ($request->isMethod('POST')) {
+            $old_password = $request->input('old_password');
+            $new_password = $request->input('new_password');
+            $username     = $request->input('username');
+            $wechat       = $request->input('wechat');
+            $qq           = $request->input('qq');
+            $passwd       = $request->input('passwd');
+
+            // 修改密码
+            if ($old_password && $new_password) {
+                if ( ! Hash::check($old_password, $user->password)) {
+                    return Redirect::to('profile#tab_1')->withErrors(
+                        '旧密码错误,请重新输入'
+                    );
+                }
+
+                if (Hash::check($new_password, $user->password)) {
+                    return Redirect::to('profile#tab_1')->withErrors(
+                        '新密码不可与旧密码一样,请重新输入'
+                    );
+                }
+
+                // 演示环境禁止改管理员密码
+                if ($user->id === 1 && env('APP_DEMO')) {
+                    return Redirect::to('profile#tab_1')->withErrors(
+                        '演示环境禁止修改管理员密码'
+                    );
+                }
+
+                if ( ! $user->update(
+                    ['password' => Hash::make($new_password)]
+                )) {
+                    return Redirect::to('profile#tab_1')->withErrors('修改失败');
+                }
+
+                return Redirect::to('profile#tab_1')->with(
+                    'successMsg',
+                    '修改成功'
+                );
+                // 修改代理密码
+            }
+
+            if ($passwd) {
+                if ( ! $user->update(['passwd' => $passwd])) {
+                    return Redirect::to('profile#tab_3')->withErrors('修改失败');
+                }
+
+                return Redirect::to('profile#tab_3')->with(
+                    'successMsg',
+                    '修改成功'
+                );
+            }
+
+            // 修改联系方式
+            if (empty($username)) {
+                return Redirect::to('profile#tab_2')->withErrors(
+                    '修改失败,昵称不能为空值'
+                );
+            }
+
+            if ( ! $user->update(
+                ['username' => $username, 'wechat' => $wechat, 'qq' => $qq]
+            )) {
+                return Redirect::to('profile#tab_2')->withErrors('修改失败');
+            }
+
+            return Redirect::to('profile#tab_2')->with('successMsg', '修改成功');
+        }
+
+        return view('user.profile');
+    }
+
+    // 商品列表
+    public function services(Request $request)
+    {
+        $user = Auth::getUser();
+        // 余额充值商品,只取10个
+        $view['chargeGoodsList'] = Goods::type(3)->whereStatus(1)->orderBy(
+            'price'
+        )->limit(10)->get();
+        $view['goodsList']       = Goods::whereStatus(1)
+                                        ->where('type', '<=', '2')
+                                        ->orderByDesc('type')
+                                        ->orderByDesc('sort')
+                                        ->paginate(10)
+                                        ->appends($request->except('page'));
+        $renewOrder              = Order::userActivePlan($user->id)->first();
+        $renewPrice              = $renewOrder ? $renewOrder->goods : 0;
+        $view['renewTraffic']    = $renewPrice ? $renewPrice->renew : 0;
+        // 有重置日时按照重置日为标准,否者就以过期日为标准
+        $dataPlusDays         = $user->reset_time ?: $user->expired_at;
+        $view['dataPlusDays'] = $dataPlusDays > date(
+            'Y-m-d'
+        ) ? Helpers::daysToNow($dataPlusDays) : 0;
+
+        return view('user.services', $view);
+    }
+
+    //重置流量
+    public function resetUserTraffic(): ?JsonResponse
+    {
+        $user      = Auth::getUser();
+        $order     = Order::userActivePlan()->first();
+        $renewCost = $order->goods->renew;
+        if ($user->credit < $renewCost) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '余额不足,请充值余额']
+            );
+        }
+
+        $user->update(['u' => 0, 'd' => 0]);
+
+        // 扣余额
+        (new UserService($user))->updateCredit(-$renewCost);
+
+        // 记录余额操作日志
+        Helpers::addUserCreditLog(
+            $user->id,
+            '',
+            $user->credit,
+            $user->credit - $renewCost,
+            -1 * $renewCost,
+            '用户自行重置流量'
+        );
+
+        return Response::json(['status' => 'success', 'message' => '重置成功']);
+    }
+
+    // 工单
+    public function ticketList(Request $request)
+    {
+        $view['ticketList'] = Ticket::uid()->latest()->paginate(10)->appends(
+            $request->except('page')
+        );
+
+        return view('user.ticketList', $view);
+    }
+
+    // 订单
+    public function invoices(Request $request)
+    {
+        $view['orderList']   = Order::uid()
+                                    ->with(['goods', 'payment'])
+                                    ->orderByDesc('id')
+                                    ->paginate(10)
+                                    ->appends($request->except('page'));
+        $view['prepaidPlan'] = Order::userPrepay()->exists();
+
+        return view('user.invoices', $view);
+    }
+
+    public function closePlan(): JsonResponse
+    {
+        $activePlan            = Order::userActivePlan()->first();
+        $activePlan->is_expire = 1;
+
+        if ($activePlan->save()) {
+            // 关闭先前套餐后,新套餐自动运行
+            if (Order::userActivePlan()->exists()) {
+                return Response::json(
+                    ['status' => 'success', 'message' => '激活成功']
+                );
+            }
+
+            return Response::json(['status' => 'success', 'message' => '关闭']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '关闭失败']);
+    }
+
+    // 订单明细
+    public function invoiceDetail($sn)
+    {
+        $view['order'] = Order::uid()
+                              ->with(['goods', 'coupon', 'payment'])
+                              ->whereOrderSn($sn)
+                              ->firstOrFail();
+
+        return view('user.invoiceDetail', $view);
+    }
+
+    // 添加工单
+    public function createTicket(Request $request): ?JsonResponse
+    {
+        $user    = Auth::getUser();
+        $title   = $request->input('title');
+        $content = clean($request->input('content'));
+        $content = str_replace(["atob", "eval"], "", $content);
+
+        if (empty($title) || empty($content)) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '请输入标题和内容']
+            );
+        }
+
+        $obj          = new Ticket();
+        $obj->user_id = $user->id;
+        $obj->title   = $title;
+        $obj->content = $content;
+        $obj->status  = 0;
+        $obj->save();
+
+        if ($obj->id) {
+            $emailTitle = "新工单提醒";
+            $content    = "标题:【" . $title . "】<br>用户:" . $user->email . "<br>内容:" . $content;
+
+            // 发邮件通知管理员
+            if (sysConfig('webmaster_email')) {
+                $logId = Helpers::addNotificationLog(
+                    $emailTitle,
+                    $content,
+                    1,
+                    sysConfig(
+                        'webmaster_email'
+                    )
+                );
+                Mail::to(sysConfig('webmaster_email'))->send(
+                    new newTicket($logId, $emailTitle, $content)
+                );
+            }
+
+            PushNotification::send($emailTitle, $content);
+
+            return Response::json(['status' => 'success', 'message' => '提交成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '提交失败']);
+    }
+
+    // 回复工单
+    public function replyTicket(Request $request)
+    {
+        $id = $request->input('id');
+
+        $ticket = Ticket::uid()->with('user')->whereId($id)->firstOrFail();
+
+        if ($request->isMethod('POST')) {
+            $content = clean($request->input('content'));
+            $content = str_replace(["atob", "eval"], "", $content);
+            $content = substr($content, 0, 300);
+
+            if (empty($content)) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '回复内容不能为空']
+                );
+            }
+
+            if ($ticket->status == 2) {
+                return Response::json(
+                    ['status' => 'fail', 'message' => '错误:该工单已关闭']
+                );
+            }
+
+            $obj            = new TicketReply();
+            $obj->ticket_id = $id;
+            $obj->user_id   = Auth::id();
+            $obj->content   = $content;
+            $obj->save();
+
+            if ($obj->id) {
+                // 重新打开工单
+                $ticket->status = 0;
+                $ticket->save();
+
+                $title   = "工单回复提醒";
+                $content = "标题:【" . $ticket->title . "】<br>用户回复:" . $content;
+
+                // 发邮件通知管理员
+                if (sysConfig('webmaster_email')) {
+                    $logId = Helpers::addNotificationLog(
+                        $title,
+                        $content,
+                        1,
+                        sysConfig(
+                            'webmaster_email'
+                        )
+                    );
+                    Mail::to(sysConfig('webmaster_email'))->send(
+                        new replyTicket($logId, $title, $content)
+                    );
+                }
+
+                PushNotification::send($title, $content);
+
+                return Response::json(
+                    ['status' => 'success', 'message' => '回复成功']
+                );
+            }
+
+            return Response::json(['status' => 'fail', 'message' => '回复失败']);
+        }
+
+        $view['ticket']    = $ticket;
+        $view['replyList'] = TicketReply::whereTicketId($id)
+                                        ->with('user')
+                                        ->oldest()
+                                        ->get();
+
+        return view('user.replyTicket', $view);
+    }
+
+    // 关闭工单
+    public function closeTicket(Request $request): ?JsonResponse
+    {
+        $id = $request->input('id');
+
+        $ret = Ticket::uid()->whereId($id)->update(['status' => 2]);
+        if ($ret) {
+            PushNotification::send('工单关闭提醒', '工单:ID' . $id . '用户已手动关闭');
+
+            return Response::json(['status' => 'success', 'message' => '关闭成功']);
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '关闭失败']);
+    }
+
+    // 邀请码
+    public function invite()
+    {
+        if (Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist(
+        )) {
+            return Response::view(
+                'auth.error',
+                ['message' => '本功能对非付费用户禁用!请 <a class="btn btn-sm btn-danger" href="/">返 回</a>'],
+                402
+            );
+        }
+
+        $view['num']              = Auth::getUser()->invite_num; // 还可以生成的邀请码数量
+        $view['inviteList']       = Invite::uid()
+                                          ->with(['invitee', 'inviter'])
+                                          ->paginate(10); // 邀请码列表
+        $view['referral_traffic'] = flowAutoShow(
+            sysConfig('referral_traffic') * MB
+        );
+        $view['referral_percent'] = sysConfig('referral_percent');
+
+        return view('user.invite', $view);
+    }
+
+    // 生成邀请码
+    public function makeInvite(): JsonResponse
+    {
+        $user = Auth::getUser();
+        if ($user->invite_num <= 0) {
+            return Response::json(
+                ['status' => 'fail', 'message' => '生成失败:已无邀请码生成名额']
+            );
+        }
+
+        $obj             = new Invite();
+        $obj->inviter_id = $user->id;
+        $obj->invitee_id = 0;
+        $obj->code       = strtoupper(
+            mb_substr(md5(microtime() . Str::random()), 8, 12)
+        );
+        $obj->status     = 0;
+        $obj->dateline   = date(
+            'Y-m-d H:i:s',
+            strtotime(
+                "+" . sysConfig(
+                    'user_invite_days'
+                ) . " days"
+            )
+        );
+        $obj->save();
+
+        User::uid()->decrement('invite_num', 1);
+
+        return Response::json(['status' => 'success', 'message' => '生成成功']);
+    }
+
+    // 使用优惠券
+    public function redeemCoupon(Request $request): JsonResponse
+    {
+        $coupon_sn  = $request->input('coupon_sn');
+        $good_price = $request->input('price');
+
+        if (empty($coupon_sn)) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'title'   => '使用失败',
+                    'message' => '请输入您的优惠劵!',
+                ]
+            );
+        }
+
+        $coupon = Coupon::whereSn($coupon_sn)->whereIn('type', [1, 2])->first();
+        if ( ! $coupon) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'title'   => '优惠券不存在',
+                    'message' => '请确认优惠券是否输入正确!',
+                ]
+            );
+        }
+
+        if ($coupon->status == 1) {
+            return Response::json(
+                ['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已被使用!']
+            );
+        }
+
+        if ($coupon->status == 2) {
+            return Response::json(
+                ['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已失效!']
+            );
+        }
+
+        if ($coupon->end_time < time()) {
+            $coupon->status = 2;
+            $coupon->save();
+
+            return Response::json(
+                ['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已失效!']
+            );
+        }
+
+        if ($coupon->start_time > time()) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'title'   => '优惠券尚未生效',
+                    'message' => '请等待活动正式开启',
+                ]
+            );
+        }
+
+        if ($good_price < $coupon->rule) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'title'   => '使用条件未满足',
+                    'message' => '请购买价格更高的套餐',
+                ]
+            );
+        }
+
+        $data = [
+            'name'  => $coupon->name,
+            'type'  => $coupon->type,
+            'value' => $coupon->value,
+        ];
+
+        return Response::json(
+            ['status' => 'success', 'data' => $data, 'message' => '优惠券有效']
+        );
+    }
+
+    // 购买服务
+    public function buy($goods_id)
+    {
+        $user  = Auth::getUser();
+        $goods = Goods::whereId($goods_id)->whereStatus(1)->first();
+        if (empty($goods)) {
+            return Redirect::to('services');
+        }
+        // 有重置日时按照重置日为标准,否者就以过期日为标准
+        $dataPlusDays         = $user->reset_time ?: $user->expired_at;
+        $view['dataPlusDays'] = $dataPlusDays > date(
+            'Y-m-d'
+        ) ? Helpers::daysToNow($dataPlusDays) : 0;
+        $view['activePlan']   = Order::userActivePlan()->exists();
+        $view['goods']        = $goods;
+
+        return view('user.buy', $view);
+    }
+
+    // 帮助中心
+    public function help()
+    {
+        //$view['articleList'] = Article::type(1)->orderByDesc('sort')->latest()->limit(10)->paginate(5);
+        $data = [];
+        if (Node::whereIn('type', [1, 4])->whereStatus(1)->exists()) {
+            $data[] = 'ss';
+            //array_push
+        }
+        if (Node::whereType(2)->whereStatus(1)->exists()) {
+            $data[] = 'v2';
+        }
+        if (Node::whereType(3)->whereStatus(1)->exists()) {
+            $data[] = 'trojan';
+        }
+
+        $view['sub'] = $data;
+
+        //付费用户判断
+        $view['not_paying_user'] = Order::uid()->active()->where(
+            'origin_amount',
+            '>',
+            0
+        )->doesntExist();
+        //客户端安装
+        $view['Shadowrocket_install'] = 'itms-services://?action=download-manifest&url=' . sysConfig(
+                'website_url'
+            ) . '/clients/Shadowrocket.plist';
+        $view['Quantumult_install']   = 'itms-services://?action=download-manifest&url=' . sysConfig(
+                'website_url'
+            ) . '/clients/Quantumult.plist';
+        // 订阅连接
+        $subscribe                       = UserSubscribe::whereUserId(
+            Auth::id()
+        )
+                                                        ->firstOrFail();
+        $view['subscribe_status']        = $subscribe->status;
+        $subscribe_link                  = (sysConfig(
+                'subscribe_domain'
+            ) ?: sysConfig(
+                'website_url'
+            )) . '/s/' . $subscribe->code;
+        $view['link']                    = $subscribe_link;
+        $view['subscribe_link']          = 'sub://' . base64url_encode(
+                $subscribe_link
+            );
+        $view['Shadowrocket_link']       = 'shadowrocket://add/sub://' . base64url_encode(
+                $subscribe_link
+            ) . '?remarks=' . (sysConfig('website_name') . '-' . sysConfig(
+                    'website_url'
+                ));
+        $view['Shadowrocket_linkQrcode'] = 'sub://' . base64url_encode(
+                $subscribe_link
+            ) . '#' . base64url_encode(sysConfig('website_name'));
+        $view['Quantumult_linkOut']      = 'quantumult://configuration?server=' . base64url_encode(
+                $subscribe_link
+            ) . '&filter=' . base64url_encode(
+                                               'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Pro.conf'
+                                           ) . '&rejection=' . base64url_encode(
+                                               'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Rejection.conf'
+                                           );
+        $view['Quantumult_linkIn']       = 'quantumult://configuration?server=' . base64url_encode(
+                $subscribe_link
+            ) . '&filter=' . base64url_encode(
+                                               'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/BacktoCN.conf'
+                                           ) . '&rejection=' . base64url_encode(
+                                               'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Rejection.conf'
+                                           );
+
+        return view('user.help', $view);
+    }
+
+    // 更换订阅地址
+    public function exchangeSubscribe(): ?JsonResponse
+    {
+        try {
+            DB::beginTransaction();
+
+            // 更换订阅码
+            Auth::getUser()->subscribe->update(
+                ['code' => Helpers::makeSubscribeCode()]
+            );
+
+            // 更换连接密码
+            Auth::getUser()->update(['passwd' => Str::random()]);
+
+            DB::commit();
+
+            return Response::json(['status' => 'success', 'message' => '更换成功']);
+        } catch (Exception $e) {
+            DB::rollBack();
+
+            Log::error("更换订阅地址异常:" . $e->getMessage());
+
+            return Response::json(
+                ['status' => 'fail', 'message' => '更换失败' . $e->getMessage()]
+            );
+        }
+    }
+
+    // 转换成管理员的身份
+    public function switchToAdmin(): JsonResponse
+    {
+        if ( ! Session::has('admin')) {
+            return Response::json(['status' => 'fail', 'message' => '非法请求']);
+        }
+
+        // 管理员信息重新写入user
+        $user = Auth::loginUsingId(Session::get('admin'));
+        Session::forget('admin');
+        if ($user) {
+            return Response::json(
+                ['status' => 'success', 'message' => "身份切换成功"]
+            );
+        }
+
+        return Response::json(['status' => 'fail', 'message' => '身份切换失败']);
+    }
+
+    // Todo 卡券余额合并至CouponService
+    public function charge(Request $request): ?JsonResponse
+    {
+        $validator = Validator::make(
+            $request->all(),
+            [
+                'coupon_sn' => [
+                    'required',
+                    Rule::exists('coupon', 'sn')->where(
+                        static function ($query) {
+                            $query->whereType(3)->whereStatus(0);
+                        }
+                    ),
+                ],
+            ],
+            ['coupon_sn.required' => '券码不能为空', 'coupon_sn.exists' => '该券不可用']
+        );
+
+        if ($validator->fails()) {
+            return Response::json(
+                [
+                    'status'  => 'fail',
+                    'message' => $validator->getMessageBag()->first(),
+                ]
+            );
+        }
+
+        $coupon = Coupon::whereSn($request->input('coupon_sn'))->firstOrFail();
+
+        try {
+            DB::beginTransaction();
+            // 写入日志
+            $user = Auth::getUser();
+            Helpers::addUserCreditLog(
+                $user->id,
+                0,
+                $user->credit,
+                $user->credit + $coupon->value,
+                $coupon->value,
+                '用户手动充值 - [充值券:' . $request->input('coupon_sn') . ']'
+            );
+
+            // 余额充值
+            (new UserService($user))->updateCredit($coupon->value);
+
+            // 更改卡券状态
+            Coupon::find($coupon->id)->update(['status' => 1]);
+
+            // 写入卡券日志
+            Helpers::addCouponLog('账户余额充值使用', $coupon->id);
+
+            DB::commit();
+
+            return Response::json(['status' => 'success', 'message' => '充值成功']);
+        } catch (Exception $e) {
+            Log::error('卡劵充值错误:' . $e->getMessage());
+            DB::rollBack();
+
+            return Response::json(['status' => 'fail', 'message' => '充值失败']);
+        }
+    }
+
 }

+ 68 - 65
app/Http/Kernel.php

@@ -34,73 +34,76 @@ use Illuminate\Routing\Middleware\ValidateSignature;
 use Illuminate\Session\Middleware\StartSession;
 use Illuminate\View\Middleware\ShareErrorsFromSession;
 
-class Kernel extends HttpKernel {
-	/**
-	 * The application's global HTTP middleware stack.
-	 *
-	 * These middleware are run during every request to your application.
-	 *
-	 * @var array
-	 */
-	protected $middleware = [
-		// \App\Http\Middleware\TrustHosts::class,
-		TrustProxies::class,
-		HandleCors::class,
-		CheckForMaintenanceMode::class,
-		ValidatePostSize::class,
-		TrimStrings::class,
-		ConvertEmptyStringsToNull::class,
-	];
+class Kernel extends HttpKernel
+{
 
-	/**
-	 * The application's route middleware groups.
-	 *
-	 * @var array
-	 */
-	protected $middlewareGroups = [
-		'web' => [
-			EncryptCookies::class,
-			AddQueuedCookiesToResponse::class,
-			StartSession::class,
-			// \Illuminate\Session\Middleware\AuthenticateSession::class,
-			SetLocale::class,
-			ShareErrorsFromSession::class,
-			VerifyCsrfToken::class,
-			SubstituteBindings::class,
-		],
+    /**
+     * The application's global HTTP middleware stack.
+     *
+     * These middleware are run during every request to your application.
+     *
+     * @var array
+     */
+    protected $middleware = [
+        // \App\Http\Middleware\TrustHosts::class,
+        TrustProxies::class,
+        HandleCors::class,
+        CheckForMaintenanceMode::class,
+        ValidatePostSize::class,
+        TrimStrings::class,
+        ConvertEmptyStringsToNull::class,
+    ];
 
-		'api' => [
-			'throttle:60,1',
-			SubstituteBindings::class,
-		],
-	];
+    /**
+     * The application's route middleware groups.
+     *
+     * @var array
+     */
+    protected $middlewareGroups = [
+        'web' => [
+            EncryptCookies::class,
+            AddQueuedCookiesToResponse::class,
+            StartSession::class,
+            // \Illuminate\Session\Middleware\AuthenticateSession::class,
+            SetLocale::class,
+            ShareErrorsFromSession::class,
+            VerifyCsrfToken::class,
+            SubstituteBindings::class,
+        ],
 
-	/**
-	 * The application's route middleware.
-	 *
-	 * These middleware may be assigned to groups or used individually.
-	 *
-	 * @var array
-	 */
-	protected $routeMiddleware = [
-		'auth'             => Authenticate::class,
-		'auth.basic'       => AuthenticateWithBasicAuth::class,
-		'bindings'         => SubstituteBindings::class,
-		'cache.headers'    => SetCacheHeaders::class,
-		'can'              => Authorize::class,
-		'guest'            => RedirectIfAuthenticated::class,
-		'password.confirm' => RequirePassword::class,
-		'signed'           => ValidateSignature::class,
-		'throttle'         => ThrottleRequests::class,
-		'verified'         => EnsureEmailIsVerified::class,
-		'webApi'           => WebApi::class,
-		'isAdmin'          => isAdmin::class,
-		'isAdminLogin'     => isAdminLogin::class,
-		'isLogin'          => isLogin::class,
-		'isMaintenance'    => isMaintenance::class,
-		'isSecurity'       => isSecurity::class,
-		'isForbidden'      => isForbidden::class,
-		'affiliate'        => Affiliate::class,
+        'api' => [
+            'throttle:60,1',
+            SubstituteBindings::class,
+        ],
+    ];
+
+    /**
+     * The application's route middleware.
+     *
+     * These middleware may be assigned to groups or used individually.
+     *
+     * @var array
+     */
+    protected $routeMiddleware = [
+        'auth'             => Authenticate::class,
+        'auth.basic'       => AuthenticateWithBasicAuth::class,
+        'bindings'         => SubstituteBindings::class,
+        'cache.headers'    => SetCacheHeaders::class,
+        'can'              => Authorize::class,
+        'guest'            => RedirectIfAuthenticated::class,
+        'password.confirm' => RequirePassword::class,
+        'signed'           => ValidateSignature::class,
+        'throttle'         => ThrottleRequests::class,
+        'verified'         => EnsureEmailIsVerified::class,
+        'webApi'           => WebApi::class,
+        'isAdmin'          => isAdmin::class,
+        'isAdminLogin'     => isAdminLogin::class,
+        'isLogin'          => isLogin::class,
+        'isMaintenance'    => isMaintenance::class,
+        'isSecurity'       => isSecurity::class,
+        'isForbidden'      => isForbidden::class,
+        'affiliate'        => Affiliate::class,
+
+    ];
 
-	];
 }

+ 20 - 16
app/Http/Middleware/Affiliate.php

@@ -6,21 +6,25 @@ use Closure;
 use Cookie;
 use Illuminate\Http\Request;
 
-class Affiliate {
-	/**
-	 * 返利识别
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		$aff = $request->input('aff', 0);
-		if($aff){
-			Cookie::queue('register_aff', $aff, 129600);
-		}
+class Affiliate
+{
+
+    /**
+     * 返利识别
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        $aff = $request->input('aff', 0);
+        if ($aff) {
+            Cookie::queue('register_aff', $aff, 129600);
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 18 - 12
app/Http/Middleware/Authenticate.php

@@ -4,16 +4,22 @@ namespace App\Http\Middleware;
 
 use Illuminate\Auth\Middleware\Authenticate as Middleware;
 
-class Authenticate extends Middleware {
-	/**
-	 * Get the path the user should be redirected to when they are not authenticated.
-	 *
-	 * @param  \Illuminate\Http\Request  $request
-	 * @return string|null
-	 */
-	protected function redirectTo($request) {
-		if(!$request->expectsJson()){
-			return route('login');
-		}
-	}
+class Authenticate extends Middleware
+{
+
+    /**
+     * Get the path the user should be redirected to when they are not
+     * authenticated.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     *
+     * @return string|null
+     */
+    protected function redirectTo($request)
+    {
+        if ( ! $request->expectsJson()) {
+            return route('login');
+        }
+    }
+
 }

+ 11 - 8
app/Http/Middleware/CheckForMaintenanceMode.php

@@ -4,12 +4,15 @@ namespace App\Http\Middleware;
 
 use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
 
-class CheckForMaintenanceMode extends Middleware {
-	/**
-	 * The URIs that should be reachable while maintenance mode is enabled.
-	 *
-	 * @var array
-	 */
-	protected $except = [//
-	];
+class CheckForMaintenanceMode extends Middleware
+{
+
+    /**
+     * The URIs that should be reachable while maintenance mode is enabled.
+     *
+     * @var array
+     */
+    protected $except = [//
+    ];
+
 }

+ 11 - 8
app/Http/Middleware/EncryptCookies.php

@@ -4,12 +4,15 @@ namespace App\Http\Middleware;
 
 use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
 
-class EncryptCookies extends Middleware {
-	/**
-	 * The names of the cookies that should not be encrypted.
-	 *
-	 * @var array
-	 */
-	protected $except = [//
-	];
+class EncryptCookies extends Middleware
+{
+
+    /**
+     * The names of the cookies that should not be encrypted.
+     *
+     * @var array
+     */
+    protected $except = [//
+    ];
+
 }

+ 21 - 15
app/Http/Middleware/RedirectIfAuthenticated.php

@@ -5,21 +5,27 @@ namespace App\Http\Middleware;
 use App\Providers\RouteServiceProvider;
 use Auth;
 use Closure;
+use Illuminate\Http\Request;
 
-class RedirectIfAuthenticated {
-	/**
-	 * Handle an incoming request.
-	 *
-	 * @param  \Illuminate\Http\Request  $request
-	 * @param  \Closure                  $next
-	 * @param  string|null               $guard
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next, $guard = null) {
-		if(Auth::guard($guard)->check()){
-			return redirect(RouteServiceProvider::HOME);
-		}
+class RedirectIfAuthenticated
+{
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @param  string|null  $guard
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next, $guard = null)
+    {
+        if (Auth::guard($guard)->check()) {
+            return redirect(RouteServiceProvider::HOME);
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 22 - 19
app/Http/Middleware/SetLocale.php

@@ -6,26 +6,29 @@ use Closure;
 use Illuminate\Http\Request;
 use Session;
 
-class SetLocale {
-	/**
-	 * 变更语言
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		if(Session::has('locale')){
-			app()->setLocale(Session::get('locale'));
-		}
+class SetLocale
+{
 
-		if($request->query('locale')){
-			Session::put('locale', $request->query('locale'));
-			app()->setLocale($request->query('locale'));
-		}
+    /**
+     * 变更语言
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        if (Session::has('locale')) {
+            app()->setLocale(Session::get('locale'));
+        }
 
-		return $next($request);
-	}
+        if ($request->query('locale')) {
+            Session::put('locale', $request->query('locale'));
+            app()->setLocale($request->query('locale'));
+        }
+
+        return $next($request);
+    }
 
 }

+ 13 - 10
app/Http/Middleware/TrimStrings.php

@@ -4,14 +4,17 @@ namespace App\Http\Middleware;
 
 use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
 
-class TrimStrings extends Middleware {
-	/**
-	 * The names of the attributes that should not be trimmed.
-	 *
-	 * @var array
-	 */
-	protected $except = [
-		'password',
-		'password_confirmation',
-	];
+class TrimStrings extends Middleware
+{
+
+    /**
+     * The names of the attributes that should not be trimmed.
+     *
+     * @var array
+     */
+    protected $except = [
+        'password',
+        'password_confirmation',
+    ];
+
 }

+ 15 - 11
app/Http/Middleware/TrustHosts.php

@@ -4,15 +4,19 @@ namespace App\Http\Middleware;
 
 use Illuminate\Http\Middleware\TrustHosts as Middleware;
 
-class TrustHosts extends Middleware {
-	/**
-	 * Get the host patterns that should be trusted.
-	 *
-	 * @return array
-	 */
-	public function hosts() {
-		return [
-			$this->allSubdomainsOfApplicationUrl(),
-		];
-	}
+class TrustHosts extends Middleware
+{
+
+    /**
+     * Get the host patterns that should be trusted.
+     *
+     * @return array
+     */
+    public function hosts()
+    {
+        return [
+            $this->allSubdomainsOfApplicationUrl(),
+        ];
+    }
+
 }

+ 16 - 13
app/Http/Middleware/TrustProxies.php

@@ -5,18 +5,21 @@ namespace App\Http\Middleware;
 use Fideloper\Proxy\TrustProxies as Middleware;
 use Illuminate\Http\Request;
 
-class TrustProxies extends Middleware {
-	/**
-	 * The trusted proxies for this application.
-	 *
-	 * @var array|string|null
-	 */
-	protected $proxies;
+class TrustProxies extends Middleware
+{
+
+    /**
+     * The trusted proxies for this application.
+     *
+     * @var array|string|null
+     */
+    protected $proxies;
+
+    /**
+     * The headers that should be used to detect proxies.
+     *
+     * @var int
+     */
+    protected $headers = Request::HEADER_X_FORWARDED_ALL;
 
-	/**
-	 * The headers that should be used to detect proxies.
-	 *
-	 * @var int
-	 */
-	protected $headers = Request::HEADER_X_FORWARDED_ALL;
 }

+ 12 - 9
app/Http/Middleware/VerifyCsrfToken.php

@@ -4,13 +4,16 @@ namespace App\Http\Middleware;
 
 use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
 
-class VerifyCsrfToken extends Middleware {
-	/**
-	 * The URIs that should be excluded from CSRF verification.
-	 *
-	 * @var array
-	 */
-	protected $except = [
-		"callback/notify"
-	];
+class VerifyCsrfToken extends Middleware
+{
+
+    /**
+     * The URIs that should be excluded from CSRF verification.
+     *
+     * @var array
+     */
+    protected $except = [
+        "callback/notify",
+    ];
+
 }

+ 50 - 43
app/Http/Middleware/WebApi.php

@@ -8,47 +8,54 @@ use Closure;
 use Illuminate\Http\JsonResponse;
 use Response;
 
-class WebApi {
-	/**
-	 * Handle an incoming request.
-	 *
-	 * @param           $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		$id = $request->id;
-		$key = $request->header('key');
-		$time = $request->header('timestamp');
-
-		if(!isset($key)){// 未提供 key
-			return $this->returnData('Your key is null!');
-		}
-
-		if(!isset($id)){// 未提供 node
-			return $this->returnData('Your Node Id is null!');
-		}
-
-		$node = Node::find($id);
-		if(!$node){// node不存在
-			return $this->returnData('Unknown Node!');
-		}
-
-		$nodeAuth = NodeAuth::whereNodeId($id)->first();
-		if(!$nodeAuth || $key !== $nodeAuth->key){// key不存在/不匹配
-			return $this->returnData('Token is invalid!');
-		}
-
-		if(abs($time - time()) >= 300){// 时差超过5分钟
-			return $this->returnData('Please resynchronize the server time!');
-		}
-
-		return $next($request);
-	}
-
-	// 返回数据
-	public function returnData($message): JsonResponse {
-		return Response::json(['status' => 'fail', 'code' => 404, 'message' => $message]);
-	}
+class WebApi
+{
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param           $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        $id   = $request->id;
+        $key  = $request->header('key');
+        $time = $request->header('timestamp');
+
+        if ( ! isset($key)) {// 未提供 key
+            return $this->returnData('Your key is null!');
+        }
+
+        if ( ! isset($id)) {// 未提供 node
+            return $this->returnData('Your Node Id is null!');
+        }
+
+        $node = Node::find($id);
+        if ( ! $node) {// node不存在
+            return $this->returnData('Unknown Node!');
+        }
+
+        $nodeAuth = NodeAuth::whereNodeId($id)->first();
+        if ( ! $nodeAuth || $key !== $nodeAuth->key) {// key不存在/不匹配
+            return $this->returnData('Token is invalid!');
+        }
+
+        if (abs($time - time()) >= 300) {// 时差超过5分钟
+            return $this->returnData('Please resynchronize the server time!');
+        }
+
+        return $next($request);
+    }
+
+    // 返回数据
+    public function returnData($message): JsonResponse
+    {
+        return Response::json(
+            ['status' => 'fail', 'code' => 404, 'message' => $message]
+        );
+    }
+
 }

+ 19 - 15
app/Http/Middleware/isAdmin.php

@@ -7,20 +7,24 @@ use Closure;
 use Illuminate\Http\Request;
 use Redirect;
 
-class isAdmin {
-	/**
-	 * 校验是否为管理员身份
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		if(!Auth::getUser()->is_admin){
-			return Redirect::to('/');
-		}
+class isAdmin
+{
+
+    /**
+     * 校验是否为管理员身份
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        if ( ! Auth::getUser()->is_admin) {
+            return Redirect::to('/');
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 19 - 15
app/Http/Middleware/isAdminlogin.php

@@ -6,20 +6,24 @@ use Closure;
 use Illuminate\Http\Request;
 use Redirect;
 
-class isAdminLogin {
-	/**
-	 * Handle an incoming request.
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		if(auth()->guest()){
-			return Redirect::to('admin/login');
-		}
+class isAdminLogin
+{
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        if (auth()->guest()) {
+            return Redirect::to('admin/login');
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 108 - 72
app/Http/Middleware/isForbidden.php

@@ -10,86 +10,122 @@ use Illuminate\Http\Request;
 use Log;
 use Response;
 
-class isForbidden {
-	/**
-	 * 限制机器人、指定IP访问
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		// 拒绝机器人访问
-		if(sysConfig('is_forbid_robot') && Agent::isRobot()){
-			Log::info("识别到机器人访问(".getClientIp().")");
+class isForbidden
+{
 
-			return Response::view('auth.error', ['message' => trans('error.ForbiddenRobot')], 403);
-		}
+    /**
+     * 限制机器人、指定IP访问
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        // 拒绝机器人访问
+        if (sysConfig('is_forbid_robot') && Agent::isRobot()) {
+            Log::info("识别到机器人访问(" . getClientIp() . ")");
 
-		// 拒绝通过订阅链接域名访问网站,防止网站被探测
-		if(false !== strpos(sysConfig('subscribe_domain'), $request->getHost())
-		   && !str_contains(sysConfig('subscribe_domain'), sysConfig('website_url'))){
-			Log::info("识别到通过订阅链接访问,强制跳转至百度(".getClientIp().")");
+            return Response::view(
+                'auth.error',
+                ['message' => trans('error.ForbiddenRobot')],
+                403
+            );
+        }
 
-			return redirect('https://www.baidu.com');
-		}
+        // 拒绝通过订阅链接域名访问网站,防止网站被探测
+        if (false !== strpos(sysConfig('subscribe_domain'), $request->getHost())
+            && ! str_contains(
+                sysConfig('subscribe_domain'),
+                sysConfig('website_url')
+            )) {
+            Log::info("识别到通过订阅链接访问,强制跳转至百度(" . getClientIp() . ")");
 
-		$ip = getClientIP();
-		if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
-			Log::info('识别到IPv6,尝试解析:'.$ip);
-			$isIPv6 = true;
-			$ipInfo = getIPInfo($ip);
-		}else{
-			$isIPv6 = false;
-			$ipInfo = QQWry::ip($ip); // 通过纯真IP库解析IPv4信息
-			if(isset($ipInfo['error'])){
-				Log::info('无法识别IPv4,尝试使用IPIP的IP库解析:'.$ip);
-				$ipip = IPIP::ip($ip);
-				$ipInfo = [
-					'country'  => $ipip['country_name'],
-					'province' => $ipip['region_name'],
-					'city'     => $ipip['city_name']
-				];
-			}else{
-				// 判断纯真IP库获取的国家信息是否与IPIP的IP库获取的信息一致,不一致则用IPIP的(因为纯真IP库的非大陆IP准确率较低)
-				$ipip = IPIP::ip($ip);
-				if($ipInfo['country'] != $ipip['country_name']){
-					$ipInfo['country'] = $ipip['country_name'];
-					$ipInfo['province'] = $ipip['region_name'];
-					$ipInfo['city'] = $ipip['city_name'];
-				}
-			}
-		}
+            return redirect('https://www.baidu.com');
+        }
 
-		// 拒绝无IP请求
-		if(empty($ipInfo) || empty($ipInfo['country'])){
-			return Response::view('auth.error', ['message' => trans('error.ForbiddenAccess')], 403);
-		}
+        $ip = getClientIP();
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+            Log::info('识别到IPv6,尝试解析:' . $ip);
+            $isIPv6 = true;
+            $ipInfo = getIPInfo($ip);
+        } else {
+            $isIPv6 = false;
+            $ipInfo = QQWry::ip($ip); // 通过纯真IP库解析IPv4信息
+            if (isset($ipInfo['error'])) {
+                Log::info('无法识别IPv4,尝试使用IPIP的IP库解析:' . $ip);
+                $ipip   = IPIP::ip($ip);
+                $ipInfo = [
+                    'country'  => $ipip['country_name'],
+                    'province' => $ipip['region_name'],
+                    'city'     => $ipip['city_name'],
+                ];
+            } else {
+                // 判断纯真IP库获取的国家信息是否与IPIP的IP库获取的信息一致,不一致则用IPIP的(因为纯真IP库的非大陆IP准确率较低)
+                $ipip = IPIP::ip($ip);
+                if ($ipInfo['country'] != $ipip['country_name']) {
+                    $ipInfo['country']  = $ipip['country_name'];
+                    $ipInfo['province'] = $ipip['region_name'];
+                    $ipInfo['city']     = $ipip['city_name'];
+                }
+            }
+        }
 
-		if(!in_array($ipInfo['country'], ['本机地址', '局域网'])){
-			// 拒绝大陆IP访问
-			if(sysConfig('is_forbid_china')){
-				if(($isIPv6 && $ipInfo['country'] === 'China')
-				   || ($ipInfo['country'] === '中国'
-				       && !in_array($ipInfo['province'], ['香港', '澳门', '台湾']))){
-					Log::info('识别到大陆IP,拒绝访问:'.$ip);
+        // 拒绝无IP请求
+        if (empty($ipInfo) || empty($ipInfo['country'])) {
+            return Response::view(
+                'auth.error',
+                ['message' => trans('error.ForbiddenAccess')],
+                403
+            );
+        }
 
-					return Response::view('auth.error', ['message' => trans('error.ForbiddenChina')], 403);
-				}
-			}
+        if ( ! in_array($ipInfo['country'], ['本机地址', '局域网'])) {
+            // 拒绝大陆IP访问
+            if (sysConfig('is_forbid_china')) {
+                if (($isIPv6 && $ipInfo['country'] === 'China')
+                    || ($ipInfo['country'] === '中国'
+                        && ! in_array(
+                            $ipInfo['province'],
+                            ['香港', '澳门', '台湾']
+                        ))) {
+                    Log::info('识别到大陆IP,拒绝访问:' . $ip);
 
-			// 拒绝非大陆IP访问
-			if(sysConfig('is_forbid_oversea')){
-				if(($isIPv6 && $ipInfo['country'] !== 'China') || $ipInfo['country'] !== '中国'
-				   || in_array($ipInfo['province'], ['香港', '澳门', '台湾'])){
-					Log::info('识别到海外IP,拒绝访问:'.$ip.' - '.$ipInfo['country']);
+                    return Response::view(
+                        'auth.error',
+                        [
+                            'message' => trans(
+                                'error.ForbiddenChina'
+                            ),
+                        ],
+                        403
+                    );
+                }
+            }
 
-					return Response::view('auth.error', ['message' => trans('error.ForbiddenOversea')], 403);
-				}
-			}
-		}
+            // 拒绝非大陆IP访问
+            if (sysConfig('is_forbid_oversea')) {
+                if (($isIPv6 && $ipInfo['country'] !== 'China') || $ipInfo['country'] !== '中国'
+                    || in_array($ipInfo['province'], ['香港', '澳门', '台湾'])) {
+                    Log::info(
+                        '识别到海外IP,拒绝访问:' . $ip . ' - ' . $ipInfo['country']
+                    );
+
+                    return Response::view(
+                        'auth.error',
+                        [
+                            'message' => trans(
+                                'error.ForbiddenOversea'
+                            ),
+                        ],
+                        403
+                    );
+                }
+            }
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 19 - 15
app/Http/Middleware/isLogin.php

@@ -6,20 +6,24 @@ use Closure;
 use Illuminate\Http\Request;
 use Redirect;
 
-class isLogin {
-	/**
-	 * 校验是否已登录
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		if(auth()->guest()){
-			return Redirect::to('login');
-		}
+class isLogin
+{
+
+    /**
+     * 校验是否已登录
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        if (auth()->guest()) {
+            return Redirect::to('login');
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 25 - 18
app/Http/Middleware/isMaintenance.php

@@ -5,23 +5,30 @@ namespace App\Http\Middleware;
 use Closure;
 use Illuminate\Http\Request;
 
-class isMaintenance {
-	/**
-	 * 校验是否开启维护模式
-	 *
-	 * @param  Request  $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		if(sysConfig('maintenance_mode')){
-			return response()->view('auth.maintenance', [
-				'message' => sysConfig('maintenance_content'),
-				'time'    => sysConfig('maintenance_time')?: '0'
-			]);
-		}
+class isMaintenance
+{
+
+    /**
+     * 校验是否开启维护模式
+     *
+     * @param  Request  $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        if (sysConfig('maintenance_mode')) {
+            return response()->view(
+                'auth.maintenance',
+                [
+                    'message' => sysConfig('maintenance_content'),
+                    'time'    => sysConfig('maintenance_time') ?: '0',
+                ]
+            );
+        }
+
+        return $next($request);
+    }
 
-		return $next($request);
-	}
 }

+ 41 - 29
app/Http/Middleware/isSecurity.php

@@ -7,33 +7,45 @@ use Closure;
 use Log;
 use Response;
 
-class isSecurity {
-	/**
-	 * 是否需要安全码才访问(仅用于登录页)
-	 *
-	 * @param           $request
-	 * @param  Closure  $next
-	 *
-	 * @return mixed
-	 */
-	public function handle($request, Closure $next) {
-		$ip = getClientIP();
-		$code = $request->securityCode;
-		$cacheKey = 'SecurityLogin_'.ip2long($ip);
-		$websiteSecurityCode = sysConfig('website_security_code');
-
-		if($websiteSecurityCode && !Cache::has($cacheKey)){
-			if($code != $websiteSecurityCode){
-				Log::info("拒绝非安全入口访问(".$ip.")");
-
-				return Response::view('auth.error', [
-					'message' => trans('error.SecurityError').', '.trans('error.Visit').'<a href="/login?securityCode=" target="_self">'.trans('error.SecurityEnter').'</a>'
-				], 403);
-			}
-
-			Cache::put($cacheKey, $ip, 7200); // 2小时之内无需再次输入安全码访问
-		}
-
-		return $next($request);
-	}
+class isSecurity
+{
+
+    /**
+     * 是否需要安全码才访问(仅用于登录页)
+     *
+     * @param           $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        $ip                  = getClientIP();
+        $code                = $request->securityCode;
+        $cacheKey            = 'SecurityLogin_' . ip2long($ip);
+        $websiteSecurityCode = sysConfig('website_security_code');
+
+        if ($websiteSecurityCode && ! Cache::has($cacheKey)) {
+            if ($code != $websiteSecurityCode) {
+                Log::info("拒绝非安全入口访问(" . $ip . ")");
+
+                return Response::view(
+                    'auth.error',
+                    [
+                        'message' => trans('error.SecurityError') . ', 
+                        ' . trans(
+                                'error.Visit'
+                            ) . '<a href="/login?securityCode=" target="_self">' .
+                                     trans('error.SecurityEnter') . '</a>',
+                    ],
+                    403
+                );
+            }
+
+            Cache::put($cacheKey, $ip, 7200); // 2小时之内无需再次输入安全码访问
+        }
+
+        return $next($request);
+    }
+
 }

+ 51 - 37
app/Jobs/VNet/addUser.php

@@ -10,41 +10,55 @@ use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 
-class addUser implements ShouldQueue {
-	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
-
-	private $data;
-	private $nodes;
-
-	public function __construct($userIds, $nodes) {
-		$this->nodes = $nodes;
-		$data = [];
-		foreach(User::findMany($userIds) as $user){
-			$data[] = [
-				'uid'         => $user->id,
-				'port'        => $user->port,
-				'passwd'      => $user->passwd,
-				'speed_limit' => $user->speed_limit,
-				'enable'      => $user->enable
-			];
-		}
-
-		$this->data = $data;
-	}
-
-	public function handle(): void {
-		foreach($this->nodes as $node){
-			$this->send(($node->server?: $node->ip).':'.$node->push_port, $node->auth->secret);
-		}
-	}
-
-	private function send($host, $secret): void {
-		$client = new Client([
-			'base_uri' => $host,
-			'timeout'  => 15,
-			'headers'  => ['secret' => $secret]
-		]);
-
-		$client->post('api/v2/user/add/list', ['json' => $this->data]);
-	}
+class addUser implements ShouldQueue
+{
+
+    use Dispatchable;
+    use InteractsWithQueue;
+    use Queueable;
+    use SerializesModels;
+
+    private $data;
+    private $nodes;
+
+    public function __construct($userIds, $nodes)
+    {
+        $this->nodes = $nodes;
+        $data        = [];
+        foreach (User::findMany($userIds) as $user) {
+            $data[] = [
+                'uid'         => $user->id,
+                'port'        => $user->port,
+                'passwd'      => $user->passwd,
+                'speed_limit' => $user->speed_limit,
+                'enable'      => $user->enable,
+            ];
+        }
+
+        $this->data = $data;
+    }
+
+    public function handle(): void
+    {
+        foreach ($this->nodes as $node) {
+            $this->send(
+                ($node->server ?: $node->ip) . ':' . $node->push_port,
+                $node->auth->secret
+            );
+        }
+    }
+
+    private function send($host, $secret): void
+    {
+        $client = new Client(
+            [
+                'base_uri' => $host,
+                'timeout'  => 15,
+                'headers'  => ['secret' => $secret],
+            ]
+        );
+
+        $client->post('api/v2/user/add/list', ['json' => $this->data]);
+    }
+
 }

+ 44 - 30
app/Jobs/VNet/delUser.php

@@ -9,34 +9,48 @@ use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 
-class delUser implements ShouldQueue {
-	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
-
-	private $userIds;
-	private $nodes;
-
-	public function __construct($userIds, $nodes) {
-		$this->userIds = $userIds;
-		$this->nodes = $nodes;
-	}
-
-	public function handle(): void {
-		foreach($this->nodes as $node){
-			$this->send(($node->server?: $node->ip).':'.$node->push_port, $node->auth->secret);
-		}
-	}
-
-	private function send($host, $secret): void {
-		$client = new Client([
-			'base_uri' => $host,
-			'timeout'  => 15,
-			'headers'  => ['secret' => $secret]
-		]);
-
-		if(is_array($this->userIds)){
-			$client->post('api/v2/user/del/list', ['json' => $this->userIds]);
-		}else{
-			$client->post('api/user/del/'.$this->userIds);
-		}
-	}
+class delUser implements ShouldQueue
+{
+
+    use Dispatchable;
+    use InteractsWithQueue;
+    use Queueable;
+    use SerializesModels;
+
+    private $userIds;
+    private $nodes;
+
+    public function __construct($userIds, $nodes)
+    {
+        $this->userIds = $userIds;
+        $this->nodes   = $nodes;
+    }
+
+    public function handle(): void
+    {
+        foreach ($this->nodes as $node) {
+            $this->send(
+                ($node->server ?: $node->ip) . ':' . $node->push_port,
+                $node->auth->secret
+            );
+        }
+    }
+
+    private function send($host, $secret): void
+    {
+        $client = new Client(
+            [
+                'base_uri' => $host,
+                'timeout'  => 15,
+                'headers'  => ['secret' => $secret],
+            ]
+        );
+
+        if (is_array($this->userIds)) {
+            $client->post('api/v2/user/del/list', ['json' => $this->userIds]);
+        } else {
+            $client->post('api/user/del/' . $this->userIds);
+        }
+    }
+
 }

+ 46 - 32
app/Jobs/VNet/editUser.php

@@ -10,36 +10,50 @@ use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 
-class editUser implements ShouldQueue {
-	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
-
-	private $data;
-	private $nodes;
-
-	public function __construct(User $user, $nodes) {
-		$this->nodes = $nodes;
-		$this->data = [
-			'uid'         => $user->id,
-			'port'        => (int) $user->port,
-			'passwd'      => $user->passwd,
-			'speed_limit' => $user->speed_limit,
-			'enable'      => (int) $user->enable
-		];
-	}
-
-	public function handle(): void {
-		foreach($this->nodes as $node){
-			$this->send(($node->server?: $node->ip).':'.$node->push_port, $node->auth->secret);
-		}
-	}
-
-	private function send($host, $secret): void {
-		$client = new Client([
-			'base_uri' => $host,
-			'timeout'  => 15,
-			'headers'  => ['secret' => $secret]
-		]);
-
-		$client->post('api/user/edit', ['json' => $this->data]);
-	}
+class editUser implements ShouldQueue
+{
+
+    use Dispatchable;
+    use InteractsWithQueue;
+    use Queueable;
+    use SerializesModels;
+
+    private $data;
+    private $nodes;
+
+    public function __construct(User $user, $nodes)
+    {
+        $this->nodes = $nodes;
+        $this->data  = [
+            'uid'         => $user->id,
+            'port'        => (int)$user->port,
+            'passwd'      => $user->passwd,
+            'speed_limit' => $user->speed_limit,
+            'enable'      => (int)$user->enable,
+        ];
+    }
+
+    public function handle(): void
+    {
+        foreach ($this->nodes as $node) {
+            $this->send(
+                ($node->server ?: $node->ip) . ':' . $node->push_port,
+                $node->auth->secret
+            );
+        }
+    }
+
+    private function send($host, $secret): void
+    {
+        $client = new Client(
+            [
+                'base_uri' => $host,
+                'timeout'  => 15,
+                'headers'  => ['secret' => $secret],
+            ]
+        );
+
+        $client->post('api/user/edit', ['json' => $this->data]);
+    }
+
 }

+ 70 - 51
app/Jobs/VNet/reloadNode.php

@@ -10,62 +10,81 @@ use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 use Log;
 
-class reloadNode implements ShouldQueue {
-	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+class reloadNode implements ShouldQueue
+{
 
-	private $nodes;
+    use Dispatchable;
+    use InteractsWithQueue;
+    use Queueable;
+    use SerializesModels;
 
-	public function __construct($nodes) {
-		$this->nodes = $nodes;
-	}
+    private $nodes;
 
-	public function handle(): bool {
-		$allSuccess = true;
-		foreach($this->nodes as $node){
-			$ret = $this->send(($node->server?: $node->ip).':'.$node->push_port, $node->auth->secret, [
-				'id'             => $node->id,
-				'port'           => (string) $node->port,
-				'passwd'         => $node->passwd?: '',
-				'method'         => $node->method,
-				'protocol'       => $node->protocol,
-				'obfs'           => $node->obfs,
-				'protocol_param' => $node->protocol_param,
-				'obfs_param'     => $node->obfs_param?: '',
-				'push_port'      => $node->push_port,
-				'single'         => $node->single,
-				'secret'         => $node->auth->secret,
-				//			'is_udp'         => $node->is_udp,
-				//			'speed_limit'    => $node->speed_limit,
-				//			'client_limit'   => $node->client_limit,
-				//			'redirect_url'   => (string) sysConfig('redirect_url')
-			]);
+    public function __construct($nodes)
+    {
+        $this->nodes = $nodes;
+    }
 
-			if(!$ret){
-				$allSuccess = false;
-			}
-		}
+    public function handle(): bool
+    {
+        $allSuccess = true;
+        foreach ($this->nodes as $node) {
+            $ret = $this->send(
+                ($node->server ?: $node->ip) . ':' . $node->push_port,
+                $node->auth->secret,
+                [
+                    'id'             => $node->id,
+                    'port'           => (string)$node->port,
+                    'passwd'         => $node->passwd ?: '',
+                    'method'         => $node->method,
+                    'protocol'       => $node->protocol,
+                    'obfs'           => $node->obfs,
+                    'protocol_param' => $node->protocol_param,
+                    'obfs_param'     => $node->obfs_param ?: '',
+                    'push_port'      => $node->push_port,
+                    'single'         => $node->single,
+                    'secret'         => $node->auth->secret,
+                    //			'is_udp'         => $node->is_udp,
+                    //			'speed_limit'    => $node->speed_limit,
+                    //			'client_limit'   => $node->client_limit,
+                    //			'redirect_url'   => (string) sysConfig('redirect_url')
+                ]
+            );
 
-		return $allSuccess;
-	}
+            if ( ! $ret) {
+                $allSuccess = false;
+            }
+        }
 
-	public function send($host, $secret, $data): bool {
-		$client = new Client([
-			'base_uri' => $host,
-			'timeout'  => 15,
-			'headers'  => ['secret' => $secret]
-		]);
+        return $allSuccess;
+    }
+
+    public function send($host, $secret, $data): bool
+    {
+        $client = new Client(
+            [
+                'base_uri' => $host,
+                'timeout'  => 15,
+                'headers'  => ['secret' => $secret],
+            ]
+        );
+
+        $ret = $client->post('api/v2/node/reload', ['json' => $data]);
+        if ($ret->getStatusCode() == 200) {
+            $message = json_decode($ret->getBody(), true);
+            if (array_key_exists('success', $message) && array_key_exists(
+                    'content',
+                    $message
+                )) {
+                if ($message['success']) {
+                    return true;
+                }
+                Log::error('重载节点失败:' . $host . ' 反馈:' . $message['content']);
+            }
+        }
+        Log::error('重载节点失败url: ' . $host);
+
+        return false;
+    }
 
-		$ret = $client->post('api/v2/node/reload', ['json' => $data]);
-		if($ret->getStatusCode() == 200){
-			$message = json_decode($ret->getBody(), true);
-			if(array_key_exists('success', $message) && array_key_exists('content', $message)){
-				if($message['success']){
-					return true;
-				}
-				Log::error('重载节点失败:'.$host.' 反馈:'.$message['content']);
-			}
-		}
-		Log::error('重载节点失败url: '.$host);
-		return false;
-	}
 }

+ 30 - 21
app/Mail/activeUser.php

@@ -9,25 +9,34 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class activeUser extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $activeUserUrl; // 激活用户URL
-
-	public function __construct($id, $activeUserUrl) {
-		$this->id = $id;
-		$this->activeUserUrl = $activeUserUrl;
-	}
-
-	public function build(): activeUser {
-		return $this->view('emails.activeUser')->subject('激活账号')->with([
-			'activeUserUrl' => $this->activeUserUrl
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class activeUser extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $activeUserUrl; // 激活用户URL
+
+    public function __construct($id, $activeUserUrl)
+    {
+        $this->id            = $id;
+        $this->activeUserUrl = $activeUserUrl;
+    }
+
+    public function build(): activeUser
+    {
+        return $this->view('emails.activeUser')->subject('激活账号')->with(
+            ['activeUserUrl' => $this->activeUserUrl]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 35 - 24
app/Mail/closeTicket.php

@@ -9,28 +9,39 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class closeTicket extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $title; // 工单标题
-	protected $content; // 工单内容
-
-	public function __construct($id, $title, $content) {
-		$this->id = $id;
-		$this->title = $title;
-		$this->content = $content;
-	}
-
-	public function build(): closeTicket {
-		return $this->view('emails.closeTicket')->subject('工单关闭提醒')->with([
-			'title'   => $this->title,
-			'content' => $this->content
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class closeTicket extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $title; // 工单标题
+    protected $content; // 工单内容
+
+    public function __construct($id, $title, $content)
+    {
+        $this->id      = $id;
+        $this->title   = $title;
+        $this->content = $content;
+    }
+
+    public function build(): closeTicket
+    {
+        return $this->view('emails.closeTicket')->subject('工单关闭提醒')->with(
+            [
+                'title'   => $this->title,
+                'content' => $this->content,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 35 - 24
app/Mail/newTicket.php

@@ -9,28 +9,39 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class newTicket extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $title; // 工单标题
-	protected $content; // 工单内容
-
-	public function __construct($id, $title, $content) {
-		$this->id = $id;
-		$this->title = $title;
-		$this->content = $content;
-	}
-
-	public function build(): newTicket {
-		return $this->view('emails.newTicket')->subject('新工单提醒')->with([
-			'title'   => $this->title,
-			'content' => $this->content
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class newTicket extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $title; // 工单标题
+    protected $content; // 工单内容
+
+    public function __construct($id, $title, $content)
+    {
+        $this->id      = $id;
+        $this->title   = $title;
+        $this->content = $content;
+    }
+
+    public function build(): newTicket
+    {
+        return $this->view('emails.newTicket')->subject('新工单提醒')->with(
+            [
+                'title'   => $this->title,
+                'content' => $this->content,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 29 - 19
app/Mail/nodeCrashWarning.php

@@ -9,24 +9,34 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class nodeCrashWarning extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
+class nodeCrashWarning extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+
+    public function __construct($id)
+    {
+        $this->id = $id;
+    }
+
+    public function build(): nodeCrashWarning
+    {
+        return $this->view('emails.nodeCrashWarning')->subject('节点阻断警告')->with(
+            [
+                'content' => NotificationLog::find($this->id)->content,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
 
-	protected $id; // 邮件记录ID
-
-	public function __construct($id) {
-		$this->id = $id;
-	}
-
-	public function build(): nodeCrashWarning {
-
-		return $this->view('emails.nodeCrashWarning')->subject('节点阻断警告')->with([
-			'content' => NotificationLog::find($this->id)->content
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
 }

+ 35 - 24
app/Mail/replyTicket.php

@@ -9,28 +9,39 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class replyTicket extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $title; // 工单标题
-	protected $content; // 工单内容
-
-	public function __construct($id, $title, $content) {
-		$this->id = $id;
-		$this->title = $title;
-		$this->content = $content;
-	}
-
-	public function build(): replyTicket {
-		return $this->view('emails.replyTicket')->subject('工单回复提醒')->with([
-			'title'   => $this->title,
-			'content' => $this->content
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class replyTicket extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $title; // 工单标题
+    protected $content; // 工单内容
+
+    public function __construct($id, $title, $content)
+    {
+        $this->id      = $id;
+        $this->title   = $title;
+        $this->content = $content;
+    }
+
+    public function build(): replyTicket
+    {
+        return $this->view('emails.replyTicket')->subject('工单回复提醒')->with(
+            [
+                'title'   => $this->title,
+                'content' => $this->content,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 32 - 21
app/Mail/resetPassword.php

@@ -9,25 +9,36 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class resetPassword extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $resetPasswordUrl; // 重置密码URL
-
-	public function __construct($id, $resetPasswordUrl) {
-		$this->id = $id;
-		$this->resetPasswordUrl = $resetPasswordUrl;
-	}
-
-	public function build(): resetPassword {
-		return $this->view('emails.resetPassword')->subject('重置密码')->with([
-			'resetPasswordUrl' => $this->resetPasswordUrl
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class resetPassword extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $resetPasswordUrl; // 重置密码URL
+
+    public function __construct($id, $resetPasswordUrl)
+    {
+        $this->id               = $id;
+        $this->resetPasswordUrl = $resetPasswordUrl;
+    }
+
+    public function build(): resetPassword
+    {
+        return $this->view('emails.resetPassword')->subject('重置密码')->with(
+            [
+                'resetPasswordUrl' => $this->resetPasswordUrl,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 32 - 21
app/Mail/sendUserInfo.php

@@ -9,25 +9,36 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class sendUserInfo extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $content; // 账号信息
-
-	public function __construct($id, $content) {
-		$this->id = $id;
-		$this->content = $content;
-	}
-
-	public function build(): sendUserInfo {
-		return $this->view('emails.sendUserInfo')->subject('发送账号信息')->with([
-			'content' => $this->content
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class sendUserInfo extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $content; // 账号信息
+
+    public function __construct($id, $content)
+    {
+        $this->id      = $id;
+        $this->content = $content;
+    }
+
+    public function build(): sendUserInfo
+    {
+        return $this->view('emails.sendUserInfo')->subject('发送账号信息')->with(
+            [
+                'content' => $this->content,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 32 - 21
app/Mail/sendVerifyCode.php

@@ -9,25 +9,36 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class sendVerifyCode extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $code; // 要发送的验证码
-
-	public function __construct($id, $code) {
-		$this->id = $id;
-		$this->code = $code;
-	}
-
-	public function build(): sendVerifyCode {
-		return $this->view('emails.sendVerifyCode')->subject('发送注册验证码')->with([
-			'code' => $this->code
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class sendVerifyCode extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $code; // 要发送的验证码
+
+    public function __construct($id, $code)
+    {
+        $this->id   = $id;
+        $this->code = $code;
+    }
+
+    public function build(): sendVerifyCode
+    {
+        return $this->view('emails.sendVerifyCode')->subject('发送注册验证码')->with(
+            [
+                'code' => $this->code,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 32 - 21
app/Mail/userExpireWarning.php

@@ -9,25 +9,36 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class userExpireWarning extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $lastCanUseDays; // 剩余可用天数
-
-	public function __construct($id, $lastCanUseDays) {
-		$this->id = $id;
-		$this->lastCanUseDays = $lastCanUseDays;
-	}
-
-	public function build(): userExpireWarning {
-		return $this->view('emails.userExpireWarning')->subject('账号过期提醒')->with([
-			'lastCanUseDays' => $this->lastCanUseDays
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class userExpireWarning extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $lastCanUseDays; // 剩余可用天数
+
+    public function __construct($id, $lastCanUseDays)
+    {
+        $this->id             = $id;
+        $this->lastCanUseDays = $lastCanUseDays;
+    }
+
+    public function build(): userExpireWarning
+    {
+        return $this->view('emails.userExpireWarning')->subject('账号过期提醒')->with(
+            [
+                'lastCanUseDays' => $this->lastCanUseDays,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 22 - 13
app/Mail/userExpireWarningToday.php

@@ -9,21 +9,30 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class userExpireWarningToday extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
+class userExpireWarningToday extends Mailable implements ShouldQueue
+{
 
-	protected $id; // 邮件记录ID
+    use Queueable;
+    use SerializesModels;
 
-	public function __construct($id) {
-		$this->id = $id;
-	}
+    protected $id; // 邮件记录ID
 
-	public function build(): userExpireWarningToday {
-		return $this->view('emails.userExpireWarningToday')->subject('账号过期提醒');
-	}
+    public function __construct($id)
+    {
+        $this->id = $id;
+    }
+
+    public function build(): userExpireWarningToday
+    {
+        return $this->view('emails.userExpireWarningToday')->subject('账号过期提醒');
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
 
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
 }

+ 32 - 21
app/Mail/userTrafficWarning.php

@@ -9,25 +9,36 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Mail\Mailable;
 use Illuminate\Queue\SerializesModels;
 
-class userTrafficWarning extends Mailable implements ShouldQueue {
-	use Queueable, SerializesModels;
-
-	protected $id; // 邮件记录ID
-	protected $usedPercent; // 已使用百分比
-
-	public function __construct($id, $usedPercent) {
-		$this->id = $id;
-		$this->usedPercent = $usedPercent;
-	}
-
-	public function build(): userTrafficWarning {
-		return $this->view('emails.userTrafficWarning')->subject('流量警告')->with([
-			'usedPercent' => $this->usedPercent
-		]);
-	}
-
-	// 发件失败处理
-	public function failed(Exception $e): void {
-		NotificationLog::whereId($this->id)->update(['status' => -1, 'error' => $e->getMessage()]);
-	}
+class userTrafficWarning extends Mailable implements ShouldQueue
+{
+
+    use Queueable;
+    use SerializesModels;
+
+    protected $id; // 邮件记录ID
+    protected $usedPercent; // 已使用百分比
+
+    public function __construct($id, $usedPercent)
+    {
+        $this->id          = $id;
+        $this->usedPercent = $usedPercent;
+    }
+
+    public function build(): userTrafficWarning
+    {
+        return $this->view('emails.userTrafficWarning')->subject('流量警告')->with(
+            [
+                'usedPercent' => $this->usedPercent,
+            ]
+        );
+    }
+
+    // 发件失败处理
+    public function failed(Exception $e): void
+    {
+        NotificationLog::whereId($this->id)->update(
+            ['status' => -1, 'error' => $e->getMessage()]
+        );
+    }
+
 }

+ 13 - 9
app/Models/Article.php

@@ -8,15 +8,19 @@ use Illuminate\Database\Eloquent\SoftDeletes;
 /**
  * 文章
  */
-class Article extends Model {
-	use SoftDeletes;
+class Article extends Model
+{
 
-	protected $table = 'article';
-	protected $dates = ['deleted_at'];
-	protected $guarded = ['id', 'created_at'];
+    use SoftDeletes;
+
+    protected $table = 'article';
+    protected $dates = ['deleted_at'];
+    protected $guarded = ['id', 'created_at'];
+
+    // 筛选类型
+    public function scopeType($query, $type)
+    {
+        return $query->whereType($type);
+    }
 
-	// 筛选类型
-	public function scopeType($query, $type) {
-		return $query->whereType($type);
-	}
 }

+ 10 - 7
app/Models/Config.php

@@ -7,11 +7,14 @@ use Illuminate\Database\Eloquent\Model;
 /**
  * 系统配置
  */
-class Config extends Model {
-	public $timestamps = false;
-	public $incrementing = false;
-	protected $table = 'config';
-	protected $primaryKey = 'name';
-	protected $keyType = 'string';
-	protected $fillable = ['value'];
+class Config extends Model
+{
+
+    public $timestamps = false;
+    public $incrementing = false;
+    protected $table = 'config';
+    protected $primaryKey = 'name';
+    protected $keyType = 'string';
+    protected $fillable = ['value'];
+
 }

+ 10 - 7
app/Models/Country.php

@@ -7,11 +7,14 @@ use Illuminate\Database\Eloquent\Model;
 /**
  * 国家/地区
  */
-class Country extends Model {
-	public $timestamps = false;
-	public $incrementing = false;
-	protected $table = 'country';
-	protected $primaryKey = 'code';
-	protected $keyType = 'string';
-	protected $fillable = ['*'];
+class Country extends Model
+{
+
+    public $timestamps = false;
+    public $incrementing = false;
+    protected $table = 'country';
+    protected $primaryKey = 'code';
+    protected $keyType = 'string';
+    protected $fillable = ['*'];
+
 }

+ 13 - 9
app/Models/Coupon.php

@@ -8,15 +8,19 @@ use Illuminate\Database\Eloquent\SoftDeletes;
 /**
  * 优惠券
  */
-class Coupon extends Model {
-	use SoftDeletes;
+class Coupon extends Model
+{
 
-	protected $table = 'coupon';
-	protected $dates = ['deleted_at'];
-	protected $fillable = ['usable_times', 'status'];
+    use SoftDeletes;
+
+    protected $table = 'coupon';
+    protected $dates = ['deleted_at'];
+    protected $fillable = ['usable_times', 'status'];
+
+    // 筛选类型
+    public function scopeType($query, $type)
+    {
+        return $query->whereType($type);
+    }
 
-	// 筛选类型
-	public function scopeType($query, $type) {
-		return $query->whereType($type);
-	}
 }

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio