Browse Source

first commit

ith5 6 months ago
commit
0c53860d25
100 changed files with 11848 additions and 0 deletions
  1. 8 0
      .gitignore
  2. 21 0
      LICENSE
  3. 70 0
      README.md
  4. 42 0
      app/controller/IndexController.php
  5. 4 0
      app/functions.php
  6. 42 0
      app/middleware/StaticFile.php
  7. 29 0
      app/model/Test.php
  8. 10 0
      app/process/Http.php
  9. 305 0
      app/process/Monitor.php
  10. 57 0
      app/v1/controller/center/CommonController.php
  11. 85 0
      app/v1/controller/center/GameController.php
  12. 45 0
      app/v1/controller/center/GameMainController.php
  13. 124 0
      app/v1/logic/center/GameLogic.php
  14. 65 0
      app/v1/logic/center/GameMainLogic.php
  15. 39 0
      app/v1/model/center/Game.php
  16. 43 0
      app/v1/model/center/GameMain.php
  17. 42 0
      app/v1/validate/center/GameMainValidate.php
  18. 66 0
      app/v1/validate/center/GameValidate.php
  19. 14 0
      app/view/index/view.html
  20. 56 0
      composer.json
  21. 4099 0
      composer.lock
  22. 26 0
      config/app.php
  23. 21 0
      config/autoload.php
  24. 18 0
      config/bootstrap.php
  25. 18 0
      config/cache.php
  26. 15 0
      config/container.php
  27. 15 0
      config/dependence.php
  28. 5 0
      config/event.php
  29. 17 0
      config/exception.php
  30. 32 0
      config/log.php
  31. 13 0
      config/middleware.php
  32. 83 0
      config/plugin/tinywan/jwt/app.php
  33. 76 0
      config/plugin/tinywan/storage/app.php
  34. 4 0
      config/plugin/webman/event/app.php
  35. 17 0
      config/plugin/webman/event/bootstrap.php
  36. 7 0
      config/plugin/webman/event/command.php
  37. 10 0
      config/plugin/webman/push/app.php
  38. 21 0
      config/plugin/webman/push/process.php
  39. 87 0
      config/plugin/webman/push/route.php
  40. 62 0
      config/process.php
  41. 17 0
      config/redis.php
  42. 21 0
      config/route.php
  43. 23 0
      config/server.php
  44. 65 0
      config/session.php
  45. 23 0
      config/static.php
  46. 77 0
      config/think-orm.php
  47. 25 0
      config/translation.php
  48. 22 0
      config/view.php
  49. 115 0
      plugin/saiadmin/app/cache/UserAuthCache.php
  50. 76 0
      plugin/saiadmin/app/cache/UserInfoCache.php
  51. 271 0
      plugin/saiadmin/app/controller/InstallController.php
  52. 60 0
      plugin/saiadmin/app/controller/LoginController.php
  53. 316 0
      plugin/saiadmin/app/controller/SystemController.php
  54. 139 0
      plugin/saiadmin/app/controller/system/DataBaseController.php
  55. 46 0
      plugin/saiadmin/app/controller/system/SystemAttachmentController.php
  56. 95 0
      plugin/saiadmin/app/controller/system/SystemConfigController.php
  57. 107 0
      plugin/saiadmin/app/controller/system/SystemConfigGroupController.php
  58. 151 0
      plugin/saiadmin/app/controller/system/SystemDeptController.php
  59. 100 0
      plugin/saiadmin/app/controller/system/SystemDictDataController.php
  60. 90 0
      plugin/saiadmin/app/controller/system/SystemDictTypeController.php
  61. 94 0
      plugin/saiadmin/app/controller/system/SystemLogController.php
  62. 50 0
      plugin/saiadmin/app/controller/system/SystemMailController.php
  63. 63 0
      plugin/saiadmin/app/controller/system/SystemMenuController.php
  64. 47 0
      plugin/saiadmin/app/controller/system/SystemNoticeController.php
  65. 154 0
      plugin/saiadmin/app/controller/system/SystemPostController.php
  66. 117 0
      plugin/saiadmin/app/controller/system/SystemRoleController.php
  67. 170 0
      plugin/saiadmin/app/controller/system/SystemUserController.php
  68. 133 0
      plugin/saiadmin/app/controller/tool/CrontabController.php
  69. 112 0
      plugin/saiadmin/app/controller/tool/GenerateTablesController.php
  70. 154 0
      plugin/saiadmin/app/event/SystemUser.php
  71. 68 0
      plugin/saiadmin/app/exception/Handler.php
  72. 167 0
      plugin/saiadmin/app/functions.php
  73. 193 0
      plugin/saiadmin/app/logic/system/DatabaseLogic.php
  74. 162 0
      plugin/saiadmin/app/logic/system/SystemAttachmentLogic.php
  75. 56 0
      plugin/saiadmin/app/logic/system/SystemConfigGroupLogic.php
  76. 48 0
      plugin/saiadmin/app/logic/system/SystemConfigLogic.php
  77. 194 0
      plugin/saiadmin/app/logic/system/SystemDeptLogic.php
  78. 28 0
      plugin/saiadmin/app/logic/system/SystemDictDataLogic.php
  79. 67 0
      plugin/saiadmin/app/logic/system/SystemDictTypeLogic.php
  80. 55 0
      plugin/saiadmin/app/logic/system/SystemLoginLogLogic.php
  81. 26 0
      plugin/saiadmin/app/logic/system/SystemMailLogic.php
  82. 201 0
      plugin/saiadmin/app/logic/system/SystemMenuLogic.php
  83. 26 0
      plugin/saiadmin/app/logic/system/SystemNoticeLogic.php
  84. 26 0
      plugin/saiadmin/app/logic/system/SystemOperLogLogic.php
  85. 95 0
      plugin/saiadmin/app/logic/system/SystemPostLogic.php
  86. 239 0
      plugin/saiadmin/app/logic/system/SystemRoleLogic.php
  87. 256 0
      plugin/saiadmin/app/logic/system/SystemUserLogic.php
  88. 25 0
      plugin/saiadmin/app/logic/tool/CrontabLogLogic.php
  89. 185 0
      plugin/saiadmin/app/logic/tool/CrontabLogic.php
  90. 186 0
      plugin/saiadmin/app/logic/tool/GenerateColumnsLogic.php
  91. 452 0
      plugin/saiadmin/app/logic/tool/GenerateTablesLogic.php
  92. 82 0
      plugin/saiadmin/app/middleware/CheckAuth.php
  93. 39 0
      plugin/saiadmin/app/middleware/CheckLogin.php
  94. 33 0
      plugin/saiadmin/app/middleware/CrossDomain.php
  95. 42 0
      plugin/saiadmin/app/middleware/SystemLog.php
  96. 34 0
      plugin/saiadmin/app/model/system/SystemAttachment.php
  97. 29 0
      plugin/saiadmin/app/model/system/SystemConfig.php
  98. 29 0
      plugin/saiadmin/app/model/system/SystemConfigGroup.php
  99. 42 0
      plugin/saiadmin/app/model/system/SystemDept.php
  100. 17 0
      plugin/saiadmin/app/model/system/SystemDeptLeader.php

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+/runtime
+/.idea
+/.vscode
+/vendor
+*.log
+.env
+/tests/tmp
+/tests/.phpunit.result.cache

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 70 - 0
README.md

@@ -0,0 +1,70 @@
+<div style="padding:18px;max-width: 1024px;margin:0 auto;background-color:#fff;color:#333">
+<h1>webman</h1>
+
+基于<a href="https://www.workerman.net" target="__blank">workerman</a>开发的超高性能PHP框架
+
+
+<h1>学习</h1>
+
+<ul>
+  <li>
+    <a href="https://www.workerman.net/webman" target="__blank">主页 / Home page</a>
+  </li>
+  <li>
+    <a href="https://webman.workerman.net" target="__blank">文档 / Document</a>
+  </li>
+  <li>
+    <a href="https://www.workerman.net/doc/webman/install.html" target="__blank">安装 / Install</a>
+  </li>
+  <li>
+    <a href="https://www.workerman.net/questions" target="__blank">问答 / Questions</a>
+  </li>
+  <li>
+    <a href="https://www.workerman.net/apps" target="__blank">市场 / Apps</a>
+  </li>
+  <li>
+    <a href="https://www.workerman.net/sponsor" target="__blank">赞助 / Sponsors</a>
+  </li>
+  <li>
+    <a href="https://www.workerman.net/doc/webman/thanks.html" target="__blank">致谢 / Thanks</a>
+  </li>
+</ul>
+
+<div style="float:left;padding-bottom:30px;">
+
+  <h1>赞助商</h1>
+
+  <h4>特别赞助</h4>
+  <a href="https://www.crmeb.com/?form=workerman" target="__blank">
+    <img src="https://www.workerman.net/img/sponsors/6429/20230719111500.svg" width="200">
+  </a>
+
+  <h4>铂金赞助</h4>
+  <a href="https://www.fadetask.com/?from=workerman" target="__blank"><img src="https://www.workerman.net/img/sponsors/1/20230719084316.png" width="200"></a>
+  <a href="https://www.yilianyun.net/?from=workerman" target="__blank" style="margin-left:20px;"><img src="https://www.workerman.net/img/sponsors/6218/20230720114049.png" width="200"></a>
+
+
+</div>
+
+
+<div style="float:left;padding-bottom:30px;clear:both">
+
+  <h1>请作者喝咖啡</h1>
+
+<img src="https://www.workerman.net/img/wx_donate.png" width="200">
+<img src="https://www.workerman.net/img/ali_donate.png" width="200">
+<br>
+<b>如果您觉得webman对您有所帮助,欢迎捐赠。</b>
+
+
+</div>
+
+
+<div style="clear: both">
+<h1>LICENSE</h1>
+The webman is open-sourced software licensed under the MIT.
+</div>
+
+</div>
+
+

+ 42 - 0
app/controller/IndexController.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace app\controller;
+
+use support\Request;
+
+class IndexController
+{
+    public function index(Request $request)
+    {
+        return <<<EOF
+<style>
+  * {
+    padding: 0;
+    margin: 0;
+  }
+  iframe {
+    border: none;
+    overflow: scroll;
+  }
+</style>
+<iframe
+  src="https://www.workerman.net/wellcome"
+  width="100%"
+  height="100%"
+  allow="clipboard-write"
+  sandbox="allow-scripts allow-same-origin allow-popups"
+></iframe>
+EOF;
+    }
+
+    public function view(Request $request)
+    {
+        return view('index/view', ['name' => 'webman']);
+    }
+
+    public function json(Request $request)
+    {
+        return json(['code' => 0, 'msg' => 'ok']);
+    }
+
+}

+ 4 - 0
app/functions.php

@@ -0,0 +1,4 @@
+<?php
+/**
+ * Here is your custom functions.
+ */

+ 42 - 0
app/middleware/StaticFile.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace app\middleware;
+
+use Webman\MiddlewareInterface;
+use Webman\Http\Response;
+use Webman\Http\Request;
+
+/**
+ * Class StaticFile
+ * @package app\middleware
+ */
+class StaticFile implements MiddlewareInterface
+{
+    public function process(Request $request, callable $handler): Response
+    {
+        // Access to files beginning with. Is prohibited
+        if (strpos($request->path(), '/.') !== false) {
+            return response('<h1>403 forbidden</h1>', 403);
+        }
+        /** @var Response $response */
+        $response = $handler($request);
+        // Add cross domain HTTP header
+        /*$response->withHeaders([
+            'Access-Control-Allow-Origin'      => '*',
+            'Access-Control-Allow-Credentials' => 'true',
+        ]);*/
+        return $response;
+    }
+}

+ 29 - 0
app/model/Test.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace app\model;
+
+use support\Model;
+
+class Test extends Model
+{
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected $table = 'test';
+
+    /**
+     * The primary key associated with the table.
+     *
+     * @var string
+     */
+    protected $primaryKey = 'id';
+
+    /**
+     * Indicates if the model should be timestamped.
+     *
+     * @var bool
+     */
+    public $timestamps = false;
+}

+ 10 - 0
app/process/Http.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace app\process;
+
+use Webman\App;
+
+class Http extends App
+{
+
+}

+ 305 - 0
app/process/Monitor.php

@@ -0,0 +1,305 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace app\process;
+
+use FilesystemIterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use SplFileInfo;
+use Workerman\Timer;
+use Workerman\Worker;
+
+/**
+ * Class FileMonitor
+ * @package process
+ */
+class Monitor
+{
+    /**
+     * @var array
+     */
+    protected array $paths = [];
+
+    /**
+     * @var array
+     */
+    protected array $extensions = [];
+
+    /**
+     * @var array
+     */
+    protected array $loadedFiles = [];
+
+    /**
+     * @var int
+     */
+    protected int $ppid = 0;
+
+    /**
+     * Pause monitor
+     * @return void
+     */
+    public static function pause(): void
+    {
+        file_put_contents(static::lockFile(), time());
+    }
+
+    /**
+     * Resume monitor
+     * @return void
+     */
+    public static function resume(): void
+    {
+        clearstatcache();
+        if (is_file(static::lockFile())) {
+            unlink(static::lockFile());
+        }
+    }
+
+    /**
+     * Whether monitor is paused
+     * @return bool
+     */
+    public static function isPaused(): bool
+    {
+        clearstatcache();
+        return file_exists(static::lockFile());
+    }
+
+    /**
+     * Lock file
+     * @return string
+     */
+    protected static function lockFile(): string
+    {
+        return runtime_path('monitor.lock');
+    }
+
+    /**
+     * FileMonitor constructor.
+     * @param $monitorDir
+     * @param $monitorExtensions
+     * @param array $options
+     */
+    public function __construct($monitorDir, $monitorExtensions, array $options = [])
+    {
+        $this->ppid = function_exists('posix_getppid') ? posix_getppid() : 0;
+        static::resume();
+        $this->paths = (array)$monitorDir;
+        $this->extensions = $monitorExtensions;
+        foreach (get_included_files() as $index => $file) {
+            $this->loadedFiles[$file] = $index;
+            if (strpos($file, 'webman-framework/src/support/App.php')) {
+                break;
+            }
+        }
+        if (!Worker::getAllWorkers()) {
+            return;
+        }
+        $disableFunctions = explode(',', ini_get('disable_functions'));
+        if (in_array('exec', $disableFunctions, true)) {
+            echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n";
+        } else {
+            if ($options['enable_file_monitor'] ?? true) {
+                Timer::add(1, function () {
+                    $this->checkAllFilesChange();
+                });
+            }
+        }
+
+        $memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null);
+        if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) {
+            Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]);
+        }
+    }
+
+    /**
+     * @param $monitorDir
+     * @return bool
+     */
+    public function checkFilesChange($monitorDir): bool
+    {
+        static $lastMtime, $tooManyFilesCheck;
+        if (!$lastMtime) {
+            $lastMtime = time();
+        }
+        clearstatcache();
+        if (!is_dir($monitorDir)) {
+            if (!is_file($monitorDir)) {
+                return false;
+            }
+            $iterator = [new SplFileInfo($monitorDir)];
+        } else {
+            // recursive traversal directory
+            $dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS);
+            $iterator = new RecursiveIteratorIterator($dirIterator);
+        }
+        $count = 0;
+        foreach ($iterator as $file) {
+            $count ++;
+            /** var SplFileInfo $file */
+            if (is_dir($file->getRealPath())) {
+                continue;
+            }
+            // check mtime
+            if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) {
+                $lastMtime = $file->getMTime();
+                if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) {
+                    echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n";
+                    continue;
+                }
+                $var = 0;
+                exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var);
+                if ($var) {
+                    continue;
+                }
+                // send SIGUSR1 signal to master process for reload
+                if (DIRECTORY_SEPARATOR === '/') {
+                    if ($masterPid = $this->getMasterPid()) {
+                        echo $file . " updated and reload\n";
+                        posix_kill($masterPid, SIGUSR1);
+                    } else {
+                        echo "Master process has gone away and can not reload\n";
+                    }
+                    return true;
+                }
+                echo $file . " updated and reload\n";
+                return true;
+            }
+        }
+        if (!$tooManyFilesCheck && $count > 1000) {
+            echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n";
+            $tooManyFilesCheck = 1;
+        }
+        return false;
+    }
+
+    /**
+     * @return int
+     */
+    public function getMasterPid(): int
+    {
+        if ($this->ppid === 0) {
+            return 0;
+        }
+        if (function_exists('posix_kill') && !posix_kill($this->ppid, 0)) {
+            echo "Master process has gone away\n";
+            return $this->ppid = 0;
+        }
+        if (PHP_OS_FAMILY !== 'Linux') {
+            return $this->ppid;
+        }
+        $cmdline = "/proc/$this->ppid/cmdline";
+        if (!is_readable($cmdline) || !($content = file_get_contents($cmdline)) || (!str_contains($content, 'WorkerMan') && !str_contains($content, 'php'))) {
+            // Process not exist
+            $this->ppid = 0;
+        }
+        return $this->ppid;
+    }
+
+    /**
+     * @return bool
+     */
+    public function checkAllFilesChange(): bool
+    {
+        if (static::isPaused()) {
+            return false;
+        }
+        foreach ($this->paths as $path) {
+            if ($this->checkFilesChange($path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param $memoryLimit
+     * @return void
+     */
+    public function checkMemory($memoryLimit): void
+    {
+        if (static::isPaused() || $memoryLimit <= 0) {
+            return;
+        }
+        $masterPid = $this->getMasterPid();
+        if ($masterPid <= 0) {
+            echo "Master process has gone away\n";
+            return;
+        }
+
+        $childrenFile = "/proc/$masterPid/task/$masterPid/children";
+        if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) {
+            return;
+        }
+        foreach (explode(' ', $children) as $pid) {
+            $pid = (int)$pid;
+            $statusFile = "/proc/$pid/status";
+            if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) {
+                continue;
+            }
+            $mem = 0;
+            if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
+                $mem = $match[1];
+            }
+            $mem = (int)($mem / 1024);
+            if ($mem >= $memoryLimit) {
+                posix_kill($pid, SIGINT);
+            }
+        }
+    }
+
+    /**
+     * Get memory limit
+     * @param $memoryLimit
+     * @return int
+     */
+    protected function getMemoryLimit($memoryLimit): int
+    {
+        if ($memoryLimit === 0) {
+            return 0;
+        }
+        $usePhpIni = false;
+        if (!$memoryLimit) {
+            $memoryLimit = ini_get('memory_limit');
+            $usePhpIni = true;
+        }
+
+        if ($memoryLimit == -1) {
+            return 0;
+        }
+        $unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]);
+        $memoryLimit = (int)$memoryLimit;
+        if ($unit === 'g') {
+            $memoryLimit = 1024 * $memoryLimit;
+        } else if ($unit === 'k') {
+            $memoryLimit = ($memoryLimit / 1024);
+        } else if ($unit === 'm') {
+            $memoryLimit = (int)($memoryLimit);
+        } else if ($unit === 't') {
+            $memoryLimit = (1024 * 1024 * $memoryLimit);
+        } else {
+            $memoryLimit = ($memoryLimit / (1024 * 1024));
+        }
+        if ($memoryLimit < 50) {
+            $memoryLimit = 50;
+        }
+        if ($usePhpIni) {
+            $memoryLimit = (0.8 * $memoryLimit);
+        }
+        return (int)$memoryLimit;
+    }
+
+}

+ 57 - 0
app/v1/controller/center/CommonController.php

@@ -0,0 +1,57 @@
+<?php
+namespace app\v1\controller\center;
+
+use app\v1\logic\center\GameLogic;
+use plugin\saiadmin\basic\BaseController;
+use app\v1\logic\center\GameMainLogic;
+use support\Request;
+use support\Response;
+
+/**
+ * 公共接口管理控制器
+ */
+class CommonController extends BaseController
+{
+    protected $gameMainLogic;
+    protected $gameLogic;
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        parent::__construct();
+        $this->gameMainLogic = new GameMainLogic();
+        $this->gameLogic = new GameLogic();
+    }
+    
+    /**
+     * 获取主游戏options列表
+     * @return Response
+     */
+    public function getMainGameOptions(Request $request): Response
+    {
+        $data = $this->gameMainLogic->getMainGameOptions();
+        return $this->success($data);
+    }
+
+    /**
+     * 获取子游戏options列表
+     * @return Response
+     */
+    public function getGameOptions(Request $request): Response
+    {
+        $data = $this->gameLogic->getGameOptions();
+        return $this->success($data);
+    }
+
+    /**
+     * 获取树形游戏options列表
+     * @return Response
+     */
+    public function getTreeGameOptions(Request $request): Response
+    {
+        $data = $this->gameMainLogic->getTreeGameOptions();
+        return $this->success($data);
+    }
+}

+ 85 - 0
app/v1/controller/center/GameController.php

@@ -0,0 +1,85 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\controller\center;
+
+use plugin\saiadmin\basic\BaseController;
+use app\v1\logic\center\GameLogic;
+use app\v1\validate\center\GameValidate;
+use plugin\saiadmin\app\logic\system\SystemDeptLogic;
+use plugin\saiadmin\exception\ApiException;
+use support\Request;
+use support\Response;
+
+/**
+ * 游戏列表控制器
+ */
+class GameController extends BaseController
+{
+
+  protected $systemDeptLogic;
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->logic = new GameLogic();
+        $this->validate = new GameValidate;
+        $this->systemDeptLogic = new SystemDeptLogic();
+
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request): Response
+    {
+        $where = $request->more([
+            ['id', ''],
+            ['main_id', ''],
+            ['name', ''],
+            ['os', ''],
+            ['status', ''],
+        ]);
+        $list = $this->logic->getIndex($where);
+       
+        return $this->success($list);
+    }
+
+    /**
+     * 获取所有的游戏数据
+     */
+    public function getAllGameData(Request $request): Response
+    {
+        $list = $this->logic->getAllGameData();
+        return $this->success($list);
+    }
+
+    /**
+     * 根据部门ID获取游戏列表
+     */
+    public function getGameListByDeptId(Request $request): Response
+    {
+        $dept_id = $request->get('dept_id');
+        $game_list = $this->logic->getGameListByDeptId($dept_id);
+        return $this->success($game_list);
+    }
+
+    /**
+     * 设置部门游戏权限
+     */
+    public function setGameListByDeptId(Request $request): Response
+    {
+        $game_list = $request->post('game_list');
+        $dept_id = $request->post('dept_id');
+        $this->logic->setGameListByDeptId($dept_id, $game_list);
+        return $this->success();
+    }
+
+}

+ 45 - 0
app/v1/controller/center/GameMainController.php

@@ -0,0 +1,45 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\controller\center;
+
+use plugin\saiadmin\basic\BaseController;
+use app\v1\logic\center\GameMainLogic;
+use app\v1\validate\center\GameMainValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 主包管理控制器
+ */
+class GameMainController extends BaseController
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->logic = new GameMainLogic();
+        $this->validate = new GameMainValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request): Response
+    {
+        $where = $request->more([
+            ['name', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+}

+ 124 - 0
app/v1/logic/center/GameLogic.php

@@ -0,0 +1,124 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\logic\center;
+
+use plugin\saiadmin\basic\BaseLogic;
+use app\v1\model\center\Game;
+use plugin\saiadmin\app\logic\system\SystemDeptLogic;
+
+/**
+ * 游戏列表逻辑层
+ */
+class GameLogic extends BaseLogic
+{
+
+    protected $gameMainLogic;
+    protected $systemDeptLogic;
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new Game();
+        $this->gameMainLogic = new GameMainLogic();
+        $this->systemDeptLogic = new SystemDeptLogic();
+    }
+
+    /**
+     * 获取游戏列表
+     * @param array $where
+     * @return array
+     */
+    public function getIndex($where)
+    {
+        $query = $this->search($where);
+        $query->order('sort', 'desc');
+
+        $data = $this->getList($query);
+
+        foreach ($data['data'] as $key => $value) {
+            $data['data'][$key]['main_game_name'] = $this->gameMainLogic->getMainGameNameById($value['main_id']);
+        }
+        return $data;
+    }
+
+    /**
+     * 获取游戏options列表
+     * @return array
+     */
+    public function getGameOptions()
+    {
+        $list = $this->model->field('id,name')->where('status', 1)->order('sort', 'desc')->select()->toArray();
+        $list = array_map(function ($item) {
+            return [
+                'label' => $item['name'],
+                'value' => $item['id']
+            ];
+        }, $list);
+        return $list;
+    }
+
+    /**
+     * 处理游戏tree数据
+     * @return array
+     */
+    public function getGameTree($game_list)
+    {
+         $mainGameMap = $this->gameMainLogic->getMainGameMap();
+        
+         $groupedGames = [];
+         foreach ($game_list as $game) {
+            $mainId = $game['main_id'];
+            $groupedGames[$mainId]['id'] = $mainId;
+            $groupedGames[$mainId]['name'] = $mainGameMap[$mainId] ?? $mainId;
+            $groupedGames[$mainId]['children'][] = [
+                'id' => $game['id'],
+                'name' => $game['name']
+            ];
+         }
+        $game_list = array_values($groupedGames);
+        return $game_list;
+    }
+
+    /**
+     * 获取所有的游戏数据
+     */
+    public function getAllGameData()
+    {
+        $game_list = $this->model->where('status', 1)->order('sort', 'desc')->select()->toArray();
+        return $this->getGameTree($game_list);
+    }
+
+    /**
+     * 根据部门ID获取游戏列表
+     * @param array $where
+     * @return array
+     */
+    public function getGameListByDeptId($dept_id)
+    {
+        $game_ids = $this->systemDeptLogic->getGameListByDeptId($dept_id);
+        // if($game_ids==='*'){
+        //     $game_list = $this->model->where('status', 1)->order('sort', 'desc')->select()->toArray();
+        // }else{
+        //     $game_list = $this->model->where('id', 'in', $game_ids)->where('status', 1)->order('sort', 'desc')->select()->toArray();
+        // }
+        return $game_ids;
+
+    }
+
+    /**
+     * 设置部门游戏权限
+     * @param array $where
+     * @param array $game_list
+     * @return void
+     */
+    public function setGameListByDeptId($dept_id, $game_list)
+    {
+        $this->systemDeptLogic->setGameListByDeptId($dept_id, $game_list);
+    }
+}

+ 65 - 0
app/v1/logic/center/GameMainLogic.php

@@ -0,0 +1,65 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\logic\center;
+
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+
+use app\v1\model\center\GameMain;
+use plugin\saiadmin\app\model\system\SystemUser;
+
+/**
+ * 主包管理逻辑层
+ */
+class GameMainLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new GameMain();
+    }
+
+
+    /**
+     * 获取主游戏options列表
+     * @param int $userId
+     * @return array
+     */
+    public function getMainGameOptions()
+    {
+        $list = $this->model->field('id,name')->where('status', 1)->order('sort', 'desc')->select()->toArray();
+        $list = array_map(function ($item) {
+            return [
+                'label' => $item['name'],
+                'value' => $item['id']
+            ];
+        }, $list);
+        return $list;
+    }
+
+    /**
+     * 根据主包ID获取主包名称
+     * @param int $mainId
+     * @return string
+     */
+    public function getMainGameNameById($mainId)
+    {
+        return $this->model->where('id', $mainId)->value('name');
+    }
+
+    /**
+     * 获取主包map
+     * @return array
+     */
+    public function getMainGameMap()
+    {
+        return $this->model->column('name', 'id');
+    }
+
+}

+ 39 - 0
app/v1/model/center/Game.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\model\center;
+
+use plugin\saiadmin\basic\BaseModel;
+
+/**
+ * 游戏列表模型
+ */
+class Game extends BaseModel
+{
+
+    protected $connection = 'yy_center_db';
+
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    /**
+     * 数据库表名称
+     * @var string
+     */
+    protected $table = 'yy_game';
+
+    /**
+     * 游戏名称 搜索
+     */
+    public function searchNameAttr($query, $value)
+    {
+        $query->where('name', 'like', '%'.$value.'%');
+    }
+
+}

+ 43 - 0
app/v1/model/center/GameMain.php

@@ -0,0 +1,43 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\model\center;
+
+use plugin\saiadmin\basic\BaseModel;
+
+/**
+ * 主包管理模型
+ */
+class GameMain extends BaseModel
+{
+
+    /**
+     * 数据库连接
+     * @var string
+     */
+    protected $connection = 'yy_center_db';
+
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    /**
+     * 数据库表名称
+     * @var string
+     */
+    protected $table = 'yy_main';
+
+    /**
+     * 名称 搜索
+     */
+    public function searchNameAttr($query, $value)
+    {
+        $query->where('name', 'like', '%'.$value.'%');
+    }
+
+}

+ 42 - 0
app/v1/validate/center/GameMainValidate.php

@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\validate\center;
+
+use think\Validate;
+
+/**
+ * 主包管理验证器
+ */
+class GameMainValidate extends Validate
+{
+    /**
+     * 定义验证规则
+     */
+    protected $rule =   [
+        'name' => 'require',
+    ];
+
+    /**
+     * 定义错误信息
+     */
+    protected $message  =   [
+        'name' => '名称必须填写',
+    ];
+
+    /**
+     * 定义场景
+     */
+    protected $scene = [
+        'save' => [
+            'name',
+        ],
+        'update' => [
+            'name',
+        ],
+    ];
+
+}

+ 66 - 0
app/v1/validate/center/GameValidate.php

@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: your name
+// +----------------------------------------------------------------------
+namespace app\v1\validate\center;
+
+use think\Validate;
+
+/**
+ * 游戏列表验证器
+ */
+class GameValidate extends Validate
+{
+    /**
+     * 定义验证规则
+     */
+    protected $rule =   [
+        'main_id' => 'require',
+        'name' => 'require',
+        'os' => 'require',
+        'divide' => 'require',
+        'cp_callback_type' => 'require',
+        'online_version' => 'require',
+        'package_name' => 'require',
+    ];
+
+    /**
+     * 定义错误信息
+     */
+    protected $message  =   [
+        'main_id' => '主包归属ID必须填写',
+        'name' => '游戏名称必须填写',
+        'os' => '平台必须选择',
+        'divide' => '分成比例必须填写',
+        'cp_callback_type' => '游戏发货规则必需选择',
+        'online_version' => '线上版本必需填写',
+        'package_name' => '包名必需填写',
+    ];
+
+    /**
+     * 定义场景
+     */
+    protected $scene = [
+        'save' => [
+            'main_id',
+            'name',
+            'os',
+            'divide',
+            'cp_callback_type',
+            'online_version',
+            'package_name',
+        ],
+        'update' => [
+            'main_id',
+            'name',
+            'os',
+            'divide',
+            'cp_callback_type',
+            'online_version',
+            'package_name',
+        ],
+    ];
+
+}

