本文目录导读:

我来详细讲解如何用PHP搭建一个完整的预约系统,这是一个常见需求,可以从简单到复杂逐步实现。
系统架构设计
基础结构
appointment-system/
├── config/
│ └── database.php
├── includes/
│ ├── functions.php
│ └── validation.php
├── public/
│ ├── index.php
│ ├── book.php
│ └── admin/
├── templates/
│ └── ...
└── database/
└── schema.sql
数据库设计
-- appointments.sql
CREATE TABLE services (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
duration INT NOT NULL, -- 分钟
price DECIMAL(10,2),
description TEXT,
is_active BOOLEAN DEFAULT TRUE
);
CREATE TABLE time_slots (
id INT PRIMARY KEY AUTO_INCREMENT,
service_id INT,
date DATE NOT NULL,
start_time TIME NOT NULL,
end_time TIME NOT NULL,
is_booked BOOLEAN DEFAULT FALSE,
FOREIGN KEY (service_id) REFERENCES services(id)
);
CREATE TABLE appointments (
id INT PRIMARY KEY AUTO_INCREMENT,
time_slot_id INT,
customer_name VARCHAR(100) NOT NULL,
customer_email VARCHAR(100),
customer_phone VARCHAR(20),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('confirmed', 'cancelled', 'completed') DEFAULT 'confirmed',
FOREIGN KEY (time_slot_id) REFERENCES time_slots(id)
);
核心PHP代码
数据库连接配置
<?php
// config/database.php
class Database {
private $host = "localhost";
private $db_name = "appointment_system";
private $username = "root";
private $password = "";
public $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host=" . $this->host . ";dbname=" . $this->db_name,
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $exception) {
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
预约系统核心类
<?php
// includes/AppointmentSystem.php
class AppointmentSystem {
private $db;
private $conn;
public function __construct() {
$this->db = new Database();
$this->conn = $this->db->getConnection();
}
// 获取可用时间段
public function getAvailableSlots($service_id, $date) {
$query = "SELECT * FROM time_slots
WHERE service_id = :service_id
AND date = :date
AND is_booked = FALSE
ORDER BY start_time";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':service_id', $service_id);
$stmt->bindParam(':date', $date);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 创建预约
public function createAppointment($data) {
try {
$this->conn->beginTransaction();
// 检查时间段是否仍可用
$slot = $this->checkSlotAvailability(
$data['time_slot_id']
);
if (!$slot) {
throw new Exception("时间段已被预订");
}
// 插入预约记录
$query = "INSERT INTO appointments
(time_slot_id, customer_name, customer_email,
customer_phone, notes)
VALUES (:time_slot_id, :name, :email,
:phone, :notes)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':time_slot_id', $data['time_slot_id']);
$stmt->bindParam(':name', $data['customer_name']);
$stmt->bindParam(':email', $data['customer_email']);
$stmt->bindParam(':phone', $data['customer_phone']);
$stmt->bindParam(':notes', $data['notes']);
$stmt->execute();
// 更新时间段状态
$this->bookTimeSlot($data['time_slot_id']);
$this->conn->commit();
// 发送确认邮件(可选)
$this->sendConfirmation($data);
return ['success' => true, 'message' => '预约成功'];
} catch (Exception $e) {
$this->conn->rollBack();
return ['success' => false, 'message' => $e->getMessage()];
}
}
// 检查时间段可用性
private function checkSlotAvailability($slot_id) {
$query = "SELECT * FROM time_slots
WHERE id = :id AND is_booked = FALSE";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $slot_id);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 预订时间段
private function bookTimeSlot($slot_id) {
$query = "UPDATE time_slots SET is_booked = TRUE
WHERE id = :id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $slot_id);
return $stmt->execute();
}
// 生成时间段
public function generateTimeSlots($service_id, $date, $start_time, $end_time, $interval = 30) {
$time = strtotime($start_time);
$end = strtotime($end_time);
while ($time < $end) {
$slot_start = date('H:i:s', $time);
$time += $interval * 60;
$slot_end = date('H:i:s', $time);
// 检查是否已存在
if (!$this->slotExists($service_id, $date, $slot_start)) {
$query = "INSERT INTO time_slots
(service_id, date, start_time, end_time)
VALUES (:service_id, :date, :start, :end)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':service_id', $service_id);
$stmt->bindParam(':date', $date);
$stmt->bindParam(':start', $slot_start);
$stmt->bindParam(':end', $slot_end);
$stmt->execute();
}
}
}
private function slotExists($service_id, $date, $start_time) {
$query = "SELECT id FROM time_slots
WHERE service_id = :service_id
AND date = :date
AND start_time = :start_time";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':service_id', $service_id);
$stmt->bindParam(':date', $date);
$stmt->bindParam(':start_time', $start_time);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC) ? true : false;
}
}
前端预约页面
<?php
// public/book.php
require_once '../config/database.php';
require_once '../includes/AppointmentSystem.php';
$appointmentSystem = new AppointmentSystem();
// 获取所有服务
$query = "SELECT * FROM services WHERE is_active = TRUE";
$services = $conn->query($query)->fetchAll();
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = $appointmentSystem->createAppointment($_POST);
echo json_encode($result);
exit;
}
?>
<!DOCTYPE html>
<html>
<head>预约系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
</head>
<body>
<div class="container">
<h1>在线预约</h1>
<form id="appointmentForm" method="POST">
<!-- 选择服务 -->
<div class="form-group">
<label>选择服务</label>
<select name="service_id" id="serviceSelect" required>
<option value="">请选择...</option>
<?php foreach ($services as $service): ?>
<option value="<?= $service['id'] ?>"
data-duration="<?= $service['duration'] ?>">
<?= htmlspecialchars($service['name']) ?>
(<?= $service['duration'] ?>分钟)
</option>
<?php endforeach; ?>
</select>
</div>
<!-- 选择日期 -->
<div class="form-group">
<label>选择日期</label>
<input type="text" id="datePicker" name="date"
placeholder="选择日期..." required>
</div>
<!-- 选择时间 -->
<div class="form-group">
<label>选择时间</label>
<select name="time_slot_id" id="timeSlotSelect" required>
<option value="">先选择日期和服务</option>
</select>
</div>
<!-- 客户信息 -->
<div class="form-group">
<label>姓名</label>
<input type="text" name="customer_name" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" name="customer_email">
</div>
<div class="form-group">
<label>电话</label>
<input type="tel" name="customer_phone" required>
</div>
<div class="form-group">
<label>备注</label>
<textarea name="notes"></textarea>
</div>
<button type="submit">确认预约</button>
</form>
</div>
<script>
// 日期选择器
flatpickr("#datePicker", {
minDate: "today",
dateFormat: "Y-m-d",
disable: [
// 禁用周末
function(date) {
return (date.getDay() === 0 || date.getDay() === 6);
}
],
onChange: function(selectedDates, dateStr, instance) {
loadTimeSlots();
}
});
// 加载可用时间段
document.getElementById('serviceSelect').addEventListener('change', loadTimeSlots);
function loadTimeSlots() {
const serviceId = document.getElementById('serviceSelect').value;
const date = document.getElementById('datePicker').value;
const timeSlotSelect = document.getElementById('timeSlotSelect');
if (!serviceId || !date) {
timeSlotSelect.innerHTML = '<option value="">先选择日期和服务</option>';
return;
}
// AJAX请求获取可用时间段
fetch(`get_slots.php?service_id=${serviceId}&date=${date}`)
.then(response => response.json())
.then(slots => {
timeSlotSelect.innerHTML = '<option value="">选择时间段...</option>';
slots.forEach(slot => {
const option = document.createElement('option');
option.value = slot.id;
option.textContent = `${slot.start_time} - ${slot.end_time}`;
timeSlotSelect.appendChild(option);
});
});
}
// 表单提交
document.getElementById('appointmentForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('book.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('预约成功!');
window.location.reload();
} else {
alert('预约失败: ' + data.message);
}
});
});
</script>
</body>
</html>
获取可用时间段API
<?php
// public/get_slots.php
require_once '../config/database.php';
require_once '../includes/AppointmentSystem.php';
header('Content-Type: application/json');
if (!isset($_GET['service_id']) || !isset($_GET['date'])) {
echo json_encode([]);
exit;
}
$appointmentSystem = new AppointmentSystem();
$slots = $appointmentSystem->getAvailableSlots(
$_GET['service_id'],
$_GET['date']
);
echo json_encode($slots);
高级功能扩展
邮件通知
// includes/EmailNotifier.php
class EmailNotifier {
public function sendConfirmation($appointment) {
$to = $appointment['customer_email'];
$subject = "预约确认 - " . $appointment['service_name'];
$message = "
<html>
<body>
<h2>预约确认</h2>
<p>尊敬的 {$appointment['customer_name']},</p>
<p>您的预约已成功确认:</p>
<ul>
<li>服务:{$appointment['service_name']}</li>
<li>日期:{$appointment['date']}</li>
<li>时间:{$appointment['start_time']} - {$appointment['end_time']}</li>
</ul>
<p>如有变动请提前24小时通知我们。</p>
</body>
</html>
";
$headers = "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
return mail($to, $subject, $message, $headers);
}
}
管理后台
<?php
// public/admin/dashboard.php
session_start();
// 检查管理员权限
if (!isset($_SESSION['admin_logged_in'])) {
header('Location: login.php');
exit;
}
require_once '../../config/database.php';
// 获取今日预约
$query = "SELECT a.*, s.name as service_name,
ts.date, ts.start_time, ts.end_time
FROM appointments a
JOIN time_slots ts ON a.time_slot_id = ts.id
JOIN services s ON ts.service_id = s.id
WHERE ts.date = CURDATE()
ORDER BY ts.start_time";
$todayAppointments = $conn->query($query)->fetchAll();
?>
<!DOCTYPE html>
<html>
<head>管理后台 - 预约列表</title>
</head>
<body>
<h1>今日预约</h1>
<table>
<thead>
<tr>
<th>时间</th>
<th>客户</th>
<th>服务</th>
<th>电话</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($todayAppointments as $appointment): ?>
<tr>
<td><?= $appointment['start_time'] ?></td>
<td><?= htmlspecialchars($appointment['customer_name']) ?></td>
<td><?= $appointment['service_name'] ?></td>
<td><?= htmlspecialchars($appointment['customer_phone']) ?></td>
<td><?= $appointment['status'] ?></td>
<td>
<button onclick="cancelAppointment(<?= $appointment['id'] ?>)">
取消
</button>
<button onclick="completeAppointment(<?= $appointment['id'] ?>)">
完成
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
优化建议
性能优化
- 缓存:使用Redis缓存可用时间段
- 数据库索引:为常用查询字段添加索引
- CDN:静态资源使用CDN加载
安全措施
// includes/security.php
// CSRF保护
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// XSS防护
function sanitizeOutput($data) {
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
// SQL注入防护(使用PDO预处理语句)
部署建议
- 使用PHP 7.4以上版本
- 配置Nginx/Apache
- 启用HTTPS
- 设置适当的文件权限
- 定期备份数据库
这个预约系统可以满足大多数中小企业需求,你也可以根据具体业务需求进行定制扩展。