runphp 7 месяцев назад
Родитель
Сommit
1bb7d64dad

+ 1 - 6
.gitignore

@@ -1,6 +1 @@
-composer.phar
-/vendor/
-
-# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
-# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
-# composer.lock
+.idea/

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+# Core Extension
+
+This extension provides core functionality for the backend application.
+
+## Features
+
+- 提供 \SixShop\core\Entity\BaseEntity 类,作为所有实体的基类。

+ 22 - 0
composer.json

@@ -0,0 +1,22 @@
+{
+    "name": "sixshop/core",
+    "description": "核心扩展",
+    "license": "MIT",
+    "autoload": {
+        "psr-4": {
+            "Sixshop\\Core\\": "src/"
+        },
+        "files": [
+            "src/helper.php"
+        ]
+    },
+    "authors": [
+        {
+            "name": "hui he",
+            "email": "runphp@qq.com"
+        }
+    ],
+    "require": {
+        "php": ">=8.3"
+    }
+}

+ 18 - 0
info.php

@@ -0,0 +1,18 @@
+<?php
+declare(strict_types=1);
+
+return [
+    'id' => 'core', # 扩展的唯一标识符
+    'name' => '核心', # 扩展的名称
+    'is_core' => true, # 是否核心扩展'
+    'category' => 'core', # 扩展的分类 core:核心扩展,other:其他扩展
+    'description' => '这是系统的核心扩展。', # 扩展的描述
+    'version' => '1.0.0',  # 扩展的版本
+    'core_version' => '^1.0',  # 支持的核心版本
+    'author' => 'runphp', # 作者
+    'email' => 'runphp@qq.com', # 作者的邮箱
+    'website' => '', # 扩展的地址,可以是扩展的仓库地址,帮助用户寻找扩展,安装扩展等网络地址
+    'image' => '', # 扩展的图片,用于展示扩展的图标,或者是扩展的截图等图片地址
+    'license' => 'MIT', # 扩展的开源协议
+    'weight' => 100, # 扩展的权重,用于控制加载先后顺序, 普通扩展请使用>=10000
+];

+ 12 - 0
src/Attribute/Cron.php

@@ -0,0 +1,12 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\core\Attribute;
+
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Cron
+{
+    public function __construct(
+        public string $rule,
+        public string $name = '',
+    ) {}
+}

+ 12 - 0
src/Attribute/Hook.php

@@ -0,0 +1,12 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Attribute;
+
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Hook
+{
+    public function __construct(
+        public string|array $hook,
+    ) {}
+}

+ 51 - 0
src/Contracts/ExtensionInterface.php

@@ -0,0 +1,51 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Contracts;
+
+interface ExtensionInterface
+{
+    /**
+     * 安装扩展
+     */
+    public function install(): void;
+
+    /**
+     * 卸载扩展
+     */
+    public function uninstall(): void;
+
+    /**
+     * 获取扩展信息
+     *
+     * @return array
+     */
+    public function getInfo(): array;
+
+    /**
+     * 获取扩展配置
+     *
+     * @return array
+     */
+    public function getConfig(): array;
+
+    /**
+     * 获取扩展命令
+     */
+    public function getCommands(): array;
+
+    /**
+     * 获取扩展钩子
+     */
+    public function getHooks(): array;
+
+    /**
+     * 获取扩展路由
+     */
+    public function getRoutes(): array;
+
+    /**
+     * 获取扩展计划任务
+     */
+    public function getCronJobs(): array;
+}

+ 15 - 0
src/Entity/BaseEntity.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace SixShop\core\Entity;
+
+use think\Entity;
+
+abstract class BaseEntity extends Entity
+{
+    protected function getOptions(): array
+    {
+        return [
+            'modelClass' => str_replace(['\\Entity\\', 'Entity'], ['\\Entity\\', 'Model'], static::class),
+        ];
+    }
+}

+ 46 - 0
src/Exception/ExceptionHandle.php

