Selaa lähdekoodia

Merge pull request #2348 from SSPanel-UIM/dev

Dev 20240130
M1Screw 1 vuosi sitten
vanhempi
sitoutus
6136289093
39 muutettua tiedostoa jossa 262 lisäystä ja 153 poistoa
  1. 3 3
      .github/workflows/lint.yml
  2. 2 2
      .github/workflows/minify.yml
  3. 1 1
      .github/workflows/sonarcloud.yml
  4. 1 1
      .github/workflows/stale.yml
  5. 2 2
      .github/workflows/unit.yaml
  6. 12 12
      composer.lock
  7. 1 1
      phpunit.xml
  8. 4 3
      resources/views/tabler/admin/index.tpl
  9. 6 6
      resources/views/tabler/user/header.tpl
  10. 2 2
      resources/views/tabler/user/index.tpl
  11. 0 3
      src/Controllers/PasswordController.php
  12. 2 1
      src/Controllers/User/InfoController.php
  13. 3 2
      src/Controllers/User/RateController.php
  14. 2 1
      src/Controllers/User/ServerController.php
  15. 0 2
      src/Controllers/User/TicketController.php
  16. 1 1
      src/Controllers/UserController.php
  17. 10 0
      src/Models/InviteCode.php
  18. 1 2
      src/Models/SubscribeLog.php
  19. 1 63
      src/Models/User.php
  20. 1 2
      src/Services/Bot/Telegram/Callback.php
  21. 1 1
      src/Services/Bot/Telegram/Commands/CheckinCommand.php
  22. 0 2
      src/Services/Bot/Telegram/Message.php
  23. 0 25
      src/Services/RateLimit.php
  24. 7 2
      src/Services/Subscribe.php
  25. 1 1
      src/Services/Subscribe/Clash.php
  26. 1 1
      src/Services/Subscribe/SIP002.php
  27. 1 1
      src/Services/Subscribe/SIP008.php
  28. 1 1
      src/Services/Subscribe/SS.php
  29. 1 1
      src/Services/Subscribe/SingBox.php
  30. 1 1
      src/Services/Subscribe/Trojan.php
  31. 1 1
      src/Services/Subscribe/V2Ray.php
  32. 1 1
      src/Services/Subscribe/V2RayJson.php
  33. 56 0
      tests/App/Services/CacheTest.php
  34. 64 0
      tests/App/Services/DBTest.php
  35. 71 5
      tests/App/Utils/ToolsTest.php
  36. 0 0
      tests/testDir/emptyDir/.gitkeep
  37. 0 0
      tests/testDir/file1
  38. 0 0
      tests/testDir/file2
  39. 0 0
      tests/testDir/file3

+ 3 - 3
.github/workflows/lint.yml

@@ -10,10 +10,10 @@ jobs:
     outputs:
       php: ${{ steps.filter.outputs.php }}
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         fetch-depth: 0
-    - uses: dorny/paths-filter@v2
+    - uses: dorny/paths-filter@v3
       id: filter
       with:
         filters: |
@@ -24,7 +24,7 @@ jobs:
     if: ${{ needs.php-file-changed.outputs.php == 'true' }}
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 0
       - uses: shivammathur/setup-php@v2

+ 2 - 2
.github/workflows/minify.yml

@@ -11,7 +11,7 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
         with:
           token: ${{ secrets.PAT }}
       - name: Auto Minify Tabler Theme CSS file
@@ -31,7 +31,7 @@ jobs:
         with:
           directory: 'public/assets/js'
       - name: Auto committing minified files
-        uses: stefanzweifel/git-auto-commit-action@v4
+        uses: stefanzweifel/git-auto-commit-action@v5
         with:
           repository: 'public'
           commit_message: "Github Action: Auto Minified Theme CSS/JS Files"

+ 1 - 1
.github/workflows/sonarcloud.yml

@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
       - name: Setup PHP with Xdebug

