增加扩展机制定位文件;将common模块实现扩展模式;发布新版本;

This commit is contained in:
2023-09-25 17:07:38 +08:00
parent 3dcf336bbc
commit 184dae8185
65 changed files with 3093 additions and 2751 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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'));
}
}

View File

@@ -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;
}
}

View File

@@ -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('删除成功');
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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');
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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_';
}
}

View File

@@ -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('当前请求不合法!');
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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}");
}
}

View File

@@ -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,
];
}
}

View File

@@ -2,8 +2,8 @@
namespace app\commno\exception;
use Exception;
use extend\base\commno\exception\EventExceptionBase;
class EventException extends Exception
class EventException extends EventExceptionBase
{
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -2,8 +2,10 @@
namespace app\common\provider;
use base\common\provider\RequestBase;
// 应用请求对象类
class Request extends \think\Request
class Request extends RequestBase
{
protected $filter = [];
}

View File

@@ -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));
});
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}