+ 14 - 0
app/view/index/view.html

@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="shortcut icon" href="/favicon.ico"/>
+    <title>webman</title>
+
+</head>
+<body>
+hello <?=htmlspecialchars($name)?>
+</body>
+</html>

+ 56 - 0
composer.json

@@ -0,0 +1,56 @@
+{
+  "name": "workerman/webman",
+  "type": "project",
+  "keywords": [
+    "high performance",
+    "http service"
+  ],
+  "homepage": "https://www.workerman.net",
+  "license": "MIT",
+  "description": "High performance HTTP Service Framework.",
+  "authors": [
+    {
+      "name": "walkor",
+      "email": "walkor@workerman.net",
+      "homepage": "https://www.workerman.net",
+      "role": "Developer"
+    }
+  ],
+  "support": {
+    "email": "walkor@workerman.net",
+    "issues": "https://github.com/walkor/webman/issues",
+    "forum": "https://wenda.workerman.net/",
+    "wiki": "https://workerman.net/doc/webman",
+    "source": "https://github.com/walkor/webman"
+  },
+  "require": {
+    "php": ">=8.1",
+    "workerman/webman-framework": "^2.1",
+    "monolog/monolog": "^2.0",
+    "saithink/saiadmin": "^5.0"
+  },
+  "suggest": {
+    "ext-event": "For better performance. "
+  },
+  "autoload": {
+    "psr-4": {
+      "": "./",
+      "app\\": "./app",
+      "App\\": "./app",
+      "app\\View\\Components\\": "./app/view/components"
+    }
+  },
+  "scripts": {
+    "post-package-install": [
+      "support\\Plugin::install"
+    ],
+    "post-package-update": [
+      "support\\Plugin::install"
+    ],
+    "pre-package-uninstall": [
+      "support\\Plugin::uninstall"
+    ]
+  },
+  "minimum-stability": "dev",
+  "prefer-stable": true
+}

+ 4099 - 0
composer.lock

@@ -0,0 +1,4099 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "fd85cf0ed128b514bffca0bc7c4da4d6",
+    "packages": [
+        {
+            "name": "brick/math",
+            "version": "0.13.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/brick/math.git",
+                "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
+                "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.2",
+                "phpunit/phpunit": "^10.1",
+                "vimeo/psalm": "6.8.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Brick\\Math\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Arbitrary-precision arithmetic library",
+            "keywords": [
+                "Arbitrary-precision",
+                "BigInteger",
+                "BigRational",
+                "arithmetic",
+                "bigdecimal",
+                "bignum",
+                "bignumber",
+                "brick",
+                "decimal",
+                "integer",
+                "math",
+                "mathematics",
+                "rational"
+            ],
+            "support": {
+                "issues": "https://github.com/brick/math/issues",
+                "source": "https://github.com/brick/math/tree/0.13.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/BenMorel",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-03-29T13:50:30+00:00"
+        },
+        {
+            "name": "carbonphp/carbon-doctrine-types",
+            "version": "3.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
+                "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+                "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "conflict": {
+                "doctrine/dbal": "<4.0.0 || >=5.0.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^4.0.0",
+                "nesbot/carbon": "^2.71.0 || ^3.0.0",
+                "phpunit/phpunit": "^10.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "KyleKatarn",
+                    "email": "kylekatarnls@gmail.com"
+                }
+            ],
+            "description": "Types to use Carbon in Doctrine",
+            "keywords": [
+                "carbon",
+                "date",
+                "datetime",
+                "doctrine",
+                "time"
+            ],
+            "support": {
+                "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
+                "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/kylekatarnls",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/Carbon",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-02-09T16:56:22+00:00"
+        },
+        {
+            "name": "doctrine/inflector",
+            "version": "2.0.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/inflector.git",
+                "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc",
+                "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^11.0",
+                "phpstan/phpstan": "^1.8",
+                "phpstan/phpstan-phpunit": "^1.1",
+                "phpstan/phpstan-strict-rules": "^1.3",
+                "phpunit/phpunit": "^8.5 || ^9.5",
+                "vimeo/psalm": "^4.25 || ^5.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Inflector\\": "lib/Doctrine/Inflector"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Benjamin Eberlei",
+                    "email": "kontakt@beberlei.de"
+                },
+                {
+                    "name": "Jonathan Wage",
+                    "email": "jonwage@gmail.com"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
+            "homepage": "https://www.doctrine-project.org/projects/inflector.html",
+            "keywords": [
+                "inflection",
+                "inflector",
+                "lowercase",
+                "manipulation",
+                "php",
+                "plural",
+                "singular",
+                "strings",
+                "uppercase",
+                "words"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/inflector/issues",
+                "source": "https://github.com/doctrine/inflector/tree/2.0.10"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-02-18T20:23:39+00:00"
+        },
+        {
+            "name": "firebase/php-jwt",
+            "version": "v6.11.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/firebase/php-jwt.git",
+                "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
+                "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.4",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^2.0||^3.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/firebase/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/firebase/php-jwt/issues",
+                "source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
+            },
+            "time": "2025-04-09T20:32:01+00:00"
+        },
+        {
+            "name": "godruoyi/php-snowflake",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/godruoyi/php-snowflake.git",
+                "reference": "b82b313f353e4cf3198b9e6286cd1b5e023e8626"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/godruoyi/php-snowflake/zipball/b82b313f353e4cf3198b9e6286cd1b5e023e8626",
+                "reference": "b82b313f353e4cf3198b9e6286cd1b5e023e8626",
+                "shasum": ""
+            },
+            "require": {
+                "php-64bit": ">=8.1"
+            },
+            "require-dev": {
+                "ext-redis": "*",
+                "ext-swoole": "*",
+                "illuminate/contracts": "^10.0 || ^11.0",
+                "laravel/pint": "^1.10",
+                "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^10",
+                "predis/predis": "^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Godruoyi\\Snowflake\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Godruoyi",
+                    "email": "g@godruoyi.com"
+                }
+            ],
+            "description": "An ID Generator for PHP based on Snowflake Algorithm (Twitter announced).",
+            "homepage": "https://github.com/godruoyi/php-snowflake",
+            "keywords": [
+                "Unique ID",
+                "laravel snowflake",
+                "order id",
+                "php snowflake",
+                "php sonyflake",
+                "php unique id",
+                "snowflake algorithm",
+                "sonyflake",
+                "unique order id"
+            ],
+            "support": {
+                "issues": "https://github.com/godruoyi/php-snowflake/issues",
+                "source": "https://github.com/godruoyi/php-snowflake/tree/3.2.1"
+            },
+            "funding": [
+                {
+                    "url": "https://images.godruoyi.com/wechat.png",
+                    "type": "custom"
+                }
+            ],
+            "time": "2025-05-01T02:35:04+00:00"
+        },
+        {
+            "name": "graham-campbell/result-type",
+            "version": "v1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/GrahamCampbell/Result-Type.git",
+                "reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
+                "reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GrahamCampbell\\ResultType\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "An Implementation Of The Result Type",
+            "keywords": [
+                "Graham Campbell",
+                "GrahamCampbell",
+                "Result Type",
+                "Result-Type",
+                "result"
+            ],
+            "support": {
+                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-20T21:45:45+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
+                "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+                "guzzlehttp/psr7": "^2.7.0",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.2 || ^3.0"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-curl": "*",
+                "guzzle/client-integration-tests": "3.0.2",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.9.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-03-27T13:37:11+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
+                "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/2.2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-03-27T13:27:01+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+                "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "http-interop/http-factory-tests": "0.9.0",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/2.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-03-27T12:30:47+00:00"
+        },
+        {
+            "name": "illuminate/collections",
+            "version": "v10.48.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/illuminate/collections.git",
+                "reference": "48de3d6bc6aa779112ddcb608a3a96fc975d89d8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/illuminate/collections/zipball/48de3d6bc6aa779112ddcb608a3a96fc975d89d8",
+                "reference": "48de3d6bc6aa779112ddcb608a3a96fc975d89d8",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/conditionable": "^10.0",
+                "illuminate/contracts": "^10.0",
+                "illuminate/macroable": "^10.0",
+                "php": "^8.1"
+            },
+            "suggest": {
+                "symfony/var-dumper": "Required to use the dump method (^6.2)."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "10.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "helpers.php"
+                ],
+                "psr-4": {
+                    "Illuminate\\Support\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Illuminate Collections package.",
+            "homepage": "https://laravel.com",
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2024-11-21T14:02:44+00:00"
+        },
+        {
+            "name": "illuminate/conditionable",
+            "version": "v10.48.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/illuminate/conditionable.git",
+                "reference": "3ee34ac306fafc2a6f19cd7cd68c9af389e432a5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/illuminate/conditionable/zipball/3ee34ac306fafc2a6f19cd7cd68c9af389e432a5",
+                "reference": "3ee34ac306fafc2a6f19cd7cd68c9af389e432a5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "10.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Illuminate\\Support\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Illuminate Conditionable package.",
+            "homepage": "https://laravel.com",
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2024-11-21T14:02:44+00:00"
+        },
+        {
+            "name": "illuminate/contracts",
+            "version": "v10.48.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/illuminate/contracts.git",
+                "reference": "f90663a69f926105a70b78060a31f3c64e2d1c74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/illuminate/contracts/zipball/f90663a69f926105a70b78060a31f3c64e2d1c74",
+                "reference": "f90663a69f926105a70b78060a31f3c64e2d1c74",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1",
+                "psr/container": "^1.1.1|^2.0.1",
+                "psr/simple-cache": "^1.0|^2.0|^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "10.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Illuminate\\Contracts\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Illuminate Contracts package.",
+            "homepage": "https://laravel.com",
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2024-11-21T14:02:44+00:00"
+        },
+        {
+            "name": "illuminate/macroable",
+            "version": "v10.48.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/illuminate/macroable.git",
+                "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27",
+                "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "10.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Illuminate\\Support\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Illuminate Macroable package.",
+            "homepage": "https://laravel.com",
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2023-06-05T12:46:42+00:00"
+        },
+        {
+            "name": "illuminate/redis",
+            "version": "v10.48.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/illuminate/redis.git",
+                "reference": "446d36aeb21fd2b6719293a8d930ae9ac8135be0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/illuminate/redis/zipball/446d36aeb21fd2b6719293a8d930ae9ac8135be0",
+                "reference": "446d36aeb21fd2b6719293a8d930ae9ac8135be0",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/collections": "^10.0",
+                "illuminate/contracts": "^10.0",
+                "illuminate/macroable": "^10.0",
+                "illuminate/support": "^10.0",
+                "php": "^8.1"
+            },
+            "suggest": {
+                "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).",
+                "predis/predis": "Required to use the predis connector (^2.0.2)."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "10.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Illuminate\\Redis\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Illuminate Redis package.",
+            "homepage": "https://laravel.com",
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2025-01-21T16:06:22+00:00"
+        },
+        {
+            "name": "illuminate/support",
+            "version": "v10.48.28",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/illuminate/support.git",
+                "reference": "6d09b480d34846245d9288f4dcefb17a73ce6e6a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/illuminate/support/zipball/6d09b480d34846245d9288f4dcefb17a73ce6e6a",
+                "reference": "6d09b480d34846245d9288f4dcefb17a73ce6e6a",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/inflector": "^2.0",
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "ext-mbstring": "*",
+                "illuminate/collections": "^10.0",
+                "illuminate/conditionable": "^10.0",
+                "illuminate/contracts": "^10.0",
+                "illuminate/macroable": "^10.0",
+                "nesbot/carbon": "^2.67",
+                "php": "^8.1",
+                "voku/portable-ascii": "^2.0"
+            },
+            "conflict": {
+                "tightenco/collect": "<5.5.33"
+            },
+            "suggest": {
+                "illuminate/filesystem": "Required to use the composer class (^10.0).",
+                "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).",
+                "ramsey/uuid": "Required to use Str::uuid() (^4.7).",
+                "symfony/process": "Required to use the composer class (^6.2).",
+                "symfony/uid": "Required to use Str::ulid() (^6.2).",
+                "symfony/var-dumper": "Required to use the dd function (^6.2).",
+                "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "10.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "helpers.php"
+                ],
+                "psr-4": {
+                    "Illuminate\\Support\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylor@laravel.com"
+                }
+            ],
+            "description": "The Illuminate Support package.",
+            "homepage": "https://laravel.com",
+            "support": {
+                "issues": "https://github.com/laravel/framework/issues",
+                "source": "https://github.com/laravel/framework"
+            },
+            "time": "2024-12-10T14:47:55+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "2.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "5cf826f2991858b54d5c3809bee745560a1042a7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7",
+                "reference": "5cf826f2991858b54d5c3809bee745560a1042a7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2",
+                "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "elasticsearch/elasticsearch": "^7 || ^8",
+                "ext-json": "*",
+                "graylog2/gelf-php": "^1.4.2 || ^2@dev",
+                "guzzlehttp/guzzle": "^7.4",
+                "guzzlehttp/psr7": "^2.2",
+                "mongodb/mongodb": "^1.8",
+                "php-amqplib/php-amqplib": "~2.4 || ^3",
+                "phpspec/prophecy": "^1.15",
+                "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^8.5.38 || ^9.6.19",
+                "predis/predis": "^1.1 || ^2.0",
+                "rollbar/rollbar": "^1.3 || ^2 || ^3",
+                "ruflin/elastica": "^7",
+                "swiftmailer/swiftmailer": "^5.3|^6.0",
+                "symfony/mailer": "^5.4 || ^6",
+                "symfony/mime": "^5.4 || ^6"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+                "ext-openssl": "Required to send log messages using SSL",
+                "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "https://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "https://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "support": {
+                "issues": "https://github.com/Seldaek/monolog/issues",
+                "source": "https://github.com/Seldaek/monolog/tree/2.10.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Seldaek",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-11-12T12:43:37+00:00"
+        },
+        {
+            "name": "nesbot/carbon",
+            "version": "2.73.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/CarbonPHP/carbon.git",
+                "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075",
+                "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075",
+                "shasum": ""
+            },
+            "require": {
+                "carbonphp/carbon-doctrine-types": "*",
+                "ext-json": "*",
+                "php": "^7.1.8 || ^8.0",
+                "psr/clock": "^1.0",
+                "symfony/polyfill-mbstring": "^1.0",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+            },
+            "provide": {
+                "psr/clock-implementation": "1.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0",
+                "doctrine/orm": "^2.7 || ^3.0",
+                "friendsofphp/php-cs-fixer": "^3.0",
+                "kylekatarnls/multi-tester": "^2.0",
+                "ondrejmirtes/better-reflection": "<6",
+                "phpmd/phpmd": "^2.9",
+                "phpstan/extension-installer": "^1.0",
+                "phpstan/phpstan": "^0.12.99 || ^1.7.14",
+                "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
+                "squizlabs/php_codesniffer": "^3.4"
+            },
+            "bin": [
+                "bin/carbon"
+            ],
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Carbon\\Laravel\\ServiceProvider"
+                    ]
+                },
+                "phpstan": {
+                    "includes": [
+                        "extension.neon"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-2.x": "2.x-dev",
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Carbon\\": "src/Carbon/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brian Nesbitt",
+                    "email": "brian@nesbot.com",
+                    "homepage": "https://markido.com"
+                },
+                {
+                    "name": "kylekatarnls",
+                    "homepage": "https://github.com/kylekatarnls"
+                }
+            ],
+            "description": "An API extension for DateTime that supports 281 different languages.",
+            "homepage": "https://carbon.nesbot.com",
+            "keywords": [
+                "date",
+                "datetime",
+                "time"
+            ],
+            "support": {
+                "docs": "https://carbon.nesbot.com/docs",
+                "issues": "https://github.com/briannesbitt/Carbon/issues",
+                "source": "https://github.com/briannesbitt/Carbon"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/kylekatarnls",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/Carbon#sponsor",
+                    "type": "opencollective"
+                },
+                {
+                    "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-01-08T20:10:23+00:00"
+        },
+        {
+            "name": "nikic/fast-route",
+            "version": "v1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/FastRoute.git",
+                "reference": "181d480e08d9476e61381e04a71b34dc0432e812"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
+                "reference": "181d480e08d9476e61381e04a71b34dc0432e812",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35|~5.7"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "FastRoute\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov",
+                    "email": "nikic@php.net"
+                }
+            ],
+            "description": "Fast request router for PHP",
+            "keywords": [
+                "router",
+                "routing"
+            ],
+            "support": {
+                "issues": "https://github.com/nikic/FastRoute/issues",
+                "source": "https://github.com/nikic/FastRoute/tree/master"
+            },
+            "time": "2018-02-13T20:26:39+00:00"
+        },
+        {
+            "name": "openspout/openspout",
+            "version": "v4.25.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/openspout/openspout.git",
+                "reference": "519affe730d92e1598720a6467227fc28550f0e6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/openspout/openspout/zipball/519affe730d92e1598720a6467227fc28550f0e6",
+                "reference": "519affe730d92e1598720a6467227fc28550f0e6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-filter": "*",
+                "ext-libxml": "*",
+                "ext-xmlreader": "*",
+                "ext-zip": "*",
+                "php": "~8.1.0 || ~8.2.0 || ~8.3.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "friendsofphp/php-cs-fixer": "^3.64.0",
+                "infection/infection": "^0.29.6",
+                "phpbench/phpbench": "^1.3.1",
+                "phpstan/phpstan": "^1.12.4",
+                "phpstan/phpstan-phpunit": "^1.4.0",
+                "phpstan/phpstan-strict-rules": "^1.6.1",
+                "phpunit/phpunit": "^10.5.20 || ^11.3.6"
+            },
+            "suggest": {
+                "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)",
+                "ext-mbstring": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "OpenSpout\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Adrien Loison",
+                    "email": "adrien@box.com"
+                }
+            ],
+            "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way",
+            "homepage": "https://github.com/openspout/openspout",
+            "keywords": [
+                "OOXML",
+                "csv",
+                "excel",
+                "memory",
+                "odf",
+                "ods",
+                "office",
+                "open",
+                "php",
+                "read",
+                "scale",
+                "spreadsheet",
+                "stream",
+                "write",
+                "xlsx"
+            ],
+            "support": {
+                "issues": "https://github.com/openspout/openspout/issues",
+                "source": "https://github.com/openspout/openspout/tree/v4.25.0"
+            },
+            "funding": [
+                {
+                    "url": "https://paypal.me/filippotessarotto",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/Slamdunk",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-09-24T09:03:42+00:00"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144",
+                "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "ext-hash": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+                "doctrine/annotations": "^1.2.6 || ^1.13.3",
+                "php-parallel-lint/php-console-highlighter": "^1.0.0",
+                "php-parallel-lint/php-parallel-lint": "^1.3.2",
+                "phpcompatibility/php-compatibility": "^9.3.5",
+                "roave/security-advisories": "dev-latest",
+                "squizlabs/php_codesniffer": "^3.7.2",
+                "yoast/phpunit-polyfills": "^1.0.4"
+            },
+            "suggest": {
+                "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+                "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+                "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+                "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+                "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "phpmailer@synchromedia.co.uk"
+                },
+                {
+                    "name": "Jim Jagielski",
+                    "email": "jimjag@gmail.com"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "codeworxtech@users.sourceforge.net"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "support": {
+                "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+                "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.10.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Synchro",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-04-24T15:19:31+00:00"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
+                "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "schmittjoh@gmail.com",
+                    "homepage": "https://github.com/schmittjoh"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "support": {
+                "issues": "https://github.com/schmittjoh/php-option/issues",
+                "source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-20T21:41:07+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
+                "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/cache/tree/3.0.0"
+            },
+            "time": "2021-02-03T23:26:27+00:00"
+        },
+        {
+            "name": "psr/clock",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/clock.git",
+                "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+                "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Clock\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for reading the clock.",
+            "homepage": "https://github.com/php-fig/clock",
+            "keywords": [
+                "clock",
+                "now",
+                "psr",
+                "psr-20",
+                "time"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/clock/issues",
+                "source": "https://github.com/php-fig/clock/tree/1.0.0"
+            },
+            "time": "2022-11-25T14:36:26+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/2.0.2"
+            },
+            "time": "2021-11-05T16:47:00+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-client"
+            },
+            "time": "2023-09-23T14:17:50+00:00"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-factory"
+            },
+            "time": "2024-04-15T12:06:14+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/2.0"
+            },
+            "time": "2023-04-04T09:54:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/log/tree/3.0.2"
+            },
+            "time": "2024-09-11T13:17:53+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
+                "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
+            },
+            "time": "2021-10-29T13:26:27+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "ramsey/collection",
+            "version": "2.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ramsey/collection.git",
+                "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
+                "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "captainhook/plugin-composer": "^5.3",
+                "ergebnis/composer-normalize": "^2.45",
+                "fakerphp/faker": "^1.24",
+                "hamcrest/hamcrest-php": "^2.0",
+                "jangregor/phpstan-prophecy": "^2.1",
+                "mockery/mockery": "^1.6",
+                "php-parallel-lint/php-console-highlighter": "^1.0",
+                "php-parallel-lint/php-parallel-lint": "^1.4",
+                "phpspec/prophecy-phpunit": "^2.3",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-mockery": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/phpunit": "^10.5",
+                "ramsey/coding-standard": "^2.3",
+                "ramsey/conventional-commits": "^1.6",
+                "roave/security-advisories": "dev-latest"
+            },
+            "type": "library",
+            "extra": {
+                "captainhook": {
+                    "force-install": true
+                },
+                "ramsey/conventional-commits": {
+                    "configFile": "conventional-commits.json"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Ramsey\\Collection\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ben Ramsey",
+                    "email": "ben@benramsey.com",
+                    "homepage": "https://benramsey.com"
+                }
+            ],
+            "description": "A PHP library for representing and manipulating collections.",
+            "keywords": [
+                "array",
+                "collection",
+                "hash",
+                "map",
+                "queue",
+                "set"
+            ],
+            "support": {
+                "issues": "https://github.com/ramsey/collection/issues",
+                "source": "https://github.com/ramsey/collection/tree/2.1.1"
+            },
+            "time": "2025-03-22T05:38:12+00:00"
+        },
+        {
+            "name": "ramsey/uuid",
+            "version": "4.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ramsey/uuid.git",
+                "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
+                "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
+                "shasum": ""
+            },
+            "require": {
+                "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13",
+                "ext-json": "*",
+                "php": "^8.0",
+                "ramsey/collection": "^1.2 || ^2.0"
+            },
+            "replace": {
+                "rhumsaa/uuid": "self.version"
+            },
+            "require-dev": {
+                "captainhook/captainhook": "^5.25",
+                "captainhook/plugin-composer": "^5.3",
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+                "ergebnis/composer-normalize": "^2.47",
+                "mockery/mockery": "^1.6",
+                "paragonie/random-lib": "^2",
+                "php-mock/php-mock": "^2.6",
+                "php-mock/php-mock-mockery": "^1.5",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "phpbench/phpbench": "^1.2.14",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan": "^2.1",
+                "phpstan/phpstan-mockery": "^2.0",
+                "phpstan/phpstan-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.6",
+                "slevomat/coding-standard": "^8.18",
+                "squizlabs/php_codesniffer": "^3.13"
+            },
+            "suggest": {
+                "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+                "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+                "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
+                "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+                "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+            },
+            "type": "library",
+            "extra": {
+                "captainhook": {
+                    "force-install": true
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "Ramsey\\Uuid\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
+            "keywords": [
+                "guid",
+                "identifier",
+                "uuid"
+            ],
+            "support": {
+                "issues": "https://github.com/ramsey/uuid/issues",
+                "source": "https://github.com/ramsey/uuid/tree/4.8.1"
+            },
+            "time": "2025-06-01T06:28:46+00:00"
+        },
+        {
+            "name": "saithink/saiadmin",
+            "version": "5.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/saithink/saiadmin.git",
+                "reference": "5325483e2c7db9289d96b8b83b61b0ef3d94907c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/saithink/saiadmin/zipball/5325483e2c7db9289d96b8b83b61b0ef3d94907c",
+                "reference": "5325483e2c7db9289d96b8b83b61b0ef3d94907c",
+                "shasum": ""
+            },
+            "require": {
+                "godruoyi/php-snowflake": "^3.0.0",
+                "guzzlehttp/guzzle": "^7.9",
+                "openspout/openspout": "^4.0",
+                "php": ">=8.1",
+                "phpmailer/phpmailer": "^6.9",
+                "ramsey/uuid": "^4.0",
+                "tinywan/jwt": "^1.11",
+                "tinywan/storage": "^1.1",
+                "topthink/think-orm": "^2.0.53 || ^3.0.0",
+                "topthink/think-validate": "^3.0",
+                "twig/twig": "^3.20",
+                "vlucas/phpdotenv": "^5.6",
+                "webman/cache": "^2.1",
+                "webman/captcha": "^1.0",
+                "webman/event": "^1.0",
+                "webman/push": "^1.0",
+                "webman/redis": "^2.1",
+                "webman/think-orm": "^2.1",
+                "workerman/crontab": "^1.0",
+                "zoujingli/ip2region": "^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Webman\\saiadmin\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "saithink",
+                    "email": "1430792918@qq.com"
+                }
+            ],
+            "description": "webman plugin",
+            "support": {
+                "issues": "https://github.com/saithink/saiadmin/issues",
+                "source": "https://github.com/saithink/saiadmin/tree/5.0.6"
+            },
+            "time": "2025-06-20T10:52:02+00:00"
+        },
+        {
+            "name": "symfony/cache",
+            "version": "v6.4.21",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache.git",
+                "reference": "d1abcf763a7414f2e572f676f22da7a06c8cd9ee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache/zipball/d1abcf763a7414f2e572f676f22da7a06c8cd9ee",
+                "reference": "d1abcf763a7414f2e572f676f22da7a06c8cd9ee",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/cache": "^2.0|^3.0",
+                "psr/log": "^1.1|^2|^3",
+                "symfony/cache-contracts": "^2.5|^3",
+                "symfony/service-contracts": "^2.5|^3",
+                "symfony/var-exporter": "^6.3.6|^7.0"
+            },
+            "conflict": {
+                "doctrine/dbal": "<2.13.1",
+                "symfony/dependency-injection": "<5.4",
+                "symfony/http-kernel": "<5.4",
+                "symfony/var-dumper": "<5.4"
+            },
+            "provide": {
+                "psr/cache-implementation": "2.0|3.0",
+                "psr/simple-cache-implementation": "1.0|2.0|3.0",
+                "symfony/cache-implementation": "1.1|2.0|3.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "dev-master",
+                "doctrine/dbal": "^2.13.1|^3|^4",
+                "predis/predis": "^1.1|^2.0",
+                "psr/simple-cache": "^1.0|^2.0|^3.0",
+                "symfony/config": "^5.4|^6.0|^7.0",
+                "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+                "symfony/filesystem": "^5.4|^6.0|^7.0",
+                "symfony/http-kernel": "^5.4|^6.0|^7.0",
+                "symfony/messenger": "^5.4|^6.0|^7.0",
+                "symfony/var-dumper": "^5.4|^6.0|^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Cache\\": ""
+                },
+                "classmap": [
+                    "Traits/ValueWrapper.php"
+                ],
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "caching",
+                "psr6"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache/tree/v6.4.21"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-04-08T08:21:20+00:00"
+        },
+        {
+            "name": "symfony/cache-contracts",
+            "version": "v3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache-contracts.git",
+                "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868",
+                "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/cache": "^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Cache\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to caching",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-03-13T15:25:07+00:00"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+                "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.6-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-25T14:21:43+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.32.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-09T11:45:10+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.32.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+                "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-12-23T08:48:59+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.32.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+                "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-01-02T08:10:11+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+                "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/container": "^1.1|^2.0",
+                "symfony/deprecation-contracts": "^2.5|^3"
+            },
+            "conflict": {
+                "ext-psr": "<1.1|>=2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Test/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-04-25T09:37:31+00:00"
+        },
+        {
+            "name": "symfony/translation",
+            "version": "v6.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation.git",
+                "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/7e3b3b7146c6fab36ddff304a8041174bf6e17ad",
+                "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/translation-contracts": "^2.5|^3.0"
+            },
+            "conflict": {
+                "symfony/config": "<5.4",
+                "symfony/console": "<5.4",
+                "symfony/dependency-injection": "<5.4",
+                "symfony/http-client-contracts": "<2.5",
+                "symfony/http-kernel": "<5.4",
+                "symfony/service-contracts": "<2.5",
+                "symfony/twig-bundle": "<5.4",
+                "symfony/yaml": "<5.4"
+            },
+            "provide": {
+                "symfony/translation-implementation": "2.3|3.0"
+            },
+            "require-dev": {
+                "nikic/php-parser": "^4.18|^5.0",
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^5.4|^6.0|^7.0",
+                "symfony/console": "^5.4|^6.0|^7.0",
+                "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+                "symfony/finder": "^5.4|^6.0|^7.0",
+                "symfony/http-client-contracts": "^2.5|^3.0",
+                "symfony/http-kernel": "^5.4|^6.0|^7.0",
+                "symfony/intl": "^5.4|^6.0|^7.0",
+                "symfony/polyfill-intl-icu": "^1.21",
+                "symfony/routing": "^5.4|^6.0|^7.0",
+                "symfony/service-contracts": "^2.5|^3",
+                "symfony/yaml": "^5.4|^6.0|^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools to internationalize your application",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/translation/tree/v6.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-05-29T07:06:44+00:00"
+        },
+        {
+            "name": "symfony/translation-contracts",
+            "version": "v3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation-contracts.git",
+                "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
+                "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Test/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to translation",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-27T08:32:26+00:00"
+        },
+        {
+            "name": "symfony/var-exporter",
+            "version": "v6.4.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-exporter.git",
+                "reference": "f28cf841f5654955c9f88ceaf4b9dc29571988a9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f28cf841f5654955c9f88ceaf4b9dc29571988a9",
+                "reference": "f28cf841f5654955c9f88ceaf4b9dc29571988a9",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "symfony/deprecation-contracts": "^2.5|^3"
+            },
+            "require-dev": {
+                "symfony/property-access": "^6.4|^7.0",
+                "symfony/serializer": "^6.4|^7.0",
+                "symfony/var-dumper": "^5.4|^6.0|^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\VarExporter\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Allows exporting any serializable PHP data structure to plain PHP code",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "clone",
+                "construct",
+                "export",
+                "hydrate",
+                "instantiate",
+                "lazy-loading",
+                "proxy",
+                "serialize"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/var-exporter/tree/v6.4.22"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-05-14T13:00:13+00:00"
+        },
+        {
+            "name": "tinywan/jwt",
+            "version": "v1.11.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Tinywan/webman-jwt.git",
+                "reference": "1b067c998d970c252b8ad113a460922f8108b9ac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Tinywan/webman-jwt/zipball/1b067c998d970c252b8ad113a460922f8108b9ac",
+                "reference": "1b067c998d970c252b8ad113a460922f8108b9ac",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "firebase/php-jwt": "^6.8",
+                "php": "^7.1||^8.0",
+                "workerman/webman-framework": "^1.2.1||^2.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^3.6",
+                "illuminate/database": "^8.83",
+                "mockery/mockery": "^1.5",
+                "phpstan/phpstan": "^1.4",
+                "phpunit/phpunit": "^9.0",
+                "topthink/think-orm": "^2.0",
+                "vimeo/psalm": "^4.22",
+                "workerman/webman": "^1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Tinywan\\Jwt\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "JSON Web Token (JWT) for webman plugin",
+            "support": {
+                "issues": "https://github.com/Tinywan/webman-jwt/issues",
+                "source": "https://github.com/Tinywan/webman-jwt/tree/v1.11.3"
+            },
+            "time": "2025-04-10T12:05:48+00:00"
+        },
+        {
+            "name": "tinywan/storage",
+            "version": "v1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Tinywan/webman-storage.git",
+                "reference": "1a78d4b585c34b2c0ea2e3be0e67952aff0e780c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Tinywan/webman-storage/zipball/1a78d4b585c34b2c0ea2e3be0e67952aff0e780c",
+                "reference": "1a78d4b585c34b2c0ea2e3be0e67952aff0e780c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2",
+                "workerman/webman-framework": "^1.2.1||^2.0"
+            },
+            "require-dev": {
+                "aliyuncs/oss-sdk-php": "^2.4",
+                "friendsofphp/php-cs-fixer": "^3.6",
+                "league/flysystem-aws-s3-v3": "^1.0",
+                "phpstan/phpstan": "^1.4",
+                "phpunit/phpunit": "^9.5",
+                "qcloud/cos-sdk-v5": "^2.5",
+                "qiniu/php-sdk": "^7.4",
+                "workerman/webman": "^1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Tinywan\\Storage\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "webman storage plugin",
+            "support": {
+                "issues": "https://github.com/Tinywan/webman-storage/issues",
+                "source": "https://github.com/Tinywan/webman-storage/tree/v1.1.1"
+            },
+            "time": "2025-02-11T02:56:31+00:00"
+        },
+        {
+            "name": "topthink/think-container",
+            "version": "v3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-container.git",
+                "reference": "b2df244be1e7399ad4c8be1ccc40ed57868f730a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-container/zipball/b2df244be1e7399ad4c8be1ccc40ed57868f730a",
+                "reference": "b2df244be1e7399ad4c8be1ccc40ed57868f730a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0",
+                "psr/container": "^2.0",
+                "topthink/think-helper": "^3.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "PHP Container & Facade Manager",
+            "support": {
+                "issues": "https://github.com/top-think/think-container/issues",
+                "source": "https://github.com/top-think/think-container/tree/v3.0.2"
+            },
+            "time": "2025-04-07T03:21:51+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v3.1.11",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/1d6ada9b9f3130046bf6922fe1bd159c8d88a33c",
+                "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP6 Helper Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-helper/issues",
+                "source": "https://github.com/top-think/think-helper/tree/v3.1.11"
+            },
+            "time": "2025-04-07T06:55:59+00:00"
+        },
+        {
+            "name": "topthink/think-orm",
+            "version": "v3.0.34",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-orm.git",
+                "reference": "715e55da149fe32a12d68ef10e5b00e70bd3dbec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-orm/zipball/715e55da149fe32a12d68ef10e5b00e70bd3dbec",
+                "reference": "715e55da149fe32a12d68ef10e5b00e70bd3dbec",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-pdo": "*",
+                "php": ">=8.0.0",
+                "psr/log": ">=1.0",
+                "psr/simple-cache": ">=1.0",
+                "topthink/think-helper": "^3.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6|^10"
+            },
+            "suggest": {
+                "ext-mongodb": "provide mongodb support"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "stubs/load_stubs.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the PHP Database&ORM Framework",
+            "keywords": [
+                "database",
+                "orm"
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/think-orm/issues",
+                "source": "https://github.com/top-think/think-orm/tree/v3.0.34"
+            },
+            "time": "2025-01-14T06:03:33+00:00"
+        },
+        {
+            "name": "topthink/think-validate",
+            "version": "v3.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-validate.git",
+                "reference": "85063f6d4ef8ed122f17a36179dc3e0949b30988"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-validate/zipball/85063f6d4ef8ed122f17a36179dc3e0949b30988",
+                "reference": "85063f6d4ef8ed122f17a36179dc3e0949b30988",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0",
+                "topthink/think-container": ">=3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "think validate",
+            "support": {
+                "issues": "https://github.com/top-think/think-validate/issues",
+                "source": "https://github.com/top-think/think-validate/tree/v3.0.7"
+            },
+            "time": "2025-06-11T05:51:40+00:00"
+        },
+        {
+            "name": "twig/twig",
+            "version": "v3.21.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/twigphp/Twig.git",
+                "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d",
+                "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1.0",
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/polyfill-ctype": "^1.8",
+                "symfony/polyfill-mbstring": "^1.3"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^2.0",
+                "psr/container": "^1.0|^2.0",
+                "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Resources/core.php",
+                    "src/Resources/debug.php",
+                    "src/Resources/escaper.php",
+                    "src/Resources/string_loader.php"
+                ],
+                "psr-4": {
+                    "Twig\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com",
+                    "homepage": "http://fabien.potencier.org",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Twig Team",
+                    "role": "Contributors"
+                },
+                {
+                    "name": "Armin Ronacher",
+                    "email": "armin.ronacher@active-4.com",
+                    "role": "Project Founder"
+                }
+            ],
+            "description": "Twig, the flexible, fast, and secure template language for PHP",
+            "homepage": "https://twig.symfony.com",
+            "keywords": [
+                "templating"
+            ],
+            "support": {
+                "issues": "https://github.com/twigphp/Twig/issues",
+                "source": "https://github.com/twigphp/Twig/tree/v3.21.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/twig/twig",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-05-03T07:21:55+00:00"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v5.6.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
+                "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "graham-campbell/result-type": "^1.1.3",
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.3",
+                "symfony/polyfill-ctype": "^1.24",
+                "symfony/polyfill-mbstring": "^1.24",
+                "symfony/polyfill-php80": "^1.24"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-filter": "*",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "suggest": {
+                "ext-filter": "Required to use the boolean validator."
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "5.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "https://github.com/vlucas"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "support": {
+                "issues": "https://github.com/vlucas/phpdotenv/issues",
+                "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-04-30T23:37:27+00:00"
+        },
+        {
+            "name": "voku/portable-ascii",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/voku/portable-ascii.git",
+                "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
+                "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0"
+            },
+            "suggest": {
+                "ext-intl": "Use Intl for transliterator_transliterate() support"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "voku\\": "src/voku/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Lars Moelleken",
+                    "homepage": "https://www.moelleken.org/"
+                }
+            ],
+            "description": "Portable ASCII library - performance optimized (ascii) string functions for php.",
+            "homepage": "https://github.com/voku/portable-ascii",
+            "keywords": [
+                "ascii",
+                "clean",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/voku/portable-ascii/issues",
+                "source": "https://github.com/voku/portable-ascii/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://www.paypal.me/moelleken",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/voku",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/portable-ascii",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://www.patreon.com/voku",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-11-21T01:49:47+00:00"
+        },
+        {
+            "name": "webman/cache",
+            "version": "v2.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webman-php/cache.git",
+                "reference": "66a5461ea51d23364403b54ee218b736d26bb03f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webman-php/cache/zipball/66a5461ea51d23364403b54ee218b736d26bb03f",
+                "reference": "66a5461ea51d23364403b54ee218b736d26bb03f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "psr/simple-cache": "^3.0",
+                "symfony/cache": "^6.0 || ^7.0",
+                "workerman/webman-framework": "^2.1 || dev-master"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "support\\": "src/support",
+                    "Webman\\Cache\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "support": {
+                "issues": "https://github.com/webman-php/cache/issues",
+                "source": "https://github.com/webman-php/cache/tree/v2.1.2"
+            },
+            "time": "2025-04-11T01:23:50+00:00"
+        },
+        {
+            "name": "webman/captcha",
+            "version": "v1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webman-php/captcha.git",
+                "reference": "0b2645b813466e4e70bff311511364080bad2ec5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webman-php/captcha/zipball/0b2645b813466e4e70bff311511364080bad2ec5",
+                "reference": "0b2645b813466e4e70bff311511364080bad2ec5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*",
+                "ext-mbstring": "*",
+                "php": ">=7.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Webman\\Captcha\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net"
+                },
+                {
+                    "name": "Grégoire Passault",
+                    "email": "g.passault@gmail.com",
+                    "homepage": "http://www.gregwar.com/"
+                },
+                {
+                    "name": "Jeremy Livingston",
+                    "email": "jeremy.j.livingston@gmail.com"
+                }
+            ],
+            "description": "Captcha generator",
+            "keywords": [
+                "bot",
+                "captcha",
+                "spam"
+            ],
+            "support": {
+                "source": "https://github.com/webman-php/captcha/tree/v1.0.5"
+            },
+            "time": "2025-03-01T08:43:36+00:00"
+        },
+        {
+            "name": "webman/event",
+            "version": "v1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webman-php/event.git",
+                "reference": "b1c3f6b70fd290e48288703d59bead0e28f9fb84"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webman-php/event/zipball/b1c3f6b70fd290e48288703d59bead0e28f9fb84",
+                "reference": "b1c3f6b70fd290e48288703d59bead0e28f9fb84",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Webman\\Event\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Webman event plugin",
+            "support": {
+                "issues": "https://github.com/webman-php/event/issues",
+                "source": "https://github.com/webman-php/event/tree/v1.0.5"
+            },
+            "time": "2023-12-04T09:22:12+00:00"
+        },
+        {
+            "name": "webman/push",
+            "version": "v1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webman-php/push.git",
+                "reference": "b9e51d39a6ae232eab6b3f5c48a918857976add8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webman-php/push/zipball/b9e51d39a6ae232eab6b3f5c48a918857976add8",
+                "reference": "b9e51d39a6ae232eab6b3f5c48a918857976add8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Webman\\Push\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "support": {
+                "issues": "https://github.com/webman-php/push/issues",
+                "source": "https://github.com/webman-php/push/tree/v1.1.1"
+            },
+            "time": "2025-05-15T14:32:34+00:00"
+        },
+        {
+            "name": "webman/redis",
+            "version": "v2.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webman-php/redis.git",
+                "reference": "559eb1692d39c6fef5cf526223fff728be6c0fb9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webman-php/redis/zipball/559eb1692d39c6fef5cf526223fff728be6c0fb9",
+                "reference": "559eb1692d39c6fef5cf526223fff728be6c0fb9",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/redis": "^10.0 || ^11.0 || ^12.0",
+                "workerman/webman-framework": "^2.1 || dev-master"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "support\\": "src/support",
+                    "Webman\\Redis\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Webman redis",
+            "support": {
+                "issues": "https://github.com/webman-php/redis/issues",
+                "source": "https://github.com/webman-php/redis/tree/v2.1.3"
+            },
+            "time": "2025-03-14T03:52:14+00:00"
+        },
+        {
+            "name": "webman/think-orm",
+            "version": "v2.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webman-php/think-orm.git",
+                "reference": "af4a2586177a333983e0da1bf6512ec12f894b29"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webman-php/think-orm/zipball/af4a2586177a333983e0da1bf6512ec12f894b29",
+                "reference": "af4a2586177a333983e0da1bf6512ec12f894b29",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "topthink/think-container": "^2.0|^3.0",
+                "topthink/think-orm": "^2.0.53 || ^3.0.0 || ^4.0.30 || dev-master",
+                "workerman/webman-framework": "^2.1 || dev-master"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "support\\": "src/support",
+                    "Webman\\ThinkOrm\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "support": {
+                "issues": "https://github.com/webman-php/think-orm/issues",
+                "source": "https://github.com/webman-php/think-orm/tree/v2.1.6"
+            },
+            "time": "2025-05-27T13:15:25+00:00"
+        },
+        {
+            "name": "workerman/coroutine",
+            "version": "v1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/workerman-php/coroutine.git",
+                "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/df8fc428967d512a74a8a7d80355c1d40228c9fa",
+                "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^11.0",
+                "psr/log": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Workerman\\": "src",
+                    "Workerman\\Coroutine\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Workerman coroutine",
+            "support": {
+                "issues": "https://github.com/workerman-php/coroutine/issues",
+                "source": "https://github.com/workerman-php/coroutine/tree/v1.1.3"
+            },
+            "time": "2025-02-17T03:34:21+00:00"
+        },
+        {
+            "name": "workerman/crontab",
+            "version": "v1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/crontab.git",
+                "reference": "74f51ca8204e8eb628e57bc0e640561d570da2cb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/crontab/zipball/74f51ca8204e8eb628e57bc0e640561d570da2cb",
+                "reference": "74f51ca8204e8eb628e57bc0e640561d570da2cb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0",
+                "workerman/workerman": ">=4.0.20"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Workerman\\Crontab\\": "./src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net",
+                    "homepage": "http://www.workerman.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A crontab written in PHP based on workerman",
+            "homepage": "http://www.workerman.net",
+            "keywords": [
+                "crontab"
+            ],
+            "support": {
+                "email": "walkor@workerman.net",
+                "forum": "http://wenda.workerman.net/",
+                "issues": "https://github.com/walkor/workerman/issues",
+                "source": "https://github.com/walkor/crontab",
+                "wiki": "http://doc.workerman.net/"
+            },
+            "time": "2025-01-15T07:20:50+00:00"
+        },
+        {
+            "name": "workerman/webman-framework",
+            "version": "v2.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/webman-framework.git",
+                "reference": "f803bd867f07bb0929faef060b59a19a44186bfc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f803bd867f07bb0929faef060b59a19a44186bfc",
+                "reference": "f803bd867f07bb0929faef060b59a19a44186bfc",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "nikic/fast-route": "^1.3",
+                "php": ">=8.1",
+                "psr/container": ">=1.0",
+                "psr/log": "^3.0",
+                "workerman/workerman": "^5.1 || dev-master"
+            },
+            "suggest": {
+                "ext-event": "For better performance. "
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "./src/support/helpers.php"
+                ],
+                "psr-4": {
+                    "Webman\\": "./src",
+                    "Support\\": "./src/support",
+                    "support\\": "./src/support",
+                    "Support\\View\\": "./src/support/view",
+                    "Support\\Bootstrap\\": "./src/support/bootstrap",
+                    "Support\\Exception\\": "./src/support/exception"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net",
+                    "homepage": "https://www.workerman.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "High performance HTTP Service Framework.",
+            "homepage": "https://www.workerman.net",
+            "keywords": [
+                "High Performance",
+                "http service"
+            ],
+            "support": {
+                "email": "walkor@workerman.net",
+                "forum": "https://wenda.workerman.net/",
+                "issues": "https://github.com/walkor/webman/issues",
+                "source": "https://github.com/walkor/webman-framework",
+                "wiki": "https://doc.workerman.net/"
+            },
+            "time": "2025-03-10T11:52:22+00:00"
+        },
+        {
+            "name": "workerman/workerman",
+            "version": "v5.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/workerman.git",
+                "reference": "371f3a5decb28f1bd3464ae26d47ea1a4cf0a3c5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/workerman/zipball/371f3a5decb28f1bd3464ae26d47ea1a4cf0a3c5",
+                "reference": "371f3a5decb28f1bd3464ae26d47ea1a4cf0a3c5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "php": ">=8.1",
+                "workerman/coroutine": "^1.1 || dev-main"
+            },
+            "conflict": {
+                "ext-swow": "<v1.0.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.0",
+                "mockery/mockery": "^1.6",
+                "pestphp/pest": "2.x-dev",
+                "phpstan/phpstan": "1.11.x-dev"
+            },
+            "suggest": {
+                "ext-event": "For better performance. "
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Workerman\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net",
+                    "homepage": "https://www.workerman.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
+            "homepage": "https://www.workerman.net",
+            "keywords": [
+                "asynchronous",
+                "event-loop",
+                "framework",
+                "http"
+            ],
+            "support": {
+                "email": "walkor@workerman.net",
+                "forum": "https://www.workerman.net/questions",
+                "issues": "https://github.com/walkor/workerman/issues",
+                "source": "https://github.com/walkor/workerman",
+                "wiki": "https://www.workerman.net/doc/workerman/"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/workerman",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://www.patreon.com/walkor",
+                    "type": "patreon"
+                }
+            ],
+            "time": "2025-06-12T13:34:04+00:00"
+        },
+        {
+            "name": "zoujingli/ip2region",
+            "version": "v2.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/zoujingli/ip2region.git",
+                "reference": "66895178be204521e9f5ae9df0ea502893ee53b2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/zoujingli/ip2region/zipball/66895178be204521e9f5ae9df0ea502893ee53b2",
+                "reference": "66895178be204521e9f5ae9df0ea502893ee53b2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "Ip2Region.php",
+                    "XdbSearcher.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Anyon",
+                    "email": "zoujingli@qq.com",
+                    "homepage": "https://thinkadmin.top"
+                }
+            ],
+            "description": "Ip2Region for PHP",
+            "homepage": "https://github.com/zoujingli/Ip2Region",
+            "keywords": [
+                "Ip2Region"
+            ],
+            "support": {
+                "issues": "https://github.com/zoujingli/ip2region/issues",
+                "source": "https://github.com/zoujingli/ip2region/tree/v2.0.6"
+            },
+            "time": "2024-08-02T01:01:01+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": true,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=8.1"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.6.0"
+}

