chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
namespace ywxapp\command;
use Exception;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\File;
use ywxapp\exception\AddonException;
use ywxapp\service\AddonService;
use ywxapp\service\AddonHotReload;
/**
* 插件统一管理命令(合并 develop / install / reload
*
* 用法:
* php think addon:manage <插件名> -a develop
* php think addon:manage <插件名> -a install [--local=<zip>] [--force]
* php think addon:manage [<插件名>] -a reload [--force] [--status]
*
* 说明:
* - develop / install / reload 逻辑内联于此;
* - 菜单重载 / 数据库自愈 / 配置补全已由 addon:repair 统一负责(含 --menu 单项),
* 故本命令不再保留 refresh-menu
* - health 逻辑体量大且复用 AddonHealth,故委托 ywxapp\command\AddonHealth 执行(长选项形式传参,规避短选项冲突)。
*/
class AddonManage extends Command
{
/**
* 配置命令
*/
protected function configure()
{
$this->setName('addon:manage')
->addArgument('addon', Argument::OPTIONAL, '插件名(部分动作需要;留空语义见各动作)')
->addOption('action', 'a', Option::VALUE_REQUIRED, '动作: develop|install|reload')
// install
->addOption('local', 'l', Option::VALUE_REQUIRED, '离线 zip 路径(install 离线安装)')
->addOption('force', 'f', Option::VALUE_NONE, '强制(install 在线强制覆盖 / reload 强制重载)')
// reload
->addOption('status', 's', Option::VALUE_NONE, '查看重载状态(reload')
->setDescription('插件统一管理:开发安装 / 安装 / 重导菜单 / 热重载');
}
/**
* 执行命令:按 -a 分发
*/
protected function execute(Input $input, Output $output): int
{
$action = strtolower(trim((string) $input->getOption('action')));
if ($action === '') {
$output->writeln('<error>请通过 -a/--action 指定动作:develop|install|reload</error>');
return 1;
}
switch ($action) {
case 'develop':
return $this->doDevelop($input, $output);
case 'install':
return $this->doInstall($input, $output);
case 'reload':
return $this->doReload($input, $output);
default:
$output->writeln('<error>未知动作: ' . $action . '(支持 develop|install|reload;菜单重载请用 addon:repair --menu</error>');
return 1;
}
}
/**
* 开发者模式安装(源码已在 addon/<name>,原地建表/注入菜单/启用,免打包)
*/
private function doDevelop(Input $input, Output $output): int
{
$name = trim((string) $input->getArgument('addon'));
if ($name === '') {
$output->writeln('<error>请指定插件名,例如:php think addon:manage demo -a develop</error>');
return 1;
}
try {
$info = AddonService::instance($name)->developInstall();
$version = $info['version'] ?? '';
$output->writeln("<info>开发模式安装成功:{$name} v{$version}</info>");
$output->writeln('<info>已建表、注入菜单并启用,可直接测试。</info>');
} catch (AddonException $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
return 1;
} catch (\Exception $e) {
$output->writeln('<error>开发模式安装失败:' . $e->getMessage() . '</error>');
return 1;
}
return 0;
}
/**
* 安装插件:在线下载安装(默认)或离线 zip 安装
*/
private function doInstall(Input $input, Output $output): int
{
$name = trim((string) $input->getArgument('addon'));
if ($name === '' || !preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $name)) {
$output->writeln('<error>请提供合法的插件名,例如:php think addon:manage demo -a install</error>');
return 1;
}
$extend = [
'install_user' => 0,
'install_ip' => 'cli',
'install_time' => time(),
];
try {
$localOpt = (string) $input->getOption('local');
if ($localOpt !== '') {
$local = realpath($localOpt);
if ($local === false || !is_file($local)) {
$output->writeln("<error>Local zip not found: {$localOpt}</error>");
return 1;
}
$output->writeln("<comment>Offline install from '{$local}' (addon='{$name}')...</comment>");
$file = new File($local);
AddonService::instance($name)->local($file, $extend);
$output->writeln("<info>Addon '{$name}' offline-installed successfully.</info>");
return 0;
}
$force = (bool) $input->getOption('force');
$output->writeln("<comment>Online install '{$name}' (force=" . ($force ? 'yes' : 'no') . ")...</comment>");
AddonService::instance($name)->install($force, $extend);
$output->writeln("<info>Addon '{$name}' online-installed successfully.</info>");
return 0;
} catch (AddonException $e) {
$output->writeln('<error>Install failed: ' . $e->getMessage() . '</error>');
return 1;
} catch (Exception $e) {
$output->writeln('<error>Install failed: ' . $e->getMessage() . '</error>');
return 1;
}
}
/**
* 插件热重载(需 app_debug
*/
private function doReload(Input $input, Output $output): int
{
if (!config('app.app_debug')) {
$output->error('热重载功能仅在开发环境可用');
return 1;
}
$addon = trim((string) $input->getArgument('addon'));
$force = (bool) $input->getOption('force');
$showStatus = (bool) $input->getOption('status');
if ($showStatus) {
return $this->reloadStatus($addon, $output);
}
if ($addon) {
return $this->reloadSingle($addon, $force, $output);
}
return $this->reloadAll($output);
}
private function reloadSingle(string $addon, bool $force, Output $output): int
{
$output->writeln("<info>正在重载插件: {$addon}</info>");
try {
$reloaded = AddonHotReload::reloadAddon($addon, $force);
if ($reloaded) {
$output->writeln("<info>插件 {$addon} 重载成功</info>");
$status = AddonHotReload::getReloadStatus($addon);
if (isset($status['files_changed'])) {
$output->writeln('变更文件: ' . implode(', ', $status['files_changed']));
}
} else {
$output->writeln("<comment>插件 {$addon} 无文件变更,无需重载</comment>");
}
return 0;
} catch (\Exception $e) {
$output->error('重载失败: ' . $e->getMessage());
return 1;
}
}
private function reloadAll(Output $output): int
{
$output->writeln('<info>正在重载所有已启用插件...</info>');
try {
$results = AddonHotReload::reloadAlladdon();
$successCount = 0;
$reloadedCount = 0;
$failedCount = 0;
foreach ($results as $addon => $result) {
if ($result['success']) {
$successCount++;
if ($result['reloaded']) {
$reloadedCount++;
$output->writeln("<info>✓ {$addon} - 已重载</info>");
} else {
$output->writeln("<comment>- {$addon} - 无变更</comment>");
}
} else {
$failedCount++;
$output->writeln("<error>✗ {$addon} - {$result['error']}</error>");
}
}
$output->writeln('');
$output->writeln('<info>重载完成:</info>');
$output->writeln(" 成功: {$successCount}");
$output->writeln(" 实际重载: {$reloadedCount}");
$output->writeln(" 失败: {$failedCount}");
return $failedCount > 0 ? 1 : 0;
} catch (\Exception $e) {
$output->error('重载失败: ' . $e->getMessage());
return 1;
}
}
private function reloadStatus(string $addon, Output $output): int
{
if ($addon) {
$status = AddonHotReload::getReloadStatus($addon);
$output->writeln("<info>插件 {$addon} 重载状态:</info>");
$output->writeln(' 状态: ' . ($status['status'] ?? 'unknown'));
if (isset($status['reload_time'])) {
$output->writeln(' 最后重载时间: ' . date('Y-m-d H:i:s', $status['reload_time']));
}
if (isset($status['error'])) {
$output->writeln(' 错误信息: ' . $status['error']);
}
if (isset($status['files_changed'])) {
$output->writeln(' 变更文件: ' . implode(', ', $status['files_changed']));
}
} else {
$stats = AddonHotReload::getReloadStatistics();
$output->writeln('<info>插件热重载统计:</info>');
$output->writeln(' 总重载次数: ' . ($stats['total_reloads'] ?? 0));
$output->writeln(' 成功次数: ' . ($stats['successful_reloads'] ?? 0));
$output->writeln(' 失败次数: ' . ($stats['failed_reloads'] ?? 0));
if (!empty($stats['addon'])) {
$output->writeln('');
$output->writeln('<info>各插件重载状态:</info>');
foreach ($stats['addon'] as $name => $status) {
$icon = ($status['status'] ?? '') === 'success' ? '✓' : '✗';
$color = ($status['status'] ?? '') === 'success' ? 'info' : 'error';
$output->writeln("<{$color}>{$icon} {$name}</{$color}>");
if (isset($status['reload_time'])) {
$output->writeln(' 最后重载: ' . date('Y-m-d H:i:s', $status['reload_time']));
}
}
}
}
return 0;
}
}