本文目录导读:

在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' => '借书成功!...']
第四步:关键注意事项
- 事务与锁:
- 借书/还书操作必须放在事务中。
- 查询库存时使用
SELECT ... FOR UPDATE(行级锁),防止并发情况下超借。
- 库存逻辑:
- 不要依赖代码中的
if (available > 0)来判断,一定要在SQL中执行原子操作:UPDATE books SET available = available - 1 WHERE id = ? AND available > 0。
- 不要依赖代码中的
- 安全性:
- 所有用户输入必须使用预处理语句(PDO参数绑定)。
- 密码使用
password_hash()和password_verify()。 - API接口需要有用户身份验证(如JWT或Session)。
- 性能优化:
- 为
borrow_records.user_id、books.available_quantity添加索引。 - 使用定时任务(Cron Job)每天运行
updateOverdueStatus()来更新逾期状态。
- 为
- 扩展功能:
- 预约/续借功能:新增
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等框架的实现原理与此类似)。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播