+ 1 - 1
.github/workflows/stale.yml

@@ -13,7 +13,7 @@ jobs:
       pull-requests: write
 
     steps:
-    - uses: actions/stale@v5
+    - uses: actions/stale@v9
       with:
         repo-token: ${{ secrets.GITHUB_TOKEN }}
         stale-issue-message: 'This issue has had no activity for over 60 days, hence marked as "staled-issue". This issue will be closed in 7 days.'

+ 2 - 2
.github/workflows/unit.yaml

@@ -11,7 +11,7 @@ jobs:
   unit-test-php82:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: shivammathur/setup-php@v2
       with:
         php-version: 8.2
@@ -22,7 +22,7 @@ jobs:
   unit-test-php83:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - uses: shivammathur/setup-php@v2
         with:
           php-version: 8.3

+ 12 - 12
composer.lock

@@ -123,16 +123,16 @@
         },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.297.2",
+            "version": "3.297.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "bbf516a4a88f829f92cc396628be705966880dbd"
+                "reference": "6d108ecd1825f066d47a995cb2bc211d97ef0d7a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bbf516a4a88f829f92cc396628be705966880dbd",
-                "reference": "bbf516a4a88f829f92cc396628be705966880dbd",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d108ecd1825f066d47a995cb2bc211d97ef0d7a",
+                "reference": "6d108ecd1825f066d47a995cb2bc211d97ef0d7a",
                 "shasum": ""
             },
             "require": {
@@ -212,9 +212,9 @@
             "support": {
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.297.2"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.297.3"
             },
-            "time": "2024-01-26T19:09:20+00:00"
+            "time": "2024-01-29T19:12:30+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
@@ -4326,16 +4326,16 @@
         },
         {
             "name": "sentry/sentry",
-            "version": "4.4.0",
+            "version": "4.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/getsentry/sentry-php.git",
-                "reference": "95a428a59ebddf786a27f09d19ec395a32f62082"
+                "reference": "a6e06f0b7a17e7f68e11297427da76bfe01a3ca3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/95a428a59ebddf786a27f09d19ec395a32f62082",
-                "reference": "95a428a59ebddf786a27f09d19ec395a32f62082",
+                "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a6e06f0b7a17e7f68e11297427da76bfe01a3ca3",
+                "reference": "a6e06f0b7a17e7f68e11297427da76bfe01a3ca3",
                 "shasum": ""
             },
             "require": {
@@ -4399,7 +4399,7 @@
             ],
             "support": {
                 "issues": "https://github.com/getsentry/sentry-php/issues",
-                "source": "https://github.com/getsentry/sentry-php/tree/4.4.0"
+                "source": "https://github.com/getsentry/sentry-php/tree/4.5.0"
             },
             "funding": [
                 {
@@ -4411,7 +4411,7 @@
                     "type": "custom"
                 }
             ],
