Pārlūkot izejas kodu

refactor: improve code quality

M1Screw 2 gadi atpakaļ
vecāks
revīzija
fce9d02900
42 mainītis faili ar 240 papildinājumiem un 336 dzēšanām
  1. 1 1
      .github/ISSUE_TEMPLATE/feature_request.md
  2. 1 1
      .github/workflows/lint.yml
  3. 4 45
      .github/workflows/sonarcloud.yml
  4. 3 3
      CONTRIBUTING.md
  5. 4 6
      README.md
  6. 1 1
      public/assets/css/error-pages.css
  7. 1 1
      public/assets/css/error-pages.min.css
  8. 3 3
      public/assets/css/info-pages.css
  9. 1 1
      public/assets/css/info-pages.min.css
  10. 1 1
      resources/views/tabler/admin/announcement/create.tpl
  11. 2 2
      resources/views/tabler/admin/node/create.tpl
  12. 2 2
      resources/views/tabler/admin/node/edit.tpl
  13. 1 1
      resources/views/tabler/admin/tabler_header.tpl
  14. 1 1
      resources/views/tabler/auth/login.tpl
  15. 1 1
      resources/views/tabler/auth/register.tpl
  16. 1 1
      resources/views/tabler/gateway/epay.tpl
  17. 0 1
      resources/views/tabler/live_chat.tpl
  18. 1 1
      resources/views/tabler/password/reset.tpl
  19. 1 1
      resources/views/tabler/user/index.tpl
  20. 1 1
      resources/views/tabler/user/media.tpl
  21. 10 10
      resources/views/tabler/user/tabler_header.tpl
  22. 2 1
      src/Command/Job.php
  23. 6 1
      src/Controllers/Admin/CouponController.php
  24. 16 6
      src/Controllers/Admin/GiftCardController.php
  25. 2 2
      src/Controllers/Admin/InvoiceController.php
  26. 2 2
      src/Controllers/Admin/NodeController.php
  27. 11 9
      src/Controllers/Admin/OrderController.php
  28. 1 1
      src/Controllers/Admin/PaylistController.php
  29. 5 3
      src/Controllers/Admin/TicketController.php
  30. 9 3
      src/Controllers/User/InvoiceController.php
  31. 10 14
      src/Controllers/User/OrderController.php
  32. 4 1
      src/Controllers/User/ServerController.php
  33. 5 3
      src/Controllers/User/TicketController.php
  34. 8 0
      src/Models/GiftCard.php
  35. 15 0
      src/Models/Invoice.php
  36. 2 36
      src/Models/Node.php
  37. 2 2
      src/Models/OnlineLog.php
  38. 28 0
      src/Models/Order.php
  39. 12 0
      src/Models/Paylist.php
  40. 32 0
      src/Models/Product.php
  41. 27 0
      src/Models/UserCoupon.php
  42. 0 167
      src/Utils/Tools.php

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.md

@@ -1,6 +1,6 @@
 ---
 name: Feature request
-about: Suggest an new idea for this project
+about: Suggest a new idea for this project
 title: "[Feature Request]"
 labels: enhancement
 

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

@@ -29,7 +29,7 @@ jobs:
           fetch-depth: 0
       - uses: shivammathur/setup-php@v2
         with:
-          php-version: 8.0      
+          php-version: 8.1
       - run: |
           composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
           composer require nunomaduro/phpinsights --dev

+ 4 - 45
.github/workflows/sonarcloud.yml

@@ -1,32 +1,4 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# This workflow helps you trigger a SonarCloud analysis of your code and populates
-# GitHub Code Scanning alerts with the vulnerabilities found.
-# Free for open source project.
-
-# 1. Login to SonarCloud.io using your GitHub account
-
-# 2. Import your project on SonarCloud
-#     * Add your GitHub organization first, then add your repository as a new project.
-#     * Please note that many languages are eligible for automatic analysis,
-#       which means that the analysis will start automatically without the need to set up GitHub Actions.
-#     * This behavior can be changed in Administration > Analysis Method.
-#
-# 3. Follow the SonarCloud in-product tutorial
-#     * a. Copy/paste the Project Key and the Organization Key into the args parameter below
-#          (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
-#
-#     * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
-#          (On SonarCloud, click on your avatar on top-right > My account > Security
-#           or go directly to https://sonarcloud.io/account/security/)
-
-# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
-# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
-
-name: SonarCloud analysis
+name: SonarCloud
 
 on:
   push:
@@ -36,7 +8,7 @@ on:
   workflow_dispatch:
 
 permissions:
-  pull-requests: read # allows SonarCloud to decorate PRs with analysis results
+  pull-requests: read
 
 jobs:
   Analysis:
@@ -45,24 +17,11 @@ jobs:
     steps:
       - name: Analyze with SonarCloud
 
-        # You can pin the exact commit or the version.
-        # uses: SonarSource/sonarcloud-github-action@de2e56b42aa84d0b1c5b622644ac17e505c9a049
         uses: SonarSource/sonarcloud-github-action@de2e56b42aa84d0b1c5b622644ac17e505c9a049
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information
-          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}   # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
         with:
-          # Additional arguments for the sonarcloud scanner
           args:
-            # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu)
-            # mandatory
             -Dsonar.projectKey=sspanel-uim_SSPanel-Uim-Dev
             -Dsonar.organization=sspanel-uim
-            # Comma-separated paths to directories containing main source files.
-            #-Dsonar.sources= # optional, default is project base directory
-            # When you need the analysis to take place in a directory other than the one from which it was launched
-            #-Dsonar.projectBaseDir= # optional, default is .
-            # Comma-separated paths to directories containing test source files.
-            #-Dsonar.tests= # optional. For more info about Code Coverage, please refer to https://docs.sonarcloud.io/enriching/test-coverage/overview/
-            # Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing.
-            #-Dsonar.verbose= # optional, default is false

+ 3 - 3
CONTRIBUTING.md