@@ -0,0 +1,46 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\core\Exception;
+
+use think\db\exception\ModelNotFoundException;
+use think\exception\Handle;
+use think\exception\ValidateException;
+use think\Request;
+use think\Response;
+use Throwable;
+
+class ExceptionHandle extends Handle
+{
+    public function render(Request $request, Throwable $e): Response
+    {
+        if ($e instanceof ModelNotFoundException) {
+            $e = new NotFoundException($e);
+        }
+        if ($e instanceof ValidateException) {
+            $e = new LogicException(error_response(msg:$e->getMessage(),  status: 'invalid_argument', code: 400, httpCode: 200));
+        }
+        return parent::render($request, $e);
+    }
+
+    protected function getDebugMsg(Throwable $exception): array
+    {
+        $debugInfo = parent::getDebugMsg($exception);
+
+        $debugInfo['data'] = $debugInfo['datas'];
+        $debugInfo['msg'] = $debugInfo['message'];
+        unset($debugInfo['datas'], $debugInfo['message']);
+
+
+        return $debugInfo;
+    }
+
+    protected function getDeployMsg(Throwable $exception): array
+    {
+        $deployInfo = parent::getDeployMsg($exception);
+
+        $deployInfo['msg'] = $deployInfo['message'];
+        unset($deployInfo['message']);
+
+        return $deployInfo;
+    }
+}

+ 10 - 0
src/Exception/LogicException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\core\Exception;
+
+use think\exception\HttpResponseException;
+
+class LogicException extends HttpResponseException
+{
+
+}

+ 14 - 0
src/Exception/NotFoundException.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace SixShop\core\Exception;
+
+use Exception;
+use think\exception\HttpResponseException;
+
+class NotFoundException extends HttpResponseException
+{
+    public function __construct(Exception $e)
+    {
+        parent::__construct(error_response(msg:$e->getMessage(),  status: 'not_found', code: 404, httpCode: 200));
+    }
+}

+ 13 - 0
src/Extension.php

@@ -0,0 +1,13 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core;
+
+
+class Extension extends ExtensionAbstract
+{
+    protected function getBaseDir(): string
+    {
+        return dirname(__DIR__);
+    }
+}

+ 75 - 0
src/ExtensionAbstract.php

@@ -0,0 +1,75 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core;
+
+use SixShop\core\Contracts\ExtensionInterface;
+
+abstract class ExtensionAbstract implements ExtensionInterface
+{
+    private array $info;
+
+    public function getInfo(): array
+    {
+        if (empty($this->info)) {
+            if (!file_exists($this->getBaseDir() . '/info.php')) {
+                throw new \Exception('Extension info file not found, please check the extension directory and info.php file.');
+            }
+            $this->info = require $this->getBaseDir() . '/info.php';
+        }
+        return $this->info;
+    }
+
+    abstract protected function getBaseDir(): string;
+
+    public function getConfig(): array
+    {
+        if (!file_exists($this->getBaseDir() . '/config.php')) {
+            return [];
+        }
+        return require $this->getBaseDir() . '/config.php';
+    }
+
+    public function install(): void
+    {
+    }
+
+    public function uninstall(): void
+    {
+    }
+
+    public function getCommands(): array
+    {
+        if (!file_exists($this->getBaseDir() . '/command.php')) {
+            return [];
+        }
+        return require $this->getBaseDir() . '/command.php';
+    }
+    public function getHooks(): array
+    {
+        return  [];
+    }
+
+    /**
+     * 获取路由
+     * @return array<string, string>
+     */
+    public function getRoutes(): array
+    {
+        $adminRoute = $this->getBaseDir() . '/route/admin.php';
+        $apiRoute = $this->getBaseDir() . '/route/api.php';
+        $routes = [];
+        if (file_exists($adminRoute)) {
+            $routes['admin'] = $adminRoute;
+        }
+        if (file_exists($apiRoute)) {
+            $routes['api'] = $apiRoute;
+        }
+        return $routes;
+    }
+
+    public function getCronJobs(): array
+    {
+        return [];
+    }
+}

