Files
ywxapp 1d49e6f5ee feat: 公共表更名 common/member 前缀 + 插件命令自动加载 + 版本 1.1.1
- 18 张公共表更名(addon/attachment/configure/links/spider_log/spider_stat/sms/notice/ad/task/prop/medal/help/card -> common_*,member_wallets->member_wallet,score_rule/score_log -> member_*,addon_config->common_addonconf),模型全部对齐新表名,Db 直引用清零
- Attachment 模型补  表名绑定,修复富文本上传查 wxapp_attachment 1146 隐患
- install.sql + 迁移 SQL:backend_admin/backend_role delete_at 默认 0,修复软删除(NULL != 0)误过滤导致后台菜单为空
- AppService::boot() 支持插件 info.php 声明 commands 自动注册插件命令(psr-4 自动加载,坏类名自动跳过)
- 各插件(haonav/mqttbroker/wxchat/articles/blog/forum 等)字段与配置同步调整
- 框架版本 1.1.0 -> 1.1.1
2026-08-20 20:19:15 +08:00

289 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace ywxapp\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\command\Make;
/**
* AppCommand 应用命令统一入口
*
* 将 ywxapp/command/app/ 目录下的全部命令(app 脚手架 / 生成器)逻辑内联到本类,
* 绑定为单一入口 `php think app <子命令> [参数]`,不再保留独立的命令类文件。
* 插件等其它命令(addon:* 系列)由各自独立注册,不在此处理。
*
* 子命令:
* build 构建应用目录(原 build:app)
* clear 清理运行时文件(原 clear)
* controller 生成控制器(原 app:controller,支持 --api/--plain)
* model 生成模型(原 app:model)
* event 生成事件(原 app:event)
* listener 生成监听器(原 make:listener)
* middleware 生成中间件(原 make:middleware)
* service 生成服务(原 make:service)
* subscribe 生成订阅器(原 make:subscribe)
* validate 生成验证器(原 make:validate)
*
* 用法:
* php think app # 列出所有可用子命令
* php think app controller app\index # 生成控制器
* php think app controller app\index --api # 生成 api 风格控制器
* php think app model app\demo # 生成模型
* php think app clear --cache # 清理缓存
* php think app build myapp # 构建应用目录
*/
class AppCommand extends Make
{
/**
* 应用基础目录(Build 使用)
* @var string
*/
private $basePath = '';
private $subCommon = '';
protected function getStub(): string
{
if ($this->input->getOption('api')) {
return __DIR__ . DIRECTORY_SEPARATOR . 'appstubs' . DIRECTORY_SEPARATOR . $this->subCommon . '.api.stub';
}
if ($this->input->getOption('plain')) {
return __DIR__ . DIRECTORY_SEPARATOR . 'appstubs' . DIRECTORY_SEPARATOR . $this->subCommon . '.plain.stub';
}
return __DIR__ . DIRECTORY_SEPARATOR . 'appstubs' . DIRECTORY_SEPARATOR . $this->subCommon . '.stub';
}
protected function getNamespace(string $app): string
{
if ($this->subCommon === 'model') {
return 'ywxapp\\model';
}
return parent::getNamespace($app) . '\\' . $this->subCommon;
}
protected function getClassName(string $name): string
{
if ($this->subCommon == 'controller') {
return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
}
return parent::getClassName($name);
}
protected function getPathName(string $name): string
{
if ($this->subCommon == 'model') {
$name = substr($name, 6);
return $this->app->getRootPath() . 'ywxapp' . '/' . ltrim(str_replace('\\', '/', $name), '/') . '.php';
}
$name = substr($name, 4);
return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
}
/**
* 配置命令
*/
protected function configure()
{
$this->setName('app')
->addOption('list', 'l', Option::VALUE_NONE, '列出所有可用子命令')
->addOption('api', null, Option::VALUE_NONE, '生成 api 风格控制器(仅 controller)')
->addOption('plain', null, Option::VALUE_NONE, '生成空控制器(仅 controller)')
->addOption('cache', 'c', Option::VALUE_NONE, '清理缓存目录(仅 clear)')
->addOption('log', null, Option::VALUE_NONE, '清理日志目录(仅 clear)')
->addOption('dir', 'r', Option::VALUE_NONE, '清理空目录(仅 clear)')
->addArgument('subcommand', Argument::OPTIONAL, '子命令名(build/clear/controller/model/event/listener/middleware/service/subscribe/validate)')
->addArgument('name', Argument::OPTIONAL, '类名或 app 名(如 app\\index 或 myapp)')
->setDescription('YwxApp 应用命令入口:聚合 ywxapp/command/app 下的命令,php think app <子命令> [参数]');
}
/**
* 执行命令
*/
protected function execute(Input $input, Output $output)
{
$this->subCommon = $input->getArgument('subcommand');
$name = trim($input->getArgument('name'));
if ($this->subCommon === 'build') {
return $this->runBuild($name, $output);
}
if ($this->subCommon === 'clear') {
return $this->runClear($name, $input, $output);
}
$classname = $this->getClassName($name);
$pathname = $this->getPathName($classname);
if (is_file($pathname)) {
$output->writeln('<error>' . $this->type . ':' . $classname . ' already exists!</error>');
return false;
}
if (!is_dir(dirname($pathname))) {
mkdir(dirname($pathname), 0755, true);
}
file_put_contents($pathname, $this->buildClass($classname));
$output->writeln('<info>' . $this->type . ':' . $classname . ' created successfully.</info>');
}
protected function buildClass(string $name)
{
$stub = file_get_contents($this->getStub());
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
$class = str_replace($namespace . '\\', '', $name);
return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
$class,
$this->app->config->get('route.action_suffix'),
$namespace,
$this->app->getNamespace(),
], $stub);
}
/* ===================== Build 命令 ===================== */
/**
* 运行 build 子命令(原 build:app)
*/
private function runBuild(string $app, Output $output): int
{
$this->basePath = $this->app->getRootPath() . 'app' . DIRECTORY_SEPARATOR;
$app = $app ?: '';
if (is_file($this->basePath . 'build.php')) {
$list = include $this->basePath . 'build.php';
} else {
$list = [
'__file__' => ['common.php', 'event.php', 'middleware.php', 'service.php'],
'__dir__' => ['controller', 'model', 'view', 'route'],
'controller' => ['Index'],
'model' => [],
'view' => ['index/index'],
'route' => ['app'],
];
}
$this->buildApp($app, $list);
$output->writeln('<info>Successed</info>');
return 0;
}
/**
* 创建应用
*/
private function buildApp(string $app, array $list = []): void
{
if (!is_dir($this->basePath . $app)) {
mkdir($this->basePath . $app);
}
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
$namespace = 'app' . ($app ? '\\' . $app : '');
foreach ($list as $path => $file) {
if ('__dir__' == $path) {
foreach ($file as $dir) {
$this->checkDirBuild($appPath . $dir);
}
} elseif ('__file__' == $path) {
foreach ($file as $fname) {
if (!is_file($appPath . $fname)) {
file_put_contents($appPath . $fname, 'php' == pathinfo($fname, PATHINFO_EXTENSION) ? '<?php' . PHP_EOL : '');
}
}
} else {
foreach ($file as $val) {
$val = trim($val);
$filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . '.php';
$space = $namespace . '\\' . $path;
$class = $val;
switch ($path) {
case 'controller':
if ($this->app->config->get('route.controller_suffix')) {
$filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php';
$class = $val . 'Controller';
}
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'class ' . $class . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
break;
case 'route':
$content = "<?php" . PHP_EOL . PHP_EOL . "use think\\facade\Route;";
break;
case 'model':
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'use think\Model;' . PHP_EOL . PHP_EOL . 'class ' . $class . ' extends Model' . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
break;
case 'view':
$filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . '.html';
$this->checkDirBuild(dirname($filename));
$content = '';
break;
default:
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'class ' . $class . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
}
if (!is_file($filename)) {
file_put_contents($filename, $content);
}
}
}
}
}
/**
* 创建目录
*/
private function checkDirBuild(string $dirname): void
{
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
}
/* ===================== Clear 命令 ===================== */
/**
* 运行 clear 子命令(原 clear)
*/
private function runClear(string $app, Input $input, Output $output): int
{
$runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
if ($input->getOption('cache')) {
$path = $runtimePath . 'cache';
} elseif ($input->getOption('log')) {
$path = $runtimePath . 'log';
} else {
$path = $runtimePath;
}
$rmdir = $input->getOption('dir') ? true : false;
$this->clearRuntime(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
$output->writeln('<info>Clear Successed</info>');
return 0;
}
/**
* 递归清理目录
*/
private function clearRuntime(string $path, bool $rmdir): void
{
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if ('.' != $file && '..' != $file && is_dir($path . $file)) {
array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*'));
if ($rmdir) {
rmdir($path . $file);
}
} elseif ('.gitignore' != $file && is_file($path . $file)) {
unlink($path . $file);
}
}
}
}