refactor(agents): 删除 tools:agent:publish 命令,补全 helper.php 函数覆盖机制,修正多处文档 bug

- 删除 tools:agent:publish 整套(不再兼容多套 IDE,只保留 .agents 目录)
- helper.php 全部 27 个全局函数加 if (!function_exists()) 包裹,项目侧可在 app/common.php 定义同名函数覆盖
- ulthon-file-override-mechanism.md 新增全局函数覆盖章节
- AGENTS.md 删除智能体指导章节,PHP 8+ 改为 8.0+,MySQL 8+ 改为 5.7+
- AGENTS.md 项目结构树补全 tests/、extend/think/、source/ 子项
- README.md PHP 版本统一为 8.0+
- ulthon-base-app-architecture SKILL 路径补 admin/service/ 段
- ulthon-tools-http-call SKILL 补 --page-data 参数
- ulthon-naming-convention 补元数据头
- ulthon-update-workflow 统一 extend/think/ 归类
This commit is contained in:
augushong
2026-07-21 19:35:28 +08:00
parent 4a4155070d
commit 600ee5b085
12 changed files with 261 additions and 484 deletions

View File

@@ -81,6 +81,26 @@ Ulthon Admin 在"类继承覆盖"之外,还提供"文件级覆盖"能力:业
- 覆盖某控制器视图:在 `app/admin/view/<controller>/<action>.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()` 定义)

View File

@@ -1,5 +1,9 @@
# 命名规范
> 来源框架内置ulthon-
> 作用域:所有目录命名与 PHP 文件命名场景
> 触发条件:新增目录、新增 PHP 文件、命名疑问时加载
## 目录命名
- 一个单词的目录用小写,例如 `service/``controller/`

View File

@@ -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. 核心层维护原则

View File

@@ -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默认模拟登录

View File

@@ -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/` 下的文件。
处理建议:

View File

@@ -29,7 +29,7 @@
### 通用基础规范(所有开发者必须遵守)
- 技术栈ThinkPHP 8.xPHP 8+MySQL 8+Layui 2.x模板引擎ThinkPHP 内置模板引擎
- 技术栈ThinkPHP 8.xPHP 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/ # 零散规则(按模块/场景拆分)

View File

@@ -113,8 +113,8 @@ php think admin:update
## 项目特性
* 兼容PHP8.1
* 最低版本PHP7.4
* 兼容PHP8.0
* 最低版本PHP8.0
* 支持移动端表格转卡片
* 支持多款皮肤
* 标准

View File

@@ -2,5 +2,7 @@
use think\facade\App;
// 项目如需覆盖框架全局函数extend/base/helper.php请在本行之前定义同名函数。
// helper.php 内的函数都有 if (!function_exists()) 包裹,项目侧定义的会优先生效。
// !注意,必须要引入该文件
include App::getRootPath() . '/extend/base/helper.php';

View File

@@ -1,10 +0,0 @@
<?php
namespace app\common\command\tools\agent;
use base\common\command\tools\agent\ToolsAgentPublishBase;
class ToolsAgentPublish extends ToolsAgentPublishBase
{
}

View File

@@ -1,277 +0,0 @@
<?php
namespace base\common\command\tools\agent;
use app\common\console\Command;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\App;
class ToolsAgentPublishBase extends Command
{
protected function configure()
{
parent::configure();
$this->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('<info>=== tools:agent:publish 计划 ===</info>');
$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('<comment>无变更:目标已是最新状态。</comment>');
$output->writeln('');
return;
}
if (!empty($plannedDeletes)) {
$output->writeln('<comment>将删除:</comment>');
foreach ($plannedDeletes as $path) {
$output->writeln(' - ' . $path);
}
$output->writeln('');
}
if (!empty($plannedWrites)) {
$output->writeln('<comment>将写入:</comment>');
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);
}
}

View File

@@ -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 "<script id='event-replace-js-{$name}' type='text/plain'>{$content_event}</script>";
}
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 "<script id='event-replace-js-{$name}' type='text/plain'>{$content_event}</script>";
}
$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)
{

View File

@@ -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,