+ 26 - 0
config/app.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\Request;
+
+return [
+    'debug' => true,
+    'error_reporting' => E_ALL,
+    'default_timezone' => 'Asia/Shanghai',
+    'request_class' => Request::class,
+    'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
+    'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
+    'controller_suffix' => 'Controller',
+    'controller_reuse' => false,
+];

+ 21 - 0
config/autoload.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    'files' => [
+        base_path() . '/app/functions.php',
+        base_path() . '/support/Request.php',
+        base_path() . '/support/Response.php',
+    ]
+];

+ 18 - 0
config/bootstrap.php

@@ -0,0 +1,18 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    support\bootstrap\Session::class,
+    Webman\ThinkOrm\ThinkOrm::class,
+];

+ 18 - 0
config/cache.php

@@ -0,0 +1,18 @@
+<?php
+
+return [
+    'default' => getenv('CACHE_MODE'),
+    'stores' => [
+        'file' => [
+            'driver' => 'file',
+            'path' => runtime_path('cache')
+        ],
+        'redis' => [
+            'driver' => 'redis',
+            'connection' => 'default'
+        ],
+        'array' => [
+            'driver' => 'array'
+        ]
+    ]
+];

+ 15 - 0
config/container.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return new Webman\Container;

+ 15 - 0
config/dependence.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [];

+ 5 - 0
config/event.php

@@ -0,0 +1,5 @@
+<?php
+
+return [
+    
+];

+ 17 - 0
config/exception.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    '' => support\exception\Handler::class,
+];

+ 32 - 0
config/log.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    'default' => [
+        'handlers' => [
+            [
+                'class' => Monolog\Handler\RotatingFileHandler::class,
+                'constructor' => [
+                    runtime_path() . '/logs/webman.log',
+                    7, //$maxFiles
+                    Monolog\Logger::DEBUG,
+                ],
+                'formatter' => [
+                    'class' => Monolog\Formatter\LineFormatter::class,
+                    'constructor' => [null, 'Y-m-d H:i:s', true],
+                ],
+            ]
+        ],
+    ],
+];

+ 13 - 0
config/middleware.php

@@ -0,0 +1,13 @@
+<?php
+
+use plugin\saiadmin\app\middleware\SystemLog;
+use plugin\saiadmin\app\middleware\CheckLogin;
+use plugin\saiadmin\app\middleware\CheckAuth;
+
+return [
+    '' => [
+        CheckLogin::class,
+        CheckAuth::class,
+        SystemLog::class,
+    ]
+];

+ 83 - 0
config/plugin/tinywan/jwt/app.php

@@ -0,0 +1,83 @@
+<?php
+
+return [
+    'enable' => true,
+    'jwt' => [
+        /** 算法类型 HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384、PS512 */
+        'algorithms' => 'HS256',
+
+        /** access令牌秘钥 */
+        'access_secret_key' => '2022d3d3LmJq',
+
+        /** access令牌过期时间,单位:秒。默认 2 小时 */
+        'access_exp' => 7200,
+
+        /** refresh令牌秘钥 */
+        'refresh_secret_key' => '2022KTxigxc9o50c',
+
+        /** refresh令牌过期时间,单位:秒。默认 7 天 */
+        'refresh_exp' => 604800,
+
+        /** refresh 令牌是否禁用,默认不禁用 false */
+        'refresh_disable' => false,
+
+        /** 令牌签发者 */
+        'iss' => 'webman.tinywan.cn',
+
+        /** 某个时间点后才能访问,单位秒。(如:30 表示当前时间30秒后才能使用) */
+        'nbf' => 0,
+
+        /** 时钟偏差冗余时间,单位秒。建议这个余地应该不大于几分钟 */
+        'leeway' => 60,
+
+        /** 是否允许单设备登录,默认不允许 false */
+        'is_single_device' => false,
+
+        /** 缓存令牌时间,单位:秒。默认 7 天 */
+        'cache_token_ttl' => 604800,
+
+        /** 缓存令牌前缀,默认 JWT:TOKEN: */
+        'cache_token_pre' => 'JWT:TOKEN:',
+
+        /** 缓存刷新令牌前缀,默认 JWT:REFRESH_TOKEN: */
+        'cache_refresh_token_pre' => 'JWT:REFRESH_TOKEN:',
+
+        /** 用户信息模型 */
+        'user_model' => function ($uid) {
+            return [];
+        },
+
+        /** 是否支持 get 请求获取令牌 */
+        'is_support_get_token' => false,
+        /** GET 请求获取令牌请求key */
+        'is_support_get_token_key' => 'authorization',
+
+        /** access令牌私钥 */
+        'access_private_key' => <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+...
+-----END RSA PRIVATE KEY-----
+EOD,
+
+        /** access令牌公钥 */
+        'access_public_key' => <<<EOD
+-----BEGIN PUBLIC KEY-----
+...
+-----END PUBLIC KEY-----
+EOD,
+
+        /** refresh令牌私钥 */
+        'refresh_private_key' => <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+...
+-----END RSA PRIVATE KEY-----
+EOD,
+
+        /** refresh令牌公钥 */
+        'refresh_public_key' => <<<EOD
+-----BEGIN PUBLIC KEY-----
+...
+-----END PUBLIC KEY-----
+EOD,
+    ],
+];

+ 76 - 0
config/plugin/tinywan/storage/app.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * @desc app.php 描述信息
+ *
+ * @author Tinywan(ShaoBo Wan)
+ * @date 2022/3/10 19:46
+ */
+
+return [
+    'enable' => true,
+    'storage' => [
+        'default' => 'local', // local:本地 oss:阿里云 cos:腾讯云 qos:七牛云
+        'single_limit' => 1024 * 1024 * 200, // 单个文件的大小限制,默认200M 1024 * 1024 * 200
+        'total_limit' => 1024 * 1024 * 200, // 所有文件的大小限制,默认200M 1024 * 1024 * 200
+        'nums' => 10, // 文件数量限制,默认10
+        'include' => [], // 被允许的文件类型列表
+        'exclude' => [], // 不被允许的文件类型列表
+        // 本地对象存储
+        'local' => [
+            'adapter' => \Tinywan\Storage\Adapter\LocalAdapter::class,
+            'root' => runtime_path().'/storage',
+            'dirname' => function () {
+                return date('Ymd');
+            },
+            'domain' => 'http://127.0.0.1:8787',
+            'uri' => '/runtime', // 如果 domain + uri 不在 public 目录下,请做好软链接,否则生成的url无法访问
+            'algo' => 'sha1',
+        ],
+        // 阿里云对象存储
+        'oss' => [
+            'adapter' => \Tinywan\Storage\Adapter\OssAdapter::class,
+            'accessKeyId' => 'xxxxxxxxxxxx',
+            'accessKeySecret' => 'xxxxxxxxxxxx',
+            'bucket' => 'resty-webman',
+            'dirname' => function () {
+                return 'storage';
+            },
+            'domain' => 'http://webman.oss.tinywan.com',
+            'endpoint' => 'oss-cn-hangzhou.aliyuncs.com',
+            'algo' => 'sha1',
+        ],
+        // 腾讯云对象存储
+        'cos' => [
+            'adapter' => \Tinywan\Storage\Adapter\CosAdapter::class,
+            'secretId' => 'xxxxxxxxxxxxx',
+            'secretKey' => 'xxxxxxxxxxxx',
+            'bucket' => 'resty-webman-xxxxxxxxx',
+            'dirname' => 'storage',
+            'domain' => 'http://webman.oss.tinywan.com',
+            'region' => 'ap-shanghai',
+        ],
+        // 七牛云对象存储
+        'qiniu' => [
+            'adapter' => \Tinywan\Storage\Adapter\QiniuAdapter::class,
+            'accessKey' => 'xxxxxxxxxxxxx',
+            'secretKey' => 'xxxxxxxxxxxxx',
+            'bucket' => 'resty-webman',
+            'dirname' => 'storage',
+            'domain' => 'http://webman.oss.tinywan.com',
+        ],
+        // aws
+        's3' => [
+            'adapter' => \Tinywan\Storage\Adapter\S3Adapter::class,
+            'key' => 'xxxxxxxxxxxxx',
+            'secret' => 'xxxxxxxxxxxxx',
+            'bucket' => 'resty-webman',
+            'dirname' => 'storage',
+            'domain' => 'http://webman.oss.tinywan.com',
+            'region' => 'S3_REGION',
+            'version' => 'latest',
+            'use_path_style_endpoint' => true,
+            'endpoint' => 'S3_ENDPOINT',
+            'acl' => 'public-read',
+        ],
+    ],
+];

+ 4 - 0
config/plugin/webman/event/app.php

@@ -0,0 +1,4 @@
+<?php
+return [
+    'enable' => true,
+];

+ 17 - 0
config/plugin/webman/event/bootstrap.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    Webman\Event\BootStrap::class,
+];

+ 7 - 0
config/plugin/webman/event/command.php

@@ -0,0 +1,7 @@
+<?php
+
+use Webman\Event\EventListCommand;
+
+return [
+    EventListCommand::class
+];

+ 10 - 0
config/plugin/webman/push/app.php

@@ -0,0 +1,10 @@
+<?php
+return [
+    'enable'       => true,
+    'websocket'    => 'websocket://0.0.0.0:3131',
+    'api'          => 'http://0.0.0.0:3232',
+    'app_key'      => '8f5695dcc12c3270cecb11e028ac1a35',
+    'app_secret'   => 'a40085ac7c20707db8517ac414b2de3c',
+    'channel_hook' => 'http://127.0.0.1:8787/plugin/webman/push/hook',
+    'auth'         => '/plugin/webman/push/auth'
+];

+ 21 - 0
config/plugin/webman/push/process.php

@@ -0,0 +1,21 @@
+<?php
+
+use Webman\Push\Server;
+
+return [
+    'server' => [
+        'handler'     => Server::class,
+        'listen'      => config('plugin.webman.push.app.websocket'),
+        'count'       => 1, // 必须是1
+        'reloadable'  => false, // 执行reload不重启
+        'constructor' => [
+            'api_listen' => config('plugin.webman.push.app.api'),
+            'app_info'   => [
+                config('plugin.webman.push.app.app_key') => [
+                    'channel_hook' => config('plugin.webman.push.app.channel_hook'),
+                    'app_secret'   => config('plugin.webman.push.app.app_secret'),
+                ],
+            ]
+        ]
+    ]
+];

+ 87 - 0
config/plugin/webman/push/route.php

@@ -0,0 +1,87 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\Request;
+use Webman\Route;
+use Webman\Push\Api;
+
+/**
+ * 推送js客户端文件
+ */
+Route::get('/plugin/webman/push/push.js', function (Request $request) {
+    return response()->file(base_path().'/vendor/webman/push/src/push.js');
+});
+
+/**
+ * 私有频道鉴权,这里应该使用session辨别当前用户身份,然后确定该用户是否有权限监听channel_name
+ */
+Route::post(config('plugin.webman.push.app.auth'), function (Request $request) {
+    $pusher = new Api(str_replace('0.0.0.0', '127.0.0.1', config('plugin.webman.push.app.api')), config('plugin.webman.push.app.app_key'), config('plugin.webman.push.app.app_secret'));
+    $channel_name = $request->post('channel_name');
+    $session = $request->session();
+    // 这里应该通过session和channel_name判断当前用户是否有权限监听channel_name
+    $has_authority = true;
+    if ($has_authority) {
+        return response($pusher->socketAuth($channel_name, $request->post('socket_id')));
+    } else {
+        return response('Forbidden', 403);
+    }
+});
+
+/**
+ * 当频道上线以及下线时触发的回调
+ * 频道上线:是指某个频道从没有连接在线到有连接在线的事件
+ * 频道下线:是指某个频道的所有连接都断开触发的事件
+ */
+Route::post(parse_url(config('plugin.webman.push.app.channel_hook'), PHP_URL_PATH), function (Request $request) {
+
+    // 没有x-pusher-signature头视为伪造请求
+    if (!$webhook_signature = $request->header('x-pusher-signature')) {
+        return response('401 Not authenticated', 401);
+    }
+
+    $body = $request->rawBody();
+
+    // 计算签名,$app_secret 是双方使用的密钥,是保密的,外部无从得知
+    $expected_signature = hash_hmac('sha256', $body, config('plugin.webman.push.app.app_secret'), false);
+
+    // 安全校验,如果签名不一致可能是伪造的请求,返回401状态码
+    if ($webhook_signature !== $expected_signature) {
+        return response('401 Not authenticated', 401);
+    }
+
+    // 这里存储这上线 下线的channel数据
+    $payload = json_decode($body, true);
+
+    $channels_online = $channels_offline = [];
+
+    foreach ($payload['events'] as $event) {
+        if ($event['name'] === 'channel_added') {
+            $channels_online[] = $event['channel'];
+        } else if ($event['name'] === 'channel_removed') {
+            $channels_offline[] = $event['channel'];
+        }
+    }
+
+    // 业务根据需要处理上下线的channel,例如将在线状态写入数据库,通知其它channel等
+    // 上线的所有channel
+    echo 'online channels: ' . implode(',', $channels_online) . "\n";
+    // 下线的所有channel
+    echo 'offline channels: ' . implode(',', $channels_offline) . "\n";
+
+    return 'OK';
+});
+
+
+