@@ -7,7 +7,7 @@
 
 ### Pull Requests
 
-1. Fork the this repository
+1. Fork the repository
 2. Create a new branch for each feature or improvement
 3. Send a pull request from each feature branch against the dev branch.
 
@@ -28,13 +28,13 @@ Please try to use **English** in the Commit message and use the following Type t
 - fix: fix bugs (bug fix)
 - docs: documents (documentation)
 - style: formatting    
-        changes that do not affect the operation of the code, e.g. white-space, formatting, missing semi colons
+        changes that do not affect the operation of the code, e.g. white-space, formatting, missing semicolons
 - refactor: refactoring    
         Code changes that do not add features or fix bugs
 - perf: improves performance
 - test: when adding missing tests
 - chore: maintenance    
-        Changes to builders or helpers that do not affect the operation of the code, e.g., changes to config, Grunt Task task management tools
+        Changes to builders or helpers that do not affect the operation of the code, e.g., changes to config, Grunt Task management tools
 - revert: undo a previous commit    
         Example: revert: type(scope):subject
 

+ 4 - 6
README.md

@@ -1,4 +1,4 @@
-<img src="public/images/uim-logo-round_192x192.png" alt="logo" width="130" height="130" align="left" />
+<img src="public/images/uim-logo-round_192x192.png" alt="logo" width="192" height="192" align="left" />
 
 <h1>SSPanel UIM</h1>
 
@@ -24,13 +24,11 @@ SSPanel UIM 是一款专为 Shadowsocks / V2Ray / Trojan 协议设计的多用
 ## 特性
 
 - 集成 支付宝当面付,Stripe 银行卡,彩虹易支付 等多种支付系统
-- 数据库化配置,管理面板一键配置
 - 支持多种邮件服务,内置队列功能,无需第三方组件即可使用
 - 内置基于 Bootstrap 5 的 tabler 主题,Smarty 模板引擎支持
 - 支持 Shadowsocks 2022,Shadowsocks AEAD,Trojan-Go 等最新代理协议
-- 通用订阅接口,一键 json/clash 格式订阅下发,自定义客户端更简单
-- 自定义节点配置,模块化订阅系统,支持多种传统订阅模式,自定义订阅更方便
-- 支持 用户IP匿名化,无日志模式,用户自助删除数据 等隐私保护功能,满足合规性需求
+- 通用订阅接口,一键 json/clash/sip008 格式订阅下发
+- 自定义节点配置,模块化订阅系统,支持多种传统订阅模式
 
 ## 安装
 
@@ -38,7 +36,7 @@ SSPanel UIM 的需要以下程序才能正常的安装和运行:
 
 - Git
 - Nginx(必须使用 HTTPS/HTTPS is REQUIRED)
-- PHP 8.0+
+- PHP 8.1+ (推荐开启 OPcache/OPcache is recommended)
 - MariaDB 10.6+(关闭严格模式,不兼容 MySQL/Disable strict mode, DO NOT USE MYSQL)
 
 我们推荐用户在开始使用之前至少有一定程度的 PHP 和 Linux 使用知识,能够至少正确识别使用中所出现的问题并在 issue 中提供所需的信息。

+ 1 - 1
public/assets/css/error-pages.css

