diff --git a/.agents/rules/ulthon-file-override-mechanism.md b/.agents/rules/ulthon-file-override-mechanism.md index ee138b7..f42806f 100644 --- a/.agents/rules/ulthon-file-override-mechanism.md +++ b/.agents/rules/ulthon-file-override-mechanism.md @@ -81,6 +81,26 @@ Ulthon Admin 在"类继承覆盖"之外,还提供"文件级覆盖"能力:业 - 覆盖某控制器视图:在 `app/admin/view//.html` 放同名文件 - 覆盖 CURD 迁移模板:在 `app/common/command/curd/migrate.tpl` 放同名文件 +## 全局函数覆盖 + +`extend/base/helper.php` 里的所有全局函数都有 `if (!function_exists('xxx'))` 包裹。项目侧如需覆盖某个全局函数的行为(例如修复框架 bug 或调整实现),只需在 `app/common.php` 的 `include helper.php` 语句**之前**定义同名函数即可。 + +加载顺序:`app/common.php` 先执行 → 项目侧函数先定义 → `include helper.php` 时 `if (!function_exists())` 检查发现已存在 → 跳过框架版本。 + +示例: + +```php +// app/common.php +function sysconfig($group, $name = null, $default = null) { + // 项目侧自定义实现 + return my_custom_logic($group, $name, $default); +} + +include App::getRootPath() . '/extend/base/helper.php'; +``` + +注意:该机制是"函数级覆盖",与本文档前述三类"文件级覆盖"是独立机制。helper.php 中已包裹的函数清单见源码(截至当前版本共 27 个:`__url`、`flow_url`、`password`、`xdebug`、`sysconfig`、`array_format_key`、`auth`、`get_session_admin`、`read_header_token`、`json_message`、`unparse_url`、`build_upload_url`、`event_handle_result`、`event_handle_string`、`event_view_content`、`event_view_replace`、`event_view_replace_js`、`event_response`、`app_file_path`、`array_to_table`、`format_bytes`、`parse_bytes`、`get_store_value`、`set_store_value`、`get_site_version_key`、`format_null_value`、`ctype_numeric`)。 + ## 相关文件 - `extend/base/helper.php`(`app_file_path()` 定义) diff --git a/.agents/rules/ulthon-naming-convention.md b/.agents/rules/ulthon-naming-convention.md index 18f52af..8f23be8 100644 --- a/.agents/rules/ulthon-naming-convention.md +++ b/.agents/rules/ulthon-naming-convention.md @@ -1,5 +1,9 @@ # 命名规范 +> 来源:框架内置(ulthon-) +> 作用域:所有目录命名与 PHP 文件命名场景 +> 触发条件:新增目录、新增 PHP 文件、命名疑问时加载 + ## 目录命名 - 一个单词的目录用小写,例如 `service/`、`controller/` diff --git a/.agents/skills/ulthon-base-app-architecture/SKILL.md b/.agents/skills/ulthon-base-app-architecture/SKILL.md index ea2539b..4a970e7 100644 --- a/.agents/skills/ulthon-base-app-architecture/SKILL.md +++ b/.agents/skills/ulthon-base-app-architecture/SKILL.md @@ -85,8 +85,8 @@ description: "详细说明了 Base/App 双层架构的设计理念、三层结 - **类文件**:以 `*Base.php` 结尾,放在 `extend/base/`。路径和名称与 `app/` 层一一对应。 - **辅助函数**:放在 `extend/base/helper.php`,通过 `app/common.php` 引入。 -- **初始化数据**:放在 `extend/base/adminInitData/`(带 @internal-framework 注解标记)。 -- **版本更新代码**:放在 `extend/base/adminUpdateCodeData/`(带 @internal-framework 注解标记)。 +- **初始化数据**:放在 `extend/base/admin/service/adminInitData/`(带 @internal-framework 注解标记)。 +- **版本更新代码**:放在 `extend/base/admin/service/adminUpdateCodeData/`(带 @internal-framework 注解标记)。 ### 4. 核心层维护原则 diff --git a/.agents/skills/ulthon-tools-http-call/SKILL.md b/.agents/skills/ulthon-tools-http-call/SKILL.md index 5f5f4c9..191cacb 100644 --- a/.agents/skills/ulthon-tools-http-call/SKILL.md +++ b/.agents/skills/ulthon-tools-http-call/SKILL.md @@ -33,6 +33,7 @@ php think tools:http:call --app=admin --controller=system.admin --action=index - - `--body`:原始请求体字符串。 - `--headers`:JSON 字符串的请求头对象(例如 `{ "Accept":"application/json" }`)。 - `--app --controller --action`:框架特性参数,用于拼控制器路径。 +- `--page-data`:标志参数(无需值),等价于追加 `get_page_data=1` 参数 + `Accept:application/json` 头。用于获取页面 assign 的数据(与 [ulthon-page-api-dual-mode](../skills/ulthon-page-api-dual-mode/SKILL.md) 配套使用)。 ## super token(默认模拟登录) diff --git a/.agents/skills/ulthon-update-workflow/SKILL.md b/.agents/skills/ulthon-update-workflow/SKILL.md index 6560ff3..3ca01c6 100644 --- a/.agents/skills/ulthon-update-workflow/SKILL.md +++ b/.agents/skills/ulthon-update-workflow/SKILL.md @@ -127,7 +127,7 @@ php think admin:update --dry-run --optional-conflict=skip --force-conflict=overw |------|--------|------| | `--dry-run` | (无值) | 预览模式,只看变更不执行实际文件操作 | | `--optional-conflict` | `skip` / `overwrite` / `ask` | 可选文件(app/config/route/extend/think/根目录文件)的冲突处理策略 | -| `--force-conflict` | `overwrite` / `skip` / `ask` | 强制文件(extend/base/public/database/think)的冲突处理策略 | +| `--force-conflict` | `overwrite` / `skip` / `ask` | 强制文件(extend/base/public/database)的冲突处理策略 | | `--show` | `all` / `conflict` | 变更输出范围:all 显示全部文件,conflict 只显示冲突文件 | | `--keep-repo` | (无值) | 预览模式下保留上游克隆目录(runtime/update/current/ 和 runtime/update/repo/),便于手动对比跳过的冲突文件 | | `--reinstall` | (无值) | 即使当前版本已是最新,也强制重新安装代码 | @@ -187,6 +187,8 @@ php think admin:update --optional-conflict=skip --force-conflict=skip --show=all 涉及目录:`extend/base/`、`public/`、`database/` +> 注:`extend/think/` 属于可选文件,处理方式见上方「可选文件冲突」章节。 + 这些是框架自动管理的区域。出现冲突说明开发者违反了 Base/App 架构规则,直接修改了 `extend/base/` 下的文件。 处理建议: diff --git a/AGENTS.md b/AGENTS.md index 465864c..e5170b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,7 @@ ### 通用基础规范(所有开发者必须遵守) -- 技术栈:ThinkPHP 8.x;PHP 8+;MySQL 8+;Layui 2.x;模板引擎:ThinkPHP 内置模板引擎 +- 技术栈:ThinkPHP 8.x;PHP 8.0+;MySQL 5.7+;Layui 2.x;模板引擎:ThinkPHP 内置模板引擎 - 命名规范(必读):[ulthon-naming-convention.md](./.agents/rules/ulthon-naming-convention.md) - 代码风格(必读):遵循项目根目录 `.php-cs-fixer.php` - 表结构设计规范(必读):[ulthon-scheme-definition](./.agents/skills/ulthon-scheme-definition/SKILL.md)(含特殊字段约定、字段后缀、组件类型、关联表参数) @@ -119,12 +119,6 @@ Skills 是"按场景调用的工作流说明",统一以 `.agents/skills/*/SKIL > 日志查看走通用命令 `php think tools:log:show`,详见 [快速命令参考](#快速命令参考)。 -## 智能体指导 - -使用命令可以将内置智能体规则应用到各类智能体中。设计规则时以 AGENTS.md 和 `.agents/` 目录为主。 - -php think tools:agent:publish - ## 项目结构速览 ``` @@ -141,8 +135,17 @@ ulthon_admin/ ├── view/ # 视图覆盖层 ├── route/ # 路由定义 ├── database/ # 数据库迁移与种子 +├── tests/ # 测试目录 ├── source/ # 主工程外内容统一目录(多端代码/资料/附件/各类子项目) -│ └── clients/uniapp/ # uni-app 前端工程 +│ ├── assets/ # 静态资源 +│ ├── attachments/ # 附件 +│ ├── clients/ # 客户端工程 +│ │ └── uniapp/ # uni-app 前端工程 +│ ├── docker/ # Docker 相关配置 +│ ├── docs/ # 文档 +│ ├── projects/ # 子项目 +│ ├── stack/ # 部署栈 +│ └── README.md # source 目录说明 └── .agents/ # 智能体协作资源 ├── skills/ # 工作流技能(按场景调用) ├── rules/ # 零散规则(按模块/场景拆分) diff --git a/README.md b/README.md index 944f85f..f8d6517 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,8 @@ php think admin:update ## 项目特性 -* 兼容PHP8.1 - * 最低版本PHP7.4 +* 兼容PHP8.0 + * 最低版本PHP8.0 * 支持移动端表格转卡片 * 支持多款皮肤 * 标准 diff --git a/app/common.php b/app/common.php index 9f5e993..924832d 100644 --- a/app/common.php +++ b/app/common.php @@ -2,5 +2,7 @@ use think\facade\App; +// 项目如需覆盖框架全局函数(extend/base/helper.php),请在本行之前定义同名函数。 +// helper.php 内的函数都有 if (!function_exists()) 包裹,项目侧定义的会优先生效。 // !注意,必须要引入该文件 include App::getRootPath() . '/extend/base/helper.php'; diff --git a/app/common/command/tools/agent/ToolsAgentPublish.php b/app/common/command/tools/agent/ToolsAgentPublish.php deleted file mode 100644 index 424c855..0000000 --- a/app/common/command/tools/agent/ToolsAgentPublish.php +++ /dev/null @@ -1,10 +0,0 @@ -setName('tools:agent:publish') - ->setDescription('发布 AGENTS.md 与 .agent 到兼容目标(复制 rules/skills;可选生成 CLAUDE.md)') - ->addOption('target', 't', Option::VALUE_OPTIONAL, '发布目标:trae|opencode|roocode|cursor|claude|all', 'all') - ->addOption('dry-run', null, Option::VALUE_NONE, '只输出将要写入/覆盖的清单,不落盘') - ->addOption('clean', null, Option::VALUE_NONE, '清理目标中由发布器生成的内容(仅影响 rules/skills 与发布文件)') - ->addOption('force', 'f', Option::VALUE_NONE, '跳过覆盖确认'); - } - - protected function execute(Input $input, Output $output) - { - $rootPath = rtrim(App::getRootPath(), "\\/ \t\n\r\0\x0B"); - - $sourceAgentsPath = $rootPath . DIRECTORY_SEPARATOR . 'AGENTS.md'; - $sourceRulesDir = $rootPath . DIRECTORY_SEPARATOR . '.agent' . DIRECTORY_SEPARATOR . 'rules'; - $sourceSkillsDir = $rootPath . DIRECTORY_SEPARATOR . '.agent' . DIRECTORY_SEPARATOR . 'skills'; - - if (!is_file($sourceAgentsPath)) { - $output->error('缺少单一事实来源文件:' . $sourceAgentsPath); - return; - } - if (!is_dir($sourceRulesDir)) { - $output->error('缺少单一事实来源目录:' . $sourceRulesDir); - return; - } - if (!is_dir($sourceSkillsDir)) { - $output->error('缺少单一事实来源目录:' . $sourceSkillsDir); - return; - } - - $target = strtolower((string)$input->getOption('target')); - if ($target === '') { - $target = 'all'; - } - - $supportedTargets = ['trae', 'opencode', 'roocode', 'cursor', 'claude', 'all']; - if (!in_array($target, $supportedTargets, true)) { - $output->error('不支持的 target:' . $target . '(可选:' . implode('|', $supportedTargets) . ')'); - return; - } - - $dryRun = (bool)$input->getOption('dry-run'); - $clean = (bool)$input->getOption('clean'); - - $targets = $target === 'all' ? ['trae', 'opencode', 'roocode', 'cursor', 'claude'] : [$target]; - - $writeActions = []; - $deleteActions = []; - - $agentsContent = file_get_contents($sourceAgentsPath); - if ($agentsContent === false) { - $output->error('读取失败:' . $sourceAgentsPath); - return; - } - - foreach ($targets as $t) { - if (in_array($t, ['trae', 'opencode', 'roocode', 'cursor'], true)) { - $distRoot = $rootPath . DIRECTORY_SEPARATOR . '.' . $t; - $distRules = $distRoot . DIRECTORY_SEPARATOR . 'rules'; - $distSkills = $distRoot . DIRECTORY_SEPARATOR . 'skills'; - - if ($t === 'cursor') { - $deleteActions[] = $distRules . DIRECTORY_SEPARATOR . 'ulthon-framework.mdc'; - } - - if ($clean) { - $deleteActions[] = $distRules; - $deleteActions[] = $distSkills; - } - - $writeActions = array_merge($writeActions, $this->planCopyDir($sourceRulesDir, $distRules)); - $writeActions = array_merge($writeActions, $this->planCopyDir($sourceSkillsDir, $distSkills)); - } - - if ($t === 'claude') { - $distClaude = $rootPath . DIRECTORY_SEPARATOR . 'CLAUDE.md'; - if ($clean) { - $deleteActions[] = $distClaude; - } - $writeActions = array_merge($writeActions, $this->planWriteFile($distClaude, $agentsContent)); - } - } - - $deleteActions = array_values(array_unique(array_filter($deleteActions, 'strlen'))); - - $plannedDeletes = $this->filterExistingDeletes($deleteActions); - $plannedWrites = $this->filterEffectiveWrites($writeActions, $clean); - - $output->writeln(''); - $output->writeln('=== tools:agent:publish 计划 ==='); - $output->writeln('target:' . $target); - $output->writeln('dry-run:' . ($dryRun ? 'yes' : 'no')); - $output->writeln('clean:' . ($clean ? 'yes' : 'no')); - $output->writeln(''); - - if (empty($plannedDeletes) && empty($plannedWrites)) { - $output->writeln('无变更:目标已是最新状态。'); - $output->writeln(''); - return; - } - - if (!empty($plannedDeletes)) { - $output->writeln('将删除:'); - foreach ($plannedDeletes as $path) { - $output->writeln(' - ' . $path); - } - $output->writeln(''); - } - - if (!empty($plannedWrites)) { - $output->writeln('将写入:'); - foreach ($plannedWrites as $item) { - $output->writeln(' - [' . $item['mode'] . '] ' . $item['dist']); - } - $output->writeln(''); - } - - if ($dryRun) { - return; - } - - if (!$output->confirm($input, '将执行上述写入/覆盖/删除操作,是否继续?', true)) { - $output->comment('已取消。'); - $output->newLine(); - return false; - } - - foreach ($plannedDeletes as $path) { - $this->deletePath($path); - } - - foreach ($plannedWrites as $item) { - $this->ensureDir(dirname($item['dist'])); - file_put_contents($item['dist'], $item['content']); - } - - $output->info('发布完成。'); - $output->newLine(); - } - - protected function planCopyDir(string $sourceDir, string $distDir): array - { - $actions = []; - if (!is_dir($sourceDir)) { - return $actions; - } - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::SELF_FIRST - ); - - $sourceDirNormalized = rtrim($sourceDir, "\\/") . DIRECTORY_SEPARATOR; - foreach ($iterator as $fileInfo) { - if (!$fileInfo->isFile()) { - continue; - } - - $src = $fileInfo->getPathname(); - $relative = substr($src, strlen($sourceDirNormalized)); - $dist = $distDir . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relative); - - $content = file_get_contents($src); - if ($content === false) { - continue; - } - - $actions[] = [ - 'dist' => $dist, - 'content' => $content, - ]; - } - - return $actions; - } - - protected function planWriteFile(string $dist, string $content): array - { - return [[ - 'dist' => $dist, - 'content' => $content, - ]]; - } - - protected function filterEffectiveWrites(array $actions, bool $forceWrite = false): array - { - $result = []; - $resultMap = []; - foreach ($actions as $item) { - $dist = (string)($item['dist'] ?? ''); - $content = (string)($item['content'] ?? ''); - if ($dist === '') { - continue; - } - - $mode = 'create'; - if (is_file($dist)) { - $existing = file_get_contents($dist); - if (!$forceWrite && $existing !== false && $existing === $content) { - continue; - } - $mode = 'update'; - } - - $resultMap[$dist] = [ - 'dist' => $dist, - 'content' => $content, - 'mode' => $mode, - ]; - } - $result = array_values($resultMap); - return $result; - } - - protected function filterExistingDeletes(array $deleteActions): array - { - $result = []; - foreach ($deleteActions as $path) { - if (is_file($path) || is_dir($path)) { - $result[] = $path; - } - } - return $result; - } - - protected function deletePath(string $path): void - { - if (is_file($path)) { - @unlink($path); - return; - } - if (!is_dir($path)) { - return; - } - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($iterator as $fileInfo) { - if ($fileInfo->isDir()) { - @rmdir($fileInfo->getPathname()); - continue; - } - @unlink($fileInfo->getPathname()); - } - - @rmdir($path); - } - - protected function ensureDir(string $dir): void - { - if ($dir === '' || is_dir($dir)) { - return; - } - @mkdir($dir, 0777, true); - } -} - diff --git a/extend/base/helper.php b/extend/base/helper.php index 8aa1d33..a2e6b20 100644 --- a/extend/base/helper.php +++ b/extend/base/helper.php @@ -225,41 +225,45 @@ if (!function_exists('get_session_admin')) { } } -function read_header_token() -{ - $header_authorization = Request::header('Authorization'); - if (!empty($header_authorization)) { - $token = explode(' ', $header_authorization)[1]; +if (!function_exists('read_header_token')) { + function read_header_token() + { + $header_authorization = Request::header('Authorization'); + if (!empty($header_authorization)) { + $token = explode(' ', $header_authorization)[1]; - return $token; + return $token; + } + + return null; } - - return null; } -function json_message($data = [], $code = 0, $msg = '') -{ - if (is_string($data)) { - if (strpos($data, 'http') === 0 || strpos($data, '/') === 0) { +if (!function_exists('json_message')) { + function json_message($data = [], $code = 0, $msg = '') + { + if (is_string($data)) { + if (strpos($data, 'http') === 0 || strpos($data, '/') === 0) { + $data = [ + 'jump_to_url' => $data, + ]; + } else { + $code = $code; + $msg = $data; + $data = []; + } + } elseif ($data instanceof Url) { $data = [ - 'jump_to_url' => $data, + 'jump_to_url' => (string) $data, ]; - } else { - $code = $code; - $msg = $data; - $data = []; } - } elseif ($data instanceof Url) { - $data = [ - 'jump_to_url' => (string) $data, - ]; - } - return json([ - 'code' => $code, - 'msg' => $msg, - 'data' => $data, - ]); + return json([ + 'code' => $code, + 'msg' => $msg, + 'data' => $data, + ]); + } } if (!function_exists('unparse_url')) { @@ -279,200 +283,229 @@ if (!function_exists('unparse_url')) { } } -function build_upload_url($url, $upload_type = null) -{ - if (is_null($upload_type)) { - $upload_type = sysconfig('upload', 'upload_type', 'local_public'); - } +if (!function_exists('build_upload_url')) { + function build_upload_url($url, $upload_type = null) + { + if (is_null($upload_type)) { + $upload_type = sysconfig('upload', 'upload_type', 'local_public'); + } - return Filesystem::disk($upload_type)->url($url); + return Filesystem::disk($upload_type)->url($url); + } } -/** - * 执行扩展事件. - * - * @param string $name 事件名称 - * @param string $key 处理的key - * @param string $type 处理模式 - * @param array $params 传参 - * @return array - */ -function event_handle_result($name, $key, $type = 'all', $params = []) : array -{ - $list_result = Event::trigger($name, $params); +if (!function_exists('event_handle_result')) { + /** + * 执行扩展事件. + * + * @param string $name 事件名称 + * @param string $key 处理的key + * @param string $type 处理模式 + * @param array $params 传参 + * @return array + */ + function event_handle_result($name, $key, $type = 'all', $params = []) : array + { + $list_result = Event::trigger($name, $params); - $result = []; + $result = []; - foreach ($list_result as $key_event => $value_event) { - if (!isset($value_event[$key])) { - if (Env::get('adminsystem.strict_event')) { - throw new EventException("Event view {$name} trigger a result without a {$key}"); + foreach ($list_result as $key_event => $value_event) { + if (!isset($value_event[$key])) { + if (Env::get('adminsystem.strict_event')) { + throw new EventException("Event view {$name} trigger a result without a {$key}"); + } + continue; + } + if ($type == 'all') { + $result[] = $value_event[$key]; + } elseif ($type == 'last') { + $result = []; + $result[] = $value_event[$key]; + } elseif ($type == 'first') { + $result[] = $value_event[$key]; + break; } - continue; - } - if ($type == 'all') { - $result[] = $value_event[$key]; - } elseif ($type == 'last') { - $result = []; - $result[] = $value_event[$key]; - } elseif ($type == 'first') { - $result[] = $value_event[$key]; - break; } + + return $result; } - - return $result; } -/** - * 字符拼接通用处理函数. - * - * @param string $name - * @param string $type - * @return void - */ -function event_handle_string($name, $type) -{ - $list_result = event_handle_result($name, $type); - $content = implode('', $list_result); +if (!function_exists('event_handle_string')) { + /** + * 字符拼接通用处理函数. + * + * @param string $name + * @param string $type + * @return void + */ + function event_handle_string($name, $type) + { + $list_result = event_handle_result($name, $type); + $content = implode('', $list_result); - return $content; -} - -function event_view_content($name) -{ - return event_handle_string($name, 'view_content'); -} - -function event_view_replace($content, $name) -{ - $list_result = event_handle_result($name, 'view_replace'); - - $content_event = implode('', $list_result); - - if (empty($content_event)) { return $content; } - - return $content_event; } -function event_view_replace_js($name) -{ - $list_result = event_handle_result($name, 'view_replace_js'); - - $content_event = implode('', $list_result); - - return ""; -} - -function event_response($name, $params = []) -{ - $list_result = event_handle_result($name, 'response', 'last', $params); - - if (empty($list_result)) { - return; +if (!function_exists('event_view_content')) { + function event_view_content($name) + { + return event_handle_string($name, 'view_content'); } - - $response = $list_result[0]; - - if (is_string($response)) { - $response = View::create($response); - } - - throw new HttpResponseException($response); } -/** - * 以扩展的架构定位app下的文件位置. - * - * @param string $file_path 文件路径,不要以/开头,不需要以app开头,会自动定位 app 或 extend/base。 - * @return string - */ -function app_file_path($file_path) -{ - $app_file_path = App::getRootPath() . 'app' . DIRECTORY_SEPARATOR . $file_path; - if (!is_file($app_file_path)) { - $app_file_path = App::getRootPath() . 'extend' . DIRECTORY_SEPARATOR . 'base' . DIRECTORY_SEPARATOR . $file_path; - } +if (!function_exists('event_view_replace')) { + function event_view_replace($content, $name) + { + $list_result = event_handle_result($name, 'view_replace'); - return $app_file_path; -} + $content_event = implode('', $list_result); -function array_to_table($array_tree, $prefix_key = '') -{ - $table = []; - - foreach ($array_tree as $key => $value) { - if (is_array($value)) { - $table = array_merge($table, array_to_table($value, $key . '.')); - } else { - $table[] = [ - 'key' => $prefix_key . $key, - 'value' => $value, - ]; + if (empty($content_event)) { + return $content; } + + return $content_event; } - - return $table; } -function format_bytes($size, $delimiter = '') -{ - if (is_null($size)) { - return '0B'; +if (!function_exists('event_view_replace_js')) { + function event_view_replace_js($name) + { + $list_result = event_handle_result($name, 'view_replace_js'); + + $content_event = implode('', $list_result); + + return ""; } - $units = ['B', 'K', 'M', 'G', 'T', 'P']; - for ($i = 0; $size >= 1024 && $i < 5; $i++) { - $size /= 1024; +} + +if (!function_exists('event_response')) { + function event_response($name, $params = []) + { + $list_result = event_handle_result($name, 'response', 'last', $params); + + if (empty($list_result)) { + return; + } + + $response = $list_result[0]; + + if (is_string($response)) { + $response = View::create($response); + } + + throw new HttpResponseException($response); } - - return round($size, 2) . $delimiter . $units[$i]; } -function parse_bytes($size) -{ - $unit = strtolower(substr($size, -1)); - $size = (int) $size; - switch ($unit) { - case 'b': - break; - case 'k': - $size *= 1024; - break; - case 'm': - $size *= 1024 * 1024; - break; - case 'g': - $size *= 1024 * 1024 * 1024; - break; - case 't': - $size *= 1024 * 1024 * 1024 * 1024; - break; - case 'p': - $size *= 1024 * 1024 * 1024 * 1024 * 1024; - break; - default: - return $size; + +if (!function_exists('app_file_path')) { + /** + * 以扩展的架构定位app下的文件位置. + * + * @param string $file_path 文件路径,不要以/开头,不需要以app开头,会自动定位 app 或 extend/base。 + * @return string + */ + function app_file_path($file_path) + { + $app_file_path = App::getRootPath() . 'app' . DIRECTORY_SEPARATOR . $file_path; + if (!is_file($app_file_path)) { + $app_file_path = App::getRootPath() . 'extend' . DIRECTORY_SEPARATOR . 'base' . DIRECTORY_SEPARATOR . $file_path; + } + + return $app_file_path; } - - return $size; } -function get_store_value($key, $default = null) -{ - return StoreValueTools::get($key, $default); +if (!function_exists('array_to_table')) { + function array_to_table($array_tree, $prefix_key = '') + { + $table = []; + + foreach ($array_tree as $key => $value) { + if (is_array($value)) { + $table = array_merge($table, array_to_table($value, $key . '.')); + } else { + $table[] = [ + 'key' => $prefix_key . $key, + 'value' => $value, + ]; + } + } + + return $table; + } } -function set_store_value($key, $value) -{ - return StoreValueTools::set($key, $value); +if (!function_exists('format_bytes')) { + function format_bytes($size, $delimiter = '') + { + if (is_null($size)) { + return '0B'; + } + $units = ['B', 'K', 'M', 'G', 'T', 'P']; + for ($i = 0; $size >= 1024 && $i < 5; $i++) { + $size /= 1024; + } + + return round($size, 2) . $delimiter . $units[$i]; + } } + +if (!function_exists('parse_bytes')) { + function parse_bytes($size) + { + $unit = strtolower(substr($size, -1)); + $size = (int) $size; + switch ($unit) { + case 'b': + break; + case 'k': + $size *= 1024; + break; + case 'm': + $size *= 1024 * 1024; + break; + case 'g': + $size *= 1024 * 1024 * 1024; + break; + case 't': + $size *= 1024 * 1024 * 1024 * 1024; + break; + case 'p': + $size *= 1024 * 1024 * 1024 * 1024 * 1024; + break; + default: + return $size; + } + + return $size; + } +} + +if (!function_exists('get_store_value')) { + function get_store_value($key, $default = null) + { + return StoreValueTools::get($key, $default); + } +} + +if (!function_exists('set_store_value')) { + function set_store_value($key, $value) + { + return StoreValueTools::set($key, $value); + } +} + if (!function_exists('get_site_version_key')) { function get_site_version_key() { return sysconfig('site', 'site_version') . '-' . Version::VERSION . '-' . Version::PRODUCT_VERSION . '-' . Version::LAYUI_VERSION; } } + if (!function_exists('format_null_value')) { function format_null_value($value) { @@ -483,6 +516,7 @@ if (!function_exists('format_null_value')) { return $value; } } + if (!function_exists('ctype_numeric')) { function ctype_numeric($input) { diff --git a/extend/think/UlthonAdminService.php b/extend/think/UlthonAdminService.php index 4038160..c365825 100644 --- a/extend/think/UlthonAdminService.php +++ b/extend/think/UlthonAdminService.php @@ -41,7 +41,6 @@ use app\common\command\tools\db\ToolsDbExecute; use app\common\command\tools\db\ToolsDbInfo; use app\common\command\tools\db\ToolsDbQuery; use app\common\command\tools\db\ToolsDbTable; -use app\common\command\tools\agent\ToolsAgentPublish; use app\common\command\tools\log\ToolsLogSearch; use app\common\command\tools\log\ToolsLogShow; use app\common\command\tools\log\ToolsLogStats; @@ -126,7 +125,6 @@ class UlthonAdminService extends Service Backup::class, ToolsHttpCall::class, MigrateFileData::class, - ToolsAgentPublish::class, ToolsDbQuery::class, ToolsDbExecute::class, ToolsDbTable::class,