+ 62 - 0
config/process.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\Log;
+use support\Request;
+use app\process\Http;
+
+global $argv;
+
+return [
+    'webman' => [
+        'handler' => Http::class,
+        'listen' => 'http://0.0.0.0:8787',
+        'count' => cpu_count() * 4,
+        'user' => '',
+        'group' => '',
+        'reusePort' => false,
+        'eventLoop' => '',
+        'context' => [],
+        'constructor' => [
+            'requestClass' => Request::class,
+            'logger' => Log::channel('default'),
+            'appPath' => app_path(),
+            'publicPath' => public_path()
+        ]
+    ],
+    // File update detection and automatic reload
+    'monitor' => [
+        'handler' => app\process\Monitor::class,
+        'reloadable' => false,
+        'constructor' => [
+            // Monitor these directories
+            'monitorDir' => array_merge([
+                app_path(),
+                config_path(),
+                base_path() . '/process',
+                base_path() . '/support',
+                base_path() . '/resource',
+                base_path() . '/.env',
+            ], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')),
+            // Files with these suffixes will be monitored
+            'monitorExtensions' => [
+                'php', 'html', 'htm', 'env'
+            ],
+            'options' => [
+                'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/',
+                'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
+            ]
+        ]
+    ]
+];

+ 17 - 0
config/redis.php

@@ -0,0 +1,17 @@
+<?php
+
+return [
+    'default' => [
+        'password' => getenv('REDIS_PASSWORD'),
+        'host' => getenv('REDIS_HOST'),
+        'port' => getenv('REDIS_PORT'),
+        'database' => getenv('REDIS_DB'),
+        'pool' => [
+            'max_connections' => 5,
+            'min_connections' => 1,
+            'wait_timeout' => 3,
+            'idle_timeout' => 60,
+            'heartbeat_interval' => 50,
+        ],
+    ]
+];

+ 21 - 0
config/route.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Webman\Route;
+
+
+
+
+
+

+ 23 - 0
config/server.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+    'event_loop' => '',
+    'stop_timeout' => 2,
+    'pid_file' => runtime_path() . '/webman.pid',
+    'status_file' => runtime_path() . '/webman.status',
+    'stdout_file' => runtime_path() . '/logs/stdout.log',
+    'log_file' => runtime_path() . '/logs/workerman.log',
+    'max_package_size' => 10 * 1024 * 1024
+];

+ 65 - 0
config/session.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Webman\Session\FileSessionHandler;
+use Webman\Session\RedisSessionHandler;
+use Webman\Session\RedisClusterSessionHandler;
+
+return [
+
+    'type' => 'file', // or redis or redis_cluster
+
+    'handler' => FileSessionHandler::class,
+
+    'config' => [
+        'file' => [
+            'save_path' => runtime_path() . '/sessions',
+        ],
+        'redis' => [
+            'host' => '127.0.0.1',
+            'port' => 6379,
+            'auth' => '',
+            'timeout' => 2,
+            'database' => '',
+            'prefix' => 'redis_session_',
+        ],
+        'redis_cluster' => [
+            'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
+            'timeout' => 2,
+            'auth' => '',
+            'prefix' => 'redis_session_',
+        ]
+    ],
+
+    'session_name' => 'PHPSID',
+    
+    'auto_update_timestamp' => false,
+
+    'lifetime' => 7*24*60*60,
+
+    'cookie_lifetime' => 365*24*60*60,
+
+    'cookie_path' => '/',
+
+    'domain' => '',
+    
+    'http_only' => true,
+
+    'secure' => false,
+    
+    'same_site' => '',
+
+    'gc_probability' => [1, 1000],
+
+];

+ 23 - 0
config/static.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * Static file settings
+ */
+return [
+    'enable' => true,
+    'middleware' => [     // Static file Middleware
+        //app\middleware\StaticFile::class,
+    ],
+];

+ 77 - 0
config/think-orm.php

@@ -0,0 +1,77 @@
+<?php
+
+return [
+    'default' => 'mysql',
+    'connections' => [
+        'mysql' => [
+            // 数据库类型
+            'type' => getenv('DB_TYPE'),
+            // 服务器地址
+            'hostname' => getenv('DB_HOST'),
+            // 数据库名
+            'database' => getenv('DB_NAME'),
+            // 数据库用户名
+            'username' => getenv('DB_USER'),
+            // 数据库密码
+            'password' => getenv('DB_PASSWORD'),
+            // 数据库连接端口
+            'hostport' => getenv('DB_PORT'),
+            // 数据库连接参数
+            'params' => [
+                // 连接超时3秒
+                \PDO::ATTR_TIMEOUT => 3,
+            ],
+            // 数据库编码默认采用utf8
+            'charset' => 'utf8',
+            // 数据库表前缀
+            'prefix' => getenv('DB_PREFIX'),
+            // 断线重连
+            'break_reconnect' => true,
+            // 自定义分页类
+            'bootstrap' =>  '',
+            // 连接池配置
+            'pool' => [
+                'max_connections' => 5, // 最大连接数
+                'min_connections' => 1, // 最小连接数
+                'wait_timeout' => 3,    // 从连接池获取连接等待超时时间
+                'idle_timeout' => 60,   // 连接最大空闲时间,超过该时间会被回收
+                'heartbeat_interval' => 50, // 心跳检测间隔,需要小于60秒
+            ],
+        ],
+        'yy_center_db' => [
+            // 数据库类型
+            'type' => getenv('YY_CENTER_DB_TYPE'),
+            // 服务器地址
+            'hostname' => getenv('YY_CENTER_DB_HOST'),
+            // 数据库名
+            'database' => getenv('YY_CENTER_DB_NAME'),
+            // 数据库用户名
+            'username' => getenv('YY_CENTER_DB_USER'),
+            // 数据库密码
+            'password' => getenv('YY_CENTER_DB_PASSWORD'),
+            // 数据库连接端口
+            'hostport' => getenv('YY_CENTER_DB_PORT'),
+            // 数据库连接参数
+            'params' => [
+                // 连接超时3秒
+                \PDO::ATTR_TIMEOUT => 3,
+            ],
+            // 数据库编码默认采用utf8
+            'charset' => 'utf8',
+            // 数据库表前缀
+            'prefix' => getenv('YY_CENTER_DB_PREFIX'),
+            // 断线重连
+            'break_reconnect' => true,
+            // 自定义分页类
+            'bootstrap' =>  '',
+            // 连接池配置
+            'pool' => [
+                'max_connections' => 5, // 最大连接数
+                'min_connections' => 1, // 最小连接数
+                'wait_timeout' => 3,    // 从连接池获取连接等待超时时间
+                'idle_timeout' => 60,   // 连接最大空闲时间,超过该时间会被回收
+                'heartbeat_interval' => 50, // 心跳检测间隔,需要小于60秒
+            ],
+        ],
+    ],
+];

+ 25 - 0
config/translation.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * Multilingual configuration
+ */
+return [
+    // Default language
+    'locale' => 'zh_CN',
+    // Fallback language
+    'fallback_locale' => ['zh_CN', 'en'],
+    // Folder where language files are stored
+    'path' => base_path() . '/resource/translations',
+];

+ 22 - 0
config/view.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * This file is part of webman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\view\Raw;
+use support\view\Twig;
+use support\view\Blade;
+use support\view\ThinkPHP;
+
+return [
+    'handler' => Raw::class
+];

+ 115 - 0
plugin/saiadmin/app/cache/UserAuthCache.php

@@ -0,0 +1,115 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\cache;
+
+use support\Cache;
+use plugin\saiadmin\app\logic\system\SystemMenuLogic;
+
+/**
+ * 用户权限缓存
+ */
+class UserAuthCache
+{
+    private string $prefix = 'user_auth_'; // 缓存前缀
+    private array $authCodeList = [];      // 全部权限列表
+    private string $cacheMd5Key = '';      // 权限文件MD5的key
+    private string $cacheAllKey = '';      // 全部权限的key
+    private string $cacheUrlKey = '';      // 管理员的url缓存key
+    private string $authMd5 = '';          // 权限文件MD5的值
+    private string $adminId = '';          // 管理员id
+
+    /**
+     * 初始化
+     * @param string $adminId
+     */
+    public function __construct(string $adminId = '')
+    {
+        $this->adminId = $adminId;
+
+        // 全部权限
+        $this->authCodeList = (new SystemMenuLogic())->getAllCode();
+        // 当前权限配置文件的md5
+        $this->authMd5 = md5(json_encode($this->authCodeList));
+
+        $this->cacheMd5Key = $this->prefix . 'md5';
+        $this->cacheAllKey = $this->prefix . 'all';
+        $this->cacheUrlKey = $this->prefix . 'url_' . $this->adminId;
+
+        $cacheAuthMd5 = Cache::get($this->cacheMd5Key);
+        $cacheAuth = Cache::get($this->cacheAllKey);
+
+        //权限配置和缓存权限对比,不一样说明权限配置文件已修改,清理缓存
+        if ($this->authMd5 !== $cacheAuthMd5 || empty($cacheAuth)) {
+            Cache::deleteMultiple([
+                $this->cacheMd5Key,
+                $this->cacheAllKey,
+                $this->cacheUrlKey
+            ]);
+        }
+    }
+
+    /**
+     * 获取全部权限uri
+     */
+    public function getAllUri()
+    {
+        // 从缓存获取,直接返回
+        $cacheAuth = Cache::get($this->cacheAllKey);
+        if ($cacheAuth) {
+            return $cacheAuth;
+        }
+
+        // 获取全部权限
+        $authList = (new SystemMenuLogic)->getAllCode();
+
+        // 保存到缓存并读取返回
+        Cache::set($this->cacheMd5Key, $this->authMd5);
+        Cache::set($this->cacheAllKey, $authList);
+        return $authList;
+    }
+
+    /**
+     * 获取管理权限uri
+     */
+    public function getAdminUri()
+    {
+        // 从缓存获取,直接返回
+        $urisAuth = Cache::get($this->cacheUrlKey);
+        if ($urisAuth) {
+            return $urisAuth;
+        }
+
+        // 获取角色关联的菜单id(菜单或权限)
+        $urisAuth = (new SystemMenuLogic)->getAuthByAdminId($this->adminId);
+        if (empty($urisAuth)) {
+            return [];
+        }
+
+        // 保存到缓存
+        Cache::set($this->cacheUrlKey, $urisAuth, 3600);
+        return $urisAuth;
+    }
+
+    /**
+     * 清理用户权限缓存
+     */
+    public function clearUserCache(): bool
+    {
+        Cache::delete($this->cacheUrlKey);
+        return true;
+    }
+
+    /**
+     * 清理缓存
+     */
+    public function clearAuthCache(): bool
+    {
+        Cache::delete($this->cacheAllKey);
+        return true;
+    }
+
+}

+ 76 - 0
plugin/saiadmin/app/cache/UserInfoCache.php

@@ -0,0 +1,76 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\cache;
+
+use plugin\saiadmin\app\model\system\SystemUser;
+use support\Cache;
+
+/**
+ * 用户信息缓存
+ */
+class UserInfoCache
+{
+    private string $prefix = 'user_info_';   // 缓存前置
+    private string $cacheUserKey = '';       // 管理员缓存key
+
+    private string $adminId = '';            // 管理员id
+
+    /**
+     * 初始化
+     * @param string $adminId
+     */
+    public function __construct(string $adminId = '')
+    {
+        $this->adminId = $adminId;
+        $this->cacheUserKey = $this->prefix  . $this->adminId;
+    }
+
+    /**
+     * 通过id获取缓存管理员信息
+     */
+    public function getUserInfo()
+    {
+        // 直接从缓存获取
+        $adminInfo = Cache::get($this->cacheUserKey);
+        if ($adminInfo) {
+            return $adminInfo;
+        }
+
+        // 获取信息并返回
+        $adminInfo = $this->setUserInfo();
+        if ($adminInfo) {
+            return $adminInfo;
+        }
+
+        return false;
+    }
+
+    /**
+     * 设置管理员信息
+     */
+    public function setUserInfo(): array
+    {
+        $admin = SystemUser::where('id', $this->adminId)->findOrEmpty();
+        $data = $admin->hidden(['password'])->toArray();
+        $data['roleList'] = $admin->roles->toArray() ?: [];
+        $data['postList'] = $admin->posts->toArray() ?: [];
+        $data['deptList'] = $admin->depts ? $admin->depts->toArray() : [];
+        // 保存到缓存
+        Cache::set($this->cacheUserKey, $data, 3600);
+        return $data;
+    }
+
+    /**
+     * 清理管理员信息缓存
+     */
+    public function clearUserInfo(): bool
+    {
+        Cache::delete($this->cacheUserKey);
+        return true;
+    }
+
+}

+ 271 - 0
plugin/saiadmin/app/controller/InstallController.php

@@ -0,0 +1,271 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller;
+
+use Throwable;
+use support\Request;
+use support\Response;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\basic\OpenController;
+
+/**
+ * 安装控制器
+ */
+class InstallController extends OpenController
+{
+    /**
+     * 不需要登录的方法
+     */
+    protected array $noNeedLogin = ['index', 'install'];
+
+    /**
+     * 应用名称
+     * @var string
+     */
+    protected string $app = 'saiadmin';
+
+    protected string $version = '5.0.0';
+
+    /**
+     * 安装首页
+     */
+    public function index()
+    {
+        $data['app'] = $this->app;
+        $data['version'] = config('plugin.saiadmin.app.version', $this->version);        
+
+        $env = base_path() . DIRECTORY_SEPARATOR .'.env';
+
+        clearstatcache();
+        if (is_file($env)) {
+            $data['error'] = '程序已经安装';
+            return view('install/error', $data);
+        }
+
+        if (!is_writable(base_path() . DIRECTORY_SEPARATOR . 'config')) {
+            $data['error'] = '权限认证失败';
+            return view('install/error', $data);
+        }
+
+        return view('install/index', $data);
+    }
+
+    /**
+     * 执行安装
+     */
+    public function install(Request $request)
+    {
+        $env = base_path() . DIRECTORY_SEPARATOR .'.env';
+
+        clearstatcache();
+        if (is_file($env)) {
+            return $this->fail('管理后台已经安装!如需重新安装,请删除根目录env配置文件并重启');
+        }
+
+        $user = $request->post('username');
+        $password = $request->post('password');
+        $database = $request->post('database');
+        $host = $request->post('host');
+        $port = (int)$request->post('port') ?: 3306;
+
+        try {
+            $db = $this->getPdo($host, $user, $password, $port);
+            $smt = $db->query("show databases like '$database'");
+            if (empty($smt->fetchAll())) {
+                $db->exec("create database $database CHARSET utf8mb4 COLLATE utf8mb4_general_ci");
+            }
+        } catch (\Throwable $e) {
+            $message = $e->getMessage();
+            if (stripos($message, 'Access denied for user')) {
+                return $this->fail('数据库用户名或密码错误');
+            }
+            if (stripos($message, 'Connection refused')) {
+                return $this->fail('Connection refused. 请确认数据库IP端口是否正确,数据库已经启动');
+            }
+            if (stripos($message, 'timed out')) {
+                return $this->fail('数据库连接超时,请确认数据库IP端口是否正确,安全组及防火墙已经放行端口');
+            }
+            throw $e;
+        }
+
+        $db->exec("use $database");
+
+        $smt = $db->query("show tables like 'sa_system_menu';");
+        $tables = $smt->fetchAll();
+        if (count($tables) > 0) {
+            return $this->fail('数据库已经安装,请勿重复安装');
+        }
+
+        $sql_file = base_path() . '/plugin/saiadmin/db/saiadmin-5.0.sql';
+        if (!is_file($sql_file)) {
+            return $this->fail('数据库SQL文件不存在');
+        }
+
+        $sql_query = file_get_contents($sql_file);
+
+        $db->exec($sql_query);
+
+        $this->generateConfig();
+
+        $env_config = <<<EOF
+# 数据库配置
+DB_TYPE = mysql
+DB_HOST = $host
+DB_PORT = $port
+DB_NAME = $database
+DB_USER = $user
+DB_PASSWORD = $password
+DB_PREFIX = 
+
+# 缓存方式
+CACHE_MODE = file
+
+# Redis配置
+REDIS_HOST = 127.0.0.1
+REDIS_PORT = 6379
+REDIS_PASSWORD = ''
+REDIS_DB = 0
+
+# 验证码配置
+CAPTCHA_MODE = cache
+EOF;
+        file_put_contents(base_path() . DIRECTORY_SEPARATOR . '.env', $env_config);
+
+        // 尝试reload
+        if (function_exists('posix_kill')) {
+            set_error_handler(function () {});
+            posix_kill(posix_getppid(), SIGUSR1);
+            restore_error_handler();
+        }
+
+        return $this->success('安装成功');
+    }
+
+    /**
+     * 生成配置文件 
+     */
+    protected function generateConfig()
+    {
+        // 1、think-orm配置文件
+        $think_orm_config = <<<EOF
+<?php
+
+return [
+    'default' => 'mysql',
+    'connections' => [
+        'mysql' => [
+            // 数据库类型
+            'type' => getenv('DB_TYPE'),
+            // 服务器地址
+            'hostname' => getenv('DB_HOST'),
+            // 数据库名
+            'database' => getenv('DB_NAME'),
+            // 数据库用户名
+            'username' => getenv('DB_USER'),
+            // 数据库密码
+            'password' => getenv('DB_PASSWORD'),
+            // 数据库连接端口
+            'hostport' => getenv('DB_PORT'),
+            // 数据库连接参数
+            'params' => [
+                // 连接超时3秒
+                \PDO::ATTR_TIMEOUT => 3,
+            ],
+            // 数据库编码默认采用utf8
+            'charset' => 'utf8',
+            // 数据库表前缀
+            'prefix' => getenv('DB_PREFIX'),
+            // 断线重连
+            'break_reconnect' => true,
+            // 自定义分页类
+            'bootstrap' =>  '',
+            // 连接池配置
+            'pool' => [
+                'max_connections' => 5, // 最大连接数
+                'min_connections' => 1, // 最小连接数
+                'wait_timeout' => 3,    // 从连接池获取连接等待超时时间
+                'idle_timeout' => 60,   // 连接最大空闲时间,超过该时间会被回收
+                'heartbeat_interval' => 50, // 心跳检测间隔,需要小于60秒
+            ],
+        ],
+    ],
+];
+EOF;
+        file_put_contents(base_path() . '/config/think-orm.php', $think_orm_config);
+
+        // 2、chache配置文件
+        $cache_config = <<<EOF
+<?php
+
+return [
+    'default' => getenv('CACHE_MODE'),
+    'stores' => [
+        'file' => [
+            'driver' => 'file',
+            'path' => runtime_path('cache')
+        ],
+        'redis' => [
+            'driver' => 'redis',
+            'connection' => 'default'
+        ],
+        'array' => [
+            'driver' => 'array'
+        ]
+    ]
+];
+EOF;
+        file_put_contents(base_path() . '/config/cache.php', $cache_config);        
+
+        // 3、redis配置文件
+        $redis_config = <<<EOF
+<?php
+
+return [
+    'default' => [
+        'password' => getenv('REDIS_PASSWORD'),
+        'host' => getenv('REDIS_HOST'),
+        'port' => getenv('REDIS_PORT'),
+        'database' => getenv('REDIS_DB'),
+        'pool' => [
+            'max_connections' => 5,
+            'min_connections' => 1,
+            'wait_timeout' => 3,
+            'idle_timeout' => 60,
+            'heartbeat_interval' => 50,
+        ],
+    ]
+];
+EOF;
+        file_put_contents(base_path() . '/config/redis.php', $redis_config);
+
+    }
+
+    /**
+     * 获取pdo连接
+     * @param $host
+     * @param $username
+     * @param $password
+     * @param $port
+     * @param $database
+     * @return \PDO
+     */
+    protected function getPdo($host, $username, $password, $port, $database = null): \PDO
+    {
+        $dsn = "mysql:host=$host;port=$port;";
+        if ($database) {
+            $dsn .= "dbname=$database";
+        }
+        $params = [
+            \PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4",
+            \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
+            \PDO::ATTR_EMULATE_PREPARES => false,
+            \PDO::ATTR_TIMEOUT => 5,
+            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+        ];
+        return new \PDO($dsn, $username, $password, $params);
+    }
+}

+ 60 - 0
plugin/saiadmin/app/controller/LoginController.php

@@ -0,0 +1,60 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller;
+
+use support\Request;
+use support\Response;
+use plugin\saiadmin\utils\Captcha;
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemUserLogic;
+
+/**
+ * 登录控制器
+ */
+class LoginController extends BaseController
+{
+
+    /**
+     * 不需要登录的方法
+     */
+    protected array $noNeedLogin = ['captcha', 'login'];
+
+    /**
+     * 获取验证码
+     */
+    public function captcha() : Response
+    {
+        $captcha = new Captcha();
+        $result = $captcha->imageCaptcha();
+        if ($result['result'] !== 1) {
+            return $this->fail($result['message']);
+        }
+        return $this->success($result);
+    }
+
+    /**
+     * 登录
+     * @param Request $request
+     * @return Response
+     */
+    public function login(Request $request): Response
+    {
+        $username = $request->post('username', '');
+        $password = $request->post('password', '');
+        $type = $request->post('type', 'pc');
+
+        $code = $request->post('code', '');
+        $uuid = $request->post('uuid', '');
+        $captcha = new Captcha();
+        if (!$captcha->checkCaptcha($uuid, $code)) {
+            return $this->fail('验证码错误');
+        }
+        $logic = new SystemUserLogic();
+        $data = $logic->login($username, $password, $type);
+        return $this->success($data);
+    }
+}

+ 316 - 0
plugin/saiadmin/app/controller/SystemController.php