@@ -33,6 +33,6 @@ body {
 p {
   color: #fff;
   font-size: 24px;
-  letter-spacing: .2px;
+  letter-spacing: 1px;
   margin: 0;
 }

+ 1 - 1
public/assets/css/error-pages.min.css

@@ -1 +1 @@
-@import url(https://fonts.googleapis.com/css?family=Roboto+Mono);.center-xy{top:50%;left:50%;transform:translate(-50%,-50%);position:absolute}body,html{font-family:'Roboto Mono',monospace;font-size:16px}html{box-sizing:border-box;user-select:none}body{background-color:#000}.container{width:100%}.copy-container{text-align:center}p{color:#fff;font-size:24px;letter-spacing:.2px;margin:0}
+@import url(https://fonts.googleapis.com/css?family=Roboto+Mono);.center-xy{top:50%;left:50%;transform:translate(-50%,-50%);position:absolute}body,html{font-family:'Roboto Mono',monospace;font-size:16px}html{box-sizing:border-box;user-select:none}body{background-color:#000}.container{width:100%}.copy-container{text-align:center}p{color:#fff;font-size:24px;letter-spacing:1px;margin:0}

+ 3 - 3
public/assets/css/info-pages.css

@@ -35,21 +35,21 @@ body {
 h1 {
   color: #fff;
   font-size: 24px;
-  letter-spacing: .2px;
+  letter-spacing: 1px;
   margin: 0;
 }
 
 h2 {
     color: #fff;
     font-size: 22px;
-    letter-spacing: .2px;
+    letter-spacing: 1px;
     margin: 0;
 }
 
 p {
   color: #fff;
   font-size: 20px;
-  letter-spacing: .2px;
+  letter-spacing: 1px;
   margin: 0;
 }
 

+ 1 - 1
public/assets/css/info-pages.min.css

@@ -1 +1 @@
-@import url(https://fonts.googleapis.com/css2?family=Roboto+Mono);@import url(https://fonts.googleapis.com/css2?family=Noto+Sans+SC);.center-xy{top:10%;left:20%;right:20%;position:absolute;padding-bottom:5%}body,html{font-family:'Roboto Mono','Noto Sans SC',monospace;font-size:16px}html{box-sizing:border-box;user-select:none}body{background-color:#000}.container{width:100%}.copy-container{text-align:center}h1{color:#fff;font-size:24px;letter-spacing:.2px;margin:0}h2{color:#fff;font-size:22px;letter-spacing:.2px;margin:0}p{color:#fff;font-size:20px;letter-spacing:.2px;margin:0}a{color:#fff;text-decoration:none}
+@import url(https://fonts.googleapis.com/css2?family=Roboto+Mono);@import url(https://fonts.googleapis.com/css2?family=Noto+Sans+SC);.center-xy{top:10%;left:20%;right:20%;position:absolute;padding-bottom:5%}body,html{font-family:'Roboto Mono','Noto Sans SC',monospace;font-size:16px}html{box-sizing:border-box;user-select:none}body{background-color:#000}.container{width:100%}.copy-container{text-align:center}h1{color:#fff;font-size:24px;letter-spacing:1px;margin:0}h2{color:#fff;font-size:22px;letter-spacing:1px;margin:0}p{color:#fff;font-size:20px;letter-spacing:1px;margin:0}a{color:#fff;text-decoration:none}

+ 1 - 1
resources/views/tabler/admin/announcement/create.tpl

@@ -38,7 +38,7 @@
                     <div class="mb-3">
                         <label class="form-label col-3 col-form-label">公告通知的用户等级,0为不分级</label>
                         <div class="col">
-                            <input id="email_notify_class" type="text" class="form-control" value=""></input>
+                            <input id="email_notify_class" type="text" class="form-control" value="">
                         </div>
                     </div>
                     <div class="mb-3">

+ 2 - 2
resources/views/tabler/admin/node/create.tpl

@@ -44,7 +44,7 @@
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-3 col-form-label required">连接地址</label>
                                 <div class="col">
-                                    <input id="server" type="text" class="form-control" value=""></input>
+                                    <input id="server" type="text" class="form-control" value="">
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">
@@ -72,7 +72,7 @@
                             </div>
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-3 col-form-label">自定义配置</label>
-                                <dev id="custom_config"></dev>
+                                <div id="custom_config"></div>
                                 <label class="form-label col-form-label">
                                     请参考 <a href="//wiki.sspanel.org/#/custom-config" target="_blank">wiki.sspanel.org/#/custom-config</a> 修改节点自定义配置
                                 </label>

+ 2 - 2
resources/views/tabler/admin/node/edit.tpl

@@ -44,7 +44,7 @@
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-3 col-form-label">连接地址</label>
                                 <div class="col">
-                                    <input id="server" type="text" class="form-control" value="{$node->server}"></input>
+                                    <input id="server" type="text" class="form-control" value="{$node->server}">
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">
@@ -72,7 +72,7 @@
                             </div>
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-3 col-form-label">自定义配置</label>
-                                <dev id="custom_config"></dev>
+                                <div id="custom_config"></div>
                                 <label class="form-label col-form-label">
                                     请参考 <a href="//wiki.sspanel.org/#/custom-config" target="_blank">wiki.sspanel.org/#/custom-config</a> 修改节点自定义配置
                                 </label>

+ 1 - 1
resources/views/tabler/admin/tabler_header.tpl

@@ -266,7 +266,7 @@
                                         <i class="ti ti-arrow-back-up icon"></i>
                                     </span>
                                     <span class="nav-link-title">
-                                        返回
+                                        返回用户中心
                                     </span>
                                 </a>
                             </li>

+ 1 - 1
resources/views/tabler/auth/login.tpl

@@ -97,7 +97,7 @@
     <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha" async defer></script>
     {/if}
     {if $public_setting['enable_login_captcha'] === true && $public_setting['captcha_provider'] === 'geetest'}
-        <script src="http://static.geetest.com/v4/gt4.js"></script>
+        <script src="https://static.geetest.com/v4/gt4.js"></script>
         <script>
             var geetest_result = '';
             initGeetest4({

+ 1 - 1
resources/views/tabler/auth/register.tpl

@@ -164,7 +164,7 @@
     <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha" async defer></script>
     {/if}
     {if $public_setting['enable_reg_captcha'] === true && $public_setting['captcha_provider'] === 'geetest'}
-    <script src="http://static.geetest.com/v4/gt4.js"></script>
+    <script src="https://static.geetest.com/v4/gt4.js"></script>
     <script>
         var geetest_result = '';
         initGeetest4({

+ 1 - 1
resources/views/tabler/gateway/epay.tpl

@@ -1,7 +1,7 @@
 <div class="card-inner">
     <h4>
         EPay 在线充值
-    <h4>
+    </h4>
     <p class="card-heading"></p>
     <form class="epay" name="epay" action="/user/payment/purchase/epay" method="post">
         {if $user->use_new_shop}

+ 0 - 1
resources/views/tabler/live_chat.tpl

@@ -6,7 +6,6 @@
         var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];
         s1.async=true;
         s1.src='https://embed.tawk.to/' + id + '/default';
-        s1.charset='UTF-8';
         s1.setAttribute('crossorigin','*');
         s0.parentNode.insertBefore(s1,s0);
     })();

+ 1 - 1
resources/views/tabler/password/reset.tpl

@@ -82,7 +82,7 @@
     <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha" async defer></script>
     {/if}
     {if $public_setting['enable_reset_password_captcha'] === true && $public_setting['captcha_provider'] === 'geetest'}
-    <script src="http://static.geetest.com/v4/gt4.js"></script>
+    <script src="https://static.geetest.com/v4/gt4.js"></script>
     <script>
         var geetest_result = '';
         initGeetest4({

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

@@ -553,7 +553,7 @@
     <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha" async defer></script>
     {/if}
     {if $public_setting['enable_checkin_captcha'] === true && $public_setting['captcha_provider'] === 'geetest'}
-    <script src="http://static.geetest.com/v4/gt4.js"></script>
+    <script src="https://static.geetest.com/v4/gt4.js"></script>
     <script>
         var geetest_result = '';
         initGeetest4({

+ 1 - 1
resources/views/tabler/user/media.tpl

@@ -29,7 +29,7 @@
                                             {if $key != 'BilibiliChinaMainland'}
                                                 {if $key == 'BilibiliHKMCTW'}
                                                     <th>Bilibili(港澳台)</th>
-                                                {else if $key == 'BilibiliTW'}
+                                                {elseif $key == 'BilibiliTW'}
                                                     <th>Bilibili(台湾)</th>
                                                 {else}
                                                     <th>{$key}</th>

+ 10 - 10
resources/views/tabler/user/tabler_header.tpl

@@ -96,10 +96,10 @@
                                                 <i class="ti ti-edit"></i>&nbsp;
                                                 资料修改
                                             </a>
-                                            {if $public_setting['enable_ticket']}
-                                            <a class="dropdown-item" href="/user/ticket">
-                                                <i class="ti ti-ticket"></i>&nbsp;
-                                                工单系统
+                                            {if $public_setting['display_subscribe_log'] === true}
+                                            <a class="dropdown-item" href="/user/subscribe/log">
+                                                <i class="ti ti-rss"></i></i>&nbsp;
+                                                订阅日志
                                             </a>
                                             {/if}
                                             <a class="dropdown-item" href="/user/invite">
@@ -140,7 +140,7 @@
                                         <i class="ti ti-dots-circle-horizontal icon"></i>
                                     </span>
                                     <span class="nav-link-title">
-                                        信息
+                                        支援
                                     </span>
                                 </a>
                                 <div class="dropdown-menu">
@@ -148,10 +148,10 @@
                                         <i class="ti ti-speakerphone"></i>&nbsp;
                                         站点公告
                                     </a>
-                                    {if $public_setting['display_subscribe_log'] === true}
-                                    <a class="dropdown-item" href="/user/subscribe/log">
-                                        <i class="ti ti-rss"></i></i>&nbsp;
-                                        订阅日志
+                                    {if $public_setting['enable_ticket']}
+                                    <a class="dropdown-item" href="/user/ticket">
+                                        <i class="ti ti-ticket"></i>&nbsp;
+                                        工单系统
                                     </a>
                                     {/if}
                                 </div>
@@ -232,7 +232,7 @@
                                         <i class="ti ti-settings icon"></i>
                                     </span>
                                     <span class="nav-link-title">
-                                        管理
+                                        站点管理
                                     </span>
                                 </a>
                             </li>

+ 2 - 1
src/Command/Job.php

@@ -363,7 +363,8 @@ EOL;
         foreach ($nodes as $node) {
             /** @var Node $node */
             $server = $node->server;
-            if (! Tools::isIPv4($server) && $node->changeNodeIp($server)) {
+            if (! Tools::isIPv4($server) && ! Tools::isIPv6($server)) {
+                $node->changeNodeIp($server);
                 $node->save();
             }
         }

+ 6 - 1
src/Controllers/Admin/CouponController.php

@@ -247,25 +247,30 @@ final class CouponController extends BaseController
                 <button type="button" class="btn btn-orange" id="disable-coupon-' . $coupon->id . '" 
                 onclick="disableCoupon(' . $coupon->id . ')">禁用</button>';
             }
-            $coupon->type = Tools::getCouponType($content);
+            $coupon->type = $coupon->type();
             $coupon->value = $content->value;
             $coupon->product_id = $limit->product_id;
+
             if ((int) $limit->use_time < 0) {
                 $coupon->use_time = '不限次数';
             } else {
                 $coupon->use_time = $limit->use_time;
             }
+
             if ($limit->new_user === 1) {
                 $coupon->new_user = '是';
             } else {
                 $coupon->new_user = '否';
             }
+
             if ($limit->disabled === 1) {
                 $coupon->disabled = '是';
             } else {
                 $coupon->disabled = '否';
             }
+
             $coupon->create_time = Tools::toDateTime((int) $coupon->create_time);
+
             if ($coupon->expire_time === 0) {
                 $coupon->expire_time = '永久有效';
             } else {

+ 16 - 6
src/Controllers/Admin/GiftCardController.php

@@ -63,25 +63,32 @@ final class GiftCardController extends BaseController
 
     public function add(Request $request, Response $response, array $args): ResponseInterface
     {
-        $card_number = $request->getParam('card_number');
-        $card_value = $request->getParam('card_value');
-        $card_length = $request->getParam('card_length');
+        $card_number = $request->getParam('card_number') ?? 0;
+        $card_value = $request->getParam('card_value') ?? 0;
+        $card_length = $request->getParam('card_length') ?? 0;
         $card_added = '';
 
-        if ($card_number === null || $card_number < 0) {
+        if ($card_number === '' || $card_number <= 0) {
             return $response->withJson([
                 'ret' => 0,
                 'msg' => '生成数量不能为空或小于0',
             ]);
         }
 
-        if ($card_value === null || $card_value < 0) {
+        if ($card_value === '' || $card_value <= 0) {
             return $response->withJson([
                 'ret' => 0,
                 'msg' => '礼品卡面值不能为空或小于0',
             ]);
         }
 
+        if ($card_length === '' || $card_length <= 0) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '礼品卡长度不能为空或小于0',
+            ]);
+        }
+
         for ($i = 0; $i < $card_number; $i++) {
             $card = Tools::genRandomChar($card_length);
             // save to database
@@ -106,6 +113,7 @@ final class GiftCardController extends BaseController
     {
         $card_id = $args['id'];
         GiftCard::find($card_id)->delete();
+
         return $response->withJson([
             'ret' => 1,
             'msg' => '删除成功',
@@ -115,13 +123,15 @@ final class GiftCardController extends BaseController
     public function ajax(Request $request, Response $response, array $args): ResponseInterface
     {
         $giftcards = GiftCard::orderBy('id', 'desc')->get();
+
         foreach ($giftcards as $giftcard) {
             $giftcard->op = '<button type="button" class="btn btn-red" id="delete-gift-card-' . $giftcard->id . '" 
         onclick="deleteGiftCard(' . $giftcard->id . ')">删除</button>';
-            $giftcard->status = Tools::getGiftCardStatus($giftcard);
+            $giftcard->status = $giftcard->status();
             $giftcard->create_time = Tools::toDateTime((int) $giftcard->create_time);
             $giftcard->use_time = Tools::toDateTime((int) $giftcard->use_time);
         }
+
         return $response->withJson([
             'giftcards' => $giftcards,
         ]);

+ 2 - 2
src/Controllers/Admin/InvoiceController.php

@@ -48,7 +48,7 @@ final class InvoiceController extends BaseController
             $paylist = Paylist::where('invoice_id', $invoice->id)->where('status', 1)->first();
         }
 
-        $invoice->status_text = Tools::getInvoiceStatus($invoice);
+        $invoice->status_text = $invoice->status();
         $invoice->create_time = Tools::toDateTime($invoice->create_time);
         $invoice->update_time = Tools::toDateTime($invoice->update_time);
         $invoice->pay_time = Tools::toDateTime($invoice->pay_time);
@@ -105,7 +105,7 @@ final class InvoiceController extends BaseController
 
         foreach ($invoices as $invoice) {
             $invoice->op = '<a class="btn btn-blue" href="/admin/invoice/' . $invoice->id . '/view">查看</a>';
-            $invoice->status = Tools::getInvoiceStatus($invoice);
+            $invoice->status = $invoice->status();
             $invoice->create_time = Tools::toDateTime($invoice->create_time);
             $invoice->update_time = Tools::toDateTime($invoice->update_time);
             $invoice->pay_time = Tools::toDateTime($invoice->pay_time);

+ 2 - 2
src/Controllers/Admin/NodeController.php

@@ -360,8 +360,8 @@ final class NodeController extends BaseController
             <button type="button" class="btn btn-orange" id="copy-node-' . $node->id . '" 
             onclick="copyNode(' . $node->id . ')">复制</button>
             <a class="btn btn-blue" href="/admin/node/' . $node->id . '/edit">编辑</a>';
-            $node->type = Tools::getNodeType($node);
-            $node->sort = Tools::getNodeSort($node);
+            $node->type = $node->type();
+            $node->sort = $node->sort();
             $node->node_bandwidth = round(Tools::flowToGB($node->node_bandwidth), 2);
             $node->node_bandwidth_limit = Tools::flowToGB($node->node_bandwidth_limit);
         }

+ 11 - 9
src/Controllers/Admin/OrderController.php

@@ -44,15 +44,15 @@ final class OrderController extends BaseController
         $id = $args['id'];
 
         $order = Order::find($id);
-        $order->product_type = Tools::getOrderProductType($order);
-        $order->status_text = Tools::getOrderStatus($order);
+        $order->product_type = $order->productType();
+        $order->status_text = $order->status();
         $order->create_time = Tools::toDateTime($order->create_time);
         $order->update_time = Tools::toDateTime($order->update_time);
 
         $product_content = \json_decode($order->product_content);
 
         $invoice = Invoice::where('order_id', $id)->first();
-        $invoice->status = Tools::getInvoiceStatus($invoice);
+        $invoice->status = $invoice->status();
         $invoice->create_time = Tools::toDateTime($invoice->create_time);
         $invoice->update_time = Tools::toDateTime($invoice->update_time);
         $invoice->pay_time = Tools::toDateTime($invoice->pay_time);
@@ -131,17 +131,19 @@ final class OrderController extends BaseController
         $orders = Order::orderBy('id', 'desc')->get();
 
         foreach ($orders as $order) {
-            $order->op = '<button type="button" class="btn btn-red" id="delete-order-' . $order->id . '" 
-            onclick="deleteOrder(' . $order->id . ')">删除</button>';
+            $order->op = '<button type="button" class="btn btn-red" id="delete-order-' . $order->id . '"
+             onclick="deleteOrder(' . $order->id . ')">删除</button>';
+
             if ($order->status === 'pending_payment') {
                 $order->op .= '
-                <button type="button" class="btn btn-orange" id="cancel-order-' . $order->id . '" 
-                onclick="cancelOrder(' . $order->id . ')">取消</button>';
+                <button type="button" class="btn btn-orange" id="cancel-order-' . $order->id . '"
+                 onclick="cancelOrder(' . $order->id . ')">取消</button>';
             }
+
             $order->op .= '
             <a class="btn btn-blue" href="/admin/order/' . $order->id . '/view">查看</a>';
-            $order->product_type = Tools::getOrderProductType($order);
-            $order->status = Tools::getOrderStatus($order);
+            $order->product_type = $order->productType();
+            $order->status = $order->status();
             $order->create_time = Tools::toDateTime($order->create_time);
             $order->update_time = Tools::toDateTime($order->update_time);
         }

+ 1 - 1
src/Controllers/Admin/PaylistController.php

@@ -50,7 +50,7 @@ final class PaylistController extends BaseController
         $paylists = Paylist::orderBy('id', 'desc')->get();
 
         foreach ($paylists as $paylist) {
-            $paylist->status = Tools::getPaylistStatus($paylist);
+            $paylist->status = $paylist->status();
             $paylist->datetime = Tools::toDateTime((int) $paylist->datetime);
         }
 

+ 5 - 3
src/Controllers/Admin/TicketController.php

@@ -112,7 +112,7 @@ final class TicketController extends BaseController
         $ticket = Ticket::where('id', '=', $id)->first();
 
         if ($ticket === null) {
-            return $response->withStatus(302)->withHeader('Location', '/admin/ticket');
+            return $response->withRedirect('/admin/ticket');
         }
 
         $comments = json_decode($ticket->content);
@@ -196,11 +196,13 @@ final class TicketController extends BaseController
             onclick="deleteTicket(' . $ticket->id . ')">删除</button>';
 
             if ($ticket->status !== 'closed') {
-                $ticket->op .= '<button type="button" class="btn btn-orange" id="close-ticket" 
+                $ticket->op .= '
+                <button type="button" class="btn btn-orange" id="close-ticket" 
                 onclick="closeTicket(' . $ticket->id . ')">关闭</button>';
             }
 
-            $ticket->op .= '<a class="btn btn-blue" href="/admin/ticket/' . $ticket->id . '/view">查看</a>';
+            $ticket->op .= '
+            <a class="btn btn-blue" href="/admin/ticket/' . $ticket->id . '/view">查看</a>';
             $ticket->status = $ticket->status();
             $ticket->type = $ticket->type();
             $ticket->datetime = Tools::toDateTime((int) $ticket->datetime);

+ 9 - 3
src/Controllers/User/InvoiceController.php

@@ -65,7 +65,7 @@ final class InvoiceController extends BaseController
             $paylist = Paylist::where('invoice_id', $invoice->id)->where('status', 1)->first();
         }
 
-        $invoice->status_text = Tools::getInvoiceStatus($invoice);
+        $invoice->status_text = $invoice->status();
         $invoice->create_time = Tools::toDateTime($invoice->create_time);
         $invoice->update_time = Tools::toDateTime($invoice->update_time);
         $invoice->pay_time = Tools::toDateTime($invoice->pay_time);
@@ -108,7 +108,13 @@ final class InvoiceController extends BaseController
         $user->money -= $invoice->price;
         $user->save();
 
-        (new UserMoneyLog())->addMoneyLog($user->id, (float) $money_before, (float) $user->money, -$invoice->price, '支付账单 #' . $invoice->id);
+        (new UserMoneyLog())->addMoneyLog(
+            $user->id,
+            (float) $money_before,
+            (float) $user->money,
+            -$invoice->price,
+            '支付账单 #' . $invoice->id
+        );
 
         $invoice->status = 'paid_balance';
         $invoice->update_time = time();
@@ -127,7 +133,7 @@ final class InvoiceController extends BaseController
 
         foreach ($invoices as $invoice) {
             $invoice->op = '<a class="btn btn-blue" href="/user/invoice/' . $invoice->id . '/view">查看</a>';
-            $invoice->status = Tools::getInvoiceStatus($invoice);
+            $invoice->status = $invoice->status();
             $invoice->create_time = Tools::toDateTime($invoice->create_time);
             $invoice->update_time = Tools::toDateTime($invoice->update_time);
             $invoice->pay_time = Tools::toDateTime($invoice->pay_time);

+ 10 - 14
src/Controllers/User/OrderController.php

@@ -86,15 +86,15 @@ final class OrderController extends BaseController
             return $response->withRedirect('/user/order');
         }
 
-        $order->product_type = Tools::getOrderProductType($order);
-        $order->status = Tools::getOrderStatus($order);
+        $order->product_type = $order->productType();
+        $order->status = $order->status();
         $order->create_time = Tools::toDateTime($order->create_time);
         $order->update_time = Tools::toDateTime($order->update_time);
 
         $product_content = json_decode($order->product_content);
 
         $invoice = Invoice::where('order_id', $id)->first();
-        $invoice->status = Tools::getInvoiceStatus($invoice);
+        $invoice->status = $invoice->status();
         $invoice->create_time = Tools::toDateTime($invoice->create_time);
         $invoice->update_time = Tools::toDateTime($invoice->update_time);
         $invoice->pay_time = Tools::toDateTime($invoice->pay_time);
@@ -138,14 +138,7 @@ final class OrderController extends BaseController
         if ($coupon_raw !== '') {
             $coupon = UserCoupon::where('code', $coupon_raw)->first();
 
-            if ($coupon === null) {
-                return $response->withJson([
-                    'ret' => 0,
-                    'msg' => '优惠码无效',
-                ]);
-            }
-
-            if ($coupon->expire_time < time()) {
+            if ($coupon === null || $coupon->expire_time < time()) {
                 return $response->withJson([
                     'ret' => 0,
                     'msg' => '优惠码无效',
@@ -203,7 +196,8 @@ final class OrderController extends BaseController
             ]);
         }
 
-        if ($product_limit->node_group_required !== '' && (int) $user->node_group !== (int) $product_limit->node_group_required) {
+        if ($product_limit->node_group_required !== ''
+             && (int) $user->node_group !== (int) $product_limit->node_group_required) {
             return $response->withJson([
                 'ret' => 0,
                 'msg' => '账户不满足购买条件',
@@ -279,13 +273,15 @@ final class OrderController extends BaseController
 
         foreach ($orders as $order) {
             $order->op = '<a class="btn btn-blue" href="/user/order/' . $order->id . '/view">查看</a>';
+
             if ($order->status === 'pending_payment') {
                 $invoice_id = Invoice::where('order_id', $order->id)->first()->id;
                 $order->op .= '
                 <a class="btn btn-red" href="/user/invoice/' . $invoice_id . '/view">支付</a>';
             }
-            $order->product_type = Tools::getOrderProductType($order);
-            $order->status = Tools::getOrderStatus($order);
+
+            $order->product_type = $order->productType();
+            $order->status = $order->status();
             $order->create_time = Tools::toDateTime($order->create_time);
             $order->update_time = Tools::toDateTime($order->update_time);
         }

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

@@ -24,13 +24,16 @@ final class ServerController extends BaseController
     {
         $user = $this->user;
         $query = Node::query();
-        $query->where('type', 1)->whereNotIn('sort', [9]);
+        $query->where('type', 1);
+
         if (! $user->is_admin) {
             $group = ($user->node_group !== 0 ? [0, $user->node_group] : [0]);
             $query->whereIn('node_group', $group);
         }
+
         $nodes = $query->orderBy('node_class')->orderBy('name')->get();
         $all_node = [];
+
         foreach ($nodes as $node) {
             $array_node = [];
             $array_node['id'] = $node->id;

+ 5 - 3
src/Controllers/User/TicketController.php

@@ -31,7 +31,7 @@ final class TicketController extends BaseController
     public function ticket(ServerRequest $request, Response $response, array $args): ?ResponseInterface
     {
         if (! Setting::obtain('enable_ticket')) {
-            return $response->withStatus(302)->withHeader('Location', '/user');
+            return $response->withRedirect('/user');
         }
 
         $tickets = Ticket::where('userid', $this->user->id)->orderBy('datetime', 'desc')->get();
@@ -163,7 +163,9 @@ final class TicketController extends BaseController
                     $_ENV['appName'] . '-工单被回复',
                     'news/warn.tpl',
                     [
-                        'text' => '管理员,有人回复了<a href="' . $_ENV['baseUrl'] . '/admin/ticket/' . $ticket->id . '/view">工</a>,请您及时处理。',
+                        'text' => '管理员,有人回复了 <a href="' .
+                            $_ENV['baseUrl'] . '/admin/ticket/' . $ticket->id . '/view">#' . $ticket->id .
+                            '</a> 工单,请您及时处理。',
                     ],
                     []
                 );
@@ -202,7 +204,7 @@ final class TicketController extends BaseController
         $ticket = Ticket::where('id', '=', $id)->where('userid', $this->user->id)->first();
 
         if ($ticket === null) {
-            return $response->withStatus(302)->withHeader('Location', '/user/ticket');
+            return $response->withRedirect('/user/ticket');
         }
 
         $comments = json_decode($ticket->content);

+ 8 - 0
src/Models/GiftCard.php

@@ -8,4 +8,12 @@ final class GiftCard extends Model
 {
     protected $connection = 'default';
     protected $table = 'gift_card';
+
+    /**
+     * 礼品卡状态
+     */
+    public function status(): string
+    {
+        return $this->status ? '已使用' : '未使用';
+    }
 }

+ 15 - 0
src/Models/Invoice.php

@@ -8,4 +8,19 @@ final class Invoice extends Model
 {
     protected $connection = 'default';
     protected $table = 'invoice';
+
+    /**
+     * 账单状态
+     */
+    public function status(): string
+    {
+        return match ($this->status) {
+            'unpaid' => '未支付',
+            'paid_gateway' => '已支付(支付网关)',
+            'paid_balance' => '已支付(账户余额)',
+            'paid_admin' => '已支付(管理员)',
+            'cancelled' => '已取消',
+            default => '未知',
+        };
+    }
 }

+ 2 - 36
src/Models/Node.php

@@ -11,12 +11,10 @@ use function time;
 final class Node extends Model
 {
     protected $connection = 'default';
-
     protected $table = 'node';
 
     protected $casts = [
         'traffic_rate' => 'float',
-        'mu_only' => 'int',
         'node_heartbeat' => 'int',
     ];
 
@@ -35,23 +33,9 @@ final class Node extends Model
     {
         return match ($this->sort) {
             0 => 'Shadowsocks',
-            1 => 'ShadowsocksR',
-            11 => 'V2Ray 节点',
+            11 => 'V2Ray',
             14 => 'Trojan',
-            default => '系统保留',
-        };
-    }
-
-    /**
-     * 单端口多用户启用类型
-     */
-    public function muOnly(): string
-    {
-        return match ($this->mu_only) {
-            -1 => '只启用普通端口',
-            0 => '单端口多用户与普通端口并存',
-            1 => '只启用单端口多用户',
-            default => '错误类型',
+            default => '未知',
         };
     }
 
@@ -110,14 +94,6 @@ final class Node extends Model
         return ! ($this->node_bandwidth_limit === 0 || $this->node_bandwidth < $this->node_bandwidth_limit);
     }
 
-    /**
-     * 节点是可用的,即流量未耗尽并且在线
-     */
-    public function isNodeAccessable(): bool
-    {
-        return $this->isNodeTrafficOut() === false && $this->isNodeOnline() === true;
-    }
-
     /**
      * 更新节点 IP
      */
@@ -137,14 +113,4 @@ final class Node extends Model
         }
         $this->node_ip = $ip;
     }
-
-    /**
-     * 获取节点 IP
-     */
-    public function getNodeIp(): string
-    {
-        $node_ip_str = $this->node_ip;
-        $node_ip_array = explode(',', $node_ip_str);
-        return $node_ip_array[0];
-    }
 }

+ 2 - 2
src/Models/OnlineLog.php

@@ -12,7 +12,7 @@ use function substr;
  * @property int    $id         INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY
  * @property int    $user_id    INT UNSIGNED NOT NULL, UNIQUE KEY(A0)
  * @property string $ip         INET6 NOT NULL, UNIQUE KEY(A1) \
- *      Human readable IPv6 address. \
+ *      Human-readable IPv6 address. \
  *      IPv4 Address would be IPv4-mapped IPv6 Address like `::ffff:1.1.1.1`.
  * @property int    $node_id    INT UNSIGNED NOT NULL
  * @property int    $first_time INT UNSIGNED NOT NULL \
@@ -37,7 +37,7 @@ final class OnlineLog extends Model
     public function ip(): string
     {
         $ip = $this->attributes['ip'];
-        if (substr($ip, 0, 7) === '::ffff:') {
+        if (str_starts_with($ip, '::ffff:')) {
             return substr($ip, 6);
         }
         return $ip;

+ 28 - 0
src/Models/Order.php

@@ -8,4 +8,32 @@ final class Order extends Model
 {
     protected $connection = 'default';
     protected $table = 'order';
+
+    /**
+     * 订单状态
+     */
+    public function status(): string
+    {
+        return match ($this->status) {
+            'pending_payment' => '等待支付',
+            'pending_activation' => '等待激活',
+            'activated' => '已激活',
+            'expired' => '已过期',
+            'cancelled' => '已取消',
+            default => '未知',
+        };
+    }
+
+    /**
+     * 订单商品类型
+     */
+    public function productType(): string
+    {
+        return match ($this->product_type) {
+            'tabp' => '时间流量包',
+            'time' => '时间包',
+            'bandwidth' => '流量包',
+            default => '其他',
+        };
+    }
 }

+ 12 - 0
src/Models/Paylist.php

@@ -8,4 +8,16 @@ final class Paylist extends Model
 {
     protected $connection = 'default';
     protected $table = 'paylist';
+
+    /**
+     * 网关记录状态
+     */
+    public function status(): string
+    {
+        return match ($this->status) {
+            0 => '未支付',
+            1 => '已支付',
+            default => '未知',
+        };
+    }
 }

+ 32 - 0
src/Models/Product.php

@@ -8,4 +8,36 @@ final class Product extends Model
 {
     protected $connection = 'default';
     protected $table = 'product';
+
+    /**
+     * 商品状态
+     */
+    public function status(): string
+    {
+        return $this->status ? '正常' : '下架';
+    }
+
+    /**
+     * 商品类型
+     */
+    public function type(): string
+    {
+        return match ($this->type) {
+            'tabp' => '时间流量包',
+            'time' => '时间包',
+            'bandwidth' => '流量包',
+            default => '其他',
+        };
+    }
+
+    /**
+     * 商品库存
+     */
+    public function stock(): string|int
+    {
+        if ($this->stock < 0) {
+            return '无限制';
+        }
+        return $this->stock;
+    }
 }

+ 27 - 0
src/Models/UserCoupon.php

@@ -4,8 +4,35 @@ declare(strict_types=1);
 
 namespace App\Models;
 
+use function json_decode;
+
 final class UserCoupon extends Model
 {
     protected $connection = 'default';
     protected $table = 'user_coupon';
+
+    /**
+     * 优惠码类型
+     */
+    public function type(): string
+    {
+        $content = json_decode($this->content);
+
+        return match ($content->type) {
+            'percentage' => '百分比',
+            'fixed' => '固定金额',
+            default => '未知',
+        };
+    }
+
+    /**
+     * 优惠码状态
+     */
+    public function status(): string
+    {
+        if ($this->expire_time < time()) {
+            return '已过期';
+        }
+        return '激活';
+    }
 }

+ 0 - 167
src/Utils/Tools.php

@@ -303,171 +303,4 @@ final class Tools
         };
         return is_null($number) ? 0.00 : round(floatval($number), 2);
     }
-
-
-    /**
-     * 节点状态
-     */
-    public static function getNodeType($node): string
-    {
-        return $node->type ? '显示' : '隐藏';
-    }
-
-    /**
-     * 节点类型
-     */
-    public static function getNodeSort($node): string
-    {
-        return match ((int) $node->sort) {
-            0 => 'Shadowsocks',
-            9 => 'ShadowsocksR 单端口多用户(旧)',
-            11 => 'V2Ray',
-            14 => 'Trojan',
-            default => '未知',
-        };
-    }
-
-    /**
-     * 礼品卡状态
-     */
-    public static function getGiftCardStatus($giftcard): string
-    {
-        return $giftcard->status ? '已使用' : '未使用';
-    }
-
-    /**
-     * 商品类型
-     */
-    public static function getProductType($product): string
-    {
-        if ($product->type === 'tabp') {
-            return '时间流量包';
-        }
-        if ($product->type === 'time') {
-            return '时间包';
-        }
-        if ($product->type === 'bandwidth') {
-            return '流量包';
-        }
-        return '其他';
-    }
-
-    /**
-     * 商品状态
-     */
-    public static function getProductStatus($product): string
-    {
-        return $product->status ? '正常' : '下架';
-    }
-
-    /**
-     * 商品库存
-     */
-    public static function getProductStock($product)
-    {
-        if ($product->stock === -1) {
-            return '无限制';
-        }
-        return $product->stock;
-    }
-
-    /**
-     * 订单状态
-     */
-    public static function getOrderStatus($order): string
-    {
-        if ($order->status === 'pending_payment') {
-            return '等待支付';
-        }
-        if ($order->status === 'pending_activation') {
-            return '等待激活';
-        }
-        if ($order->status === 'activated') {
-            return '已激活';
-        }
-        if ($order->status === 'expired') {
-            return '已过期';
-        }
-        if ($order->status === 'cancelled') {
-            return '已取消';
-        }
-        return '未知';
-    }
-
-    /**
-     * 订单商品类型
-     */
-    public static function getOrderProductType($order): string
-    {
-        if ($order->product_type === 'tabp') {
-            return '时间流量包';
-        }
-        if ($order->product_type === 'time') {
-            return '时间包';
-        }
-        if ($order->product_type === 'bandwidth') {
-            return '流量包';
-        }
-        return '其他';
-    }
-
-    /**
-     * 账单状态
-     */
-    public static function getInvoiceStatus($invoice): string
-    {
-        if ($invoice->status === 'unpaid') {
-            return '未支付';
-        }
-        if ($invoice->status === 'paid_gateway') {
-            return '已支付(支付网关)';
-        }
-        if ($invoice->status === 'paid_balance') {
-            return '已支付(账户余额)';
-        }
-        if ($invoice->status === 'paid_admin') {
-            return '已支付(管理员)';
-        }
-        if ($invoice->status === 'cancelled') {
-            return '已取消';
-        }
-        return '未知';
-    }
-
-    /**
-     * 优惠码状态
-     */
-    public static function getCouponStatus($coupon): string
-    {
-        if ($coupon->expire_time < time()) {
-            return '已过期';
-        }
-        return '激活';
-    }
-
-    /**
-     * 优惠码类型
-     */
-    public static function getCouponType($content): string
-    {
-        if ($content->type === 'percentage') {
-            return '百分比';
-        }
-        if ($content->type === 'fixed') {
-            return '固定金额';
-        }
-        return '未知';
-    }
-
-    /**
-     * 优惠码类型
-     */
-    public static function getPaylistStatus($paylist): string
-    {
-        return match ($paylist->status) {
-            0 => '未支付',
-            1 => '已支付',
-            default => '未知',
-        };
-    }
 }