怎样在PHP项目中实现图书借阅?

wen java案例 8

本文目录导读:

怎样在PHP项目中实现图书借阅?

  1. 第一步:数据库设计 (MySQL)
  2. 第二步:核心功能实现 (PHP + PDO)
  3. 第三步:使用示例 (前端调用)
  4. 第四步:关键注意事项
  5. 推荐的项目结构

在PHP项目中实现图书借阅功能,通常需要一个完整的 MVC(模型-视图-控制器) 结构,并配合数据库来管理用户、图书以及借阅记录。

以下是一个从数据库设计核心功能代码的完整实现方案。

第一步:数据库设计 (MySQL)

至少需要三张核心表:

-- 1. 用户表
CREATE TABLE `users` (
  `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `username` VARCHAR(50) NOT NULL UNIQUE,
  `password` VARCHAR(255) NOT NULL, -- 存储 password_hash() 的结果
  `email` VARCHAR(100),
  `status` TINYINT(1) DEFAULT 1, -- 1:正常, 0:禁用
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2. 图书表
CREATE TABLE `books` (
  `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `isbn` VARCHAR(20) UNIQUE, VARCHAR(255) NOT NULL,
  `author` VARCHAR(100),
  `total_quantity` INT(11) UNSIGNED DEFAULT 1, -- 总数量
  `available_quantity` INT(11) UNSIGNED DEFAULT 1, -- 当前可借数量
  `status` TINYINT(1) DEFAULT 1,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3. 借阅记录表
CREATE TABLE `borrow_records` (
  `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `user_id` INT(11) UNSIGNED NOT NULL,
  `book_id` INT(11) UNSIGNED NOT NULL,
  `borrow_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `due_date` DATETIME NOT NULL, -- 应还日期
  `return_date` DATETIME DEFAULT NULL, -- 实际还书日期,NULL表示未还
  `status` ENUM('borrowed', 'returned', 'overdue') DEFAULT 'borrowed',
  `fine` DECIMAL(10,2) DEFAULT 0.00, -- 罚款金额
  FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
  FOREIGN KEY (`book_id`) REFERENCES `books`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

第二步:核心功能实现 (PHP + PDO)

这里使用面向对象的方式封装核心功能。

数据库连接类 (Db.php)

<?php
class Database {
    private static $instance = null;
    private $connection;
    private $host = 'localhost';
    private $dbname = 'library_db';
    private $user = 'root';
    private $pass = '';
    private function __construct() {
        try {
            $this->connection = new PDO(
                "mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
                $this->user,
                $this->pass,
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false,
                ]
            );
        } catch (PDOException $e) {
            die("Database connection failed: " . $e->getMessage());
        }
    }
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    public function getConnection() {
        return $this->connection;
    }
}

借阅控制器 (BorrowController.php)

<?php
require_once 'Db.php';
class BorrowController {
    private $db;
    public function __construct() {
        $this->db = Database::getInstance()->getConnection();
    }
    /**
     * 借书操作
     * @param int $userId 用户ID
     * @param int $bookId 图书ID
     * @param int $borrowDays 借阅天数(默认30天)
     * @return array ['success' => bool, 'message' => string]
     */
    public function borrowBook($userId, $bookId, $borrowDays = 30) {
        try {
            // 开启事务
            $this->db->beginTransaction();
            // 1. 检查用户是否存在且未被禁用
            $userStmt = $this->db->prepare("SELECT id, status FROM users WHERE id = ?");
            $userStmt->execute([$userId]);
            $user = $userStmt->fetch();
            if (!$user) {
                throw new \Exception("用户不存在");
            }
            if ($user['status'] == 0) {
                throw new \Exception("该用户已被禁用,无法借书");
            }
            // 2. 检查图书是否可借
            $bookStmt = $this->db->prepare("SELECT id, available_quantity FROM books WHERE id = ? AND status = 1 FOR UPDATE");
            $bookStmt->execute([$bookId]);
            $book = $bookStmt->fetch();
            if (!$book) {
                throw new \Exception("图书不存在或已下架");
            }
            if ($book['available_quantity'] <= 0) {
                throw new \Exception("该书当前无库存可借");
            }
            // 3. 检查用户借阅上限(例如每人同时最多借5本)
            $countStmt = $this->db->prepare("SELECT COUNT(*) as cnt FROM borrow_records WHERE user_id = ? AND status = 'borrowed'");
            $countStmt->execute([$userId]);
            $borrowedCount = $countStmt->fetch()['cnt'];
            if ($borrowedCount >= 5) {
                throw new \Exception("您已达到最大借阅数量(5本),请先还书");
            }
            // 4. 执行扣减库存
            $updateBook = $this->db->prepare("UPDATE books SET available_quantity = available_quantity - 1 WHERE id = ? AND available_quantity > 0");
            $updateBook->execute([$bookId]);
            if ($updateBook->rowCount() === 0) {
                throw new \Exception("库存更新失败,可能库存不足");
            }
            // 5. 插入借阅记录
            $borrowDate = date('Y-m-d H:i:s');
            $dueDate = date('Y-m-d H:i:s', strtotime("+{$borrowDays} days"));
            $insertRecord = $this->db->prepare(
                "INSERT INTO borrow_records (user_id, book_id, borrow_date, due_date, status)
                 VALUES (?, ?, ?, ?, 'borrowed')"
            );
            $insertRecord->execute([$userId, $bookId, $borrowDate, $dueDate]);
            // 提交事务
            $this->db->commit();
            return ['success' => true, 'message' => '借书成功!应还日期:' . $dueDate];
        } catch (\Exception $e) {
            $this->db->rollBack();
            return ['success' => false, 'message' => '借书失败:' . $e->getMessage()];
        }
    }
    /**
     * 还书操作
     * @param int $recordId 借阅记录ID
     * @return array ['success' => bool, 'message' => string, 'fine' => float]
     */
    public function returnBook($recordId) {
        try {
            $this->db->beginTransaction();
            // 1. 获取借阅记录并加锁
            $recordStmt = $this->db->prepare(
                "SELECT * FROM borrow_records WHERE id = ? AND status = 'borrowed' FOR UPDATE"
            );
            $recordStmt->execute([$recordId]);
            $record = $recordStmt->fetch();
            if (!$record) {
                throw new \Exception("借阅记录不存在或已归还");
            }
            // 2. 计算逾期罚款(假设每天0.5元)
            $fine = 0.00;
            $returnDate = date('Y-m-d H:i:s');
            $dueDate = $record['due_date'];
            if (strtotime($returnDate) > strtotime($dueDate)) {
                $daysOverdue = ceil((strtotime($returnDate) - strtotime($dueDate)) / 86400);
                $fine = $daysOverdue * 0.50;
            }
            // 3. 更新记录
            $updateRecord = $this->db->prepare(
                "UPDATE borrow_records 
                 SET return_date = ?, status = 'returned', fine = ?
                 WHERE id = ? AND status = 'borrowed'"
            );
            $updateRecord->execute([$returnDate, $fine, $recordId]);
            // 4. 归还库存
            $updateBook = $this->db->prepare(
                "UPDATE books SET available_quantity = available_quantity + 1 WHERE id = ?"
            );
            $updateBook->execute([$record['book_id']]);
            $this->db->commit();
            $msg = '还书成功!';
            if ($fine > 0) {
                $msg .= " 共逾期 " . ($daysOverdue ?? 0) . " 天,产生罚款:¥" . number_format($fine, 2);
            }
            return ['success' => true, 'message' => $msg, 'fine' => $fine];
        } catch (\Exception $e) {
            $this->db->rollBack();
            return ['success' => false, 'message' => '还书失败:' . $e->getMessage()];
        }
    }
    /**
     * 查询用户当前的借阅列表
     * @param int $userId
     * @return array
     */
    public function getUserBorrowedBooks($userId) {
        $stmt = $this->db->prepare(
            "SELECT br.*, b.title, b.isbn, b.author
             FROM borrow_records br
             JOIN books b ON br.book_id = b.id
             WHERE br.user_id = ? AND br.status = 'borrowed'
             ORDER BY br.borrow_date DESC"
        );
        $stmt->execute([$userId]);
        return $stmt->fetchAll();
    }
    /**
     * 检查并更新逾期状态(可由定时任务调用)
     */
    public function updateOverdueStatus() {
        $stmt = $this->db->prepare(
            "UPDATE borrow_records 
             SET status = 'overdue' 
             WHERE status = 'borrowed' AND due_date < NOW()"
        );
        $stmt->execute();
        return $stmt->rowCount();
    }
}

第三步:使用示例 (前端调用)

<?php
require_once 'BorrowController.php';
$borrow = new BorrowController();
// 用户1借阅图书2,为期14天
$result = $borrow->borrowBook(1, 2, 14);
print_r($result); // ['success' => true, 'message' => '借书成功!...']

第四步:关键注意事项

  1. 事务与锁
    • 借书/还书操作必须放在事务中。
    • 查询库存时使用 SELECT ... FOR UPDATE(行级锁),防止并发情况下超借。
  2. 库存逻辑
    • 不要依赖代码中的 if (available > 0) 来判断,一定要在SQL中执行原子操作:UPDATE books SET available = available - 1 WHERE id = ? AND available > 0
  3. 安全性
    • 所有用户输入必须使用预处理语句(PDO参数绑定)。
    • 密码使用 password_hash()password_verify()
    • API接口需要有用户身份验证(如JWT或Session)。
  4. 性能优化
    • borrow_records.user_idbooks.available_quantity 添加索引。
    • 使用定时任务(Cron Job)每天运行 updateOverdueStatus() 来更新逾期状态。
  5. 扩展功能
    • 预约/续借功能:新增 reservations 表,添加续借次数限制。
    • 图书搜索:使用 LIKE 或 Elasticsearch。
    • 邮件通知:借阅成功、逾期提醒(使用PHPMailer)。

推荐的项目结构

library-system/
├── config/
│   └── database.php
├── models/
│   ├── User.php
│   ├── Book.php
│   └── BorrowRecord.php
├── controllers/
│   ├── AuthController.php
│   ├── BookController.php
│   └── BorrowController.php
├── views/
│   ├── layout/
│   ├── book/
│   └── borrow/
├── public/
│   └── index.php (前端入口)
├── migrations/
│   └── init.sql
└── vendor/ (Composer)

这种结构可以让你轻松扩展为完整的Web应用(Laravel、Symfony等框架的实现原理与此类似)。

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