+ 177 - 0
src/Job/BaseJob.php

@@ -0,0 +1,177 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Job;
+
+use think\facade\Log;
+use think\queue\Job;
+use function Opis\Closure\{serialize, unserialize};
+use Closure;
+
+/**
+ * @template T
+ */
+abstract class BaseJob
+{
+    // 最大重试次数
+    protected int $maxAttempts = 3;
+
+    // 重试延迟时间(秒)
+    protected int $retryDelay = 60;
+
+    // 是否启用失败回调
+    protected bool $enableFailedCallback = true;
+
+    // 是否闭包
+    protected bool $isClosure = false;
+
+    /**
+     * 任务失败处理方法 - 子类可重写
+     *
+     * @param T $data 任务数据
+     */
+    protected function onFailed(mixed $data): void
+    {
+        // 默认失败处理逻辑
+        Log::error('队列任务执行失败: ' . static::class, (array)$data);
+    }
+
+    /**
+     * 任务前置处理 - 子类可重写
+     *
+     * @param T $data 任务数据
+     * @return bool 是否继续执行
+     */
+    protected function beforeExecute(mixed $data): bool
+    {
+        return true;
+    }
+
+    /**
+     * 任务后置处理 - 子类可重写
+     *
+     * @param T $data 任务数据
+     * @param mixed $result 执行结果
+     */
+    protected function afterExecute(mixed $data, mixed $result): void
+    {
+        // 可以在这里添加通用的后置处理逻辑
+    }
+
+    /**
+     * 主要的处理方法 - 不需要子类重写
+     *
+     * @param Job $job 队列任务对象
+     * @param T $data 任务数据
+     */
+    public function fire(Job $job, mixed $data): void
+    {
+        try {
+            if ($this->isClosure) {
+                $data = unserialize($data);
+            }
+            // 前置处理
+            if (!$this->beforeExecute($data)) {
+                $job->delete();
+                return;
+            }
+
+            if (method_exists($this, 'execute')) {
+                // 执行任务
+                $result = $this->execute($data);
+            }
+
+            // 后置处理
+            $this->afterExecute($data, $result);
+
+            // 标记任务完成
+            $job->delete();
+
+        } catch (\Exception|\Throwable $e) {
+            $this->handleException($job, $data, $e);
+        }
+    }
+
+    /**
+     * 分发任务
+     *
+     * @param T $data 任务数据
+     * @param int $delay 延迟时间
+     * @param string|null $queue 队列名称
+     */
+    public static function dispatch(mixed $data = '', int $delay = 0, ?string $queue = null): JobDispatcher
+    {
+        if ($data instanceof Closure) {
+            $data = serialize($data);
+        }
+        return new JobDispatcher(static ::class, $data, $delay, $queue);
+    }
+
+    /**
+     * 异常处理
+     *
+     * @param Job $job 队列任务对象
+     * @param T $data 任务数据
+     * @param \Throwable|\Exception $exception 异常对象
+     */
+    protected function handleException(Job $job, mixed $data, \Throwable|\Exception $exception): void
+    {
+        Log::error('队列任务执行异常: ' . static::class . ' - ' . $exception->getMessage(), [
+            'data' => is_array($data) ? $data : ['data' => $data],
+            'trace' => $exception->getTraceAsString()
+        ]);
+
+        // 判断是否需要重试
+        if ($job->attempts() < $this->maxAttempts) {
+            // 重新发布任务
+            $job->release($this->retryDelay);
+        } else {
+            // 标记任务失败
+            $job->failed($exception);
+
+            // 执行失败回调
+            if ($this->enableFailedCallback) {
+                try {
+                    $this->onFailed($data);
+                } catch (\Exception $e) {
+                    Log::error('任务失败回调执行异常: ' . $e->getMessage());
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置最大重试次数
+     *
+     * @param int $attempts
+     * @return $this
+     */
+    protected function setMaxAttempts(int $attempts): static
+    {
+        $this->maxAttempts = $attempts;
+        return $this;
+    }
+
+    /**
+     * 设置重试延迟时间
+     *
+     * @param int $delay
+     * @return $this
+     */
+    protected function setRetryDelay(int $delay): static
+    {
+        $this->retryDelay = $delay;
+        return $this;
+    }
+
+    /**
+     * 禁用失败回调
+     *
+     * @return $this
+     */
+    protected function disableFailedCallback(): static
+    {
+        $this->enableFailedCallback = false;
+        return $this;
+    }
+}

+ 59 - 0
src/Job/JobDispatcher.php

@@ -0,0 +1,59 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Job;
+
+class JobDispatcher
+{
+    public function __construct(
+        private readonly string $jobClass,
+        private readonly mixed $data = null,
+        private int $delay = 0,
+        private ?string $queue = null
+    ) {
+    }
+
+    /**
+     * 设置延迟时间
+     *
+     * @param int $delay 延迟秒数
+     * @return $this
+     */
+    public function delay(int $delay): self
+    {
+        $this->delay = $delay;
+        return $this;
+    }
+
+    /**
+     * 设置队列名称
+     *
+     * @param string $queue 队列名称
+     * @return $this
+     */
+    public function onQueue(string $queue): self
+    {
+        $this->queue = $queue;
+        return $this;
+    }
+
+    /**
+     * 设置在特定时间执行
+     *
+     * @param int $timestamp 时间戳
+     * @return $this
+     */
+    public function at(int $timestamp): self
+    {
+        $this->delay = $timestamp - time();
+        return $this;
+    }
+
+    /**
+     * 析构时自动分发任务
+     */
+    public function __destruct()
+    {
+        queue($this->jobClass, $this->data, $this->delay, $this->queue);
+    }
+}

+ 24 - 0
src/Middleware/ExtensionStatusMiddleware.php

@@ -0,0 +1,24 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Middleware;
+
+use Closure;
+use SixShop\Extension\system\Enum\ExtensionStatusEnum;
+use SixShop\Extension\system\ExtensionManager;
+
+class ExtensionStatusMiddleware
+{
+    public function __construct(protected ExtensionManager $extensionManager)
+    {
+    }
+
+    public function handle($request, Closure $next, $moduleName)
+    {
+        $extensionModel = $this->extensionManager->getInfo($moduleName);
+        return match ($extensionModel->status) {
+            ExtensionStatusEnum::ENABLED => $next($request),
+            default => error_response(msg: '模块`' . $moduleName . '`未启用', httpCode: 403)
+        };
+    }
+}

+ 24 - 0
src/Middleware/MacroPageMiddleware.php

@@ -0,0 +1,24 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Middleware;
+
+use Closure;
+use SixShop\core\Request;
+use think\Exception;
+use think\Response;
+
+class MacroPageMiddleware
+{
+    public function handle(Request $request, Closure $next): Response
+    {
+        $request->macro('pageAndLimit', function (): array {
+            $page = input('page/d', 1);
+            $limit = input('limit/d', 10);
+            throw_if($page < 1, Exception::class,'页码不能小于1');
+            throw_if($limit < 1 || $limit > 100, Exception::class,'每页数量必须在1-100之间');
+            return ['page' => $page, 'list_rows' => $limit];
+        });
+        return $next($request);
+    }
+}

+ 16 - 0
src/Request.php

@@ -0,0 +1,16 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\core;
+
+use think\helper\Macroable;
+
+/**
+ * @property string $adminID 管理员ID
+ * @property string $userID 用户ID
+ * @property string $token 令牌
+ * @method array pageAndLimit() 分页和每页数量[page, limit]
+ */
+class Request extends \think\Request
+{
+    use Macroable;
+}

+ 31 - 0
src/Response/Xml.php

@@ -0,0 +1,31 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Response;
+
+class Xml extends \think\response\Xml
+{
+    protected function xmlEncode($data, string $root, string $item, $attr, string $id, string $encoding): string
+    {
+        if (is_array($attr)) {
+            $array = [];
+            foreach ($attr as $key => $value) {
+                $array[] = "{$key}=\"{$value}\"";
+            }
+            $attr = implode(' ', $array);
+        }
+
+        $attr = trim($attr);
+        $attr = empty($attr) ? '' : " {$attr}";
+        $xml = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
+        if ($this->options['xslt']) {
+            $xml .= '<?xml-stylesheet type="text/xsl" href="' . $this->options['xslt'] . '"?>';
+        }
+        $xml .= "<{$root}{$attr}>";
+        $xml .= $this->dataToXml($data, $item, $id);
+        $xml .= "</{$root}>";
+
+        return $xml;
+
+    }
+}

+ 45 - 0
src/Service/AutoloadService.php

@@ -0,0 +1,45 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Service;
+
+use Composer\Json\JsonFile;
+use SixShop\core\SixShopKernel;
+
+
+class AutoloadService
+{
+    public function init(SixShopKernel $app): void
+    {
+        $extensionPath = $app->getExtensionPath();
+        $classLoader = $app->classLoader;
+        $moduleNameList = $app->getModuleNameList();
+        foreach ($moduleNameList as $moduleName) {
+            $dir = $extensionPath.$moduleName;
+            if (file_exists($dir . '/composer.json')) {
+                $composerJson = new JsonFile($dir . '/composer.json');
+                $composer = $composerJson->read();
+                $autoload = $composer['autoload'] ?? [];
+                $autoload['psr-4'] = $autoload['psr-4'] ?? [];
+                foreach ($autoload['psr-4'] as $namespace => $path) {
+                    $classLoader->addPsr4($namespace, $dir . '/' . $path);
+                }
+                $autoload['files'] = $autoload['files'] ?? [];
+                foreach ($autoload['files'] as $file) {
+                    require_once $dir . '/' . $file;
+                }
+            } else {
+                $namespace = "SixShop\\Extension\\$moduleName\\";
+                $path = $dir . '/src';
+                if (!isset($classLoader->getPrefixesPsr4()[$namespace])) {
+                    $classLoader->addPsr4($namespace, $path);
+                    $helperFunctionFile = $dir . "/src/helper.php";
+                    if (is_file($helperFunctionFile)) {
+                        require_once $helperFunctionFile;
+                    }
+                }
+            }
+            $app->bind('extension.' . $moduleName, $namespace.'Extension');
+        }
+    }
+}

+ 22 - 0
src/Service/CommandService.php

@@ -0,0 +1,22 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Service;
+
+use SixShop\core\SixShopKernel;
+use SixShop\Extension\system\ExtensionManager;
+
+class CommandService
+{
+    public function init(SixShopKernel $app): void
+    {
+        $commands = $app->config->get('console.commands', []);
+        $extensionManager = $app->make(ExtensionManager::class);
+        foreach (module_name_list() as $moduleName) {
+            $commands = array_merge($commands, $extensionManager->getExtension($moduleName)->getCommands());
+        }
+        $app->config->set([
+            'commands' => $commands
+        ], 'console');
+    }
+}

+ 40 - 0
src/Service/HookAttributeService.php

@@ -0,0 +1,40 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Service;
+
+use ReflectionClass;
+use ReflectionMethod;
+use SixShop\core\Attribute\Hook;
+use SixShop\core\SixShopKernel;
+use SixShop\Extension\system\Enum\ExtensionStatusEnum;
+use SixShop\Extension\system\ExtensionManager;
+use think\facade\Event;
+
+class HookAttributeService
+{
+    public function init(SixShopKernel $app): void
+    {
+        $extensionManager = $app->make(ExtensionManager::class);
+        foreach (module_name_list() as $moduleName) {
+            if ($extensionManager->getInfo($moduleName)->status !== ExtensionStatusEnum::ENABLED) {
+                continue;
+            }
+            $extension = $extensionManager->getExtension($moduleName);
+            $hookClassList = $extension->getHooks();
+            foreach ($hookClassList as $hookClass) {
+                $ref = new ReflectionClass($hookClass);
+                foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
+                    $attributes = $method->getAttributes(Hook::class);
+                    foreach ($attributes as $attr) {
+                        $hookNameList = (array)$attr->newInstance()->hook;
+                        foreach ($hookNameList as $hookName) {
+                            Event::listen($hookName, [$hookClass, $method->getName()]);
+                        }
+                    }
+                }
+            }
+        }
+        $app->event->trigger('hook_init', $app);
+    }
+}

+ 33 - 0
src/Service/RegisterRouteService.php

@@ -0,0 +1,33 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Service;
+
+use SixShop\core\Middleware\ExtensionStatusMiddleware;
+use SixShop\core\SixShopKernel;
+use SixShop\Extension\system\ExtensionManager;
+use think\event\RouteLoaded;
+use think\facade\Route;
+
+class RegisterRouteService
+{
+    public function init(SixShopKernel $app): void
+    {
+        $extensionManager = $app->make(ExtensionManager::class);
+        $app->event->listen(RouteLoaded::class, function () use ($extensionManager, $app) {
+            $appName = $app->http->getName();
+            foreach (module_name_list() as $moduleName) {
+                $extension = $extensionManager->getExtension($moduleName);
+                $routes = $extension->getRoutes();
+                if (isset($routes[$appName])) {
+                    $routeFile = $routes[$appName];
+                    Route::group($moduleName, function () use ($routeFile) {
+                        include $routeFile;
+                    })->middleware(
+                        ExtensionStatusMiddleware::class, $moduleName
+                    );
+                }
+            }
+        });
+    }
+}

+ 121 - 0
src/SixShopKernel.php

@@ -0,0 +1,121 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core;
+
+use Composer\Autoload\ClassLoader;
+use SixShop\core\Exception\ExceptionHandle;
+use SixShop\core\Service\AutoloadService;
+use SixShop\core\Service\CommandService;
+use SixShop\core\Service\HookAttributeService;
+use SixShop\core\Service\RegisterRouteService;
+use think\App;
+use think\exception\Handle;
+
+/**
+ * @property ClassLoader $classLoader
+ */
+class SixShopKernel extends App
+{
+    /**
+     * 扩展目录
+     */
+    protected string $extensionPath = '';
+
+    protected array $initializerServices = [
+        AutoloadService::class,
+        RegisterRouteService::class,
+        HookAttributeService::class,
+    ];
+
+    private array $moduleNameList = [];
+
+    public function __construct(ClassLoader $classLoader, string $environment = '', string $rootPath = '')
+    {
+        parent::__construct($rootPath);
+        if ($environment === '') {
+            $environment = getenv('RUNTIME_ENVIRONMENT') ?: '';
+            if (getenv('IS_DDEV_PROJECT') == 'true') {
+                $environment = 'ddev';
+            }
+        }
+        $this->setEnvName($environment);
+        $this->bind('think\Request', Request::class);
+        $this->instance('classLoader', $classLoader);
+        $this->extensionPath = $this->rootPath . 'extension' . DIRECTORY_SEPARATOR; // todo config
+        $this->initializers = array_merge($this->initializers, $this->initializerServices);
+    }
+
+    public function runHttp(): void
+    {
+        $this->bind(Handle::class, ExceptionHandle::class);
+        $response = $this->http->run();
+        $response->send();
+        $this->http->end($response);
+    }
+
+    public function getExtensionPath(): string
+    {
+        return $this->extensionPath;
+    }
+
+    public function runConsole(): int
+    {
+        $this->initializers[] = CommandService::class;
+        return $this->initialize()->console->run();
+    }
+
+    public function getEnvName(): string
+    {
+        return $this->envName;
+    }
+
+    public function getModuleNameList(): array
+    {
+        if (empty($this->moduleNameList)) {
+            $coreFile = $this->rootPath . 'runtime/module_name_list_core.php';
+            if (file_exists($coreFile)) {
+                $this->moduleNameList = require $coreFile;
+            } else {
+                $moduleInfoList = [];
+                $extensionDirs = array_diff(scandir($this->extensionPath), ['.', '..']);
+                foreach ($extensionDirs as $item) {
+                    if (!is_dir($this->extensionPath . $item)) {
+                        continue;
+                    }
+                    $infoFile = $this->extensionPath . $item . '/info.php';
+                    if (is_file($infoFile)) {
+                        $info = require $infoFile;
+                        $info['weight'] = $info['weight'] ?? 10000;
+                        if ($info['is_core'] ?? false) {
+                            $this->moduleNameList[] = $info['id'];
+                            $moduleInfoList[] = $info;
+                        }
+                    }
+                }
+                usort($moduleInfoList, function ($a, $b) {
+                    return $a['weight'] <=> $b['weight'];
+                });
+                $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL;
+                $content = '<?php ' . PHP_EOL . $header . "return " . var_export(array_column($moduleInfoList, 'id'), true) . ';';
+                file_put_contents($coreFile, $content);
+            }
+            $normalFile = $this->rootPath . 'runtime/module_name_list_normal.php';
+            if (file_exists($normalFile)) {
+                $this->moduleNameList = array_merge($this->moduleNameList, require $normalFile);
+            }
+        }
+        return $this->moduleNameList;
+    }
+
+    public function loadEnv(string $envName = ''): void
+    {
+        $home = getenv('HOME');
+        $envFile = $envName ? $home . '/.env.' . $envName : $home . '/.env';
+        if (is_file($envFile)) {
+            $this->env->load($envFile);
+            return;
+        }
+        parent::loadEnv($envName);
+    }
+}

+ 22 - 0
src/Trait/ConfigTrait.php

@@ -0,0 +1,22 @@
+<?php
+declare(strict_types=1);
+
+namespace SixShop\core\Trait;
+
+use SixShop\Extension\system\ExtensionManager;
+
+trait ConfigTrait
+{
+    public function __construct(private readonly ExtensionManager $extensionManager, private array $options = [])
+    {
+    }
+
+    public function getConfig(string $key = null): mixed
+    {
+        if (empty($this->options)) {
+            $extensionID = explode('\\', static::class)[2];
+            $this->options = $this->extensionManager->getExtensionConfig($extensionID);
+        }
+        return $key ? ($this->options[$key] ?? null) : $this->options;
+    }
+}

+ 16 - 0
src/Trait/EventTrait.php

@@ -0,0 +1,16 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\core\Trait;
+
+
+use think\facade\Event;
+
+trait EventTrait
+{
+    public static function trigger(mixed $content): static
+    {
+        $event = new static($content);
+        Event::trigger($event);
+        return $event;
+    }
+}

+ 163 - 0
src/helper.php

@@ -0,0 +1,163 @@
+<?php
+declare(strict_types=1);
+
+use SixShop\core\Exception\LogicException;
+use SixShop\core\Response\Xml;
+use think\Container;
+use think\Paginator;
+use think\Response;
+use think\response\Json;
+
+if (!function_exists('success_response')) {
+
+    /**
+     * 返回成功数据
+     */
+    function success_response(mixed $data = [], string $status = 'ok', int $code = 200, string $msg = 'success', string $type = 'json', string $xslt = ''): Response
+    {
+        if ($xslt) {
+            $type = 'xml';
+        }
+        $responseData = [
+            'code' => $code,
+            'status' => $status,
+            'msg' => $msg,
+            'data' => $data
+        ];
+        if ($type == 'xml') {
+            /* @var Xml $response */
+            $response = Container::getInstance()->invokeClass(Xml::class, [$responseData, 200]);
+            $response = $response->options(['root_node' => 'root', 'xslt' => $xslt]);
+        } else {
+            $response = Response::create($responseData, $type);
+        }
+        return $response;
+    }
+}
+
+if (!function_exists('page_response')) {
+    /**
+     * 返回分页数据
+     */
+    function page_response(Paginator $page, mixed $data = [], string $status = 'ok', int $code = 200, string $msg = 'success'): Response
+    {
+        return json([
+            'code' => $code,
+            'status' => $status,
+            'msg' => $msg,
+            'page' => $page,
+            'data' => $data
+        ]);
+    }
+}
+
+if (!function_exists('error_response')) {
+    /**
+     * 返回失败数据
+     */
+    function error_response(string $msg = 'error', string $status = 'error', int $code = 1, mixed $data = [], int $httpCode = 400, $header = [], $options = []): Response
+    {
+        return json([
+            'code' => $code,
+            'status' => $status,
+            'msg' => $msg,
+            'data' => $data
+        ], $httpCode, $header, $options);
+    }
+}
+
+if (!function_exists('throw_logic_exception')) {
+    /**
+     * 抛出逻辑异常
+     */
+    function throw_logic_exception(string $msg = 'error', int $code = 1, string $status = 'error', mixed $data = [], int $httpCode = 200, $header = [], $options = []): void
+    {
+        throw new LogicException(error_response($msg, $status, $code, $data, $httpCode, $header, $options));
+    }
+}
+
+if (!function_exists('build_tree_options')) {
+    /**
+     * 构建树形结构选项
+     * @param array $data 数据源
+     * @param string $valueField 值字段
+     * @param string $labelField 标签字段
+     * @param string $parentField 父字段
+     * @param int $parentId 父ID
+     * @param string $childrenKey 子节点键
+     * @param bool $preserveOriginal 是否保留原始数据
+     */
+    function build_tree_options(
+        array  $data,
+        string $valueField = 'id',
+        string $labelField = 'name',
+        string $parentField = 'parent_id',
+        int    $parentId = 0,
+        string $childrenKey = 'children',
+        bool   $preserveOriginal = true
+    ): array
+    {
+        $tree = [];
+        foreach ($data as $item) {
+            if ($item[$parentField] == $parentId) {
+                $node = [
+                    'value' => $item[$valueField],
+                    'label' => $item[$labelField]
+                ];
+
+                // 根据参数决定是否保留原始数据
+                if ($preserveOriginal) {
+                    $node = array_merge($item, $node);
+                }
+
+                $children = build_tree_options(
+                    $data,
+                    $valueField,
+                    $labelField,
+                    $parentField,
+                    $item[$valueField],
+                    $childrenKey,
+                    $preserveOriginal // 传递参数到递归调用
+                );
+
+                if ($children) {
+                    $node[$childrenKey] = $children;
+                }
+                $tree[] = $node;
+            }
+        }
+        return $tree;
+    }
+}
+
+
+if (!function_exists('secret_password')) {
+    /**
+     * 生成随机密码
+     * @param int $length 密码长度
+     * @return string 生成的密码
+     */
+    function secret_password(int $length = 16): string
+    {
+        // 确保密码包含各种字符类型
+        $lowercase = 'abcdefghijklmnopqrstuvwxyz';
+        $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        $numbers = '0123456789';
+        $specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?';
+
+        // 至少包含每种类型的一个字符
+        $password = $lowercase[random_int(0, strlen($lowercase) - 1)];
+        $password .= $uppercase[random_int(0, strlen($uppercase) - 1)];
+        $password .= $numbers[random_int(0, strlen($numbers) - 1)];
+        $password .= $specialChars[random_int(0, strlen($specialChars) - 1)];
+
+        // 剩余字符随机生成
+        $allChars = $lowercase . $uppercase . $numbers . $specialChars;
+        for ($i = 4; $i < $length; $i++) {
+            $password .= $allChars[random_int(0, strlen($allChars) - 1)];
+        }
+
+        // 打乱字符顺序
+        return str_shuffle($password);
+    }
+}