diff --git a/app/Console/Commands/CheckCommission.php b/app/Console/Commands/CheckCommission.php index f9e5518..3c3589b 100644 --- a/app/Console/Commands/CheckCommission.php +++ b/app/Console/Commands/CheckCommission.php @@ -112,13 +112,16 @@ class CheckCommission extends Command DB::rollBack(); return false; } - CommissionLog::create([ + if (!CommissionLog::create([ 'invite_user_id' => $inviteUserId, 'user_id' => $order->user_id, 'trade_no' => $order->trade_no, 'order_amount' => $order->total_amount, 'get_amount' => $commissionBalance - ]); + ])) { + DB::rollBack(); + return false; + } $inviteUserId = $inviter->invite_user_id; // update order actual commission balance $order->actual_commission_balance = $order->actual_commission_balance + $commissionBalance; diff --git a/app/Console/Commands/ExportV2Log.php b/app/Console/Commands/ExportV2Log.php index 1bc1fa3..ec2ccbe 100644 --- a/app/Console/Commands/ExportV2Log.php +++ b/app/Console/Commands/ExportV2Log.php @@ -19,11 +19,11 @@ class ExportV2Log extends Command public function handle() { $days = $this->argument('days'); - $date = Carbon::now()->subDays((float) $days)->startOfDay(); + $date = Carbon::now()->subDays($days)->startOfDay(); $logs = DB::table('v2_log') - ->where('created_at', '>=', $date->timestamp) - ->get(); + ->where('created_at', '>=', $date->timestamp) + ->get(); $fileName = "v2_logs_" . Carbon::now()->format('Y_m_d_His') . ".csv"; $handle = fopen(storage_path("logs/$fileName"), 'w'); @@ -35,19 +35,19 @@ class ExportV2Log extends Command fputcsv($handle, [ $log->level, $log->id, - $log->title, - $log->host, - $log->uri, - $log->method, - $log->data, - $log->ip, - $log->context, - Carbon::createFromTimestamp($log->created_at)->toDateTimeString(), + $log->title, + $log->host, + $log->uri, + $log->method, + $log->data, + $log->ip, + $log->context, + Carbon::createFromTimestamp($log->created_at)->toDateTimeString(), Carbon::createFromTimestamp($log->updated_at)->toDateTimeString() ]); } fclose($handle); - $this->info("日志成功导出到: " . storage_path("logs/$fileName")); + $this->info("日志成功导出到: ". storage_path("logs/$fileName")); } } diff --git a/app/Console/Commands/ResetTraffic.php b/app/Console/Commands/ResetTraffic.php index 60ee4a1..4c90b1a 100644 --- a/app/Console/Commands/ResetTraffic.php +++ b/app/Console/Commands/ResetTraffic.php @@ -5,26 +5,30 @@ namespace App\Console\Commands; use App\Models\Plan; use Illuminate\Console\Command; use App\Models\User; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\DB; class ResetTraffic extends Command { - /** - * @var Builder - */ protected $builder; - /** + * The name and signature of the console command. + * * @var string */ protected $signature = 'reset:traffic'; /** + * The console command description. + * * @var string */ protected $description = '流量清空'; + /** + * Create a new command instance. + * + * @return void + */ public function __construct() { parent::__construct(); @@ -33,13 +37,13 @@ class ResetTraffic extends Command } /** - * 执行流量重置命令 + * Execute the console command. + * + * @return mixed */ public function handle() { ini_set('memory_limit', -1); - - // 按重置方法分组查询所有套餐 $resetMethods = Plan::select( DB::raw("GROUP_CONCAT(`id`) as plan_ids"), DB::raw("reset_traffic_method as method") @@ -47,117 +51,138 @@ class ResetTraffic extends Command ->groupBy('reset_traffic_method') ->get() ->toArray(); - - // 使用闭包直接引用方法 - $resetHandlers = [ - Plan::RESET_TRAFFIC_FIRST_DAY_MONTH => fn($builder) => $this->resetByMonthFirstDay($builder), - Plan::RESET_TRAFFIC_MONTHLY => fn($builder) => $this->resetByExpireDay($builder), - Plan::RESET_TRAFFIC_NEVER => null, - Plan::RESET_TRAFFIC_FIRST_DAY_YEAR => fn($builder) => $this->resetByYearFirstDay($builder), - Plan::RESET_TRAFFIC_YEARLY => fn($builder) => $this->resetByExpireYear($builder), - ]; - - // 处理每种重置方法 foreach ($resetMethods as $resetMethod) { $planIds = explode(',', $resetMethod['plan_ids']); - - // 获取重置方法 - $method = $resetMethod['method']; - if ($method === NULL) { - $method = (int) admin_setting('reset_traffic_method', 0); + switch (true) { + case ($resetMethod['method'] === NULL): { + $resetTrafficMethod = admin_setting('reset_traffic_method', 0); + $builder = with(clone ($this->builder))->whereIn('plan_id', $planIds); + switch ((int) $resetTrafficMethod) { + // month first day + case 0: + $this->resetByMonthFirstDay($builder); + break; + // expire day + case 1: + $this->resetByExpireDay($builder); + break; + // no action + case 2: + break; + // year first day + case 3: + $this->resetByYearFirstDay($builder); + // year expire day + case 4: + $this->resetByExpireYear($builder); + } + break; + } + case ($resetMethod['method'] === 0): { + $builder = with(clone ($this->builder))->whereIn('plan_id', $planIds); + $this->resetByMonthFirstDay($builder); + break; + } + case ($resetMethod['method'] === 1): { + $builder = with(clone ($this->builder))->whereIn('plan_id', $planIds); + $this->resetByExpireDay($builder); + break; + } + case ($resetMethod['method'] === 2): { + break; + } + case ($resetMethod['method'] === 3): { + $builder = with(clone ($this->builder))->whereIn('plan_id', $planIds); + $this->resetByYearFirstDay($builder); + break; + } + case ($resetMethod['method'] === 4): { + $builder = with(clone ($this->builder))->whereIn('plan_id', $planIds); + $this->resetByExpireYear($builder); + break; + } } - - // 跳过不重置的方法 - if ($method === 2) { - continue; - } - - // 获取该方法的处理器 - $handler = $resetHandlers[$method] ?? null; - if (!$handler) { - continue; - } - - // 创建查询构建器并执行重置 - $userQuery = (clone $this->builder)->whereIn('plan_id', $planIds); - $handler($userQuery); } } - /** - * 按用户年度到期日重置流量 - */ - private function resetByExpireYear(Builder $builder): void + private function resetByExpireYear($builder): void { - $today = date('m-d'); - $this->resetUsersByDateCondition($builder, function ($user) use ($today) { - return date('m-d', $user->expired_at) === $today; - }); - } - /** - * 按新年第一天重置流量 - */ - private function resetByYearFirstDay(Builder $builder): void - { - $isNewYear = date('md') === '0101'; - if (!$isNewYear) { - return; + $users = $builder->with('plan')->get(); + $usersToUpdate = []; + foreach ($users as $user) { + $expireDay = date('m-d', $user->expired_at); + $today = date('m-d'); + if ($expireDay === $today) { + $usersToUpdate[] = [ + 'id' => $user->id, + 'transfer_enable' => $user->plan->transfer_enable + ]; + } } - $this->resetAllUsers($builder); + foreach ($usersToUpdate as $userData) { + User::where('id', $userData['id'])->update([ + 'transfer_enable' => (intval($userData['transfer_enable']) * 1073741824), + 'u' => 0, + 'd' => 0 + ]); + } } - /** - * 按月初第一天重置流量 - */ - private function resetByMonthFirstDay(Builder $builder): void + private function resetByYearFirstDay($builder): void { - $isFirstDayOfMonth = date('d') === '01'; - if (!$isFirstDayOfMonth) { - return; + $users = $builder->with('plan')->get(); + $usersToUpdate = []; + foreach ($users as $user) { + if ((string) date('md') === '0101') { + $usersToUpdate[] = [ + 'id' => $user->id, + 'transfer_enable' => $user->plan->transfer_enable + ]; + } } - $this->resetAllUsers($builder); + foreach ($usersToUpdate as $userData) { + User::where('id', $userData['id'])->update([ + 'transfer_enable' => (intval($userData['transfer_enable']) * 1073741824), + 'u' => 0, + 'd' => 0 + ]); + } } - /** - * 按用户到期日重置流量 - */ - private function resetByExpireDay(Builder $builder): void + private function resetByMonthFirstDay($builder): void + { + $users = $builder->with('plan')->get(); + $usersToUpdate = []; + foreach ($users as $user) { + if ((string) date('d') === '01') { + $usersToUpdate[] = [ + 'id' => $user->id, + 'transfer_enable' => $user->plan->transfer_enable + ]; + } + } + + foreach ($usersToUpdate as $userData) { + User::where('id', $userData['id'])->update([ + 'transfer_enable' => (intval($userData['transfer_enable']) * 1073741824), + 'u' => 0, + 'd' => 0 + ]); + } + } + private function resetByExpireDay($builder): void { - $today = date('d'); $lastDay = date('d', strtotime('last day of +0 months')); - - $this->resetUsersByDateCondition($builder, function ($user) use ($today, $lastDay) { - $expireDay = date('d', $user->expired_at); - return $expireDay === $today || ($today === $lastDay && $expireDay >= $today); - }); - } - - /** - * 重置所有符合条件的用户流量 - */ - private function resetAllUsers(Builder $builder): void - { - $this->resetUsersByDateCondition($builder, function () { - return true; - }); - } - - /** - * 根据日期条件重置用户流量 - * @param Builder $builder 用户查询构建器 - * @param callable $condition 日期条件回调 - */ - private function resetUsersByDateCondition(Builder $builder, callable $condition): void - { - /** @var \App\Models\User[] $users */ + $today = date('d'); $users = $builder->with('plan')->get(); $usersToUpdate = []; foreach ($users as $user) { - if ($condition($user)) { + $expireDay = date('d', $user->expired_at); + if ($expireDay === $today || ($today === $lastDay && $expireDay >= $today)) { $usersToUpdate[] = [ 'id' => $user->id, 'transfer_enable' => $user->plan->transfer_enable diff --git a/app/Console/Commands/XboardInstall.php b/app/Console/Commands/XboardInstall.php index a33d2f2..658fd06 100644 --- a/app/Console/Commands/XboardInstall.php +++ b/app/Console/Commands/XboardInstall.php @@ -50,9 +50,9 @@ class XboardInstall extends Command { try { $isDocker = file_exists('/.dockerenv'); - $enableSqlite = getenv('ENABLE_SQLITE', false); - $enableRedis = getenv('ENABLE_REDIS', false); - $adminAccount = getenv('ADMIN_ACCOUNT', false); + $enableSqlite = env('ENABLE_SQLITE', false); + $enableRedis = env('ENABLE_REDIS', false); + $adminAccount = env('ADMIN_ACCOUNT', ''); $this->info("__ __ ____ _ "); $this->info("\ \ / /| __ ) ___ __ _ _ __ __| | "); $this->info(" \ \/ / | __ \ / _ \ / _` | '__/ _` | "); @@ -60,7 +60,7 @@ class XboardInstall extends Command $this->info("/_/ \_\|____/ \___/ \__,_|_| \__,_| "); if ( (File::exists(base_path() . '/.env') && $this->getEnvValue('INSTALLED')) - || (getenv('INSTALLED', false) && $isDocker) + || (env('INSTALLED', false) && $isDocker) ) { $securePath = admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))); $this->info("访问 http(s)://你的站点/{$securePath} 进入管理面板,你可以在用户中心修改你的密码。"); diff --git a/app/Console/Commands/XboardStatistics.php b/app/Console/Commands/XboardStatistics.php index 0bd4736..01c275e 100644 --- a/app/Console/Commands/XboardStatistics.php +++ b/app/Console/Commands/XboardStatistics.php @@ -2,10 +2,12 @@ namespace App\Console\Commands; +use App\Models\StatServer; +use App\Models\StatUser; use App\Services\StatisticalService; use Illuminate\Console\Command; use App\Models\Stat; -use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\DB; class XboardStatistics extends Command { @@ -48,6 +50,67 @@ class XboardStatistics extends Command info('统计任务执行完毕。耗时:' . (microtime(true) - $startAt) / 1000); } + private function statServer() + { + try { + DB::beginTransaction(); + $createdAt = time(); + $recordAt = strtotime('-1 day', strtotime(date('Y-m-d'))); + $statService = new StatisticalService(); + $statService->setStartAt($recordAt); + $stats = $statService->getStatServer(); + foreach ($stats as $stat) { + if (!StatServer::insert([ + 'server_id' => $stat['server_id'], + 'server_type' => $stat['server_type'], + 'u' => $stat['u'], + 'd' => $stat['d'], + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + 'record_type' => 'd', + 'record_at' => $recordAt + ])) { + throw new \Exception('stat server fail'); + } + } + DB::commit(); + $statService->clearStatServer(); + } catch (\Exception $e) { + DB::rollback(); + \Log::error($e->getMessage(), ['exception' => $e]); + } + } + + private function statUser() + { + try { + DB::beginTransaction(); + $createdAt = time(); + $recordAt = strtotime('-1 day', strtotime(date('Y-m-d'))); + $statService = new StatisticalService(); + $statService->setStartAt($recordAt); + $stats = $statService->getStatUser(); + foreach ($stats as $stat) { + if (!StatUser::insert([ + 'user_id' => $stat['user_id'], + 'u' => $stat['u'], + 'd' => $stat['d'], + 'server_rate' => $stat['server_rate'], + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + 'record_type' => 'd', + 'record_at' => $recordAt + ])) { + throw new \Exception('stat user fail'); + } + } + DB::commit(); + $statService->clearStatUser(); + } catch (\Exception $e) { + DB::rollback(); + \Log::error($e->getMessage(), ['exception' => $e]); + } + } private function stat() { @@ -69,7 +132,7 @@ class XboardStatistics extends Command } Stat::create($data); } catch (\Exception $e) { - Log::error($e->getMessage(), ['exception' => $e]); + \Log::error($e->getMessage(), ['exception' => $e]); } } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index feb49c3..62b2fc7 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -42,9 +42,9 @@ class Kernel extends ConsoleKernel // horizon metrics $schedule->command('horizon:snapshot')->everyFiveMinutes()->onOneServer(); // backup Timing - // if (env('ENABLE_AUTO_BACKUP_AND_UPDATE', false)) { - // $schedule->command('backup:database', ['true'])->daily()->onOneServer(); - // } + if (env('ENABLE_AUTO_BACKUP_AND_UPDATE', false)) { + $schedule->command('backup:database', ['true'])->daily()->onOneServer(); + } // 每分钟清理过期的在线状态 $schedule->call(function () { app(UserOnlineService::class)->cleanExpiredOnlineStatus(); diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index f76d9c0..7b789aa 100755 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -16,7 +16,7 @@ class Handler extends ExceptionHandler /** * A list of the exception types that are not reported. * - * @var array> + * @var array */ protected $dontReport = [ ApiException::class, @@ -26,7 +26,7 @@ class Handler extends ExceptionHandler /** * A list of the inputs that are never flashed for validation exceptions. * - * @var array + * @var array */ protected $dontFlash = [ 'password', diff --git a/app/Http/Controllers/V1/Passport/AuthController.php b/app/Http/Controllers/V1/Passport/AuthController.php index 4bea6a6..cd34e4f 100644 --- a/app/Http/Controllers/V1/Passport/AuthController.php +++ b/app/Http/Controllers/V1/Passport/AuthController.php @@ -7,89 +7,227 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Passport\AuthForget; use App\Http\Requests\Passport\AuthLogin; use App\Http\Requests\Passport\AuthRegister; -use App\Services\Auth\LoginService; -use App\Services\Auth\MailLinkService; -use App\Services\Auth\RegisterService; +use App\Jobs\SendEmailJob; +use App\Models\InviteCode; +use App\Models\Plan; +use App\Models\User; use App\Services\AuthService; +use App\Utils\CacheKey; +use App\Utils\Dict; +use App\Utils\Helper; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; +use ReCaptcha\ReCaptcha; class AuthController extends Controller { - protected MailLinkService $mailLinkService; - protected RegisterService $registerService; - protected LoginService $loginService; - - public function __construct( - MailLinkService $mailLinkService, - RegisterService $registerService, - LoginService $loginService - ) { - $this->mailLinkService = $mailLinkService; - $this->registerService = $registerService; - $this->loginService = $loginService; - } - - /** - * 通过邮件链接登录 - */ public function loginWithMailLink(Request $request) { + if (!(int)admin_setting('login_with_mail_link_enable')) { + return $this->fail([404,null]); + } $params = $request->validate([ 'email' => 'required|email:strict', 'redirect' => 'nullable' ]); - [$success, $result] = $this->mailLinkService->handleMailLink( - $params['email'], - $request->input('redirect') - ); - - if (!$success) { - return $this->fail($result); + if (Cache::get(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']))) { + return $this->fail([429 ,__('Sending frequently, please try again later')]); } - return $this->success($result); + $user = User::where('email', $params['email'])->first(); + if (!$user) { + return $this->success(true); + } + + $code = Helper::guid(); + $key = CacheKey::get('TEMP_TOKEN', $code); + Cache::put($key, $user->id, 300); + Cache::put(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']), time(), 60); + + + $redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard'); + if (admin_setting('app_url')) { + $link = admin_setting('app_url') . $redirect; + } else { + $link = url($redirect); + } + + SendEmailJob::dispatch([ + 'email' => $user->email, + 'subject' => __('Login to :name', [ + 'name' => admin_setting('app_name', 'XBoard') + ]), + 'template_name' => 'login', + 'template_value' => [ + 'name' => admin_setting('app_name', 'XBoard'), + 'link' => $link, + 'url' => admin_setting('app_url') + ] + ]); + + return $this->success($link); + } - /** - * 用户注册 - */ public function register(AuthRegister $request) { - [$success, $result] = $this->registerService->register($request); - - if (!$success) { - return $this->fail($result); + if ((int)admin_setting('register_limit_by_ip_enable', 0)) { + $registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0; + if ((int)$registerCountByIP >= (int)admin_setting('register_limit_count', 3)) { + return $this->fail([429,__('Register frequently, please try again after :minute minute', [ + 'minute' => admin_setting('register_limit_expire', 60) + ])]); + } + } + if ((int)admin_setting('recaptcha_enable', 0)) { + $recaptcha = new ReCaptcha(admin_setting('recaptcha_key')); + $recaptchaResp = $recaptcha->verify($request->input('recaptcha_data')); + if (!$recaptchaResp->isSuccess()) { + return $this->fail([400,__('Invalid code is incorrect')]); + } + } + if ((int)admin_setting('email_whitelist_enable', 0)) { + if (!Helper::emailSuffixVerify( + $request->input('email'), + admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)) + ) { + return $this->fail([400,__('Email suffix is not in the Whitelist')]); + } + } + if ((int)admin_setting('email_gmail_limit_enable', 0)) { + $prefix = explode('@', $request->input('email'))[0]; + if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) { + return $this->fail([400,__('Gmail alias is not supported')]); + } + } + if ((int)admin_setting('stop_register', 0)) { + return $this->fail([400,__('Registration has closed')]); + } + if ((int)admin_setting('invite_force', 0)) { + if (empty($request->input('invite_code'))) { + return $this->fail([422,__('You must use the invitation code to register')]); + } + } + if ((int)admin_setting('email_verify', 0)) { + if (empty($request->input('email_code'))) { + return $this->fail([422,__('Email verification code cannot be empty')]); + } + if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) { + return $this->fail([400,__('Incorrect email verification code')]); + } + } + $email = $request->input('email'); + $password = $request->input('password'); + $exist = User::where('email', $email)->first(); + if ($exist) { + return $this->fail([400201,__('Email already exists')]); + } + $user = new User(); + $user->email = $email; + $user->password = password_hash($password, PASSWORD_DEFAULT); + $user->uuid = Helper::guid(true); + $user->token = Helper::guid(); + // TODO 增加过期默认值、流量告急提醒默认值 + $user->remind_expire = admin_setting('default_remind_expire',1); + $user->remind_traffic = admin_setting('default_remind_traffic',1); + if ($request->input('invite_code')) { + $inviteCode = InviteCode::where('code', $request->input('invite_code')) + ->where('status', 0) + ->first(); + if (!$inviteCode) { + if ((int)admin_setting('invite_force', 0)) { + return $this->fail([400,__('Invalid invitation code')]); + } + } else { + $user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null; + if (!(int)admin_setting('invite_never_expire', 0)) { + $inviteCode->status = 1; + $inviteCode->save(); + } + } } - $authService = new AuthService($result); - return $this->success($authService->generateAuthData()); + // try out + if ((int)admin_setting('try_out_plan_id', 0)) { + $plan = Plan::find(admin_setting('try_out_plan_id')); + if ($plan) { + $user->transfer_enable = $plan->transfer_enable * 1073741824; + $user->plan_id = $plan->id; + $user->group_id = $plan->group_id; + $user->expired_at = time() + (admin_setting('try_out_hour', 1) * 3600); + $user->speed_limit = $plan->speed_limit; + } + } + + if (!$user->save()) { + return $this->fail([500,__('Register failed')]); + } + if ((int)admin_setting('email_verify', 0)) { + Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))); + } + + $user->last_login_at = time(); + $user->save(); + + if ((int)admin_setting('register_limit_by_ip_enable', 0)) { + Cache::put( + CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip()), + (int)$registerCountByIP + 1, + (int)admin_setting('register_limit_expire', 60) * 60 + ); + } + + $authService = new AuthService($user); + + $data = $authService->generateAuthData(); + return $this->success($data); } - /** - * 用户登录 - */ public function login(AuthLogin $request) { $email = $request->input('email'); $password = $request->input('password'); - [$success, $result] = $this->loginService->login($email, $password); - - if (!$success) { - return $this->fail($result); + if ((int)admin_setting('password_limit_enable', 1)) { + $passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0); + if ($passwordErrorCount >= (int)admin_setting('password_limit_count', 5)) { + return $this->fail([429,__('There are too many password errors, please try again after :minute minutes.', [ + 'minute' => admin_setting('password_limit_expire', 60) + ])]); + } } - $authService = new AuthService($result); + $user = User::where('email', $email)->first(); + if (!$user) { + return $this->fail([400, __('Incorrect email or password')]); + } + if (!Helper::multiPasswordVerify( + $user->password_algo, + $user->password_salt, + $password, + $user->password) + ) { + if ((int)admin_setting('password_limit_enable')) { + Cache::put( + CacheKey::get('PASSWORD_ERROR_LIMIT', $email), + (int)$passwordErrorCount + 1, + 60 * (int)admin_setting('password_limit_expire', 60) + ); + } + return $this->fail([400, __('Incorrect email or password')]); + } + + if ($user->banned) { + return $this->fail([400, __('Your account has been suspended')]); + } + + $authService = new AuthService($user); return $this->success($authService->generateAuthData()); } - /** - * 通过token登录 - */ public function token2Login(Request $request) { - // 处理直接通过token重定向 if ($token = $request->input('token')) { $redirect = '/#/login?verify=' . $token . '&redirect=' . ($request->input('redirect', 'dashboard')); @@ -100,9 +238,9 @@ class AuthController extends Controller ); } - // 处理通过验证码登录 if ($verify = $request->input('verify')) { - $userId = $this->mailLinkService->handleTokenLogin($verify); + $key = CacheKey::get('TEMP_TOKEN', $verify); + $userId = Cache::get($key); if (!$userId) { return response()->json([ @@ -110,14 +248,15 @@ class AuthController extends Controller ], 400); } - $user = \App\Models\User::find($userId); + $user = User::findOrFail($userId); - if (!$user) { + if ($user->banned) { return response()->json([ - 'message' => __('User not found') + 'message' => __('Your account has been suspended') ], 400); } + Cache::forget($key); $authService = new AuthService($user); return response()->json([ @@ -130,9 +269,6 @@ class AuthController extends Controller ], 400); } - /** - * 获取快速登录URL - */ public function getQuickLoginUrl(Request $request) { $authorization = $request->input('auth_data') ?? $request->header('authorization'); @@ -151,25 +287,38 @@ class AuthController extends Controller ], 401); } - $url = $this->mailLinkService->getQuickLoginUrl($user, $request->input('redirect')); + $code = Helper::guid(); + $key = CacheKey::get('TEMP_TOKEN', $code); + Cache::put($key, $user['id'], 60); + $redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard'); + if (admin_setting('app_url')) { + $url = admin_setting('app_url') . $redirect; + } else { + $url = url($redirect); + } return $this->success($url); } - /** - * 忘记密码处理 - */ public function forget(AuthForget $request) { - [$success, $result] = $this->loginService->resetPassword( - $request->input('email'), - $request->input('email_code'), - $request->input('password') - ); - - if (!$success) { - return $this->fail($result); + $forgetRequestLimitKey = CacheKey::get('FORGET_REQUEST_LIMIT', $request->input('email')); + $forgetRequestLimit = (int)Cache::get($forgetRequestLimitKey); + if ($forgetRequestLimit >= 3) return $this->fail([429, __('Reset failed, Please try again later')]); + if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) { + Cache::put($forgetRequestLimitKey, $forgetRequestLimit ? $forgetRequestLimit + 1 : 1, 300); + return $this->fail([400,__('Incorrect email verification code')]); } - + $user = User::where('email', $request->input('email'))->first(); + if (!$user) { + return $this->fail([400,__('This email is not registered in the system')]); + } + $user->password = password_hash($request->input('password'), PASSWORD_DEFAULT); + $user->password_algo = NULL; + $user->password_salt = NULL; + if (!$user->save()) { + return $this->fail([500,__('Reset failed')]); + } + Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))); return $this->success(true); } } diff --git a/app/Http/Controllers/V1/Passport/CommController.php b/app/Http/Controllers/V1/Passport/CommController.php index 60b2cd7..ce135fa 100644 --- a/app/Http/Controllers/V1/Passport/CommController.php +++ b/app/Http/Controllers/V1/Passport/CommController.php @@ -15,10 +15,14 @@ use ReCaptcha\ReCaptcha; class CommController extends Controller { + private function isEmailVerify() + { + return $this->success((int)admin_setting('email_verify', 0) ? 1 : 0); + } public function sendEmailVerify(CommSendEmailVerify $request) { - if ((int) admin_setting('recaptcha_enable', 0)) { + if ((int)admin_setting('recaptcha_enable', 0)) { $recaptcha = new ReCaptcha(admin_setting('recaptcha_key')); $recaptchaResp = $recaptcha->verify($request->input('recaptcha_data')); if (!$recaptchaResp->isSuccess()) { @@ -59,4 +63,12 @@ class CommController extends Controller return $this->success(true); } + private function getEmailSuffix() + { + $suffix = admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT); + if (!is_array($suffix)) { + return preg_split('/,/', $suffix); + } + return $suffix; + } } diff --git a/app/Http/Controllers/V1/Server/UniProxyController.php b/app/Http/Controllers/V1/Server/UniProxyController.php index 860199f..36c358c 100644 --- a/app/Http/Controllers/V1/Server/UniProxyController.php +++ b/app/Http/Controllers/V1/Server/UniProxyController.php @@ -150,20 +150,6 @@ class UniProxyController extends Controller 'socks' => [ 'server_port' => (int) $serverPort, ], - 'naive' => [ - 'server_port' => (int) $serverPort, - 'tls' => (int) $protocolSettings['tls'], - 'tls_settings' => $protocolSettings['tls_settings'] - ], - 'http' => [ - 'server_port' => (int) $serverPort, - 'tls' => (int) $protocolSettings['tls'], - 'tls_settings' => $protocolSettings['tls_settings'] - ], - 'mieru' => [ - 'server_port' => (string) $serverPort, - 'protocol' => (int) $protocolSettings['protocol'], - ], default => [] }; @@ -177,7 +163,7 @@ class UniProxyController extends Controller } $eTag = sha1(json_encode($response)); - if (strpos($request->header('If-None-Match', ''), $eTag) !== false) { + if (strpos($request->header('If-None-Match', '') ?? '', $eTag) !== false) { return response(null, 304); } return response($response)->header('ETag', "\"{$eTag}\""); diff --git a/app/Http/Controllers/V1/Staff/NoticeController.php b/app/Http/Controllers/V1/Staff/NoticeController.php new file mode 100644 index 0000000..cf30cb3 --- /dev/null +++ b/app/Http/Controllers/V1/Staff/NoticeController.php @@ -0,0 +1,58 @@ +get(); + return $this->success($data); + } + + public function save(NoticeSave $request) + { + $data = $request->only([ + 'title', + 'content', + 'img_url' + ]); + if (!$request->input('id')) { + if (!Notice::create($data)) { + return $this->fail([500, '创建失败']); + } + } else { + try { + Notice::find($request->input('id'))->update($data); + } catch (\Exception $e) { + \Log::error($e); + return $this->fail([500, '保存失败']); + } + } + return $this->success(true); + } + + public function drop(Request $request) + { + $request->validate([ + 'id' => 'required' + ],[ + 'id.required' => '公告ID不能为空' + ]); + + $notice = Notice::find($request->input('id')); + if (!$notice) { + return $this->fail([400202,'公告不存在']); + } + if (!$notice->delete()) { + return $this->fail([500,'公告删除失败']); + } + return $this->success(true); + } +} diff --git a/app/Http/Controllers/V1/Staff/PlanController.php b/app/Http/Controllers/V1/Staff/PlanController.php new file mode 100755 index 0000000..96a507e --- /dev/null +++ b/app/Http/Controllers/V1/Staff/PlanController.php @@ -0,0 +1,35 @@ +where('plan_id', '!=', NULL) + ->where(function ($query) { + $query->where('expired_at', '>=', time()) + ->orWhere('expired_at', NULL); + }) + ->groupBy("plan_id") + ->get(); + $plans = Plan::orderBy('sort', 'ASC')->get(); + foreach ($plans as $k => $v) { + $plans[$k]->count = 0; + foreach ($counts as $kk => $vv) { + if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count; + } + } + return $this->success($plans); + } +} diff --git a/app/Http/Controllers/V1/Staff/TicketController.php b/app/Http/Controllers/V1/Staff/TicketController.php new file mode 100644 index 0000000..ae32373 --- /dev/null +++ b/app/Http/Controllers/V1/Staff/TicketController.php @@ -0,0 +1,82 @@ +input('id')) { + $ticket = Ticket::where('id', $request->input('id')) + ->first(); + if (!$ticket) { + return $this->fail([400,'工单不存在']); + } + $ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get(); + for ($i = 0; $i < count($ticket['message']); $i++) { + if ($ticket['message'][$i]['user_id'] !== $ticket->user_id) { + $ticket['message'][$i]['is_me'] = true; + } else { + $ticket['message'][$i]['is_me'] = false; + } + } + return $this->success($ticket); + } + $current = $request->input('current') ? $request->input('current') : 1; + $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; + $model = Ticket::orderBy('created_at', 'DESC'); + if ($request->input('status') !== NULL) { + $model->where('status', $request->input('status')); + } + $total = $model->count(); + $res = $model->forPage($current, $pageSize) + ->get(); + return response([ + 'data' => $res, + 'total' => $total + ]); + } + + public function reply(Request $request) + { + $request->validate([ + 'id' => 'required', + 'message' => 'required|string' + ],[ + 'id.required' => '工单ID不能为空', + 'message.required' => '消息不能为空' + ]); + $ticketService = new TicketService(); + $ticketService->replyByAdmin( + $request->input('id'), + $request->input('message'), + $request->user()->id + ); + return $this->success(true); + } + + public function close(Request $request) + { + + if (empty($request->input('id'))) { + return $this->fail([422,'工单ID不能为空']); + } + $ticket = Ticket::where('id', $request->input('id')) + ->first(); + if (!$ticket) { + return $this->fail([400202,'工单不存在']); + } + $ticket->status = Ticket::STATUS_CLOSED; + if (!$ticket->save()) { + return $this->fail([500, '工单关闭失败']); + } + return $this->success(true); + } +} diff --git a/app/Http/Controllers/V1/Staff/UserController.php b/app/Http/Controllers/V1/Staff/UserController.php new file mode 100644 index 0000000..97bd9df --- /dev/null +++ b/app/Http/Controllers/V1/Staff/UserController.php @@ -0,0 +1,102 @@ +input('id'))) { + return $this->fail([422,'用户ID不能为空']); + } + $user = User::where('is_admin', 0) + ->where('id', $request->input('id')) + ->where('is_staff', 0) + ->first(); + if (!$user) return $this->fail([400202,'用户不存在']); + return $this->success($user); + } + + public function update(UserUpdate $request) + { + $params = $request->validated(); + $user = User::find($request->input('id')); + if (!$user) { + return $this->fail([400202,'用户不存在']); + } + if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) { + return $this->fail([400201,'邮箱已被使用']); + } + if (isset($params['password'])) { + $params['password'] = password_hash($params['password'], PASSWORD_DEFAULT); + $params['password_algo'] = NULL; + } else { + unset($params['password']); + } + if (isset($params['plan_id'])) { + $plan = Plan::find($params['plan_id']); + if (!$plan) { + return $this->fail([400202,'订阅不存在']); + } + $params['group_id'] = $plan->group_id; + } + + try { + $user->update($params); + } catch (\Exception $e) { + \Log::error($e); + return $this->fail([500,'更新失败']); + } + return $this->success(true); + } + + public function sendMail(UserSendMail $request) + { + $sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC'; + $sort = $request->input('sort') ? $request->input('sort') : 'created_at'; + $builder = User::orderBy($sort, $sortType); + $this->filter($request, $builder); + $users = $builder->get(); + foreach ($users as $user) { + SendEmailJob::dispatch([ + 'email' => $user->email, + 'subject' => $request->input('subject'), + 'template_name' => 'notify', + 'template_value' => [ + 'name' => admin_setting('app_name', 'XBoard'), + 'url' => admin_setting('app_url'), + 'content' => $request->input('content') + ] + ]); + } + + return $this->success(true); + } + + public function ban(Request $request) + { + $sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC'; + $sort = $request->input('sort') ? $request->input('sort') : 'created_at'; + $builder = User::orderBy($sort, $sortType); + $this->filter($request, $builder); + try { + $builder->update([ + 'banned' => 1 + ]); + } catch (\Exception $e) { + \Log::error($e); + return $this->fail([500,'处理上失败']); + } + + return $this->success(true); + } +} diff --git a/app/Http/Controllers/V1/User/OrderController.php b/app/Http/Controllers/V1/User/OrderController.php index 08a925b..6ad6742 100755 --- a/app/Http/Controllers/V1/User/OrderController.php +++ b/app/Http/Controllers/V1/User/OrderController.php @@ -43,15 +43,16 @@ class OrderController extends Controller $request->validate([ 'trade_no' => 'required|string', ]); - $order = Order::with(['payment','plan']) + $order = Order::with('payment') ->where('user_id', $request->user()->id) ->where('trade_no', $request->input('trade_no')) ->first(); if (!$order) { return $this->fail([400, __('Order does not exist or has been paid')]); } + $order['plan'] = Plan::find($order->plan_id); $order['try_out_plan_id'] = (int) admin_setting('try_out_plan_id'); - if (!$order->plan) { + if (!$order['plan']) { return $this->fail([400, __('Subscription plan does not exist')]); } if ($order->surplus_order_ids) { @@ -80,7 +81,7 @@ class OrderController extends Controller // Validate plan purchase $planService->validatePurchase($user, $request->input('period')); - return DB::transaction(function () use ($request, $plan, $user, $userService) { + return DB::transaction(function () use ($request, $plan, $user, $userService, $planService) { $period = $request->input('period'); $newPeriod = PlanService::getPeriodKey($period); @@ -168,13 +169,12 @@ class OrderController extends Controller ]); } $payment = Payment::find($method); - if (!$payment || !$payment->enable) { + if (!$payment || $payment->enable !== 1) return $this->fail([400, __('Payment method is not available')]); - } $paymentService = new PaymentService($payment->payment, $payment->id); $order->handling_amount = NULL; if ($payment->handling_fee_fixed || $payment->handling_fee_percent) { - $order->handling_amount = (int) round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed); + $order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed); } $order->payment_id = $method; if (!$order->save()) diff --git a/app/Http/Controllers/V1/User/UserController.php b/app/Http/Controllers/V1/User/UserController.php index 5fbc733..c33f00c 100755 --- a/app/Http/Controllers/V1/User/UserController.php +++ b/app/Http/Controllers/V1/User/UserController.php @@ -58,13 +58,11 @@ class UserController extends Controller if (!$user) { return $this->fail([400, __('The user does not exist')]); } - if ( - !Helper::multiPasswordVerify( - $user->password_algo, - $user->password_salt, - $request->input('old_password'), - $user->password - ) + if (!Helper::multiPasswordVerify( + $user->password_algo, + $user->password_salt, + $request->input('old_password'), + $user->password) ) { return $this->fail([400, __('The old password is wrong')]); } diff --git a/app/Http/Controllers/V2/Admin/ConfigController.php b/app/Http/Controllers/V2/Admin/ConfigController.php index e41498d..48a9483 100644 --- a/app/Http/Controllers/V2/Admin/ConfigController.php +++ b/app/Http/Controllers/V2/Admin/ConfigController.php @@ -64,6 +64,12 @@ class ConfigController extends Controller return $this->success(true); } + private function getTemplateContent(string $filename): string + { + $path = resource_path("rules/{$filename}"); + return File::exists($path) ? File::get($path) : ''; + } + public function fetch(Request $request) { $key = $request->input('key'); diff --git a/app/Http/Controllers/V2/Admin/OrderController.php b/app/Http/Controllers/V2/Admin/OrderController.php index 05ebdb6..ddc4f50 100644 --- a/app/Http/Controllers/V2/Admin/OrderController.php +++ b/app/Http/Controllers/V2/Admin/OrderController.php @@ -15,7 +15,6 @@ use App\Utils\Helper; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\Log; class OrderController extends Controller { @@ -28,7 +27,7 @@ class OrderController extends Controller if ($order->surplus_order_ids) { $order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get(); } - $order['period'] = PlanService::getLegacyPeriod((string) $order->period); + $order['period'] = PlanService::getLegacyPeriod($order->period); return $this->success($order); } @@ -46,21 +45,17 @@ class OrderController extends Controller $this->applyFiltersAndSorts($request, $orderModel); - /** @var \Illuminate\Pagination\LengthAwarePaginator $paginatedResults */ - $paginatedResults = $orderModel - ->latest('created_at') - ->paginate( - perPage: $pageSize, - page: $current - ); - - $paginatedResults->getCollection()->transform(function($order) { - $orderArray = $order->toArray(); - $orderArray['period'] = PlanService::getLegacyPeriod((string) $order->period); - return $orderArray; - }); - - return response()->json($paginatedResults); + return response()->json( + $orderModel + ->latest('created_at') + ->paginate( + perPage: $pageSize, + page: $current + )->through(fn($order) => [ + ...$order->toArray(), + 'period' => PlanService::getLegacyPeriod($order->period) + ]), + ); } private function applyFiltersAndSorts(Request $request, Builder $builder): void @@ -117,8 +112,8 @@ class OrderController extends Controller 'lte' => '<=', 'like' => 'like', 'notlike' => 'not like', - 'null' => static fn($q) => $q->whereNull($field), - 'notnull' => static fn($q) => $q->whereNotNull($field), + 'null' => static fn($q) => $q->whereNull($queryField), + 'notnull' => static fn($q) => $q->whereNotNull($queryField), default => 'like' }, match (strtolower($operator)) { 'like', 'notlike' => "%{$filterValue}%", @@ -189,7 +184,7 @@ class OrderController extends Controller try { $order->update($params); } catch (\Exception $e) { - Log::error($e); + \Log::error($e); return $this->fail([500, '更新失败']); } @@ -220,12 +215,11 @@ class OrderController extends Controller $orderService = new OrderService($order); $order->user_id = $user->id; $order->plan_id = $plan->id; - $period = $request->input('period'); - $order->period = (int) PlanService::getPeriodKey((string) $period); + $order->period = PlanService::getPeriodKey($request->input('period')); $order->trade_no = Helper::guid(); $order->total_amount = $request->input('total_amount'); - if (PlanService::getPeriodKey((string) $order->period) === Plan::PERIOD_RESET_TRAFFIC) { + if (PlanService::getPeriodKey($order->period) === Plan::PERIOD_RESET_TRAFFIC) { $order->type = Order::TYPE_RESET_TRAFFIC; } else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) { $order->type = Order::TYPE_UPGRADE; diff --git a/app/Http/Controllers/V2/Admin/PlanController.php b/app/Http/Controllers/V2/Admin/PlanController.php index 63c817f..9d7063a 100644 --- a/app/Http/Controllers/V2/Admin/PlanController.php +++ b/app/Http/Controllers/V2/Admin/PlanController.php @@ -8,7 +8,6 @@ use App\Models\Plan; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; class PlanController extends Controller { @@ -59,7 +58,7 @@ class PlanController extends Controller return $this->success(true); } catch (\Exception $e) { DB::rollBack(); - Log::error($e); + \Log::error($e); return $this->fail([500, '保存失败']); } } @@ -77,12 +76,12 @@ class PlanController extends Controller if (User::where('plan_id', $request->input('id'))->first()) { return $this->fail([400201, '该订阅下存在用户无法删除']); } - - $plan = Plan::find($request->input('id')); - if (!$plan) { - return $this->fail([400202, '该订阅不存在']); + if ($request->input('id')) { + $plan = Plan::find($request->input('id')); + if (!$plan) { + return $this->fail([400202, '该订阅不存在']); + } } - return $this->success($plan->delete()); } @@ -102,7 +101,7 @@ class PlanController extends Controller try { $plan->update($updateData); } catch (\Exception $e) { - Log::error($e); + \Log::error($e); return $this->fail([500, '保存失败']); } @@ -125,7 +124,7 @@ class PlanController extends Controller DB::commit(); } catch (\Exception $e) { DB::rollBack(); - Log::error($e); + \Log::error($e); return $this->fail([500, '保存失败']); } return $this->success(true); diff --git a/app/Http/Controllers/V2/Admin/Server/GroupController.php b/app/Http/Controllers/V2/Admin/Server/GroupController.php index 83a53ac..a8c96fc 100644 --- a/app/Http/Controllers/V2/Admin/Server/GroupController.php +++ b/app/Http/Controllers/V2/Admin/Server/GroupController.php @@ -14,15 +14,15 @@ class GroupController extends Controller { public function fetch(Request $request): JsonResponse { + $serverGroups = ServerGroup::query() ->orderByDesc('id') ->withCount('users') - ->get(); - - // 只在需要时手动加载server_count - $serverGroups->each(function ($group) { - $group->setAttribute('server_count', $group->server_count); - }); + ->get() + ->transform(function ($group) { + $group->server_count = $group->servers()->count(); + return $group; + }); return $this->success($serverGroups); } diff --git a/app/Http/Controllers/V2/Admin/Server/ManageController.php b/app/Http/Controllers/V2/Admin/Server/ManageController.php index 762ecd9..2def3af 100644 --- a/app/Http/Controllers/V2/Admin/Server/ManageController.php +++ b/app/Http/Controllers/V2/Admin/Server/ManageController.php @@ -10,13 +10,12 @@ use App\Models\ServerGroup; use App\Services\ServerService; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; class ManageController extends Controller { public function getNodes(Request $request) { - $servers = ServerService::getAllServers()->map(function ($item) { + $servers = collect(ServerService::getAllServers())->map(function ($item) { $item['groups'] = ServerGroup::whereIn('id', $item['group_ids'])->get(['name', 'id']); $item['parent'] = $item->parent; return $item; @@ -42,7 +41,7 @@ class ManageController extends Controller DB::commit(); } catch (\Exception $e) { DB::rollBack(); - Log::error($e); + \Log::error($e); return $this->fail([500, '保存失败']); } @@ -61,7 +60,7 @@ class ManageController extends Controller $server->update($params); return $this->success(true); } catch (\Exception $e) { - Log::error($e); + \Log::error($e); return $this->fail([500, '保存失败']); } } @@ -70,7 +69,7 @@ class ManageController extends Controller Server::create($params); return $this->success(true); } catch (\Exception $e) { - Log::error($e); + \Log::error($e); return $this->fail([500, '创建失败']); } @@ -84,7 +83,7 @@ class ManageController extends Controller 'show' => 'integer', ]); - if (!Server::where('id', $request->id)->update(['show' => $request->show])) { + if (Server::where('id', $request->id)->update(['show' => $request->show]) === false) { return $this->fail([500, '保存失败']); } return $this->success(true); diff --git a/app/Http/Controllers/V2/Admin/StatController.php b/app/Http/Controllers/V2/Admin/StatController.php index e7d615c..5eac168 100644 --- a/app/Http/Controllers/V2/Admin/StatController.php +++ b/app/Http/Controllers/V2/Admin/StatController.php @@ -25,7 +25,8 @@ class StatController extends Controller { // 获取在线节点数 $onlineNodes = Server::all()->filter(function ($server) { - return !!$server->is_online; + $server->loadServerStatus(); + return $server->is_online; })->count(); // 获取在线设备数和在线用户数 $onlineDevices = User::where('t', '>=', time() - 600) @@ -267,7 +268,8 @@ class StatController extends Controller // 获取在线节点数 $onlineNodes = Server::all()->filter(function ($server) { - return !!$server->is_online; + $server->loadServerStatus(); + return $server->is_online; })->count(); // 获取在线设备数和在线用户数 diff --git a/app/Http/Controllers/V2/Admin/TicketController.php b/app/Http/Controllers/V2/Admin/TicketController.php index ca6d8c4..039570f 100644 --- a/app/Http/Controllers/V2/Admin/TicketController.php +++ b/app/Http/Controllers/V2/Admin/TicketController.php @@ -55,10 +55,13 @@ class TicketController extends Controller if (!$ticket) { return $this->fail([400202, '工单不存在']); } - $result = $ticket->toArray(); - $result['user'] = UserController::transformUserData($ticket->user); + $ticket->user = UserController::transformUserData($ticket->user); + $ticket->messages->each(function ($message) use ($ticket) { + $message->is_me = $message->user_id !== $ticket->user_id; - return $this->success($result); + }); + + return $this->success($ticket); } /** @@ -88,16 +91,12 @@ class TicketController extends Controller perPage: $request->integer('pageSize', 10), page: $request->integer('current', 1) ); - - // 获取items然后映射转换 - $items = collect($tickets->items())->map(function ($ticket) { - $ticketData = $ticket->toArray(); - $ticketData['user'] = UserController::transformUserData($ticket->user); - return $ticketData; - })->all(); - + $tickets->getCollection()->transform(function ($ticket) { + $ticket->user = UserController::transformUserData($ticket->user); + return $ticket; + }); return response([ - 'data' => $items, + 'data' => $tickets->items(), 'total' => $tickets->total() ]); } @@ -138,19 +137,4 @@ class TicketController extends Controller return $this->fail([500101, '关闭失败']); } } - - public function show($ticketId) - { - $ticket = Ticket::with([ - 'user', - 'messages' => function ($query) { - $query->with(['user']); // 如果需要用户信息 - } - ])->findOrFail($ticketId); - - // 自动包含 is_me 属性 - return response()->json([ - 'data' => $ticket - ]); - } } diff --git a/app/Http/Controllers/V2/Admin/UserController.php b/app/Http/Controllers/V2/Admin/UserController.php index f144b81..6b28c4c 100644 --- a/app/Http/Controllers/V2/Admin/UserController.php +++ b/app/Http/Controllers/V2/Admin/UserController.php @@ -81,7 +81,7 @@ class UserController extends Controller // 处理关联查询 if (str_contains($field, '.')) { [$relation, $relationField] = explode('.', $field); - $query->whereHas($relation, function ($q) use ($relationField, $value) { + $query->whereHas($relation, function($q) use ($relationField, $value) { if (is_array($value)) { $q->whereIn($relationField, $value); } else if (is_string($value) && str_contains($value, ':')) { @@ -163,7 +163,7 @@ class UserController extends Controller $users = $userModel->orderBy('id', 'desc') ->paginate($pageSize, ['*'], 'page', $current); - $users->getCollection()->map(function ($user) { + $users->getCollection()->transform(function ($user) { return self::transformUserData($user); }); @@ -177,14 +177,13 @@ class UserController extends Controller * Transform user data for response * * @param User $user - * @return array + * @return User */ - public static function transformUserData(User $user): array + public static function transformUserData(User $user): User { - $user = $user->toArray(); - $user['balance'] = $user['balance'] / 100; - $user['commission_balance'] = $user['commission_balance'] / 100; - $user['subscribe_url'] = Helper::getSubscribeUrl($user['token']); + $user->subscribe_url = Helper::getSubscribeUrl($user->token); + $user->balance = $user->balance / 100; + $user->commission_balance = $user->commission_balance / 100; return $user; } @@ -236,7 +235,7 @@ class UserController extends Controller if (isset($params['banned']) && (int) $params['banned'] === 1) { $authService = new AuthService($user); - $authService->removeAllSessions(); + $authService->removeSession(); } if (isset($params['balance'])) { $params['balance'] = $params['balance'] * 100; @@ -264,7 +263,7 @@ class UserController extends Controller { ini_set('memory_limit', '-1'); gc_enable(); // 启用垃圾回收 - + // 优化查询:使用with预加载plan关系,避免N+1问题 $query = User::with('plan:id,name') ->orderBy('id', 'asc') @@ -279,18 +278,18 @@ class UserController extends Controller 'token', 'plan_id' ]); - + $this->applyFiltersAndSorts($request, $query); - + $filename = 'users_' . date('Y-m-d_His') . '.csv'; - - return response()->streamDownload(function () use ($query) { + + return response()->streamDownload(function() use ($query) { // 打开输出流 $output = fopen('php://output', 'w'); - + // 添加BOM标记,确保Excel正确显示中文 - fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF)); - + fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF)); + // 写入CSV头部 fputcsv($output, [ '邮箱', @@ -302,9 +301,9 @@ class UserController extends Controller '订阅计划', '订阅地址' ]); - + // 分批处理数据以减少内存使用 - $query->chunk(500, function ($users) use ($output) { + $query->chunk(500, function($users) use ($output) { foreach ($users as $user) { try { $row = [ @@ -326,11 +325,11 @@ class UserController extends Controller continue; // 继续处理下一条记录 } } - + // 清理内存 gc_collect_cycles(); }); - + fclose($output); }, $filename, [ 'Content-Type' => 'text/csv; charset=UTF-8', diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4801da4..d8db93e 100755 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -11,7 +11,7 @@ class Kernel extends HttpKernel * * These middleware are run during every request to your application. * - * @var array + * @var array */ protected $middleware = [ \Illuminate\Http\Middleware\HandleCors::class, @@ -26,7 +26,7 @@ class Kernel extends HttpKernel /** * The application's route middleware groups. * - * @var array> + * @var array */ protected $middlewareGroups = [ 'web' => [ @@ -57,7 +57,7 @@ class Kernel extends HttpKernel * * These middleware may be assigned to groups or used individually. * - * @var array + * @var array */ protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, @@ -84,7 +84,7 @@ class Kernel extends HttpKernel * * This forces non-global middleware to always be in the given order. * - * @var array + * @var array */ protected $middlewarePriority = [ \Illuminate\Session\Middleware\StartSession::class, diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php index 1a63b4f..b39f49d 100755 --- a/app/Http/Middleware/Admin.php +++ b/app/Http/Middleware/Admin.php @@ -5,7 +5,6 @@ namespace App\Http\Middleware; use App\Exceptions\ApiException; use Illuminate\Support\Facades\Auth; use Closure; -use App\Models\User; class Admin { @@ -18,13 +17,15 @@ class Admin */ public function handle($request, Closure $next) { - /** @var User|null $user */ - $user = Auth::guard('sanctum')->user(); - - if (!$user || !$user->is_admin) { - return response()->json(['message' => 'Unauthorized'], 403); + if (!Auth::guard('sanctum')->check()) { + throw new ApiException('未登录或登陆已过期', 403); } - + + $user = Auth::guard('sanctum')->user(); + if (!$user->is_admin) { + throw new ApiException('无管理员权限', 403); + } + return $next($request); } } diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php index 53fcdd5..35b9824 100755 --- a/app/Http/Middleware/CheckForMaintenanceMode.php +++ b/app/Http/Middleware/CheckForMaintenanceMode.php @@ -2,17 +2,16 @@ namespace App\Http\Middleware; -use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; +use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware; -class CheckForMaintenanceMode extends PreventRequestsDuringMaintenance +class CheckForMaintenanceMode extends Middleware { /** - * 维护模式白名单URI - * @var array + * The URIs that should be reachable while maintenance mode is enabled. + * + * @var array */ protected $except = [ - // 示例: - // '/api/health-check', - // '/status' + // ]; } diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 31e9d1a..033136a 100755 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -7,8 +7,9 @@ use Illuminate\Cookie\Middleware\EncryptCookies as Middleware; class EncryptCookies extends Middleware { /** - * 不需要加密的Cookie名称列表 - * @var array + * The names of the cookies that should not be encrypted. + * + * @var array */ protected $except = [ // diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index fb507a4..5a50e7b 100755 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -7,13 +7,12 @@ use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware; class TrimStrings extends Middleware { /** - * 不需要去除前后空格的字段名 - * @var array + * The names of the attributes that should not be trimmed. + * + * @var array */ protected $except = [ 'password', 'password_confirmation', - 'encrypted_data', - 'signature' ]; } diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 83c5400..392efca 100755 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -8,8 +8,9 @@ use Illuminate\Http\Request; class TrustProxies extends Middleware { /** - * 可信代理列表 - * @var array|string|null + * The trusted proxies for this application. + * + * @var array|string */ protected $proxies = [ "173.245.48.0/20", @@ -35,7 +36,8 @@ class TrustProxies extends Middleware ]; /** - * 代理头映射 + * The headers that should be used to detect proxies. + * * @var int */ protected $headers = diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 9e7c0bd..324a166 100755 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -7,14 +7,16 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { /** - * 是否在响应中设置XSRF-TOKEN cookie + * Indicates whether the XSRF-TOKEN cookie should be set on the response. + * * @var bool */ protected $addHttpCookie = true; /** - * 不需要CSRF验证的URI列表 - * @var array + * The URIs that should be excluded from CSRF verification. + * + * @var array */ protected $except = [ // diff --git a/app/Http/Requests/Admin/ServerSave.php b/app/Http/Requests/Admin/ServerSave.php index b4d40b7..54fc40e 100644 --- a/app/Http/Requests/Admin/ServerSave.php +++ b/app/Http/Requests/Admin/ServerSave.php @@ -54,19 +54,7 @@ class ServerSave extends FormRequest 'reality_settings.short_id' => 'nullable|string', ], 'socks' => [ - ], - 'naive' => [ - 'tls' => 'required|integer', - 'tls_settings' => 'nullable|array', - ], - 'http' => [ - 'tls' => 'required|integer', - 'tls_settings' => 'nullable|array', - ], - 'mieru' => [ - 'transport' => 'required|string', - 'multiplexing' => 'required|string', - ], + ] ]; private function getBaseRules(): array diff --git a/app/Http/Resources/OrderResource.php b/app/Http/Resources/OrderResource.php index ae3e6e4..d2d5ff8 100644 --- a/app/Http/Resources/OrderResource.php +++ b/app/Http/Resources/OrderResource.php @@ -2,14 +2,10 @@ namespace App\Http\Resources; -use App\Models\Order; use App\Services\PlanService; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; -/** - * @mixin Order - */ class OrderResource extends JsonResource { /** @@ -21,8 +17,8 @@ class OrderResource extends JsonResource { return [ ...parent::toArray($request), - 'period' => PlanService::getLegacyPeriod((string)$this->period), - 'plan' => $this->whenLoaded('plan', fn() => PlanResource::make($this->plan)), + 'period' => PlanService::getLegacyPeriod($this->period), + 'plan' => PlanResource::make($this->plan), ]; } } diff --git a/app/Http/Routes/V1/ClientRoute.php b/app/Http/Routes/V1/ClientRoute.php index ad13989..8c38cfb 100644 --- a/app/Http/Routes/V1/ClientRoute.php +++ b/app/Http/Routes/V1/ClientRoute.php @@ -1,8 +1,6 @@ 'client' ], function ($router) { // Client - $router->get('/subscribe', [ClientController::class, 'subscribe'])->name('client.subscribe.legacy'); + $router->get('/subscribe', 'V1\\Client\\ClientController@subscribe')->name('client.subscribe.legacy'); // App - $router->get('/app/getConfig', [AppController::class, 'getConfig']); - $router->get('/app/getVersion', [AppController::class, 'getVersion']); + $router->get('/app/getConfig', 'V1\\Client\\AppController@getConfig'); + $router->get('/app/getVersion', 'V1\\Client\\AppController@getVersion'); }); } } diff --git a/app/Http/Routes/V1/PassportRoute.php b/app/Http/Routes/V1/PassportRoute.php index 3134b96..dc405e8 100644 --- a/app/Http/Routes/V1/PassportRoute.php +++ b/app/Http/Routes/V1/PassportRoute.php @@ -1,8 +1,6 @@ 'passport' ], function ($router) { // Auth - $router->post('/auth/register', [AuthController::class, 'register']); - $router->post('/auth/login', [AuthController::class, 'login']); - $router->get('/auth/token2Login', [AuthController::class, 'token2Login']); - $router->post('/auth/forget', [AuthController::class, 'forget']); - $router->post('/auth/getQuickLoginUrl', [AuthController::class, 'getQuickLoginUrl']); - $router->post('/auth/loginWithMailLink', [AuthController::class, 'loginWithMailLink']); + $router->post('/auth/register', 'V1\\Passport\\AuthController@register'); + $router->post('/auth/login', 'V1\\Passport\\AuthController@login'); + $router->get ('/auth/token2Login', 'V1\\Passport\\AuthController@token2Login'); + $router->post('/auth/forget', 'V1\\Passport\\AuthController@forget'); + $router->post('/auth/getQuickLoginUrl', 'V1\\Passport\\AuthController@getQuickLoginUrl'); + $router->post('/auth/loginWithMailLink', 'V1\\Passport\\AuthController@loginWithMailLink'); // Comm - $router->post('/comm/sendEmailVerify', [CommController::class, 'sendEmailVerify']); - $router->post('/comm/pv', [CommController::class, 'pv']); + $router->post('/comm/sendEmailVerify', 'V1\\Passport\\CommController@sendEmailVerify'); + $router->post('/comm/pv', 'V1\\Passport\\CommController@pv'); }); } } diff --git a/app/Http/Routes/V1/StaffRoute.php b/app/Http/Routes/V1/StaffRoute.php new file mode 100644 index 0000000..3889bee --- /dev/null +++ b/app/Http/Routes/V1/StaffRoute.php @@ -0,0 +1,32 @@ +group([ + 'prefix' => 'staff', + 'middleware' => 'staff' + ], function ($router) { + // Ticket + // $router->get ('/ticket/fetch', 'V1\\Staff\\TicketController@fetch'); + // $router->post('/ticket/reply', 'V1\\Staff\\TicketController@reply'); + // $router->post('/ticket/close', 'V1\\Staff\\TicketController@close'); + // // User + // $router->post('/user/update', 'V1\\Staff\\UserController@update'); + // $router->get ('/user/getUserInfoById', 'V1\\Staff\\UserController@getUserInfoById'); + // $router->post('/user/sendMail', 'V1\\Staff\\UserController@sendMail'); + // $router->post('/user/ban', 'V1\\Staff\\UserController@ban'); + // // Plan + // $router->get ('/plan/fetch', 'V1\\Staff\\PlanController@fetch'); + // // Notice + // $router->get ('/notice/fetch', 'V1\\Admin\\NoticeController@fetch'); + // $router->post('/notice/save', 'V1\\Admin\\NoticeController@save'); + // $router->post('/notice/update', 'V1\\Admin\\NoticeController@update'); + // $router->post('/notice/drop', 'V1\\Admin\\NoticeController@drop'); + }); + } +} diff --git a/app/Http/Routes/V1/UserRoute.php b/app/Http/Routes/V1/UserRoute.php index 578f64d..839a552 100644 --- a/app/Http/Routes/V1/UserRoute.php +++ b/app/Http/Routes/V1/UserRoute.php @@ -1,18 +1,6 @@ 'user' ], function ($router) { // User - $router->get('/resetSecurity', [UserController::class, 'resetSecurity']); - $router->get('/info', [UserController::class, 'info']); - $router->post('/changePassword', [UserController::class, 'changePassword']); - $router->post('/update', [UserController::class, 'update']); - $router->get('/getSubscribe', [UserController::class, 'getSubscribe']); - $router->get('/getStat', [UserController::class, 'getStat']); - $router->get('/checkLogin', [UserController::class, 'checkLogin']); - $router->post('/transfer', [UserController::class, 'transfer']); - $router->post('/getQuickLoginUrl', [UserController::class, 'getQuickLoginUrl']); - $router->get('/getActiveSession', [UserController::class, 'getActiveSession']); - $router->post('/removeActiveSession', [UserController::class, 'removeActiveSession']); + $router->get ('/resetSecurity', 'V1\\User\\UserController@resetSecurity'); + $router->get ('/info', 'V1\\User\\UserController@info'); + $router->post('/changePassword', 'V1\\User\\UserController@changePassword'); + $router->post('/update', 'V1\\User\\UserController@update'); + $router->get ('/getSubscribe', 'V1\\User\\UserController@getSubscribe'); + $router->get ('/getStat', 'V1\\User\\UserController@getStat'); + $router->get ('/checkLogin', 'V1\\User\\UserController@checkLogin'); + $router->post('/transfer', 'V1\\User\\UserController@transfer'); + $router->post('/getQuickLoginUrl', 'V1\\User\\UserController@getQuickLoginUrl'); + $router->get ('/getActiveSession', 'V1\\User\\UserController@getActiveSession'); + $router->post('/removeActiveSession', 'V1\\User\\UserController@removeActiveSession'); // Order - $router->post('/order/save', [OrderController::class, 'save']); - $router->post('/order/checkout', [OrderController::class, 'checkout']); - $router->get('/order/check', [OrderController::class, 'check']); - $router->get('/order/detail', [OrderController::class, 'detail']); - $router->get('/order/fetch', [OrderController::class, 'fetch']); - $router->get('/order/getPaymentMethod', [OrderController::class, 'getPaymentMethod']); - $router->post('/order/cancel', [OrderController::class, 'cancel']); + $router->post('/order/save', 'V1\\User\\OrderController@save'); + $router->post('/order/checkout', 'V1\\User\\OrderController@checkout'); + $router->get ('/order/check', 'V1\\User\\OrderController@check'); + $router->get ('/order/detail', 'V1\\User\\OrderController@detail'); + $router->get ('/order/fetch', 'V1\\User\\OrderController@fetch'); + $router->get ('/order/getPaymentMethod', 'V1\\User\\OrderController@getPaymentMethod'); + $router->post('/order/cancel', 'V1\\User\\OrderController@cancel'); // Plan - $router->get('/plan/fetch', [PlanController::class, 'fetch']); + $router->get ('/plan/fetch', 'V1\\User\\PlanController@fetch'); // Invite - $router->get('/invite/save', [InviteController::class, 'save']); - $router->get('/invite/fetch', [InviteController::class, 'fetch']); - $router->get('/invite/details', [InviteController::class, 'details']); + $router->get ('/invite/save', 'V1\\User\\InviteController@save'); + $router->get ('/invite/fetch', 'V1\\User\\InviteController@fetch'); + $router->get ('/invite/details', 'V1\\User\\InviteController@details'); // Notice - $router->get('/notice/fetch', [NoticeController::class, 'fetch']); + $router->get ('/notice/fetch', 'V1\\User\\NoticeController@fetch'); // Ticket - $router->post('/ticket/reply', [TicketController::class, 'reply']); - $router->post('/ticket/close', [TicketController::class, 'close']); - $router->post('/ticket/save', [TicketController::class, 'save']); - $router->get('/ticket/fetch', [TicketController::class, 'fetch']); - $router->post('/ticket/withdraw', [TicketController::class, 'withdraw']); + $router->post('/ticket/reply', 'V1\\User\\TicketController@reply'); + $router->post('/ticket/close', 'V1\\User\\TicketController@close'); + $router->post('/ticket/save', 'V1\\User\\TicketController@save'); + $router->get ('/ticket/fetch', 'V1\\User\\TicketController@fetch'); + $router->post('/ticket/withdraw', 'V1\\User\\TicketController@withdraw'); // Server - $router->get('/server/fetch', [ServerController::class, 'fetch']); + $router->get ('/server/fetch', 'V1\\User\\ServerController@fetch'); // Coupon - $router->post('/coupon/check', [CouponController::class, 'check']); + $router->post('/coupon/check', 'V1\\User\\CouponController@check'); // Telegram - $router->get('/telegram/getBotInfo', [TelegramController::class, 'getBotInfo']); + $router->get ('/telegram/getBotInfo', 'V1\\User\\TelegramController@getBotInfo'); // Comm - $router->get('/comm/config', [CommController::class, 'config']); - $router->Post('/comm/getStripePublicKey', [CommController::class, 'getStripePublicKey']); + $router->get ('/comm/config', 'V1\\User\\CommController@config'); + $router->Post('/comm/getStripePublicKey', 'V1\\User\\CommController@getStripePublicKey'); // Knowledge - $router->get('/knowledge/fetch', [KnowledgeController::class, 'fetch']); - $router->get('/knowledge/getCategory', [KnowledgeController::class, 'getCategory']); + $router->get ('/knowledge/fetch', 'V1\\User\\KnowledgeController@fetch'); + $router->get ('/knowledge/getCategory', 'V1\\User\\KnowledgeController@getCategory'); // Stat - $router->get('/stat/getTrafficLog', [StatController::class, 'getTrafficLog']); + $router->get ('/stat/getTrafficLog', 'V1\\User\\StatController@getTrafficLog'); }); } } diff --git a/app/Http/Routes/V2/PassportRoute.php b/app/Http/Routes/V2/PassportRoute.php index ed91d81..d45fc65 100644 --- a/app/Http/Routes/V2/PassportRoute.php +++ b/app/Http/Routes/V2/PassportRoute.php @@ -1,8 +1,6 @@ 'passport' ], function ($router) { // Auth - $router->post('/auth/register', [AuthController::class, 'register']); - $router->post('/auth/login', [AuthController::class, 'login']); - $router->get ('/auth/token2Login', [AuthController::class, 'token2Login']); - $router->post('/auth/forget', [AuthController::class, 'forget']); - $router->post('/auth/getQuickLoginUrl', [AuthController::class, 'getQuickLoginUrl']); - $router->post('/auth/loginWithMailLink', [AuthController::class, 'loginWithMailLink']); + $router->post('/auth/register', 'V1\\Passport\\AuthController@register'); + $router->post('/auth/login', 'V1\\Passport\\AuthController@login'); + $router->get ('/auth/token2Login', 'V1\\Passport\\AuthController@token2Login'); + $router->post('/auth/forget', 'V1\\Passport\\AuthController@forget'); + $router->post('/auth/getQuickLoginUrl', 'V1\\Passport\\AuthController@getQuickLoginUrl'); + $router->post('/auth/loginWithMailLink', 'V1\\Passport\\AuthController@loginWithMailLink'); // Comm - $router->post('/comm/sendEmailVerify', [CommController::class, 'sendEmailVerify']); - $router->post('/comm/pv', [CommController::class, 'pv']); + $router->post('/comm/sendEmailVerify', 'V1\\Passport\\CommController@sendEmailVerify'); + $router->post('/comm/pv', 'V1\\Passport\\CommController@pv'); }); } } diff --git a/app/Http/Routes/V2/UserRoute.php b/app/Http/Routes/V2/UserRoute.php index 38bc12f..ceed9bc 100644 --- a/app/Http/Routes/V2/UserRoute.php +++ b/app/Http/Routes/V2/UserRoute.php @@ -1,7 +1,6 @@ 'user' ], function ($router) { // User - $router->get('/resetSecurity', [UserController::class, 'resetSecurity']); - $router->get('/info', [UserController::class, 'info']); + $router->get('/resetSecurity', 'V1\\User\\UserController@resetSecurity'); + $router->get('/info', 'V1\\User\\UserController@info'); }); } } diff --git a/app/Logging/MysqlLoggerHandler.php b/app/Logging/MysqlLoggerHandler.php index eb0b09e..dfe178a 100644 --- a/app/Logging/MysqlLoggerHandler.php +++ b/app/Logging/MysqlLoggerHandler.php @@ -17,28 +17,28 @@ class MysqlLoggerHandler extends AbstractProcessingHandler protected function write(LogRecord $record): void { $record = $record->toArray(); - try { - if (isset($record['context']['exception']) && is_object($record['context']['exception'])) { + try{ + if(isset($record['context']['exception']) && is_object($record['context']['exception'])){ $record['context']['exception'] = (array)$record['context']['exception']; } - - $record['request_data'] = request()->all(); - + $record['request_data'] = request()->all() ??[]; $log = [ 'title' => $record['message'], 'level' => $record['level_name'], - 'host' => $record['extra']['request_host'] ?? request()->getSchemeAndHttpHost(), - 'uri' => $record['extra']['request_uri'] ?? request()->getRequestUri(), - 'method' => $record['extra']['request_method'] ?? request()->getMethod(), + 'host' => $record['request_host'] ?? request()->getSchemeAndHttpHost(), + 'uri' => $record['request_uri'] ?? request()->getRequestUri(), + 'method' => $record['request_method'] ?? request()->getMethod(), 'ip' => request()->getClientIp(), - 'data' => json_encode($record['request_data']), - 'context' => json_encode($record['context']), + 'data' => json_encode($record['request_data']) , + 'context' => isset($record['context']) ? json_encode($record['context']) : '', 'created_at' => $record['datetime']->getTimestamp(), 'updated_at' => $record['datetime']->getTimestamp(), ]; - LogModel::insert($log); - } catch (\Exception $e) { + LogModel::insert( + $log + ); + }catch (\Exception $e){ // Log::channel('daily')->error($e->getMessage().$e->getFile().$e->getTraceAsString()); } } diff --git a/app/Models/Order.php b/app/Models/Order.php index 4ee0b14..593c9a1 100755 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -3,38 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; -/** - * App\Models\Order - * - * @property int $id - * @property int $user_id - * @property int $plan_id - * @property int|null $payment_id - * @property int $period - * @property string $trade_no - * @property int $total_amount - * @property int|null $handling_amount - * @property int|null $balance_amount - * @property int $type - * @property int $status - * @property array|null $surplus_order_ids - * @property int|null $coupon_id - * @property int $created_at - * @property int $updated_at - * @property int|null $commission_status - * @property int|null $invite_user_id - * @property int|null $actual_commission_balance - * @property int|null $commission_rate - * @property int|null $commission_auto_check - * - * @property-read Plan $plan - * @property-read Payment|null $payment - * @property-read User $user - * @property-read \Illuminate\Database\Eloquent\Collection $commission_log - */ class Order extends Model { protected $table = 'v2_order'; @@ -43,8 +12,7 @@ class Order extends Model protected $casts = [ 'created_at' => 'timestamp', 'updated_at' => 'timestamp', - 'surplus_order_ids' => 'array', - 'handling_amount' => 'integer' + 'surplus_order_ids' => 'array' ]; const STATUS_PENDING = 0; // 待支付 @@ -72,34 +40,21 @@ class Order extends Model self::TYPE_RESET_TRAFFIC => '流量重置', ]; - /** - * 获取与订单关联的支付方式 - */ - public function payment(): BelongsTo + public function payment() { return $this->belongsTo(Payment::class, 'payment_id', 'id'); } - /** - * 获取与订单关联的用户 - */ - public function user(): BelongsTo + public function user() { return $this->belongsTo(User::class, 'user_id', 'id'); } - - /** - * 获取与订单关联的套餐 - */ - public function plan(): BelongsTo + public function plan() { - return $this->belongsTo(Plan::class, 'plan_id', 'id'); + return $this->belongsTo(Plan::class); } - /** - * 获取与订单关联的佣金记录 - */ - public function commission_log(): HasMany + public function commission_log() { return $this->hasMany(CommissionLog::class, 'trade_no', 'trade_no'); } diff --git a/app/Models/Payment.php b/app/Models/Payment.php index fec8b00..5830367 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -12,7 +12,6 @@ class Payment extends Model protected $casts = [ 'created_at' => 'timestamp', 'updated_at' => 'timestamp', - 'config' => 'array', - 'enable' => 'boolean' + 'config' => 'array' ]; } diff --git a/app/Models/Plan.php b/app/Models/Plan.php index 84740af..ba48ef1 100755 --- a/app/Models/Plan.php +++ b/app/Models/Plan.php @@ -7,31 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use InvalidArgumentException; use Carbon\Carbon; -use Illuminate\Database\Eloquent\Relations\HasOne; -/** - * App\Models\Plan - * - * @property int $id - * @property string $name 套餐名称 - * @property int|null $group_id 权限组ID - * @property int $transfer_enable 流量(KB) - * @property int|null $speed_limit 速度限制Mbps - * @property bool $show 是否显示 - * @property bool $renew 是否允许续费 - * @property bool $sell 是否允许购买 - * @property array|null $prices 价格配置 - * @property int $sort 排序 - * @property string|null $content 套餐描述 - * @property int $reset_traffic_method 流量重置方式 - * @property int|null $capacity_limit 订阅人数限制 - * @property int|null $device_limit 设备数量限制 - * @property int $created_at - * @property int $updated_at - * - * @property-read ServerGroup|null $group 关联的权限组 - * @property-read \Illuminate\Database\Eloquent\Collection $order 关联的订单 - */ class Plan extends Model { use HasFactory; @@ -40,12 +16,12 @@ class Plan extends Model protected $dateFormat = 'U'; // 定义流量重置方式 - public const RESET_TRAFFIC_FOLLOW_SYSTEM = null; // 跟随系统设置 - public const RESET_TRAFFIC_FIRST_DAY_MONTH = 0; // 每月1号 - public const RESET_TRAFFIC_MONTHLY = 1; // 按月重置 - public const RESET_TRAFFIC_NEVER = 2; // 不重置 - public const RESET_TRAFFIC_FIRST_DAY_YEAR = 3; // 每年1月1日 - public const RESET_TRAFFIC_YEARLY = 4; // 按年重置 + public const RESET_TRAFFIC_FOLLOW_SYSTEM = 0; // 跟随系统设置 + public const RESET_TRAFFIC_FIRST_DAY_MONTH = 1; // 每月1号 + public const RESET_TRAFFIC_MONTHLY = 2; // 按月重置 + public const RESET_TRAFFIC_NEVER = 3; // 不重置 + public const RESET_TRAFFIC_FIRST_DAY_YEAR = 4; // 每年1月1日 + public const RESET_TRAFFIC_YEARLY = 5; // 按年重置 // 定义价格类型 public const PRICE_TYPE_RESET_TRAFFIC = 'reset_traffic'; // 重置流量价格 @@ -370,7 +346,7 @@ class Plan extends Model return $this->hasMany(User::class); } - public function group(): HasOne + public function group() { return $this->hasOne(ServerGroup::class, 'id', 'group_id'); } @@ -408,9 +384,4 @@ class Plan extends Model $prices[self::PRICE_TYPE_RESET_TRAFFIC] = max(0, $price); $this->prices = $prices; } - - public function order(): HasMany - { - return $this->hasMany(Order::class); - } } \ No newline at end of file diff --git a/app/Models/Server.php b/app/Models/Server.php index ce0047d..02b51da 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -9,45 +9,7 @@ use Illuminate\Support\Facades\Cache; use App\Utils\CacheKey; use App\Utils\Helper; use App\Models\User; -use Illuminate\Database\Eloquent\Casts\Attribute; -/** - * App\Models\Server - * - * @property int $id - * @property string $name 节点名称 - * @property string $type 服务类型 - * @property string $host 主机地址 - * @property string $port 端口 - * @property string|null $server_port 服务器端口 - * @property array|null $group_ids 分组IDs - * @property array|null $route_ids 路由IDs - * @property array|null $tags 标签 - * @property string|null $show 是否显示 - * @property string|null $allow_insecure 是否允许不安全 - * @property string|null $network 网络类型 - * @property int|null $parent_id 父节点ID - * @property float|null $rate 倍率 - * @property int|null $sort 排序 - * @property array|null $protocol_settings 协议设置 - * @property int $created_at - * @property int $updated_at - * - * @property-read Server|null $parent 父节点 - * @property-read \Illuminate\Database\Eloquent\Collection $stats 节点统计 - * - * @property-read int|null $last_check_at 最后检查时间(Unix时间戳) - * @property-read int|null $last_push_at 最后推送时间(Unix时间戳) - * @property-read int $online 在线用户数 - * @property-read int $is_online 是否在线(1在线 0离线) - * @property-read string $available_status 可用状态描述 - * @property-read string $cache_key 缓存键 - * @property string|null $ports 端口范围 - * @property string|null $password 密码 - * @property int|null $u 上行流量 - * @property int|null $d 下行流量 - * @property int|null $total 总流量 - */ class Server extends Model { public const TYPE_HYSTERIA = 'hysteria'; @@ -57,9 +19,6 @@ class Server extends Model public const TYPE_TUIC = 'tuic'; public const TYPE_SHADOWSOCKS = 'shadowsocks'; public const TYPE_SOCKS = 'socks'; - public const TYPE_NAIVE = 'naive'; - public const TYPE_HTTP = 'http'; - public const TYPE_MIERU = 'mieru'; public const STATUS_OFFLINE = 0; public const STATUS_ONLINE_NO_PUSH = 1; public const STATUS_ONLINE = 2; @@ -94,9 +53,6 @@ class Server extends Model self::TYPE_TUIC, self::TYPE_SHADOWSOCKS, self::TYPE_SOCKS, - self::TYPE_NAIVE, - self::TYPE_HTTP, - self::TYPE_MIERU, ]; protected $table = 'v2_server'; @@ -187,32 +143,6 @@ class Server extends Model 'allow_insecure' => ['type' => 'boolean', 'default' => false] ] ] - ], - self::TYPE_SOCKS => [ - 'tls' => ['type' => 'integer', 'default' => 0], - 'tls_settings' => [ - 'type' => 'object', - 'fields' => [ - 'allow_insecure' => ['type' => 'boolean', 'default' => false] - ] - ] - ], - self::TYPE_NAIVE => [ - 'tls' => ['type' => 'integer', 'default' => 0], - 'tls_settings' => ['type' => 'array', 'default' => null] - ], - self::TYPE_HTTP => [ - 'tls' => ['type' => 'integer', 'default' => 0], - 'tls_settings' => [ - 'type' => 'object', - 'fields' => [ - 'allow_insecure' => ['type' => 'boolean', 'default' => false] - ] - ] - ], - self::TYPE_MIERU => [ - 'transport' => ['type' => 'string', 'default' => 'tcp'], - 'multiplexing' => ['type' => 'string', 'default' => 'MULTIPLEXING_LOW'] ] ]; @@ -244,6 +174,19 @@ class Server extends Model return $result; } + private function getDefaultSettings(array $configs): array + { + $defaults = []; + foreach ($configs as $key => $config) { + if ($config['type'] === 'object') { + $defaults[$key] = $this->getDefaultSettings($config['fields']); + } else { + $defaults[$key] = $config['default']; + } + } + return $defaults; + } + public function getProtocolSettingsAttribute($value) { $settings = json_decode($value, true) ?? []; @@ -263,22 +206,53 @@ class Server extends Model $this->attributes['protocol_settings'] = json_encode($castedSettings); } - public function generateShadowsocksPassword(User $user): string + public function loadParentCreatedAt(): void + { + if ($this->parent_id) { + $this->created_at = $this->parent()->value('created_at'); + } + } + + public function loadServerStatus(): void + { + $type = strtoupper($this->type); + $serverId = $this->parent_id ?: $this->id; + + $this->last_check_at = Cache::get(CacheKey::get("SERVER_{$type}_LAST_CHECK_AT", $serverId)); + $this->last_push_at = Cache::get(CacheKey::get("SERVER_{$type}_LAST_PUSH_AT", $serverId)); + $this->online = Cache::get(CacheKey::get("SERVER_{$type}_ONLINE_USER", $serverId)) ?? 0; + $this->is_online = (time() - 300 > $this->last_check_at) ? 0 : 1; + $this->available_status = $this->getAvailableStatus(); + $this->cache_key = "{$this->type}-{$this->id}-{$this->updated_at}-{$this->is_online}"; + } + + public function handlePortAllocation(): void + { + if (strpos($this->port, '-') !== false) { + $this->ports = $this->port; + $this->port = Helper::randomPort($this->port); + } else { + $this->port = (int) $this->port; + } + } + + public function generateShadowsocksPassword(User $user): void { if ($this->type !== self::TYPE_SHADOWSOCKS) { - return $user->uuid; + return; } + $this->password = $user->uuid; $cipher = data_get($this, 'protocol_settings.cipher'); if (!$cipher || !isset(self::CIPHER_CONFIGURATIONS[$cipher])) { - return $user->uuid; + return; } $config = self::CIPHER_CONFIGURATIONS[$cipher]; $serverKey = Helper::getServerKey($this->created_at, $config['serverKeySize']); $userKey = Helper::uuidToBase64($user->uuid, $config['userKeySize']); - return "{$serverKey}:{$userKey}"; + $this->password = "{$serverKey}:{$userKey}"; } public static function normalizeType(string $type): string @@ -323,99 +297,4 @@ class Server extends Model return ServerRoute::whereIn('id', $this->route_ids)->get(); } - /** - * 最后检查时间访问器 - */ - protected function lastCheckAt(): Attribute - { - return Attribute::make( - get: function () { - $type = strtoupper($this->type); - $serverId = $this->parent_id ?: $this->id; - return Cache::get(CacheKey::get("SERVER_{$type}_LAST_CHECK_AT", $serverId)); - } - ); - } - - /** - * 最后推送时间访问器 - */ - protected function lastPushAt(): Attribute - { - return Attribute::make( - get: function () { - $type = strtoupper($this->type); - $serverId = $this->parent_id ?: $this->id; - return Cache::get(CacheKey::get("SERVER_{$type}_LAST_PUSH_AT", $serverId)); - } - ); - } - - /** - * 在线用户数访问器 - */ - protected function online(): Attribute - { - return Attribute::make( - get: function () { - $type = strtoupper($this->type); - $serverId = $this->parent_id ?: $this->id; - return Cache::get(CacheKey::get("SERVER_{$type}_ONLINE_USER", $serverId)) ?? 0; - } - ); - } - - /** - * 是否在线访问器 - */ - protected function isOnline(): Attribute - { - return Attribute::make( - get: function () { - return (time() - 300 > $this->last_check_at) ? 0 : 1; - } - ); - } - - /** - * 可用状态访问器 - */ - protected function availableStatus(): Attribute - { - return Attribute::make( - get: function () { - if ($this->is_online) { - return true; - } - return false; - } - ); - } - - /** - * 缓存键访问器 - */ - protected function cacheKey(): Attribute - { - return Attribute::make( - get: function () { - return "{$this->type}-{$this->id}-{$this->updated_at}-{$this->is_online}"; - } - ); - } - - /** - * 服务器密钥访问器 - */ - protected function serverKey(): Attribute - { - return Attribute::make( - get: function () { - if ($this->type === self::TYPE_SHADOWSOCKS) { - return Helper::getServerKey($this->created_at, 16); - } - return null; - } - ); - } } diff --git a/app/Models/ServerGroup.php b/app/Models/ServerGroup.php index 57f3514..3a47144 100755 --- a/app/Models/ServerGroup.php +++ b/app/Models/ServerGroup.php @@ -4,17 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Casts\Attribute; -/** - * App\Models\ServerGroup - * - * @property int $id - * @property string $name 分组名 - * @property int $created_at - * @property int $updated_at - * @property-read int $server_count 服务器数量 - */ class ServerGroup extends Model { protected $table = 'v2_server_group'; @@ -33,14 +23,4 @@ class ServerGroup extends Model { return Server::whereJsonContains('group_ids', (string) $this->id)->get(); } - - /** - * 获取服务器数量 - */ - protected function serverCount(): Attribute - { - return Attribute::make( - get: fn () => Server::whereJsonContains('group_ids', (string) $this->id)->count(), - ); - } } diff --git a/app/Models/StatServer.php b/app/Models/StatServer.php index efa1fc6..fc4cbd1 100644 --- a/app/Models/StatServer.php +++ b/app/Models/StatServer.php @@ -4,18 +4,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -/** - * App\Models\StatServer - * - * @property int $id - * @property int $server_id 服务器ID - * @property int $u 上行流量 - * @property int $d 下行流量 - * @property int $record_at 记录时间 - * @property int $created_at - * @property int $updated_at - * @property-read int $value 通过SUM(u + d)计算的总流量值,仅在查询指定时可用 - */ class StatServer extends Model { protected $table = 'v2_stat_server'; diff --git a/app/Models/StatUser.php b/app/Models/StatUser.php index a956bd7..07984d9 100644 --- a/app/Models/StatUser.php +++ b/app/Models/StatUser.php @@ -4,18 +4,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -/** - * App\Models\StatUser - * - * @property int $id - * @property int $user_id 用户ID - * @property int $u 上行流量 - * @property int $d 下行流量 - * @property int $record_at 记录时间 - * @property int $created_at - * @property int $updated_at - * @property-read int $value 通过SUM(u + d)计算的总流量值,仅在查询指定时可用 - */ class StatUser extends Model { protected $table = 'v2_stat_user'; diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 86ba9b6..b92a5e9 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -3,25 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; -/** - * App\Models\Ticket - * - * @property int $id - * @property int $user_id 用户ID - * @property string $subject 工单主题 - * @property string|null $level 工单等级 - * @property int $status 工单状态 - * @property int|null $reply_status 回复状态 - * @property int|null $last_reply_user_id 最后回复人 - * @property int $created_at - * @property int $updated_at - * - * @property-read User $user 关联的用户 - * @property-read \Illuminate\Database\Eloquent\Collection $messages 关联的工单消息 - */ class Ticket extends Model { protected $table = 'v2_ticket'; @@ -39,21 +21,16 @@ class Ticket extends Model self::STATUS_CLOSED => '关闭' ]; - public function user(): BelongsTo + public function user() { return $this->belongsTo(User::class, 'user_id', 'id'); } - - /** - * 关联的工单消息 - */ - public function messages(): HasMany + public function messages() { return $this->hasMany(TicketMessage::class, 'ticket_id', 'id'); } - // 即将删除 - public function message(): HasMany + public function message() { return $this->hasMany(TicketMessage::class, 'ticket_id', 'id'); } diff --git a/app/Models/TicketMessage.php b/app/Models/TicketMessage.php index 0cc3c95..4673b33 100644 --- a/app/Models/TicketMessage.php +++ b/app/Models/TicketMessage.php @@ -3,20 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -/** - * App\Models\TicketMessage - * - * @property int $id - * @property int $ticket_id - * @property int $user_id - * @property string $message - * @property \Illuminate\Support\Carbon $created_at - * @property \Illuminate\Support\Carbon $updated_at - * @property-read \App\Models\Ticket $ticket 关联的工单 - * @property-read bool $is_me 当前消息是否由工单发起人发送 - */ class TicketMessage extends Model { protected $table = 'v2_ticket_message'; @@ -26,22 +13,4 @@ class TicketMessage extends Model 'created_at' => 'timestamp', 'updated_at' => 'timestamp' ]; - - protected $appends = ['is_me']; - - /** - * 关联的工单 - */ - public function ticket(): BelongsTo - { - return $this->belongsTo(Ticket::class, 'ticket_id', 'id'); - } - - /** - * 判断消息是否由工单发起人发送 - */ - public function getIsMeAttribute(): bool - { - return $this->ticket->user_id === $this->user_id; - } } diff --git a/app/Models/User.php b/app/Models/User.php index 238130a..e23baa0 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,55 +2,10 @@ namespace App\Models; -use App\Utils\Helper; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Sanctum\HasApiTokens; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; -/** - * App\Models\User - * - * @property int $id 用户ID - * @property string $email 邮箱 - * @property string $password 密码 - * @property string|null $password_algo 加密方式 - * @property string|null $password_salt 加密盐 - * @property string $token 邀请码 - * @property string $uuid - * @property int|null $invite_user_id 邀请人 - * @property int|null $plan_id 订阅ID - * @property int|null $group_id 权限组ID - * @property int|null $transfer_enable 流量(KB) - * @property int|null $speed_limit 限速Mbps - * @property int|null $u 上行流量 - * @property int|null $d 下行流量 - * @property int|null $banned 是否封禁 - * @property int|null $remind_expire 到期提醒 - * @property int|null $remind_traffic 流量提醒 - * @property int|null $expired_at 过期时间 - * @property int|null $balance 余额 - * @property int|null $commission_balance 佣金余额 - * @property float $commission_rate 返佣比例 - * @property int|null $device_limit 设备限制数量 - * @property int|null $discount 折扣 - * @property int|null $last_login_at 最后登录时间 - * @property int|null $parent_id 父账户ID - * @property int|null $is_admin 是否管理员 - * @property int $created_at - * @property int $updated_at - * @property bool $commission_auto_check 是否自动计算佣金 - * - * @property-read User|null $invite_user 邀请人信息 - * @property-read \App\Models\Plan|null $plan 用户订阅计划 - * @property-read ServerGroup|null $group 权限组 - * @property-read \Illuminate\Database\Eloquent\Collection $codes 邀请码列表 - * @property-read \Illuminate\Database\Eloquent\Collection $orders 订单列表 - * @property-read \Illuminate\Database\Eloquent\Collection $stat 统计信息 - * @property-read \Illuminate\Database\Eloquent\Collection $tickets 工单列表 - * @property-read User|null $parent 父账户 - * @property-read string $subscribe_url 订阅链接(动态生成) - */ class User extends Authenticatable { use HasApiTokens; @@ -59,72 +14,52 @@ class User extends Authenticatable protected $guarded = ['id']; protected $casts = [ 'created_at' => 'timestamp', - 'updated_at' => 'timestamp', - 'banned' => 'boolean', - 'remind_expire' => 'boolean', - 'remind_traffic' => 'boolean', - 'commission_auto_check' => 'boolean', - 'commission_rate' => 'float' + 'updated_at' => 'timestamp' ]; protected $hidden = ['password']; - public const COMMISSION_TYPE_SYSTEM = 0; - public const COMMISSION_TYPE_PERIOD = 1; - public const COMMISSION_TYPE_ONETIME = 2; - // 获取邀请人信息 - public function invite_user(): BelongsTo + public function invite_user() { return $this->belongsTo(self::class, 'invite_user_id', 'id'); } - /** - * 获取用户订阅计划 - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function plan(): BelongsTo + // 获取用户套餐 + public function plan() { return $this->belongsTo(Plan::class, 'plan_id', 'id'); } - public function group(): BelongsTo + public function group() { return $this->belongsTo(ServerGroup::class, 'group_id', 'id'); } // 获取用户邀请码列表 - public function codes(): HasMany + public function codes() { return $this->hasMany(InviteCode::class, 'user_id', 'id'); } - public function orders(): HasMany + public function orders() { return $this->hasMany(Order::class, 'user_id', 'id'); } - public function stat(): HasMany + public function stat() { return $this->hasMany(StatUser::class, 'user_id', 'id'); } // 关联工单列表 - public function tickets(): HasMany + public function tickets() { return $this->hasMany(Ticket::class, 'user_id', 'id'); } - public function parent(): BelongsTo + public function parent() { return $this->belongsTo(self::class, 'parent_id', 'id'); } - - /** - * 获取订阅链接属性 - */ - public function getSubscribeUrlAttribute(): string - { - return Helper::getSubscribeUrl($this->token); - } } diff --git a/app/Payments/BTCPay.php b/app/Payments/BTCPay.php index 432fc17..00a3cea 100644 --- a/app/Payments/BTCPay.php +++ b/app/Payments/BTCPay.php @@ -83,6 +83,7 @@ class BTCPay implements PaymentInterface if (!self::hashEqual($signraturHeader, $computedSignature)) { throw new ApiException('HMAC signature does not match', 400); + return false; } //get order id store in metadata @@ -111,8 +112,8 @@ class BTCPay implements PaymentInterface $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, false); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 300); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); curl_setopt( diff --git a/app/Payments/BinancePay.php b/app/Payments/BinancePay.php deleted file mode 100644 index 7b727ba..0000000 --- a/app/Payments/BinancePay.php +++ /dev/null @@ -1,118 +0,0 @@ -config = $config; - } - - public function form() - { - return [ - 'api_key' => [ - 'label' => 'API Key', - 'type' => 'input', - 'description' => '请输入您的 Binance API Key' - ], - 'secret_key' => [ - 'label' => 'Secret Key', - 'type' => 'input', - 'description' => '请输入您的 Binance Secret Key' - ] - ]; - } - - public function pay($order) - { - $timestamp = intval(microtime(true) * 1000); // Timestamp in milliseconds - $nonceStr = bin2hex(random_bytes(16)); // Generating a nonce - $request = [ - "env" => [ - "terminalType" => "APP" - ], - 'merchantTradeNo' => strval($order['trade_no']), - 'fiatCurrency' => 'CNY', - 'fiatAmount' => ($order["total_amount"] / 100), - 'supportPayCurrency' => "USDT,BNB", - 'description' => strval($order['trade_no']), - 'webhookUrl' => $order['notify_url'], - 'returnUrl' => $order['return_url'], - "goodsDetails" => [ - [ - "goodsType" => "01", - "goodsCategory" => "D000", - "referenceGoodsId" => "7876763A3B", - "goodsName" => "Ice Cream", - "goodsDetail" => "Greentea ice cream cone" - ] - ] - ]; - $body = json_encode($request, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'https://bpay.binanceapi.com/binancepay/openapi/v3/order'); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json; charset=utf-8', - 'BinancePay-Timestamp: ' . $timestamp, - 'BinancePay-Nonce: ' . $nonceStr, - 'BinancePay-Certificate-SN: ' . $this->config['api_key'], - 'BinancePay-Signature: ' . $this->generateSignature($body, $this->config['secret_key'], $timestamp, $nonceStr), - ]); - curl_setopt($ch, CURLOPT_PROXY, "socks5h://154.3.37.204:47714"); - curl_setopt($ch, CURLOPT_PROXYUSERPWD, "GGn28Io5fW:9VkWfoPGiG"); - $response = curl_exec($ch); - curl_close($ch); - if (!$response) { - abort(400, '支付失败,请稍后再试'); - } - $res = json_decode($response, true); - \Log::channel('daily')->info($res); - if (!is_array($res)) { - abort(400, '支付失败,请稍后再试'); - } - if (isset($res['code']) && $res['code'] == '400201') { - $res['data'] = \Cache::get('CheckoutInfo_' . strval($order['trade_no'])); - } - if (!isset($res['data'])) { - abort(400, '支付失败,请稍后再试'); - } - if (!is_array($res['data']) || !isset($res['data']['checkoutUrl'])) { - abort(400, '支付失败,请稍后再试'); - } - // 缓存支付信息 - \Cache::put('CheckoutInfo_' . strval($order['trade_no']), $res['data']); - return [ - 'type' => 1, // 0:qrcode 1:url - 'data' => $res['data']['checkoutUrl'] - ]; - } - - public function notify($params) - { - $bizStatus = $params['bizStatus']; - if ($bizStatus !== 'PAY_SUCCESS'){ - return false; - } - $data = json_decode($params['data'], true); - - return [ - 'trade_no' => $data['merchantTradeNo'], - 'callback_no' => $params['bizIdStr'], - 'custom_result' => '{"returnCode":"SUCCESS","returnMessage":null}' - ]; - } - private function generateSignature($body, $secret, $timestamp, $nonceStr) - { - $payload = $timestamp . chr(0x0A) . $nonceStr . chr(0x0A) . $body . chr(0x0A); - return strtoupper(hash_hmac('sha512', $payload, $secret)); - } -} diff --git a/app/Payments/Coinbase.php b/app/Payments/Coinbase.php index 9c9cdc2..bb5c3bb 100644 --- a/app/Payments/Coinbase.php +++ b/app/Payments/Coinbase.php @@ -95,8 +95,8 @@ class Coinbase implements PaymentInterface $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, false); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 300); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); curl_setopt( diff --git a/app/Payments/EPayWxpay.php b/app/Payments/EPayWxpay.php deleted file mode 100644 index db1deca..0000000 --- a/app/Payments/EPayWxpay.php +++ /dev/null @@ -1,71 +0,0 @@ -config = $config; - } - - public function form() - { - return [ - 'url' => [ - 'label' => 'URL', - 'description' => '', - 'type' => 'input', - ], - 'pid' => [ - 'label' => 'PID', - 'description' => '', - 'type' => 'input', - ], - 'key' => [ - 'label' => 'KEY', - 'description' => '', - 'type' => 'input', - ] - ]; - } - - public function pay($order) - { - $params = [ - 'money' => $order['total_amount'] / 100, - 'type' => 'wxpay', - 'name' => $order['trade_no'], - 'notify_url' => $order['notify_url'], - 'return_url' => $order['return_url'], - 'out_trade_no' => $order['trade_no'], - 'pid' => $this->config['pid'] - ]; - ksort($params); - reset($params); - $str = stripslashes(urldecode(http_build_query($params))) . $this->config['key']; - $params['sign'] = md5($str); - $params['sign_type'] = 'MD5'; - return [ - 'type' => 1, // 0:qrcode 1:url - 'data' => $this->config['url'] . '/submit.php?' . http_build_query($params) - ]; - } - - public function notify($params) - { - $sign = $params['sign']; - unset($params['sign']); - unset($params['sign_type']); - ksort($params); - reset($params); - $str = stripslashes(urldecode(http_build_query($params))) . $this->config['key']; - if ($sign !== md5($str)) { - return false; - } - return [ - 'trade_no' => $params['out_trade_no'], - 'callback_no' => $params['trade_no'] - ]; - } -} diff --git a/app/Payments/HiiCashPayment.php b/app/Payments/HiiCashPayment.php deleted file mode 100644 index 8571653..0000000 --- a/app/Payments/HiiCashPayment.php +++ /dev/null @@ -1,113 +0,0 @@ -config = $config; - } - - public function form() - { - return [ - 'rate' => [ - 'label' => '汇率', - 'description' => 'HiiCash支付单位为美元,如果您站点金额单位不为美元则需要填写汇率', - 'type' => 'input', - 'default' => '2333' - ], - 'pid' => [ - 'label' => '商户号', - 'description' => '', - 'type' => 'input', - ], - 'appid' => [ - 'label' => '应用ID', - 'description' => '', - 'type' => 'input' - ], - 'key' => [ - 'label' => '私钥', - 'description' => '', - 'type' => 'input', - ] - ]; - } - - public function pay($order) - { - - $request = [ - "mchNo" => $this->config["pid"], - "appId" => $this->config["appid"], - "mchOrderNo" => $order["trade_no"], - "amount" => ceil(($order["total_amount"] * 100) / ($this->config['rate'] ?? "1")) / 100, - "payDataType" => "Cashier", - "currency" => "USD", - "subject" => $order["trade_no"], - "notifyUrl" => $order["notify_url"], - "returnUrl" => $order["return_url"], - ]; - $headers = [ - "HiicashPay-Timestamp" => (int)(string)floor(microtime(true) * 1000), - "HiicashPay-Nonce" => \Str::random(32), - "HiicashPay-AppId" => $this->config["appid"] - ]; - $body = json_encode($request, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - $payload = $headers['HiicashPay-Timestamp'] . chr(0x0A) . $headers['HiicashPay-Nonce'] . chr(0x0A) . $body . chr(0x0A); - $signature = $this->generate_signature($payload, $this->config['key']); - $headers["HiicashPay-Signature"] = $signature; - $jsonStr = json_encode($request, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - $httpHeaders = []; - foreach ($headers as $key => $header) { - $httpHeaders[] = $key . ': ' . $header; - } - $httpHeaders[] = 'Content-Type: application/json; charset=utf-8'; - $httpHeaders[] = 'Content-Length: ' . strlen($jsonStr); - $ch = curl_init(file_get_contents('https://hiicash.oss-ap-northeast-1.aliyuncs.com/gateway.txt') . 'pay/order/create'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr); - curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders); - $response = curl_exec($ch); - curl_close($ch); - if (!$response) { - abort(400, '支付失败,请稍后再试'); - } - $res = json_decode($response, true); - if (!is_array($res) || !isset($res['data'])) { - abort(400, '支付失败,请稍后再试'); - } - if (!is_array($res['data']) || !isset($res['data']['payData'])) { - abort(400, '支付失败,请稍后再试'); - } - return [ - 'type' => 1, // 0:qrcode 1:url - 'data' => $res['data']['payData'] - ]; - } - - public function notify($params) - { - if (!isset($params['mchOrderNo']) || !isset($params['mchOrderNo'])) { - return false; - } - return [ - 'trade_no' => $params['mchOrderNo'], - 'callback_no' => $params['payOrderId'], - 'custom_result' => '{"returnCode": "success","returnMsg": ""}' - ]; - } - - - // 使用 HMAC-SHA512 算法生成签名 - function generate_signature(string $payload, string $secret_key) - { - $hash = hash_hmac("sha512", $payload, $secret_key, true); - // 将签名转换为大写字符串 - return strtoupper(bin2hex($hash)); - } -} diff --git a/app/Payments/PayPal.php b/app/Payments/PayPal.php deleted file mode 100644 index 544f9b4..0000000 --- a/app/Payments/PayPal.php +++ /dev/null @@ -1,132 +0,0 @@ -config = $config; - $this->client = new Client(); - $this->apiHost = optional($this->config)['mode'] == 'sandbox' ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; - } - - public function form() - { - return [ - 'mode' => [ - 'label' => 'Mode', - 'description' => '沙箱/生产模式 sandbox/live', - 'type' => 'input', - ], - 'client_id' => [ - 'label' => 'Client ID', - 'description' => 'PayPal Client ID', - 'type' => 'input', - ], - 'client_secret' => [ - 'label' => 'Client Secret', - 'description' => 'PayPal Client Secret', - 'type' => 'input', - ], - 'rate' => [ - 'label' => '汇率', - 'description' => 'Paypal支付单位为USD,如果您站点金额单位不为USD则需要填写汇率', - 'type' => 'input', - 'default' => '2333' - ], - ]; - } - - public function pay($order) - { - $this->token = json_decode($this->client->post("{$this->apiHost}/v1/oauth2/token", [ - 'auth' => [$this->config['client_id'], $this->config['client_secret']], - 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], - 'form_params' => ['grant_type' => 'client_credentials'] - ])->getBody(), true)['access_token']; - // 创建订单 - $order = json_decode($this->client->request('POST', "{$this->apiHost}/v2/checkout/orders", [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'PayPal-Request-Id' => $order['trade_no'], - 'Authorization' => "Bearer {$this->token}" - ], - 'json' => [ - 'intent' => 'CAPTURE', - 'purchase_units' => [ - [ - "reference_id" => $order['trade_no'], - "amount" => [ - "currency_code" => "USD", - "value" => number_format(ceil(($order["total_amount"] * 100) / ($this->config['rate'] ?? "1")) / 10000, 2, '.', '') - ] - ] - ], - "payment_source" => [ - "paypal" => [ - "experience_context" => [ - "payment_method_preference" => "UNRESTRICTED", - "brand_name" => $order['trade_no'], - "locale" => "zh-CN", - "landing_page" => "NO_PREFERENCE", - "shipping_preference" => "NO_SHIPPING", - "user_action" => "PAY_NOW", - "return_url" => $order['return_url'], - "cancel_url" => $order['return_url'] - ] - ] - ] - ] - - ])->getBody(), true); - - $payerActionUrl = ''; - foreach ($order['links'] as $link) { - if ($link['rel'] === 'payer-action') { - $payerActionUrl = $link['href']; - break; - } - } - - return [ - 'type' => 1, // 0:qrcode 1:url - 'data' => $payerActionUrl - ]; - - } - - public function notify($params) - { - $this->token = json_decode($this->client->post("{$this->apiHost}/v1/oauth2/token", [ - 'auth' => [$this->config['client_id'], $this->config['client_secret']], - 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], - 'form_params' => ['grant_type' => 'client_credentials'] - ])->getBody(), true)['access_token']; - $resource = $params['resource']; - $purchase_units = $resource['purchase_units']; - if ($params['event_type'] == 'CHECKOUT.ORDER.APPROVED') { - $order = json_decode($this->client->request('POST', "{$this->apiHost}/v2/checkout/orders/{$resource['id']}/capture", [ - "headers" => [ - 'Content-Type' => 'application/json', - 'Authorization' => "Bearer {$this->token}" - ] - ])->getBody(), true); - if ($order['status'] == 'COMPLETED') { - return [ - 'trade_no' => $purchase_units[0]['reference_id'], - 'callback_no' => $order['id'] - ]; - } - } - return false; - - } -} diff --git a/app/Protocols/Clash.php b/app/Protocols/Clash.php index 2946e02..63bca4b 100644 --- a/app/Protocols/Clash.php +++ b/app/Protocols/Clash.php @@ -28,7 +28,7 @@ class Clash implements ProtocolInterface $servers = $this->servers; $user = $this->user; $appName = admin_setting('app_name', 'XBoard'); - + // 优先从 admin_setting 获取模板 $template = admin_setting('subscribe_template_clash'); if (empty($template)) { @@ -40,7 +40,7 @@ class Clash implements ProtocolInterface $template = file_get_contents($defaultConfig); } } - + $config = Yaml::parse($template); $proxy = []; $proxies = []; @@ -67,14 +67,6 @@ class Clash implements ProtocolInterface array_push($proxy, self::buildTrojan($user['uuid'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'socks') { - array_push($proxy, self::buildSocks5($user['uuid'], $item)); - array_push($proxies, $item['name']); - } - if ($item['type'] === 'http') { - array_push($proxy, self::buildHttp($user['uuid'], $item)); - array_push($proxies, $item['name']); - } } $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy); @@ -179,7 +171,7 @@ class Clash implements ProtocolInterface if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') { $array['http-opts'] = [ 'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'), - 'path' => \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])) + 'path' => \Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])) ]; } break; @@ -239,56 +231,9 @@ class Clash implements ProtocolInterface return $array; } - public static function buildSocks5($password, $server) - { - $protocol_settings = $server['protocol_settings']; - $array = []; - $array['name'] = $server['name']; - $array['type'] = 'socks5'; - $array['server'] = $server['host']; - $array['port'] = $server['port']; - $array['udp'] = true; - - $array['username'] = $password; - $array['password'] = $password; - - // TLS 配置 - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = true; - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false); - } - - return $array; - } - - public static function buildHttp($password, $server) - { - $protocol_settings = $server['protocol_settings']; - $array = []; - $array['name'] = $server['name']; - $array['type'] = 'http'; - $array['server'] = $server['host']; - $array['port'] = $server['port']; - - $array['username'] = $password; - $array['password'] = $password; - - // TLS 配置 - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = true; - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false); - } - - return $array; - } - private function isMatch($exp, $str) { - try { - return preg_match($exp, $str) === 1; - } catch (\Exception $e) { - return false; - } + return @preg_match($exp, $str); } private function isRegex($exp) @@ -296,10 +241,6 @@ class Clash implements ProtocolInterface if (empty($exp)) { return false; } - try { - return preg_match($exp, '') !== false; - } catch (\Exception $e) { - return false; - } + return @preg_match((string) $exp, '') !== false; } } diff --git a/app/Protocols/ClashMeta.php b/app/Protocols/ClashMeta.php index bac28f2..c14c7a2 100644 --- a/app/Protocols/ClashMeta.php +++ b/app/Protocols/ClashMeta.php @@ -13,11 +13,7 @@ class ClashMeta implements ProtocolInterface private $servers; private $user; - /** - * @param mixed $user 用户实例 - * @param array $servers 服务器列表 - */ - public function __construct($user, $servers) + public function __construct($user, $servers, array $options = null) { $this->user = $user; $this->servers = $servers; @@ -82,18 +78,6 @@ class ClashMeta implements ProtocolInterface array_push($proxy, self::buildTuic($user['uuid'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'socks') { - array_push($proxy, self::buildSocks5($user['uuid'], $item)); - array_push($proxies, $item['name']); - } - if ($item['type'] === 'http') { - array_push($proxy, self::buildHttp($user['uuid'], $item)); - array_push($proxies, $item['name']); - } - if ($item['type'] === 'mieru') { - array_push($proxy, self::buildMieru($user['uuid'], $item)); - array_push($proxies, $item['name']); - } } $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy); @@ -192,7 +176,7 @@ class ClashMeta implements ProtocolInterface if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') { $array['http-opts'] = [ 'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'), - 'path' => \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])) + 'path' => \Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])) ]; } break; @@ -385,78 +369,9 @@ class ClashMeta implements ProtocolInterface return $array; } - public static function buildMieru($password, $server) - { - $protocol_settings = data_get($server, 'protocol_settings', []); - $array = [ - 'name' => $server['name'], - 'type' => 'mieru', - 'server' => $server['host'], - 'port' => $server['port'], - 'username' => $password, - 'password' => $password, - 'transport' => strtoupper(data_get($protocol_settings, 'transport', 'TCP')), - 'multiplexing' => data_get($protocol_settings, 'multiplexing', 'MULTIPLEXING_LOW') - ]; - - // 如果配置了端口范围 - if (isset($server['ports'])) { - $array['port-range'] = $server['ports']; - } - - return $array; - } - - public static function buildSocks5($password, $server) - { - $protocol_settings = $server['protocol_settings']; - $array = []; - $array['name'] = $server['name']; - $array['type'] = 'socks5'; - $array['server'] = $server['host']; - $array['port'] = $server['port']; - $array['udp'] = true; - - $array['username'] = $password; - $array['password'] = $password; - - // TLS 配置 - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = true; - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false); - } - - return $array; - } - - public static function buildHttp($password, $server) - { - $protocol_settings = $server['protocol_settings']; - $array = []; - $array['name'] = $server['name']; - $array['type'] = 'http'; - $array['server'] = $server['host']; - $array['port'] = $server['port']; - - $array['username'] = $password; - $array['password'] = $password; - - // TLS 配置 - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = true; - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false); - } - - return $array; - } - private function isMatch($exp, $str) { - try { - return preg_match($exp, $str) === 1; - } catch (\Exception $e) { - return false; - } + return @preg_match($exp, $str); } private function isRegex($exp) @@ -464,10 +379,6 @@ class ClashMeta implements ProtocolInterface if (empty($exp)) { return false; } - try { - return preg_match($exp, '') !== false; - } catch (\Exception $e) { - return false; - } + return @preg_match($exp, '') !== false; } } diff --git a/app/Protocols/General.php b/app/Protocols/General.php index 6da11f3..5c18caa 100644 --- a/app/Protocols/General.php +++ b/app/Protocols/General.php @@ -5,7 +5,7 @@ namespace App\Protocols; use App\Contracts\ProtocolInterface; use App\Utils\Helper; -use Illuminate\Support\Arr; + class General implements ProtocolInterface { public $flags = ['general', 'v2rayn', 'v2rayng', 'passwall', 'ssrplus', 'sagernet']; @@ -45,9 +45,6 @@ class General implements ProtocolInterface if ($item['type'] === 'hysteria') { $uri .= self::buildHysteria($user['uuid'], $item); } - if ($item['type'] === 'socks') { - $uri .= self::buildSocks($user['uuid'], $item); - } } return base64_encode($uri); } @@ -90,11 +87,8 @@ class General implements ProtocolInterface case 'tcp': if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') { $config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'http'); - $config['path'] = Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])); - $config['host'] = - data_get($protocol_settings, 'network_settings.headers.Host') - ? Arr::random(data_get($protocol_settings, 'network_settings.headers.Host', ['/']), ) - : null; + $config['path'] = \Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])); + $config['host'] = data_get($protocol_settings, 'network_settings.headers.Host') ? \Arr::random(data_get($protocol_settings, 'network_settings.headers.Host'), ['/']) : null; } break; case 'ws': @@ -255,11 +249,4 @@ class General implements ProtocolInterface return $uri; } - public static function buildSocks($password, $server) - { - $name = rawurlencode($server['name']); - $credentials = base64_encode("{$password}:{$password}"); - return "socks://{$credentials}@{$server['host']}:{$server['port']}#{$name}\r\n"; - } - } diff --git a/app/Protocols/QuantumultX.php b/app/Protocols/QuantumultX.php index b759a4a..25cf6a3 100644 --- a/app/Protocols/QuantumultX.php +++ b/app/Protocols/QuantumultX.php @@ -53,6 +53,7 @@ class QuantumultX implements ProtocolInterface 'udp-relay=true', "tag={$server['name']}" ]; + $config = array_filter($config); $uri = implode(',', $config); $uri .= "\r\n"; return $uri; diff --git a/app/Protocols/Shadowrocket.php b/app/Protocols/Shadowrocket.php index fe63e05..017b840 100644 --- a/app/Protocols/Shadowrocket.php +++ b/app/Protocols/Shadowrocket.php @@ -89,9 +89,9 @@ class Shadowrocket implements ProtocolInterface if ($protocol_settings['tls']) { $config['tls'] = 1; if (data_get($protocol_settings, 'tls_settings')) { - if (!!data_get($protocol_settings, 'tls_settings.allow_insecure')) + if (data_get($protocol_settings, 'tls_settings.allow_insecure') && !empty(data_get($protocol_settings, 'tls_settings.allow_insecure'))) $config['allowInsecure'] = (int) data_get($protocol_settings, 'tls_settings.allow_insecure'); - if (!!data_get($protocol_settings, 'tls_settings.server_name')) + if (data_get($protocol_settings, 'tls_settings.server_name') && !empty(data_get($protocol_settings, 'tls_settings.server_name'))) $config['peer'] = data_get($protocol_settings, 'tls_settings.server_name'); } } @@ -100,8 +100,8 @@ class Shadowrocket implements ProtocolInterface case 'tcp': if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') { $config['obfs'] = data_get($protocol_settings, 'network_settings.header.type'); - $config['path'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])); - $config['obfsParam'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['www.example.com'])); + $config['path'] = \Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])); + $config['obfsParam'] = \Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['www.example.com'])); } break; case 'ws': @@ -168,8 +168,8 @@ class Shadowrocket implements ProtocolInterface case 'tcp': if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') { $config['obfs'] = data_get($protocol_settings, 'network_settings.header.type'); - $config['path'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])); - $config['obfsParam'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['www.example.com'])); + $config['path'] = \Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])); + $config['obfsParam'] = \Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['www.example.com'])); } break; case 'ws': @@ -225,8 +225,6 @@ class Shadowrocket implements ProtocolInterface public static function buildHysteria($password, $server) { $protocol_settings = $server['protocol_settings']; - $uri = ''; // 初始化变量 - switch (data_get($protocol_settings, 'version')) { case 1: $params = [ diff --git a/app/Protocols/Shadowsocks.php b/app/Protocols/Shadowsocks.php index 3bebc48..dd76370 100644 --- a/app/Protocols/Shadowsocks.php +++ b/app/Protocols/Shadowsocks.php @@ -47,7 +47,7 @@ class Shadowsocks implements ProtocolInterface $subs['version'] = 1; $subs['bytes_used'] = $bytesUsed; $subs['bytes_remaining'] = $bytesRemaining; - $subs['servers'] = array_merge($subs['servers'], $configs); + $subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs); return json_encode($subs, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); } diff --git a/app/Protocols/SingBox.php b/app/Protocols/SingBox.php index 877c568..192ea36 100644 --- a/app/Protocols/SingBox.php +++ b/app/Protocols/SingBox.php @@ -11,7 +11,7 @@ class SingBox implements ProtocolInterface private $user; private $config; - public function __construct($user, $servers) + public function __construct($user, $servers, array $options = null) { $this->user = $user; $this->servers = $servers; @@ -84,14 +84,6 @@ class SingBox implements ProtocolInterface $tuicConfig = $this->buildTuic($this->user['uuid'], $item); $proxies[] = $tuicConfig; } - if ($item['type'] === 'socks') { - $socksConfig = $this->buildSocks($this->user['uuid'], $item); - $proxies[] = $socksConfig; - } - if ($item['type'] === 'http') { - $httpConfig = $this->buildHttp($this->user['uuid'], $item); - $proxies[] = $httpConfig; - } } foreach ($outbounds as &$outbound) { if (in_array($outbound['type'], ['urltest', 'selector'])) { @@ -369,58 +361,4 @@ class SingBox implements ProtocolInterface return $array; } - - protected function buildSocks($password, $server): array - { - $protocol_settings = data_get($server, 'protocol_settings', []); - $array = [ - 'type' => 'socks', - 'tag' => $server['name'], - 'server' => $server['host'], - 'server_port' => $server['port'], - 'version' => '5', // 默认使用 socks5 - 'username' => $password, - 'password' => $password, - ]; - - if (data_get($protocol_settings, 'udp_over_tcp')) { - $array['udp_over_tcp'] = true; - } - - return $array; - } - - protected function buildHttp($password, $server): array - { - $protocol_settings = data_get($server, 'protocol_settings', []); - $array = [ - 'type' => 'http', - 'tag' => $server['name'], - 'server' => $server['host'], - 'server_port' => $server['port'], - 'username' => $password, - 'password' => $password, - ]; - - if ($path = data_get($protocol_settings, 'path')) { - $array['path'] = $path; - } - - if ($headers = data_get($protocol_settings, 'headers')) { - $array['headers'] = $headers; - } - - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = [ - 'enabled' => true, - 'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false), - ]; - - if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) { - $array['tls']['server_name'] = $serverName; - } - } - - return $array; - } } diff --git a/app/Protocols/Stash.php b/app/Protocols/Stash.php index 4d2522c..927e95e 100644 --- a/app/Protocols/Stash.php +++ b/app/Protocols/Stash.php @@ -82,18 +82,6 @@ class Stash implements ProtocolInterface array_push($proxy, self::buildTrojan($user['uuid'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'tuic') { - array_push($proxy, self::buildTuic($user['uuid'], $item)); - array_push($proxies, $item['name']); - } - if ($item['type'] === 'socks') { - array_push($proxy, self::buildSocks5($user['uuid'], $item)); - array_push($proxies, $item['name']); - } - if ($item['type'] === 'http') { - array_push($proxy, self::buildHttp($user['uuid'], $item)); - array_push($proxies, $item['name']); - } } $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy); @@ -301,91 +289,12 @@ class Stash implements ProtocolInterface } - public static function buildTuic($password, $server) - { - $protocol_settings = data_get($server, 'protocol_settings', []); - $array = [ - 'name' => $server['name'], - 'type' => 'tuic', - 'server' => $server['host'], - 'port' => $server['port'], - 'uuid' => $password, - 'password' => $password, - 'congestion-controller' => data_get($protocol_settings, 'congestion_control', 'cubic'), - 'udp-relay-mode' => data_get($protocol_settings, 'udp_relay_mode', 'native'), - 'alpn' => data_get($protocol_settings, 'alpn', ['h3']), - 'reduce-rtt' => true, - 'fast-open' => true, - 'heartbeat-interval' => 10000, - 'request-timeout' => 8000, - 'max-udp-relay-packet-size' => 1500, - ]; - - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls.allow_insecure', false); - if ($serverName = data_get($protocol_settings, 'tls.server_name')) { - $array['sni'] = $serverName; - } - - return $array; - } - - public static function buildSocks5($password, $server) - { - $protocol_settings = $server['protocol_settings']; - $array = [ - 'name' => $server['name'], - 'type' => 'socks5', - 'server' => $server['host'], - 'port' => $server['port'], - 'username' => $password, - 'password' => $password, - 'udp' => true, - ]; - - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = true; - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false); - if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) { - $array['sni'] = $serverName; - } - } - - return $array; - } - - public static function buildHttp($password, $server) - { - $protocol_settings = $server['protocol_settings']; - $array = [ - 'name' => $server['name'], - 'type' => 'http', - 'server' => $server['host'], - 'port' => $server['port'], - 'username' => $password, - 'password' => $password, - ]; - - if (data_get($protocol_settings, 'tls')) { - $array['tls'] = true; - $array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false); - if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) { - $array['sni'] = $serverName; - } - } - - return $array; - } - private function isRegex($exp) { if (empty($exp)) { return false; } - try { - return preg_match($exp, '') !== false; - } catch (\Exception $e) { - return false; - } + return @preg_match($exp, '') !== false; } private function isMatch($exp, $str) diff --git a/app/Protocols/Surfboard.php b/app/Protocols/Surfboard.php index 525ce1c..5fe05d7 100644 --- a/app/Protocols/Surfboard.php +++ b/app/Protocols/Surfboard.php @@ -4,7 +4,6 @@ namespace App\Protocols; use App\Utils\Helper; use App\Contracts\ProtocolInterface; -use Illuminate\Support\Facades\File; class Surfboard implements ProtocolInterface { @@ -64,7 +63,7 @@ class Surfboard implements ProtocolInterface $defaultConfig = base_path() . '/resources/rules/default.surfboard.conf'; $customConfig = base_path() . '/resources/rules/custom.surfboard.conf'; - if (File::exists($customConfig)) { + if (\File::exists($customConfig)) { $config = file_get_contents("$customConfig"); } else { $config = file_get_contents("$defaultConfig"); @@ -128,9 +127,9 @@ class Surfboard implements ProtocolInterface array_push($config, 'tls=true'); if (data_get($protocol_settings, 'tls_settings')) { $tlsSettings = data_get($protocol_settings, 'tls_settings'); - if (!!data_get($tlsSettings, 'allowInsecure')) + if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false')); - if (!!data_get($tlsSettings, 'serverName')) + if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) array_push($config, "sni={$tlsSettings['serverName']}"); } } @@ -162,8 +161,8 @@ class Surfboard implements ProtocolInterface 'tfo=true', 'udp-relay=true' ]; - if (data_get($protocol_settings, 'allow_insecure')) { - array_push($config, !!data_get($protocol_settings, 'allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false'); + if (!empty($protocol_settings['allow_insecure'])) { + array_push($config, $protocol_settings['allow_insecure'] ? 'skip-cert-verify=true' : 'skip-cert-verify=false'); } $config = array_filter($config); $uri = implode(',', $config); diff --git a/app/Protocols/Surge.php b/app/Protocols/Surge.php index 1657436..4b8931f 100644 --- a/app/Protocols/Surge.php +++ b/app/Protocols/Surge.php @@ -164,7 +164,7 @@ class Surge implements ProtocolInterface 'udp-relay=true' ]; if (!empty($protocol_settings['allow_insecure'])) { - array_push($config, !!data_get($protocol_settings, 'allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false'); + array_push($config, $protocol_settings['allow_insecure'] ? 'skip-cert-verify=true' : 'skip-cert-verify=false'); } $config = array_filter($config); $uri = implode(',', $config); @@ -189,7 +189,7 @@ class Surge implements ProtocolInterface 'udp-relay=true' ]; if (data_get($protocol_settings, 'tls.allow_insecure')) { - $config[] = !!data_get($protocol_settings, 'tls.allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false'; + $config[] = data_get($protocol_settings, 'tls.allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false'; } $config = array_filter($config); $uri = implode(',', $config); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index b92a7dd..3049068 100755 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -8,15 +8,17 @@ use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { /** - * 策略映射 - * @var array + * The policy mappings for the application. + * + * @var array */ protected $policies = [ // 'App\Model' => 'App\Policies\ModelPolicy', ]; /** - * 注册任何认证/授权服务 + * Register any authentication / authorization services. + * * @return void */ public function boot() diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 299c8be..e2d1497 100755 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -8,14 +8,16 @@ use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { /** - * 事件监听器映射 - * @var array> + * The event listener mappings for the application. + * + * @var array */ protected $listen = [ ]; /** - * 注册任何事件 + * Register any events for your application. + * * @return void */ public function boot() diff --git a/app/Services/Auth/LoginService.php b/app/Services/Auth/LoginService.php deleted file mode 100644 index 8538d1e..0000000 --- a/app/Services/Auth/LoginService.php +++ /dev/null @@ -1,111 +0,0 @@ -= (int)admin_setting('password_limit_count', 5)) { - return [false, [429, __('There are too many password errors, please try again after :minute minutes.', [ - 'minute' => admin_setting('password_limit_expire', 60) - ])]]; - } - } - - // 查找用户 - $user = User::where('email', $email)->first(); - if (!$user) { - return [false, [400, __('Incorrect email or password')]]; - } - - // 验证密码 - if (!Helper::multiPasswordVerify( - $user->password_algo, - $user->password_salt, - $password, - $user->password) - ) { - // 增加密码错误计数 - if ((int)admin_setting('password_limit_enable', true)) { - $passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0); - Cache::put( - CacheKey::get('PASSWORD_ERROR_LIMIT', $email), - (int)$passwordErrorCount + 1, - 60 * (int)admin_setting('password_limit_expire', 60) - ); - } - return [false, [400, __('Incorrect email or password')]]; - } - - // 检查账户状态 - if ($user->banned) { - return [false, [400, __('Your account has been suspended')]]; - } - - // 更新最后登录时间 - $user->last_login_at = time(); - $user->save(); - - return [true, $user]; - } - - /** - * 处理密码重置 - * - * @param string $email 用户邮箱 - * @param string $emailCode 邮箱验证码 - * @param string $password 新密码 - * @return array [成功状态, 结果或错误信息] - */ - public function resetPassword(string $email, string $emailCode, string $password): array - { - // 检查重置请求限制 - $forgetRequestLimitKey = CacheKey::get('FORGET_REQUEST_LIMIT', $email); - $forgetRequestLimit = (int)Cache::get($forgetRequestLimitKey); - if ($forgetRequestLimit >= 3) { - return [false, [429, __('Reset failed, Please try again later')]]; - } - - // 验证邮箱验证码 - if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $email)) !== (string)$emailCode) { - Cache::put($forgetRequestLimitKey, $forgetRequestLimit ? $forgetRequestLimit + 1 : 1, 300); - return [false, [400, __('Incorrect email verification code')]]; - } - - // 查找用户 - $user = User::where('email', $email)->first(); - if (!$user) { - return [false, [400, __('This email is not registered in the system')]]; - } - - // 更新密码 - $user->password = password_hash($password, PASSWORD_DEFAULT); - $user->password_algo = NULL; - $user->password_salt = NULL; - - if (!$user->save()) { - return [false, [500, __('Reset failed')]]; - } - - // 清除邮箱验证码 - Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $email)); - - return [true, true]; - } -} \ No newline at end of file diff --git a/app/Services/Auth/MailLinkService.php b/app/Services/Auth/MailLinkService.php deleted file mode 100644 index 653df7a..0000000 --- a/app/Services/Auth/MailLinkService.php +++ /dev/null @@ -1,122 +0,0 @@ -first(); - if (!$user) { - return [true, true]; // 成功但用户不存在,保护用户隐私 - } - - $code = Helper::guid(); - $key = CacheKey::get('TEMP_TOKEN', $code); - Cache::put($key, $user->id, 300); - Cache::put(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $email), time(), 60); - - $redirectUrl = '/#/login?verify=' . $code . '&redirect=' . ($redirect ? $redirect : 'dashboard'); - if (admin_setting('app_url')) { - $link = admin_setting('app_url') . $redirectUrl; - } else { - $link = url($redirectUrl); - } - - $this->sendMailLinkEmail($user, $link); - - return [true, $link]; - } - - /** - * 发送邮件链接登录邮件 - * - * @param User $user 用户对象 - * @param string $link 登录链接 - * @return void - */ - private function sendMailLinkEmail(User $user, string $link): void - { - SendEmailJob::dispatch([ - 'email' => $user->email, - 'subject' => __('Login to :name', [ - 'name' => admin_setting('app_name', 'XBoard') - ]), - 'template_name' => 'login', - 'template_value' => [ - 'name' => admin_setting('app_name', 'XBoard'), - 'link' => $link, - 'url' => admin_setting('app_url') - ] - ]); - } - - /** - * 获取快速登录URL - * - * @param User $user 用户对象 - * @param string|null $redirect 重定向地址 - * @return string 登录URL - */ - public function getQuickLoginUrl(User $user, ?string $redirect = null): string - { - $code = Helper::guid(); - $key = CacheKey::get('TEMP_TOKEN', $code); - Cache::put($key, $user->id, 60); - - $redirectUrl = '/#/login?verify=' . $code . '&redirect=' . ($redirect ? $redirect : 'dashboard'); - - if (admin_setting('app_url')) { - return admin_setting('app_url') . $redirectUrl; - } else { - return url($redirectUrl); - } - } - - /** - * 处理Token登录 - * - * @param string $token 登录令牌 - * @return int|null 用户ID或null - */ - public function handleTokenLogin(string $token): ?int - { - $key = CacheKey::get('TEMP_TOKEN', $token); - $userId = Cache::get($key); - - if (!$userId) { - return null; - } - - $user = User::find($userId); - - if (!$user || $user->banned) { - return null; - } - - Cache::forget($key); - - return $userId; - } -} \ No newline at end of file diff --git a/app/Services/Auth/RegisterService.php b/app/Services/Auth/RegisterService.php deleted file mode 100644 index 9956b49..0000000 --- a/app/Services/Auth/RegisterService.php +++ /dev/null @@ -1,209 +0,0 @@ -ip())) ?? 0; - if ((int)$registerCountByIP >= (int)admin_setting('register_limit_count', 3)) { - return [false, [429, __('Register frequently, please try again after :minute minute', [ - 'minute' => admin_setting('register_limit_expire', 60) - ])]]; - } - } - - // 检查验证码 - if ((int)admin_setting('recaptcha_enable', 0)) { - $recaptcha = new ReCaptcha(admin_setting('recaptcha_key')); - $recaptchaResp = $recaptcha->verify($request->input('recaptcha_data')); - if (!$recaptchaResp->isSuccess()) { - return [false, [400, __('Invalid code is incorrect')]]; - } - } - - // 检查邮箱白名单 - if ((int)admin_setting('email_whitelist_enable', 0)) { - if (!Helper::emailSuffixVerify( - $request->input('email'), - admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)) - ) { - return [false, [400, __('Email suffix is not in the Whitelist')]]; - } - } - - // 检查Gmail限制 - if ((int)admin_setting('email_gmail_limit_enable', 0)) { - $prefix = explode('@', $request->input('email'))[0]; - if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) { - return [false, [400, __('Gmail alias is not supported')]]; - } - } - - // 检查是否关闭注册 - if ((int)admin_setting('stop_register', 0)) { - return [false, [400, __('Registration has closed')]]; - } - - // 检查邀请码要求 - if ((int)admin_setting('invite_force', 0)) { - if (empty($request->input('invite_code'))) { - return [false, [422, __('You must use the invitation code to register')]]; - } - } - - // 检查邮箱验证 - if ((int)admin_setting('email_verify', 0)) { - if (empty($request->input('email_code'))) { - return [false, [422, __('Email verification code cannot be empty')]]; - } - if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) { - return [false, [400, __('Incorrect email verification code')]]; - } - } - - // 检查邮箱是否存在 - $email = $request->input('email'); - $exist = User::where('email', $email)->first(); - if ($exist) { - return [false, [400201, __('Email already exists')]]; - } - - return [true, null]; - } - - /** - * 处理邀请码 - * - * @param User $user 用户对象 - * @param string|null $inviteCode 邀请码 - * @return array [是否成功, 错误消息] - */ - public function handleInviteCode(User $user, ?string $inviteCode): array - { - if (!$inviteCode) { - return [true, null]; - } - - $inviteCodeModel = InviteCode::where('code', $inviteCode) - ->where('status', 0) - ->first(); - - if (!$inviteCodeModel) { - if ((int)admin_setting('invite_force', 0)) { - return [false, [400, __('Invalid invitation code')]]; - } - return [true, null]; - } - - $user->invite_user_id = $inviteCodeModel->user_id ? $inviteCodeModel->user_id : null; - - if (!(int)admin_setting('invite_never_expire', 0)) { - $inviteCodeModel->status = true; - $inviteCodeModel->save(); - } - - return [true, null]; - } - - /** - * 处理试用计划 - * - * @param User $user 用户对象 - * @return void - */ - public function handleTryOut(User $user): void - { - if ((int)admin_setting('try_out_plan_id', 0)) { - $plan = Plan::find(admin_setting('try_out_plan_id')); - if ($plan) { - $user->transfer_enable = $plan->transfer_enable * 1073741824; - $user->plan_id = $plan->id; - $user->group_id = $plan->group_id; - $user->expired_at = time() + (admin_setting('try_out_hour', 1) * 3600); - $user->speed_limit = $plan->speed_limit; - } - } - } - - /** - * 注册用户 - * - * @param Request $request 请求对象 - * @return array [成功状态, 用户对象或错误信息] - */ - public function register(Request $request): array - { - // 验证注册数据 - [$valid, $error] = $this->validateRegister($request); - if (!$valid) { - return [false, $error]; - } - - $email = $request->input('email'); - $password = $request->input('password'); - - // 创建用户 - $user = new User(); - $user->email = $email; - $user->password = password_hash($password, PASSWORD_DEFAULT); - $user->uuid = Helper::guid(true); - $user->token = Helper::guid(); - $user->remind_expire = admin_setting('default_remind_expire', 1); - $user->remind_traffic = admin_setting('default_remind_traffic', 1); - - // 处理邀请码 - [$inviteSuccess, $inviteError] = $this->handleInviteCode($user, $request->input('invite_code')); - if (!$inviteSuccess) { - return [false, $inviteError]; - } - - // 处理试用计划 - $this->handleTryOut($user); - - // 保存用户 - if (!$user->save()) { - return [false, [500, __('Register failed')]]; - } - - // 清除邮箱验证码 - if ((int)admin_setting('email_verify', 0)) { - Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $email)); - } - - // 更新最近登录时间 - $user->last_login_at = time(); - $user->save(); - - // 更新IP注册计数 - if ((int)admin_setting('register_limit_by_ip_enable', 0)) { - $registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0; - Cache::put( - CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip()), - (int)$registerCountByIP + 1, - (int)admin_setting('register_limit_expire', 60) * 60 - ); - } - - return [true, $user]; - } -} \ No newline at end of file diff --git a/app/Services/AuthService.php b/app/Services/AuthService.php index f82fdf9..9b73072 100644 --- a/app/Services/AuthService.php +++ b/app/Services/AuthService.php @@ -40,13 +40,7 @@ class AuthService return $this->user->tokens()->get()->toArray(); } - public function removeSession(string $sessionId): bool - { - $this->user->tokens()->where('id', $sessionId)->delete(); - return true; - } - - public function removeAllSessions(): bool + public function removeSession(): bool { $this->user->tokens()->delete(); return true; @@ -60,26 +54,4 @@ class AuthService return $accessToken?->tokenable; } - - /** - * 解密认证数据 - * - * @param string $authorization - * @return array|null 用户数据或null - */ - public static function decryptAuthData(string $authorization): ?array - { - $user = self::findUserByBearerToken($authorization); - - if (!$user) { - return null; - } - - return [ - 'id' => $user->id, - 'email' => $user->email, - 'is_admin' => (bool)$user->is_admin, - 'is_staff' => (bool)$user->is_staff - ]; - } } diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 329ae67..73848d1 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -127,30 +127,30 @@ class OrderService $inviter = User::find($user->invite_user_id); if (!$inviter) return; - $commissionType = (int) $inviter->commission_type; - if ($commissionType === User::COMMISSION_TYPE_SYSTEM) { - $commissionType = (bool) admin_setting('commission_first_time_enable', true) ? User::COMMISSION_TYPE_ONETIME : User::COMMISSION_TYPE_PERIOD; - } $isCommission = false; - switch ($commissionType) { - case User::COMMISSION_TYPE_PERIOD: + switch ((int) $inviter->commission_type) { + case 0: + $commissionFirstTime = (int) admin_setting('commission_first_time_enable', 1); + $isCommission = (!$commissionFirstTime || ($commissionFirstTime && !$this->haveValidOrder($user))); + break; + case 1: $isCommission = true; break; - case User::COMMISSION_TYPE_ONETIME: + case 2: $isCommission = !$this->haveValidOrder($user); break; } if (!$isCommission) return; - if ($inviter->commission_rate) { + if ($inviter && $inviter->commission_rate) { $order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100); } else { $order->commission_balance = $order->total_amount * (admin_setting('invite_commission', 10) / 100); } } - private function haveValidOrder(User $user): Order|null + private function haveValidOrder(User $user) { return Order::where('user_id', $user->id) ->whereNotIn('status', [0, 2]) diff --git a/app/Services/PlanService.php b/app/Services/PlanService.php index e67363d..52549cf 100644 --- a/app/Services/PlanService.php +++ b/app/Services/PlanService.php @@ -74,9 +74,10 @@ class PlanService // 转换周期格式为新版格式 $periodKey = self::getPeriodKey($period); - $price = $this->plan->prices[$periodKey] ?? null; - if ($price === null) { + + // 检查价格时使用新版格式 + if (!isset($this->plan->prices[$periodKey]) || $this->plan->prices[$periodKey] === NULL) { throw new ApiException(__('This payment period cannot be purchased, please choose another period')); } diff --git a/app/Services/Plugin/HookManager.php b/app/Services/Plugin/HookManager.php index 6e3ef51..4147962 100644 --- a/app/Services/Plugin/HookManager.php +++ b/app/Services/Plugin/HookManager.php @@ -45,16 +45,9 @@ class HookManager * @param mixed ...$args 其他参数 * @return mixed */ - public static function filter(string $hook, mixed $value, mixed ...$args): mixed + public static function filter(string $hook, mixed $value): mixed { - if (!self::hasHook($hook)) { - return $value; - } - - /** @phpstan-ignore-next-line */ - $result = Eventy::filter($hook, $value, ...$args); - - return $result; + return Eventy::filter($hook, $value); } /** @@ -95,10 +88,4 @@ class HookManager Eventy::removeAction($hook, $callback); Eventy::removeFilter($hook, $callback); } - - private static function hasHook(string $hook): bool - { - // Implementation of hasHook method - return true; // Placeholder return, actual implementation needed - } } \ No newline at end of file diff --git a/app/Services/ServerService.php b/app/Services/ServerService.php index 40d01fa..b2ff59d 100644 --- a/app/Services/ServerService.php +++ b/app/Services/ServerService.php @@ -15,18 +15,14 @@ class ServerService * 获取所有服务器列表 * @return Collection */ - public static function getAllServers(): Collection + public static function getAllServers() { - $query = Server::orderBy('sort', 'ASC'); - - return $query->get()->append([ - 'last_check_at', - 'last_push_at', - 'online', - 'is_online', - 'available_status', - 'cache_key' - ]); + return Server::orderBy('sort', 'ASC') + ->get() + ->transform(function (Server $server) { + $server->loadServerStatus(); + return $server; + }); } /** @@ -36,25 +32,28 @@ class ServerService */ public static function getAvailableServers(User $user): array { - $servers = Server::whereJsonContains('group_ids', (string) $user->group_id) + return Server::whereJsonContains('group_ids', (string) $user->group_id) ->where('show', true) ->orderBy('sort', 'ASC') ->get() - ->append(['last_check_at', 'last_push_at', 'online', 'is_online', 'available_status', 'cache_key', 'server_key']); + ->transform(function (Server $server) use ($user) { + $server->loadParentCreatedAt(); + $server->handlePortAllocation(); + $server->loadServerStatus(); + if ($server->type === 'shadowsocks') { + $server->server_key = Helper::getServerKey($server->created_at, 16); + } + $server->generateShadowsocksPassword($user); - $servers = collect($servers)->map(function ($server) use ($user) { - // 判断动态端口 - if (str_contains($server?->port, '-')) { - $server->port = (int) Helper::randomPort($server->port); - $server->ports = $server->port; - } - $server->password = $server->generateShadowsocksPassword($user); - return $server; - })->toArray(); - - return $servers; + return $server; + }) + ->toArray(); } + /** + * 加 + */ + /** * 根据权限组获取可用的用户列表 * @param array $groupIds diff --git a/app/Services/StatisticalService.php b/app/Services/StatisticalService.php index e57d6ae..8d38c63 100644 --- a/app/Services/StatisticalService.php +++ b/app/Services/StatisticalService.php @@ -122,7 +122,7 @@ class StatisticalService $key = "{$rate}_{$uid}"; $stats[$key] = $stats[$key] ?? [ 'record_at' => $this->startAt, - 'server_rate' => number_format((float) $rate, 2, '.', ''), + 'server_rate' => number_format($rate, 2, '.', ''), 'u' => 0, 'd' => 0, 'user_id' => intval($userId), @@ -156,40 +156,27 @@ class StatisticalService /** - * Retrieve server statistics from Redis cache. - * - * @return array + * 获取缓存中的服务器爆表 */ - public function getStatServer(): array + public function getStatServer() { - /** @var array $stats */ $stats = []; $statsServer = $this->redis->zrange($this->statServerKey, 0, -1, true); - foreach ($statsServer as $member => $value) { - $parts = explode('_', $member); - if (count($parts) !== 3) { - continue; // Skip malformed members - } - [$serverType, $serverId, $type] = $parts; - - if (!in_array($type, ['u', 'd'], true)) { - continue; // Skip invalid types - } - + list($serverType, $serverId, $type) = explode('_', $member); $key = "{$serverType}_{$serverId}"; if (!isset($stats[$key])) { $stats[$key] = [ - 'server_id' => (int) $serverId, + 'server_id' => intval($serverId), 'server_type' => $serverType, - 'u' => 0.0, - 'd' => 0.0, + 'u' => 0, + 'd' => 0, ]; } - $stats[$key][$type] += (float) $value; + $stats[$key][$type] += $value; } - return array_values($stats); + } /** @@ -294,22 +281,25 @@ class StatisticalService ->where('record_type', 'd'); } ) - ->withSum('stats as u', 'u') // 预加载 u 的总和 - ->withSum('stats as d', 'd') // 预加载 d 的总和 ->get() - ->map(function ($item) { - return [ - 'server_name' => optional($item->parent)->name ?? $item->name, - 'server_id' => $item->id, - 'server_type' => $item->type, - 'u' => (int) $item->u, - 'd' => (int) $item->d, - 'total' => (int) $item->u + (int) $item->d, - ]; + ->each(function ($item) { + $item->u = (int) $item->stats()->sum('u'); + $item->d = (int) $item->stats()->sum('d'); + $item->total = (int) $item->u + $item->d; + $item->server_name = optional($item->parent)->name ?? $item->name; + $item->server_id = $item->id; + $item->server_type = $item->type; }) ->sortByDesc('total') - ->values() - ->toArray(); + ->select([ + 'server_name', + 'server_id', + 'server_type', + 'u', + 'd', + 'total' + ]) + ->values()->toArray(); return $statistics; } diff --git a/app/Services/UserOnlineService.php b/app/Services/UserOnlineService.php index 8ddc556..5445fa0 100644 --- a/app/Services/UserOnlineService.php +++ b/app/Services/UserOnlineService.php @@ -156,17 +156,13 @@ class UserOnlineService } /** - * Calculate the number of devices based on IPs array and device limit mode. - * - * @param array $ipsArray Array containing IP data - * @return int Number of devices + * 计算设备数量 */ private function calculateDeviceCount(array $ipsArray): int { - $mode = (int) admin_setting('device_limit_mode', 0); - - return match ($mode) { - // Loose mode: Count unique IPs (ignoring suffixes after '_') + // 设备限制模式 + return match ((int) admin_setting('device_limit_mode', 0)) { + // 宽松模式 1 => collect($ipsArray) ->filter(fn(mixed $data): bool => is_array($data) && isset($data['aliveips'])) ->flatMap( @@ -177,12 +173,9 @@ class UserOnlineService ) ->unique() ->count(), - // Strict mode: Sum total number of alive IPs 0 => collect($ipsArray) ->filter(fn(mixed $data): bool => is_array($data) && isset($data['aliveips'])) - ->sum(fn(array $data): int => count($data['aliveips'])), - // Handle invalid modes - default => throw new \InvalidArgumentException("Invalid device limit mode: $mode"), + ->sum(fn(array $data): int => count($data['aliveips'])) }; } } \ No newline at end of file diff --git a/app/Services/UserService.php b/app/Services/UserService.php index a085630..a733bfc 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -12,7 +12,7 @@ use App\Services\Plugin\HookManager; class UserService { - private function calcResetDayByMonthFirstDay(): int + private function calcResetDayByMonthFirstDay() { $today = date('d'); $lastDay = date('d', strtotime('last day of +0 months')); @@ -51,34 +51,55 @@ class UserService return (int) (($nextYear - time()) / 86400); } - public function getResetDay(User $user): ?int + public function getResetDay(User $user) { - // 前置条件检查 - if ($user->expired_at <= time() || $user->expired_at === null) { - return null; + if (!isset($user->plan)) { + $user->plan = Plan::find($user->plan_id); } - - // 获取重置方式逻辑统一 - $resetMethod = $user->plan->reset_traffic_method === Plan::RESET_TRAFFIC_FOLLOW_SYSTEM - ? (int)admin_setting('reset_traffic_method', 0) - : $user->plan->reset_traffic_method; - - // 验证重置方式有效性 - if (!in_array($resetMethod, array_keys(Plan::getResetTrafficMethods()), true)) { + if ($user->expired_at <= time() || $user->expired_at === NULL) return null; + // if reset method is not reset + if ($user->plan->reset_traffic_method === 2) + return null; + switch (true) { + case ($user->plan->reset_traffic_method === NULL): { + $resetTrafficMethod = admin_setting('reset_traffic_method', 0); + switch ((int) $resetTrafficMethod) { + // month first day + case 0: + return $this->calcResetDayByMonthFirstDay(); + // expire day + case 1: + return $this->calcResetDayByExpireDay($user->expired_at); + // no action + case 2: + return null; + // year first day + case 3: + return $this->calcResetDayByYearFirstDay(); + // year expire day + case 4: + return $this->calcResetDayByYearExpiredAt($user->expired_at); + } + break; + } + case ($user->plan->reset_traffic_method === 0): { + return $this->calcResetDayByMonthFirstDay(); + } + case ($user->plan->reset_traffic_method === 1): { + return $this->calcResetDayByExpireDay($user->expired_at); + } + case ($user->plan->reset_traffic_method === 2): { + return null; + } + case ($user->plan->reset_traffic_method === 3): { + return $this->calcResetDayByYearFirstDay(); + } + case ($user->plan->reset_traffic_method === 4): { + return $this->calcResetDayByYearExpiredAt($user->expired_at); + } } - - // 方法映射表 - $methodHandlers = [ - Plan::RESET_TRAFFIC_FIRST_DAY_MONTH => fn() => $this->calcResetDayByMonthFirstDay(), - Plan::RESET_TRAFFIC_MONTHLY => fn() => $this->calcResetDayByExpireDay($user->expired_at), - Plan::RESET_TRAFFIC_FIRST_DAY_YEAR => fn() => $this->calcResetDayByYearFirstDay(), - Plan::RESET_TRAFFIC_YEARLY => fn() => $this->calcResetDayByYearExpiredAt($user->expired_at), - ]; - - $handler = $methodHandlers[$resetMethod] ?? null; - - return $handler ? $handler() : null; + return null; } public function isAvailable(User $user) diff --git a/app/Support/Setting.php b/app/Support/Setting.php index 6b7165b..d112ef9 100644 --- a/app/Support/Setting.php +++ b/app/Support/Setting.php @@ -32,11 +32,10 @@ class Setting /** * 设置配置信息. * - * @param string $key - * @param mixed $value - * @return bool 设置是否成功 + * @param array $data + * @return $this */ - public function set(string $key, $value = null): bool + public function set($key, $value = null): bool { if (is_array($value)) { $value = json_encode($value); @@ -51,12 +50,12 @@ class Setting /** * 保存配置到数据库. * - * @param array $settings 要保存的设置数组 - * @return bool 保存是否成功 + * @param array $data + * @return $this */ - public function save(array $settings): bool + public function save(array $data = []): bool { - foreach ($settings as $key => $value) { + foreach ($data as $key => $value) { $this->set($key, $value); } @@ -100,22 +99,4 @@ class Setting { return $this->fromDatabase(); } - - /** - * 更新单个设置项 - * - * @param string $key 设置键名 - * @param mixed $value 设置值 - * @return bool 更新是否成功 - */ - public function update(string $key, $value): bool - { - if (is_array($value)) { - $value = json_encode($value); - } - $key = strtolower($key); - SettingModel::updateOrCreate(['name' => $key], ['value' => $value]); - $this->cache->forget(self::CACHE_KEY); - return true; - } } diff --git a/app/Utils/CacheKey.php b/app/Utils/CacheKey.php index 384f012..132186e 100644 --- a/app/Utils/CacheKey.php +++ b/app/Utils/CacheKey.php @@ -31,22 +31,6 @@ class CacheKey 'MULTI_SERVER_TUIC_ONLINE_USER' => 'TUIC节点多服务器在线用户', 'SERVER_TUIC_LAST_CHECK_AT' => 'TUIC节点最后检查时间', 'SERVER_TUIC_LAST_PUSH_AT' => 'TUIC节点最后推送时间', - 'SERVER_SOCKS_ONLINE_USER' => 'socks节点在线用户', - 'MULTI_SERVER_SOCKS_ONLINE_USER' => 'socks节点多服务器在线用户', - 'SERVER_SOCKS_LAST_CHECK_AT' => 'socks节点最后检查时间', - 'SERVER_SOCKS_LAST_PUSH_AT' => 'socks节点最后推送时间', - 'SERVER_NAIVE_ONLINE_USER' => 'naive节点在线用户', - 'MULTI_SERVER_NAIVE_ONLINE_USER' => 'naive节点多服务器在线用户', - 'SERVER_NAIVE_LAST_CHECK_AT' => 'naive节点最后检查时间', - 'SERVER_NAIVE_LAST_PUSH_AT' => 'naive节点最后推送时间', - 'SERVER_HTTP_ONLINE_USER' => 'http节点在线用户', - 'MULTI_SERVER_HTTP_ONLINE_USER' => 'http节点多服务器在线用户', - 'SERVER_HTTP_LAST_CHECK_AT' => 'http节点最后检查时间', - 'SERVER_HTTP_LAST_PUSH_AT' => 'http节点最后推送时间', - 'SERVER_MIERU_ONLINE_USER' => 'mieru节点在线用户', - 'MULTI_SERVER_MIERU_ONLINE_USER' => 'mieru节点多服务器在线用户', - 'SERVER_MIERU_LAST_CHECK_AT' => 'mieru节点最后检查时间', - 'SERVER_MIERU_LAST_PUSH_AT' => 'mieru节点最后推送时间', 'TEMP_TOKEN' => '临时令牌', 'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒', 'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间', diff --git a/app/Utils/Helper.php b/app/Utils/Helper.php index d153875..f974af8 100644 --- a/app/Utils/Helper.php +++ b/app/Utils/Helper.php @@ -134,7 +134,7 @@ class Helper public static function randomPort($range): int { $portRange = explode('-', $range); - return random_int((int)$portRange[0], (int)$portRange[1]); + return random_int($portRange[0], $portRange[1]); } public static function base64EncodeUrlSafe($data) diff --git a/composer.json b/composer.json index 6b9f00c..eca5f28 100755 --- a/composer.json +++ b/composer.json @@ -37,10 +37,8 @@ "require-dev": { "barryvdh/laravel-debugbar": "^3.9", "fakerphp/faker": "^1.9.1", - "larastan/larastan": "^3.0", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.0", - "nunomaduro/larastan": "^3.1", "orangehill/iseed": "^3.0", "phpunit/phpunit": "^10.5", "spatie/laravel-ignition": "^2.4" diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index d14e701..0000000 --- a/phpstan.neon +++ /dev/null @@ -1,17 +0,0 @@ -includes: - - vendor/larastan/larastan/extension.neon - - vendor/nesbot/carbon/extension.neon - -parameters: - - paths: - - app/ - - # Level 10 is the highest level - level: 5 - - ignoreErrors: - - '#Negated boolean expression is always false\.#' - -# excludePaths: -# - ./*/*/FileToBeExcluded.php \ No newline at end of file