@@ -0,0 +1,316 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller;
+
+use plugin\saiadmin\app\cache\UserAuthCache;
+use plugin\saiadmin\app\cache\UserInfoCache;
+use plugin\saiadmin\app\logic\system\SystemDictTypeLogic;
+use plugin\saiadmin\app\logic\system\SystemLoginLogLogic;
+use plugin\saiadmin\app\logic\system\SystemNoticeLogic;
+use plugin\saiadmin\app\logic\system\SystemOperLogLogic;
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemMenuLogic;
+use plugin\saiadmin\app\logic\system\SystemUserLogic;
+use plugin\saiadmin\app\logic\system\SystemAttachmentLogic;
+use plugin\saiadmin\utils\ServerMonitor;
+use support\Request;
+use support\Response;
+use plugin\saiadmin\utils\Arr;
+use Tinywan\Storage\Storage;
+
+/**
+ * 系统控制器
+ */
+class SystemController extends BaseController
+{
+
+    /**
+     * 用户信息
+     */
+    public function userInfo(): Response
+    {
+        $logic = new SystemMenuLogic();
+        $info['user'] = $this->adminInfo;
+        if ($this->adminInfo['id'] === 1) {
+            $info['codes'] = ['*'];
+            $info['roles'] = ['superAdmin'];
+            $info['routers'] = $logic->getAllMenus();
+        } else {
+            $info['codes'] = $logic->getAuthByAdminId($this->adminInfo['id']);
+            $info['roles'] = Arr::getArrayColumn($this->adminInfo['roleList'],'code');
+            $info['routers'] = $logic->getRoutersByAdminId($this->adminInfo['id']);
+        }
+        return $this->success($info);
+    }
+
+    /**
+     * 全部字典数据
+     */
+    public function dictAll(): Response
+    {
+        $logic = new SystemDictTypeLogic();
+        $query = $logic->where('status', 1)
+            ->field('id, name, code, remark')
+            ->with(['dicts' => function ($query) {
+                $query->where('status', 1)->withoutField(['created_by','updated_by','create_time','update_time'])->order('sort desc');
+            }]);
+        $data = $logic->getAll($query);
+        $dict = $this->packageDict($data, 'code');
+        return $this->success($dict);
+    }
+
+    /**
+     * 组合数据
+     * @param $array
+     * @param $field
+     * @return array
+     */
+    private function packageDict($array, $field): array
+    {
+        $result = [];
+        foreach ($array as $item) {
+            if (isset($item[$field])) {
+                if (isset($result[$item[$field]])) {
+                    $result[$item[$field]] = [($result[$item[$field]])];
+                    $result[$item[$field]][] = $item['dicts'];
+                } else {
+                    $result[$item[$field]] = $item['dicts'];
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 获取资源列表
+     * @param Request $request
+     * @return Response
+     */
+    public function getResourceList(Request $request): Response
+    {
+        $logic = new SystemAttachmentLogic();
+        $where = $request->more([
+            ['origin_name', ''],
+            ['storage_mode', ''],
+            ['mime_type', ''],
+        ]);
+        $query = $logic->search($where);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 获取用户列表
+     * @param Request $request
+     * @return Response
+     */
+    public function getUserList(Request $request): Response
+    {
+        $logic = new SystemUserLogic();
+        $where = $request->more([
+            ['username', ''],
+            ['nickname', ''],
+            ['phone', ''],
+            ['email', ''],
+            ['dept_id', ''],
+            ['role_id', ''],
+            ['post_id', ''],
+        ]);
+        $query = $logic->search($where);
+        $query->field('id, username, nickname, phone, email, create_time');
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 根据id获取用户信息
+     * @param Request $request
+     * @return Response
+     */
+    public function getUserInfoByIds(Request $request): Response
+    {
+        $ids = $request->input('ids');
+        $logic = new SystemUserLogic();
+        $data = $logic->where('id', 'in', $ids)
+            ->field('id, username, nickname, phone, email, create_time')
+            ->select()
+            ->toArray();
+        return $this->success($data);
+    }
+
+    /**
+     * 下载网络图片
+     */
+    public function saveNetworkImage(Request $request): Response
+    {
+        $url = $request->input('url', '');
+        $config = Storage::getConfig('local');
+        $logic = new SystemAttachmentLogic();
+        $data = $logic->saveNetworkImage($url, $config);
+        return $this->success($data, '操作成功');
+    }
+
+    /**
+     * 上传图片
+     */
+    public function uploadImage(Request $request): Response
+    {
+        $logic = new SystemAttachmentLogic();
+        $type = $request->input('mode', 'system');
+        if ($type == 'local') {
+            return $this->success($logic->uploadBase('image', true));
+        }
+        return $this->success($logic->uploadBase('image'));
+    }
+
+    /**
+     * 上传文件
+     */
+    public function uploadFile(Request $request): Response
+    {
+        $logic = new SystemAttachmentLogic();
+        $type = $request->input('mode', 'system');
+        if ($type == 'local') {
+            return $this->success($logic->uploadBase('file', true));
+        }
+        return $this->success($logic->uploadBase('file'));
+    }
+
+    /**
+     * 根据id下载资源
+     * @param Request $request
+     * @return Response
+     */
+    public function downloadById(Request $request): Response
+    {
+        $id = $request->input('id');
+        $logic = new SystemAttachmentLogic();
+        $model = $logic->find($id);
+        $object_name = $model->object_name;
+        return response()->download($model->storage_path, $object_name);
+    }
+
+    /**
+     * 根据hash下载资源
+     * @param Request $request
+     * @return Response
+     */
+    public function downloadByHash(Request $request): Response
+    {
+        $hash = $request->input('hash');
+        $logic = new SystemAttachmentLogic();
+        $model = $logic->where('hash', $hash)->find();
+        $object_name = $model->object_name;
+        return response()->download($model->storage_path, $object_name);
+    }
+
+    /**
+     * 获取登录日志
+     * @return Response
+     */
+    public function getLoginLogList(): Response
+    {
+        $logic = new SystemLoginLogLogic();
+        $logic->init($this->adminInfo);
+        $query = $logic->search(['username' => $this->adminName]);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 获取操作日志
+     * @return Response
+     */
+    public function getOperationLogList(): Response
+    {
+        $logic = new SystemOperLogLogic();
+        $logic->init($this->adminInfo);
+        $query = $logic->search(['username' => $this->adminName])->hidden(['request_data', 'delete_time']);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 获取服务器信息
+     * @return Response
+     */
+    public function getServerInfo(): Response
+    {
+        $service = new ServerMonitor();
+        return $this->success([
+            'cpu' => $service->getCpuInfo(),
+            'memory' => $service->getMemInfo(),
+            'phpenv' => $service->getPhpAndEnvInfo(),
+        ]);
+    }
+
+    /**
+     * 基本统计
+     * @return Response
+     */
+    public function statistics(): Response
+    {
+        $userLogic = new SystemUserLogic();
+        $userCount = $userLogic->count('id');
+        $uploadLogic = new SystemAttachmentLogic();
+        $attachCount = $uploadLogic->count('id');
+        $loginLogic = new SystemLoginLogLogic();
+        $loginCount = $loginLogic->count('id');
+        $operLogic = new SystemOperLogLogic();
+        $operCount = $operLogic->count('id');
+        return $this->success([
+            'user' => $userCount,
+            'attach' => $attachCount,
+            'login' => $loginCount,
+            'operate' => $operCount,
+        ]);
+    }
+
+    /**
+     * 登录统计图表
+     * @return Response
+     */
+    public function loginChart(): Response
+    {
+        $logic = new SystemLoginLogLogic();
+        $data = $logic->loginChart();
+        return $this->success($data);
+    }
+
+    /**
+     * 系统通知
+     * @param Request $request
+     * @return Response
+     */
+    public function systemNotice(Request $request) : Response
+    {
+        $where = $request->more([
+            ['title', ''],
+            ['type', ''],
+            ['create_time', ''],
+        ]);
+        $logic = new SystemNoticeLogic();
+        $logic->init($this->adminInfo);
+        $query = $logic->search($where);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+	
+	/**
+     * 清除缓存
+     * @return Response
+     */
+    public function clearAllCache() : Response
+    {
+        $userInfoCache = new UserInfoCache($this->adminId);
+        $userInfoCache->clearUserInfo();
+        $userAuthCache = new UserAuthCache($this->adminId);
+        $userAuthCache->clearUserCache();
+        return $this->success([], '清除缓存成功!');
+    }
+
+}

+ 139 - 0
plugin/saiadmin/app/controller/system/DataBaseController.php

@@ -0,0 +1,139 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\app\logic\system\DatabaseLogic;
+use plugin\saiadmin\basic\BaseController;
+use support\Request;
+use support\Response;
+
+/**
+ * 数据表维护控制器
+ */
+class DataBaseController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new DatabaseLogic();
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request): Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['source', ''],
+        ]);
+        $data = $this->logic->getList($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 数据源列表
+     * @return Response
+     */
+    public function source(): Response
+    {
+        $list = dbSourceList();
+        return $this->success($list);
+    }
+
+    /**
+     * 回收站数据
+     * @param Request $request
+     * @return Response
+     */
+    public function recycle(Request $request): Response
+    {
+        $table = $request->input('table', '');
+        $data = $this->logic->recycleData($table);
+        return $this->success($data);
+    }
+
+    /**
+     * 销毁数据
+     * @param Request $request
+     * @return Response
+     */
+    public function delete(Request $request): Response
+    {
+        $table = $request->input('table', '');
+        $ids = $request->input('ids', '');
+        if (!empty($ids)) {
+            $result = $this->logic->delete($table, $ids);
+            if (!$result) {
+                return $this->fail('操作失败');
+            }
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('参数错误,请检查');
+        }
+    }
+
+    /**
+     * 恢复数据
+     * @param Request $request
+     * @return Response
+     */
+    public function recovery(Request $request): Response
+    {
+        $table = $request->input('table', '');
+        $ids = $request->input('ids', '');
+        if (!empty($ids)) {
+            $result = $this->logic->recovery($table, $ids);
+            if (!$result) {
+                return $this->fail('操作失败');
+            }
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('参数错误,请检查');
+        }
+    }
+
+    /**
+     * 获取表字段信息
+     * @param Request $request
+     * @return Response
+     */
+    public function detailed(Request $request): Response
+    {
+        $table = $request->input('table', '');
+        $data = $this->logic->getColumnList($table, '');
+        return $this->success($data);
+    }
+
+    /**
+     * 优化表
+     * @param Request $request
+     * @return Response
+     */
+    public function optimize(Request $request): Response
+    {
+        $tables = $request->input('tables', []);
+        $this->logic->optimizeTable($tables);
+        return $this->success('优化成功');
+    }
+
+    /**
+     * 清理表碎片
+     */
+    public function fragment(Request $request): Response
+    {
+        $tables = $request->input('tables', []);
+        $this->logic->fragmentTable($tables);
+        return $this->success('清理成功');
+    }
+
+}

+ 46 - 0
plugin/saiadmin/app/controller/system/SystemAttachmentController.php

@@ -0,0 +1,46 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemAttachmentLogic;
+use support\Request;
+use support\Response;
+
+/**
+ * 附件管理控制器
+ */
+class SystemAttachmentController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemAttachmentLogic();
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['origin_name', ''],
+            ['storage_mode', ''],
+            ['mime_type', ''],
+            ['create_time', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+}

+ 95 - 0
plugin/saiadmin/app/controller/system/SystemConfigController.php

@@ -0,0 +1,95 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemConfigLogic;
+use plugin\saiadmin\app\logic\system\SystemConfigGroupLogic;
+use plugin\saiadmin\app\validate\system\SystemConfigValidate;
+use support\Cache;
+use support\Request;
+use support\Response;
+
+/**
+ * 配置项数据控制器
+ */
+class SystemConfigController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemConfigLogic();
+        $this->validate = new SystemConfigValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['group_id', ''],
+            ['name', ''],
+            ['key', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getAll($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 修改配置内容
+     * @param Request $request
+     * @return Response
+     */
+    public function batchUpdate(Request $request): Response
+    {
+        $group_id = $request->post('group_id');
+        $config = $request->post('config');
+        $groupLogic = new SystemConfigGroupLogic();
+        $group = $groupLogic->where('id', $group_id)->findOrEmpty();
+        if ($group->isEmpty()) {
+            $this->fail('配置分组查找失败');
+        }
+        $saveData = [];
+        foreach ($config as $key => $value) {
+            $saveData[] = [
+                'id' => $value['id'],
+                'sort' => $value['sort'],
+                'name' => $value['name'],
+                'key' => $value['key'],
+                'value' => $value['value']
+            ];
+        }
+        $this->logic->saveAll($saveData);
+        Cache::set('cfg_'.$group->code, $saveData);
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        if (in_array($type, ['save', 'update'])) {
+            $groupLogic = new SystemConfigGroupLogic();
+            $group = $groupLogic->findOrEmpty(request()->input('group_id'));
+            if (!$group->isEmpty()) {
+                Cache::delete('cfg_' . $group['code']);
+            }
+        }
+    }
+
+}

+ 107 - 0
plugin/saiadmin/app/controller/system/SystemConfigGroupController.php

@@ -0,0 +1,107 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemConfigGroupLogic;
+use plugin\saiadmin\app\validate\system\SystemConfigGroupValidate;
+use plugin\saiadmin\utils\Arr;
+use support\Cache;
+use support\Request;
+use support\Response;
+use plugin\saiadmin\service\EmailService;
+use plugin\saiadmin\app\model\system\SystemMail;
+
+/**
+ * 配置控制器
+ */
+class SystemConfigGroupController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemConfigGroupLogic();
+        $this->validate = new SystemConfigGroupValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['code', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getAll($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 邮件测试
+     * @param Request $request
+     * @return Response
+     */
+    public function email(Request $request) : Response
+    {
+        $email = $request->input('email', '');
+        if (empty($email)) {
+            return $this->fail('请输入邮箱');
+        }
+        $subject = "测试邮件";
+        $code = "9527";
+        $content = "<h1>验证码:{code}</h1><p>这是一封测试邮件,请忽略</p>";
+        $template = [
+            'code' => $code
+        ];
+        $config = EmailService::getConfig();
+        $model = SystemMail::create([
+            'gateway' => Arr::getConfigValue($config,'Host'),
+            'from' => Arr::getConfigValue($config,'From'),
+            'email' => $email,
+            'code' => $code,
+        ]);
+        try {
+            $result = EmailService::sendByTemplate($email, $subject, $content, $template);
+            if (!empty($result)) {
+                $model->status = 'failure';
+                $model->response = $result;
+                $model->save();
+                return $this->fail('发送失败,请查看日志');
+            } else {
+                $model->status = 'success';
+                $model->save();
+                return $this->success([], '发送成功');
+            }
+        } catch (\Exception $e) {
+            $model->status = 'failure';
+            $model->response = $e->getMessage();
+            $model->save();
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        if (in_array($type, ['save', 'update'])) {
+            Cache::delete('cfg_' . request()->input('code'));
+        }
+    }
+
+}

+ 151 - 0
plugin/saiadmin/app/controller/system/SystemDeptController.php

@@ -0,0 +1,151 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\app\cache\UserInfoCache;
+use plugin\saiadmin\app\model\system\SystemUser;
+use plugin\saiadmin\app\validate\system\SystemDeptValidate;
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemDeptLogic;
+use support\Request;
+use support\Response;
+
+/**
+ * 部门控制器
+ */
+class SystemDeptController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemDeptLogic();
+        $this->validate = new SystemDeptValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['leader', ''],
+            ['phone', ''],
+            ['status', ''],
+        ]);
+        $data = $this->logic->tree($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        // 批量清理用户缓存
+        if ($type == 'update') {
+            $dept_id = request()->input('id', '');
+            $userIds = SystemUser::where('dept_id', $dept_id)->column('id');
+            foreach ($userIds as $userId) {
+                $userInfoCache = new UserInfoCache($userId);
+                $userInfoCache->clearUserInfo();
+            }
+        }
+        if ($type == 'destroy') {
+            $dept_ids = request()->input('ids', '');
+            if (is_array($dept_ids)) {
+                $userIds = SystemUser::whereIn('dept_id', $dept_ids)->column('id');
+                foreach ($userIds as $userId) {
+                    $userInfoCache = new UserInfoCache($userId);
+                    $userInfoCache->clearUserInfo();
+                }
+            }
+        }
+    }
+
+    /**
+     * 可操作部门
+     * @param Request $request
+     * @return Response
+     */
+    public function accessDept(Request $request) : Response
+    {
+        $where = [];
+        $data = $this->logic->accessDept($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 部门领导列表
+     * @param Request $request
+     * @return Response
+     */
+    public function leaders(Request $request) : Response
+    {
+        $where = $request->more([
+            ['dept_id', ''],
+            ['username', ''],
+            ['nickname', ''],
+            ['status', ''],
+        ]);
+        $data = $this->logic->leaders($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 添加部门领导
+     * @param Request $request
+     * @return Response
+     */
+    public function addLeader(Request $request) : Response
+    {
+        $id = $request->post('id');
+        $users = $request->post('users');
+        if (empty($users)) {
+            return $this->fail('请选择人员');
+        }
+        $this->logic->addLeader($id, $users);
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 删除部门领导
+     * @param Request $request
+     * @return Response
+     */
+    public function delLeader(Request $request) : Response
+    {
+        $id = $request->post('id');
+        $ids = $request->post('ids');
+        if (!empty($id)) {
+            $this->logic->delLeader($id, $ids);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('参数错误,请检查');
+        }
+    }
+
+    /**
+     * 获取部门游戏列表
+     * @param Request $request
+     * @return Response
+     */
+    public function getGameListByDeptId(Request $request) : Response
+    {
+        $dept_id = $request->more(['dept_id']);
+        $data = $this->logic->getGameListByDeptId($dept_id);
+        return $this->success($data);
+    }
+}

+ 100 - 0
plugin/saiadmin/app/controller/system/SystemDictDataController.php

@@ -0,0 +1,100 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemDictDataLogic;
+use plugin\saiadmin\app\validate\system\SystemDictDataValidate;
+use support\Cache;
+use support\Request;
+use support\Response;
+
+/**
+ * 字典数据控制器
+ */
+class SystemDictDataController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemDictDataLogic();
+        $this->validate = new SystemDictDataValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['label', ''],
+            ['value', ''],
+            ['type_id', ''],
+            ['status', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 修改状态
+     * @param Request $request
+     * @return Response
+     */
+    public function changeStatus(Request $request) : Response
+    {
+        $id = $request->input('id', '');
+        $status = $request->input('status', 1);
+        $model = $this->logic->findOrEmpty($id);
+        if ($model->isEmpty()) {
+            return $this->fail('未查找到信息');
+        }
+        $result = $model->save(['status' => $status]);
+        if ($result) {
+            $this->afterChange('changeStatus', $model);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('操作失败');
+        }
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        if (in_array($type, ['save', 'update'])) {
+            Cache::delete(request()->input('code'));
+        }
+        if ($type === 'changeStatus') {
+            $id = request()->input('id', '');
+            $info = $this->logic->findOrEmpty($id);
+            if (!$info->isEmpty()) {
+                Cache::delete($info->code);
+            }
+        }
+        if ($type === 'destroy') {
+            $ids = request()->input('ids', '');
+            if (is_array($ids)) {
+                $id = $ids[0];
+                $info = $this->logic->findOrEmpty($id);
+                if (!$info->isEmpty()) {
+                    Cache::delete($info->code);
+                }
+            }
+        }
+    }
+}

+ 90 - 0
plugin/saiadmin/app/controller/system/SystemDictTypeController.php

@@ -0,0 +1,90 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemDictTypeLogic;
+use plugin\saiadmin\app\validate\system\SystemDictTypeValidate;
+use support\Cache;
+use support\Request;
+use support\Response;
+
+/**
+ * 字典类型控制器
+ */
+class SystemDictTypeController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemDictTypeLogic();
+        $this->validate = new SystemDictTypeValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['code', ''],
+            ['status', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 修改状态
+     * @param Request $request
+     * @return Response
+     */
+    public function changeStatus(Request $request) : Response
+    {
+        $id = $request->input('id', '');
+        $status = $request->input('status', 1);
+        $model = $this->logic->findOrEmpty($id);
+        if ($model->isEmpty()) {
+            return $this->fail('未查找到信息');
+        }
+        $result = $model->save(['status' => $status]);
+        if ($result) {
+            $this->afterChange('changeStatus', $model);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('操作失败');
+        }
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        if (in_array($type, ['save', 'update'])) {
+            Cache::delete(request()->input('code'));
+        }
+        if ($type === 'changeStatus') {
+            $id = request()->input('id', '');
+            $info = $this->logic->findOrEmpty($id);
+            if (!$info->isEmpty()) {
+                Cache::delete($info->code);
+            }
+        }
+    }
+
+}

+ 94 - 0
plugin/saiadmin/app/controller/system/SystemLogController.php

@@ -0,0 +1,94 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemLoginLogLogic;
+use plugin\saiadmin\app\logic\system\SystemOperLogLogic;
+use support\Request;
+use support\Response;
+
+/**
+ * 日志控制器
+ */
+class SystemLogController extends BaseController
+{
+
+    /**
+     * 登录日志列表
+     * @param Request $request
+     * @return Response
+     */
+    public function getLoginLogPageList(Request $request) : Response
+    {
+        $where = $request->more([
+            ['login_time', ''],
+            ['username', ''],
+            ['status', ''],
+            ['ip', ''],
+        ]);
+        $logic = new SystemLoginLogLogic();
+        $query = $logic->search($where);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 删除登录日志
+     * @param Request $request
+     * @return Response
+     */
+    public function deleteLoginLog(Request $request) : Response
+    {
+        $ids = $request->input('ids', '');
+        $logic = new SystemLoginLogLogic();
+        if (!empty($ids)) {
+            $logic->destroy($ids);
+            return $this->success('删除成功');
+        } else {
+            return $this->fail('参数错误,请检查');
+        }
+    }
+
+    /**
+     * 操作日志列表
+     * @param Request $request
+     * @return Response
+     */
+    public function getOperLogPageList(Request $request) : Response
+    {
+        $where = $request->more([
+            ['create_time', ''],
+            ['username', ''],
+            ['service_name', ''],
+            ['ip', ''],
+        ]);
+        $logic = new SystemOperLogLogic();
+        $logic->init($this->adminInfo);
+        $query = $logic->search($where);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 删除操作日志
+     * @param Request $request
+     * @return Response
+     */
+    public function deleteOperLog(Request $request) : Response
+    {
+        $ids = $request->input('ids', '');
+        $logic = new SystemOperLogLogic();
+        if (!empty($ids)) {
+            $logic->destroy($ids);
+            return $this->success('删除成功');
+        } else {
+            return $this->fail('参数错误,请检查');
+        }
+    }
+
+}

+ 50 - 0
plugin/saiadmin/app/controller/system/SystemMailController.php

@@ -0,0 +1,50 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemMailLogic;
+use plugin\saiadmin\app\validate\system\SystemMailValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 邮件记录控制器
+ */
+class SystemMailController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemMailLogic();
+        $this->validate = new SystemMailValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request): Response
+    {
+        $where = $request->more([
+            ['gateway', ''],
+            ['from', ''],
+            ['code', ''],
+            ['email', ''],
+            ['status', ''],
+            ['create_time', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+}

+ 63 - 0
plugin/saiadmin/app/controller/system/SystemMenuController.php

@@ -0,0 +1,63 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemMenuLogic;
+use plugin\saiadmin\app\validate\system\SystemMenuValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 菜单控制器
+ */
+class SystemMenuController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemMenuLogic();
+        $this->validate = new SystemMenuValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['code', ''],
+            ['is_hidden', ''],
+            ['status', ''],
+        ]);
+        $data = $this->logic->tree($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 可操作菜单
+     * @param Request $request
+     * @return Response
+     */
+    public function accessMenu(Request $request) : Response
+    {
+        $where = [];
+        if ($this->adminId > 1) {
+            $data = $this->logic->auth();
+        } else {
+            $data = $this->logic->tree($where);
+        }
+        return $this->success($data);
+    }
+
+}

+ 47 - 0
plugin/saiadmin/app/controller/system/SystemNoticeController.php

@@ -0,0 +1,47 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemNoticeLogic;
+use plugin\saiadmin\app\validate\system\SystemNoticeValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 系统公告控制器
+ */
+class SystemNoticeController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemNoticeLogic();
+        $this->validate = new SystemNoticeValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['title', ''],
+            ['type', ''],
+            ['create_time', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+}

+ 154 - 0
plugin/saiadmin/app/controller/system/SystemPostController.php

@@ -0,0 +1,154 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\app\cache\UserInfoCache;
+use plugin\saiadmin\app\model\system\SystemUserPost;
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\system\SystemPostLogic;
+use plugin\saiadmin\app\validate\system\SystemPostValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 岗位信息控制器
+ */
+class SystemPostController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemPostLogic();
+        $this->validate = new SystemPostValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['code', ''],
+            ['status', ''],
+            ['create_time', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 修改状态
+     * @param Request $request
+     * @return Response
+     */
+    public function changeStatus(Request $request) : Response
+    {
+        $id = $request->input('id', '');
+        $status = $request->input('status', 1);
+        $model = $this->logic->findOrEmpty($id);
+        if ($model->isEmpty()) {
+            return $this->fail('未查找到信息');
+        }
+        $result = $model->save(['status' => $status]);
+        if ($result) {
+            $this->afterChange('changeStatus', $model);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('操作失败');
+        }
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        // 批量清理用户缓存
+        if ($type == 'update') {
+            $post_id = request()->input('id', '');
+            $userIds = SystemUserPost::where('post_id', $post_id)->column('user_id');
+            $userIds = array_unique($userIds);
+            foreach ($userIds as $userId) {
+                $userInfoCache = new UserInfoCache($userId);
+                $userInfoCache->clearUserInfo();
+            }
+        }
+        if ($type == 'destroy') {
+            $post_ids = request()->input('ids', '');
+            if (is_array($post_ids)) {
+                $userIds = SystemUserPost::whereIn('post_id', $post_ids)->column('user_id');
+                $userIds = array_unique($userIds);
+                foreach ($userIds as $userId) {
+                    $userInfoCache = new UserInfoCache($userId);
+                    $userInfoCache->clearUserInfo();
+                }
+            }
+        }
+    }
+
+    /**
+     * 可操作岗位
+     * @param Request $request
+     * @return Response
+     */
+    public function accessPost(Request $request) : Response
+    {
+        $where = [];
+        $data = $this->logic->accessPost($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 下载导入模板
+     * @return Response
+     */
+    public function downloadTemplate() : Response
+    {
+        $file_name = "template.xlsx";
+        return downloadFile($file_name);
+    }
+
+    /**
+     * 导入数据
+     * @param Request $request
+     * @return Response
+     */
+    public function import(Request $request) : Response
+    {
+        $file = current($request->file());
+        if (!$file || !$file->isValid()) {
+            return $this->fail('未找到上传文件');
+        }
+        $this->logic->import($file);
+        return $this->success('导入成功');
+    }
+
+    /**
+     * 导出数据
+     * @param Request $request
+     * @return Response
+     */
+    public function export(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['code', ''],
+            ['status', ''],
+        ]);
+        return $this->logic->export($where);
+    }
+}

+ 117 - 0
plugin/saiadmin/app/controller/system/SystemRoleController.php

@@ -0,0 +1,117 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\app\model\system\SystemUserRole;
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\cache\UserInfoCache;
+use plugin\saiadmin\app\model\system\SystemUser;
+use plugin\saiadmin\app\validate\system\SystemRoleValidate;
+use plugin\saiadmin\app\logic\system\SystemRoleLogic;
+use support\Request;
+use support\Response;
+
+/**
+ * 角色控制器
+ */
+class SystemRoleController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemRoleLogic();
+        $this->validate = new SystemRoleValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['code', ''],
+            ['status', ''],
+        ]);
+        $data = $this->logic->tree($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 可操作角色
+     * @param Request $request
+     * @return Response
+     */
+    public function accessRole(Request $request) : Response
+    {
+        $where = [];
+        $data = $this->logic->accessRole($where);
+        return $this->success($data);
+    }
+
+    /**
+     * 菜单权限
+     * @param Request $request
+     * @return Response
+     */
+    public function menuPermission(Request $request) : Response
+    {
+        $id = $request->get('id');
+        $menu_ids = $request->post('menu_ids');
+        $this->logic->saveMenuPermission($id, $menu_ids);
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 根据角色获取菜单
+     * @param Request $request
+     * @return Response
+     */
+    public function getMenuByRole(Request $request) : Response
+    {
+        $id = $request->get('id');
+        $data = $this->logic->getMenuByRole($id);
+        return $this->success($data);
+    }
+
+    /**
+     * 数据改变后执行
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        // 批量清理用户缓存
+        if ($type == 'update') {
+            $role_id = request()->input('id', '');
+            $userIds = SystemUserRole::where('role_id', $role_id)->column('user_id');
+            $userIds = array_unique($userIds);
+            foreach ($userIds as $userId) {
+                $userInfoCache = new UserInfoCache($userId);
+                $userInfoCache->clearUserInfo();
+            }
+        }
+        if ($type == 'destroy') {
+            $role_ids = request()->input('ids', '');
+            if (is_array($role_ids)) {
+                $userIds = SystemUserRole::whereIn('role_id', $role_ids)->column('user_id');
+                $userIds = array_unique($userIds);
+                foreach ($userIds as $userId) {
+                    $userInfoCache = new UserInfoCache($userId);
+                    $userInfoCache->clearUserInfo();
+                }
+            }
+        }
+    }
+
+}

+ 170 - 0
plugin/saiadmin/app/controller/system/SystemUserController.php

@@ -0,0 +1,170 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\system;
+
+use plugin\saiadmin\app\cache\UserAuthCache;
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\cache\UserInfoCache;
+use plugin\saiadmin\app\logic\system\SystemUserLogic;
+use plugin\saiadmin\app\validate\system\SystemUserValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 用户信息控制器
+ */
+class SystemUserController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new SystemUserLogic();
+        $this->validate = new SystemUserValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['username', ''],
+            ['phone', ''],
+            ['email', ''],
+            ['status', ''],
+            ['dept_id', ''],
+            ['create_time', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $query->auth([
+            'id' => $this->adminId,
+            'dept' => $this->adminInfo['deptList']
+        ]);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 修改状态
+     * @param Request $request
+     * @return Response
+     */
+    public function changeStatus(Request $request) : Response
+    {
+        $id = $request->input('id', '');
+        $status = $request->input('status', 1);
+        $model = $this->logic->findOrEmpty($id);
+        if ($model->isEmpty()) {
+            return $this->fail('未查找到信息');
+        }
+        $result = $model->save(['status' => $status]);
+        if ($result) {
+            $this->afterChange('changeStatus', $model);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('操作失败');
+        }
+    }
+
+    /**
+     * 更新资料
+     * @param Request $request
+     * @return Response
+     */
+    public function updateInfo(Request $request) : Response
+    {
+        $data = $request->post();
+        unset($data['deptList']);
+        unset($data['postList']);
+        unset($data['roleList']);
+        $result = $this->logic->update($data, ['id' => $this->adminId], ['nickname', 'phone', 'signed', 'email', 'avatar', 'backend_setting']);
+        if ($result) {
+            $userInfoCache = new UserInfoCache($this->adminId);
+            $userInfoCache->clearUserInfo();
+            $userAuthCache = new UserAuthCache($this->adminId);
+            $userAuthCache->clearUserCache();
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('操作失败');
+        }
+    }
+
+    /**
+     * 修改密码
+     * @param Request $request
+     * @return Response
+     */
+    public function modifyPassword(Request $request) : Response
+    {
+        $oldPassword = $request->input('oldPassword');
+        $newPassword = $request->input('newPassword');
+        $this->logic->modifyPassword($this->adminId, $oldPassword, $newPassword);
+        $userInfoCache = new UserInfoCache($this->adminId);
+        $userInfoCache->clearUserInfo();
+        $userAuthCache = new UserAuthCache($this->adminId);
+        $userAuthCache->clearUserCache();
+        return $this->success('修改成功');
+    }
+
+    /**
+     * 清理用户缓存
+     * @param Request $request
+     * @return Response
+     */
+    public function clearCache(Request $request) : Response
+    {
+        $id = $request->post('id', '');
+        $userInfoCache = new UserInfoCache($id);
+        $userInfoCache->clearUserInfo();
+        $userAuthCache = new UserAuthCache($id);
+        $userAuthCache->clearUserCache();
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 重置密码
+     * @param Request $request
+     * @return Response
+     */
+    public function initUserPassword(Request $request) : Response
+    {
+        $id = $request->post('id', '');
+        if ($id == 1) {
+            return $this->fail('超级管理员不允许重置密码');
+        }
+        $data = ['password' => password_hash('sai123456', PASSWORD_DEFAULT)];
+        $this->logic->authEdit($id, $data);
+        $userInfoCache = new UserInfoCache($id);
+        $userInfoCache->clearUserInfo();
+        $userAuthCache = new UserAuthCache($id);
+        $userAuthCache->clearUserCache();
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 设置首页
+     * @param Request $request
+     * @return Response
+     */
+    public function setHomePage(Request $request) : Response
+    {
+        $id = $request->post('id', '');
+        $dashboard = $request->post('dashboard', '');
+        $data = ['dashboard' => $dashboard];
+        $this->logic->authEdit($id, $data);
+        $userInfoCache = new UserInfoCache($id);
+        $userInfoCache->clearUserInfo();
+        $userAuthCache = new UserAuthCache($id);
+        $userAuthCache->clearUserCache();
+        return $this->success('操作成功');
+    }
+}

+ 133 - 0
plugin/saiadmin/app/controller/tool/CrontabController.php

@@ -0,0 +1,133 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\tool;
+
+use plugin\saiadmin\app\logic\tool\CrontabLogic;
+use plugin\saiadmin\app\logic\tool\CrontabLogLogic;
+use plugin\saiadmin\app\validate\tool\CrontabValidate;
+use plugin\saiadmin\basic\BaseController;
+use support\Request;
+use support\Response;
+
+/**
+ * 定时任务控制器
+ */
+class CrontabController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new CrontabLogic();
+        $this->validate = new CrontabValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request) : Response
+    {
+        $where = $request->more([
+            ['name', ''],
+            ['type', ''],
+            ['status', ''],
+            ['create_time', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 修改状态
+     * @param Request $request
+     * @return Response
+     */
+    public function changeStatus(Request $request) : Response
+    {
+        $id = $request->input('id', '');
+        $status = $request->input('status', 1);
+        $model = $this->logic->findOrEmpty($id);
+        if ($model->isEmpty()) {
+            return $this->fail('未查找到信息');
+        }
+        $result = $model->save(['status' => $status]);
+        if ($result) {
+            $this->afterChange('changeStatus', $model);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('操作失败');
+        }
+    }
+
+    /**
+     * 更新crontab任务
+     * @param $type
+     * @param $args
+     * @return void
+     */
+    protected function afterChange($type, $args): void
+    {
+        if (in_array($type, ['save', 'update', 'changeStatus'])) {
+            $task = new \plugin\saiadmin\process\Task();
+            $task->reload();
+        }
+    }
+
+    /**
+     * 执行定时任务
+     * @param Request $request
+     * @return Response
+     */
+    public function run(Request $request) : Response
+    {
+        $id = $request->input('id', '');
+        $result = $this->logic->run($id);
+        if ($result) {
+            return $this->success('执行成功');
+        } else {
+            return $this->fail('执行失败');
+        }
+    }
+
+    /**
+     * 定时任务日志
+     * @param Request $request
+     * @return Response
+     */
+    public function logPageList(Request $request) : Response
+    {
+        $where = $request->more([
+            ['crontab_id', ''],
+        ]);
+        $logic = new CrontabLogLogic();
+        $query = $logic->search($where);
+        $data = $logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 删除定时任务日志数据
+     * @param Request $request
+     * @return Response
+     */
+    public function deleteCrontabLog(Request $request) : Response
+    {
+        $ids = $request->input('ids', '');
+        if (!empty($ids)) {
+            $logic = new CrontabLogLogic();
+            $logic->destroy($ids);
+            return $this->success('操作成功');
+        } else {
+            return $this->fail('参数错误,请检查');
+        }
+    }
+}

+ 112 - 0
plugin/saiadmin/app/controller/tool/GenerateTablesController.php

@@ -0,0 +1,112 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\controller\tool;
+
+use plugin\saiadmin\basic\BaseController;
+use plugin\saiadmin\app\logic\tool\GenerateTablesLogic;
+use plugin\saiadmin\app\validate\tool\GenerateTablesValidate;
+use support\Request;
+use support\Response;
+
+/**
+ * 代码生成控制器
+ */
+class GenerateTablesController extends BaseController
+{
+    /**
+     * 构造
+     */
+    public function __construct()
+    {
+        $this->logic = new GenerateTablesLogic();
+        $this->validate = new GenerateTablesValidate;
+        parent::__construct();
+    }
+
+    /**
+     * 数据列表
+     * @param Request $request
+     * @return Response
+     */
+    public function index(Request $request): Response
+    {
+        $where = $request->more([
+            ['table_name', ''],
+        ]);
+        $query = $this->logic->search($where);
+        $data = $this->logic->getList($query);
+        return $this->success($data);
+    }
+
+    /**
+     * 装载数据表
+     * @param Request $request
+     * @return Response
+     */
+    public function loadTable(Request $request): Response
+    {
+        $names = $request->input('names', []);
+        $source = $request->input('source', '');
+        $this->logic->loadTable($names, $source);
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 同步数据表字段信息
+     * @param Request $request
+     * @return Response
+     */
+    public function sync(Request $request): Response
+    {
+        $id = $request->input('id', '');
+        $this->logic->sync($id);
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 代码预览
+     */
+    public function preview(Request $request): Response
+    {
+        $id = $request->input('id', '');
+        $data = $this->logic->preview($id);
+        return $this->success($data);
+    }
+
+    /**
+     * 代码生成
+     */
+    public function generate(Request $request): Response
+    {
+        $ids = $request->input('ids', '');
+        $data = $this->logic->generate($ids);
+        return response()->download($data['download'], $data['filename']);
+    }
+
+    /**
+     * 生成到模块
+     */
+    public function generateFile(Request $request): Response
+    {
+        $id = $request->input('id', '');
+        $this->logic->generateFile($id);
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 获取数据表字段信息
+     * @param Request $request
+     * @return Response
+     */
+    public function getTableColumns(Request $request): Response
+    {
+        $table_id = $request->input('table_id', '');
+        $data = $this->logic->getTableColumns($table_id);
+        return $this->success($data);
+    }
+
+}

+ 154 - 0
plugin/saiadmin/app/event/SystemUser.php

@@ -0,0 +1,154 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\event;
+
+use plugin\saiadmin\app\model\system\SystemLoginLog;
+use plugin\saiadmin\app\model\system\SystemOperLog;
+use plugin\saiadmin\app\model\system\SystemMenu;
+
+class SystemUser
+{
+    /**
+     * 登录日志
+     * @param $item
+     */
+    public function login($item)
+    {
+        $ip = request()->getRealIp();
+        $http_user_agent = request()->header('user-agent');
+        $data['username'] = $item['username'];
+        $data['ip'] = $ip;
+        $data['ip_location'] = self::getIpLocation($ip);
+        $data['os'] = self::getOs($http_user_agent);
+        $data['browser'] = self::getBrowser($http_user_agent);
+        $data['status'] = $item['status'];
+        $data['message'] = $item['message'];
+        $data['login_time'] = date('Y-m-d H:i:s');
+        if (isset($item['admin_id'])) {
+            $data['created_by'] = $item['admin_id'];
+            $data['updated_by'] = $item['admin_id'];
+        }
+        SystemLoginLog::create($data);
+    }
+
+    /**
+     * 记录操作日志
+     */
+    public function operateLog(): bool
+    {
+        if (request()->method() === 'GET') {
+            return false;
+        }
+        $info = getCurrentInfo();
+        $ip = request()->getRealIp();
+        $module = request()->plugin;
+        $rule = trim(strtolower(request()->uri()));
+        $data['username'] = $info['username'];
+        $data['method'] = request()->method();
+        $data['router'] = $rule;
+        $data['service_name'] = self::getServiceName();
+        $data['app'] = $module;
+        $data['ip'] = $ip;
+        $data['ip_location'] = self::getIpLocation($ip);
+        $data['request_data'] = $this->filterParams(request()->all());
+        SystemOperLog::create($data);
+        return true;
+    }
+
+    /**
+     * 获取业务名称
+     */
+    protected function getServiceName(): string
+    {
+        $path = request()->path();
+        $menu = SystemMenu::where('code', $path)->findOrEmpty();
+        if (!$menu->isEmpty()) {
+            return $menu->getAttr('name');
+        } else {
+            return '未命名业务';
+        }
+    }
+
+    /**
+     * 过滤字段
+     */
+    protected function filterParams($params): string
+    {
+        $blackList = ['password', 'oldPassword', 'newPassword'];
+        foreach ($params as $key => $value) {
+            if (in_array($key, $blackList)) {
+                $params[$key] = '******';
+            }
+        }
+        return json_encode($params, JSON_UNESCAPED_UNICODE);
+    }
+
+    /**
+     * 获取IP地理位置
+     */
+    protected function getIpLocation($ip): string
+    {
+        $ip2region = new \Ip2Region();
+        try {
+            $region = $ip2region->memorySearch($ip);
+        } catch (\Exception $e) {
+            return '未知';
+        }
+        list($country, $number, $province, $city, $network) = explode('|', $region['region']);
+        if ($network === '内网IP') {
+            return $network;
+        }
+        if ($country == '中国') {
+            return $province.'-'.$city.':'.$network;
+        } else if ($country == '0') {
+            return '未知';
+        } else {
+            return $country;
+        }
+    }
+
+    /**
+     * 获取浏览器信息
+     */
+    protected function getBrowser($user_agent): string
+    {
+        $br = 'Unknown';
+        if (preg_match('/MSIE/i', $user_agent)) {
+            $br = 'MSIE';
+        } elseif (preg_match('/Firefox/i', $user_agent)) {
+            $br = 'Firefox';
+        } elseif (preg_match('/Chrome/i', $user_agent)) {
+            $br = 'Chrome';
+        } elseif (preg_match('/Safari/i', $user_agent)) {
+            $br = 'Safari';
+        } elseif (preg_match('/Opera/i', $user_agent)) {
+            $br = 'Opera';
+        } else {
+            $br = 'Other';
+        }
+        return $br;
+    }
+
+    /**
+     * 获取操作系统信息
+     */
+    protected function getOs($user_agent): string
+    {
+        $os = 'Unknown';
+        if (preg_match('/win/i', $user_agent)) {
+            $os = 'Win';
+        } elseif (preg_match('/mac/i', $user_agent)) {
+            $os = 'Mac';
+        } elseif (preg_match('/linux/i', $user_agent)) {
+            $os = 'Linux';
+        } else {
+            $os = 'Other';
+        }
+        return $os;
+    }
+
+}

+ 68 - 0
plugin/saiadmin/app/exception/Handler.php

@@ -0,0 +1,68 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\exception;
+
+use Throwable;
+use Webman\Http\Request;
+use Webman\Http\Response;
+use Webman\Exception\ExceptionHandler;
+use plugin\saiadmin\exception\ApiException;
+
+/**
+ * 异常处理类
+ */
+class Handler extends ExceptionHandler
+{
+    public $dontReport = [
+        ApiException::class,
+    ];
+
+    public function report(Throwable $exception)
+    {
+        if ($this->shouldntReport($exception)) {
+            return;
+        }
+        $logs = '';
+        if ($request = \request()) {
+            $user = getCurrentInfo();
+            $logs .= $request->method() . ' ' . $request->uri();
+            $logs .= PHP_EOL . '[request_param]: ' . json_encode($request->all());
+            $logs .= PHP_EOL . '[timestamp]: ' . date('Y-m-d H:i:s');
+            $logs .= PHP_EOL . '[client_ip]: ' . $request->getRealIp();
+            $logs .= PHP_EOL . '[action_user]: ' . var_export($user, true);
+            $logs .= PHP_EOL . '[exception_handle]: ' . get_class($exception);
+            $logs .= PHP_EOL . '[exception_info]: ' . PHP_EOL . $exception;
+        }
+        $this->logger->error($logs);
+    }
+
+    public function render(Request $request, Throwable $exception): Response
+    {
+        $debug = config('app.debug', true);
+        $code = $exception->getCode();
+        $json = [
+            'code' => $code ? $code : 500,
+            'message' => $code !== 500 ? $exception->getMessage() : 'Server internal error',
+            'type' => 'failed'
+        ];
+        if ($debug) {
+            $json['request_url'] = $request->method() . ' ' . $request->uri();
+            $json['timestamp'] = date('Y-m-d H:i:s');
+            $json['client_ip'] = $request->getRealIp();
+            $json['request_param'] = $request->all();
+            $json['exception_handle'] = get_class($exception);
+            $json['exception_info'] = [
+                'code' => $exception->getCode(),
+                'message' => $exception->getMessage(),
+                'file' => $exception->getFile(),
+                'line' => $exception->getLine(),
+                'trace' => explode("\n", $exception->getTraceAsString())
+            ];
+        }
+        return new Response(200, ['Content-Type' => 'application/json;charset=utf-8'], json_encode($json));
+    }
+}

+ 167 - 0
plugin/saiadmin/app/functions.php

@@ -0,0 +1,167 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+use Webman\Route;
+use support\Cache;
+use support\Response;
+use Tinywan\Jwt\JwtToken;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\app\model\system\SystemDictData;
+use plugin\saiadmin\app\logic\system\SystemConfigLogic;
+
+if (!function_exists('getCurrentInfo')) {
+    /**
+     * 获取当前登录用户
+     */
+    function getCurrentInfo(): bool|array
+    {
+        if (!request()) {
+            return false;
+        }
+        try {
+            $token = JwtToken::getExtend();
+        } catch (\Throwable $e) {
+            return false;
+        }
+        return $token;
+    }
+}
+
+if (!function_exists('fastRoute')) {
+    /**
+     * 快速注册路由[index|save|update|read|changeStatus|destroy|import|export]
+     * @param string $name
+     * @param string $controller
+     * @return void
+     */
+    function fastRoute(string $name, string $controller): void
+    {
+        $name = trim($name, '/');
+        if (method_exists($controller, 'index')) Route::get("/$name/index", [$controller, 'index']);
+        if (method_exists($controller, 'save')) Route::post("/$name/save", [$controller, 'save']);
+        if (method_exists($controller, 'update')) Route::put("/$name/update", [$controller, 'update']);
+        if (method_exists($controller, 'read')) Route::get("/$name/read", [$controller, 'read']);
+        if (method_exists($controller, 'changeStatus')) Route::post("/$name/changeStatus", [$controller, 'changeStatus']);
+        if (method_exists($controller, 'destroy')) Route::delete("/$name/destroy", [$controller, 'destroy']);
+        if (method_exists($controller, 'import')) Route::post("/$name/import", [$controller, 'import']);
+        if (method_exists($controller, 'export')) Route::post("/$name/export", [$controller, 'export']);
+    }
+}
+
+if (!function_exists('downloadFile')) {
+    /**
+     * 下载模板
+     * @param $file_name
+     * @return Response
+     */
+    function downloadFile($file_name): Response
+    {
+        $base_dir = config('plugin.saiadmin.saithink.template',base_path().'/public/template');
+        if (file_exists($base_dir. DIRECTORY_SEPARATOR.$file_name)) {
+            return response()->download($base_dir. DIRECTORY_SEPARATOR.$file_name, urlencode($file_name));
+        } else {
+            throw new ApiException('模板不存在');
+        }
+    }
+}
+
+if (!function_exists('formatBytes')) {
+    /**
+     * 根据字节计算大小
+     * @param $bytes
+     * @return string
+     */
+    function formatBytes($bytes): string
+    {
+        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+        for ($i = 0; $bytes > 1024; $i++) {
+            $bytes /= 1024;
+        }
+        return round($bytes, 2) . ' ' . $units[$i];
+    }
+}
+
+if (!function_exists('getConfigGroup')) {
+    /**
+     * 读取配置组
+     * @param $group
+     * @return mixed
+     */
+    function getConfigGroup($group): mixed
+    {
+        $logic = new SystemConfigLogic();
+        return $logic->getGroup($group);
+    }
+}
+
+if (!function_exists('dictDataList')) {
+    /**
+     * 根据字典编码获取字典列表
+     * @param string $code 字典编码
+     * @return array
+     */
+    function dictDataList(string $code): array
+    {
+        $data = Cache::get($code);
+        if ($data) {
+            return $data;
+        }
+        $model = new SystemDictData;
+        $query = $model->where('status', 1)->where('code', $code)->field('id, label, value')->order('sort desc');
+        $data = $query->select()->toArray();
+        Cache::set($code, $data);
+        return $data;
+    }
+}
+
+if (!function_exists('dbSourceList')) {
+    /**
+     * 数据源列表
+     * @return array
+     */
+    function dbSourceList(): array
+    {
+        $data = config('think-orm.connections');
+        if (empty($data)) {
+            $data = config('thinkorm.connections');
+        }
+        $list = [];
+        foreach ($data as $k => $v) {
+            $list[] = $k;
+        }
+        return $list;
+    }
+}
+
+if (!function_exists('defaultDbSource')) {
+    /**
+     * 获取默认数据源
+     * @return string
+     */
+    function defaultDbSource(): string
+    {
+        $config = config('think-orm');
+        if (empty($config)) {
+            $config = config('thinkorm');
+        }
+        return $config['default'] ?? 'mysql';
+    }
+}
+
+if (!function_exists('dbSource')) {
+    /**
+     * 数据源
+     * @return array
+     */
+    function dbSource(): array
+    {
+        $data = config('think-orm.connections');
+        if (empty($data)) {
+            $data = config('thinkorm.connections');
+        }
+        return $data;
+    }
+}

+ 193 - 0
plugin/saiadmin/app/logic/system/DatabaseLogic.php

@@ -0,0 +1,193 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use think\facade\Db;
+
+/**
+ * 数据表维护逻辑层
+ */
+class DatabaseLogic extends BaseLogic
+{
+    /**
+     * 数据列表
+     * @param $query
+     * @return mixed
+     */
+    public function getList($query): mixed
+    {
+        $page = request()->input('page') ? request()->input('page') : 1;
+        $limit = request()->input('limit') ? request()->input('limit') : 10;
+
+        return self::getTableList($query, $page, $limit);
+    }
+
+    /**
+     * 获取数据库表数据
+     */
+    public function getTableList($query, $current_page = 1, $per_page = 10): array
+    {
+        if (!empty($query['source'])) {
+            if (!empty($query['name'])) {
+                $sql = 'show table status where name=:name ';
+                $list = Db::connect($query['source'])->query($sql, ['name' => $query['name']]);
+            } else {
+                $list = Db::connect($query['source'])->query('show table status');
+            }
+        } else {
+            if (!empty($query['name'])) {
+                $sql = 'show table status where name=:name ';
+                $list = Db::query($sql, ['name' => $query['name']]);
+            } else {
+                $list = Db::query('show table status');
+            }
+        }
+
+        $data = [];
+        foreach ($list as $item) {
+            $data[] = [
+                'name' => $item['Name'],
+                'engine' => $item['Engine'],
+                'rows' => $item['Rows'],
+                'data_free' => $item['Data_free'],
+                'data_length' => $item['Data_length'],
+                'index_length' => $item['Index_length'],
+                'collation' => $item['Collation'],
+                'create_time' => $item['Create_time'],
+                'update_time' => $item['Update_time'],
+                'comment' => $item['Comment'],
+            ];
+        }
+        $total = count($data);
+        $last_page = ceil($total/$per_page);
+        $startIndex = ($current_page - 1) * $per_page;
+        $pageData = array_slice($data, $startIndex, $per_page);
+        return [
+            'data' => $pageData,
+            'total' => $total,
+            'current_page' => $current_page,
+            'per_page' => $per_page,
+            'last_page' => $last_page,
+        ];
+    }
+
+    /**
+     * 获取列信息
+     */
+    public function getColumnList($table, $source): array
+    {
+        $columnList = [];
+        if (preg_match("/^[a-zA-Z0-9_]+$/", $table)) {
+            if (!empty($source)) {
+                $list = Db::connect($source)->query('SHOW FULL COLUMNS FROM `'.$table.'`');
+            } else {
+                $list = Db::query('SHOW FULL COLUMNS FROM `'.$table.'`');
+            }
+            foreach ($list as $column) {
+                preg_match('/^\w+/', $column['Type'], $matches);
+                $columnList[] = [
+                    'column_key' => $column['Key'],
+                    'column_name'=> $column['Field'],
+                    'column_type' => $matches[0],
+                    'column_comment' => trim(preg_replace("/\([^()]*\)/", "", $column['Comment'])),
+                    'extra' => $column['Extra'],
+                    'default_value' => $column['Default'],
+                    'is_nullable' => $column['Null'],
+                ];
+            }
+        }
+        return $columnList;
+    }
+
+    /**
+     * 优化表
+     */
+    public function optimizeTable($tables)
+    {
+        foreach ($tables as $table) {
+            if (preg_match("/^[a-zA-Z0-9_]+$/", $table)) {
+                Db::execute('OPTIMIZE TABLE `'. $table. '`');
+            }
+        }
+    }
+
+    /**
+     * 清理表碎片
+     */
+    public function fragmentTable($tables)
+    {
+        foreach ($tables as $table) {
+            if (preg_match("/^[a-zA-Z0-9_]+$/", $table)) {
+                Db::execute('ANALYZE TABLE `'. $table. '`');
+            }
+        }
+    }
+
+    /**
+     * 获取回收站数据
+     */
+    public function recycleData($table)
+    {
+        if (preg_match("/^[a-zA-Z0-9_]+$/", $table)) {
+            // 查询表字段
+            $sql = 'SHOW COLUMNS FROM `'.$table.'` where Field = "delete_time"';
+            $columns = Db::query($sql);
+            $isDeleteTime = false;
+            if (count($columns) > 0) {
+                $isDeleteTime = true;
+            }
+            if (!$isDeleteTime) {
+                throw new ApiException('当前表不支持回收站功能');
+            }
+            // 查询软删除数据
+            $limit = request()->input('limit') ? request()->input('limit') : 10;
+            return Db::table($table)->whereNotNull('delete_time')
+                ->order('delete_time', 'desc')
+                ->paginate($limit)
+                ->toArray();
+        } else {
+            return [];
+        }
+    }
+
+    /**
+     * 删除数据
+     * @param $table
+     * @param $ids
+     * @return bool
+     */
+    public function delete($table, $ids)
+    {
+        if (preg_match("/^[a-zA-Z0-9_]+$/", $table)) {
+            $count = Db::table($table)->delete($ids);
+            return $count > 0;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 恢复数据
+     * @param $table
+     * @param $ids
+     * @return bool
+     */
+    public function recovery($table, $ids)
+    {
+        if (preg_match("/^[a-zA-Z0-9_]+$/", $table)) {
+            $count = Db::table($table)
+                ->where('id', 'in', $ids)
+                ->update(['delete_time' => null]);
+            return $count > 0;
+        } else {
+            return false;
+        }
+    }
+
+}

+ 162 - 0
plugin/saiadmin/app/logic/system/SystemAttachmentLogic.php

@@ -0,0 +1,162 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use Exception;
+use plugin\saiadmin\app\model\system\SystemAttachment;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\service\storage\UploadService;
+use plugin\saiadmin\utils\Arr;
+use plugin\saiadmin\utils\Helper;
+use support\Request;
+
+/**
+ * 角色逻辑层
+ */
+class SystemAttachmentLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemAttachment();
+    }
+
+    /**
+     * 保存网络图片
+     * @param $url
+     * @param $config
+     * @return array
+     * @throws ApiException|Exception
+     */
+    public function saveNetworkImage($url, $config): array
+    {
+        $image_data = file_get_contents($url);
+        if ($image_data === false) {
+            throw new ApiException('获取文件资源失败');
+        }
+        $image_resource = imagecreatefromstring($image_data);
+        if (!$image_resource) {
+            throw new ApiException('创建图片资源失败');
+        }
+        $filename = basename($url);
+        $file_extension = pathinfo($filename, PATHINFO_EXTENSION);
+        $full_dir = runtime_path() . '/resource/';
+        if (!is_dir($full_dir)) {
+            mkdir($full_dir, 0777, true);
+        }
+        $save_path = $full_dir . $filename;
+        $mime_type = 'image/';
+        switch($file_extension) {
+            case 'jpg':
+            case 'jpeg':
+                $mime_type = 'image/jpeg';
+                $result = imagejpeg($image_resource, $save_path);
+                break;
+            case 'png':
+                $mime_type = 'image/png';
+                $result = imagepng($image_resource, $save_path);
+                break;
+            case 'gif':
+                $mime_type = 'image/gif';
+                $result = imagegif($image_resource, $save_path);
+                break;
+            default:
+                imagedestroy($image_resource);
+                throw new ApiException('文件格式错误');
+        }
+        imagedestroy($image_resource);
+        if (!$result) {
+            throw new ApiException('文件保存失败');
+        }
+
+        $hash = md5_file($save_path);
+        $size = filesize($save_path);
+
+        $model = $this->model->where('hash', $hash)->find();
+        if ($model) {
+            unlink($save_path);
+            return $model->toArray();
+        } else {
+
+            $logic = new SystemConfigLogic();
+            $uploadConfig = $logic->getGroup('upload_config');
+
+            $root = Arr::getConfigValue($uploadConfig,'local_root');
+
+            $folder = date('Ymd');
+            $full_dir = base_path().DIRECTORY_SEPARATOR.$root.$folder.DIRECTORY_SEPARATOR;
+            if (!is_dir($full_dir)) {
+                mkdir($full_dir, 0777, true);
+            }
+            $object_name = bin2hex(pack('Nn',time(), random_int(1, 65535))) . ".$file_extension";
+            $newPath = $full_dir . $object_name;
+
+            copy($save_path, $newPath);
+            unlink($save_path);
+            $domain = Arr::getConfigValue($uploadConfig,'local_domain');
+            $uri = Arr::getConfigValue($uploadConfig,'local_uri');
+            $baseUrl = $domain.$uri.$folder.'/';
+
+            $info['storage_mode'] = 1;
+            $info['origin_name'] = $filename;
+            $info['object_name'] = $object_name;
+            $info['hash'] = $hash;
+            $info['mime_type'] = $mime_type;
+            $info['storage_path'] = $root.$folder.'/'.$object_name;
+            $info['suffix'] = $file_extension;
+            $info['size_byte'] = $size;
+            $info['size_info'] = formatBytes($size);
+            $info['url'] = $baseUrl . $object_name;
+            $this->model->save($info);
+            return $info;
+        }
+    }
+
+    /**
+     * 文件上传
+     * @param string $upload
+     * @param bool $local
+     * @return array
+     */
+    public function uploadBase($upload = 'image', $local = false)
+    {
+        $logic = new SystemConfigLogic();
+        $uploadConfig = $logic->getGroup('upload_config');
+        $type = Arr::getConfigValue($uploadConfig, 'upload_mode');
+        if ($local === true) {
+            $type = 1;
+        }
+        $result = UploadService::disk($type, $upload)->uploadFile();
+        $data = $result[0];
+        $hash = $data['unique_id'];
+        $hash_check = config('plugin.saiadmin.saithink.file_hash', false);
+        if ($hash_check) {
+            $model = $this->model->where('hash', $hash)->findOrEmpty();
+            if (!$model->isEmpty()) {
+                return $model->toArray();
+            }
+        }
+        $url = str_replace('\\', '/', $data['url']);
+        $savePath = str_replace('\\', '/', $data['save_path']);
+        $info['storage_mode'] = $type;
+        $info['origin_name'] = $data['origin_name'];
+        $info['object_name'] = $data['save_name'];
+        $info['hash'] = $data['unique_id'];
+        $info['mime_type'] = $data['mime_type'];
+        $info['storage_path'] = $savePath;
+        $info['suffix'] = $data['extension'];
+        $info['size_byte'] = $data['size'];
+        $info['size_info'] = formatBytes($data['size']);
+        $info['url'] = $url;
+        $this->model->save($info);
+        return $info;
+    }
+
+}

+ 56 - 0
plugin/saiadmin/app/logic/system/SystemConfigGroupLogic.php

@@ -0,0 +1,56 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemConfigGroup;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\app\model\system\SystemConfig;
+use support\Cache;
+use think\facade\Db;
+
+/**
+ * 参数配置分组逻辑层
+ */
+class SystemConfigGroupLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemConfigGroup();
+    }
+
+    /**
+     * 删除配置信息
+     */
+    public function destroy($ids)
+    {
+        $model = $this->model->where('id', $ids)->findOrEmpty();
+        if ($model->isEmpty()) {
+            throw new ApiException('配置数据未找到');
+        }
+        if (in_array(intval($ids), [1, 2, 3])) {
+            throw new ApiException('系统默认分组,无法删除');
+        }
+        Db::startTrans();
+        try {
+            // 删除配置组
+            $model->delete();
+            // 删除配置组数据
+            $typeIds = SystemConfig::where('group_id', $ids)->column('id');
+            SystemConfig::destroy($typeIds);
+            Cache::delete('cfg_' . $model->code);
+            Db::commit();
+            return true;
+        } catch (\Exception $e) {
+            Db::rollback();
+            throw new ApiException('删除数据异常,请检查');
+        }
+    }
+}

+ 48 - 0
plugin/saiadmin/app/logic/system/SystemConfigLogic.php

@@ -0,0 +1,48 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\app\model\system\SystemConfig;
+use plugin\saiadmin\app\model\system\SystemConfigGroup;
+use support\Cache;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 参数配置逻辑层
+ */
+class SystemConfigLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemConfig();
+    }
+
+    /**
+     * 获取配置组
+     */
+    public function getGroup($config)
+    {
+        $prefix = 'cfg_';
+        $data = Cache::get($prefix . $config);
+        if (!is_null($data)) {
+            return $data;
+        }
+        $group = SystemConfigGroup::where('code', $config)->findOrEmpty();
+        if ($group->isEmpty()) {
+            throw new ApiException('配置组不存在');
+        }
+        $info = $this->model->where('group_id', $group->id)->select();
+        Cache::set($prefix . $config, $info->toArray());
+        return $info;
+    }
+
+}

+ 194 - 0
plugin/saiadmin/app/logic/system/SystemDeptLogic.php

@@ -0,0 +1,194 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\app\model\system\SystemDept;
+use plugin\saiadmin\app\model\system\SystemUser;
+use plugin\saiadmin\app\model\system\SystemDeptLeader;
+use plugin\saiadmin\utils\Helper;
+use plugin\saiadmin\utils\Arr;
+
+/**
+ * 部门逻辑层
+ */
+class SystemDeptLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemDept();
+    }
+
+    /**
+     * 添加数据
+     */
+    public function add($data): mixed
+    {
+        $data = $this->handleData($data);
+        $data['game_list'] = $this->getGameListByDeptId(['dept_id' => $data['parent_id']]);
+        $this->model->save($data);
+        return $this->model->getKey();
+    }
+
+    /**
+     * 修改数据
+     */
+    public function edit($id, $data): mixed
+    {
+        $oldLevel = $data['level'].",".$id;
+        $data = $this->handleData($data);
+        if ($data['parent_id'] == $id) {
+            throw new ApiException('不能设置父级为自身');
+        }
+        if (in_array($id, explode(',', $data['level']))) {
+			throw new ApiException('不能设置父级为下级部门');
+		}
+        $newLevel = $data['level'].",".$id;
+        $deptIds = $this->model->whereRaw('FIND_IN_SET("'.$id.'", level) > 0')->column('id');
+        $this->model->whereIn('id', $deptIds)->exp('level', "REPLACE(level, '$oldLevel', '$newLevel')")->update();
+        return $this->model->update($data, ['id' => $id]);
+    }
+
+    /**
+     * 数据删除
+     */
+    public function destroy($ids)
+    {
+        $num = $this->model->where('parent_id', 'in', $ids)->count();
+        if ($num > 0) {
+            throw new ApiException('该部门下存在子部门,请先删除子部门');
+        } else {
+            $count = SystemUser::where('dept_id', 'in', $ids)->count();
+            if ($count > 0) {
+                throw new ApiException('该部门下存在用户,请先删除或者转移用户');
+            }
+            return $this->model->destroy($ids);
+        }
+    }
+
+    /**
+     * 数据处理
+     */
+    protected function handleData($data)
+    {
+        if (empty($data['parent_id']) || $data['parent_id'] == 0) {
+            $data['level'] = '0';
+            $data['parent_id'] = 0;
+        } else {
+            $parentMenu = SystemDept::findOrEmpty($data['parent_id']);
+            $data['level'] = $parentMenu['level'] . ',' . $parentMenu['id'];
+        }
+        return $data;
+    }
+
+    /**
+     * 数据树形化
+     * @param array $where
+     * @return array
+     */
+    public function tree(array $where = []): array
+    {
+        $query = $this->search($where);
+        if (request()->input('tree', 'false') === 'true') {
+            $query->field('id, id as value, name as label, parent_id');
+        }
+        $query->order('sort', 'desc');
+        $query->with(['leader' => function($query) {
+            $query->field('id, username, nickname');
+        }]);
+        $data = $this->getAll($query);
+        return Helper::makeTree($data);
+    }
+
+    /**
+     * 可操作部门
+     * @param array $where
+     * @return array
+     */
+    public function accessDept(array $where = []): array
+    {
+        $query = $this->search($where);
+        $query->auth($this->adminInfo['deptList']);
+        $query->field('id, id as value, name as label, parent_id');
+        $query->order('sort', 'desc');
+        $data = $this->getAll($query);
+        foreach ($data as &$item) {
+            if ($item['id'] === $this->adminInfo['dept_id']) {
+                $item['disabled'] = true;
+            } else {
+                $item['disabled'] = false;
+            }
+        }
+        return Helper::makeTree($data);
+    }
+
+    /**
+     * 领导列表
+     */
+    public function leaders($where = [])
+    {
+        $dept_id = $where['dept_id'];
+        unset($where['dept_id']);
+        $logic = new SystemUserLogic();
+        $query = $logic->search($where)->alias('user')->join('sa_system_dept_leader dept', 'user.id = dept.user_id')
+            ->where('dept.dept_id', $dept_id);
+        return $logic->getList($query);
+    }
+
+    /**
+     * 添加领导
+     */
+    public function addLeader($dept_id ,$users)
+    {
+        $model = $this->model->findOrEmpty($dept_id);
+        $leader = new SystemDeptLeader();
+        foreach ($users as $key => $user) {
+            $info = $leader->where('user_id', $user['user_id'])->where('dept_id', $dept_id)->findOrEmpty();
+            if (!$info->isEmpty()) {
+                unset($users[$key]);
+            }
+        }
+        $model->leader()->saveAll(Arr::getArrayColumn($users, 'user_id'));
+    }
+
+    /**
+     * 删除领导
+     */
+    public function delLeader($id, $ids)
+    {
+        $model = $this->model->findOrEmpty($id);
+        $model->leader()->detach($ids);
+    }
+
+    /**
+     * 根据部门ID获取游戏列表
+     */
+    public function getGameListByDeptId($where)
+    {
+        $dept_id = $where['dept_id'];
+        $query = $this->model->field('game_list')->where('id', $dept_id);
+        $data = $query->find();
+        
+        return $data->game_list;
+    }
+
+    /**
+     * 设置部门游戏权限
+     */
+    public function setGameListByDeptId($dept_id, $game_list)
+    {
+        $result = $this->model->where('id', $dept_id)->update(['game_list' => $game_list]);
+        if (!$result) {
+            throw new ApiException('更新部门游戏权限失败');
+        }
+        return $result;
+    }
+}

+ 28 - 0
plugin/saiadmin/app/logic/system/SystemDictDataLogic.php

@@ -0,0 +1,28 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\app\model\system\SystemDictData;
+use plugin\saiadmin\app\model\system\SystemDictType;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 字典类型逻辑层
+ */
+class SystemDictDataLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemDictData();
+    }
+
+}

+ 67 - 0
plugin/saiadmin/app/logic/system/SystemDictTypeLogic.php

@@ -0,0 +1,67 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\app\model\system\SystemDictType;
+use plugin\saiadmin\app\model\system\SystemDictData;
+use think\facade\Db;
+
+/**
+ * 字典类型逻辑层
+ */
+class SystemDictTypeLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemDictType();
+    }
+
+    /**
+     * 数据更新
+     */
+    public function edit($id, $data): mixed
+    {
+        Db::startTrans();
+        try {
+            // 修改数据字典类型
+            $result = $this->model->update($data, ['id' => $id]);
+            // 更新数据字典数据
+            SystemDictData::update(['code' => $data['code']], ['type_id' => $id]);
+            Db::commit();
+            return $result;
+        } catch (\Exception $e) {
+            Db::rollback();
+            throw new ApiException('修改数据异常,请检查');
+        }
+    }
+
+    /**
+     * 数据删除
+     */
+    public function destroy($ids)
+    {
+        Db::startTrans();
+        try {
+            // 删除数据字典类型
+            $result = $this->model->destroy($ids);
+            // 删除数据字典数据
+            $typeIds = SystemDictData::where('type_id', 'in', $ids)->column('id');
+            SystemDictData::destroy($typeIds);
+            Db::commit();
+            return $result;
+        } catch (\Exception $e) {
+            Db::rollback();
+            throw new ApiException('删除数据异常,请检查');
+        }
+    }
+
+}

+ 55 - 0
plugin/saiadmin/app/logic/system/SystemLoginLogLogic.php

@@ -0,0 +1,55 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemLoginLog;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\utils\Helper;
+use think\facade\Db;
+
+/**
+ * 登录日志逻辑层
+ */
+class SystemLoginLogLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemLoginLog();
+    }
+
+    /**
+     * 登录统计图表
+     * @return array
+     */
+    public function loginChart(): array
+    {
+        $sql = "
+            SELECT
+                d.date AS login_date,
+                COUNT(l.login_time) AS login_count
+            FROM
+                (SELECT CURDATE() - INTERVAL (a.N) DAY AS date
+                 FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
+                       UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
+                       UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
+                 ) d
+            LEFT JOIN sa_system_login_log l
+                ON DATE(l.login_time) = d.date
+            GROUP BY d.date
+            ORDER BY d.date ASC;
+        ";
+        $data = Db::query($sql);
+        return [
+            'login_count' => array_column($data, 'login_count'),
+            'login_date'  => array_column($data, 'login_date'),
+        ];
+    }
+
+}

+ 26 - 0
plugin/saiadmin/app/logic/system/SystemMailLogic.php

@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemMail;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 邮件模型逻辑层
+ */
+class SystemMailLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemMail();
+    }
+
+}

+ 201 - 0
plugin/saiadmin/app/logic/system/SystemMenuLogic.php

@@ -0,0 +1,201 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemMenu;
+use plugin\saiadmin\app\model\system\SystemRoleMenu;
+use plugin\saiadmin\app\model\system\SystemUserRole;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\utils\Arr;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 菜单逻辑层
+ */
+class SystemMenuLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemMenu();
+    }
+
+    /**
+     * 数据添加
+     */
+    public function add($data): mixed
+    {
+        $data = $this->handleData($data);
+        return $this->model->save($data);
+    }
+
+    /**
+     * 数据修改
+     */
+    public function edit($id, $data): mixed
+    {
+        $data = $this->handleData($data);
+        if ($data['parent_id'] == $id) {
+            throw new ApiException('不能设置父级为自身');
+        }
+        return $this->model->update($data, ['id' => $id]);
+    }
+
+    /**
+     * 数据删除
+     */
+    public function destroy($ids)
+    {
+        $num = $this->model->where('parent_id', 'in', $ids)->count();
+        if ($num > 0) {
+            throw new ApiException('该菜单下存在子菜单,请先删除子菜单');
+        } else {
+            return $this->model->destroy($ids);
+        }
+    }
+
+    /**
+     * 数据处理
+     */
+    protected function handleData($data)
+    {
+        if (empty($data['parent_id']) || $data['parent_id'] == 0) {
+            $data['level'] = '0';
+            $data['parent_id'] = 0;
+            $data['type'] = $data['type'] === 'B' ? 'M' : $data['type'];
+        } else {
+            $parentMenu = $this->model->findOrEmpty($data['parent_id']);
+            $data['level'] = $parentMenu['level'] . ',' . $parentMenu['id'];
+        }
+        return $data;
+    }
+
+    /**
+     * 数据树形化
+     * @param $where
+     * @return array
+     */
+    public function tree($where = []): array
+    {
+        $query = $this->search($where);
+        if (request()->input('tree', 'false') === 'true') {
+            $query->field('id, id as value, name as label, parent_id');
+        }
+        $query->order('sort', 'desc');
+        $data = $this->getAll($query);
+        return Helper::makeTree($data);
+    }
+
+    /**
+     * 权限菜单
+     * @return array
+     */
+    public function auth(): array
+    {
+        $roleLogic = new SystemRoleLogic();
+        $role_ids = Arr::getArrayColumn($this->adminInfo['roleList'], 'id');
+        $roles = $roleLogic->getMenuIdsByRoleIds($role_ids);
+        $ids = $this->filterMenuIds($roles);
+        $query = $this->model
+            ->field('id, id as value, name as label, parent_id')
+            ->where('status', 1)
+            ->where('id', 'in', $ids)
+            ->order('sort', 'desc');
+        $data = $this->getAll($query);
+        return Helper::makeTree($data);
+    }
+
+    /**
+     * 获取全部菜单
+     */
+    public function getAllMenus(): array
+    {
+        $query = $this->search(['status' => 1, 'type' => ['M','I','L']])->order('sort', 'desc');
+        $data = $this->getAll($query);
+        return Helper::makeArcoMenus($data);
+    }
+
+    /**
+     * 获取全部操作code
+     */
+    public function getAllCode(): array
+    {
+        $query = $this->search(['type' => 'B']);
+        return $query->column('code');
+    }
+
+    /**
+     * 根据ids获取权限
+     * @param $ids
+     * @return array
+     */
+    public function getMenuCode($ids): array
+    {
+        return $this->model
+            ->where('status', 1)
+            ->where('id', 'in', $ids)
+            ->column('code');
+    }
+
+    /**
+     * 根据管理员id获取权限
+     * @param $id
+     * @return array
+     */
+    public function getAuthByAdminId($id): array
+    {
+        $roleIds = SystemUserRole::where('user_id', $id)->column('role_id');
+        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');
+
+        return SystemMenu::distinct(true)
+            ->where('type', 'B')
+            ->where('status', 1)
+            ->where('id', 'in', array_unique($menuId))
+            ->column('code');
+    }
+
+    /**
+     * 根据管理员id获取菜单
+     * @param $id
+     * @return array
+     */
+    public function getRoutersByAdminId($id): array
+    {
+        $roleIds = SystemUserRole::where('user_id', $id)->column('role_id');
+        $menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');
+
+        $data = SystemMenu::distinct(true)
+            ->where('status', 1)
+            ->where('type', 'in', ['M','I','L'])
+            ->where('id', 'in', array_unique($menuId))
+            ->order('sort', 'desc')
+            ->select()
+            ->toArray();
+        return Helper::makeArcoMenus($data);
+    }
+
+    /**
+     * 过滤通过角色查询出来的菜单id列表,并去重
+     * @param array $roleData
+     * @return array
+     */
+    public function filterMenuIds(array &$roleData): array
+    {
+        $ids = [];
+        foreach ($roleData as $val) {
+            foreach ($val['menus'] as $menu) {
+                $ids[] = $menu['id'];
+            }
+        }
+        unset($roleData);
+        return array_unique($ids);
+    }
+
+}

+ 26 - 0
plugin/saiadmin/app/logic/system/SystemNoticeLogic.php

@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemNotice;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 系统公告逻辑层
+ */
+class SystemNoticeLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemNotice();
+    }
+
+}

+ 26 - 0
plugin/saiadmin/app/logic/system/SystemOperLogLogic.php

@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemOperLog;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 操作日志逻辑层
+ */
+class SystemOperLogLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemOperLog();
+    }
+
+}

+ 95 - 0
plugin/saiadmin/app/logic/system/SystemPostLogic.php

@@ -0,0 +1,95 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\model\system\SystemPost;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\service\OpenSpoutWriter;
+use OpenSpout\Reader\XLSX\Reader;
+
+/**
+ * 岗位管理逻辑层
+ */
+class SystemPostLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemPost();
+    }
+
+    /**
+     * 可操作岗位
+     * @param array $where
+     * @return array
+     */
+    public function accessPost(array $where = []): array
+    {
+        $query = $this->search($where);
+        $query->field('id, id as value, name as label, name, code');
+        return $this->getAll($query);
+    }
+
+    /**
+     * 导入数据
+     */
+    public function import($file)
+    {
+        $path = $this->getImport($file);
+        $reader = new Reader();
+        try {
+            $reader->open($path);
+            $data = [];
+            foreach ($reader->getSheetIterator() as $sheet) {
+                $isHeader = true;
+                foreach ($sheet->getRowIterator() as $row) {
+                    if ($isHeader) {
+                        $isHeader = false;
+                        continue;
+                    }
+                    $cells = $row->getCells();
+                    $data[] = [
+                        'name' => $cells[0]->getValue(),
+                        'code' => $cells[1]->getValue(),
+                        'sort' => $cells[2]->getValue(),
+                        'status' => $cells[3]->getValue(),
+                    ];
+                }
+            }
+            $this->saveAll($data);
+        } catch (\Exception $e) {
+            throw new ApiException('导入文件错误,请上传正确的文件格式xlsx');
+        }
+    }
+
+    /**
+     * 导出数据
+     */
+    public function export($where = [])
+    {
+        $query = $this->search($where)->field('id,name,code,sort,status,create_time');
+        $data = $this->getAll($query);
+        $file_name = '岗位数据.xlsx';
+        $header = ['编号', '岗位名称', '岗位标识', '排序', '状态', '创建时间'];
+        $filter = [
+            'status' => [
+                ['value' => 1, 'label' => '正常'],
+                ['value' => 2, 'label' => '禁用']
+            ]
+        ];
+        $writer = new OpenSpoutWriter($file_name);
+        $writer->setWidth([15, 15, 20, 15, 15, 25]);
+        $writer->setHeader($header);
+        $writer->setData($data, null, $filter);
+        $file_path = $writer->returnFile();
+        return response()->download($file_path, urlencode($file_name));
+    }
+
+}

