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
+435
View File
@@ -0,0 +1,435 @@
<?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 think\console\command\Make;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
/**
* Addon 统一脚手架 / 生成器命令
*
* 合并原 addon:build / addon:clear / addon:controller / addon:model /
* addon:middleware / addon:validate / addon:event / addon:listener /
* addon:service / addon:subscribe 十条命令为单入口:
* php think addon:make <插件名> -a <action> [--name=<类>] [动作选项]
*
* @author ywxapp <admin@ywxapp.cn>
*/
class AddonMake extends Make
{
/**
* 当前生成动作对应的类类型(首字母大写),供 getStub/buildClass 使用
* @var string
*/
protected $currentType = '';
/**
* 当前生成动作对应的 stub 关键字
* @var string
*/
protected $currentStub = '';
/**
* 应用基础目录
* @var string
*/
protected $basePath;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('addon:make')
->addArgument('addon', Argument::OPTIONAL, 'addon name, e.g. demo (required for build & generators)')
->addOption('action', 'a', Option::VALUE_REQUIRED, 'build|clear|controller|model|middleware|validate|event|listener|service|subscribe')
->addOption('name', 'n', Option::VALUE_OPTIONAL, 'class name for generator actions, e.g. User')
->addOption('api', null, Option::VALUE_NONE, 'controller: generate an api controller class.')
->addOption('plain', null, Option::VALUE_NONE, 'controller: generate an empty controller class.')
->addOption('cache', 'c', Option::VALUE_NONE, 'clear: clear cache file')
->addOption('log', 'l', Option::VALUE_NONE, 'clear: clear log file')
->addOption('dir', 'r', Option::VALUE_NONE, 'clear: clear empty dir')
->setDescription('Unified addon scaffold & class generator (build / clear / controller / model / middleware / validate / event / listener / service / subscribe)');
}
protected function execute(Input $input, Output $output): int
{
$addon = trim((string) $input->getArgument('addon'));
$action = strtolower(trim((string) $input->getOption('action')));
if ($action === '') {
$output->writeln('<error>Please specify an action via -a/--action, e.g. -a build, -a controller.</error>');
return 1;
}
// clear 允许插件名为空(清理根 runtime)
if ($action === 'clear') {
return $this->doClear($addon, $input, $output);
}
// build 与生成器均要求合法的插件名
if ($addon === '' || ! preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $addon)) {
$output->writeln('<error>Please provide a valid addon name, e.g. php think addon demo -a ' . $action . '</error>');
return 1;
}
switch ($action) {
case 'build':
return $this->doBuild($addon, $output);
case 'controller':
case 'model':
case 'middleware':
case 'validate':
case 'event':
case 'listener':
case 'service':
case 'subscribe':
return $this->doGenerate($action, $addon, $input, $output);
default:
$output->writeln('<error>Unknown action: ' . $action . '</error>');
return 1;
}
}
/* ----------------------------- 生成器动作 ----------------------------- */
/**
* 生成各类插件类文件(controller/model/middleware/validate/event/listener/service/subscribe
*
* @return int
*/
protected function doGenerate(string $action, string $addon, Input $input, Output $output): int
{
$this->currentType = ucfirst($action);
$this->currentStub = $action;
$class = trim((string) ($input->getOption('name') ?: ''));
if ($class === '' || ! preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $class)) {
$output->writeln('<error>Please provide a valid class name via --name, e.g. --name=User</error>');
return 1;
}
$name = 'addon' . '\\' . $addon . '\\' . $action . '\\' . $class;
$pathname = $this->app->getRootPath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
if (is_file($pathname)) {
$output->writeln('<error>' . $this->currentType . ':' . $name . ' already exists!</error>');
return 1;
}
if (! is_dir(dirname($pathname))) {
mkdir(dirname($pathname), 0755, true);
}
file_put_contents($pathname, $this->buildClass($name));
$output->writeln('<info>' . $this->currentType . ':' . $name . ' created successfully.</info>');
return 0;
}
/**
* 根据 stub 构建类内容(覆盖 Make::buildClass 以使用插件命名空间)
*
* @param string $name 完整类名 addon\<addon>\<type>\<Class>
* @return string
*/
protected function buildClass(string $name): string
{
$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
);
}
/**
* 根据当前动作与 --api/--plain 选项返回 stub 路径
*
* @return string
*/
protected function getStub(): string
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR;
if ($this->currentStub === 'controller') {
if ($this->input->getOption('api')) {
return $stubPath . 'controller.api.stub';
}
if ($this->input->getOption('plain')) {
return $stubPath . 'controller.plain.stub';
}
return $stubPath . 'controller.stub';
}
return $stubPath . $this->currentStub . '.stub';
}
/* ------------------------------- build -------------------------------- */
/**
* 脚手架生成插件骨架
*
* @return int
*/
protected function doBuild(string $app, Output $output): int
{
$this->basePath = $this->app->getRootPath() . 'addon' . DIRECTORY_SEPARATOR;
if (is_dir($this->basePath . $app)) {
$output->writeln("<comment>Addon '{$app}' already exists, ensuring skeleton only.</comment>");
}
$this->buildAddon($app, []);
$output->writeln("<info>Addon '{$app}' scaffold ready at addon/{$app}/</info>");
return 0;
}
/**
* 创建应用
*
* @param string $app 应用名
* @param array $list 目录结构
* @return void
*/
protected function buildAddon(string $app, array $list = []): void
{
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
$namespace = 'addon' . ($app ? '\\' . $app : '');
if (! is_dir($appPath)) {
mkdir($appPath);
}
$this->buildAddonFile($app, $namespace);
$this->buildInfoFile($app);
$this->buildCommonFile($app);
$this->buildConfigFile($app);
if (! is_dir($appPath . 'controller')) {
mkdir($appPath . 'controller');
mkdir($appPath . 'controller/backend');
mkdir($appPath . 'controller/member');
}
$this->buildIndexController($app, $namespace);
if (! is_dir($appPath . 'lang')) {
mkdir($appPath . 'lang');
}
if (! is_dir($appPath . 'model')) {
mkdir($appPath . 'model');
}
if (! is_dir($appPath . 'view')) {
mkdir($appPath . 'view');
mkdir($appPath . 'view/frontend');
mkdir($appPath . 'view/backend');
mkdir($appPath . 'view/member');
}
if (! is_dir($appPath . 'route')) {
mkdir($appPath . 'route');
}
$this->buildRouteFile($app, $namespace);
$this->buildMenuJson($app);
$appPublicPath = public_path() . 'static' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
if (! is_dir($appPublicPath)) {
mkdir($appPublicPath, 0755, true);
mkdir($appPublicPath . 'img', 0755, true);
mkdir($appPublicPath . 'css', 0755, true);
mkdir($appPublicPath . 'js', 0755, true);
}
}
/**
* 创建插件主类文件
*
* @param string $app 应用名
* @param string $namespace 类库命名空间
* @return void
*/
protected function buildAddonFile(string $app, string $namespace): void
{
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
if (! is_file($appPath . 'Addon.php')) {
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'addon.stub');
$content = str_replace(['{%namespace%}', '{%addon%}', '{%className%}'], [$namespace, $app, 'Addon'], $content);
file_put_contents($appPath . 'Addon.php', $content);
}
}
/**
* 创建插件信息文件
*
* @param string $app 目录
* @return void
*/
protected function buildInfoFile(string $app): void
{
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'info.php';
if (! is_file($filename)) {
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'info.stub');
$content = str_replace(['{%app%}'], [$app], $content);
$this->checkDirBuild(dirname($filename));
file_put_contents($filename, $content);
}
}
/**
* 创建菜单 JSON
*
* @param string $app 应用名
* @return void
*/
protected function buildMenuJson(string $app): void
{
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'menu.json';
if (! is_file($filename)) {
$menu = ["frontend" => [], "member" => [], "backend" => []];
$content = json_encode($menu, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
file_put_contents($filename, $content);
}
}
/**
* 创建公共文件
*
* @param string $app 目录
* @return void
*/
public function buildCommonFile(string $app): void
{
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
if (! is_file($appPath . 'common.php')) {
file_put_contents($appPath . 'common.php', "<?php" . PHP_EOL . "// 这是系统自动生成的公共文件" . PHP_EOL);
}
}
/**
* 创建插件配置文件(后台「配置」表单数据源)
*
* @param string $app 应用名
* @return void
*/
protected function buildConfigFile(string $app): void
{
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'config.php';
if (! is_file($filename)) {
file_put_contents($filename, "<?php" . PHP_EOL
. "// 插件配置项(后台「配置」表单数据源)" . PHP_EOL
. "return [];" . PHP_EOL);
}
}
/**
* 创建首页控制器
*
* @param string $app 应用名
* @param string $namespace 类库命名空间
* @return void
*/
public function buildIndexController(string $app, string $namespace): void
{
$suffix = $this->app->config->get('route.controller_suffix') ? 'Controller' : '';
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php';
if (! is_file($filename)) {
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'controller.stub');
$content = str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], ['Index', $suffix, $namespace . '\\controller', $namespace], $content);
$this->checkDirBuild(dirname($filename));
file_put_contents($filename, $content);
}
}
/**
* 创建路由文件
*
* @param string $app 应用名
* @param string $namespace 类库命名空间
* @return void
*/
public function buildRouteFile(string $app, string $namespace): void
{
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR . 'app.php';
if (! is_file($filename)) {
$this->checkDirBuild(dirname($filename));
file_put_contents($filename, "<?php" . PHP_EOL . "use think\\facade\\Route;" . PHP_EOL . PHP_EOL);
}
}
/**
* 创建目录
*
* @param string $dirname 目录名称
* @return void
*/
public function checkDirBuild(string $dirname): void
{
if (! is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
}
/* ------------------------------- clear -------------------------------- */
/**
* 清理 runtime 文件
*
* @return int
*/
protected function doClear(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->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
$output->writeln("<info>Clear Successed</info>");
return 0;
}
/**
* 递归清理目录
*
* @param string $path 目录
* @param bool $rmdir 是否删除空目录
* @return void
*/
protected function clear(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);
}
}
}
}