-            "time": "2024-01-23T09:49:55+00:00"
+            "time": "2024-01-29T16:16:10+00:00"
         },
         {
             "name": "slim/http",

+ 1 - 1
phpunit.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
          bootstrap="vendor/autoload.php"
          cacheDirectory=".phpunit.cache"
          executionOrder="depends,defects"

+ 4 - 3
resources/views/tabler/admin/index.tpl

@@ -172,7 +172,7 @@
                 grid: {
                     strokeDashArray: 3,
                 },
-                colors: ["#0780cf", "#009db2", "#fa6d1d"],
+                colors: [tabler.getColor("azure"), tabler.getColor("cyan"), tabler.getColor("orange")],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -212,7 +212,7 @@
                 grid: {
                     strokeDashArray: 2,
                 },
-                colors: ["#71ae46", "#d85d2a"],
+                colors: [tabler.getColor("lime"), tabler.getColor("red")],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -252,7 +252,7 @@
                 grid: {
                     strokeDashArray: 4,
                 },
-                colors: ["#f59311", "#6ca30f"],
+                colors: [tabler.getColor("yellow"), tabler.getColor("lime")],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -292,6 +292,7 @@
                 grid: {
                     strokeDashArray: 3,
                 },
+                colors: [tabler.getColor("green"), tabler.getColor("lime"), tabler.getColor("yellow")],
                 legend: {
                     show: true,
                     position: 'bottom',

+ 6 - 6
resources/views/tabler/user/header.tpl

@@ -109,12 +109,6 @@
                                                 订阅日志
                                             </a>
                                         {/if}
-                                        {if $public_setting['traffic_log']}
-                                            <a class="dropdown-item" href="/user/traffic">
-                                                <i class="ti ti-traffic-lights"></i>&nbsp;
-                                                流量日志
-                                            </a>
-                                        {/if}
                                         <a class="dropdown-item" href="/user/invite">
                                             <i class="ti ti-friends"></i>&nbsp;
                                             邀请
@@ -142,6 +136,12 @@
                                     <i class="ti ti-chart-bar"></i>&nbsp;
                                     流量倍率
                                 </a>
+                                {if $public_setting['traffic_log']}
+                                    <a class="dropdown-item" href="/user/traffic">
+                                        <i class="ti ti-traffic-lights"></i>&nbsp;
+                                        流量日志
+                                    </a>
+                                {/if}
                             </div>
                         </li>
                         <li class="nav-item dropdown">

+ 2 - 2
resources/views/tabler/user/index.tpl

@@ -76,7 +76,7 @@
                                 <div class="card-body">
                                     <div class="row align-items-center">
                                         <div class="col-auto">
-                                            <span class="bg-twitter text-white avatar">
+                                            <span class="bg-azure text-white avatar">
                                                 <i class="ti ti-devices-pc icon"></i>
                                             </span>
                                         </div>
@@ -101,7 +101,7 @@
                                 <div class="card-body">
                                     <div class="row align-items-center">
                                         <div class="col-auto">
-                                            <span class="bg-facebook text-white avatar">
+                                            <span class="bg-indigo text-white avatar">
                                                 <i class="ti ti-rocket icon"></i>
                                             </span>
                                         </div>

+ 0 - 3
src/Controllers/PasswordController.php

@@ -40,9 +40,6 @@ final class PasswordController extends BaseController
         );
     }
 
