webman/plugin/admin/app/controller/InstallController.php

392 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace plugin\admin\app\controller;
use Illuminate\Database\Capsule\Manager;
use plugin\admin\app\common\Util;
use support\exception\BusinessException;
use support\Request;
use support\Response;
use Webman\Captcha\CaptchaBuilder;
/**
* 安装
*/
class InstallController extends Base
{
/**
* 不需要登录的方法
* @var string[]
*/
protected $noNeedLogin = ['step1', 'step2'];
/**
* 设置数据库
* @param Request $request
* @return Response
* @throws BusinessException|\Throwable
*/
public function step1(Request $request): Response
{
$database_config_file = base_path() . '/plugin/admin/config/database.php';
clearstatcache();
if (is_file($database_config_file)) {
return $this->json(1, '管理后台已经安装!如需重新安装,请删除该插件数据库配置文件并重启');
}
if (!class_exists(CaptchaBuilder::class) || !class_exists(Manager::class)) {
return $this->json(1, '请运行 composer require -W illuminate/database 安装illuminate/database组件并重启');
}
$user = $request->post('user');
$password = $request->post('password');
$database = $request->post('database');
$host = $request->post('host');
$port = (int)$request->post('port') ?: 3306;
$overwrite = $request->post('overwrite');
try {
$db = $this->getPdo($host, $user, $password, $port);
$smt = $db->query("show databases like '$database'");
if (empty($smt->fetchAll())) {
$db->exec("create database $database");
}
$db->exec("use $database");
$smt = $db->query("show tables");
$tables = $smt->fetchAll();
} catch (\Throwable $e) {
if (stripos($e, 'Access denied for user')) {
return $this->json(1, '数据库用户名或密码错误');
}
if (stripos($e, 'Connection refused')) {
return $this->json(1, 'Connection refused. 请确认数据库IP端口是否正确数据库已经启动');
}
if (stripos($e, 'timed out')) {
return $this->json(1, '数据库连接超时请确认数据库IP端口是否正确安全组及防火墙已经放行端口');
}
throw $e;
}
$tables_to_install = [
'wa_admins',
'wa_admin_roles',
'wa_roles',
'wa_rules',
'wa_options',
'wa_users',
'wa_uploads',
];
$tables_exist = [];
foreach ($tables as $table) {
$tables_exist[] = current($table);
}
$tables_conflict = array_intersect($tables_to_install, $tables_exist);
if (!$overwrite) {
if ($tables_conflict) {
return $this->json(1, '以下表' . implode(',', $tables_conflict) . '已经存在,如需覆盖请选择强制覆盖');
}
} else {
foreach ($tables_conflict as $table) {
$db->exec("DROP TABLE `$table`");
}
}
$sql_file = base_path() . '/plugin/admin/install.sql';
if (!is_file($sql_file)) {
return $this->json(1, '数据库SQL文件不存在');
}
$sql_query = file_get_contents($sql_file);
$sql_query = $this->removeComments($sql_query);
$sql_query = $this->splitSqlFile($sql_query, ';');
foreach ($sql_query as $sql) {
$db->exec($sql);
}
// 导入菜单
$menus = include base_path() . '/plugin/admin/config/menu.php';
// 安装过程中没有数据库配置无法使用api\Menu::import()方法
$this->importMenu($menus, $db);
$config_content = <<<EOF
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '$host',
'port' => '$port',
'database' => '$database',
'username' => '$user',
'password' => '$password',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
],
];
EOF;
file_put_contents($database_config_file, $config_content);
$think_orm_config = <<<EOF
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '$host',
// 数据库名
'database' => '$database',
// 数据库用户名
'username' => '$user',
// 数据库密码
'password' => '$password',
// 数据库连接端口
'hostport' => $port,
// 数据库连接参数
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8mb4',
// 数据库表前缀
'prefix' => '',
// 断线重连
'break_reconnect' => true,
// 关闭SQL监听日志
'trigger_sql' => true,
// 自定义分页类
'bootstrap' => ''
],
],
];
EOF;
file_put_contents(base_path() . '/plugin/admin/config/thinkorm.php', $think_orm_config);
// 尝试reload
if (function_exists('posix_kill')) {
set_error_handler(function () {});
posix_kill(posix_getppid(), SIGUSR1);
restore_error_handler();
}
return $this->json(0);
}
/**
* 设置管理员
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function step2(Request $request): Response
{
$username = $request->post('username');
$password = $request->post('password');
$password_confirm = $request->post('password_confirm');
if ($password != $password_confirm) {
return $this->json(1, '两次密码不一致');
}
if (!is_file($config_file = base_path() . '/plugin/admin/config/database.php')) {
return $this->json(1, '请先完成第一步数据库配置');
}
$config = include $config_file;
$connection = $config['connections']['mysql'];
$pdo = $this->getPdo($connection['host'], $connection['username'], $connection['password'], $connection['port'], $connection['database']);
if ($pdo->query('select * from `wa_admins`')->fetchAll()) {
return $this->json(1, '后台已经安装完毕,无法通过此页面创建管理员');
}
$smt = $pdo->prepare("insert into `wa_admins` (`username`, `password`, `nickname`, `created_at`, `updated_at`) values (:username, :password, :nickname, :created_at, :updated_at)");
$time = date('Y-m-d H:i:s');
$data = [
'username' => $username,
'password' => Util::passwordHash($password),
'nickname' => '超级管理员',
'created_at' => $time,
'updated_at' => $time
];
foreach ($data as $key => $value) {
$smt->bindValue($key, $value);
}
$smt->execute();
$admin_id = $pdo->lastInsertId();
$smt = $pdo->prepare("insert into `wa_admin_roles` (`role_id`, `admin_id`) values (:role_id, :admin_id)");
$smt->bindValue('role_id', 1);
$smt->bindValue('admin_id', $admin_id);
$smt->execute();
$request->session()->flush();
return $this->json(0);
}
/**
* 添加菜单
* @param array $menu
* @param \PDO $pdo
* @return int
*/
protected function addMenu(array $menu, \PDO $pdo): int
{
$allow_columns = ['title', 'key', 'icon', 'href', 'pid', 'weight', 'type'];
$data = [];
foreach ($allow_columns as $column) {
if (isset($menu[$column])) {
$data[$column] = $menu[$column];
}
}
$time = date('Y-m-d H:i:s');
$data['created_at'] = $data['updated_at'] = $time;
$values = [];
foreach ($data as $k => $v) {
$values[] = ":$k";
}
$columns = array_keys($data);
foreach ($columns as $k => $column) {
$columns[$k] = "`$column`";
}
$sql = "insert into wa_rules (" .implode(',', $columns). ") values (" . implode(',', $values) . ")";
$smt = $pdo->prepare($sql);
foreach ($data as $key => $value) {
$smt->bindValue($key, $value);
}
$smt->execute();
return $pdo->lastInsertId();
}
/**
* 导入菜单
* @param array $menu_tree
* @param \PDO $pdo
* @return void
*/
protected function importMenu(array $menu_tree, \PDO $pdo)
{
if (is_numeric(key($menu_tree)) && !isset($menu_tree['key'])) {
foreach ($menu_tree as $item) {
$this->importMenu($item, $pdo);
}
return;
}
$children = $menu_tree['children'] ?? [];
unset($menu_tree['children']);
$smt = $pdo->prepare("select * from wa_rules where `key`=:key limit 1");
$smt->execute(['key' => $menu_tree['key']]);
$old_menu = $smt->fetch();
if ($old_menu) {
$pid = $old_menu['id'];
$params = [
'title' => $menu_tree['title'],
'icon' => $menu_tree['icon'] ?? '',
'key' => $menu_tree['key'],
];
$sql = "update wa_rules set title=:title, icon=:icon where `key`=:key";
$smt = $pdo->prepare($sql);
$smt->execute($params);
} else {
$pid = $this->addMenu($menu_tree, $pdo);
}
foreach ($children as $menu) {
$menu['pid'] = $pid;
$this->importMenu($menu, $pdo);
}
}
/**
* 去除sql文件中的注释
* @param $sql
* @return string
*/
protected function removeComments($sql): string
{
return preg_replace("/(\n--[^\n]*)/","", $sql);
}
/**
* 分割sql文件
* @param $sql
* @param $delimiter
* @return array
*/
function splitSqlFile($sql, $delimiter): array
{
$tokens = explode($delimiter, $sql);
$output = array();
$matches = array();
$token_count = count($tokens);
for ($i = 0; $i < $token_count; $i++) {
if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0))) {
$total_quotes = preg_match_all("/'/", $tokens[$i], $matches);
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches);
$unescaped_quotes = $total_quotes - $escaped_quotes;
if (($unescaped_quotes % 2) == 0) {
$output[] = $tokens[$i];
$tokens[$i] = "";
} else {
$temp = $tokens[$i] . $delimiter;
$tokens[$i] = "";
$complete_stmt = false;
for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++) {
$total_quotes = preg_match_all("/'/", $tokens[$j], $matches);
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches);
$unescaped_quotes = $total_quotes - $escaped_quotes;
if (($unescaped_quotes % 2) == 1) {
$output[] = $temp . $tokens[$j];
$tokens[$j] = "";
$temp = "";
$complete_stmt = true;
$i = $j;
} else {
$temp .= $tokens[$j] . $delimiter;
$tokens[$j] = "";
}
}
}
}
}
return $output;
}
/**
* 获取pdo连接
* @param $host
* @param $username
* @param $password
* @param $port
* @param $database
* @return \PDO
*/
protected function getPdo($host, $username, $password, $port, $database = null): \PDO
{
$dsn = "mysql:host=$host;port=$port;";
if ($database) {
$dsn .= "dbname=$database";
}
$params = [
\PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4",
\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_TIMEOUT => 5,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
];
return new \PDO($dsn, $username, $password, $params);
}
}