mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-01 15:32:48 +08:00
增加扩展机制定位文件;将common模块实现扩展模式;发布新版本;
This commit is contained in:
@@ -1,146 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use base\common\command\CurdBase;
|
||||
|
||||
use app\admin\service\curd\BuildCurdService;
|
||||
use app\common\tools\PathTools;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Exception;
|
||||
use think\facade\App;
|
||||
|
||||
class Curd extends Command
|
||||
class Curd extends CurdBase
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('curd')
|
||||
->addOption('table', 't', Option::VALUE_REQUIRED, '主表名', null)
|
||||
->addOption('controllerFilename', 'c', Option::VALUE_REQUIRED, '控制器文件名', null)
|
||||
->addOption('modelFilename', 'm', Option::VALUE_REQUIRED, '主表模型文件名', null)
|
||||
#
|
||||
->addOption('force', 'f', Option::VALUE_NONE, '强制覆盖模式')
|
||||
->addOption('delete', 'd', Option::VALUE_NONE, '删除模式')
|
||||
->addOption('runtime', 'r', Option::VALUE_NONE, '临时生成')
|
||||
->setDescription('一键curd命令服务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
|
||||
$table = $input->getOption('table');
|
||||
$controllerFilename = $input->getOption('controllerFilename');
|
||||
$modelFilename = $input->getOption('modelFilename');
|
||||
|
||||
$force = 0;
|
||||
$delete = 0;
|
||||
|
||||
if ($input->hasOption('force')) {
|
||||
$force = 1;
|
||||
}
|
||||
|
||||
if ($input->hasOption('delete')) {
|
||||
$delete = 1;
|
||||
}
|
||||
|
||||
if (empty($table)) {
|
||||
$output->error('请设置主表');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$build = (new BuildCurdService())
|
||||
->setTable($table)
|
||||
->setForce($force);
|
||||
|
||||
if ($input->hasOption('runtime')) {
|
||||
|
||||
$runtime_path = App::getRuntimePath() . 'source/build/' . date('YmdHis') . '/';
|
||||
dump($runtime_path);
|
||||
PathTools::intiDir($runtime_path . 'a.temp');
|
||||
|
||||
$build->setRootDir($runtime_path);
|
||||
}
|
||||
|
||||
$columns = $build->getTableColumns();
|
||||
|
||||
$relations = [];
|
||||
|
||||
foreach ($columns as $field => $column) {
|
||||
|
||||
if (isset($column['formType']) && $column['formType'] == 'relation') {
|
||||
$define = $column['define'];
|
||||
|
||||
if (!isset($define['table'])) {
|
||||
$output->error("关联字段{$field}没有设置关联表名称");
|
||||
return false;
|
||||
}
|
||||
|
||||
$relations[] = [
|
||||
'table' => $define['table'],
|
||||
'foreignKey' => $field,
|
||||
'primaryKey' => $define['primaryKey'] ?? null,
|
||||
'modelFilename' => $define['modelFilename'] ?? null,
|
||||
'onlyFileds' => isset($define['onlyFileds']) ? explode("|", $define['onlyFileds']) : [],
|
||||
'relationBindSelect' => $define['relationBindSelect'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
!empty($controllerFilename) && $build = $build->setControllerFilename($controllerFilename);
|
||||
!empty($modelFilename) && $build = $build->setModelFilename($modelFilename);
|
||||
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
$build = $build->setRelation($relation['table'], $relation['foreignKey'], $relation['primaryKey'], $relation['modelFilename'], $relation['onlyFileds'], $relation['relationBindSelect']);
|
||||
}
|
||||
|
||||
$build = $build->render();
|
||||
$fileList = $build->getFileList();
|
||||
|
||||
if (!$delete) {
|
||||
if ($force) {
|
||||
$output->writeln(">>>>>>>>>>>>>>>");
|
||||
foreach ($fileList as $key => $val) {
|
||||
$output->writeln($key);
|
||||
}
|
||||
$output->writeln(">>>>>>>>>>>>>>>");
|
||||
|
||||
$ask_force_delete_result = $output->confirm($input,'确定强制生成上方所有文件? 如果文件存在会直接覆盖。');
|
||||
|
||||
if (!$ask_force_delete_result) {
|
||||
throw new Exception("取消文件CURD生成操作");
|
||||
}
|
||||
}
|
||||
$result = $build->create();
|
||||
$output->info('自动生成CURD成功');
|
||||
} else {
|
||||
$output->writeln(">>>>>>>>>>>>>>>");
|
||||
foreach ($fileList as $key => $val) {
|
||||
$output->writeln($key);
|
||||
}
|
||||
$output->writeln(">>>>>>>>>>>>>>>");
|
||||
|
||||
$ask_force_delete_result = $output->confirm($input,'确定删除上方所有文件? ');
|
||||
|
||||
if (!$ask_force_delete_result) {
|
||||
throw new Exception("取消删除文件操作");
|
||||
}
|
||||
$result = $build->delete();
|
||||
$output->info('>>>>>>>>>>>>>>>');
|
||||
$output->info('删除自动生成CURD文件成功');
|
||||
}
|
||||
$output->info('>>>>>>>>>>>>>>>');
|
||||
foreach ($result as $vo) {
|
||||
$output->info($vo);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$output->error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use base\common\command\NodeBase;
|
||||
|
||||
use app\admin\model\SystemNode;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use app\admin\service\NodeService;
|
||||
|
||||
class Node extends Command
|
||||
class Node extends NodeBase
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('node')
|
||||
->addOption('force', null, Option::VALUE_REQUIRED, '是否强制刷新', 0)
|
||||
->setDescription('系统节点刷新服务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$force = $input->getOption('force');
|
||||
$output->writeln("========正在刷新节点服务:=====" . date('Y-m-d H:i:s'));
|
||||
$check = $this->refresh($force);
|
||||
$check !== true && $output->writeln("节点刷新失败:" . $check);
|
||||
$output->writeln("刷新完成:" . date('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
protected function refresh($force)
|
||||
{
|
||||
$nodeList = (new NodeService())->getNodelist();
|
||||
if (empty($nodeList)) {
|
||||
return true;
|
||||
}
|
||||
$model = new SystemNode();
|
||||
try {
|
||||
if ($force == 1) {
|
||||
$updateNodeList = $model->whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($updateNodeList as $vo) {
|
||||
isset($formatNodeList[$vo['node']]) && $model->where('id', $vo['id'])->update([
|
||||
'title' => $formatNodeList[$vo['node']]['title'],
|
||||
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
$existNodeList = $model->field('node,title,type,is_auth')->select();
|
||||
foreach ($nodeList as $key => $vo) {
|
||||
foreach ($existNodeList as $v) {
|
||||
if ($vo['node'] == $v->node) {
|
||||
unset($nodeList[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$model->insertAll($nodeList);
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\common\service\UploadService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Filesystem;
|
||||
use think\File;
|
||||
use base\common\command\OssStaticBase;
|
||||
|
||||
class OssStatic extends Command
|
||||
class OssStatic extends OssStaticBase
|
||||
{
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('OssStatic')
|
||||
->setDescription('将静态资源上传到oss上');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln("========正在上传静态资源到OSS上:========" . date('Y-m-d H:i:s'));
|
||||
|
||||
$list = Filesystem::disk('local_static')->listContents('/', true);
|
||||
$upload_service = new UploadService();
|
||||
$uploadPrefix = config('app.oss_static_prefix', 'oss_static_prefix');
|
||||
|
||||
foreach ($list as $file_item) {
|
||||
|
||||
if ($file_item['type'] != 'file') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_path = $file_item['path'];
|
||||
|
||||
$file_path = Filesystem::disk('local_static')->path($file_path);
|
||||
|
||||
$file = new File($file_path, false);
|
||||
|
||||
$save_name = $file_item['path'];
|
||||
try {
|
||||
$model_file = $upload_service->save($file, $save_name, true, $uploadPrefix, true);
|
||||
$output->info('文件上传成功:' . $save_name . '。上传地址:' . $model_file['url']);
|
||||
} catch (\Throwable $th) {
|
||||
$output->error('文件上传失败:' . $save_name . '。错误信息:' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
$output->writeln("========已完成静态资源上传到OSS上:========" . date('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('删除成功');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<bool|string>
|
||||
*/
|
||||
$changed_files = [];
|
||||
|
||||
foreach ($current_version_list_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
|
||||
$current_version_file_path = $current_version_dir . '/' . $file_path;
|
||||
|
||||
$compare_result = PathTools::compareFiles($now_file_path, $current_version_file_path, true);
|
||||
|
||||
if (!$compare_result || is_string($compare_result)) {
|
||||
$changed_files[$file_path] = $compare_result;
|
||||
}
|
||||
}
|
||||
|
||||
// 有定制修改则退出
|
||||
|
||||
if (!empty($changed_files)) {
|
||||
$output->warning('无法自动更新,以下文件被定制,请还原或手动升级:');
|
||||
|
||||
foreach ($changed_files as $file_path => $compare_result) {
|
||||
$output->warning($file_path);
|
||||
|
||||
if (is_string($compare_result)) {
|
||||
$output->writeln($compare_result);
|
||||
}
|
||||
}
|
||||
|
||||
$file_count = count($changed_files);
|
||||
|
||||
$ask_result = $output->ask($input, "发现{$file_count}个文件被修改,如果您认为以上文件可以被覆盖或为检测错误,可以强制覆盖。\n是否强制继续更新?(y/n)");
|
||||
|
||||
if (!$ask_result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最新版本需要跳过的文件
|
||||
|
||||
$last_version_skip_config = include $last_version_dir . '/config/update.php';
|
||||
// 获取最新版本要替换的文件
|
||||
$last_version_filesystem = new Filesystem(new LocalFilesystemAdapter($last_version_dir));
|
||||
$last_version_list_files = $last_version_filesystem->listContents('/', Filesystem::LIST_DEEP)
|
||||
->filter(function (StorageAttributes $attributes) use ($last_version_skip_config) {
|
||||
if ($attributes->isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $attributes->path();
|
||||
|
||||
$skip_files = $last_version_skip_config['skip_files'] ?? [];
|
||||
|
||||
if (in_array($path, $skip_files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$skip_dir = $last_version_skip_config['skip_dir'] ?? [];
|
||||
$skip_dir[] = '.git';
|
||||
|
||||
foreach ($skip_dir as $dir) {
|
||||
if (str_starts_with($path, $dir)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->map(fn (StorageAttributes $attributes) => $attributes->path())
|
||||
->toArray();
|
||||
|
||||
$delete_files = array_diff($current_version_list_files, $last_version_list_files);
|
||||
|
||||
foreach ($delete_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
unlink($now_file_path);
|
||||
}
|
||||
|
||||
foreach ($last_version_list_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
$last_file_path = $last_version_dir . '/' . $file_path;
|
||||
|
||||
$file_content = file_get_contents($last_file_path);
|
||||
|
||||
PathTools::intiDir($now_file_path);
|
||||
file_put_contents($now_file_path, $file_content);
|
||||
}
|
||||
|
||||
// 处理append的文件
|
||||
$last_version_list_skip_files = $last_version_filesystem->listContents('/', Filesystem::LIST_DEEP)
|
||||
->filter(function (StorageAttributes $attributes) use ($last_version_skip_config) {
|
||||
if ($attributes->isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $attributes->path();
|
||||
|
||||
if (str_starts_with($path, '.git')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$skip_files = $last_version_skip_config['skip_files'] ?? [];
|
||||
|
||||
if (in_array($path, $skip_files)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$skip_dir = $last_version_skip_config['skip_dir'] ?? [];
|
||||
|
||||
foreach ($skip_dir as $dir) {
|
||||
if (str_starts_with($path, $dir)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->map(fn (StorageAttributes $attributes) => $attributes->path())
|
||||
->toArray();
|
||||
|
||||
$last_version_list_append_files = [];
|
||||
|
||||
foreach ($last_version_list_skip_files as $file_path) {
|
||||
if (in_array($file_path, $last_version_skip_config['append_files'])) {
|
||||
$last_version_list_append_files[] = $file_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($last_version_skip_config['append_dir'] as $dir) {
|
||||
if (str_starts_with($file_path, $dir)) {
|
||||
$last_version_list_append_files[] = $file_path;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($last_version_list_append_files as $file_path) {
|
||||
$now_file_path = $now_dir . '/' . $file_path;
|
||||
$last_file_path = $last_version_dir . '/' . $file_path;
|
||||
|
||||
if (file_exists($now_file_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_content = file_get_contents($last_file_path);
|
||||
|
||||
PathTools::intiDir($now_file_path);
|
||||
file_put_contents($now_file_path, $file_content);
|
||||
}
|
||||
|
||||
// 检测now的composer依赖和最新的composer依赖
|
||||
|
||||
$last_composer_json = file_get_contents($last_version_dir . '/composer.json');
|
||||
|
||||
$output->writeln($last_composer_json);
|
||||
$output->writeln('请参考以上最新composer文件调整您的依赖');
|
||||
|
||||
// 分析出最新需要的但now没有的
|
||||
|
||||
// 为用户整理出要手动调整的composer命令
|
||||
|
||||
$this->cleanWorkpaceDir();
|
||||
$output->writeln('更新完成');
|
||||
// 更新完成
|
||||
|
||||
if (!empty(Version::UPDATE_TIPS)) {
|
||||
foreach (Version::UPDATE_TIPS as $content) {
|
||||
$output->writeln($content);
|
||||
}
|
||||
$output->writeln('删除对应文件之后,可以通过以下命令重新安装');
|
||||
$output->writeln('php think admin:update --resinstall');
|
||||
}
|
||||
}
|
||||
|
||||
protected function cleanWorkpaceDir()
|
||||
{
|
||||
$dir = App::getRuntimePath() . '/update/';
|
||||
|
||||
$this->output->writeln('清理目录 ' . $dir);
|
||||
PathTools::removeDir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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', "<?php\n\n" . $migrate_content);
|
||||
file_put_contents($dist_file_path, "<?php\n\n" . $migrate_content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\constants;
|
||||
|
||||
use base\common\constants\AdminConstantBase;
|
||||
|
||||
/**
|
||||
* 管理员常量
|
||||
* Class AdminConstant
|
||||
* @package app\common\constants
|
||||
* Class AdminConstant.
|
||||
*/
|
||||
class AdminConstant
|
||||
class AdminConstant extends AdminConstantBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 超级管理员,不受权限控制
|
||||
*/
|
||||
const SUPER_ADMIN_ID = 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\constants;
|
||||
|
||||
use base\common\constants\MenuConstantBase;
|
||||
|
||||
/**
|
||||
* 菜单常量
|
||||
* Class MenuConstant
|
||||
* @package app\common\constants
|
||||
* Class MenuConstant.
|
||||
*/
|
||||
class MenuConstant
|
||||
class MenuConstant extends MenuConstantBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 首页的PID
|
||||
*/
|
||||
const HOME_PID = 99999999;
|
||||
|
||||
/**
|
||||
* 模块名前缀
|
||||
*/
|
||||
const MODULE_PREFIX = 'ulthon_admin_';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,442 +2,11 @@
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\BaseController;
|
||||
use app\common\constants\AdminConstant;
|
||||
use app\common\service\AuthService;
|
||||
use think\facade\Env;
|
||||
use think\facade\View;
|
||||
use think\Model;
|
||||
use think\template\exception\TemplateNotFoundException;
|
||||
use think\Validate;
|
||||
use base\common\controller\AdminControllerBase;
|
||||
|
||||
/**
|
||||
* Class AdminController.
|
||||
*/
|
||||
class AdminController extends BaseController
|
||||
class AdminController extends AdminControllerBase
|
||||
{
|
||||
use \app\common\traits\JumpTrait;
|
||||
|
||||
/**
|
||||
* 当前模型.
|
||||
* @Model
|
||||
* @var object
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* 字段排序.
|
||||
* @var array
|
||||
*/
|
||||
protected $sort = [
|
||||
'id' => '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 "<script>{$content_js}</script>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析和获取模板内容 用于输出.
|
||||
* @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('当前请求不合法!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace app\commno\exception;
|
||||
|
||||
use Exception;
|
||||
use extend\base\commno\exception\EventExceptionBase;
|
||||
|
||||
class EventException extends Exception
|
||||
class EventException extends EventExceptionBase
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
|
||||
use think\model\concern\SoftDelete;
|
||||
use base\common\model\TimeModelBase;
|
||||
|
||||
/**
|
||||
* 有关时间的模型
|
||||
* Class TimeModel
|
||||
* @package app\common\model
|
||||
* Class TimeModel.
|
||||
*/
|
||||
class TimeModel extends BaseModel
|
||||
class TimeModel extends TimeModelBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 自动时间戳类型
|
||||
* @var string
|
||||
*/
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
/**
|
||||
* 添加时间
|
||||
* @var string
|
||||
*/
|
||||
protected $createTime = 'create_time';
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
* @var string
|
||||
*/
|
||||
protected $updateTime = 'update_time';
|
||||
|
||||
/**
|
||||
* 软删除
|
||||
*/
|
||||
use SoftDelete;
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
protected $defaultSoftDelete = 0;
|
||||
}
|
||||
|
||||
@@ -1,58 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\provider;
|
||||
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
use think\exception\Handle;
|
||||
use think\exception\HttpException;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\exception\ValidateException;
|
||||
use think\Response;
|
||||
use Throwable;
|
||||
use base\common\provider\ExceptionHandleBase;
|
||||
|
||||
/**
|
||||
* 应用异常处理类
|
||||
* 应用异常处理类.
|
||||
*/
|
||||
class ExceptionHandle extends Handle
|
||||
class ExceptionHandle extends ExceptionHandleBase
|
||||
{
|
||||
/**
|
||||
* 不需要记录信息(日志)的异常类列表
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoreReport = [
|
||||
HttpException::class,
|
||||
HttpResponseException::class,
|
||||
ModelNotFoundException::class,
|
||||
DataNotFoundException::class,
|
||||
ValidateException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* 记录异常信息(包括日志或者其它方式记录)
|
||||
*
|
||||
* @access public
|
||||
* @param Throwable $exception
|
||||
* @return void
|
||||
*/
|
||||
public function report(Throwable $exception): void
|
||||
{
|
||||
// 使用内置的方式记录异常日志
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @access public
|
||||
* @param \think\Request $request
|
||||
* @param Throwable $e
|
||||
* @return Response
|
||||
*/
|
||||
public function render($request, Throwable $e): Response
|
||||
{
|
||||
// 添加自定义异常处理机制
|
||||
|
||||
// 其他错误交给系统处理
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace app\common\provider;
|
||||
|
||||
use base\common\provider\RequestBase;
|
||||
|
||||
// 应用请求对象类
|
||||
class Request extends \think\Request
|
||||
class Request extends RequestBase
|
||||
{
|
||||
protected $filter = [];
|
||||
}
|
||||
|
||||
@@ -2,82 +2,8 @@
|
||||
|
||||
namespace app\common\provider;
|
||||
|
||||
use think\View as ThinkView;
|
||||
use base\common\provider\ViewBase;
|
||||
|
||||
class View extends ThinkView
|
||||
class View extends ViewBase
|
||||
{
|
||||
/**
|
||||
* 设置布局
|
||||
* ! 注意,layout并非view类的标准方法,仅仅是think-view和think-template的特性方法
|
||||
* ! 但是,如果在provider中定制,则view的所有操作预期会发生错误
|
||||
* ! 同时,由于后台从设计支出就完全依赖think-view,所以专门为此扩展和特性定义方法时没有问题的.
|
||||
* @param bool|string $name 布局模板名称 false 则关闭布局
|
||||
* @param string $replace 布局模板内容替换标识
|
||||
* @return $this
|
||||
*/
|
||||
public function layout(bool|string $name, string $replace = ''): static
|
||||
{
|
||||
if (false === $name) {
|
||||
// 关闭布局
|
||||
$this->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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,211 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\constants\AdminConstant;
|
||||
use think\facade\Config;
|
||||
use think\facade\Db;
|
||||
use base\common\service\AuthServiceBase;
|
||||
|
||||
/**
|
||||
* 权限验证服务
|
||||
* Class AuthService
|
||||
* @package app\common\service
|
||||
* Class AuthService.
|
||||
*/
|
||||
class AuthService
|
||||
class AuthService extends AuthServiceBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
* @var null
|
||||
*/
|
||||
protected $adminId = null;
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'auth_on' => 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 <shaofa.zhong@happy-seed.com>
|
||||
*/
|
||||
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 <shaofa.zhong@happy-seed.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\constants\MenuConstant;
|
||||
use think\facade\Db;
|
||||
use base\common\service\MenuServiceBase;
|
||||
|
||||
class MenuService
|
||||
class MenuService extends MenuServiceBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
* @var integer
|
||||
*/
|
||||
protected $adminId;
|
||||
|
||||
public function __construct($adminId)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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), '<?php') !== false) {
|
||||
if ($fail_exception) {
|
||||
throw new ValidateException("文件含有PHP注入代码");
|
||||
} else {
|
||||
return '文件含有PHP注入代码';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validateException($file, $allow_ext = null, $allow_size = null)
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
{__NOLAYOUT__}<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>跳转提示</title>
|
||||
<style type="text/css">
|
||||
*{box-sizing:border-box;margin:0;padding:0;font-family:Lantinghei SC,Open Sans,Arial,Hiragino Sans GB,Microsoft YaHei,"微软雅黑",STHeiti,WenQuanYi Micro Hei,SimSun,sans-serif;-webkit-font-smoothing:antialiased}
|
||||
body{padding:70px 0;background:#edf1f4;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
|
||||
a{outline:0;color:#3498db;text-decoration:none;cursor:pointer}
|
||||
.system-message{margin:20px 5%;padding:40px 20px;background:#fff;box-shadow:1px 1px 1px hsla(0,0%,39%,.1);text-align:center}
|
||||
.system-message h1{margin:0;margin-bottom:9pt;color:#444;font-weight:400;font-size:40px}
|
||||
.system-message .jump,.system-message .image{margin:20px 0;padding:0;padding:10px 0;font-weight:400}
|
||||
.system-message .jump{font-size:14px}
|
||||
.system-message .jump a{color:#333}
|
||||
.system-message p{font-size:9pt;line-height:20px}
|
||||
.system-message .btn{display:inline-block;margin-right:10px;width:138px;height:2pc;border:1px solid #44a0e8;border-radius:30px;color:#44a0e8;text-align:center;font-size:1pc;line-height:2pc;margin-bottom:5px;}
|
||||
.success .btn{border-color:#69bf4e;color:#69bf4e}
|
||||
.error .btn{border-color:#ff8992;color:#ff8992}
|
||||
.info .btn{border-color:#3498db;color:#3498db}
|
||||
.copyright p{width:100%;color:#919191;text-align:center;font-size:10px}
|
||||
.system-message .btn-grey{border-color:#bbb;color:#bbb}
|
||||
.clearfix:after{clear:both;display:block;visibility:hidden;height:0;content:"."}
|
||||
@media (max-width:768px){body {padding:20px 0;}}
|
||||
@media (max-width:480px){.system-message h1{font-size:30px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$codeText = $code == 1 ? 'success' : ($code == 0 ? 'error' : 'info');
|
||||
?>
|
||||
<div class="system-message {$codeText}">
|
||||
<div class="image">
|
||||
<img src="/static/common/images/{$codeText}.svg" alt="" width="150" />
|
||||
</div>
|
||||
<h1><?php echo(strip_tags($msg));?></h1>
|
||||
<p class="jump">
|
||||
页面将在 <span id="wait"><?php echo($wait);?></span> 秒后自动跳转
|
||||
</p>
|
||||
<p class="clearfix">
|
||||
<a href="#" onClick="javascript :history.back(-1);" class="btn btn-grey">返回上一页</a>
|
||||
<a id="href" href="{$url}" class="btn btn-primary">立即跳转</a>
|
||||
</p>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
var wait = document.getElementById('wait'),
|
||||
href = document.getElementById('href').href;
|
||||
var interval = setInterval(function(){
|
||||
var time = --wait.innerHTML;
|
||||
if(time <= 0) {
|
||||
location.href = href;
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, 1000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
$cdnurl = function_exists('config') ? config('view_replace_str.__CDN__') : '';
|
||||
$publicurl = function_exists('config') ? config('view_replace_str.__PUBLIC__') : '/';
|
||||
$debug = function_exists('config') ? config('app_debug') : false;
|
||||
|
||||
$lang = [
|
||||
'An error occurred' => '发生错误',
|
||||
'Home' => '返回主页',
|
||||
'Feedback' => '反馈错误',
|
||||
'The page you are looking for is temporarily unavailable' => '你所浏览的页面暂时无法访问',
|
||||
'You can return to the previous page and try again' => '你可以返回上一页重试,或直接向我们反馈错误报告'
|
||||
];
|
||||
|
||||
$langSet = '';
|
||||
|
||||
if (isset($_GET['lang'])) {
|
||||
$langSet = strtolower($_GET['lang']);
|
||||
} elseif (isset($_COOKIE['think_var'])) {
|
||||
$langSet = strtolower($_COOKIE['think_var']);
|
||||
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
|
||||
$langSet = strtolower($matches[1]);
|
||||
}
|
||||
$langSet = $langSet && in_array($langSet, ['zh-cn', 'en']) ? $langSet : 'zh-cn';
|
||||
$langSet == 'en' && $lang = array_combine(array_keys($lang), array_keys($lang));
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title><?=$lang['An error occurred']?></title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<link rel="shortcut icon" href="<?php echo $cdnurl;?>/assets/img/favicon.ico" />
|
||||
<style>
|
||||
* {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;}
|
||||
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,caption,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video {margin:0;padding:0;border:0;outline:0;vertical-align:baseline;background:transparent;}
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section {display:block;}
|
||||
html {font-size:16px;line-height:24px;width:100%;height:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;overflow-y:scroll;overflow-x:hidden;}
|
||||
img {vertical-align:middle;max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;}
|
||||
body {min-height:100%;background:#edf1f4;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",微软雅黑,Arial,sans-serif;}
|
||||
.clearfix {clear:both;zoom:1;}
|
||||
.clearfix:before,.clearfix:after {content:"\0020";display:block;height:0;visibility:hidden;}
|
||||
.clearfix:after {clear:both;}
|
||||
body.error-page-wrapper,.error-page-wrapper.preview {background-position:center center;background-repeat:no-repeat;background-size:cover;position:relative;}
|
||||
.error-page-wrapper .content-container {border-radius:2px;text-align:center;box-shadow:1px 1px 1px rgba(99,99,99,0.1);padding:50px;background-color:#fff;width:100%;max-width:560px;position:absolute;left:50%;top:50%;margin-top:-220px;margin-left:-280px;}
|
||||
.error-page-wrapper .content-container.in {left:0px;opacity:1;}
|
||||
.error-page-wrapper .head-line {transition:color .2s linear;font-size:40px;line-height:60px;letter-spacing:-1px;margin-bottom:20px;color:#777;}
|
||||
.error-page-wrapper .subheader {transition:color .2s linear;font-size:32px;line-height:46px;color:#494949;}
|
||||
.error-page-wrapper .hr {height:1px;background-color:#eee;width:80%;max-width:350px;margin:25px auto;}
|
||||
.error-page-wrapper .context {transition:color .2s linear;font-size:16px;line-height:27px;color:#aaa;}
|
||||
.error-page-wrapper .context p {margin:0;}
|
||||
.error-page-wrapper .context p:nth-child(n+2) {margin-top:16px;}
|
||||
.error-page-wrapper .buttons-container {margin-top:35px;overflow:hidden;}
|
||||
.error-page-wrapper .buttons-container a {transition:text-indent .2s ease-out,color .2s linear,background-color .2s linear;text-indent:0px;font-size:14px;text-transform:uppercase;text-decoration:none;color:#fff;background-color:#2ecc71;border-radius:99px;padding:8px 0 8px;text-align:center;display:inline-block;overflow:hidden;position:relative;width:45%;}
|
||||
.error-page-wrapper .buttons-container a:hover {text-indent:15px;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(1) {float:left;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(2) {float:right;}
|
||||
@media screen and (max-width:580px) {
|
||||
.error-page-wrapper {padding:30px 5%;}
|
||||
.error-page-wrapper .content-container {padding:37px;position:static;left:0;margin-top:0;margin-left:0;}
|
||||
.error-page-wrapper .head-line {font-size:36px;}
|
||||
.error-page-wrapper .subheader {font-size:27px;line-height:37px;}
|
||||
.error-page-wrapper .hr {margin:30px auto;width:215px;}
|
||||
}
|
||||
@media screen and (max-width:450px) {
|
||||
.error-page-wrapper {padding:30px;}
|
||||
.error-page-wrapper .head-line {font-size:32px;}
|
||||
.error-page-wrapper .hr {margin:25px auto;width:180px;}
|
||||
.error-page-wrapper .context {font-size:15px;line-height:22px;}
|
||||
.error-page-wrapper .context p:nth-child(n+2) {margin-top:10px;}
|
||||
.error-page-wrapper .buttons-container {margin-top:29px;}
|
||||
.error-page-wrapper .buttons-container a {float:none !important;width:65%;margin:0 auto;font-size:13px;padding:9px 0;}
|
||||
.error-page-wrapper .buttons-container a:nth-child(2) {margin-top:12px;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="error-page-wrapper">
|
||||
<div class="content-container">
|
||||
<div class="head-line">
|
||||
<img src="/static/common/images/error.svg" alt="" width="150" />
|
||||
</div>
|
||||
<div class="subheader">
|
||||
<?=$debug?$message:$lang['The page you are looking for is temporarily unavailable']?>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<div class="context">
|
||||
|
||||
<p>
|
||||
<?=$lang['You can return to the previous page and try again']?>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="buttons-container">
|
||||
<a href="/"><?=$lang['Home']?></a>
|
||||
<a href="/"><?=$lang['Feedback']?></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user