From 184dae8185a229efec47355bfe62e3f0739716f2 Mon Sep 17 00:00:00 2001 From: augushong Date: Mon, 25 Sep 2023 17:07:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=A9=E5=B1=95=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E5=AE=9A=E4=BD=8D=E6=96=87=E4=BB=B6=EF=BC=9B=E5=B0=86?= =?UTF-8?q?common=E6=A8=A1=E5=9D=97=E5=AE=9E=E7=8E=B0=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=EF=BC=9B=E5=8F=91=E5=B8=83=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common.php | 17 + app/common/command/Curd.php | 141 +----- app/common/command/Node.php | 63 +-- app/common/command/OssStatic.php | 47 +- app/common/command/Timer.php | 126 +---- app/common/command/admin/Clear.php | 45 +- app/common/command/admin/ResetPassword.php | 43 +- app/common/command/admin/Update.php | 305 +----------- app/common/command/admin/Version.php | 65 +-- app/common/command/curd/Migrate.php | 216 +-------- app/common/constants/AdminConstant.php | 16 +- app/common/constants/MenuConstant.php | 21 +- app/common/controller/AdminController.php | 435 +---------------- app/common/controller/TimerController.php | 33 +- app/common/controller/ToolsController.php | 6 +- .../event/AdminLoginSuccess/LogEvent.php | 10 +- app/common/event/AdminLoginType/DemoEvent.php | 18 +- app/common/exception/EventException.php | 4 +- app/common/model/BaseModel.php | 102 +--- app/common/model/TimeModel.php | 37 +- app/common/provider/ExceptionHandle.php | 54 +-- app/common/provider/Request.php | 4 +- app/common/provider/View.php | 78 +-- app/common/provider/db/Query.php | 55 +-- app/common/service/AuthService.php | 204 +------- app/common/service/MenuService.php | 102 +--- app/common/service/UploadService.php | 169 +------ app/common/tools/ExportTools.php | 109 +---- app/common/tools/PathTools.php | 157 +------ app/common/traits/JumpTrait.php | 142 +----- config/app.php | 31 +- config/update.php | 33 ++ extend/base/common/command/CurdBase.php | 142 ++++++ extend/base/common/command/NodeBase.php | 64 +++ extend/base/common/command/OssStaticBase.php | 49 ++ extend/base/common/command/TimerBase.php | 125 +++++ .../base/common/command/admin/ClearBase.php | 50 ++ .../command/admin/ResetPasswordBase.php | 48 ++ .../base/common/command/admin/UpdateBase.php | 313 +++++++++++++ .../base/common/command/admin/VersionBase.php | 72 +++ .../base/common/command/curd/MigrateBase.php | 207 ++++++++ extend/base/common/command/curd/migrate.tpl | 40 ++ extend/base/common/command/timer/config.php | 16 + .../common/constants/AdminConstantBase.php | 15 + .../common/constants/MenuConstantBase.php | 20 + .../common/controller/AdminControllerBase.php | 443 ++++++++++++++++++ .../common/controller/TimerControllerBase.php | 35 ++ .../common/controller/ToolsControllerBase.php | 11 + .../event/AdminLoginSuccess/LogEventBase.php | 15 + .../event/AdminLoginType/DemoEventBase.php | 23 + .../common/exception/EventExceptionBase.php | 9 + extend/base/common/model/BaseModelBase.php | 105 +++++ extend/base/common/model/TimeModelBase.php | 40 ++ .../common/provider/ExceptionHandleBase.php | 57 +++ extend/base/common/provider/RequestBase.php | 9 + extend/base/common/provider/ViewBase.php | 83 ++++ extend/base/common/provider/db/QueryBase.php | 60 +++ .../base/common/service/AuthServiceBase.php | 212 +++++++++ .../base/common/service/MenuServiceBase.php | 111 +++++ .../base/common/service/UploadServiceBase.php | 168 +++++++ extend/base/common/tools/ExportToolsBase.php | 111 +++++ extend/base/common/tools/PathToolsBase.php | 162 +++++++ .../base}/common/tpl/dispatch_jump.tpl | 0 .../base}/common/tpl/think_exception.tpl | 0 extend/base/common/traits/JumpTraitBase.php | 141 ++++++ 65 files changed, 3093 insertions(+), 2751 deletions(-) create mode 100644 extend/base/common/command/CurdBase.php create mode 100644 extend/base/common/command/NodeBase.php create mode 100644 extend/base/common/command/OssStaticBase.php create mode 100644 extend/base/common/command/TimerBase.php create mode 100644 extend/base/common/command/admin/ClearBase.php create mode 100644 extend/base/common/command/admin/ResetPasswordBase.php create mode 100644 extend/base/common/command/admin/UpdateBase.php create mode 100644 extend/base/common/command/admin/VersionBase.php create mode 100644 extend/base/common/command/curd/MigrateBase.php create mode 100644 extend/base/common/command/curd/migrate.tpl create mode 100644 extend/base/common/command/timer/config.php create mode 100644 extend/base/common/constants/AdminConstantBase.php create mode 100644 extend/base/common/constants/MenuConstantBase.php create mode 100644 extend/base/common/controller/AdminControllerBase.php create mode 100644 extend/base/common/controller/TimerControllerBase.php create mode 100644 extend/base/common/controller/ToolsControllerBase.php create mode 100644 extend/base/common/event/AdminLoginSuccess/LogEventBase.php create mode 100644 extend/base/common/event/AdminLoginType/DemoEventBase.php create mode 100644 extend/base/common/exception/EventExceptionBase.php create mode 100644 extend/base/common/model/BaseModelBase.php create mode 100644 extend/base/common/model/TimeModelBase.php create mode 100644 extend/base/common/provider/ExceptionHandleBase.php create mode 100644 extend/base/common/provider/RequestBase.php create mode 100644 extend/base/common/provider/ViewBase.php create mode 100644 extend/base/common/provider/db/QueryBase.php create mode 100644 extend/base/common/service/AuthServiceBase.php create mode 100644 extend/base/common/service/MenuServiceBase.php create mode 100644 extend/base/common/service/UploadServiceBase.php create mode 100644 extend/base/common/tools/ExportToolsBase.php create mode 100644 extend/base/common/tools/PathToolsBase.php rename {app => extend/base}/common/tpl/dispatch_jump.tpl (100%) rename {app => extend/base}/common/tpl/think_exception.tpl (100%) create mode 100644 extend/base/common/traits/JumpTraitBase.php diff --git a/app/common.php b/app/common.php index d0a71b3..8c485f2 100644 --- a/app/common.php +++ b/app/common.php @@ -5,6 +5,7 @@ use app\commno\exception\EventException; use app\common\service\AuthService; use think\exception\HttpResponseException; +use think\facade\App; use think\facade\Cache; use think\facade\Env; use think\facade\Event; @@ -306,3 +307,19 @@ function event_response($name, $params = []) 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; + } + + return $app_file_path; +} diff --git a/app/common/command/Curd.php b/app/common/command/Curd.php index 90994ea..ec10f70 100644 --- a/app/common/command/Curd.php +++ b/app/common/command/Curd.php @@ -1,146 +1,9 @@ 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; - } - } } diff --git a/app/common/command/Node.php b/app/common/command/Node.php index cbb6f54..c29e9bd 100644 --- a/app/common/command/Node.php +++ b/app/common/command/Node.php @@ -1,66 +1,9 @@ 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; - } - -} \ No newline at end of file +} diff --git a/app/common/command/OssStatic.php b/app/common/command/OssStatic.php index 4531027..bcf5bdb 100644 --- a/app/common/command/OssStatic.php +++ b/app/common/command/OssStatic.php @@ -1,52 +1,9 @@ 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')); - } } diff --git a/app/common/command/Timer.php b/app/common/command/Timer.php index 9b67418..394be63 100644 --- a/app/common/command/Timer.php +++ b/app/common/command/Timer.php @@ -4,130 +4,8 @@ declare(strict_types=1); namespace app\common\command; -use GuzzleHttp\Client; -use GuzzleHttp\Promise\Utils; -use think\console\Command; -use think\console\Input; -use think\console\input\Argument; -use think\console\input\Option; -use think\console\Output; -use think\facade\Cache; -use think\facade\Log; +use base\common\command\TimerBase; -class Timer extends Command +class Timer extends TimerBase { - 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; - } } diff --git a/app/common/command/admin/Clear.php b/app/common/command/admin/Clear.php index 81056e3..bcf5cd1 100644 --- a/app/common/command/admin/Clear.php +++ b/app/common/command/admin/Clear.php @@ -4,49 +4,8 @@ declare(strict_types=1); namespace app\common\command\admin; -use think\console\Command; -use think\console\Input; -use think\console\input\Argument; -use think\console\input\Option; -use think\console\Output; -use think\facade\App; +use base\common\command\admin\ClearBase; -class Clear extends Command +class Clear extends ClearBase { - 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('删除成功'); - } } diff --git a/app/common/command/admin/ResetPassword.php b/app/common/command/admin/ResetPassword.php index 5917c72..789d2d4 100644 --- a/app/common/command/admin/ResetPassword.php +++ b/app/common/command/admin/ResetPassword.php @@ -4,47 +4,8 @@ declare(strict_types=1); namespace app\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\Argument; -use think\console\input\Option; -use think\console\Output; +use base\common\command\admin\ResetPasswordBase; -class ResetPassword extends Command +class ResetPassword extends ResetPasswordBase { - 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); - } } diff --git a/app/common/command/admin/Update.php b/app/common/command/admin/Update.php index cd98c53..c325711 100644 --- a/app/common/command/admin/Update.php +++ b/app/common/command/admin/Update.php @@ -4,309 +4,8 @@ declare(strict_types=1); namespace app\common\command\admin; -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; +use base\common\command\admin\UpdateBase; -class Update extends Command +class Update extends UpdateBase { - 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 - */ - $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); - } } diff --git a/app/common/command/admin/Version.php b/app/common/command/admin/Version.php index 40a93aa..4a21f5c 100644 --- a/app/common/command/admin/Version.php +++ b/app/common/command/admin/Version.php @@ -4,69 +4,8 @@ declare(strict_types=1); namespace app\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; +use base\common\command\admin\VersionBase; -class Version extends Command +class Version extends VersionBase { - public const VERSION = 'v2.0.45'; - - public const LAYUI_VERSION = '2.8.16'; - - public const COMMENT = [ - '实现view的扩展架构', - '调整think-view依赖', - '发布新版本', - ]; - - public const UPDATE_TIPS = [ - '本次更新调整了composer依赖,请根据实际情况调整', - '可以删除app下admin的view目录了', - '删除 think-view', - '引入 topthink/think-template-view', - ]; - - 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'); - } - } } diff --git a/app/common/command/curd/Migrate.php b/app/common/command/curd/Migrate.php index 36ce691..a360bb1 100644 --- a/app/common/command/curd/Migrate.php +++ b/app/common/command/curd/Migrate.php @@ -4,220 +4,8 @@ declare(strict_types=1); namespace app\common\command\curd; -use think\console\Command; -use think\console\Input; -use think\console\input\Argument; -use think\console\input\Option; -use think\console\Output; -use think\facade\App; -use think\facade\Db; -use think\facade\View; +use base\common\command\curd\MigrateBase; -class Migrate extends Command +class Migrate extends MigrateBase { - 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', " 'desc', - ]; - - /** - * 允许修改的字段. - * @var array - */ - protected $allowModifyFields = [ - 'status', - 'sort', - 'remark', - 'is_delete', - 'is_auth', - 'title', - ]; - - /** - * 不导出的字段信息. - * @var array - */ - protected $noExportFields = ['delete_time', 'update_time']; - - /** - * 下拉选择条件. - * @var array - */ - protected $selectWhere = []; - - /** - * 是否关联查询. - * @var bool - */ - protected $relationSearch = false; - - /** - * 模板布局, false取消. - * @var string|bool - */ - protected $layout = 'layout/default'; - - /** - * 是否为演示环境. - * @var bool - */ - protected $isDemo = false; - - /** - * 多元传参 - * - * @var array - */ - private $dataBrage = []; - - protected $validateRule = null; - - protected $validateMessage = []; - - protected $batchValidate = false; - - /** - * 导出文件的名称,支持中文,为空则获取模型名称. - * - * @var string - */ - protected $exportFileName = null; - - /** - * 当前登陆的管理员. - * @var SystemAdmin - */ - protected $sessionAdmin; - - /** - * 初始化方法. - */ - protected function initialize() - { - parent::initialize(); - $this->layout && $this->app->view->engine()->layout($this->layout); - $this->isDemo = Env::get('adminsystem.is_demo', false); - $this->viewInit(); - $this->checkAuth(); - - $this->initSort(); - } - - public function initSort() - { - $sort = $this->request->param('sort'); - - if (!empty($sort)) { - $this->sort = $sort; - } - } - - /** - * 模板变量赋值 - * @param string|array $name 模板变量 - * @param mixed $value 变量值 - * @return mixed - */ - public function assign($name, $value = null, $isAppendToDataBrage = false) - { - if ($isAppendToDataBrage) { - $this->dataBrage[$name] = $value; - } - - return $this->app->view->assign($name, $value); - } - - public function fetchJS($template = '') - { - $content_js = ''; - - $common_template = '_common'; - - if (!empty($template)) { - $template_arr = explode('/', $template); - unset($template_arr[count($template_arr) - 1]); - $template_arr[] = '_common'; - $common_template = implode('/', $template_arr); - } - - try { - $content_js .= View::layout(false)->fetchJS($common_template); - $content_js .= View::layout(false)->fetchJS($template); - } catch (TemplateNotFoundException $th) { - if (Env::get('adminsystem.strict_view_js', true)) { - throw $th; - } - } - - return ""; - } - - /** - * 解析和获取模板内容 用于输出. - * @param string $template - * @param array $vars - * @return mixed - */ - public function fetch($template = '', $vars = []) - { - $this->assign('data_brage', json_encode($this->dataBrage)); - - $vars['content_js'] = $this->fetchJS($template); - - $content_main = View::layout($this->layout) - ->config([ - 'view_suffix' => 'html', - ])->fetch($template, $vars); - - $html = ''; - $html .= $content_main; - - return $html; - } - - /** - * 设置dataBrage数据. - * - * @param string $name - * @param mixed $value - * @return void - */ - public function setDataBrage($name, $value) - { - $this->dataBrage[$name] = $value; - - return $this; - } - - /** - * 重写验证规则. - * @param array $data - * @param array|string $validate - * @param array $message - * @param bool|null $batch - * @return array|bool|string|true - */ - public function validate(array $data, $validate, array $message = [], bool $batch = null) - { - try { - $message = array_merge($this->validateMessage, $message); - - if (is_null($batch)) { - $batch = $this->batchValidate; - } - - if ($this->validateRule instanceof Validate) { - $this->validateRule->message($message); - - // 是否批量验证 - if ($batch) { - $this->validateRule->batch(true); - } - - $this->validateRule->failException(true)->check($data); - } elseif (is_array($this->validateRule)) { - parent::validate($data, $this->validateRule, $message, $batch); - } else { - parent::validate($data, $validate, $message, $batch); - } - } catch (\Exception $e) { - $this->error($e->getMessage()); - } - - return true; - } - - /** - * 构建请求参数. - * @param array $excludeFields 忽略构建搜索的字段 - * @return array - */ - protected function buildTableParames($excludeFields = []) - { - $get = $this->request->get('', null); - - $page = isset($get['page']) && !empty($get['page']) ? $get['page'] : 1; - $limit = isset($get['limit']) && !empty($get['limit']) ? $get['limit'] : 15; - $group = isset($get['group']) && !empty($get['group']) ? $get['group'] : null; - $filters = isset($get['filter']) && !empty($get['filter']) ? $get['filter'] : '{}'; - $ops = isset($get['op']) && !empty($get['op']) ? $get['op'] : '{}'; - // json转数组 - $filters = json_decode($filters, true) ?? []; - $ops = json_decode($ops, true); - $where = []; - $excludes = []; - - $request_options = []; - - // 判断是否关联查询 - $tableName = \think\helper\Str::snake(lcfirst($this->model->getName())); - - foreach ($filters as $key => $val) { - if (in_array($key, $excludeFields)) { - $excludes[$key] = $val; - continue; - } - $op = isset($ops[$key]) && !empty($ops[$key]) ? $ops[$key] : '%*%'; - - if (strpos($key, '[') === 0) { - $key = str_replace('[', '', $key); - - $key = explode(']', $key)[0]; - } - - if ($this->relationSearch && count(explode('.', $key)) == 1) { - $key = "{$tableName}.{$key}"; - } - switch (strtolower($op)) { - case '=': - $where[] = [$key, '=', $val]; - break; - case '%*%': - $where[] = [$key, 'LIKE', "%{$val}%"]; - break; - case '*%': - $where[] = [$key, 'LIKE', "{$val}%"]; - break; - case '%*': - $where[] = [$key, 'LIKE', "%{$val}"]; - case 'in': - $where[] = [$key, 'IN', "{$val}"]; - break; - case 'min': - $where[] = [$key, '>=', $val]; - break; - case 'max': - $where[] = [$key, '<=', $val]; - break; - case 'min_date': - $where[] = [$key, '>=', strtotime($val)]; - break; - case 'max_date': - $where[] = [$key, '<=', strtotime($val)]; - break; - case 'range': - [$beginTime, $endTime] = explode(' - ', $val); - $where[] = [$key, '>=', strtotime($beginTime)]; - $where[] = [$key, '<=', strtotime($endTime)]; - break; - case 'none': - $request_options[$key] = $val; - break; - default: - $where[] = [$key, $op, "%{$val}"]; - } - } - - return [$page, $limit, $where, $excludes, $request_options, $group]; - } - - /** - * 下拉选择列表. - * @return \think\response\Json - */ - public function selectList() - { - $fields = input('selectFields'); - $data = $this->model - ->where($this->selectWhere) - ->field($fields) - ->select(); - $this->success(null, $data); - } - - /** - * 初始化视图参数. - */ - private function viewInit() - { - $request = app()->request; - list($thisModule, $thisController, $thisAction) = [app('http')->getName(), app()->request->controller(), $request->action()]; - list($thisControllerArr, $jsPath) = [explode('.', $thisController), null]; - foreach ($thisControllerArr as $vo) { - empty($jsPath) ? $jsPath = parse_name($vo) : $jsPath .= '/' . parse_name($vo); - } - $autoloadJs = file_exists(root_path('public') . "static/{$thisModule}/js/{$jsPath}.js") ? true : false; - $thisControllerJsPath = "{$thisModule}/js/{$jsPath}.js"; - $adminModuleName = config('app.admin_alias_name'); - $isSuperAdmin = session('admin.id') == AdminConstant::SUPER_ADMIN_ID ? true : false; - $data = [ - 'adminModuleName' => $adminModuleName, - 'thisController' => parse_name($thisController), - 'thisAction' => $thisAction, - 'thisRequest' => parse_name("{$thisModule}/{$thisController}/{$thisAction}"), - 'thisControllerJsPath' => "{$thisControllerJsPath}", - 'autoloadJs' => $autoloadJs, - 'isSuperAdmin' => $isSuperAdmin, - 'version' => env('app_debug') ? time() : sysconfig('site', 'site_version'), - ]; - - View::assign($data); - } - - /** - * 检测权限. - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - protected function checkAuth($currentNode = null, $haltRequest = true) - { - $adminConfig = config('admin'); - $adminId = session('admin.id'); - $expireTime = session('admin.expire_time'); - /** @var AuthService $authService */ - $authService = app(AuthService::class, ['adminId' => $adminId]); - - $currentNode = $authService->getCurrentNode(); - if (is_null($currentNode)) { - $currentNode = $authService->getCurrentNode(); - } - - $currentController = parse_name(app()->request->controller()); - - // 验证登录 - if ( - !in_array($currentController, $adminConfig['no_login_controller']) && - !in_array($currentNode, $adminConfig['no_login_node']) - ) { - empty($adminId) && $this->error('请先登录后台', [], __url('admin/login/index')); - - // 判断是否登录过期 - if ($expireTime !== true && time() > $expireTime) { - session('admin', null); - $this->error('登录已过期,请重新登录', [], __url('admin/login/index')); - } - } - - // 验证权限 - if ( - !in_array($currentController, $adminConfig['no_auth_controller']) && - !in_array($currentNode, $adminConfig['no_auth_node']) - ) { - $check = $authService->checkNode($currentNode); - - if ($haltRequest) { - if (!$check) { - $this->error('无权限访问'); - } - } else { - return $check; - } - - // 判断是否为演示环境 - if (env('adminsystem.is_demo', false) && app()->request->isPost()) { - $this->error('演示环境下不允许修改'); - } - } - - $model_admin = SystemAdmin::autoCache('read', $adminId)->find($adminId); - - $this->sessionAdmin = $model_admin; - - $this->assign('session_admin', $model_admin); - } - - /** - * 严格校验接口是否为POST请求 - */ - protected function checkPostRequest() - { - if (!$this->request->isPost()) { - $this->error('当前请求不合法!'); - } - } } diff --git a/app/common/controller/TimerController.php b/app/common/controller/TimerController.php index 9c05f06..ab6fff1 100644 --- a/app/common/controller/TimerController.php +++ b/app/common/controller/TimerController.php @@ -2,37 +2,8 @@ namespace app\common\controller; -use think\facade\Cache; +use base\common\controller\TimerControllerBase; -class TimerController extends ToolsController +class TimerController extends TimerControllerBase { - - protected $frequency = null; - - public function initialize() - { - - parent::initialize(); - - if (is_integer($this->frequency)) { - $this->protectVisit($this->frequency); - } - } - - protected function protectVisit(int $frequency) - { - - $cache_tag = 'timer_protect'; - - $cache_key = 'timer_protect_' . md5($this->request->url()); - - $last_exec_time = Cache::get($cache_key, 0); - - if ($last_exec_time >= time() - $frequency) { - - return $this->error('请不要频繁请求'); - } - - Cache::tag($cache_tag)->set($cache_key, time()); - } } diff --git a/app/common/controller/ToolsController.php b/app/common/controller/ToolsController.php index aab4cdc..3380607 100644 --- a/app/common/controller/ToolsController.php +++ b/app/common/controller/ToolsController.php @@ -2,10 +2,8 @@ namespace app\common\controller; -use app\BaseController; -use app\common\traits\JumpTrait; +use base\common\controller\ToolsControllerBase; -class ToolsController extends BaseController +class ToolsController extends ToolsControllerBase { - use JumpTrait; } diff --git a/app/common/event/AdminLoginSuccess/LogEvent.php b/app/common/event/AdminLoginSuccess/LogEvent.php index 3f7a82d..319a50a 100644 --- a/app/common/event/AdminLoginSuccess/LogEvent.php +++ b/app/common/event/AdminLoginSuccess/LogEvent.php @@ -2,14 +2,8 @@ namespace app\common\event\AdminLoginSuccess; -use app\admin\model\SystemAdmin; -use think\facade\Log; +use base\common\event\AdminLoginSuccess\LogEventBase; -class LogEvent +class LogEvent extends LogEventBase { - public function handle(SystemAdmin $system_admin) - { - // 事件监听处理 - Log::report("admin login success,{$system_admin->username}"); - } } diff --git a/app/common/event/AdminLoginType/DemoEvent.php b/app/common/event/AdminLoginType/DemoEvent.php index 6eb7bd9..e805185 100644 --- a/app/common/event/AdminLoginType/DemoEvent.php +++ b/app/common/event/AdminLoginType/DemoEvent.php @@ -2,22 +2,8 @@ namespace app\common\event\AdminLoginType; -use think\facade\Env; -use think\facade\View; +use base\common\event\AdminLoginType\DemoEventBase; -class DemoEvent +class DemoEvent extends DemoEventBase { - public function handle() - { - $content = ''; - - if (Env::get('adminsystem.is_demo', false)) { - $content = View::layout(false)->fetch('login/ext/demo'); - } - - // 事件监听处理 - return [ - 'view_content' => $content, - ]; - } } diff --git a/app/common/exception/EventException.php b/app/common/exception/EventException.php index f43ae88..553c2fa 100644 --- a/app/common/exception/EventException.php +++ b/app/common/exception/EventException.php @@ -2,8 +2,8 @@ namespace app\commno\exception; -use Exception; +use extend\base\commno\exception\EventExceptionBase; -class EventException extends Exception +class EventException extends EventExceptionBase { } diff --git a/app/common/model/BaseModel.php b/app/common/model/BaseModel.php index 279201b..7c05f6d 100644 --- a/app/common/model/BaseModel.php +++ b/app/common/model/BaseModel.php @@ -2,106 +2,8 @@ namespace app\common\model; -use think\facade\Cache; -use think\facade\Env; -use think\facade\Log; -use think\Model; -use think\helper\Str; +use base\common\model\BaseModelBase; -class BaseModel extends Model +class BaseModel extends BaseModelBase { - /** - * 自动清除的缓存值 - * - * [ - * 'name'=>'', - * 'type'=>'', // tag/key - * 'field'=>'' // 为空则不做拼接 - * ] - * - * @var array - */ - public static $autoCache = []; - - public static function onAfterWrite($model) - { - static::autoRemoveCache($model); - } - public static function onAfterDelete($model) - { - static::autoRemoveCache($model); - } - public static function onAfterRestore($model) - { - static::autoRemoveCache($model); - } - public static function autoRemoveCache($model) - { - - - if(!isset(static::$autoCache['table'])){ - static::$autoCache['table'] = [ - 'name' => 'table', - 'type' => 'tag' - ]; - } - - if(!isset(static::$autoCache['read'])){ - static::$autoCache['read'] = [ - 'name' => 'read', - 'field' => 'id' - ]; - } - - - foreach (static::$autoCache as $cache_item) { - $type = $cache_item['type'] ?? 'key'; - - $field = $cache_item['field'] ?? ''; - - $cache_key = $cache_item['name'] ?? ''; - - if (empty($cache_key)) { - continue; - } - - $cache_key = Str::snake($model->getName()) . '_' . $cache_key; - - - if (!empty($field) && !is_null($model->$field)) { - // 用字段新的值拼接key - $cache_key_attr = $cache_key . '_' . $model->getAttr($field); - - static::doRemoveCache($type, $cache_key_attr); - - // 用字段旧的值拼接key - $cache_key_original = $cache_key . '_' . $model->getOrigin($field); - - if ($cache_key_original != $cache_key_attr) { - static::doRemoveCache($type, $cache_key_original); - } - } else { - static::doRemoveCache($type, $cache_key); - } - } - } - - protected static function doRemoveCache($type, $cache_key) - { - - if (Env::get('app.auto_cache_log')) { - Log::debug("清除 ORM 自动缓存 {$type}:{$cache_key}"); - } - - if ($type == 'key') { - Cache::delete($cache_key); - } else { - Cache::tag($cache_key)->clear(); - } - } - - public static function getClassNameKey() - { - return str_replace('\\', '_', static::class); - } } diff --git a/app/common/model/TimeModel.php b/app/common/model/TimeModel.php index 14f4b7d..60a6fb9 100644 --- a/app/common/model/TimeModel.php +++ b/app/common/model/TimeModel.php @@ -1,44 +1,13 @@ config(['layout_on' => false]); - } else { - // 开启布局 - $this->config(['layout_on' => true]); - - // 名称必须为字符串 - if (is_string($name)) { - $this->config(['layout_name' => $name]); - } - - if (!empty($replace)) { - $this->config(['layout_item' => $replace]); - } - } - - return $this; - } - - /** - * 配置模板引擎. - * @param array $config 参数 - * @return $this - */ - public function config(array $config) - { - $this->driver()->config($config); - - return $this; - } - - /** - * 解析和获取模板内容 用于输出. - * @param string $template 模板文件名或者内容 - * @param array $vars 模板变量 - * @return string - * @throws \Exception - */ - public function fetch(string $template = '', array $vars = []): string - { - return $this->config([ - 'view_suffix' => 'html', - ])->getContent(function () use ($vars, $template) { - $this->engine()->fetch($template, array_merge($this->data, $vars)); - }); - } - - /** - * 解析和获取模板内容 用于输出. - * @param string $template 模板文件名或者内容 - * @param array $vars 模板变量 - * @return string - * @throws \Exception - */ - public function fetchJS(string $template = '', array $vars = []): string - { - return $this->config([ - 'view_suffix' => 'js', - ])->getContent(function () use ($vars, $template) { - $this->engine()->fetch($template, array_merge($this->data, $vars)); - }); - } } diff --git a/app/common/provider/db/Query.php b/app/common/provider/db/Query.php index db171a8..8e81d34 100644 --- a/app/common/provider/db/Query.php +++ b/app/common/provider/db/Query.php @@ -2,59 +2,8 @@ namespace app\common\provider\db; -use think\db\Query as DbQuery; -use think\facade\Env; -use think\facade\Log; -use think\helper\Str; +use base\common\provider\db\QueryBase; -class Query extends DbQuery +class Query extends QueryBase { - /** - * autoCache 自动生成缓存. - * - * @param null|string $key - * @param null|string|int $field_key_value - * @param null|string $tag - * @param null|string|int $field_tag_value - * @return void - */ - public function autoCache($key = null, $field_key_value = null, $tag = null, $field_tag_value = null) - { - $table_name = Str::snake($this->getName()); - - if (is_null($key)) { - $key = $this->getOptionsMd5(); - } - - if (!is_null($field_key_value)) { - $key = $key . '_' . $field_key_value; - } - $key = $table_name . '_' . $key; - - if (!is_null($tag)) { - if (!is_null($field_tag_value)) { - $tag = $tag . '_' . $field_tag_value; - } - - $tag = $table_name . '_' . $tag; - } - - if (Env::get('app.auto_cache_log')) { - Log::debug('use auto cache:' . $key); - Log::debug('use auto cache tag:' . $tag); - } - - $this->cache($key, null, $tag); - - return $this; - } - - public function getOptionsMd5() - { - $options = $this->getOptions(); - - // TODO:支持获取回调函数的设置 - - return md5(json_encode($options)); - } } diff --git a/app/common/service/AuthService.php b/app/common/service/AuthService.php index 1542b78..5246c9a 100644 --- a/app/common/service/AuthService.php +++ b/app/common/service/AuthService.php @@ -1,211 +1,13 @@ true, // 权限开关 - 'system_admin' => 'system_admin', // 用户表 - 'system_auth' => 'system_auth', // 权限表 - 'system_node' => 'system_node', // 节点表 - 'system_auth_node' => 'system_auth_node', // 权限-节点表 - ]; - - /** - * 管理员信息 - * @var array|\think\Model|null - */ - protected $adminInfo; - - /** - * 所有节点信息 - * @var array - */ - protected $nodeList; - - /** - * 管理员所有授权节点 - * @var array - */ - protected $adminNode; - - /*** - * 构造方法 - * AuthService constructor. - * @param null $adminId - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function __construct($adminId = null) - { - $this->adminId = $adminId; - $this->adminInfo = $this->getAdminInfo(); - $this->nodeList = $this->getNodeList(); - $this->adminNode = $this->getAdminNode(); - return $this; - } - - /** - * 检测检测权限 - * @param null $node - * @return bool - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function checkNode($node = null) - { - // 判断是否为超级管理员 - if ($this->adminId == AdminConstant::SUPER_ADMIN_ID) { - return true; - } - // 判断权限验证开关 - if ($this->config['auth_on'] == false) { - return true; - } - // 判断是否需要获取当前节点 - if (empty($node)) { - $node = $this->getCurrentNode(); - } else { - $node = $this->parseNodeStr($node); - } - // 判断是否加入节点控制,优先获取缓存信息 - if (!isset($this->nodeList[$node])) { - - return Config::get('admin.default_auth_check'); - } - $nodeInfo = $this->nodeList[$node]; - if ($nodeInfo['is_auth'] == 0) { - return true; - } - // 用户验证,优先获取缓存信息 - if (empty($this->adminInfo) || $this->adminInfo['status'] != 1 || empty($this->adminInfo['auth_ids'])) { - return false; - } - // 判断该节点是否允许访问 - if (in_array($node, $this->adminNode)) { - return true; - } - return false; - } - - /** - * 获取当前节点 - * @return string - */ - public function getCurrentNode() - { - $node = $this->parseNodeStr(request()->controller() . '/' . request()->action()); - return $node; - } - - /** - * 获取当前管理员所有节点 - * @return array - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function getAdminNode() - { - $nodeList = []; - $adminInfo = $this->getAdminInfo(); - - if (!empty($adminInfo) && $adminInfo['status'] != 1) { - return $nodeList; - } - - if (!empty($adminInfo) && !empty($adminInfo['auth_ids'])) { - $buildAuthSql = Db::name($this->config['system_auth']) - ->distinct(true) - ->whereIn('id', $adminInfo['auth_ids']) - ->field('id') - ->buildSql(true); - $buildAuthNodeSql = Db::name($this->config['system_auth_node']) - ->distinct(true) - ->where("auth_id IN {$buildAuthSql}") - ->field('node_id') - ->buildSql(true); - $nodeList = Db::name($this->config['system_node']) - ->distinct(true) - ->where("id IN {$buildAuthNodeSql}") - ->column('node'); - } - return $nodeList; - } - - /** - * 获取所有节点信息 - * @time 2021-01-07 - * @return array - * @author zhongshaofa - */ - public function getNodeList() - { - return Db::name($this->config['system_node']) - ->autoCache(null, null, 'table') - ->column('id,node,title,type,is_auth', 'node'); - } - - /** - * 获取管理员信息 - * @time 2021-01-07 - * @return array|\think\Model|null - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - * @author zhongshaofa - */ - public function getAdminInfo() - { - return Db::name($this->config['system_admin']) - ->where('id', $this->adminId) - ->autoCache('info', $this->adminId) - ->find(); - } - - /** - * 驼峰转下划线规则 - * @param string $node - * @return string - */ - public function parseNodeStr($node) - { - $array = explode('/', $node); - foreach ($array as $key => $val) { - if ($key == 0) { - $val = explode('.', $val); - foreach ($val as &$vo) { - $vo = \think\helper\Str::snake(lcfirst($vo)); - } - $val = implode('.', $val); - $array[$key] = $val; - } - } - $node = implode('/', $array); - return $node; - } } diff --git a/app/common/service/MenuService.php b/app/common/service/MenuService.php index e566a1e..06917ab 100644 --- a/app/common/service/MenuService.php +++ b/app/common/service/MenuService.php @@ -1,107 +1,9 @@ adminId = $adminId; - return $this; - } - - /** - * 获取首页信息 - * @return array|\think\Model|null - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function getHomeInfo() - { - $data = Db::name('system_menu') - ->field('title,icon,href') - ->where("delete_time", 0) - ->where('pid', MenuConstant::HOME_PID) - ->find(); - !empty($data) && $data['href'] = __url($data['href']); - return $data; - } - - /** - * 获取后台菜单树信息 - * @return mixed - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function getMenuTree() - { - /** @var AuthService $authService */ - $authServer = app(AuthService::class, ['adminId' => $this->adminId]); - return $this->buildMenuChild(0, $this->getMenuData(), $authServer); - } - - private function buildMenuChild($pid, $menuList, AuthService $authServer) - { - $treeList = []; - foreach ($menuList as &$v) { - $check = false; - if (!empty($v['auth_node'])) { - $check = $authServer->checkNode($v['auth_node']); - } else if (!empty($v['href'])) { - $check = $authServer->checkNode($v['href']); - } else { - $check = true; - } - - !empty($v['href']) && $v['href'] = __url($v['href']); - if ($pid == $v['pid'] && $check) { - $node = $v; - $child = $this->buildMenuChild($v['id'], $menuList, $authServer); - if (!empty($child)) { - $node['child'] = $child; - } - if (!empty($v['href']) || !empty($child)) { - $treeList[] = $node; - } - } - } - return $treeList; - } - - /** - * 获取所有菜单数据 - * @return \think\Collection - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - protected function getMenuData() - { - $menuData = Db::name('system_menu') - ->field('id,pid,title,icon,href,target') - ->where("delete_time", 0) - ->where([ - ['status', '=', '1'], - ['pid', '<>', MenuConstant::HOME_PID], - ]) - ->order([ - 'sort' => 'desc', - 'id' => 'asc', - ]) - ->select(); - return $menuData; - } } diff --git a/app/common/service/UploadService.php b/app/common/service/UploadService.php index 8641ded..8ee68eb 100644 --- a/app/common/service/UploadService.php +++ b/app/common/service/UploadService.php @@ -2,173 +2,8 @@ namespace app\common\service; -use app\admin\model\SystemUploadfile; -use app\common\tools\PathTools; -use think\exception\ValidateException; -use think\facade\App; -use think\facade\Filesystem; -use think\facade\Validate; -use think\File; -use think\file\UploadedFile; +use base\common\service\UploadServiceBase; -class UploadService +class UploadService extends UploadServiceBase { - - protected $uploadType = 'local_public'; - - public function __construct($upload_type = null) - { - $uploadConfig = sysconfig('upload'); - - empty($upload_type) && $upload_type = $uploadConfig['upload_type']; - - $this->uploadType = $upload_type; - } - - public function validate(File $file, $allow_ext = null, $allow_size = null, $fail_exception = false) - { - $uploadConfig = sysconfig('upload'); - - if (!is_null($allow_ext)) { - $uploadConfig['upload_allow_ext'] = $allow_ext; - } - - if (!is_null($allow_size)) { - $uploadConfig['upload_allow_size'] = $allow_size; - } - - $rule = [ - 'upload_type|指定上传类型有误' => "in:{$uploadConfig['upload_allow_type']}", - 'file|文件' => "require|file|fileExt:{$uploadConfig['upload_allow_ext']}|fileSize:{$uploadConfig['upload_allow_size']}", - ]; - - $validat_result = Validate::failException($fail_exception)->check([ - 'upload_type' => $this->uploadType, - 'file' => $file - ], $rule); - - if (!$validat_result) { - return $validat_result; - } - - - // 出于性能原因,您可以注释掉下面的代码 - $file_path = $file->getRealPath(); - - if (strpos(file_get_contents($file_path), 'validate($file, $allow_ext, $allow_size, true); - } - - public function url($save_name) - { - $url = Filesystem::disk($this->uploadType)->url($save_name); - return $url; - } - - /** - * 存储文件 - * - * @param File $file - * @param string|null $save_name - * @param boolean $force_save 指定$save_name才可以用,设为true强制写入(不报错,如果存在则删除),否则是驱动的默认行为(覆盖、失败、异常) - * @param string $upload_dir - * @param bool $disable_model - * @return mixed - */ - public function save(File $file, string $save_name = null, $force_save = false, $upload_dir = 'upload', $disable_model = false) - { - - $model_file = null; - - if ($force_save && !is_null($save_name)) { - $file_path = $upload_dir . '/' . $save_name; - if (Filesystem::disk($this->uploadType)->has($file_path)) { - $model_file = SystemUploadfile::where('save_name', $save_name)->where('upload_type', $this->uploadType)->find(); - Filesystem::disk($this->uploadType)->delete($file_path); - } - } - - if (empty($model_file)) { - $model_file = new SystemUploadfile(); - } - - $model_file->upload_type = $this->uploadType; - $model_file->file_ext = strtolower($file->extension()); - - - if ($file instanceof UploadedFile) { - - $model_file->original_name = $file->getOriginalName(); - $model_file->mime_type = $file->getOriginalMime(); - } else { - $model_file->original_name = $file->getFilename(); - $model_file->mime_type = $file->getMime(); - } - - - $save_name = Filesystem::disk($this->uploadType)->putFile($upload_dir, $file, function () use ($save_name, $file) { - if (!is_null($save_name)) { - - $ext_name = $file->extension(); - - if (empty($ext_name)) { - return $save_name; - } - - $list_name = explode('.', $save_name); - - array_pop($list_name); - - return implode('.', $list_name); - } - return date('Ymd') . '/' . uniqid(); - }); - - $url = $this->url($save_name); - - $model_file->url = $url; - $model_file->save_name = $save_name; - - $model_file->sha1 = $file->sha1(); - - $model_file->file_size = $file->getSize(); - - if (!$disable_model) { - $model_file->save(); - } - return $model_file; - } - - public function saveUrlFile($url, string $save_name = null, $force_save = false, $upload_dir = 'upload', $disable_model = false) - { - - $runtime_path = App::getRuntimePath() . 'upload/temp/' . basename($url); - - PathTools::intiDir($runtime_path); - - - $file_content = file_get_contents($url); - - file_put_contents($runtime_path, $file_content); - - $file = new File($runtime_path); - - $response = $this->save($file, $save_name, $force_save, $upload_dir, $disable_model); - - unlink($runtime_path); - - return $response; - } } diff --git a/app/common/tools/ExportTools.php b/app/common/tools/ExportTools.php index 27c8d97..855bdc4 100644 --- a/app/common/tools/ExportTools.php +++ b/app/common/tools/ExportTools.php @@ -2,113 +2,8 @@ namespace app\common\tools; -use PhpOffice\PhpSpreadsheet\Cell\DataType; +use base\common\tools\ExportToolsBase; -class ExportTools +class ExportTools extends ExportToolsBase { - public static function excel($model, $where = [], $fields = [], $image_fields = [], $select_fields = [], $date_fields = []) - { - - $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); - - $write_col = 1; - $write_line = 1; - foreach ($fields as $field_key => $field_name) { - $col_key = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($write_col); - $sheet->setCellValue($col_key . $write_line, $field_name); - $sheet->getColumnDimension($col_key)->setWidth(mb_strlen($field_name) * 3); - - $write_col++; - } - - $runtime_file_list = []; - - $limit = 1000; - - $page = 0; - - while (true) { - $page++; - $list = $model - ->where($where)->page($page, $limit)->select(); - foreach ($list as $list_index => $item) { - $write_line++; - $write_col = 1; - - foreach ($fields as $field_key => $field_name) { - $col_key = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($write_col); - - $cel = null; - - $value = \think\helper\Arr::get($item, $field_key); - - if (in_array($field_key, $image_fields)) { - // 是图片 - $cel = $value; - - try { - - if (filter_var($value, FILTER_VALIDATE_URL)) { - - $runtime_file = PathTools::tempBuildPath(uniqid()); - - $runtime_file_list[] = $runtime_file; - - file_put_contents($runtime_file, file_get_contents($value)); - - $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $drawing->setName($field_name); - $drawing->setDescription($field_key); - $drawing->setPath($runtime_file); - $drawing->setHeight(36); - - $drawing->setCoordinates($col_key . $write_line); - $drawing->setWorksheet($sheet); - } - } catch (\Throwable $th) { - $message = $th->getMessage(); - - $cel .= "\n" . $message; - } - } else if (array_key_exists($field_key, $select_fields)) { - // 需要设置选项 - - $cel = $select_fields[$field_key][$value] ?? $value; - } else if (in_array($field_key, $date_fields)) { - if (empty($value)) { - $cel = ''; - } else { - $cel = date('Y-m-d H:i:s', $value); - } - } else { - $cel = $value; - } - - $sheet->setCellValueExplicit($col_key . $write_line, $cel, DataType::TYPE_STRING); - $write_col++; - } - } - - if (count($list) < $limit) { - break; - } - } - - - $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); - - ob_start(); - $writer->save('php://output'); - $content = ob_get_contents(); - ob_clean(); - - foreach ($runtime_file_list as $runtime_file) { - if (file_exists($runtime_file)) { - unlink($runtime_file); - } - } - - return $content; - } } diff --git a/app/common/tools/PathTools.php b/app/common/tools/PathTools.php index 8bbccd0..333aa10 100644 --- a/app/common/tools/PathTools.php +++ b/app/common/tools/PathTools.php @@ -2,161 +2,8 @@ namespace app\common\tools; -use Localheinz\Diff\Differ; -use think\facade\App; +use base\common\tools\PathToolsBase; -class PathTools +class PathTools extends PathToolsBase { - /** - * 系统生成的文件,这些文件应当是可以任意删除的. - * - * @param string $file_name - * @return string - */ - public static function publicBuildPath($file_name) - { - $file_path = App::getRootPath() . 'public/build/' . $file_name; - - return self::intiDir($file_path); - } - - public static function publicBuildSaveName($file_name) - { - return '/build/' . $file_name; - } - - public static function safeBuildPath($save_name) - { - $file_path = App::getRootPath() . 'storage/' . $save_name; - - return self::intiDir($file_path); - } - - public static function tempBuildPath($file_name) - { - $runtime_path = App::getRuntimePath() . 'temp/' . $file_name; - - return self::intiDir($runtime_path); - } - - public static function intiDir($file_path, $is_dirname = false) - { - if (!$is_dirname) { - $dir_name = dirname($file_path); - } else { - $dir_name = $file_path; - } - - if (!is_dir($dir_name)) { - mkdir($dir_name, 0777, true); - } - - return $file_path; - } - - public static function removeDir($dir_name) - { - if (!is_dir($dir_name)) { - return false; - } - - if (strpos(strtolower(PHP_OS), 'win') === 0) { - $dir_name = static::formatWinPath($dir_name); - exec("rd /s /q {$dir_name}", $output); - - return; - } - - $handle = opendir($dir_name); - while (false !== ($file = readdir($handle))) { - if ($file != '.' && $file != '..') { - is_dir("$dir_name/$file") ? self::removeDir("$dir_name/$file") : unlink("$dir_name/$file"); - } - } - closedir($handle); - - return rmdir($dir_name); - } - - public static function mapDir($dir, $callback = null) - { - $result = []; - $cdir = scandir($dir); - foreach ($cdir as $key => $value) { - if (!in_array($value, ['.', '..'])) { - $current_path = $dir . DS . $value; - if (is_dir($current_path)) { - $result[$value] = self::mapDir($current_path, $callback); - } else { - if (is_callable($callback)) { - $result[$value] = $callback($current_path, $value, $dir); - } else { - $result[$value] = $current_path; - } - } - } - } - - return $result; - } - - public static function formatWinPath($content) - { - return str_replace('/', '\\', $content); - } - - public static function compareFiles($a, $b, $return_diff = false):bool|string - { - $result = true; - - // Check if filesize is different - if (filesize($a) !== filesize($b)) { - $result = false; - } - - if ($result) { - // Check if content is different - $ah = fopen($a, 'rb'); - $bh = fopen($b, 'rb'); - - while (!feof($ah)) { - if (fread($ah, 8192) != fread($bh, 8192)) { - $result = false; - break; - } - } - - fclose($ah); - fclose($bh); - } - - if (!$result) { - // 如果前面的方法认为文件变化,那么以文本变化的方式识别是否变化,并给出变化的内容 - $text_mime_type = []; - $text_mime_type[] = 'text/html'; - $text_mime_type[] = 'text/plain'; - $text_mime_type[] = 'text/css'; - $text_mime_type[] = 'image/svg+xml'; - $text_mime_type[] = 'text/x-php'; - $text_mime_type[] = 'application/json'; - $text_mime_type[] = 'application/x-wine-extension-ini'; - - if (in_array(mime_content_type($a), $text_mime_type) && in_array(mime_content_type($b), $text_mime_type)) { - $a_content = file_get_contents($a); - $b_content = file_get_contents($b); - - $diff = new Differ(); - - $diff_content = $diff->diff($a_content, $b_content); - - if (!empty($diff_content)) { - $result = $return_diff ? $diff_content : false; - } else { - $result = true; - } - } - } - - return $result; - } } diff --git a/app/common/traits/JumpTrait.php b/app/common/traits/JumpTrait.php index 1e8ed2c..287757d 100644 --- a/app/common/traits/JumpTrait.php +++ b/app/common/traits/JumpTrait.php @@ -2,148 +2,12 @@ namespace app\common\traits; -use think\exception\HttpResponseException; -use think\Response; +use base\common\traits\JumpTraitBase; /** - * Trait JumpTrait - * @package app\common\traits + * Trait JumpTrait. */ trait JumpTrait { - - /** - * 操作成功跳转的快捷方法 - * @access protected - * @param mixed $msg 提示信息 - * @param mixed $data 返回的数据 - * @param string $url 跳转的 URL 地址 - * @param int $wait 跳转等待时间 - * @param array $header 发送的 Header 信息 - * @return void - * @throws HttpResponseException - */ - protected function success($msg = '', $data = '', $url = null, $wait = 3, array $header = []) - { - if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { - $url = $_SERVER["HTTP_REFERER"]; - } elseif ($url) { - $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : app('route')->buildUrl($url)->__toString(); - } - - $result = [ - 'code' => 1, - 'msg' => $msg, - 'data' => $data, - 'url' => $url, - 'wait' => $wait, - ]; - - $type = $this->getResponseType(); - if ($type == 'html') { - - if (is_null($result['url'])) { - $result['url'] = ''; - } - - $response = view(app('config')->get('app.dispatch_success_tmpl'), $result); - } elseif ($type == 'json') { - $response = json($result); - } - throw new HttpResponseException($response); - } - - /** - * 操作错误跳转的快捷方法 - * @access protected - * @param mixed $msg 提示信息 - * @param mixed $data 返回的数据 - * @param string $url 跳转的 URL 地址 - * @param int $wait 跳转等待时间 - * @param array $header 发送的 Header 信息 - * @return void - * @throws HttpResponseException - */ - protected function error($msg = '', $data = '', $url = null, $wait = 3, array $header = []) - { - if (is_null($url)) { - $url = request()->isAjax() ? '' : 'javascript:history.back(-1);'; - } elseif ($url) { - $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : app('route')->buildUrl($url)->__toString(); - } - - $type = $this->getResponseType(); - $result = [ - 'code' => 0, - 'msg' => $msg, - 'data' => $data, - 'url' => $url, - 'wait' => $wait, - ]; - if ($type == 'html') { - if (is_null($result['url'])) { - $result['url'] = ''; - } - $response = view(app('config')->get('app.dispatch_error_tmpl'), $result); - } elseif ($type == 'json') { - $response = json($result); - } - throw new HttpResponseException($response); - } - - /** - * 返回封装后的 API 数据到客户端 - * @access protected - * @param mixed $data 要返回的数据 - * @param int $code 返回的 code - * @param mixed $msg 提示信息 - * @param string $type 返回数据格式 - * @param array $header 发送的 Header 信息 - * @return void - * @throws HttpResponseException - */ - protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) - { - $result = [ - 'code' => $code, - 'msg' => $msg, - 'time' => time(), - 'data' => $data, - ]; - $type = $type ?: $this->getResponseType(); - $response = Response::create($result, $type)->header($header); - - throw new HttpResponseException($response); - } - - /** - * URL 重定向 - * @access protected - * @param string $url 跳转的 URL 表达式 - * @param array|int $params 其它 URL 参数 - * @param int $code http code - * @param array $with 隐式传参 - * @return void - * @throws HttpResponseException - */ - protected function redirect($url = [], $params = [], $code = 302) - { - if (is_integer($params)) { - $code = $params; - $params = []; - } - - $response = Response::create($url, 'redirect', $code); - throw new HttpResponseException($response); - } - - /** - * 获取当前的 response 输出类型 - * @access protected - * @return string - */ - protected function getResponseType() - { - return (request()->isJson() || request()->isAjax() || request()->isPost()) ? 'json' : 'html'; - } + use JumpTraitBase; } diff --git a/config/app.php b/config/app.php index b37a42a..5348568 100644 --- a/config/app.php +++ b/config/app.php @@ -1,4 +1,5 @@ Env::get('app.host', ''), + 'app_host' => Env::get('app.host', ''), // 应用的命名空间 - 'app_namespace' => '', + 'app_namespace' => '', // 是否启用路由 - 'with_route' => true, + 'with_route' => true, // 是否启用事件 - 'with_event' => true, + 'with_event' => true, // 开启应用快速访问 - 'app_express' => true, + 'app_express' => true, // 默认应用 - 'default_app' => 'index', + 'default_app' => 'index', // 默认时区 'default_timezone' => 'Asia/Shanghai', // 应用映射(自动多应用模式有效) - 'app_map' => [ + 'app_map' => [ Env::get('adminsystem.admin', 'admin') => 'admin', ], // 后台别名 'admin_alias_name' => Env::get('adminsystem.admin', 'admin'), // 域名绑定(自动多应用模式有效) - 'domain_bind' => [], + 'domain_bind' => [], // 禁止URL访问的应用列表(自动多应用模式有效) - 'deny_app_list' => ['common'], + 'deny_app_list' => ['common'], // 异常页面的模板文件 - 'exception_tmpl' => Env::get('app_debug') == 1 ? app()->getThinkPath() . 'tpl/think_exception.tpl' : app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'think_exception.tpl', + 'exception_tmpl' => Env::get('app_debug') == 1 ? app()->getThinkPath() . 'tpl/think_exception.tpl' : app_file_path('common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'think_exception.tpl'), // 跳转页面的成功模板文件 - 'dispatch_success_tmpl' => app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl', + 'dispatch_success_tmpl' => app_file_path('common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl'), // 跳转页面的失败模板文件 - 'dispatch_error_tmpl' => app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => app_file_path('common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl'), // 错误显示信息,非调试模式有效 - 'error_message' => '页面错误!请稍后再试~', + 'error_message' => '页面错误!请稍后再试~', // 显示错误信息 - 'show_error_msg' => false, + 'show_error_msg' => false, // 静态资源上传到OSS前缀 - 'oss_static_prefix' => Env::get('adminsystem.oss_static_prefix', 'static_ulthon_admin'), + 'oss_static_prefix' => Env::get('adminsystem.oss_static_prefix', 'static_ulthon_admin'), ]; diff --git a/config/update.php b/config/update.php index 3195144..18fba2d 100644 --- a/config/update.php +++ b/config/update.php @@ -14,11 +14,16 @@ $skip_files[] = 'README.md'; $skip_files[] = 'README.en.md'; $skip_files[] = 'composer.json'; +$skip_files[] = 'app/common/event/AdminLoginSuccess/LogEvent.php'; +$skip_files[] = 'app/common/event/AdminLoginType/DemoEvent.php'; + $config['skip_files'] = $skip_files; $skip_dir = []; + $skip_dir[] = 'runtime'; $skip_dir[] = 'vendor'; + $skip_dir[] = 'app/admin/controller'; $skip_dir[] = 'app/admin/middleware'; $skip_dir[] = 'app/admin/model'; @@ -26,20 +31,37 @@ $skip_dir[] = 'app/admin/service'; $skip_dir[] = 'app/admin/traits'; $skip_dir[] = 'app/admin/view'; +$skip_dir[] = 'app/common/controller'; +$skip_dir[] = 'app/common/command'; +$skip_dir[] = 'app/common/constants'; +$skip_dir[] = 'app/common/exception'; +$skip_dir[] = 'app/common/model'; +$skip_dir[] = 'app/common/provider'; +$skip_dir[] = 'app/common/service'; +$skip_dir[] = 'app/common/tools'; +$skip_dir[] = 'app/common/traits'; +$skip_dir[] = 'app/common/tpl'; + $config['skip_dir'] = $skip_dir; // append 如果当前版本不存在,则追加,如果存在,则不应当覆盖 // append 的文件应当在skip内部 $append_files = []; + $append_files[] = 'app/common/app/functions.php'; $append_files[] = 'app/common/app/listen.php'; $append_files[] = 'app/common/app/middleware.php'; $append_files[] = 'app/common/app/service.php'; $append_files[] = 'app/common/app/provider.php'; + +$append_files[] = 'app/common/event/AdminLoginSuccess/LogEvent.php'; +$append_files[] = 'app/common/event/AdminLoginType/DemoEvent.php'; + $config['append_files'] = $append_files; $append_dir = []; + $append_dir[] = 'app/admin/controller'; $append_dir[] = 'app/admin/middleware'; $append_dir[] = 'app/admin/model'; @@ -47,6 +69,17 @@ $append_dir[] = 'app/admin/service'; $append_dir[] = 'app/admin/traits'; $append_dir[] = 'app/admin/view'; +$append_dir[] = 'app/common/controller'; +$append_dir[] = 'app/common/command'; +$append_dir[] = 'app/common/constants'; +$append_dir[] = 'app/common/exception'; +$append_dir[] = 'app/common/model'; +$append_dir[] = 'app/common/provider'; +$append_dir[] = 'app/common/service'; +$append_dir[] = 'app/common/tools'; +$append_dir[] = 'app/common/traits'; +$append_dir[] = 'app/common/tpl'; + $config['append_dir'] = $append_dir; return $config; diff --git a/extend/base/common/command/CurdBase.php b/extend/base/common/command/CurdBase.php new file mode 100644 index 0000000..643dce0 --- /dev/null +++ b/extend/base/common/command/CurdBase.php @@ -0,0 +1,142 @@ +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; + } + } +} diff --git a/extend/base/common/command/NodeBase.php b/extend/base/common/command/NodeBase.php new file mode 100644 index 0000000..fd74bfb0 --- /dev/null +++ b/extend/base/common/command/NodeBase.php @@ -0,0 +1,64 @@ +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; + } +} diff --git a/extend/base/common/command/OssStaticBase.php b/extend/base/common/command/OssStaticBase.php new file mode 100644 index 0000000..67c6949 --- /dev/null +++ b/extend/base/common/command/OssStaticBase.php @@ -0,0 +1,49 @@ +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')); + } +} diff --git a/extend/base/common/command/TimerBase.php b/extend/base/common/command/TimerBase.php new file mode 100644 index 0000000..ab2762d --- /dev/null +++ b/extend/base/common/command/TimerBase.php @@ -0,0 +1,125 @@ +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; + } +} diff --git a/extend/base/common/command/admin/ClearBase.php b/extend/base/common/command/admin/ClearBase.php new file mode 100644 index 0000000..d272eb3 --- /dev/null +++ b/extend/base/common/command/admin/ClearBase.php @@ -0,0 +1,50 @@ +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('删除成功'); + } +} diff --git a/extend/base/common/command/admin/ResetPasswordBase.php b/extend/base/common/command/admin/ResetPasswordBase.php new file mode 100644 index 0000000..fc78719 --- /dev/null +++ b/extend/base/common/command/admin/ResetPasswordBase.php @@ -0,0 +1,48 @@ +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); + } +} diff --git a/extend/base/common/command/admin/UpdateBase.php b/extend/base/common/command/admin/UpdateBase.php new file mode 100644 index 0000000..97c3552 --- /dev/null +++ b/extend/base/common/command/admin/UpdateBase.php @@ -0,0 +1,313 @@ +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 + */ + $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); + } +} diff --git a/extend/base/common/command/admin/VersionBase.php b/extend/base/common/command/admin/VersionBase.php new file mode 100644 index 0000000..61c70bb --- /dev/null +++ b/extend/base/common/command/admin/VersionBase.php @@ -0,0 +1,72 @@ +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'); + } + } +} diff --git a/extend/base/common/command/curd/MigrateBase.php b/extend/base/common/command/curd/MigrateBase.php new file mode 100644 index 0000000..6d404f5 --- /dev/null +++ b/extend/base/common/command/curd/MigrateBase.php @@ -0,0 +1,207 @@ +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', "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(); + } +} diff --git a/extend/base/common/command/timer/config.php b/extend/base/common/command/timer/config.php new file mode 100644 index 0000000..32202e4 --- /dev/null +++ b/extend/base/common/command/timer/config.php @@ -0,0 +1,16 @@ + '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秒过后执行一次 + ], +]; diff --git a/extend/base/common/constants/AdminConstantBase.php b/extend/base/common/constants/AdminConstantBase.php new file mode 100644 index 0000000..92c4726 --- /dev/null +++ b/extend/base/common/constants/AdminConstantBase.php @@ -0,0 +1,15 @@ + 'desc', + ]; + + /** + * 允许修改的字段. + * @var array + */ + protected $allowModifyFields = [ + 'status', + 'sort', + 'remark', + 'is_delete', + 'is_auth', + 'title', + ]; + + /** + * 不导出的字段信息. + * @var array + */ + protected $noExportFields = ['delete_time', 'update_time']; + + /** + * 下拉选择条件. + * @var array + */ + protected $selectWhere = []; + + /** + * 是否关联查询. + * @var bool + */ + protected $relationSearch = false; + + /** + * 模板布局, false取消. + * @var string|bool + */ + protected $layout = 'layout/default'; + + /** + * 是否为演示环境. + * @var bool + */ + protected $isDemo = false; + + /** + * 多元传参 + * + * @var array + */ + private $dataBrage = []; + + protected $validateRule = null; + + protected $validateMessage = []; + + protected $batchValidate = false; + + /** + * 导出文件的名称,支持中文,为空则获取模型名称. + * + * @var string + */ + protected $exportFileName = null; + + /** + * 当前登陆的管理员. + * @var SystemAdmin + */ + protected $sessionAdmin; + + /** + * 初始化方法. + */ + protected function initialize() + { + parent::initialize(); + $this->layout && $this->app->view->engine()->layout($this->layout); + $this->isDemo = Env::get('adminsystem.is_demo', false); + $this->viewInit(); + $this->checkAuth(); + + $this->initSort(); + } + + public function initSort() + { + $sort = $this->request->param('sort'); + + if (!empty($sort)) { + $this->sort = $sort; + } + } + + /** + * 模板变量赋值 + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return mixed + */ + public function assign($name, $value = null, $isAppendToDataBrage = false) + { + if ($isAppendToDataBrage) { + $this->dataBrage[$name] = $value; + } + + return $this->app->view->assign($name, $value); + } + + public function fetchJS($template = '') + { + $content_js = ''; + + $common_template = '_common'; + + if (!empty($template)) { + $template_arr = explode('/', $template); + unset($template_arr[count($template_arr) - 1]); + $template_arr[] = '_common'; + $common_template = implode('/', $template_arr); + } + + try { + $content_js .= View::layout(false)->fetchJS($common_template); + $content_js .= View::layout(false)->fetchJS($template); + } catch (TemplateNotFoundException $th) { + if (Env::get('adminsystem.strict_view_js', true)) { + throw $th; + } + } + + return ""; + } + + /** + * 解析和获取模板内容 用于输出. + * @param string $template + * @param array $vars + * @return mixed + */ + public function fetch($template = '', $vars = []) + { + $this->assign('data_brage', json_encode($this->dataBrage)); + + $vars['content_js'] = $this->fetchJS($template); + + $content_main = View::layout($this->layout) + ->config([ + 'view_suffix' => 'html', + ])->fetch($template, $vars); + + $html = ''; + $html .= $content_main; + + return $html; + } + + /** + * 设置dataBrage数据. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function setDataBrage($name, $value) + { + $this->dataBrage[$name] = $value; + + return $this; + } + + /** + * 重写验证规则. + * @param array $data + * @param array|string $validate + * @param array $message + * @param bool|null $batch + * @return array|bool|string|true + */ + public function validate(array $data, $validate, array $message = [], bool $batch = null) + { + try { + $message = array_merge($this->validateMessage, $message); + + if (is_null($batch)) { + $batch = $this->batchValidate; + } + + if ($this->validateRule instanceof Validate) { + $this->validateRule->message($message); + + // 是否批量验证 + if ($batch) { + $this->validateRule->batch(true); + } + + $this->validateRule->failException(true)->check($data); + } elseif (is_array($this->validateRule)) { + parent::validate($data, $this->validateRule, $message, $batch); + } else { + parent::validate($data, $validate, $message, $batch); + } + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + + return true; + } + + /** + * 构建请求参数. + * @param array $excludeFields 忽略构建搜索的字段 + * @return array + */ + protected function buildTableParames($excludeFields = []) + { + $get = $this->request->get('', null); + + $page = isset($get['page']) && !empty($get['page']) ? $get['page'] : 1; + $limit = isset($get['limit']) && !empty($get['limit']) ? $get['limit'] : 15; + $group = isset($get['group']) && !empty($get['group']) ? $get['group'] : null; + $filters = isset($get['filter']) && !empty($get['filter']) ? $get['filter'] : '{}'; + $ops = isset($get['op']) && !empty($get['op']) ? $get['op'] : '{}'; + // json转数组 + $filters = json_decode($filters, true) ?? []; + $ops = json_decode($ops, true); + $where = []; + $excludes = []; + + $request_options = []; + + // 判断是否关联查询 + $tableName = \think\helper\Str::snake(lcfirst($this->model->getName())); + + foreach ($filters as $key => $val) { + if (in_array($key, $excludeFields)) { + $excludes[$key] = $val; + continue; + } + $op = isset($ops[$key]) && !empty($ops[$key]) ? $ops[$key] : '%*%'; + + if (strpos($key, '[') === 0) { + $key = str_replace('[', '', $key); + + $key = explode(']', $key)[0]; + } + + if ($this->relationSearch && count(explode('.', $key)) == 1) { + $key = "{$tableName}.{$key}"; + } + switch (strtolower($op)) { + case '=': + $where[] = [$key, '=', $val]; + break; + case '%*%': + $where[] = [$key, 'LIKE', "%{$val}%"]; + break; + case '*%': + $where[] = [$key, 'LIKE', "{$val}%"]; + break; + case '%*': + $where[] = [$key, 'LIKE', "%{$val}"]; + case 'in': + $where[] = [$key, 'IN', "{$val}"]; + break; + case 'min': + $where[] = [$key, '>=', $val]; + break; + case 'max': + $where[] = [$key, '<=', $val]; + break; + case 'min_date': + $where[] = [$key, '>=', strtotime($val)]; + break; + case 'max_date': + $where[] = [$key, '<=', strtotime($val)]; + break; + case 'range': + [$beginTime, $endTime] = explode(' - ', $val); + $where[] = [$key, '>=', strtotime($beginTime)]; + $where[] = [$key, '<=', strtotime($endTime)]; + break; + case 'none': + $request_options[$key] = $val; + break; + default: + $where[] = [$key, $op, "%{$val}"]; + } + } + + return [$page, $limit, $where, $excludes, $request_options, $group]; + } + + /** + * 下拉选择列表. + * @return \think\response\Json + */ + public function selectList() + { + $fields = input('selectFields'); + $data = $this->model + ->where($this->selectWhere) + ->field($fields) + ->select(); + $this->success(null, $data); + } + + /** + * 初始化视图参数. + */ + private function viewInit() + { + $request = app()->request; + list($thisModule, $thisController, $thisAction) = [app('http')->getName(), app()->request->controller(), $request->action()]; + list($thisControllerArr, $jsPath) = [explode('.', $thisController), null]; + foreach ($thisControllerArr as $vo) { + empty($jsPath) ? $jsPath = parse_name($vo) : $jsPath .= '/' . parse_name($vo); + } + $autoloadJs = file_exists(root_path('public') . "static/{$thisModule}/js/{$jsPath}.js") ? true : false; + $thisControllerJsPath = "{$thisModule}/js/{$jsPath}.js"; + $adminModuleName = config('app.admin_alias_name'); + $isSuperAdmin = session('admin.id') == AdminConstant::SUPER_ADMIN_ID ? true : false; + $data = [ + 'adminModuleName' => $adminModuleName, + 'thisController' => parse_name($thisController), + 'thisAction' => $thisAction, + 'thisRequest' => parse_name("{$thisModule}/{$thisController}/{$thisAction}"), + 'thisControllerJsPath' => "{$thisControllerJsPath}", + 'autoloadJs' => $autoloadJs, + 'isSuperAdmin' => $isSuperAdmin, + 'version' => env('app_debug') ? time() : sysconfig('site', 'site_version'), + ]; + + View::assign($data); + } + + /** + * 检测权限. + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + protected function checkAuth($currentNode = null, $haltRequest = true) + { + $adminConfig = config('admin'); + $adminId = session('admin.id'); + $expireTime = session('admin.expire_time'); + /** @var AuthService $authService */ + $authService = app(AuthService::class, ['adminId' => $adminId]); + + $currentNode = $authService->getCurrentNode(); + if (is_null($currentNode)) { + $currentNode = $authService->getCurrentNode(); + } + + $currentController = parse_name(app()->request->controller()); + + // 验证登录 + if ( + !in_array($currentController, $adminConfig['no_login_controller']) && + !in_array($currentNode, $adminConfig['no_login_node']) + ) { + empty($adminId) && $this->error('请先登录后台', [], __url('admin/login/index')); + + // 判断是否登录过期 + if ($expireTime !== true && time() > $expireTime) { + session('admin', null); + $this->error('登录已过期,请重新登录', [], __url('admin/login/index')); + } + } + + // 验证权限 + if ( + !in_array($currentController, $adminConfig['no_auth_controller']) && + !in_array($currentNode, $adminConfig['no_auth_node']) + ) { + $check = $authService->checkNode($currentNode); + + if ($haltRequest) { + if (!$check) { + $this->error('无权限访问'); + } + } else { + return $check; + } + + // 判断是否为演示环境 + if (env('adminsystem.is_demo', false) && app()->request->isPost()) { + $this->error('演示环境下不允许修改'); + } + } + + $model_admin = SystemAdmin::autoCache('read', $adminId)->find($adminId); + + $this->sessionAdmin = $model_admin; + + $this->assign('session_admin', $model_admin); + } + + /** + * 严格校验接口是否为POST请求 + */ + protected function checkPostRequest() + { + if (!$this->request->isPost()) { + $this->error('当前请求不合法!'); + } + } +} diff --git a/extend/base/common/controller/TimerControllerBase.php b/extend/base/common/controller/TimerControllerBase.php new file mode 100644 index 0000000..1a6283f --- /dev/null +++ b/extend/base/common/controller/TimerControllerBase.php @@ -0,0 +1,35 @@ +frequency)) { + $this->protectVisit($this->frequency); + } + } + + protected function protectVisit(int $frequency) + { + $cache_tag = 'timer_protect'; + + $cache_key = 'timer_protect_' . md5($this->request->url()); + + $last_exec_time = Cache::get($cache_key, 0); + + if ($last_exec_time >= time() - $frequency) { + return $this->error('请不要频繁请求'); + } + + Cache::tag($cache_tag)->set($cache_key, time()); + } +} diff --git a/extend/base/common/controller/ToolsControllerBase.php b/extend/base/common/controller/ToolsControllerBase.php new file mode 100644 index 0000000..acfe6e8 --- /dev/null +++ b/extend/base/common/controller/ToolsControllerBase.php @@ -0,0 +1,11 @@ +username}"); + } +} diff --git a/extend/base/common/event/AdminLoginType/DemoEventBase.php b/extend/base/common/event/AdminLoginType/DemoEventBase.php new file mode 100644 index 0000000..fd7c6f5 --- /dev/null +++ b/extend/base/common/event/AdminLoginType/DemoEventBase.php @@ -0,0 +1,23 @@ +fetch('login/ext/demo'); + } + + // 事件监听处理 + return [ + 'view_content' => $content, + ]; + } +} diff --git a/extend/base/common/exception/EventExceptionBase.php b/extend/base/common/exception/EventExceptionBase.php new file mode 100644 index 0000000..87aa7f1 --- /dev/null +++ b/extend/base/common/exception/EventExceptionBase.php @@ -0,0 +1,9 @@ +'', + * 'type'=>'', // tag/key + * 'field'=>'' // 为空则不做拼接 + * ] + * + * @var array + */ + public static $autoCache = []; + + public static function onAfterWrite($model) + { + static::autoRemoveCache($model); + } + + public static function onAfterDelete($model) + { + static::autoRemoveCache($model); + } + + public static function onAfterRestore($model) + { + static::autoRemoveCache($model); + } + + public static function autoRemoveCache($model) + { + if (!isset(static::$autoCache['table'])) { + static::$autoCache['table'] = [ + 'name' => 'table', + 'type' => 'tag', + ]; + } + + if (!isset(static::$autoCache['read'])) { + static::$autoCache['read'] = [ + 'name' => 'read', + 'field' => 'id', + ]; + } + + foreach (static::$autoCache as $cache_item) { + $type = $cache_item['type'] ?? 'key'; + + $field = $cache_item['field'] ?? ''; + + $cache_key = $cache_item['name'] ?? ''; + + if (empty($cache_key)) { + continue; + } + + $cache_key = Str::snake($model->getName()) . '_' . $cache_key; + + if (!empty($field) && !is_null($model->$field)) { + // 用字段新的值拼接key + $cache_key_attr = $cache_key . '_' . $model->getAttr($field); + + static::doRemoveCache($type, $cache_key_attr); + + // 用字段旧的值拼接key + $cache_key_original = $cache_key . '_' . $model->getOrigin($field); + + if ($cache_key_original != $cache_key_attr) { + static::doRemoveCache($type, $cache_key_original); + } + } else { + static::doRemoveCache($type, $cache_key); + } + } + } + + protected static function doRemoveCache($type, $cache_key) + { + if (Env::get('app.auto_cache_log')) { + Log::debug("清除 ORM 自动缓存 {$type}:{$cache_key}"); + } + + if ($type == 'key') { + Cache::delete($cache_key); + } else { + Cache::tag($cache_key)->clear(); + } + } + + public static function getClassNameKey() + { + return str_replace('\\', '_', static::class); + } +} diff --git a/extend/base/common/model/TimeModelBase.php b/extend/base/common/model/TimeModelBase.php new file mode 100644 index 0000000..c330bc0 --- /dev/null +++ b/extend/base/common/model/TimeModelBase.php @@ -0,0 +1,40 @@ +config(['layout_on' => false]); + } else { + // 开启布局 + $this->config(['layout_on' => true]); + + // 名称必须为字符串 + if (is_string($name)) { + $this->config(['layout_name' => $name]); + } + + if (!empty($replace)) { + $this->config(['layout_item' => $replace]); + } + } + + return $this; + } + + /** + * 配置模板引擎. + * @param array $config 参数 + * @return $this + */ + public function config(array $config) + { + $this->driver()->config($config); + + return $this; + } + + /** + * 解析和获取模板内容 用于输出. + * @param string $template 模板文件名或者内容 + * @param array $vars 模板变量 + * @return string + * @throws \Exception + */ + public function fetch(string $template = '', array $vars = []): string + { + return $this->config([ + 'view_suffix' => 'html', + ])->getContent(function () use ($vars, $template) { + $this->engine()->fetch($template, array_merge($this->data, $vars)); + }); + } + + /** + * 解析和获取模板内容 用于输出. + * @param string $template 模板文件名或者内容 + * @param array $vars 模板变量 + * @return string + * @throws \Exception + */ + public function fetchJS(string $template = '', array $vars = []): string + { + return $this->config([ + 'view_suffix' => 'js', + ])->getContent(function () use ($vars, $template) { + $this->engine()->fetch($template, array_merge($this->data, $vars)); + }); + } +} diff --git a/extend/base/common/provider/db/QueryBase.php b/extend/base/common/provider/db/QueryBase.php new file mode 100644 index 0000000..71e9769 --- /dev/null +++ b/extend/base/common/provider/db/QueryBase.php @@ -0,0 +1,60 @@ +getName()); + + if (is_null($key)) { + $key = $this->getOptionsMd5(); + } + + if (!is_null($field_key_value)) { + $key = $key . '_' . $field_key_value; + } + $key = $table_name . '_' . $key; + + if (!is_null($tag)) { + if (!is_null($field_tag_value)) { + $tag = $tag . '_' . $field_tag_value; + } + + $tag = $table_name . '_' . $tag; + } + + if (Env::get('app.auto_cache_log')) { + Log::debug('use auto cache:' . $key); + Log::debug('use auto cache tag:' . $tag); + } + + $this->cache($key, null, $tag); + + return $this; + } + + public function getOptionsMd5() + { + $options = $this->getOptions(); + + // TODO:支持获取回调函数的设置 + + return md5(json_encode($options)); + } +} diff --git a/extend/base/common/service/AuthServiceBase.php b/extend/base/common/service/AuthServiceBase.php new file mode 100644 index 0000000..d5cdf83 --- /dev/null +++ b/extend/base/common/service/AuthServiceBase.php @@ -0,0 +1,212 @@ + true, // 权限开关 + 'system_admin' => 'system_admin', // 用户表 + 'system_auth' => 'system_auth', // 权限表 + 'system_node' => 'system_node', // 节点表 + 'system_auth_node' => 'system_auth_node', // 权限-节点表 + ]; + + /** + * 管理员信息. + * @var array|\think\Model|null + */ + protected $adminInfo; + + /** + * 所有节点信息. + * @var array + */ + protected $nodeList; + + /** + * 管理员所有授权节点. + * @var array + */ + protected $adminNode; + + /*** + * 构造方法 + * AuthService constructor. + * @param null $adminId + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function __construct($adminId = null) + { + $this->adminId = $adminId; + $this->adminInfo = $this->getAdminInfo(); + $this->nodeList = $this->getNodeList(); + $this->adminNode = $this->getAdminNode(); + + return $this; + } + + /** + * 检测检测权限. + * @param null $node + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function checkNode($node = null) + { + // 判断是否为超级管理员 + if ($this->adminId == AdminConstant::SUPER_ADMIN_ID) { + return true; + } + // 判断权限验证开关 + if ($this->config['auth_on'] == false) { + return true; + } + // 判断是否需要获取当前节点 + if (empty($node)) { + $node = $this->getCurrentNode(); + } else { + $node = $this->parseNodeStr($node); + } + // 判断是否加入节点控制,优先获取缓存信息 + if (!isset($this->nodeList[$node])) { + return Config::get('admin.default_auth_check'); + } + $nodeInfo = $this->nodeList[$node]; + if ($nodeInfo['is_auth'] == 0) { + return true; + } + // 用户验证,优先获取缓存信息 + if (empty($this->adminInfo) || $this->adminInfo['status'] != 1 || empty($this->adminInfo['auth_ids'])) { + return false; + } + // 判断该节点是否允许访问 + if (in_array($node, $this->adminNode)) { + return true; + } + + return false; + } + + /** + * 获取当前节点. + * @return string + */ + public function getCurrentNode() + { + $node = $this->parseNodeStr(request()->controller() . '/' . request()->action()); + + return $node; + } + + /** + * 获取当前管理员所有节点. + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function getAdminNode() + { + $nodeList = []; + $adminInfo = $this->getAdminInfo(); + + if (!empty($adminInfo) && $adminInfo['status'] != 1) { + return $nodeList; + } + + if (!empty($adminInfo) && !empty($adminInfo['auth_ids'])) { + $buildAuthSql = Db::name($this->config['system_auth']) + ->distinct(true) + ->whereIn('id', $adminInfo['auth_ids']) + ->field('id') + ->buildSql(true); + $buildAuthNodeSql = Db::name($this->config['system_auth_node']) + ->distinct(true) + ->where("auth_id IN {$buildAuthSql}") + ->field('node_id') + ->buildSql(true); + $nodeList = Db::name($this->config['system_node']) + ->distinct(true) + ->where("id IN {$buildAuthNodeSql}") + ->column('node'); + } + + return $nodeList; + } + + /** + * 获取所有节点信息. + * @time 2021-01-07 + * @return array + * @author zhongshaofa + */ + public function getNodeList() + { + return Db::name($this->config['system_node']) + ->autoCache(null, null, 'table') + ->column('id,node,title,type,is_auth', 'node'); + } + + /** + * 获取管理员信息. + * @time 2021-01-07 + * @return array|\think\Model|null + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + * @author zhongshaofa + */ + public function getAdminInfo() + { + return Db::name($this->config['system_admin']) + ->where('id', $this->adminId) + ->autoCache('info', $this->adminId) + ->find(); + } + + /** + * 驼峰转下划线规则. + * @param string $node + * @return string + */ + public function parseNodeStr($node) + { + $array = explode('/', $node); + foreach ($array as $key => $val) { + if ($key == 0) { + $val = explode('.', $val); + foreach ($val as &$vo) { + $vo = \think\helper\Str::snake(lcfirst($vo)); + } + $val = implode('.', $val); + $array[$key] = $val; + } + } + $node = implode('/', $array); + + return $node; + } +} diff --git a/extend/base/common/service/MenuServiceBase.php b/extend/base/common/service/MenuServiceBase.php new file mode 100644 index 0000000..efc6aca --- /dev/null +++ b/extend/base/common/service/MenuServiceBase.php @@ -0,0 +1,111 @@ +adminId = $adminId; + + return $this; + } + + /** + * 获取首页信息. + * @return array|\think\Model|null + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function getHomeInfo() + { + $data = Db::name('system_menu') + ->field('title,icon,href') + ->where('delete_time', 0) + ->where('pid', MenuConstant::HOME_PID) + ->find(); + !empty($data) && $data['href'] = __url($data['href']); + + return $data; + } + + /** + * 获取后台菜单树信息. + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function getMenuTree() + { + /** @var AuthService $authService */ + $authServer = app(AuthService::class, ['adminId' => $this->adminId]); + + return $this->buildMenuChild(0, $this->getMenuData(), $authServer); + } + + private function buildMenuChild($pid, $menuList, AuthService $authServer) + { + $treeList = []; + foreach ($menuList as &$v) { + $check = false; + if (!empty($v['auth_node'])) { + $check = $authServer->checkNode($v['auth_node']); + } elseif (!empty($v['href'])) { + $check = $authServer->checkNode($v['href']); + } else { + $check = true; + } + + !empty($v['href']) && $v['href'] = __url($v['href']); + if ($pid == $v['pid'] && $check) { + $node = $v; + $child = $this->buildMenuChild($v['id'], $menuList, $authServer); + if (!empty($child)) { + $node['child'] = $child; + } + if (!empty($v['href']) || !empty($child)) { + $treeList[] = $node; + } + } + } + + return $treeList; + } + + /** + * 获取所有菜单数据. + * @return \think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + protected function getMenuData() + { + $menuData = Db::name('system_menu') + ->field('id,pid,title,icon,href,target') + ->where('delete_time', 0) + ->where([ + ['status', '=', '1'], + ['pid', '<>', MenuConstant::HOME_PID], + ]) + ->order([ + 'sort' => 'desc', + 'id' => 'asc', + ]) + ->select(); + + return $menuData; + } +} diff --git a/extend/base/common/service/UploadServiceBase.php b/extend/base/common/service/UploadServiceBase.php new file mode 100644 index 0000000..d8babe4 --- /dev/null +++ b/extend/base/common/service/UploadServiceBase.php @@ -0,0 +1,168 @@ +uploadType = $upload_type; + } + + public function validate(File $file, $allow_ext = null, $allow_size = null, $fail_exception = false) + { + $uploadConfig = sysconfig('upload'); + + if (!is_null($allow_ext)) { + $uploadConfig['upload_allow_ext'] = $allow_ext; + } + + if (!is_null($allow_size)) { + $uploadConfig['upload_allow_size'] = $allow_size; + } + + $rule = [ + 'upload_type|指定上传类型有误' => "in:{$uploadConfig['upload_allow_type']}", + 'file|文件' => "require|file|fileExt:{$uploadConfig['upload_allow_ext']}|fileSize:{$uploadConfig['upload_allow_size']}", + ]; + + $validat_result = Validate::failException($fail_exception)->check([ + 'upload_type' => $this->uploadType, + 'file' => $file, + ], $rule); + + if (!$validat_result) { + return $validat_result; + } + + // 出于性能原因,您可以注释掉下面的代码 + $file_path = $file->getRealPath(); + + if (strpos(file_get_contents($file_path), 'validate($file, $allow_ext, $allow_size, true); + } + + public function url($save_name) + { + $url = Filesystem::disk($this->uploadType)->url($save_name); + + return $url; + } + + /** + * 存储文件. + * + * @param File $file + * @param string|null $save_name + * @param bool $force_save 指定$save_name才可以用,设为true强制写入(不报错,如果存在则删除),否则是驱动的默认行为(覆盖、失败、异常) + * @param string $upload_dir + * @param bool $disable_model + * @return mixed + */ + public function save(File $file, string $save_name = null, $force_save = false, $upload_dir = 'upload', $disable_model = false) + { + $model_file = null; + + if ($force_save && !is_null($save_name)) { + $file_path = $upload_dir . '/' . $save_name; + if (Filesystem::disk($this->uploadType)->has($file_path)) { + $model_file = SystemUploadfile::where('save_name', $save_name)->where('upload_type', $this->uploadType)->find(); + Filesystem::disk($this->uploadType)->delete($file_path); + } + } + + if (empty($model_file)) { + $model_file = new SystemUploadfile(); + } + + $model_file->upload_type = $this->uploadType; + $model_file->file_ext = strtolower($file->extension()); + + if ($file instanceof UploadedFile) { + $model_file->original_name = $file->getOriginalName(); + $model_file->mime_type = $file->getOriginalMime(); + } else { + $model_file->original_name = $file->getFilename(); + $model_file->mime_type = $file->getMime(); + } + + $save_name = Filesystem::disk($this->uploadType)->putFile($upload_dir, $file, function () use ($save_name, $file) { + if (!is_null($save_name)) { + $ext_name = $file->extension(); + + if (empty($ext_name)) { + return $save_name; + } + + $list_name = explode('.', $save_name); + + array_pop($list_name); + + return implode('.', $list_name); + } + + return date('Ymd') . '/' . uniqid(); + }); + + $url = $this->url($save_name); + + $model_file->url = $url; + $model_file->save_name = $save_name; + + $model_file->sha1 = $file->sha1(); + + $model_file->file_size = $file->getSize(); + + if (!$disable_model) { + $model_file->save(); + } + + return $model_file; + } + + public function saveUrlFile($url, string $save_name = null, $force_save = false, $upload_dir = 'upload', $disable_model = false) + { + $runtime_path = App::getRuntimePath() . 'upload/temp/' . basename($url); + + PathTools::intiDir($runtime_path); + + $file_content = file_get_contents($url); + + file_put_contents($runtime_path, $file_content); + + $file = new File($runtime_path); + + $response = $this->save($file, $save_name, $force_save, $upload_dir, $disable_model); + + unlink($runtime_path); + + return $response; + } +} diff --git a/extend/base/common/tools/ExportToolsBase.php b/extend/base/common/tools/ExportToolsBase.php new file mode 100644 index 0000000..a91a2c7 --- /dev/null +++ b/extend/base/common/tools/ExportToolsBase.php @@ -0,0 +1,111 @@ +getActiveSheet(); + + $write_col = 1; + $write_line = 1; + foreach ($fields as $field_key => $field_name) { + $col_key = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($write_col); + $sheet->setCellValue($col_key . $write_line, $field_name); + $sheet->getColumnDimension($col_key)->setWidth(mb_strlen($field_name) * 3); + + $write_col++; + } + + $runtime_file_list = []; + + $limit = 1000; + + $page = 0; + + while (true) { + $page++; + $list = $model + ->where($where)->page($page, $limit)->select(); + foreach ($list as $list_index => $item) { + $write_line++; + $write_col = 1; + + foreach ($fields as $field_key => $field_name) { + $col_key = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($write_col); + + $cel = null; + + $value = \think\helper\Arr::get($item, $field_key); + + if (in_array($field_key, $image_fields)) { + // 是图片 + $cel = $value; + + try { + if (filter_var($value, FILTER_VALIDATE_URL)) { + $runtime_file = PathTools::tempBuildPath(uniqid()); + + $runtime_file_list[] = $runtime_file; + + file_put_contents($runtime_file, file_get_contents($value)); + + $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); + $drawing->setName($field_name); + $drawing->setDescription($field_key); + $drawing->setPath($runtime_file); + $drawing->setHeight(36); + + $drawing->setCoordinates($col_key . $write_line); + $drawing->setWorksheet($sheet); + } + } catch (\Throwable $th) { + $message = $th->getMessage(); + + $cel .= "\n" . $message; + } + } elseif (array_key_exists($field_key, $select_fields)) { + // 需要设置选项 + + $cel = $select_fields[$field_key][$value] ?? $value; + } elseif (in_array($field_key, $date_fields)) { + if (empty($value)) { + $cel = ''; + } else { + $cel = date('Y-m-d H:i:s', $value); + } + } else { + $cel = $value; + } + + $sheet->setCellValueExplicit($col_key . $write_line, $cel, DataType::TYPE_STRING); + $write_col++; + } + } + + if (count($list) < $limit) { + break; + } + } + + $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); + + ob_start(); + $writer->save('php://output'); + $content = ob_get_contents(); + ob_clean(); + + foreach ($runtime_file_list as $runtime_file) { + if (file_exists($runtime_file)) { + unlink($runtime_file); + } + } + + return $content; + } +} diff --git a/extend/base/common/tools/PathToolsBase.php b/extend/base/common/tools/PathToolsBase.php new file mode 100644 index 0000000..f5b1eff --- /dev/null +++ b/extend/base/common/tools/PathToolsBase.php @@ -0,0 +1,162 @@ + $value) { + if (!in_array($value, ['.', '..'])) { + $current_path = $dir . DS . $value; + if (is_dir($current_path)) { + $result[$value] = self::mapDir($current_path, $callback); + } else { + if (is_callable($callback)) { + $result[$value] = $callback($current_path, $value, $dir); + } else { + $result[$value] = $current_path; + } + } + } + } + + return $result; + } + + public static function formatWinPath($content) + { + return str_replace('/', '\\', $content); + } + + public static function compareFiles($a, $b, $return_diff = false):bool|string + { + $result = true; + + // Check if filesize is different + if (filesize($a) !== filesize($b)) { + $result = false; + } + + if ($result) { + // Check if content is different + $ah = fopen($a, 'rb'); + $bh = fopen($b, 'rb'); + + while (!feof($ah)) { + if (fread($ah, 8192) != fread($bh, 8192)) { + $result = false; + break; + } + } + + fclose($ah); + fclose($bh); + } + + if (!$result) { + // 如果前面的方法认为文件变化,那么以文本变化的方式识别是否变化,并给出变化的内容 + $text_mime_type = []; + $text_mime_type[] = 'text/html'; + $text_mime_type[] = 'text/plain'; + $text_mime_type[] = 'text/css'; + $text_mime_type[] = 'image/svg+xml'; + $text_mime_type[] = 'text/x-php'; + $text_mime_type[] = 'application/json'; + $text_mime_type[] = 'application/x-wine-extension-ini'; + + if (in_array(mime_content_type($a), $text_mime_type) && in_array(mime_content_type($b), $text_mime_type)) { + $a_content = file_get_contents($a); + $b_content = file_get_contents($b); + + $diff = new Differ(); + + $diff_content = $diff->diff($a_content, $b_content); + + if (!empty($diff_content)) { + $result = $return_diff ? $diff_content : false; + } else { + $result = true; + } + } + } + + return $result; + } +} diff --git a/app/common/tpl/dispatch_jump.tpl b/extend/base/common/tpl/dispatch_jump.tpl similarity index 100% rename from app/common/tpl/dispatch_jump.tpl rename to extend/base/common/tpl/dispatch_jump.tpl diff --git a/app/common/tpl/think_exception.tpl b/extend/base/common/tpl/think_exception.tpl similarity index 100% rename from app/common/tpl/think_exception.tpl rename to extend/base/common/tpl/think_exception.tpl diff --git a/extend/base/common/traits/JumpTraitBase.php b/extend/base/common/traits/JumpTraitBase.php new file mode 100644 index 0000000..5928a6c --- /dev/null +++ b/extend/base/common/traits/JumpTraitBase.php @@ -0,0 +1,141 @@ +buildUrl($url)->__toString(); + } + + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + $type = $this->getResponseType(); + if ($type == 'html') { + if (is_null($result['url'])) { + $result['url'] = ''; + } + + $response = view(app('config')->get('app.dispatch_success_tmpl'), $result); + } elseif ($type == 'json') { + $response = json($result); + } + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法. + * @param mixed $msg 提示信息 + * @param mixed $data 返回的数据 + * @param string $url 跳转的 URL 地址 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function error($msg = '', $data = '', $url = null, $wait = 3, array $header = []) + { + if (is_null($url)) { + $url = request()->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ($url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : app('route')->buildUrl($url)->__toString(); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + if ($type == 'html') { + if (is_null($result['url'])) { + $result['url'] = ''; + } + $response = view(app('config')->get('app.dispatch_error_tmpl'), $result); + } elseif ($type == 'json') { + $response = json($result); + } + throw new HttpResponseException($response); + } + + /** + * 返回封装后的 API 数据到客户端. + * @param mixed $data 要返回的数据 + * @param int $code 返回的 code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => time(), + 'data' => $data, + ]; + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL 重定向. + * @param string $url 跳转的 URL 表达式 + * @param array|int $params 其它 URL 参数 + * @param int $code http code + * @param array $with 隐式传参 + * @return void + * @throws HttpResponseException + */ + protected function redirect($url = [], $params = [], $code = 302) + { + if (is_int($params)) { + $code = $params; + $params = []; + } + + $response = Response::create($url, 'redirect', $code); + throw new HttpResponseException($response); + } + + /** + * 获取当前的 response 输出类型. + * @return string + */ + protected function getResponseType() + { + return (request()->isJson() || request()->isAjax() || request()->isPost()) ? 'json' : 'html'; + } +}