-    /**
-     * @throws RedisException
-     */
     public function handleReset(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         if (Config::obtain('enable_reset_password_captcha')) {

+ 2 - 1
src/Controllers/User/InfoController.php

@@ -6,6 +6,7 @@ namespace App\Controllers\User;
 
 use App\Controllers\BaseController;
 use App\Models\Config;
+use App\Models\InviteCode;
 use App\Models\User;
 use App\Services\Auth;
 use App\Services\Cache;
@@ -240,7 +241,7 @@ final class InfoController extends BaseController
     {
         $user = $this->user;
         $user->clearInviteCodes();
-        $code = $this->user->addInviteCode();
+        $code = (new InviteCode())->add($user->id);
 
         return $response->withJson([
             'ret' => 1,

+ 3 - 2
src/Controllers/User/RateController.php

@@ -6,6 +6,7 @@ namespace App\Controllers\User;
 
 use App\Controllers\BaseController;
 use App\Services\DynamicRate;
+use App\Services\Subscribe;
 use App\Utils\ResponseHelper;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -21,7 +22,7 @@ final class RateController extends BaseController
      */
     public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $nodes = $this->user->getUserFrontEndNodes();
+        $nodes = Subscribe::getUserNodes($this->user);
         $node_list = [];
 
         foreach ($nodes as $node) {
@@ -40,7 +41,7 @@ final class RateController extends BaseController
 
     public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $nodes = $this->user->getUserFrontEndNodes();
+        $nodes = Subscribe::getUserNodes($this->user);
         $node = $nodes->find($request->getParam('node_id'));
 
         if ($node === null) {

+ 2 - 1
src/Controllers/User/ServerController.php

@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace App\Controllers\User;
 
 use App\Controllers\BaseController;
+use App\Services\Subscribe;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -18,7 +19,7 @@ final class ServerController extends BaseController
      */
     public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
-        $nodes = $this->user->getUserFrontEndNodes();
+        $nodes = Subscribe::getUserNodes($this->user, true);
         $node_list = [];
 
         foreach ($nodes as $node) {

+ 0 - 2
src/Controllers/User/TicketController.php

@@ -14,7 +14,6 @@ use Exception;
 use GuzzleHttp\Exception\GuzzleException;
 use Psr\Http\Client\ClientExceptionInterface;
 use Psr\Http\Message\ResponseInterface;
-use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use Telegram\Bot\Exceptions\TelegramSDKException;
@@ -53,7 +52,6 @@ final class TicketController extends BaseController
     }
 
     /**
-     * @throws RedisException
      * @throws ClientExceptionInterface
      * @throws TelegramSDKException
      * @throws GuzzleException

+ 1 - 1
src/Controllers/UserController.php

@@ -108,7 +108,7 @@ final class UserController extends BaseController
         $code = (new InviteCode())->where('user_id', $this->user->id)->first()?->code;
 
         if ($code === null) {
-            $code = $this->user->addInviteCode();
+            $code = (new InviteCode())->add($this->user->id);
         }
 
         $paybacks = (new Payback())->where('ref_by', $this->user->id)

+ 10 - 0
src/Models/InviteCode.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace App\Models;
 
+use App\Utils\Tools;
 use Illuminate\Database\Query\Builder;
 
 /**
@@ -17,4 +18,13 @@ final class InviteCode extends Model
 {
     protected $connection = 'default';
     protected $table = 'user_invite_code';
+
+    public function add(int $user_id): string
+    {
+        $this->code = Tools::genRandomChar(10);
+        $this->user_id = $user_id;
+        $this->save();
+
+        return $this->code;
+    }
 }

+ 1 - 2
src/Models/SubscribeLog.php

@@ -9,7 +9,6 @@ use App\Utils\Tools;
 use Exception;
 use GuzzleHttp\Exception\GuzzleException;
 use Illuminate\Database\Query\Builder;
-use MaxMind\Db\Reader\InvalidDatabaseException;
 use Psr\Http\Client\ClientExceptionInterface;
 use Telegram\Bot\Exceptions\TelegramSDKException;
 use function time;
@@ -44,7 +43,7 @@ final class SubscribeLog extends Model
     {
         try {
             return Tools::getIpLocation($this->request_ip);
-        } catch (InvalidDatabaseException|Exception $e) {
+        } catch (Exception $e) {
             return '未知';
         }
     }

+ 1 - 63
src/Models/User.php

@@ -8,15 +8,12 @@ use App\Services\IM;
 use App\Utils\Hash;
 use App\Utils\Tools;
 use GuzzleHttp\Exception\GuzzleException;
-use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Database\Query\Builder;
 use Ramsey\Uuid\Uuid;
 use Telegram\Bot\Exceptions\TelegramSDKException;
 use function date;
-use function is_null;
 use function md5;
 use function round;
-use function time;
 use const PHP_EOL;
 
 /**
@@ -107,23 +104,6 @@ final class User extends Model
         return Tools::genSs2022UserPk($this->passwd, $len);
     }
 
-    public function getUserFrontEndNodes(): Collection
-    {
-        $query = Node::query();
-        $query->where('type', 1);
-
-        if (! $this->is_admin) {
-            $group = ($this->node_group !== 0 ? [0, $this->node_group] : [0]);
-            $query->whereIn('node_group', $group);
-        }
-
-        return $query->where(static function ($query): void {
-            $query->where('node_bandwidth_limit', '=', 0)->orWhereRaw('node_bandwidth < node_bandwidth_limit');
-        })->orderBy('node_class')
-            ->orderBy('name')
-            ->get();
-    }
-
     /**
      * DiceBear 头像
      */
@@ -170,19 +150,6 @@ final class User extends Model
         return $this->save();
     }
 
-    /**
-     * 生成邀请码
-     */
-    public function addInviteCode(): string
-    {
-        $code = new InviteCode();
-        $code->code = Tools::genRandomChar(10);
-        $code->user_id = $this->id;
-        $code->save();
-
-        return $code->code;
-    }
-
     /**
      * 生成新的 API Token
      */
@@ -217,17 +184,6 @@ final class User extends Model
         return Tools::autoBytes($this->transfer_total);
     }
 
-    /*
-     * 已用流量占总流量的百分比
-     */
-    public function trafficUsagePercent(): float
-    {
-        return $this->transfer_enable === 0 ?
-            0
-            :
-            round(($this->u + $this->d) / $this->transfer_enable, 2) * 100;
-    }
-
     /*
      * 剩余流量[自动单位]
      */
@@ -309,25 +265,6 @@ final class User extends Model
         (new InviteCode())->where('user_id', $this->id)->delete();
     }
 
-    /**
-     * 累计充值金额
-     */
-    public function getTopUp(): float
-    {
-        $number = (new Paylist())->where('userid', $this->id)->sum('number');
-        return is_null($number) ? 0.00 : round((float) $number, 2);
-    }
-
-    /**
-     * 在线 IP 个数
-     */
-    public function onlineIpCount(): int
-    {
-        return (new OnlineLog())->where('user_id', $this->id)
-            ->where('last_time', '>', time() - 90)
-            ->count();
-    }
-
     /**
      * 销户
      */
@@ -350,6 +287,7 @@ final class User extends Model
     {
         $this->im_type = 0;
         $this->im_value = '';
+
         return $this->save();
     }
 

+ 1 - 2
src/Services/Bot/Telegram/Callback.php

@@ -937,8 +937,7 @@ final class Callback
             $code = (new InviteCode())->where('user_id', $this->user->id)->first();
 
             if ($code === null) {
-                $this->user->addInviteCode();
-                $code = (new InviteCode())->where('user_id', $this->user->id)->first();
+                $code = (new InviteCode())->add($this->user->id);
             }
 
             $inviteUrl = $_ENV['baseUrl'] . '/auth/register?code=' . $code->code;

+ 1 - 1
src/Services/Bot/Telegram/Commands/CheckinCommand.php

@@ -66,7 +66,7 @@ final class CheckinCommand extends Command
             );
         } else {
             if ($user->isAbleToCheckin()) {
-                $traffic = Reward::issueCheckinReward($this->user->id);
+                $traffic = Reward::issueCheckinReward($user->id);
 
                 if (! $traffic) {
                     $msg = '签到失败';

+ 0 - 2
src/Services/Bot/Telegram/Message.php

@@ -182,8 +182,6 @@ final class Message
     {
         $text = [
             '当前余额:' . $user->money,
-            '在线 IP 数:' . ($user->node_iplimit !== 0 ? $user->onlineIpCount() .
-                ' / ' . $user->node_iplimit : $user->onlineIpCount() . ' / 不限制'),
             '端口速率:' . ($user->node_speedlimit > 0 ? $user->node_speedlimit . 'Mbps' : '不限制'),
             '上次使用:' . $user->lastUseTime(),
             '过期时间:' . $user->class_expire,

+ 0 - 25
src/Services/RateLimit.php

@@ -8,13 +8,9 @@ use App\Models\Config;
 use RateLimit\Exception\LimitExceeded;
 use RateLimit\Rate;
 use RateLimit\RedisRateLimiter;
-use RedisException;
 
 final class RateLimit
 {
-    /**
-     * @throws RedisException
-     */
     public static function checkIPLimit(string $request_ip): bool
     {
         $ip_limiter = new RedisRateLimiter(
@@ -31,9 +27,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkSubLimit(string $sub_token): bool
     {
         $sub_limiter = new RedisRateLimiter(
@@ -50,9 +43,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkWebAPILimit(string $web_api_token): bool
     {
         $webapi_limiter = new RedisRateLimiter(
@@ -69,9 +59,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkUserAPILimit(string $user_api_token): bool
     {
         $user_api_limiter = new RedisRateLimiter(
@@ -88,9 +75,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkAdminAPILimit(string $admin_api_token): bool
     {
         $admin_api_limiter = new RedisRateLimiter(
@@ -107,9 +91,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkEmailIpLimit(string $request_ip): bool
     {
         $email_ip_limiter = new RedisRateLimiter(
@@ -126,9 +107,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkEmailAddressLimit(string $request_address): bool
     {
         $email_address_limiter = new RedisRateLimiter(
@@ -145,9 +123,6 @@ final class RateLimit
         return true;
     }
 
-    /**
-     * @throws RedisException
-     */
     public static function checkTicketLimit(int $user_id): bool
     {
         $ticket_limiter = new RedisRateLimiter(

+ 7 - 2
src/Services/Subscribe.php

@@ -37,13 +37,18 @@ final class Subscribe
 
     /**
      * @param $user
+     * @param bool $show_all_nodes
      *
      * @return Collection
      */
-    public static function getUserSubNodes($user): Collection
+    public static function getUserNodes($user, bool $show_all_nodes = false): Collection
     {
         $query = Node::query();
-        $query->where('type', 1)->where('node_class', '<=', $user->class);
+        $query->where('type', 1);
+
+        if (! $show_all_nodes) {
+            $query->where('node_class', '<=', $user->class);
+        }
 
         if (! $user->is_admin) {
             $group = ($user->node_group !== 0 ? [0, $user->node_group] : [0]);

+ 1 - 1
src/Services/Subscribe/Clash.php

@@ -18,7 +18,7 @@ final class Clash extends Base
         $clash_config = $_ENV['Clash_Config'];
         $clash_group_indexes = $_ENV['Clash_Group_Indexes'];
         $clash_group_config = $_ENV['Clash_Group_Config'];
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 1 - 1
src/Services/Subscribe/SIP002.php

@@ -19,7 +19,7 @@ final class SIP002 extends Base
             return $links;
         }
 
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 1 - 1
src/Services/Subscribe/SIP008.php

@@ -19,7 +19,7 @@ final class SIP008 extends Base
             return '';
         }
 
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 1 - 1
src/Services/Subscribe/SS.php

@@ -19,7 +19,7 @@ final class SS extends Base
             return $links;
         }
 
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             if ((int) $node_raw->sort === 0) {

+ 1 - 1
src/Services/Subscribe/SingBox.php

@@ -16,7 +16,7 @@ final class SingBox extends Base
     {
         $nodes = [];
         $singbox_config = $_ENV['SingBox_Config'];
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 1 - 1
src/Services/Subscribe/Trojan.php

@@ -19,7 +19,7 @@ final class Trojan extends Base
             return $links;
         }
 
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 1 - 1
src/Services/Subscribe/V2Ray.php

@@ -21,7 +21,7 @@ final class V2Ray extends Base
             return $links;
         }
 
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 1 - 1
src/Services/Subscribe/V2RayJson.php

@@ -16,7 +16,7 @@ final class V2RayJson extends Base
     {
         $nodes = [];
         $v2rayjson_config = $_ENV['V2RayJson_Config'];
-        $nodes_raw = Subscribe::getUserSubNodes($user);
+        $nodes_raw = Subscribe::getUserNodes($user);
 
         foreach ($nodes_raw as $node_raw) {
             $node_custom_config = json_decode($node_raw->custom_config, true);

+ 56 - 0
tests/App/Services/CacheTest.php

@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services;
+
+use PHPUnit\Framework\TestCase;
+
+class CacheTest extends TestCase
+{
+    /**
+     * @covers App\Services\Cache::getRedisConfig
+     */
+    public function testGetRedisConfig()
+    {
+        // Scenario 1: All parameters are set
+        $_ENV['redis_host'] = 'localhost';
+        $_ENV['redis_port'] = '6379';
+        $_ENV['redis_connect_timeout'] = '1.0';
+        $_ENV['redis_read_timeout'] = '1.0';
+        $_ENV['redis_username'] = 'username';
+        $_ENV['redis_password'] = 'password';
+        $_ENV['redis_ssl'] = true;
+        $_ENV['redis_ssl_context'] = [];
+
+        $expected1 = [
+            'host' => 'localhost',
+            'port' => '6379',
+            'connectTimeout' => '1.0',
+            'readTimeout' => '1.0',
+            'auth' => [
+                'user' => 'username',
+                'pass' => 'password',
+            ],
+            'ssl' => [],
+        ];
+
+        $result1 = Cache::getRedisConfig();
+        $this->assertEquals($expected1, $result1);
+
+        // Scenario 2: Optional parameters are not set
+        $_ENV['redis_username'] = '';
+        $_ENV['redis_password'] = '';
+        $_ENV['redis_ssl'] = false;
+
+        $expected2 = [
+            'host' => 'localhost',
+            'port' => '6379',
+            'connectTimeout' => '1.0',
+            'readTimeout' => '1.0',
+        ];
+
+        $result2 = Cache::getRedisConfig();
+        $this->assertEquals($expected2, $result2);
+    }
+}

+ 64 - 0
tests/App/Services/DBTest.php

@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services;
+
+use PHPUnit\Framework\TestCase;
+
+class DBTest extends TestCase
+{
+    /**
+     * @covers App\Services\DB::getConfig
+     */
+    public function testGetConfig()
+    {
+        // Scenario 1: All parameters are set
+        $_ENV['db_driver'] = 'mysql';
+        $_ENV['db_host'] = 'localhost';
+        $_ENV['db_socket'] = '/tmp/mysql.sock';
+        $_ENV['db_database'] = 'test_db';
+        $_ENV['db_username'] = 'username';
+        $_ENV['db_password'] = 'password';
+        $_ENV['db_charset'] = 'utf8';
+        $_ENV['db_collation'] = 'utf8_unicode_ci';
+        $_ENV['db_prefix'] = '';
+        $_ENV['db_port'] = '3306';
+
+        $expected1 = [
+            'driver' => 'mysql',
+            'host' => 'localhost',
+            'unix_socket' => '/tmp/mysql.sock',
+            'database' => 'test_db',
+            'username' => 'username',
+            'password' => 'password',
+            'charset' => 'utf8',
+            'collation' => 'utf8_unicode_ci',
+            'prefix' => '',
+            'port' => '3306',
+        ];
+
+        $result1 = DB::getConfig();
+        $this->assertEquals($expected1, $result1);
+
+        // Scenario 2: Missing parameters
+        $_ENV['db_socket'] = '';
+        $_ENV['db_port'] = '';
+
+        $expected2 = [
+            'driver' => 'mysql',
+            'host' => 'localhost',
+            'unix_socket' => '',
+            'database' => 'test_db',
+            'username' => 'username',
+            'password' => 'password',
+            'charset' => 'utf8',
+            'collation' => 'utf8_unicode_ci',
+            'prefix' => '',
+            'port' => '',
+        ];
+
+        $result2 = DB::getConfig();
+        $this->assertEquals($expected2, $result2);
+    }
+}

+ 71 - 5
tests/App/Utils/ToolsTest.php

@@ -12,7 +12,6 @@ class ToolsTest extends TestCase
 {
     /**
      * @covers App\Utils\Tools::getIpLocation
-     * @throws InvalidDatabaseException
      */
     public function testGetIpLocation()
     {
@@ -139,6 +138,23 @@ class ToolsTest extends TestCase
         $this->assertEquals($expected, $result);
     }
 
+    /**
+     * @covers App\Utils\Tools::getDir
+     */
+    public function testGetDir()
+    {
+        // Scenario 1: Valid directory
+        $dir1 = 'tests/testDir';
+        $expected1 = ['emptyDir', 'file1', 'file2', 'file3']; // Replace with actual expected result
+        $result1 = Tools::getDir($dir1);
+        $this->assertEqualsCanonicalizing($expected1, $result1);
+
+        // Scenario 2: Directory with .gitkeep
+        $dir2 = 'tests/testDir/emptyDir';
+        $result2 = Tools::getDir($dir2);
+        $this->assertEqualsCanonicalizing(['.gitkeep'], $result2);
+    }
+
     /**
      * @covers App\Utils\Tools::isParamValidate
      */
@@ -149,12 +165,52 @@ class ToolsTest extends TestCase
     }
 
     /**
-     * @covers App\Utils\Tools::isEmail
+     * @covers App\Utils\Tools::getSsMethod
      */
-    public function testIsEmail()
+    public function testGetSsMethod()
     {
-        $this->assertTrue(Tools::isEmail('[email protected]'));
-        $this->assertFalse(Tools::isEmail('test@example'));
+        // Scenario 1: ss_obfs
+        $expected1 = [
+            'simple_obfs_http',
+            'simple_obfs_http_compatible',
+            'simple_obfs_tls',
+            'simple_obfs_tls_compatible',
+        ];
+        $result1 = Tools::getSsMethod('ss_obfs');
+        $this->assertEquals($expected1, $result1);
+
+        // Scenario 2: default
+        $expected2 = [
+            'aes-128-gcm',
+            'aes-192-gcm',
+            'aes-256-gcm',
+            'chacha20-ietf-poly1305',
+            'xchacha20-ietf-poly1305',
+        ];
+        $result2 = Tools::getSsMethod('default');
+        $this->assertEquals($expected2, $result2);
+
+        // Scenario 3: Random string
+        $expected3 = [
+            'aes-128-gcm',
+            'aes-192-gcm',
+            'aes-256-gcm',
+            'chacha20-ietf-poly1305',
+            'xchacha20-ietf-poly1305',
+        ];
+        $result3 = Tools::getSsMethod('randomString');
+        $this->assertEquals($expected3, $result3);
+
+        // Scenario 4: Empty string
+        $expected4 = [
+            'aes-128-gcm',
+            'aes-192-gcm',
+            'aes-256-gcm',
+            'chacha20-ietf-poly1305',
+            'xchacha20-ietf-poly1305',
+        ];
+        $result4 = Tools::getSsMethod('');
+        $this->assertEquals($expected4, $result4);
     }
 
     /**
@@ -175,6 +231,15 @@ class ToolsTest extends TestCase
         $this->assertEquals($expected2, Tools::isEmailLegal($email2));
     }
 
+    /**
+     * @covers App\Utils\Tools::isEmail
+     */
+    public function testIsEmail()
+    {
+        $this->assertTrue(Tools::isEmail('[email protected]'));
+        $this->assertFalse(Tools::isEmail('test@example'));
+    }
+
     /**
      * @covers App\Utils\Tools::isIPv4
      */
@@ -209,5 +274,6 @@ class ToolsTest extends TestCase
     {
         $this->assertTrue(Tools::isJson('{}'));
         $this->assertFalse(Tools::isJson('[]'));
+        $this->assertFalse(Tools::isJson('what the'));
     }
 }

+ 0 - 0
tests/testDir/emptyDir/.gitkeep


+ 0 - 0
tests/testDir/file1


+ 0 - 0
tests/testDir/file2


+ 0 - 0
tests/testDir/file3