+ 239 - 0
plugin/saiadmin/app/logic/system/SystemRoleLogic.php

@@ -0,0 +1,239 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\cache\UserAuthCache;
+use plugin\saiadmin\app\model\system\SystemRole;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\utils\Helper;
+use think\db\Query;
+use think\facade\Db;
+
+/**
+ * 角色逻辑层
+ */
+class SystemRoleLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemRole();
+    }
+
+    /**
+     * 添加数据
+     */
+    public function add($data): mixed
+    {
+        $data = $this->handleData($data);
+        $this->model->save($data);
+        return $this->model->getKey();
+    }
+
+    /**
+     * 修改数据
+     */
+    public function edit($id, $data): mixed
+    {
+        $oldLevel = $data['level'] . "," . $id;
+        $data = $this->handleData($data);
+        if ($data['parent_id'] == $id) {
+            throw new ApiException('不能设置父级为自身');
+        }
+        if (in_array($id, explode(',', $data['level']))) {
+            throw new ApiException('不能设置父级为下级角色');
+        }
+        $query = $this->model->where('id', $id);
+        $query->auth([
+            'id' => $this->adminInfo['id'],
+            'roles' => $this->adminInfo['roleList']
+        ]);
+        $model = $query->findOrEmpty();
+        if ($model->isEmpty() || in_array($id, array_column($this->adminInfo['roleList'], 'id'))) {
+            throw new ApiException('没有权限操作该数据');
+        }
+        $newLevel = $data['level'].",".$id;
+        $roleIds = SystemRole::whereRaw('FIND_IN_SET("'.$id.'", level) > 0')->column('id');
+        SystemRole::whereIn('id', $roleIds)->exp('level', "REPLACE(level, '$oldLevel', '$newLevel')")->update();
+        return $model->save($data);
+    }
+
+    /**
+     * 删除数据
+     */
+    public function destroy($ids)
+    {
+        // 判断是否所属角色下的角色
+        if ($this->adminInfo['id'] > 1) {
+            $roleList = $this->adminInfo['roleList'];
+            $roleIds = [];
+            foreach ($roleList as $item) {
+                $temp = SystemRole::whereRaw('FIND_IN_SET("'.$item['id'].'", level) > 0')->column('id');
+                $roleIds = array_merge($roleIds, $temp);
+            }
+            if (count(array_diff($ids, $roleIds)) > 0) {
+                throw new ApiException('删除角色不在当前角色下');
+            }
+        }
+        $num = SystemRole::where('parent_id', 'in', $ids)->count();
+        if ($num > 0) {
+            throw new ApiException('该角色下存在子角色,请先删除子角色');
+        } else {
+            return $this->model->destroy($ids);
+        }
+    }
+
+    /**
+     * 数据处理
+     */
+    protected function handleData($data)
+    {
+        if ($this->adminInfo['id'] > 1) {
+            // 判断parent_id是否允许使用
+            $ids = [];
+            foreach ($this->adminInfo['roleList'] as $item) {
+                $ids[] = $item['id'];
+                $temp = SystemRole::whereRaw('FIND_IN_SET("'.$item['id'].'", level) > 0')->column('id');
+                $ids = array_merge($ids, $temp);
+            }
+            if (!in_array($data['parent_id'], array_unique($ids))) {
+                throw new ApiException('父级角色不在当前角色下');
+            }
+        }
+        if (empty($data['parent_id'])) {
+            $data['level'] = '0';
+            $data['parent_id'] = 0;
+        } else {
+            $parentMenu = SystemRole::findOrEmpty($data['parent_id']);
+            $data['level'] = $parentMenu['level'] . ',' . $parentMenu['id'];
+        }
+        return $data;
+    }
+
+    /**
+     * 数据树形化
+     * @param array $where
+     * @return array
+     */
+    public function tree(array $where = []): array
+    {
+        $query = $this->search($where);
+        if (request()->input('tree', 'false') === 'true') {
+            $query->field('id, id as value, name as label, parent_id');
+        }
+        $query->auth([
+            'id' => $this->adminInfo['id'],
+            'roles' => $this->adminInfo['roleList']
+        ]);
+        if ($this->adminInfo['id'] === 1) {
+            $disabled = [1];
+        } else {
+            $disabled = array_column($this->adminInfo['roleList'], 'id');
+        }
+        $query->order('sort', 'desc');
+        $data = $this->getAll($query);
+        if (request()->input('filter', 'true') === 'true') {
+            if (!empty($disabled)) {
+                foreach ($data as &$item) {
+                    if (in_array($item['id'], $disabled)) {
+                        $item['disabled'] = true;
+                    } else {
+                        $item['disabled'] = false;
+                    }
+                }
+            }
+        }
+        return Helper::makeTree($data);
+    }
+
+    /**
+     * 可操作角色
+     * @param array $where
+     * @return array
+     */
+    public function accessRole(array $where = []): array
+    {
+        $query = $this->search($where);
+        $query->field('id, id as value, name as label, parent_id');
+        $query->auth([
+            'id' => $this->adminInfo['id'],
+            'roles' => $this->adminInfo['roleList']
+        ]);
+        if ($this->adminInfo['id'] === 1) {
+            $disabled = [1];
+        } else {
+            $disabled = array_column($this->adminInfo['roleList'], 'id');
+        }
+        $query->order('sort', 'desc');
+        $data = $this->getAll($query);
+        if (!empty($disabled)) {
+            foreach ($data as &$item) {
+                if (in_array($item['id'], $disabled)) {
+                    $item['disabled'] = true;
+                } else {
+                    $item['disabled'] = false;
+                }
+            }
+        }
+        return Helper::makeTree($data);
+    }
+
+    /**
+     * 根据角色数组获取菜单
+     * @param $ids
+     * @return array
+     */
+    public function getMenuIdsByRoleIds($ids) : array
+    {
+        if (empty($ids)) return [];
+        return $this->model->where('id', 'in', $ids)->with(['menus' => function(Query $query) {
+            $query->where('status', 1)->order('sort', 'desc');
+        }])->select()->toArray();
+
+    }
+
+    /**
+     * 根据角色获取菜单
+     * @param $id
+     * @return array
+     */
+    public function getMenuByRole($id): array
+    {
+        $role = $this->model->findOrEmpty($id);
+        $menus = $role->menus ?: [];
+        return [
+            'id' => $id,
+            'menus' => $menus
+        ];
+    }
+
+    /**
+     * 保存菜单权限
+     * @param $id
+     * @param $menu_ids
+     * @return mixed
+     */
+    public function saveMenuPermission($id, $menu_ids): mixed
+    {
+        return $this->transaction(function () use ($id, $menu_ids) {
+            $role = $this->model->findOrEmpty($id);
+            if ($role) {
+                $role->menus()->detach();
+                $data = array_map(function($menu_id) use ($id) {
+                    return ['menu_id' => $menu_id, 'role_id' => $id];
+                }, $menu_ids);
+                Db::name('sa_system_role_menu')->limit(100)->insertAll($data);
+            }
+            (new UserAuthCache($this->adminInfo['id']))->clearAuthCache();
+            return true;
+        });
+    }
+
+}

