mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-06 10:02:49 +08:00
增加扩展机制定位文件;将common模块实现扩展模式;发布新版本;
This commit is contained in:
142
extend/base/common/command/CurdBase.php
Normal file
142
extend/base/common/command/CurdBase.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\admin\service\curd\BuildCurdService;
|
||||
use app\common\tools\PathTools;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Exception;
|
||||
use think\facade\App;
|
||||
|
||||
class CurdBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('curd')
|
||||
->addOption('table', 't', Option::VALUE_REQUIRED, '主表名', null)
|
||||
->addOption('controllerFilename', 'c', Option::VALUE_REQUIRED, '控制器文件名', null)
|
||||
->addOption('modelFilename', 'm', Option::VALUE_REQUIRED, '主表模型文件名', null)
|
||||
//
|
||||
->addOption('force', 'f', Option::VALUE_NONE, '强制覆盖模式')
|
||||
->addOption('delete', 'd', Option::VALUE_NONE, '删除模式')
|
||||
->addOption('runtime', 'r', Option::VALUE_NONE, '临时生成')
|
||||
->setDescription('一键curd命令服务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$table = $input->getOption('table');
|
||||
$controllerFilename = $input->getOption('controllerFilename');
|
||||
$modelFilename = $input->getOption('modelFilename');
|
||||
|
||||
$force = 0;
|
||||
$delete = 0;
|
||||
|
||||
if ($input->hasOption('force')) {
|
||||
$force = 1;
|
||||
}
|
||||
|
||||
if ($input->hasOption('delete')) {
|
||||
$delete = 1;
|
||||
}
|
||||
|
||||
if (empty($table)) {
|
||||
$output->error('请设置主表');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$build = (new BuildCurdService())
|
||||
->setTable($table)
|
||||
->setForce($force);
|
||||
|
||||
if ($input->hasOption('runtime')) {
|
||||
$runtime_path = App::getRuntimePath() . 'source/build/' . date('YmdHis') . '/';
|
||||
dump($runtime_path);
|
||||
PathTools::intiDir($runtime_path . 'a.temp');
|
||||
|
||||
$build->setRootDir($runtime_path);
|
||||
}
|
||||
|
||||
$columns = $build->getTableColumns();
|
||||
|
||||
$relations = [];
|
||||
|
||||
foreach ($columns as $field => $column) {
|
||||
if (isset($column['formType']) && $column['formType'] == 'relation') {
|
||||
$define = $column['define'];
|
||||
|
||||
if (!isset($define['table'])) {
|
||||
$output->error("关联字段{$field}没有设置关联表名称");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$relations[] = [
|
||||
'table' => $define['table'],
|
||||
'foreignKey' => $field,
|
||||
'primaryKey' => $define['primaryKey'] ?? null,
|
||||
'modelFilename' => $define['modelFilename'] ?? null,
|
||||
'onlyFileds' => isset($define['onlyFileds']) ? explode('|', $define['onlyFileds']) : [],
|
||||
'relationBindSelect' => $define['relationBindSelect'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
!empty($controllerFilename) && $build = $build->setControllerFilename($controllerFilename);
|
||||
!empty($modelFilename) && $build = $build->setModelFilename($modelFilename);
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
$build = $build->setRelation($relation['table'], $relation['foreignKey'], $relation['primaryKey'], $relation['modelFilename'], $relation['onlyFileds'], $relation['relationBindSelect']);
|
||||
}
|
||||
|
||||
$build = $build->render();
|
||||
$fileList = $build->getFileList();
|
||||
|
||||
if (!$delete) {
|
||||
if ($force) {
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
foreach ($fileList as $key => $val) {
|
||||
$output->writeln($key);
|
||||
}
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
|
||||
$ask_force_delete_result = $output->confirm($input, '确定强制生成上方所有文件? 如果文件存在会直接覆盖。');
|
||||
|
||||
if (!$ask_force_delete_result) {
|
||||
throw new Exception('取消文件CURD生成操作');
|
||||
}
|
||||
}
|
||||
$result = $build->create();
|
||||
$output->info('自动生成CURD成功');
|
||||
} else {
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
foreach ($fileList as $key => $val) {
|
||||
$output->writeln($key);
|
||||
}
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
|
||||
$ask_force_delete_result = $output->confirm($input, '确定删除上方所有文件? ');
|
||||
|
||||
if (!$ask_force_delete_result) {
|
||||
throw new Exception('取消删除文件操作');
|
||||
}
|
||||
$result = $build->delete();
|
||||
$output->info('>>>>>>>>>>>>>>>');
|
||||
$output->info('删除自动生成CURD文件成功');
|
||||
}
|
||||
$output->info('>>>>>>>>>>>>>>>');
|
||||
foreach ($result as $vo) {
|
||||
$output->info($vo);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
extend/base/common/command/NodeBase.php
Normal file
64
extend/base/common/command/NodeBase.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\admin\model\SystemNode;
|
||||
use app\admin\service\NodeService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class NodeBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('node')
|
||||
->addOption('force', null, Option::VALUE_REQUIRED, '是否强制刷新', 0)
|
||||
->setDescription('系统节点刷新服务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$force = $input->getOption('force');
|
||||
$output->writeln('========正在刷新节点服务:=====' . date('Y-m-d H:i:s'));
|
||||
$check = $this->refresh($force);
|
||||
$check !== true && $output->writeln('节点刷新失败:' . $check);
|
||||
$output->writeln('刷新完成:' . date('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
protected function refresh($force)
|
||||
{
|
||||
$nodeList = (new NodeService())->getNodelist();
|
||||
if (empty($nodeList)) {
|
||||
return true;
|
||||
}
|
||||
$model = new SystemNode();
|
||||
try {
|
||||
if ($force == 1) {
|
||||
$updateNodeList = $model->whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($updateNodeList as $vo) {
|
||||
isset($formatNodeList[$vo['node']]) && $model->where('id', $vo['id'])->update([
|
||||
'title' => $formatNodeList[$vo['node']]['title'],
|
||||
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
$existNodeList = $model->field('node,title,type,is_auth')->select();
|
||||
foreach ($nodeList as $key => $vo) {
|
||||
foreach ($existNodeList as $v) {
|
||||
if ($vo['node'] == $v->node) {
|
||||
unset($nodeList[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$model->insertAll($nodeList);
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
49
extend/base/common/command/OssStaticBase.php
Normal file
49
extend/base/common/command/OssStaticBase.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\common\service\UploadService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Filesystem;
|
||||
use think\File;
|
||||
|
||||
class OssStaticBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('OssStatic')
|
||||
->setDescription('将静态资源上传到oss上');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('========正在上传静态资源到OSS上:========' . date('Y-m-d H:i:s'));
|
||||
|
||||
$list = Filesystem::disk('local_static')->listContents('/', true);
|
||||
$upload_service = new UploadService();
|
||||
$uploadPrefix = config('app.oss_static_prefix', 'oss_static_prefix');
|
||||
|
||||
foreach ($list as $file_item) {
|
||||
if ($file_item['type'] != 'file') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_path = $file_item['path'];
|
||||
|
||||
$file_path = Filesystem::disk('local_static')->path($file_path);
|
||||
|
||||
$file = new File($file_path, false);
|
||||
|
||||
$save_name = $file_item['path'];
|
||||
try {
|
||||
$model_file = $upload_service->save($file, $save_name, true, $uploadPrefix, true);
|
||||
$output->info('文件上传成功:' . $save_name . '。上传地址:' . $model_file['url']);
|
||||
} catch (\Throwable $th) {
|
||||
$output->error('文件上传失败:' . $save_name . '。错误信息:' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
$output->writeln('========已完成静态资源上传到OSS上:========' . date('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
125
extend/base/common/command/TimerBase.php
Normal file
125
extend/base/common/command/TimerBase.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Promise\Utils;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Log;
|
||||
|
||||
class TimerBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('timer')
|
||||
->addOption('temp', null, Option::VALUE_NONE)
|
||||
->addOption('quit', null, Option::VALUE_NONE)
|
||||
->setDescription('内置秒级定时器');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('start timer');
|
||||
|
||||
$site_domain = sysconfig('site', 'site_domain');
|
||||
|
||||
if (empty($site_domain)) {
|
||||
$output->writeln('请前往后台设置站点域名(site_domain)配置项');
|
||||
|
||||
return;
|
||||
}
|
||||
$output->writeln('站点域名:' . $site_domain);
|
||||
|
||||
$client = new Client([
|
||||
'base_uri' => $site_domain,
|
||||
'verify' => false,
|
||||
]);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
$config_list = include __DIR__ . '/timer/config.php';
|
||||
|
||||
$list_promises = [];
|
||||
foreach ($config_list as $config_item) {
|
||||
$config_item = static::initConfigItem($config_item);
|
||||
|
||||
$name = $config_item['name'];
|
||||
|
||||
if ($name == 'http_demo' && !env('adminsystem.is_demo', false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cache_key = 'timer_' . $name;
|
||||
$cache_tag = 'system_timer';
|
||||
|
||||
$last_exec_time = Cache::get($cache_key, 0);
|
||||
|
||||
if ($last_exec_time >= time() - $config_item['frequency']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Cache::tag($cache_tag)->set($cache_key, time());
|
||||
|
||||
$type = $config_item['type'];
|
||||
|
||||
switch ($type) {
|
||||
case 'site':
|
||||
$output->writeln(date('Y-m-d H:i:s') . ': build site request async:' . $config_item['target']);
|
||||
$list_promises[$config_item['name']] = $client->getAsync($config_item['target']);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$output->writeln(date('Y-m-d H:i:s') . 'unsupport type:' . $type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($list_promises)) {
|
||||
if (!$input->hasOption('quit')) {
|
||||
$output->writeln(date('Y-m-d H:i:s') . ' no request');
|
||||
}
|
||||
} else {
|
||||
$results = Utils::unwrap($list_promises);
|
||||
$output->writeln(date('Y-m-d H:i:s') . ': request all finished');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
// throw $th;
|
||||
$output->writeln('error:' . $th->getMessage());
|
||||
Log::error($th->getMessage());
|
||||
}
|
||||
|
||||
if ($input->hasOption('temp')) {
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static function initConfigItem($config)
|
||||
{
|
||||
$default = [
|
||||
'name' => 'http_demo',
|
||||
'type' => 'site',
|
||||
'target' => '',
|
||||
'frequency' => 600,
|
||||
];
|
||||
|
||||
$data = array_merge($default, $config);
|
||||
|
||||
if ($data['frequency'] < 0) {
|
||||
$data['frequency'] = 0;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
50
extend/base/common/command/admin/ClearBase.php
Normal file
50
extend/base/common/command/admin/ClearBase.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\App;
|
||||
|
||||
class ClearBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('admin:clear')
|
||||
->setDescription('删除开发临时生成目录');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('删除测试目录');
|
||||
|
||||
$command_line = '';
|
||||
|
||||
$dir = App::getRootPath() . '/runtime/source/';
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
$output->writeln('删除成功');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (strpos(strtolower(PHP_OS), 'win') === 0) {
|
||||
$command_line = implode(' ', ['rd', '/s', '/q', str_replace('/', '\\', $dir)]);
|
||||
} else {
|
||||
$command_line = implode(' ', ['rm', '-rf', $dir]);
|
||||
}
|
||||
|
||||
$output->info('删除目录:' . $command_line);
|
||||
|
||||
$output->info('run command: ' . $command_line);
|
||||
|
||||
exec($command_line);
|
||||
|
||||
$output->info('删除成功');
|
||||
}
|
||||
}
|
||||
48
extend/base/common/command/admin/ResetPasswordBase.php
Normal file
48
extend/base/common/command/admin/ResetPasswordBase.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\common\constants\AdminConstant;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class ResetPasswordBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('admin:reset:password')
|
||||
->addOption('password', 'p', Option::VALUE_OPTIONAL)
|
||||
->setDescription('重置超管密码');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('admin:reset:password');
|
||||
|
||||
$model_admin = SystemAdmin::find(AdminConstant::SUPER_ADMIN_ID);
|
||||
if (empty($model_admin)) {
|
||||
$output->writeln('管理员不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$password = $input->getOption('password');
|
||||
|
||||
if (is_null($password)) {
|
||||
$password = uniqid();
|
||||
}
|
||||
|
||||
$model_admin->save([
|
||||
'password' => password($password),
|
||||
]);
|
||||
|
||||
$output->writeln('密码修改为:' . $password);
|
||||
}
|
||||
}
|
||||
313
extend/base/common/command/admin/UpdateBase.php
Normal file
313
extend/base/common/command/admin/UpdateBase.php
Normal file
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use app\common\command\admin\Version;
|
||||
use app\common\tools\PathTools;
|
||||
use CzProject\GitPhp\Git;
|
||||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\Local\LocalFilesystemAdapter;
|
||||
use League\Flysystem\StorageAttributes;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\facade\App;
|
||||
use think\facade\Env;
|
||||
|
||||
class UpdateBase extends Command
|
||||
{
|
||||
public const REPO = 'https://gitee.com/ulthon/ulthon_admin.git';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('admin:update')
|
||||
->addOption('reinstall', null, Option::VALUE_NONE, '重装版本')
|
||||
->setDescription('the admin:update command');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('admin:update');
|
||||
|
||||
$this->cleanWorkpaceDir();
|
||||
|
||||
$current_version = Version::VERSION;
|
||||
|
||||
$current_version_dir = App::getRuntimePath() . '/update/' . $current_version;
|
||||
|
||||
$last_version_dir = App::getRuntimePath() . '/update/last';
|
||||
|
||||
$version_file_regx = "/\bconst VERSION\s*=\s*'[\d\.a-z]+'/";
|
||||
|
||||
$output->writeln('获取最新代码');
|
||||
$last_version_git = new Git();
|
||||
|
||||
$last_version_repo = $last_version_git->cloneRepository(self::REPO, $last_version_dir);
|
||||
|
||||
$tags = $last_version_repo->getTags();
|
||||
|
||||
$update_level = Env::get('adminsystem.update_level', 'production');
|
||||
|
||||
if ($update_level == 'production') {
|
||||
$tags = array_filter($tags, function ($value) {
|
||||
if (strpos($value, '-')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
usort($tags, function ($a, $b) {
|
||||
return version_compare($a, $b);
|
||||
});
|
||||
|
||||
$last_version = $tags[count($tags) - 1];
|
||||
|
||||
if ($last_version == $current_version) {
|
||||
$output->writeln('当前版本为最新版本');
|
||||
$this->cleanWorkpaceDir();
|
||||
|
||||
if ($input->hasOption('reinstall')) {
|
||||
$output->writeln('重装代码');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 将最新代码切换到最新版本,因为最新的提交可能没有发布版本
|
||||
$output->writeln('切换最新代码的最新版本');
|
||||
$last_version_repo->checkout($last_version);
|
||||
|
||||
$current_version_git = new Git();
|
||||
$output->writeln('获取当前版本代码');
|
||||
$current_version_repo = $current_version_git->cloneRepository(self::REPO, $current_version_dir);
|
||||
$output->writeln('切换版本' . $current_version);
|
||||
$current_version_repo->checkout($current_version);
|
||||
|
||||
// 获取当前版本需要跳过的文件
|
||||
$current_version_update_config = include $current_version_dir . '/config/update.php';
|
||||
|
||||
// 获取当前版本要替换的文件
|
||||
$current_version_filesystem = new Filesystem(new LocalFilesystemAdapter($current_version_dir));
|
||||
$current_version_list_files = $current_version_filesystem->listContents('/', Filesystem::LIST_DEEP)
|
||||
->filter(function (StorageAttributes $attributes) use ($current_version_update_config) {
|
||||
if ($attributes->isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $attributes->path();
|
||||
|
||||
$skip_files = $current_version_update_config['skip_files'] ?? [];
|
||||
|
||||
if (in_array($path, $skip_files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$skip_dir = $current_version_update_config['skip_dir'] ?? [];
|
||||
$skip_dir[] = '.git';
|
||||
|
||||
foreach ($skip_dir as $dir) {
|
||||
if (str_starts_with($path, $dir)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->map(fn (StorageAttributes $attributes) => $attributes->path())
|
||||
->toArray();
|
||||
|
||||
// 对比现在的代码,检查是否有定制修改
|
||||
|
||||
$output->writeln('对比源码是否被定制');
|
||||
$now_dir = App::getRootPath();
|
||||
|
||||
/**
|
||||
* @var array<bool|string>
|
||||
*/
|
||||
$changed_files = [];
|
||||
|
||||
foreach ($current_version_list_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
|
||||
$current_version_file_path = $current_version_dir . '/' . $file_path;
|
||||
|
||||
$compare_result = PathTools::compareFiles($now_file_path, $current_version_file_path, true);
|
||||
|
||||
if (!$compare_result || is_string($compare_result)) {
|
||||
$changed_files[$file_path] = $compare_result;
|
||||
}
|
||||
}
|
||||
|
||||
// 有定制修改则退出
|
||||
|
||||
if (!empty($changed_files)) {
|
||||
$output->warning('无法自动更新,以下文件被定制,请还原或手动升级:');
|
||||
|
||||
foreach ($changed_files as $file_path => $compare_result) {
|
||||
$output->warning($file_path);
|
||||
|
||||
if (is_string($compare_result)) {
|
||||
$output->writeln($compare_result);
|
||||
}
|
||||
}
|
||||
|
||||
$file_count = count($changed_files);
|
||||
|
||||
$ask_result = $output->ask($input, "发现{$file_count}个文件被修改,如果您认为以上文件可以被覆盖或为检测错误,可以强制覆盖。\n是否强制继续更新?(y/n)");
|
||||
|
||||
if (!$ask_result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最新版本需要跳过的文件
|
||||
|
||||
$last_version_skip_config = include $last_version_dir . '/config/update.php';
|
||||
// 获取最新版本要替换的文件
|
||||
$last_version_filesystem = new Filesystem(new LocalFilesystemAdapter($last_version_dir));
|
||||
$last_version_list_files = $last_version_filesystem->listContents('/', Filesystem::LIST_DEEP)
|
||||
->filter(function (StorageAttributes $attributes) use ($last_version_skip_config) {
|
||||
if ($attributes->isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $attributes->path();
|
||||
|
||||
$skip_files = $last_version_skip_config['skip_files'] ?? [];
|
||||
|
||||
if (in_array($path, $skip_files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$skip_dir = $last_version_skip_config['skip_dir'] ?? [];
|
||||
$skip_dir[] = '.git';
|
||||
|
||||
foreach ($skip_dir as $dir) {
|
||||
if (str_starts_with($path, $dir)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->map(fn (StorageAttributes $attributes) => $attributes->path())
|
||||
->toArray();
|
||||
|
||||
$delete_files = array_diff($current_version_list_files, $last_version_list_files);
|
||||
|
||||
foreach ($delete_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
unlink($now_file_path);
|
||||
}
|
||||
|
||||
foreach ($last_version_list_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
$last_file_path = $last_version_dir . '/' . $file_path;
|
||||
|
||||
$file_content = file_get_contents($last_file_path);
|
||||
|
||||
PathTools::intiDir($now_file_path);
|
||||
file_put_contents($now_file_path, $file_content);
|
||||
}
|
||||
|
||||
// 处理append的文件
|
||||
$last_version_list_skip_files = $last_version_filesystem->listContents('/', Filesystem::LIST_DEEP)
|
||||
->filter(function (StorageAttributes $attributes) use ($last_version_skip_config) {
|
||||
if ($attributes->isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $attributes->path();
|
||||
|
||||
if (str_starts_with($path, '.git')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$skip_files = $last_version_skip_config['skip_files'] ?? [];
|
||||
|
||||
if (in_array($path, $skip_files)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$skip_dir = $last_version_skip_config['skip_dir'] ?? [];
|
||||
|
||||
foreach ($skip_dir as $dir) {
|
||||
if (str_starts_with($path, $dir)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->map(fn (StorageAttributes $attributes) => $attributes->path())
|
||||
->toArray();
|
||||
|
||||
$last_version_list_append_files = [];
|
||||
|
||||
foreach ($last_version_list_skip_files as $file_path) {
|
||||
if (in_array($file_path, $last_version_skip_config['append_files'])) {
|
||||
$last_version_list_append_files[] = $file_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($last_version_skip_config['append_dir'] as $dir) {
|
||||
if (str_starts_with($file_path, $dir)) {
|
||||
$last_version_list_append_files[] = $file_path;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($last_version_list_append_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
$last_file_path = $last_version_dir . '/' . $file_path;
|
||||
|
||||
if (file_exists($now_file_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_content = file_get_contents($last_file_path);
|
||||
|
||||
PathTools::intiDir($now_file_path);
|
||||
file_put_contents($now_file_path, $file_content);
|
||||
}
|
||||
|
||||
// 检测now的composer依赖和最新的composer依赖
|
||||
|
||||
$last_composer_json = file_get_contents($last_version_dir . '/composer.json');
|
||||
|
||||
$output->writeln($last_composer_json);
|
||||
$output->writeln('请参考以上最新composer文件调整您的依赖');
|
||||
|
||||
// 分析出最新需要的但now没有的
|
||||
|
||||
// 为用户整理出要手动调整的composer命令
|
||||
|
||||
$this->cleanWorkpaceDir();
|
||||
$output->writeln('更新完成');
|
||||
// 更新完成
|
||||
|
||||
if (!empty(Version::UPDATE_TIPS)) {
|
||||
foreach (Version::UPDATE_TIPS as $content) {
|
||||
$output->writeln($content);
|
||||
}
|
||||
$output->writeln('删除对应文件之后,可以通过以下命令重新安装');
|
||||
$output->writeln('php think admin:update --resinstall');
|
||||
}
|
||||
}
|
||||
|
||||
protected function cleanWorkpaceDir()
|
||||
{
|
||||
$dir = App::getRuntimePath() . '/update/';
|
||||
|
||||
$this->output->writeln('清理目录 ' . $dir);
|
||||
PathTools::removeDir($dir);
|
||||
}
|
||||
}
|
||||
72
extend/base/common/command/admin/VersionBase.php
Normal file
72
extend/base/common/command/admin/VersionBase.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use think\App as ThinkApp;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class VersionBase extends Command
|
||||
{
|
||||
public const VERSION = 'v2.0.46';
|
||||
|
||||
public const LAYUI_VERSION = '2.8.16';
|
||||
|
||||
public const COMMENT = [
|
||||
'增加扩展机制定位文件',
|
||||
'将common模块实现扩展模式',
|
||||
'发布新版本',
|
||||
];
|
||||
|
||||
public const UPDATE_TIPS = [
|
||||
'本次将common下的代码实现了扩展模式',
|
||||
'可以删除app下common代码',
|
||||
'然后重新执行更新命令,删除后会自动初始化新的代码',
|
||||
|
||||
];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('admin:version')
|
||||
->addOption('push-tag', null, Option::VALUE_NONE, '使用git命令生成tag并推送')
|
||||
->setDescription('查看当前ulthon_admin的版本号');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->info('当前版本号为:' . $this::VERSION);
|
||||
$output->info('当前Layui版本号为:' . $this::LAYUI_VERSION);
|
||||
$output->info('当前ThinkPHP版本号为:' . ThinkApp::VERSION);
|
||||
|
||||
$output->writeln('当前的修改说明:');
|
||||
$output->writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
|
||||
|
||||
foreach ($this::COMMENT as $comment) {
|
||||
$output->info($comment);
|
||||
}
|
||||
$output->writeln('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<');
|
||||
|
||||
$output->highlight('代码托管地址:https://gitee.com/ulthon/ulthon_admin');
|
||||
$output->highlight('开发文档地址:http://doc.ulthon.com/home/read/ulthon_admin/home.html');
|
||||
|
||||
$is_push_tag = $input->hasOption('push-tag');
|
||||
|
||||
if ($is_push_tag) {
|
||||
$output->writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
|
||||
|
||||
$version = $this::VERSION;
|
||||
$comment = implode(';', $this::COMMENT);
|
||||
$output->info('生成标签:' . $version);
|
||||
$output->info('标签描述:' . $comment);
|
||||
exec("git tag -a $version -m \"$comment\"");
|
||||
$output->info('推送到远程仓库');
|
||||
exec('git push --tags');
|
||||
}
|
||||
}
|
||||
}
|
||||
207
extend/base/common/command/curd/MigrateBase.php
Normal file
207
extend/base/common/command/curd/MigrateBase.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\curd;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\facade\App;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
|
||||
class MigrateBase extends Command
|
||||
{
|
||||
protected $table;
|
||||
protected $database;
|
||||
protected $tablePrefix;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
// 指令配置
|
||||
$this->setName('curd:migrate')
|
||||
->addOption('table', 't', Option::VALUE_REQUIRED, '主表名')
|
||||
->addOption('tableName', '', Option::VALUE_OPTIONAL, '要生成的表名')
|
||||
->addOption('fileName', '', Option::VALUE_OPTIONAL, '要生成的文件名')
|
||||
->addOption('force', 'f', Option::VALUE_NONE, '强制生成')
|
||||
->setDescription('the curd:migrate command');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('curd:migrate');
|
||||
|
||||
$table = $input->getOption('table');
|
||||
$file_name = $input->getOption('fileName');
|
||||
$table_name = $input->getOption('tableName');
|
||||
$force = $input->getOption('force');
|
||||
|
||||
if (empty($table)) {
|
||||
$output->error('请输入表名');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($table_name)) {
|
||||
$table_name = $table;
|
||||
}
|
||||
|
||||
if (empty($file_name)) {
|
||||
$file_name = $table_name;
|
||||
}
|
||||
$this->table = $table;
|
||||
|
||||
$this->database = config('database.connections.mysql.database');
|
||||
$this->tablePrefix = config('database.connections.mysql.prefix');
|
||||
|
||||
$table_info_sql = "select * from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='{$this->database}' and TABLE_NAME='{$this->tablePrefix}{$this->table}'";
|
||||
|
||||
$table_info = Db::query($table_info_sql);
|
||||
|
||||
if (empty($table_info)) {
|
||||
$output->error('表不存在');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dist_file_path = App::getRootPath() . '/database/migrations/' . date('YmdHis') . '_' . $file_name . '.php';
|
||||
|
||||
$patt_path = App::getRootPath() . '/database/migrations/*' . $file_name . '.php';
|
||||
|
||||
$patt_files = glob($patt_path);
|
||||
|
||||
$is_extis = false;
|
||||
foreach ($patt_files as $patt_file) {
|
||||
$patt = '/.*database\/migrations\/\d+_' . $file_name . '.php$/';
|
||||
|
||||
$preg_result = preg_match($patt, $patt_file);
|
||||
|
||||
if ($preg_result) {
|
||||
$is_extis = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_extis) {
|
||||
$output->error('文件已存在:' . $patt_files[0]);
|
||||
if (!$force) {
|
||||
$confirm_force = $output->confirm($input, '确定要覆盖文件吗?', false);
|
||||
|
||||
if (!$confirm_force) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$output->highlight('执行覆盖操作');
|
||||
|
||||
$dist_file_path = $patt_files[0];
|
||||
}
|
||||
|
||||
$columns = Db::query("SHOW FULL COLUMNS FROM {$this->tablePrefix}{$this->table}");
|
||||
|
||||
$data['class_name'] = \think\helper\Str::studly($file_name);
|
||||
|
||||
$table_columns = [];
|
||||
|
||||
$table_keys = [];
|
||||
$table_keys_text = [];
|
||||
$table_keys_uni = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if ($column['Field'] == 'id') {
|
||||
continue;
|
||||
}
|
||||
$column_item = [];
|
||||
$column_item['options'] = [];
|
||||
|
||||
$column_item['field'] = $column['Field'];
|
||||
|
||||
$column_item['type'] = '';
|
||||
|
||||
$type = $column['Type'];
|
||||
|
||||
if (strpos($type, '(') !== false) {
|
||||
// 带有长度
|
||||
|
||||
$type_info = explode('(', $type);
|
||||
$column_item['type'] = $type_info[0];
|
||||
|
||||
$length = substr($type_info[1], 0, strpos($type_info[1], ')'));
|
||||
|
||||
if (strpos($length, ',') !== false) {
|
||||
$length_info = explode(',', $length);
|
||||
|
||||
$column_item['options']['precision'] = $length_info[0];
|
||||
$column_item['options']['scale'] = $length_info[1];
|
||||
} else {
|
||||
$column_item['options']['limit'] = $length;
|
||||
}
|
||||
|
||||
if (strpos($type, 'unsigned') !== false) {
|
||||
// 无符号
|
||||
$column_item['options']['signed'] = 0;
|
||||
}
|
||||
} else {
|
||||
$column_item['type'] = $type;
|
||||
}
|
||||
|
||||
$column_item['options']['null'] = $column['Null'] == 'YES' ? 1 : 0;
|
||||
if ($column['Default'] !== null) {
|
||||
$column_item['options']['default'] = $column['Default'];
|
||||
}
|
||||
$column_item['options']['comment'] = $column['Comment'];
|
||||
|
||||
$key = $column['Key'];
|
||||
|
||||
if (!empty($key)) {
|
||||
if ($key == 'MUL') {
|
||||
if ($type == 'text' || $type == 'longtext') {
|
||||
$table_keys_text[] = $column['Field'];
|
||||
} else {
|
||||
$table_keys[] = $column['Field'];
|
||||
}
|
||||
} elseif ($key == 'UNI') {
|
||||
$table_keys_uni[] = $column['Field'];
|
||||
}
|
||||
}
|
||||
|
||||
$table_columns[] = $column_item;
|
||||
}
|
||||
|
||||
$type_map = [
|
||||
'bigint' => 'biginteger',
|
||||
'int' => 'integer',
|
||||
'varchar' => 'string',
|
||||
'smallint' => 'integer',
|
||||
'tinyint' => 'integer',
|
||||
'longtext' => 'text',
|
||||
];
|
||||
|
||||
foreach ($table_columns as &$column_item_set) {
|
||||
if (isset($type_map[$column_item_set['type']])) {
|
||||
$column_item_set['type'] = $type_map[$column_item_set['type']];
|
||||
}
|
||||
|
||||
foreach ($column_item_set['options'] as $key => $option) {
|
||||
if (is_array($option)) {
|
||||
$column_item_set['options'][$key] = '[' . implode(',', $option) . ']';
|
||||
} else {
|
||||
$column_item_set['options'][$key] = "'{$option}'";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['table_info'] = $table_info[0];
|
||||
$data['table'] = $table_name;
|
||||
$data['table_columns'] = $table_columns;
|
||||
$data['table_keys'] = $table_keys;
|
||||
$data['table_keys_uni'] = $table_keys_uni;
|
||||
$data['table_keys_text'] = $table_keys_text;
|
||||
|
||||
$migrate_content = View::fetch(__DIR__ . '/migrate.tpl', $data);
|
||||
|
||||
file_put_contents(__DIR__ . '/migrate_output.php', "<?php\n\n" . $migrate_content);
|
||||
file_put_contents($dist_file_path, "<?php\n\n" . $migrate_content);
|
||||
}
|
||||
}
|
||||
40
extend/base/common/command/curd/migrate.tpl
Normal file
40
extend/base/common/command/curd/migrate.tpl
Normal file
@@ -0,0 +1,40 @@
|
||||
use think\migration\Migrator;
|
||||
use think\migration\db\Column;
|
||||
|
||||
class {$class_name} extends Migrator
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* Write your reversible migrations using this method.
|
||||
*
|
||||
* More information on writing migrations is available here:
|
||||
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||
*
|
||||
* The following commands can be used in this method and Phinx will
|
||||
* automatically reverse them when rolling back:
|
||||
*
|
||||
* createTable
|
||||
* renameTable
|
||||
* addColumn
|
||||
* renameColumn
|
||||
* addIndex
|
||||
* addForeignKey
|
||||
*
|
||||
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||
* with the Table class.
|
||||
*/
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('{$table}')
|
||||
->setComment('{$table_info.TABLE_COMMENT}')
|
||||
{volist name="table_columns" id="column"}->addColumn('{$column.field}', '{$column.type}', [{volist name="column.options" id="option"}'{$key}' => {$option|raw}, {/volist}])
|
||||
{/volist}
|
||||
{volist name="table_keys_uni" id="vo"}->addIndex('{$vo}',['unique'=>true])
|
||||
{/volist}
|
||||
{volist name="table_keys_text" id="vo"}->addIndex('{$vo}',['type'=>'fulltext'])
|
||||
{/volist}
|
||||
{volist name="table_keys" id="vo"}->addIndex('{$vo}')
|
||||
{/volist}->create();
|
||||
}
|
||||
}
|
||||
16
extend/base/common/command/timer/config.php
Normal file
16
extend/base/common/command/timer/config.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => 'http_demo', // 定时任务的名称,不能重复
|
||||
'type' => 'site', // 定时任务的类型,默认只支持site,你也可以重写定时器命令行以支持其他命令
|
||||
'target' => '/tools/timer.ResetPassword/do', // 要访问的地址,如果不是以https开头,那么以后台的系统配置中读取相关配置,如果没有配置则不执行
|
||||
'frequency' => 600 // 执行频率,单位:秒,填写10,则每10秒过后执行一次
|
||||
],
|
||||
[
|
||||
'name' => 'clear_log', // 定时任务的名称,不能重复
|
||||
'type' => 'site', // 定时任务的类型,默认只支持site,你也可以重写定时器命令行以支持其他命令
|
||||
'target' => '/tools/timer.ClearLog/do', // 要访问的地址,如果不是以https开头,那么以后台的系统配置中读取相关配置,如果没有配置则不执行
|
||||
'frequency' => 600 // 执行频率,单位:秒,填写10,则每10秒过后执行一次
|
||||
],
|
||||
];
|
||||
Reference in New Issue
Block a user