如何用PHP项目搭建预约系统?

wen PHP项目 2

本文目录导读:

如何用PHP项目搭建预约系统?

  1. 系统架构设计
  2. 数据库设计
  3. 核心PHP代码
  4. 高级功能扩展
  5. 优化建议

我来详细讲解如何用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>

优化建议

性能优化

  1. 缓存:使用Redis缓存可用时间段
  2. 数据库索引:为常用查询字段添加索引
  3. 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预处理语句)

部署建议

  1. 使用PHP 7.4以上版本
  2. 配置Nginx/Apache
  3. 启用HTTPS
  4. 设置适当的文件权限
  5. 定期备份数据库

这个预约系统可以满足大多数中小企业需求,你也可以根据具体业务需求进行定制扩展。

抱歉,评论功能暂时关闭!