+ 256 - 0
plugin/saiadmin/app/logic/system/SystemUserLogic.php

@@ -0,0 +1,256 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\system;
+
+use plugin\saiadmin\app\cache\UserInfoCache;
+use plugin\saiadmin\app\model\system\SystemDept;
+use plugin\saiadmin\app\model\system\SystemRole;
+use plugin\saiadmin\app\model\system\SystemUser;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\basic\BaseLogic;
+use Webman\Event\Event;
+use Tinywan\Jwt\JwtToken;
+
+/**
+ * 用户信息逻辑层
+ */
+class SystemUserLogic extends BaseLogic
+{
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new SystemUser();
+    }
+
+    /**
+     * 读取数据
+     * @param $id
+     * @return array
+     */
+    public function read($id): array
+    {
+        $admin = $this->model->findOrEmpty($id);
+        $data = $admin->hidden(['password'])->toArray();
+        $data['roleList'] = $admin->roles->toArray() ?: [];
+        $data['postList'] = $admin->posts->toArray() ?: [];
+        $data['deptList'] = $admin->depts ? $admin->depts->toArray() : [];
+        if ($this->adminInfo['id'] > 1) {
+            // 判断部门id是否有操作权限
+            $dept_ids = SystemDept::whereRaw('FIND_IN_SET("'.$this->adminInfo['dept_id'].'", level) > 0')->column('id');
+            if (!in_array($admin['dept_id'], $dept_ids)) {
+                throw new ApiException('没有权限操作该部门数据');
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 添加数据
+     * @param $data
+     * @return mixed
+     */
+    public function add($data): mixed
+    {
+        $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
+        return $this->transaction(function () use ($data) {
+            $role_ids = $data['role_ids'] ?? [];
+            $post_ids = $data['post_ids'] ?? [];
+            if ($this->adminInfo['id'] > 1) {
+                // 1、判断部门id是否有操作权限
+                $dept_ids = SystemDept::whereRaw('FIND_IN_SET("' . $this->adminInfo['dept_id'] . '", level) > 0')->column('id');
+                if (!in_array($data['dept_id'], $dept_ids)) {
+                    throw new ApiException('没有权限操作该部门数据');
+                }
+                // 2、判断角色id是否有操作权限
+                $roleIds = [];
+                foreach ($this->adminInfo['roleList'] as $item) {
+                    $temp = SystemRole::whereRaw('FIND_IN_SET("' . $item['id'] . '", level) > 0')->column('id');
+                    $roleIds = array_merge($roleIds, $temp);
+                }
+                if (count(array_diff($role_ids, $roleIds)) > 0) {
+                    throw new ApiException('没有权限操作该角色数据');
+                }
+            }
+            $user = SystemUser::create($data);
+            $user->roles()->detach();
+            $user->posts()->detach();
+            $user->roles()->saveAll($role_ids);
+            if (!empty($post_ids)) {
+                $user->posts()->save($post_ids);
+            }
+            return $user->getKey();
+        });
+    }
+
+    /**
+     * 修改数据
+     * @param $id
+     * @param $data
+     * @return mixed
+     */
+    public function edit($id, $data): mixed
+    {
+        unset($data['password']);
+        return $this->transaction(function () use ($data, $id) {
+            $role_ids = $data['role_ids'] ?? [];
+            $post_ids = $data['post_ids'] ?? [];
+            // 1、判断用户是否可以操作
+            $query = $this->model->where('id', $id);
+            $query->auth([
+                'id' => $this->adminInfo['id'],
+                'dept' => $this->adminInfo['deptList']
+            ]);
+            $user = $query->findOrEmpty();
+            if ($user->isEmpty()) {
+                throw new ApiException('没有权限操作该数据');
+            }
+            if ($this->adminInfo['id'] > 1) {
+                // 2、判断部门id是否有操作权限
+                $dept_ids = SystemDept::whereRaw('FIND_IN_SET("' . $this->adminInfo['dept_id'] . '", level) > 0')->column('id');
+                if (!in_array($data['dept_id'], $dept_ids)) {
+                    throw new ApiException('没有权限操作该部门数据');
+                }
+                // 3、判断角色id是否有操作权限
+                $roleIds = [];
+                foreach ($this->adminInfo['roleList'] as $item) {
+                    $temp = SystemRole::whereRaw('FIND_IN_SET("' . $item['id'] . '", level) > 0')->column('id');
+                    $roleIds = array_merge($roleIds, $temp);
+                }
+                if (count(array_diff($role_ids, $roleIds)) > 0) {
+                    throw new ApiException('没有权限操作该角色数据');
+                }
+            }
+            $result = parent::edit($id, $data);
+            if ($result) {
+                $user->roles()->detach();
+                $user->posts()->detach();
+                $user->roles()->saveAll($role_ids);
+                if (!empty($post_ids)) {
+                    $user->posts()->save($post_ids);
+                }
+                $userInfoCache = new UserInfoCache($id);
+                $userInfoCache->clearUserInfo();
+            }
+            return $result;
+        });
+    }
+
+    /**
+     * 删除数据
+     * @param $ids
+     */
+    public function destroy($ids)
+    {
+        if (is_array($ids)) {
+            if (count($ids) > 1) {
+                throw new ApiException('禁止批量删除操作');
+            }
+            $ids = $ids[0];
+        }
+        if ($ids == 1) {
+            throw new ApiException('超级管理员禁止删除');
+        }
+        $query = $this->model->where('id', $ids);
+        $query->auth([
+            'id' => $this->adminInfo['id'],
+            'dept' => $this->adminInfo['deptList']
+        ]);
+        $user = $query->findOrEmpty();
+        if ($user->isEmpty()) {
+            throw new ApiException('没有权限操作该数据');
+        }
+        $userInfoCache = new UserInfoCache($ids);
+        $userInfoCache->clearUserInfo();
+        parent::destroy($ids);
+    }
+
+    /**
+     * 用户登录
+     * @param string $username
+     * @param string $password
+     * @param string $type
+     * @return array
+     */
+    public function login(string $username, string $password, string $type): array
+    {
+        $adminInfo = $this->model->where('username', $username)->findOrEmpty();
+        $status = 1;
+        $message = '登录成功';
+        if ($adminInfo->isEmpty()) {
+            $message = '账号或密码错误,请重新输入!';
+            throw new ApiException($message);
+        }
+        if ($adminInfo->status === 2) {
+            $status = 0;
+            $message = '您已被禁止登录!';
+        }
+        if (!password_verify($password, $adminInfo->password)) {
+            $status = 0;
+            $message = '账号或密码错误,请重新输入!';
+        }
+        if ($status === 0) {
+            // 登录事件
+            Event::emit('user.login', compact('username','status','message'));
+            throw new ApiException($message);
+        }
+        $adminInfo->login_time = date('Y-m-d H:i:s');
+        $adminInfo->login_ip = request()->getRealIp();
+        $adminInfo->save();
+
+        $token = JwtToken::generateToken([
+            'id' => $adminInfo->id,
+            'username' => $adminInfo->username,
+            'type' => $type
+        ]);
+        // 登录事件
+        $admin_id = $adminInfo->id;
+        Event::emit('user.login', compact('username','status','message','admin_id'));
+        return $token;
+    }
+
+    /**
+     * 密码修改
+     * @param $adminId
+     * @param $oldPassword
+     * @param $newPassword
+     * @return bool
+     */
+    public function modifyPassword($adminId, $oldPassword, $newPassword): bool
+    {
+        $model = $this->model->findOrEmpty($adminId);
+        if (password_verify($oldPassword, $model->password)) {
+            $model->password = password_hash($newPassword, PASSWORD_DEFAULT);
+            return $model->save();
+        } else {
+            throw new ApiException('原密码错误');
+        }
+    }
+
+    /**
+     * 修改数据
+     */
+    public function authEdit($id, $data)
+    {
+        if ($this->adminInfo['id'] > 1) {
+            // 判断用户是否可以操作
+            $query = SystemUser::where('id', $id);
+            $query->auth([
+                'id' => $this->adminInfo['id'],
+                'dept' => $this->adminInfo['deptList']
+            ]);
+            $user = $query->findOrEmpty();
+            if ($user->isEmpty()) {
+                throw new ApiException('没有权限操作该数据');
+            }
+        }
+        parent::edit($id, $data);
+    }
+
+}

+ 25 - 0
plugin/saiadmin/app/logic/tool/CrontabLogLogic.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\tool;
+
+use plugin\saiadmin\app\model\tool\CrontabLog;
+use plugin\saiadmin\basic\BaseLogic;
+
+/**
+ * 定时任务日志逻辑层
+ */
+class CrontabLogLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new CrontabLog();
+    }
+
+}

+ 185 - 0
plugin/saiadmin/app/logic/tool/CrontabLogic.php

@@ -0,0 +1,185 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\tool;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use plugin\saiadmin\app\model\tool\Crontab;
+use plugin\saiadmin\app\model\tool\CrontabLog;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\exception\ApiException;
+
+/**
+ * 定时任务逻辑层
+ */
+class CrontabLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new Crontab();
+    }
+
+    /**
+     * 添加任务
+     */
+    public function add($data): mixed
+    {
+        $second = $data['second'];
+        $minute = $data['minute'];
+        $hour = $data['hour'];
+        $week = $data['week'];
+        $day = $data['day'];
+        $month = $data['month'];
+
+        // 规则处理
+        $rule = match ($data['task_style']) {
+            1 => "0 {$minute} {$hour} * * *",
+            2 => "0 {$minute} * * * *",
+            3 => "0 {$minute} */{$hour} * * *",
+            4 => "0 */{$minute} * * * *",
+            5 => "*/{$second} * * * * *",
+            6 => "0 {$minute} {$hour} * * {$week}",
+            7 => "0 {$minute} {$hour} {$day} * *",
+            8 => "0 {$minute} {$hour} {$day} {$month} *",
+            default => throw new ApiException("任务类型异常"),
+        };
+
+        // 定时任务模型新增
+        $model = Crontab::create([
+            'name' => $data['name'],
+            'type' => $data['type'],
+            'task_style' => $data['task_style'],
+            'rule' => $rule,
+            'target' => $data['target'],
+            'parameter' => $data['parameter'],
+            'status' => $data['status'],
+            'remark' => $data['remark'],
+        ]);
+        return $model->getKey();
+    }
+
+    /**
+     * 修改任务
+     */
+    public function edit($id, $data): mixed
+    {
+        $second = $data['second'];
+        $minute = $data['minute'];
+        $hour = $data['hour'];
+        $week = $data['week'];
+        $day = $data['day'];
+        $month = $data['month'];
+
+        // 规则处理
+        $rule = match ($data['task_style']) {
+            1 => "0 {$minute} {$hour} * * *",
+            2 => "0 {$minute} * * * *",
+            3 => "0 {$minute} */{$hour} * * *",
+            4 => "0 */{$minute} * * * *",
+            5 => "*/{$second} * * * * *",
+            6 => "0 {$minute} {$hour} * * {$week}",
+            7 => "0 {$minute} {$hour} {$day} * *",
+            8 => "0 {$minute} {$hour} {$day} {$month} *",
+            default => throw new ApiException("任务类型异常"),
+        };
+
+        // 查询任务数据
+        $model = $this->model->findOrEmpty($id);
+        if ($model->isEmpty()) {
+            throw new ApiException('数据不存在');
+        }
+
+        // 修改任务数据
+        return $model->save([
+            'name' => $data['name'],
+            'type' => $data['type'],
+            'task_style' => $data['task_style'],
+            'rule' => $rule,
+            'target' => $data['target'],
+            'parameter' => $data['parameter'],
+            'status' => $data['status'],
+            'remark' => $data['remark'],
+        ]);
+    }
+
+    /**
+     * 执行定时任务
+     * @param $id
+     * @return bool
+     */
+    public function run($id): bool
+    {
+        $info = $this->find($id);
+        $data['crontab_id'] = $info->id;
+        $data['name'] = $info->name;
+        $data['target'] = $info->target;
+        $data['parameter'] = $info->parameter;
+        switch ($info->type) {
+            case 1:
+                // URL任务GET
+                $httpClient = new Client([
+                    'timeout' => 5,
+                    'verify' => false,
+                ]);
+                try {
+                    $httpClient->request('GET', $info->target);
+                    $data['status'] = 1;
+                    CrontabLog::create($data);
+                    return true;
+                } catch (GuzzleException $e) {
+                    $data['status'] = 2;
+                    $data['exception_info'] = $e->getMessage();
+                    CrontabLog::create($data);
+                    return false;
+                }
+            case 2:
+                // URL任务POST
+                $httpClient = new Client([
+                    'timeout' => 5,
+                    'verify' => false,
+                ]);
+                try {
+                    $res = $httpClient->request('POST', $info->target, [
+                        'form_params' => json_decode($info->parameter ?? '',true)
+                    ]);
+                    $data['status'] = 1;
+                    $data['exception_info'] = $res->getBody();
+                    CrontabLog::create($data);
+                    return true;
+                } catch (GuzzleException $e) {
+                    $data['status'] = 2;
+                    $data['exception_info'] = $e->getMessage();
+                    CrontabLog::create($data);
+                    return false;
+                }
+            case 3:
+                // 类任务
+                $class_name = $info->target;
+                $method_name = 'run';
+                $class = new $class_name;
+                if (method_exists($class, $method_name)) {
+                    $return = $class->$method_name($info->parameter);
+                    $data['status'] = 1;
+                    $data['exception_info'] = $return;
+                    CrontabLog::create($data);
+                    return true;
+                } else {
+                    $data['status'] = 2;
+                    $data['exception_info'] = '类:'.$class_name.',方法:run,未找到';
+                    CrontabLog::create($data);
+                    return false;
+
+                }
+            default:
+                return false;
+        }
+    }
+
+}

