要实现一个**可交互的数据库连接配置界面**,让用户在安装过程中输入 `host`、`username`、`password`、`dbname` 等信息,并自动写入 `config/db.php` 文件,我们需要:
1. 提供一个表单供用户填写数据库连接参数
2. 测试连接是否成功
3. 动态生成 `db.php` 配置文件
4. 安全地保存配置(避免敏感信息暴露)
---
## ✅ 最终目标
允许用户通过浏览器访问 `install.php` 时:
- 输入自己的数据库连接信息
- 点击“测试连接”或“下一步”
- 系统尝试连接 MySQL
- 成功后将配置写入 `config/db.php`
- 继续后续安装流程(如导入 SQL 结构)
---
### 📄 完整代码:支持数据库配置的安装向导
```php
<?php
// install.php - 支持用户自定义数据库配置的安装向导
session_start();
// 检查是否已安装(通过锁文件)
if (file_exists('config/installed.lock')) {
die('<h3 style="color:red;">⚠️ 系统已安装完毕!请删除 install.php 文件以确保安全。</h3>');
}
$step = $_GET['step'] ?? 'form'; // 步骤:form => test => install
$message = '';
$host = $username = $password = $dbname = $group_name = $admin_user = $admin_pass = '';
// 如果是 POST 请求,接收数据
if ($_POST) {
$host = trim($_POST['db_host'] ?? 'localhost');
$username = trim($_POST['db_username']);
$password = $_POST['db_password'];
$dbname = trim($_POST['db_name'] ?? 'leave_db');
$group_name = trim($_POST['group_name'] ?? '中国XX集团');
$admin_user = trim($_POST['admin_username'] ?? 'admin');
$admin_pass = $_POST['admin_password'];
$confirm_pass = $_POST['confirm_password'];
// 保留数据用于回显
$_SESSION['setup_data'] = compact('host', 'username', 'password', 'dbname', 'group_name', 'admin_user', 'admin_pass');
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>系统安装向导</title>
<style>
body { font-family: 'Segoe UI', sans-serif; padding: 40px; background: #f0f2f5; }
.container { max-width: 700px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
h2 { color: #007cba; border-bottom: 2px solid #eee; padding-bottom: 10px; }
label { display: block; margin-top: 15px; font-weight: bold; color: #333; }
input[type="text"], input[type="password"] { width: 100%; padding: 10px; margin-top: 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 16px; }
button { margin-top: 20px; padding: 12px 24px; background: #007cba; color: white; border: none; cursor: pointer; font-size: 16px; border-radius: 4px; }
button:hover { background: #005a87; }
.error { color: red; margin: 15px 0; }
.success { color: green; margin: 15px 0; }
pre { background: #f5f5f5; padding: 10px; border: 1px solid #ddd; border-radius: 4px; overflow-x: auto; }
.tab { margin-top: 20px; }
.tab button { margin-right: 10px; background: #e0e0e0; color: #333; }
.tab button.active { background: #007cba; color: white; }
</style>
</head>
<body>
<div class="container">
<h2>🔧 单位管理系统 - 安装向导</h2>
<?php if ($step === 'form'): ?>
<p>请填写数据库连接信息:</p>
<form method="post" action="?step=test">
<label>数据库主机:</label>
<input type="text" name="db_host" value="<?= htmlspecialchars($host ?: 'localhost') ?>" required>
<label>用户名:</label>
<input type="text" name="db_username" value="<?= htmlspecialchars($username) ?>" required>
<label>密码:</label>
<input type="password" name="db_password" value="<?= htmlspecialchars($password) ?>">
<label>数据库名:</label>
<input type="text" name="db_name" value="<?= htmlspecialchars($dbname ?: 'leave_db') ?>" required>
<h3>📌 初始数据设置</h3>
<label>集团名称:</label>
<input type="text" name="group_name" value="<?= htmlspecialchars($group_name ?: '中国XX集团') ?>">
<label>管理员账号:</label>
<input type="text" name="admin_username" value="<?= htmlspecialchars($admin_user ?: 'admin') ?>" required>
<label>管理员密码:</label>
<input type="password" name="admin_password" required>
<label>确认密码:</label>
<input type="password" name="confirm_password" required>
<button type="submit">→ 测试数据库连接</button>
</form>
<?php endif; ?>
<?php if ($step === 'test'): ?>
<?php
// 接收并验证数据
$host = $_POST['db_host'] ?? 'localhost';
$username = $_POST['db_username'] ?? '';
$password = $_POST['db_password'] ?? '';
$dbname = $_POST['db_name'] ?? 'leave_db';
$group_name = $_POST['group_name'] ?? '中国XX集团';
$admin_user = $_POST['admin_username'] ?? 'admin';
$admin_pass = $_POST['admin_password'] ?? '';
$confirm_pass = $_POST['confirm_password'] ?? '';
if (empty($username)) {
$message = "数据库用户名不能为空。";
} elseif ($admin_pass !== $confirm_pass) {
$message = "两次管理员密码不一致!";
} elseif (strlen($admin_pass) < 6) {
$message = "管理员密码不能少于6位。";
} else {
try {
// 尝试连接数据库
$pdo = new PDO("mysql:host=$host;charset=utf8mb4", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 检查数据库是否存在,不存在则创建
$pdo->exec("CREATE DATABASE IF NOT EXISTS `$dbname` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;");
$pdo->exec("USE `$dbname`;");
// 保存配置到 session
$_SESSION['db_config'] = [
'host' => $host,
'username' => $username,
'password' => $password,
'dbname' => $dbname,
'group_name' => $group_name,
'admin_user' => $admin_user,
'admin_pass' => $admin_pass,
];
echo "<div class='success'>✅ 数据库连接成功!</div>";
echo "<p>准备初始化数据结构...</p>";
echo "<form method='post' action='?step=install'>";
foreach ($_POST as $k => $v) {
if ($k !== 'confirm_password') {
echo "<input type='hidden' name='" . htmlspecialchars($k) . "' value='" . htmlspecialchars($v) . "'>";
}
}
echo "<button type='submit'>→ 开始安装系统</button></form>";
goto end;
} catch (PDOException $e) {
$message = "数据库连接失败:" . $e->getMessage();
}
}
?>
<p class="error"><?= htmlspecialchars($message) ?></p>
<a href="?step=form"><button>← 返回修改</button></a>
<?php endif; ?>
<?php if ($step === 'install'): ?>
<?php
// 获取之前提交的数据
$host = $_POST['db_host'] ?? 'localhost';
$username = $_POST['db_username'];
$password = $_POST['db_password'];
$dbname = $_POST['db_name'];
$group_name = $_POST['group_name'];
$admin_user = $_POST['admin_username'];
$admin_pass = $_POST['admin_password'];
// 1. 生成 db.php 配置文件
$configContent = <<<PHP
<?php
// config/db.php - 自动生成于 " . date('Y-m-d H:i:s') . "
\$host = '$host';
\$dbname = '$dbname';
\$username = '$username';
\$password = '$password';
try {
\$pdo = new PDO("mysql:host=\$host;dbname=\$dbname;charset=utf8mb4", \$username, \$password);
\$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException \$e) {
die("数据库连接失败: " . \$e->getMessage());
}
?>
PHP;
// 写入配置文件
\$written = file_put_contents('config/db.php', \$configContent);
if (!\$written) {
\$message = "❌ 无法写入 config/db.php,请检查目录权限(需要可写)";
} else {
// 重新包含刚刚写入的配置
require_once 'config/db.php';
// 初始化数据库结构(简化版,你也可以读取 sql/leave_db.sql)
try {
initDatabase(\$pdo, \$admin_user, \$admin_pass, \$group_name, \$dbname);
// 创建安装完成标记
file_put_contents('config/installed.lock', time());
echo "<div class='success'>
<h3>🎉 安装成功!</h3>
<p>系统数据库和管理员账户已创建。</p>
<p><strong>登录账号:</strong> $admin_user</p>
<p><strong>登录密码:</strong> 已设置(请妥善保管)</p>
<a href='auth/login.php'><button>➡️ 前往登录页</button></a>
</div>";
// 安全提醒
echo "<p style='color:#999;font-size:14px;margin-top:20px;'>
⚠️ 为安全起见,请立即删除 <code>install.php</code> 文件。
</p>";
goto end;
} catch (Exception \$e) {
\$message = "初始化数据失败:" . \$e->getMessage();
}
}
?>
<p class="error"><?= htmlspecialchars(\$message) ?></p>
<a href="?step=form"><button>← 返回重试</button></a>
<?php endif; ?>
<?php
end:
?>
</div>
<?php
// 辅助函数:初始化数据库表结构和初始数据
function initDatabase(\$pdo, \$adminUser, \$adminPass, \$groupName, \$groupCode = 'GROUP')
{
\$hashedPass = password_hash(\$adminPass, PASSWORD_DEFAULT);
\$pdo->exec("DROP TABLE IF EXISTS organizations, accounts, approval_flows, home_stay_records");
\$pdo->exec("CREATE TABLE organizations (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
code VARCHAR(50) UNIQUE NOT NULL,
level TINYINT NOT NULL CHECK (level BETWEEN 1 AND 5),
parent_id INT NULL,
path VARCHAR(255) NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES organizations(id) ON DELETE CASCADE,
INDEX idx_path (path)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
\$pdo->exec("CREATE TABLE accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
real_name VARCHAR(50) NOT NULL,
org_id INT NOT NULL,
role ENUM('super_admin','dept_admin','approver_l1','approver_l2','approver_l3') DEFAULT 'dept_admin',
status ENUM('active','disabled') DEFAULT 'active',
last_login_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (org_id) REFERENCES organizations(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
\$pdo->exec("CREATE TABLE approval_flows (
id INT AUTO_INCREMENT PRIMARY KEY,
org_id INT NOT NULL,
flow_type ENUM('leave','home_stay') NOT NULL,
levels TINYINT NOT NULL DEFAULT 2,
approver_l1_org_id INT NULL,
approver_l2_org_id INT NULL,
approver_l3_org_id INT NULL,
FOREIGN KEY (org_id) REFERENCES organizations(id),
UNIQUE(org_id, flow_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
\$pdo->exec("CREATE TABLE home_stay_records (
id INT AUTO_INCREMENT PRIMARY KEY,
employee_name VARCHAR(50) NOT NULL,
employee_id_card VARCHAR(18) NULL,
org_id INT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
total_days INT AS (DATEDIFF(end_date, start_date)+1) STORED,
reason TEXT,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('pending','approved','rejected') DEFAULT 'pending',
approver_notes TEXT,
approved_by INT NULL,
approved_at TIMESTAMP NULL,
FOREIGN KEY (org_id) REFERENCES organizations(id),
FOREIGN KEY (approved_by) REFERENCES accounts(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
// 插入组织结构
\$pdo->exec("INSERT INTO organizations (name, code, level, parent_id, path) VALUES
('$groupName', '$groupCode', 1, NULL, '$groupCode'),
('华东分公司', 'EAST_CHINA', 2, 1, '$groupCode/EAST_CHINA'),
('人力资源部', 'HR', 3, 2, '$groupCode/EAST_CHINA/HR'),
('招聘科', 'RECRUIT', 4, 3, '$groupCode/EAST_CHINA/HR/RECRUIT'),
('一线岗位A', 'POST_A', 5, 4, '$groupCode/EAST_CHINA/HR/RECRUIT/POST_A')");
// 插入超级管理员
\$pdo->exec("INSERT INTO accounts (username, password, real_name, org_id, role) VALUES
('$adminUser', '$hashedPass', '系统管理员', 1, 'super_admin')");
// 设置审批流
\$pdo->exec("INSERT INTO approval_flows (org_id, flow_type, levels, approver_l1_org_id, approver_l2_org_id, approver_l3_org_id) VALUES
(3, 'leave', 3, 4, 2, 1),
(3, 'home_stay', 2, 4, 2, NULL)");
}
?>
```
---
## ✅ 使用说明
### 目录结构要求
```
your-project/
├── config/
│ └── db.php # 自动生成
│ └── installed.lock # 安装完成后生成
├── sql/
│ └── leave_db.sql # 可选:更复杂的SQL脚本
├── auth/
│ └── login.php
├── install.php # 本文件
└── ...
```
### 权限要求
确保 `config/` 目录对 Web 服务器可写:
```bash
chmod 755 config/
# 或 Linux 上:
sudo chown www-data:www-data config/ -R
```
---
## 🔐 安全建议
| 措施 | 说明 |
|------|------|
| ✅ 删除 `install.php` | 安装完成后必须删除,防止被他人重置系统 |
| ✅ 锁文件保护 | `installed.lock` 防止重复安装 |
| ✅ 密码加密存储 | 使用 `password_hash()` |
| ✅ 防 XSS 输出 | 所有用户输入用 `htmlspecialchars()` 转义 |
| ✅ 不暴露异常细节 | 生产环境关闭错误显示 |
---