249 lines
14 KiB
PHP
249 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* 自由云商城 · 安装向导
|
|
* 步骤:1 环境检测 → 2 填写数据库/管理员 → 3 执行安装 → done 完成
|
|
* 本文件为独立脚本,不依赖 config.php。
|
|
*/
|
|
session_start();
|
|
|
|
$step = isset($_GET['step']) ? (string) $_GET['step'] : '1';
|
|
$force = isset($_GET['force']);
|
|
$installed = file_exists(__DIR__ . '/config.php');
|
|
|
|
// 已安装且非强制重装:提示已安装
|
|
if ($installed && !$force && $step !== 'done') {
|
|
showInstalled();
|
|
exit;
|
|
}
|
|
|
|
// ====================== 步骤 3:执行安装 ======================
|
|
if ($step === '3' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$host = trim($_POST['db_host'] ?? '');
|
|
$port = trim($_POST['db_port'] ?? '3306');
|
|
$dbname = trim($_POST['db_name'] ?? '');
|
|
$dbuser = trim($_POST['db_user'] ?? '');
|
|
$dbpass = $_POST['db_pass'] ?? '';
|
|
$prefix = trim($_POST['db_prefix'] ?? 'fnw_');
|
|
$sitename = trim($_POST['site_name'] ?? '自由云商城');
|
|
$adminuser = trim($_POST['admin_user'] ?? '');
|
|
$adminpass = $_POST['admin_pass'] ?? '';
|
|
$adminpass2= $_POST['admin_pass2'] ?? '';
|
|
$adminemail= trim($_POST['admin_email'] ?? '');
|
|
|
|
$errors = [];
|
|
if ($host === '') $errors[] = '请填写数据库主机';
|
|
if (!ctype_digit($port)) $errors[] = '端口必须为数字';
|
|
if (!preg_match('/^[A-Za-z0-9_]+$/', $dbname)) $errors[] = '数据库名只能含字母、数字、下划线';
|
|
if ($dbuser === '') $errors[] = '请填写数据库用户名';
|
|
if (!preg_match('/^[A-Za-z0-9_]+$/', $prefix)) $errors[] = '表前缀只能含字母、数字、下划线';
|
|
if ($adminuser === '' || strlen($adminuser) < 3) $errors[] = '管理员账号至少 3 个字符';
|
|
if (strlen($adminpass) < 6) $errors[] = '管理员密码至少 6 位';
|
|
if ($adminpass !== $adminpass2) $errors[] = '两次输入的密码不一致';
|
|
if ($adminemail !== '' && !filter_var($adminemail, FILTER_VALIDATE_EMAIL)) $errors[] = '管理员邮箱格式不正确';
|
|
|
|
if (empty($errors)) {
|
|
try {
|
|
// 1) 连接 MySQL(不含库名),创建数据库
|
|
$opts = [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
];
|
|
$pdo = new PDO("mysql:host={$host};port={$port};charset=utf8mb4", $dbuser, $dbpass, $opts);
|
|
$pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbname}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
|
$pdo = new PDO("mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4", $dbuser, $dbpass, $opts);
|
|
|
|
// 2) 导入表结构与初始数据
|
|
$sql = file_get_contents(__DIR__ . '/db.sql');
|
|
$sql = preg_replace('/^--.*$/m', '', $sql); // 去掉 -- 注释行
|
|
$sql = str_replace('__PREFIX__', $prefix, $sql);
|
|
$stmts = preg_split('/;\s*\n/', $sql);
|
|
foreach ($stmts as $s) {
|
|
$s = trim($s);
|
|
if ($s === '') continue;
|
|
$pdo->exec($s);
|
|
}
|
|
|
|
// 3) 创建管理员账号(同时生成唯一邀请码,避免 uk_invite_code 冲突)
|
|
$hash = password_hash($adminpass, PASSWORD_DEFAULT);
|
|
$adminCode = strtoupper(substr(md5(uniqid('admin', true)), 0, 8));
|
|
$pdo->prepare("INSERT INTO `{$prefix}users` (username,password,email,invite_code,is_admin,role,created_at) VALUES (?,?,?,?,1,'admin',NOW())")
|
|
->execute([$adminuser, $hash, $adminemail, $adminCode]);
|
|
|
|
// 4) 生成 config.php
|
|
$cfg = "<?php\n";
|
|
$cfg .= "// 本文件由安装程序自动生成,请勿手动修改\n";
|
|
$cfg .= "define('DB_HOST', " . var_export($host, true) . ");\n";
|
|
$cfg .= "define('DB_PORT', " . var_export($port, true) . ");\n";
|
|
$cfg .= "define('DB_NAME', " . var_export($dbname, true) . ");\n";
|
|
$cfg .= "define('DB_USER', " . var_export($dbuser, true) . ");\n";
|
|
$cfg .= "define('DB_PASS', " . var_export($dbpass, true) . ");\n";
|
|
$cfg .= "define('DB_PREFIX', " . var_export($prefix, true) . ");\n";
|
|
$cfg .= "define('SITE_NAME', " . var_export($sitename, true) . ");\n";
|
|
$cfg .= "define('SITE_KEY', " . var_export(bin2hex(random_bytes(16)), true) . ");\n";
|
|
$cfg .= "define('INSTALLED', true);\n";
|
|
|
|
$cfgPath = __DIR__ . '/config.php';
|
|
$written = @file_put_contents($cfgPath, $cfg);
|
|
if ($written === false) {
|
|
// 无法写入:提示手动保存
|
|
showManualConfig($cfg);
|
|
exit;
|
|
}
|
|
header('Location: install.php?step=done');
|
|
exit;
|
|
} catch (Throwable $e) {
|
|
$errors[] = '安装失败:' . $e->getMessage();
|
|
}
|
|
}
|
|
// 出错则回到表单(下方会渲染步骤 2 并显示错误)
|
|
$step = '2';
|
|
}
|
|
|
|
// ====================== 视图渲染 ======================
|
|
$title = '自由云商城 · 安装向导';
|
|
$cssVer = file_exists(__DIR__ . '/assets/style.css') ? filemtime(__DIR__ . '/assets/style.css') : time();
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= htmlspecialchars($title) ?></title>
|
|
<link rel="stylesheet" href="assets/vendor/fontawesome/all.min.css">
|
|
<link rel="stylesheet" href="assets/style.css?v=<?= $cssVer ?>">
|
|
<style>
|
|
.install-wrap{max-width:720px;margin:40px auto;}
|
|
.install-card{background:var(--surface);border:1px solid var(--outline);border-radius:14px;padding:34px;box-shadow:0 10px 40px rgba(0,0,0,.12);}
|
|
.install-card h1{color:var(--primary);font-size:1.6rem;margin-bottom:6px;}
|
|
.install-sub{color:var(--muted);margin-bottom:22px;}
|
|
.check-list{list-style:none;padding:0;margin:0 0 22px;}
|
|
.check-list li{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--outline);border-radius:8px;margin-bottom:8px;}
|
|
.check-list .ok{color:#22c55e;}.check-list .bad{color:#ef4444;}
|
|
.err-box{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c;padding:12px 16px;border-radius:8px;margin-bottom:18px;}
|
|
.err-box li{margin-left:18px;}
|
|
.step-bar{display:flex;gap:8px;margin-bottom:24px;}
|
|
.step-bar .s{flex:1;text-align:center;padding:8px;font-size:.85rem;border-radius:8px;background:var(--secondary);color:var(--muted);}
|
|
.step-bar .s.active{background:var(--primary);color:#fff;}
|
|
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px;}
|
|
.form-grid label{display:flex;flex-direction:column;gap:6px;font-size:.9rem;color:var(--muted);}
|
|
.form-grid input{padding:10px 12px;border:2px solid var(--outline);border-radius:8px;font-size:1rem;background:var(--bg);color:var(--text);font-family:inherit;}
|
|
.form-grid .span2{grid-column:1 / -1;}
|
|
@media (max-width:600px){.form-grid{grid-template-columns:1fr;}}
|
|
</style>
|
|
</head>
|
|
<body data-theme="light">
|
|
<div class="install-wrap">
|
|
<div class="install-card">
|
|
<h1><i class="fas fa-store"></i> 自由云商城安装向导</h1>
|
|
<div class="install-sub">只需几步,填写数据库信息即可完成安装。</div>
|
|
<div class="step-bar">
|
|
<div class="s <?= $step==='1'?'active':'' ?>">1. 环境检测</div>
|
|
<div class="s <?= $step==='2'?'active':'' ?>">2. 数据库设置</div>
|
|
<div class="s <?= $step==='3'||$step==='done'?'active':'' ?>">3. 完成</div>
|
|
</div>
|
|
|
|
<?php if ($step === '1'): ?>
|
|
<?php
|
|
$checks = [
|
|
['PHP 版本 ≥ 7.0', version_compare(PHP_VERSION, '7.0.0', '>='), '当前版本 ' . PHP_VERSION],
|
|
['PDO 扩展', extension_loaded('pdo'), '用于数据库连接'],
|
|
['PDO_MYSQL 扩展', extension_loaded('pdo_mysql'), '用于 MySQL 连接'],
|
|
['config.php 可写', is_writable(__DIR__), '需对商城目录有写权限'],
|
|
];
|
|
$allOk = true;
|
|
foreach ($checks as $c) { if (!$c[1]) $allOk = false; }
|
|
?>
|
|
<ul class="check-list">
|
|
<?php foreach ($checks as $c): ?>
|
|
<li>
|
|
<i class="fas <?= $c[1] ? 'fa-check-circle ok' : 'fa-times-circle bad' ?>"></i>
|
|
<span><strong><?= htmlspecialchars($c[0]) ?></strong> — <?= htmlspecialchars($c[2]) ?></span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php if ($allOk): ?>
|
|
<a href="install.php?step=2" class="btn btn-primary" style="display:inline-block;padding:12px 22px;">下一步:填写数据库信息 →</a>
|
|
<?php else: ?>
|
|
<p class="err-box">请先满足上述环境要求(尤其启用 pdo_mysql 扩展,并确保目录可写)后再继续。</p>
|
|
<?php endif; ?>
|
|
|
|
<?php elseif ($step === '2'): ?>
|
|
<?php if (!empty($errors)): ?>
|
|
<ul class="err-box"><?php foreach ($errors as $e): ?><li><?= htmlspecialchars($e) ?></li><?php endforeach; ?></ul>
|
|
<?php endif; ?>
|
|
<form method="post" action="install.php?step=3">
|
|
<h3 style="margin:6px 0 12px;color:var(--primary)"><i class="fas fa-database"></i> 数据库信息</h3>
|
|
<div class="form-grid">
|
|
<label>数据库主机<input name="db_host" value="<?= htmlspecialchars($_POST['db_host'] ?? 'localhost') ?>" required></label>
|
|
<label>端口<input name="db_port" value="<?= htmlspecialchars($_POST['db_port'] ?? '3306') ?>" required></label>
|
|
<label>数据库名<input name="db_name" value="<?= htmlspecialchars($_POST['db_name'] ?? 'freenw_mall') ?>" required></label>
|
|
<label>表前缀<input name="db_prefix" value="<?= htmlspecialchars($_POST['db_prefix'] ?? 'fnw_') ?>"></label>
|
|
<label>数据库用户名<input name="db_user" value="<?= htmlspecialchars($_POST['db_user'] ?? 'root') ?>" required></label>
|
|
<label>数据库密码<input type="text" name="db_pass" value="<?= htmlspecialchars($_POST['db_pass'] ?? '') ?>" placeholder="无密码可留空"></label>
|
|
</div>
|
|
<h3 style="margin:18px 0 12px;color:var(--primary)"><i class="fas fa-cog"></i> 站点与管理员</h3>
|
|
<div class="form-grid">
|
|
<label>站点名称<input name="site_name" value="<?= htmlspecialchars($_POST['site_name'] ?? '自由云商城') ?>"></label>
|
|
<label>管理员账号<input name="admin_user" value="<?= htmlspecialchars($_POST['admin_user'] ?? '') ?>" placeholder="至少3位" required></label>
|
|
<label>管理员密码<input type="password" name="admin_pass" placeholder="至少6位" required></label>
|
|
<label>确认密码<input type="password" name="admin_pass2" required></label>
|
|
<label style="grid-column:1/-1">管理员邮箱<input name="admin_email" value="<?= htmlspecialchars($_POST['admin_email'] ?? '') ?>" placeholder="选填"></label>
|
|
</div>
|
|
<button class="btn btn-primary" style="margin-top:18px;width:100%;padding:13px;">开始安装</button>
|
|
</form>
|
|
|
|
<?php elseif ($step === 'done'): ?>
|
|
<div style="text-align:center;padding:10px 0;">
|
|
<i class="fas fa-check-circle" style="font-size:56px;color:#22c55e;"></i>
|
|
<h2 style="margin:14px 0 6px;color:var(--primary)">安装成功!</h2>
|
|
<p class="install-sub">数据库已创建、数据表与初始商品已写入,管理员账号已生成。</p>
|
|
<div style="display:flex;gap:12px;justify-content:center;margin-top:18px;flex-wrap:wrap;">
|
|
<a href="index.php" class="btn btn-primary" style="padding:12px 22px;">进入商城前台 →</a>
|
|
<a href="admin/" class="btn btn-ghost" style="padding:12px 22px;">进入后台管理</a>
|
|
</div>
|
|
<p style="margin-top:18px;font-size:.85rem;color:var(--text-muted);">为安全起见,建议安装完成后删除或重命名 <code>install.php</code>。</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<p style="text-align:center;color:var(--text-muted);font-size:.85rem;margin-top:14px;">自由云科技团队 · 公益技术团队</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
|
|
<?php
|
|
// ====================== 辅助函数 ======================
|
|
function showInstalled() {
|
|
?>
|
|
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
|
|
<title>已安装</title>
|
|
<link rel="stylesheet" href="assets/vendor/fontawesome/all.min.css">
|
|
<link rel="stylesheet" href="assets/style.css?v=<?= filemtime(__DIR__ . '/assets/style.css') ?>"></head>
|
|
<body data-theme="light"><div class="install-wrap"><div class="install-card">
|
|
<h1><i class="fas fa-info-circle"></i> 商城已安装</h1>
|
|
<p class="install-sub">检测到 <code>config.php</code> 已存在,无需重复安装。</p>
|
|
<div style="display:flex;gap:12px;flex-wrap:wrap;">
|
|
<a href="index.php" class="btn btn-primary" style="padding:12px 22px;">进入商城前台</a>
|
|
<a href="admin/" class="btn btn-ghost" style="padding:12px 22px;">后台管理</a>
|
|
<a href="install.php?step=1&force=1" class="btn btn-ghost" style="padding:12px 22px;">强制重新安装</a>
|
|
</div>
|
|
<p style="margin-top:16px;font-size:.85rem;color:var(--text-muted);">如需重新安装,请先删除 <code>config.php</code>,或点击「强制重新安装」。</p>
|
|
</div></div></body></html>
|
|
<?php
|
|
}
|
|
|
|
function showManualConfig($cfg) {
|
|
?>
|
|
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
|
|
<title>手动保存配置</title>
|
|
<link rel="stylesheet" href="assets/vendor/fontawesome/all.min.css">
|
|
<link rel="stylesheet" href="assets/style.css?v=<?= filemtime(__DIR__ . '/assets/style.css') ?>"></head>
|
|
<body data-theme="light"><div class="install-wrap"><div class="install-card">
|
|
<h1><i class="fas fa-exclamation-triangle"></i> 无法写入 config.php</h1>
|
|
<p class="install-sub">数据库已初始化成功,但目录不可写。请手动创建 <code>mall/config.php</code> 并粘贴以下内容:</p>
|
|
<textarea style="width:100%;height:240px;font-family:monospace;" readonly><?= htmlspecialchars($cfg) ?></textarea>
|
|
<p style="margin-top:12px;font-size:.85rem;color:var(--text-muted);">保存后访问 <a href="index.php">index.php</a> 即可使用。</p>
|
|
</div></div></body></html>
|
|
<?php
|
|
}
|