//一键安装向导功能规划与开发
1.功能目标
用户下载源码后,访问网站自动跳转到安装页面,填写数据库信息和管理员账号,一键完成安装。
2.核心文件
文件 位置 作用
public/index.php Laravel 入口文件 添加安装检测,无锁文件时跳转安装
public/install.php 独立安装向导 显示安装表单,执行安装逻辑
storage/install.lock 安装锁文件 标记已安装,防止重复安装
3、实现逻辑
(1)安装检测(public/index.php)
在文件最开头添加:
// 一键安装检测
$lockFile = __DIR__ . '/../storage/install.lock';
if (!file_exists($lockFile) && strpos($_SERVER['REQUEST_URI'], '/install.php') === false) {
header('Location: /install.php');
exit;
}
(2)安装向导(public/install.php)
工作流程
a. 检查 install.lock 是否存在
↓ 存在 → 跳转首页
↓ 不存在 → 显示安装表单
b. 用户提交表单
↓
c. 验证输入(数据库配置 + 管理员账号)
↓
d. 测试数据库连接
↓ 失败 → 返回错误
↓ 成功
e. 创建数据库(如果不存在)
f. 写入 .env 文件(生成 APP_KEY)
g. 创建数据表(纯 SQL,不依赖命令行)
h. 创建管理员用户
i. 创建默认站点配置
j. 创建默认页面(首页、关于我们)
k. 生成 install.lock 文件
l. 跳转首页
4.关键代码
(1)写入 .env 文件:
$appKey = 'base64:' . base64_encode(random_bytes(32));
$envContent = "APP_NAME=\"好站站企业建站引擎\"\n";
$envContent .= "APP_ENV=production\n";
$envContent .= "APP_KEY=" . $appKey . "\n";
// ... 其他配置
file_put_contents(ENV_PATH, $envContent);
(2)创建数据表(纯 SQL):
$pdo->exec("CREATE TABLE IF NOT EXISTS `sites` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`site_name` varchar(255) NOT NULL DEFAULT '好站站企业官网',
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
(3)创建管理员:
$hashed = password_hash($admin_pass, PASSWORD_BCRYPT);
$pdo->prepare("INSERT INTO users (...) VALUES (...)")
->execute([$admin_name, $admin_email, $hashed]);
5.完整代码实现
(1)修改 public/index.php 添加安装检测
<?php
// ========== 一键安装检测 ==========
$lockFile = __DIR__ . '/../storage/install.lock';
// 注意:install.php 和 index.php 在同一目录(public)
if (!file_exists($lockFile) && strpos($_SERVER['REQUEST_URI'], '/install.php') === false) {
header('Location: /install.php');
exit;
}
// ========== 一键安装检测结束 ==========
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());
(2)新增public/install.php 一键安装向导
<?php
/**
* 好站站企业建站引擎 - 一键安装向导
* 纯 PHP 实现,不依赖 exec(),跨平台兼容
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
define('ROOT_PATH', dirname(__DIR__));
define('ENV_PATH', ROOT_PATH . '/.env');
define('LOCK_FILE', ROOT_PATH . '/storage/install.lock');
// 如果已安装,跳转首页
if (file_exists(LOCK_FILE)) {
header('Location: /');
exit;
}
// 处理安装提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = [];
$db_host = trim($_POST['db_host'] ?? '127.0.0.1');
$db_port = trim($_POST['db_port'] ?? '3306');
$db_name = trim($_POST['db_name'] ?? '');
$db_user = trim($_POST['db_user'] ?? '');
$db_pass = $_POST['db_pass'] ?? '';
$admin_name = trim($_POST['admin_name'] ?? '管理员');
$admin_email = trim($_POST['admin_email'] ?? '');
$admin_pass = $_POST['admin_pass'] ?? '';
if (empty($db_name)) $errors[] = '请填写数据库名';
if (empty($db_user)) $errors[] = '请填写数据库用户名';
if (empty($admin_email)) $errors[] = '请填写管理员邮箱';
if (!filter_var($admin_email, FILTER_VALIDATE_EMAIL)) $errors[] = '管理员邮箱格式不正确';
if (strlen($admin_pass) < 6) $errors[] = '管理员密码至少6位';
// 测试数据库连接
if (empty($errors)) {
try {
$pdo = new PDO("mysql:host=$db_host;port=$db_port;charset=utf8mb4", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE DATABASE IF NOT EXISTS `$db_name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$pdo->exec("USE `$db_name`");
} catch (PDOException $e) {
$errors[] = '数据库连接失败:' . $e->getMessage();
}
}
if (empty($errors)) {
// 生成 APP_KEY
$appKey = 'base64:' . base64_encode(random_bytes(32));
// 写入 .env 文件
$envContent = '';
$envContent .= 'APP_NAME="好站站企业建站引擎"' . "\n";
$envContent .= 'APP_ENV=production' . "\n";
$envContent .= 'APP_KEY=' . $appKey . "\n";
$envContent .= 'APP_DEBUG=false' . "\n";
$envContent .= 'APP_URL=http://' . $_SERVER['HTTP_HOST'] . "\n";
$envContent .= "\n";
$envContent .= 'DB_CONNECTION=mysql' . "\n";
$envContent .= 'DB_HOST=' . $db_host . "\n";
$envContent .= 'DB_PORT=' . $db_port . "\n";
$envContent .= 'DB_DATABASE=' . $db_name . "\n";
$envContent .= 'DB_USERNAME=' . $db_user . "\n";
$envContent .= 'DB_PASSWORD=' . $db_pass . "\n";
$envContent .= "\n";
$envContent .= 'SESSION_DRIVER=database' . "\n";
$envContent .= 'QUEUE_CONNECTION=database' . "\n";
$envContent .= 'CACHE_STORE=database' . "\n";
file_put_contents(ENV_PATH, $envContent);
// 执行数据库迁移(纯 SQL,不依赖 exec)
$migrationFiles = glob(ROOT_PATH . '/database/migrations/*.php');
$migrateSuccess = true;
foreach ($migrationFiles as $file) {
$content = file_get_contents($file);
// 解析表名
preg_match('/Schema::create\([\'"]([^\'"]+)[\'"]/', $content, $matches);
if (!isset($matches[1])) {
continue;
}
$table = $matches[1];
// 检查表是否已存在
$result = $pdo->query("SHOW TABLES LIKE '$table'");
if ($result->rowCount() > 0) {
continue; // 表已存在,跳过
}
// 根据表名生成 SQL
$sql = $pdo->prepare("SELECT 1");
try {
if ($table === 'sites') {
$pdo->exec("CREATE TABLE IF NOT EXISTS `sites` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`site_name` varchar(255) NOT NULL DEFAULT '好站站企业官网',
`site_logo` varchar(500) DEFAULT '/logo.png',
`site_keywords` varchar(500) DEFAULT '企业建站,可视化建站,拖拽建站,好站站',
`site_description` text,
`created_at` timestamp NULL,
`updated_at` timestamp NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
} elseif ($table === 'pages') {
$pdo->exec("CREATE TABLE IF NOT EXISTS `pages` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`site_id` bigint unsigned NOT NULL,
`title` varchar(255) NOT NULL DEFAULT '新页面',
`slug` varchar(255) NOT NULL,
`is_home` tinyint(1) NOT NULL DEFAULT '0',
`status` tinyint(1) NOT NULL DEFAULT '0',
`created_at` timestamp NULL,
`updated_at` timestamp NULL,
FOREIGN KEY (`site_id`) REFERENCES `sites`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
} elseif ($table === 'page_components') {
$pdo->exec("CREATE TABLE IF NOT EXISTS `page_components` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`page_id` bigint unsigned NOT NULL,
`component_type` varchar(50) NOT NULL,
`content` json DEFAULT NULL,
`settings` json DEFAULT NULL,
`sort_order` int NOT NULL DEFAULT '0',
`created_at` timestamp NULL,
`updated_at` timestamp NULL,
FOREIGN KEY (`page_id`) REFERENCES `pages`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}
} catch (PDOException $e) {
$migrateSuccess = false;
$errors[] = "创建表 `$table` 失败:" . $e->getMessage();
break;
}
}
// 运行 Laravel 默认的迁移(users、sessions 等)
if ($migrateSuccess) {
try {
// 创建 users 表(如果不存在)
$pdo->exec("CREATE TABLE IF NOT EXISTS `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL UNIQUE,
`email_verified_at` timestamp NULL,
`password` varchar(255) NOT NULL,
`remember_token` varchar(100) DEFAULT NULL,
`created_at` timestamp NULL,
`updated_at` timestamp NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
// 创建 password_reset_tokens 表
$pdo->exec("CREATE TABLE IF NOT EXISTS `password_reset_tokens` (
`email` varchar(255) NOT NULL PRIMARY KEY,
`token` varchar(255) NOT NULL,
`created_at` timestamp NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
// 创建 sessions 表
$pdo->exec("CREATE TABLE IF NOT EXISTS `sessions` (
`id` varchar(255) NOT NULL PRIMARY KEY,
`user_id` bigint unsigned DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
`user_agent` text,
`payload` longtext NOT NULL,
`last_activity` int NOT NULL,
INDEX `sessions_user_id_index` (`user_id`),
INDEX `sessions_last_activity_index` (`last_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
// 创建 cache 表
$pdo->exec("CREATE TABLE IF NOT EXISTS `cache` (
`key` varchar(255) NOT NULL PRIMARY KEY,
`value` mediumtext NOT NULL,
`expiration` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
// 创建 jobs 表
$pdo->exec("CREATE TABLE IF NOT EXISTS `jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`queue` varchar(255) NOT NULL,
`payload` longtext NOT NULL,
`attempts` tinyint unsigned NOT NULL,
`reserved_at` int unsigned DEFAULT NULL,
`available_at` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL,
INDEX `jobs_queue_index` (`queue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
} catch (PDOException $e) {
$migrateSuccess = false;
$errors[] = '创建系统表失败:' . $e->getMessage();
}
}
if ($migrateSuccess) {
// 创建管理员用户
$hashed = password_hash($admin_pass, PASSWORD_BCRYPT);
$stmt = $pdo->prepare("INSERT INTO users (name, email, password, created_at, updated_at) VALUES (?, ?, ?, NOW(), NOW())");
$stmt->execute([$admin_name, $admin_email, $hashed]);
// 获取 site_id(刚创建的第一个站点)
$siteId = 1;
// 创建默认页面
$stmt = $pdo->prepare("INSERT INTO pages (site_id, title, slug, is_home, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NOW(), NOW())");
$stmt->execute([$siteId, '首页', 'index', 1, 1]);
$stmt->execute([$siteId, '关于我们', 'about', 0, 1]);
// 生成安装锁文件
file_put_contents(LOCK_FILE, date('Y-m-d H:i:s'));
// 安装完成,跳转首页
header('Location: /');
exit;
}
}
if (!empty($errors)) {
// 安装失败,删除 .env 回滚
if (file_exists(ENV_PATH)) {
unlink(ENV_PATH);
}
$errorMsg = implode('<br>', $errors);
echo "<script>alert('安装失败:\\n$errorMsg'); window.history.back();</script>";
exit;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>好站站企业建站引擎 - 一键安装</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; }
.container { max-width: 600px; margin: 50px auto; padding: 20px; }
.card { background: #fff; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); overflow: hidden; }
.header { background: #3b82f6; color: #fff; padding: 30px; text-align: center; }
.header h1 { font-size: 28px; margin-bottom: 8px; }
.header p { opacity: 0.9; }
.body { padding: 30px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; font-weight: 500; color: #333; }
input { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; }
input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.1); }
.row { display: flex; gap: 15px; }
.row .form-group { flex: 1; }
hr { margin: 20px 0; border: none; border-top: 1px solid #eee; }
button { width: 100%; padding: 12px; background: #3b82f6; color: #fff; border: none; border-radius: 8px; font-size: 16px; font-weight: 500; cursor: pointer; }
button:hover { background: #2563eb; }
.footer { text-align: center; padding: 20px; color: #888; font-size: 12px; border-top: 1px solid #eee; }
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="header">
<h1>好站站企业建站引擎</h1>
<p>一键安装,轻松建站</p>
</div>
<form method="post">
<div class="body">
<h3>📁 数据库配置</h3>
<div class="row">
<div class="form-group">
<label>数据库主机</label>
<input type="text" name="db_host" value="127.0.0.1" required>
</div>
<div class="form-group">
<label>端口</label>
<input type="text" name="db_port" value="3306" required>
</div>
</div>
<div class="form-group">
<label>数据库名</label>
<input type="text" name="db_name" placeholder="例如: haozhanzhan" required>
</div>
<div class="form-group">
<label>数据库用户名</label>
<input type="text" name="db_user" value="root" required>
</div>
<div class="form-group">
<label>数据库密码</label>
<input type="password" name="db_pass" placeholder="留空如果无密码">
</div>
<hr>
<h3>👤 管理员账号</h3>
<div class="form-group">
<label>姓名</label>
<input type="text" name="admin_name" value="管理员" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" name="admin_email" placeholder="admin@example.com" required>
</div>
<div class="form-group">
<label>密码(至少6位)</label>
<input type="password" name="admin_pass" required>
</div>
<button type="submit">开始安装</button>
</div>
</form>
<div class="footer">
© 2026 南京可道有思科技有限公司
</div>
</div>
</div>
</body>
</html>
(3)新增 storage/install.lock 安装运行时产生
(4)把install.lock添加到 .gitignore
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/install.lock
/storage/pail
/vendor
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/auth.json
/.fleet
/.idea
/.nova
/.vscode
/.zed