+ 186 - 0
plugin/saiadmin/app/logic/tool/GenerateColumnsLogic.php

@@ -0,0 +1,186 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\tool;
+
+use plugin\saiadmin\app\model\tool\GenerateColumns;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\utils\Helper;
+
+/**
+ * 代码生成业务字段逻辑层
+ */
+class GenerateColumnsLogic extends BaseLogic
+{
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new GenerateColumns();
+    }
+
+    public function saveExtra($data)
+    {
+        $default_column = ['create_time', 'update_time', 'created_by', 'updated_by', 'delete_time', 'remark'];
+        // 组装数据
+        foreach ($data as $k => $item) {
+
+            if ($item['column_name'] == 'delete_time') {
+                continue;
+            }
+
+            $column = [
+                'table_id' => $item['table_id'],
+                'column_name' => $item['column_name'],
+                'column_comment' => $item['column_comment'],
+                'column_type' => $item['column_type'],
+                'default_value' => $item['default_value'],
+                'is_pk' => ($item['column_key'] == 'PRI') ? 2 : 1 ,
+                'is_required' => $item['is_nullable'] == 'NO' ? 2 : 1,
+                'query_type' => 'eq',
+                'view_type' => 'input',
+                'sort' => count($data) - $k,
+                'options' => $item['options'] ?? null
+            ];
+
+            // 设置默认选项
+            if (!in_array($item['column_name'], $default_column) && empty($item['column_key'])) {
+                $column = array_merge(
+                    $column,
+                    [
+                        'is_insert' => 2,
+                        'is_edit' => 2,
+                        'is_list' => 2,
+                        'is_query' => 1,
+                        'is_sort' => 1,
+                    ]
+                );
+            }
+            $keyList = [
+                'column_comment', 'column_type', 'default_value', 'is_pk', 'is_required', 'is_insert', 'is_edit', 'is_list',
+                'is_query', 'is_sort', 'query_type', 'view_type', 'dict_type', 'options', 'sort', 'is_cover'
+            ];
+            foreach ($keyList as $key) {
+                if (isset($item[$key])) $column[$key] = $item[$key];
+            }
+            GenerateColumns::create($this->fieldDispose($column));
+        }
+    }
+
+    public function update($data, $where)
+    {
+        $data['is_insert'] = $data['is_insert'] ? 2 : 1;
+        $data['is_edit'] = $data['is_edit'] ? 2 : 1;
+        $data['is_list'] = $data['is_list'] ? 2 : 1;
+        $data['is_query'] = $data['is_query'] ? 2 : 1;
+        $data['is_sort'] = $data['is_sort'] ? 2 : 1;
+        $data['is_required'] = $data['is_required'] ? 2 : 1;
+        $this->model->update($data, $where);
+    }
+
+    private function fieldDispose(array $column): array
+    {
+        $object = new class {
+            public function viewTypeDispose(&$column): void
+            {
+                switch ($column['column_type']) {
+                    case 'varchar':
+                        $column['view_type'] = 'input';
+                        break;
+                    // 富文本
+                    case 'text':
+                    case 'longtext':
+                        $column['is_list'] = 1;
+                        $column['is_query'] = 1;
+                        $column['view_type'] = 'wangEditor';
+                        $options = [
+                            'height' => 400,
+                        ];
+                        $column['options'] = $options;
+                        break;
+                    // 日期字段
+                    case 'datetime':
+                        $column['view_type'] = 'date';
+                        $options = [
+                            'mode' => 'date',
+                            'showTime' => true,
+                        ];
+                        $column['options'] = $options;
+                        $column['query_type'] = 'between';
+                        break;
+                    case 'date':
+                        $column['view_type'] = 'date';
+                        $options = [
+                            'mode' => 'date',
+                            'showTime' => false,
+                        ];
+                        $column['options'] = $options;
+                        $column['query_type'] = 'between';
+                        break;
+                }
+            }
+
+            public function columnName(&$column): void
+            {
+                if (stristr($column['column_name'], 'name')) {
+                    $column['is_query'] = 2;
+                    $column['is_required'] = 2;
+                    $column['query_type'] = 'like';
+                }
+
+                if (stristr($column['column_name'], 'title')) {
+                    $column['is_query'] = 2;
+                    $column['is_required'] = 2;
+                    $column['query_type'] = 'like';
+                }
+
+                if (stristr($column['column_name'], 'type')) {
+                    $column['is_query'] = 2;
+                    $column['is_required'] = 2;
+                    $column['query_type'] = 'eq';
+                }
+
+                if (stristr($column['column_name'], 'image')) {
+                    $column['is_query'] = 1;
+                    $column['view_type'] = 'uploadImage';
+                    $options = [
+                        'multiple' => false,
+                        'limit' => 3,
+                    ];
+                    $column['options'] = $options;
+                }
+
+                if (stristr($column['column_name'], 'file')) {
+                    $column['is_query'] = 1;
+                    $column['view_type'] = 'uploadFile';
+                    $options = [
+                        'multiple' => false,
+                        'limit' => 3,
+                    ];
+                    $column['options'] = $options;
+                }
+
+                if (stristr($column['column_name'], 'attach')) {
+                    $column['is_query'] = 1;
+                    $column['view_type'] = 'uploadFile';
+                    $options = [
+                        'multiple' => false,
+                        'limit' => 3,
+                    ];
+                    $column['options'] = $options;
+                }
+            }
+        };
+
+        if(!$column['is_cover']) {
+            $object->viewTypeDispose($column);
+            $object->columnName($column);
+        }
+        $column['options'] = json_encode($column['options'], JSON_UNESCAPED_UNICODE);
+        return $column;
+    }
+}

+ 452 - 0
plugin/saiadmin/app/logic/tool/GenerateTablesLogic.php

@@ -0,0 +1,452 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\logic\tool;
+
+use plugin\saiadmin\app\logic\system\DatabaseLogic;
+use plugin\saiadmin\app\model\system\SystemMenu;
+use plugin\saiadmin\app\model\tool\GenerateTables;
+use plugin\saiadmin\app\model\tool\GenerateColumns;
+use plugin\saiadmin\exception\ApiException;
+use plugin\saiadmin\basic\BaseLogic;
+use plugin\saiadmin\utils\Helper;
+use plugin\saiadmin\utils\code\CodeZip;
+use plugin\saiadmin\utils\code\CodeEngine;
+
+/**
+ * 代码生成业务逻辑层
+ */
+class GenerateTablesLogic extends BaseLogic
+{
+    protected $columnLogic = null;
+
+    protected $dataLogic = null;
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->model = new GenerateTables();
+        $this->columnLogic = new GenerateColumnsLogic();
+        $this->dataLogic = new DatabaseLogic();
+    }
+
+    /**
+     * 删除表和字段信息
+     * @param $ids
+     */
+    public function destroy($ids)
+    {
+        $this->transaction(function () use ($ids) {
+            parent::destroy($ids);
+            GenerateColumns::destroy(function ($query) use ($ids) {
+                $query->where('table_id', 'in', $ids);
+            });
+        });
+    }
+
+    /**
+     * 装载表信息
+     * @param $names
+     * @param $source
+     * @return void
+     */
+    public function loadTable($names, $source): void
+    {
+        $config = dbSource()[$source];
+        if (!$config) {
+            throw new ApiException('数据库配置读取失败');
+        }
+
+        $prefix = $config['prefix'] ?? '';
+        foreach ($names as $item) {
+            $class_name = $item['name'];
+            if (!empty($prefix)) {
+                $class_name = Helper::str_replace_once($prefix, '', $class_name);
+            }
+            $class_name = Helper::camel($class_name);
+            $tableInfo = [
+                'table_name' => $item['name'],
+                'table_comment' => $item['comment'],
+                'class_name' => $class_name,
+                'business_name' => Helper::get_business($item['name']),
+                'belong_menu_id' => 4000,
+                'menu_name' => $item['comment'],
+                'tpl_category' => 'single',
+                'template' => 'app',
+                'stub' => 'saiadmin',
+                'namespace' => '',
+                'package_name' => '',
+                'source' => $source,
+                'generate_menus' => 'index,save,update,read,delete',
+            ];
+            $model = GenerateTables::create($tableInfo);
+            $columns = $this->dataLogic->getColumnList($item['name'], $source);
+            foreach ($columns as &$column) {
+                $column['table_id'] = $model->id;
+                $column['is_cover'] = false;
+            }
+            $this->columnLogic->saveExtra($columns);
+        }
+    }
+
+    /**
+     * 同步表字段信息
+     * @param $id
+     * @return void
+     */
+    public function sync($id)
+    {
+        $model = $this->model->findOrEmpty($id);
+        // 拉取已有数据表信息
+        $queryModel = $this->columnLogic->model->where('table_id', $id);
+        $columnLogicData = $this->columnLogic->getAll($queryModel);
+        $columnLogicList = [];
+        foreach ($columnLogicData as $item) {
+            $columnLogicList[$item['column_name']] = $item;
+        }
+        $this->columnLogic->destroy(function ($query) use ($id) {
+            $query->where('table_id', $id);
+        }, true);
+        $columns = $this->dataLogic->getColumnList($model->table_name, $model->source ?? '');
+        foreach ($columns as &$column) {
+            $column['table_id'] = $model->id;
+            $column['is_cover'] = false;
+            if (isset($columnLogicList[$column['column_name']])) {
+                // 存在历史信息的情况
+                $getcolumnLogicItem = $columnLogicList[$column['column_name']];
+                if ($getcolumnLogicItem['column_type'] == $column['column_type']) {
+                    $column['is_cover'] = true;
+                    foreach ($getcolumnLogicItem as $key => $item) {
+                        $array = [
+                            'column_comment', 'column_type', 'default_value', 'is_pk', 'is_required', 'is_insert', 'is_edit', 'is_list',
+                            'is_query', 'is_sort', 'query_type', 'view_type', 'dict_type', 'options', 'sort', 'is_cover'
+                        ];
+                        if (in_array($key, $array)){
+                            $column[$key] = $item;
+                        }
+                    }
+                }
+            }
+        }
+        $this->columnLogic->saveExtra($columns);
+    }
+
+    /**
+     * 代码预览
+     * @param $id
+     * @return array
+     */
+    public function preview($id): array
+    {
+        $data = $this->renderData($id);
+
+        $codeEngine = new CodeEngine($data);
+        $controllerContent = $codeEngine->renderContent('php', 'controller.stub');
+        $logicContent = $codeEngine->renderContent('php', 'logic.stub');
+        $modelContent = $codeEngine->renderContent('php', 'model.stub');
+        $validateContent = $codeEngine->renderContent('php', 'validate.stub');
+        $sqlContent = $codeEngine->renderContent('sql', 'sql.stub');
+        $indexContent = $codeEngine->renderContent('vue', 'index.stub');
+        $editContent = $codeEngine->renderContent('vue', 'edit.stub');
+        $apiContent = $codeEngine->renderContent('js', 'api.stub');
+
+        // 返回生成内容
+        return [
+            [
+                'tab_name' => 'controller.php',
+                'name' => 'controller',
+                'lang' => 'php',
+                'code' => $controllerContent
+            ],
+            [
+                'tab_name' => 'logic.php',
+                'name' => 'logic',
+                'lang' => 'php',
+                'code' => $logicContent
+            ],
+            [
+                'tab_name' => 'model.php',
+                'name' => 'model',
+                'lang' => 'php',
+                'code' => $modelContent
+            ],
+            [
+                'tab_name' => 'validate.php',
+                'name' => 'validate',
+                'lang' => 'php',
+                'code' => $validateContent
+            ],
+            [
+                'tab_name' => 'sql.sql',
+                'name' => 'sql',
+                'lang' => 'mysql',
+                'code' => $sqlContent
+            ],
+            [
+                'tab_name' => 'index.vue',
+                'name' => 'index',
+                'lang' => 'html',
+                'code' => $indexContent
+            ],
+            [
+                'tab_name' => 'edit.vue',
+                'name' => 'edit',
+                'lang' => 'html',
+                'code' => $editContent
+            ],
+            [
+                'tab_name' => 'api.js',
+                'name' => 'api',
+                'lang' => 'javascript',
+                'code' => $apiContent
+            ]
+        ];
+    }
+
+    /**
+     * 生成到模块
+     * @param $id
+     */
+    public function genModule($id)
+    {
+        $data = $this->renderData($id);
+
+        // 生成文件到模块
+        $codeEngine = new CodeEngine($data);
+        $codeEngine->generateBackend('controller', $codeEngine->renderContent('php', 'controller.stub'));
+        $codeEngine->generateBackend('logic', $codeEngine->renderContent('php', 'logic.stub'));
+        $codeEngine->generateBackend('model', $codeEngine->renderContent('php', 'model.stub'));
+        $codeEngine->generateBackend('validate', $codeEngine->renderContent('php', 'validate.stub'));
+        $codeEngine->generateFrontend('index', $codeEngine->renderContent('vue', 'index.stub'));
+        $codeEngine->generateFrontend('edit', $codeEngine->renderContent('vue', 'edit.stub'));
+        $codeEngine->generateFrontend('api', $codeEngine->renderContent('js', 'api.stub'));
+    }
+
+    /**
+     * 处理数据
+     * @param $id
+     * @return array
+     */
+    protected function renderData($id): array
+    {
+        $table = $this->model->findOrEmpty($id);
+        if (!in_array($table['template'], ["plugin", "app"])) {
+            throw new ApiException('应用类型必须为plugin或者app');
+        }
+        if (empty($table['namespace'])) {
+            throw new ApiException('请先设置应用名称');
+        }
+
+        $columns = $this->columnLogic->where('table_id', $id)
+            ->order('sort', 'desc')
+            ->select()
+            ->toArray();
+        $pk = 'id';
+        foreach ($columns as &$column) {
+            if ($column['is_pk'] == 2) {
+                $pk = $column['column_name'];
+            }
+            if ($column['column_name'] == 'delete_time') {
+                unset($column['column_name']);
+            }
+        }
+
+        // 处理特殊变量
+        if ($table['template'] == 'plugin') {
+            $namespace_start = "plugin\\".$table['namespace']."\\app\\";
+            $namespace_end =  $table['package_name'] != "" ? "\\".$table['package_name'] : "";
+            $url_path = 'app/'.$table['namespace'] . ($table['package_name'] != "" ? "/".$table['package_name'] : "") .'/'.$table['class_name'];
+            $route = 'app/';
+        } else {
+            $namespace_start = "app\\".$table['namespace']."\\";
+            $namespace_end =  $table['package_name'] != "" ? "\\".$table['package_name'] : "";
+            $url_path = $table['namespace'] . ($table['package_name'] != "" ? "/".$table['package_name'] : "") .'/'.$table['class_name'];
+            $route = '';
+        }
+        $data = $table->toArray();
+        $data['pk'] = $pk;
+        $data['namespace_start'] = $namespace_start;
+        $data['namespace_end'] = $namespace_end;
+        $data['url_path'] = $url_path;
+        $data['route'] = $route;
+        $data['tables'] = [$data];
+        $data['columns'] = $columns;
+        $data['db_source'] = defaultDbSource();
+
+        return $data;
+    }
+
+    /**
+     * 生成到模块
+     */
+    public function generateFile($id)
+    {
+        $table = $this->model->where('id', $id)->findOrEmpty();
+        if ($table->isEmpty()) {
+            throw new ApiException('请选择要生成的表');
+        }
+        $debug = config('app.debug', true);
+        if (!$debug) {
+            throw new ApiException('非调试模式下,不允许生成文件');
+        }
+        $this->genModule($id);
+        $this->updateMenu($table);
+    }
+
+    /**
+     * 代码生成下载
+     */
+    public function generate($idsArr): array
+    {
+        $zip = new CodeZip();
+        $tables = $this->model->where('id', 'in', $idsArr)->select()->toArray();
+        foreach ($idsArr as $table_id) {
+            $data = $this->renderData($table_id);
+            $data['tables'] = $tables;
+            $codeEngine = new CodeEngine($data);
+            $codeEngine->generateTemp();
+        }
+
+        $filename = 'saiadmin.zip';
+        $download = $zip->compress();
+
+        return compact('filename', 'download');
+    }
+
+    /**
+     * 处理菜单列表
+     * @param $tables
+     */
+    public function updateMenu($tables)
+    {
+        /*不存在的情况下进行新建操作*/
+        if ($tables['template'] == 'plugin') {
+            $url_path = 'app/'.$tables['namespace'] . ($tables['package_name'] != "" ? "/".$tables['package_name'] : "") .'/'.$tables['class_name'];
+            $code = 'app/'.$tables['namespace'] . ($tables['package_name'] != "" ? "/".$tables['package_name'] : "") .'/'.$tables['business_name'];
+        } else {
+            $url_path = $tables['namespace'] . ($tables['package_name'] != "" ? "/".$tables['package_name'] : "") .'/'.$tables['class_name'];
+            $code = $tables['namespace'] . ($tables['package_name'] != "" ? "/".$tables['package_name'] : "") .'/'.$tables['business_name'];
+        }
+        $component = $tables['namespace'] . ($tables['package_name'] != "" ? "/".$tables['package_name'] : "") .'/'.$tables['business_name'];
+
+        /*先获取一下已有的路由中是否包含当前ID的路由的核心信息*/
+        $model = new SystemMenu();
+        $tableMenu = $model->where('generate_id', $tables['id'])->findOrEmpty();
+        $fistMenu = [
+            'parent_id' => $tables['belong_menu_id'],
+            'level' => '0,' . $tables['belong_menu_id'],
+            'name' => $tables['menu_name'],
+            'code' => $code,
+            'icon' => 'icon-home',
+            'route' => $code,
+            'component' => "$component/index",
+            'redirect' => null,
+            'is_hidden' => 2,
+            'type' => 'M',
+            'status' => 1,
+            'sort' => 0,
+            'remark' => null,
+            'generate_id' => $tables['id']
+        ];
+        if ($tableMenu->isEmpty()) {
+            $temp = SystemMenu::create($fistMenu);
+            $fistMenuId = $temp->id;
+        } else {
+            $fistMenu['id'] = $tableMenu['id'];
+            $tableMenu->save($fistMenu);
+            $fistMenuId = $tableMenu['id'];
+        }
+        /*开始进行子权限的判定操作*/
+        $childNodes = [
+            ['name' => '列表', 'key' => 'index'],
+            ['name' => '保存', 'key' => 'save'],
+            ['name' => '更新', 'key' => 'update'],
+            ['name' => '读取', 'key' => 'read'],
+            ['name' => '删除', 'key' => 'destroy'],
+        ];
+
+        foreach ($childNodes as $node) {
+            $nodeData = $model->where('parent_id', $fistMenuId)->where('generate_key', $node['key'])->findOrEmpty();
+            $childNodeData = [
+                'parent_id' => $fistMenuId,
+                'level' => "{$tables['belong_menu_id']},{$fistMenuId}",
+                'name' => $tables['menu_name'] . $node['name'],
+                'code' => "/$url_path/{$node['key']}",
+                'icon' => null,
+                'route' => null,
+                'component' => null,
+                'redirect' => null,
+                'is_hidden' => 1,
+                'type' => 'B',
+                'status' => 1,
+                'sort' => 0,
+                'remark' => null,
+                'generate_key' => $node['key']
+            ];
+            if (!empty($nodeData)) {
+                $childNodeData['id'] = $nodeData['id'];
+                $nodeData->save($childNodeData);
+            } else {
+                $menuModel = new SystemMenu();
+                $menuModel->save($childNodeData);
+            }
+        }
+    }
+
+    /**
+     * 获取数据表字段信息
+     * @param $table_id
+     * @return mixed
+     */
+    public function getTableColumns($table_id): mixed
+    {
+        $query = $this->columnLogic->where('table_id', $table_id);
+        return $this->columnLogic->getAll($query);
+    }
+
+    /**
+     * 编辑数据
+     * @param $id
+     * @param $data
+     * @return mixed
+     */
+    public function edit($id, $data): mixed
+    {
+        $columns = $data['columns'];
+
+        unset($data['columns']);
+
+        if (!empty($data['belong_menu_id'])) {
+            $data['belong_menu_id'] = is_array($data['belong_menu_id']) ? array_pop($data['belong_menu_id']) : $data['belong_menu_id'];
+        } else {
+            $data['belong_menu_id'] = 0;
+        }
+
+        $data['generate_menus'] = implode(',', $data['generate_menus']);
+
+        if (empty($data['options'])) {
+            unset($data['options']);
+        }
+
+        $data['options'] = json_encode($data['options'], JSON_UNESCAPED_UNICODE);
+
+        // 更新业务表
+        $this->update($data, ['id' => $id]);
+
+        // 更新业务字段表
+        foreach ($columns as $column) {
+            if ($column['options']) {
+                $column['options'] = json_encode($column['options'], JSON_NUMERIC_CHECK);
+            }
+            $this->columnLogic->update($column, ['id' => $column['id']]);
+        }
+
+        return true;
+    }
+
+}

+ 82 - 0
plugin/saiadmin/app/middleware/CheckAuth.php

@@ -0,0 +1,82 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\middleware;
+
+use ReflectionClass;
+use Webman\Http\Request;
+use Webman\Http\Response;
+use Webman\MiddlewareInterface;
+use plugin\saiadmin\app\cache\UserAuthCache;
+use plugin\saiadmin\exception\SystemException;
+
+/**
+ * 权限检查中间件
+ */
+class CheckAuth implements MiddlewareInterface
+{
+    public function process(Request $request, callable $handler) : Response
+    {
+        // 通过反射获取控制器哪些方法不需要登录
+        $controller = new ReflectionClass($request->controller);
+        $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? [];
+
+        // 不登录访问,无需权限验证
+        if (in_array($request->action, $noNeedLogin)) {
+            return $handler($request);
+        }
+
+        // 登录信息
+        $token = getCurrentInfo();
+        if ($token === false) {
+            throw new SystemException('权限不足,无法访问或操作');
+        }
+
+        // 系统默认超级管理员,无需权限验证
+        if ($token['id'] === 1) {
+            return $handler($request);
+        }
+
+        // 接口请求权限判断
+        $path = $request->path();
+
+        // 处理接口路由替换
+        $replace = config('plugin.saiadmin.saithink.route_replace');
+        if (isset($replace[$path])) {
+            $path = $replace[$path];
+        }
+        $path = strtolower($path);
+
+        // 用户权限缓存
+        $userAuthCache = new UserAuthCache($token['id']);
+
+        // 全部路由文件
+        $routes = $this->formatUrl($userAuthCache->getAllUri());
+        // 请求接口有权限配置则进行验证
+        if (in_array($path, $routes)) {
+
+            $allowCodes = $userAuthCache->getAdminUri() ?? [];
+            $allowCodes = $this->formatUrl($allowCodes);
+            if (!in_array($path, $allowCodes)) {
+                throw new SystemException('权限不足,无法访问或操作');
+            }
+        }
+        return $handler($request);
+    }
+
+    /**
+     * 格式化URL
+     * @param array $data
+     * @return array|string[]
+     */
+    public function formatUrl(array $data): array
+    {
+        return array_map(function ($item) {
+            return strtolower($item);
+        }, $data);
+    }
+
+}

+ 39 - 0
plugin/saiadmin/app/middleware/CheckLogin.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\middleware;
+
+use ReflectionClass;
+use Webman\Http\Request;
+use Webman\Http\Response;
+use Webman\MiddlewareInterface;
+use Tinywan\Jwt\JwtToken;
+use plugin\saiadmin\exception\ApiException;
+
+/**
+ * 登录检查中间件
+ */
+class CheckLogin implements MiddlewareInterface
+{
+    public function process(Request $request, callable $handler): Response
+    {
+        // 通过反射获取控制器哪些方法不需要登录
+        $controller = new ReflectionClass($request->controller);
+        $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? [];
+
+        // 访问的方法需要登录
+        if (!in_array($request->action, $noNeedLogin)) {
+            try {
+                $token = JwtToken::getExtend();
+                $request->setHeader('check_login', true);
+                $request->setHeader('check_admin', $token);
+            } catch (\Throwable $e) {
+                throw new ApiException('您的登录凭证错误或者已过期,请重新登录', 401);
+            }
+        }
+        return $handler($request);
+    }
+}

+ 33 - 0
plugin/saiadmin/app/middleware/CrossDomain.php

@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\middleware;
+
+use Webman\Http\Request;
+use Webman\Http\Response;
+use Webman\MiddlewareInterface;
+
+/**
+ * 跨域中间件
+ */
+class CrossDomain implements MiddlewareInterface
+{
+    public function process(Request $request, callable $handler) : Response
+    {
+        // 如果是options请求则返回一个空响应,否则继续向洋葱芯穿越,并得到一个响应
+        $response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
+
+        // 给响应添加跨域相关的http头
+        $response->withHeaders([
+            'Access-Control-Allow-Credentials' => 'true',
+            'Access-Control-Allow-Origin' => $request->header('origin', '*'),
+            'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
+            'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
+        ]);
+
+        return $response;
+    }
+}

+ 42 - 0
plugin/saiadmin/app/middleware/SystemLog.php

@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\middleware;
+
+use ReflectionClass;
+use Webman\Event\Event;
+use Webman\Http\Request;
+use Webman\Http\Response;
+use Webman\MiddlewareInterface;
+use plugin\saiadmin\exception\ApiException;
+
+class SystemLog implements MiddlewareInterface
+{
+    /**
+     * @param Request $request
+     * @param callable $handler
+     * @return Response
+     */
+    public function process(Request $request, callable $handler): Response
+    {
+
+        // 通过反射获取控制器哪些方法不需要登录
+        $controller = new ReflectionClass($request->controller);
+        $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? [];
+
+        // 访问的方法需要登录
+        if (!in_array($request->action, $noNeedLogin)) {
+            try {
+                // 记录日志
+                Event::emit('user.operateLog', true);
+            } catch (\Throwable $e) {
+                throw new ApiException('登录凭获取失败,请检查');
+            }
+        }
+
+        return $handler($request);
+    }
+}

+ 34 - 0
plugin/saiadmin/app/model/system/SystemAttachment.php

@@ -0,0 +1,34 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\model\system;
+
+use plugin\saiadmin\basic\BaseModel;
+
+/**
+ * 附件模型
+ */
+class SystemAttachment extends BaseModel
+{
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    protected $table = 'sa_system_attachment';
+
+    public function searchOriginNameAttr($query, $value)
+    {
+        $query->where('origin_name', 'like', '%'.$value.'%');
+    }
+
+    public function searchMimeTypeAttr($query, $value)
+    {
+        $query->where('mime_type', 'like', $value.'/%');
+    }
+
+}

+ 29 - 0
plugin/saiadmin/app/model/system/SystemConfig.php

@@ -0,0 +1,29 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\model\system;
+
+use plugin\saiadmin\basic\BaseModel;
+
+/**
+ * 参数配置模型
+ */
+class SystemConfig extends BaseModel
+{
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    protected $table = 'sa_system_config';
+
+    public function getConfigSelectDataAttr($value)
+    {
+        return json_decode($value ?? '', true);
+    }
+
+}

+ 29 - 0
plugin/saiadmin/app/model/system/SystemConfigGroup.php

@@ -0,0 +1,29 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\model\system;
+
+use plugin\saiadmin\basic\BaseModel;
+
+/**
+ * 参数配置分组模型
+ */
+class SystemConfigGroup extends BaseModel
+{
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    protected $table = 'sa_system_config_group';
+
+    public function configs()
+    {
+        return $this->hasMany(SystemConfig::class, 'group_id', 'id');
+    }
+
+}

+ 42 - 0
plugin/saiadmin/app/model/system/SystemDept.php

@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\model\system;
+
+use plugin\saiadmin\basic\BaseModel;
+
+/**
+ * 部门模型
+ */
+class SystemDept extends BaseModel
+{
+    /**
+     * 数据表主键
+     * @var string
+     */
+    protected $pk = 'id';
+
+    protected $table = 'sa_system_dept';
+
+    /**
+     * 权限范围
+     */
+    public function scopeAuth($query, $value)
+    {
+        if (!empty($value)) {
+            $deptIds[] = $value['id'];
+            $ids = static::whereRaw('FIND_IN_SET("'.$value['id'].'", level) > 0')->column('id');
+            $deptIds = array_merge($deptIds, $ids);
+            $query->whereIn('id', $deptIds);
+        }
+    }
+
+    public function leader()
+    {
+        return $this->belongsToMany(SystemUser::class, SystemDeptLeader::class, 'user_id', 'dept_id');
+    }
+
+}

+ 17 - 0
plugin/saiadmin/app/model/system/SystemDeptLeader.php

@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | saiadmin [ saiadmin快速开发框架 ]
+// +----------------------------------------------------------------------
+// | Author: sai <1430792918@qq.com>
+// +----------------------------------------------------------------------
+namespace plugin\saiadmin\app\model\system;
+
+use think\model\Pivot;
+
+/**
+ * 部门领导关联模型
+ */
+class SystemDeptLeader extends Pivot
+{
+    protected $table = 'sa_system_dept_leader';
+}

Some files were not shown because too many files changed in this diff