作为一名在图书馆信息化领域摸爬滚打多年的老码农,今天想和大家分享一个基于PHP+MySQL的多角色图书管理系统开发实录。这个系统最初是为本地社区图书馆设计的,后来在GitHub开源后收到了不少反馈,现在把完整开发过程和关键实现细节整理出来。
系统采用经典的三层架构(表现层-业务层-数据层),支持管理员和普通用户两种角色,实现了图书管理、借阅管理、用户管理等核心功能。特别在权限控制和数据一致性方面做了重点设计,适合中小型图书馆或图书管理场景使用。
选择PHP+MySQL这套经典组合主要基于以下考虑:
提示:虽然现在流行各种前端框架,但对于这类内部管理系统,传统服务端渲染模式反而更简单直接。
核心表结构设计如下(简化版):
sql复制CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`book_name` varchar(100) NOT NULL,
`author` varchar(50) NOT NULL,
`category_id` int(11) NOT NULL,
`isbn` varchar(20) NOT NULL,
`stock` int(11) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`role_type` tinyint(4) NOT NULL COMMENT '1-管理员 2-普通用户',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `borrow` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`book_id` int(11) NOT NULL,
`status` tinyint(4) NOT NULL COMMENT '0-待审批 1-已借出 2-已归还 3-已拒绝',
`borrow_time` datetime NOT NULL,
`return_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `book_id` (`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计要点:
登录验证是系统的第一道防线,实现要点:
php复制// 使用预处理语句防止SQL注入
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();
if ($user && password_verify($_POST['password'], $user['password'])) {
// 使用更强的哈希算法替代MD5
$_SESSION['user_id'] = $user['id'];
$_SESSION['role'] = $user['role_type'];
// 记录登录日志
logLogin($user['id'], $_SERVER['REMOTE_ADDR']);
// 根据角色跳转不同页面
header('Location: '.($user['role_type'] == 1 ? 'admin.php' : 'user.php'));
exit;
} else {
// 统一提示避免泄露用户存在信息
$error = '用户名或密码错误';
}
权限校验中间件(在每个管理页面开头调用):
php复制function checkAdminAuth() {
if (empty($_SESSION['role']) || $_SESSION['role'] != 1) {
http_response_code(403);
include('403.html');
exit;
}
}
图书检索与分页实现:
php复制// 初始化查询条件
$where = [];
$params = [];
$perpage = 20;
$page = max(1, intval($_GET['page'] ?? 1));
// 动态构建查询条件
if (!empty($_GET['keyword'])) {
$where[] = "(book_name LIKE ? OR author LIKE ? OR isbn = ?)";
$params[] = '%'.$_GET['keyword'].'%';
$params[] = '%'.$_GET['keyword'].'%';
$params[] = $_GET['keyword'];
}
if (!empty($_GET['category_id'])) {
$where[] = "category_id = ?";
$params[] = $_GET['category_id'];
}
// 构建基础SQL
$sql = "SELECT b.*, c.name as category_name FROM books b
LEFT JOIN categories c ON b.category_id = c.id";
// 添加WHERE条件
if ($where) {
$sql .= " WHERE " . implode(" AND ", $where);
}
// 获取总数
$countSql = "SELECT COUNT(1) FROM books b" . ($where ? " WHERE " . implode(" AND ", $where) : "");
$total = $pdo->prepare($countSql);
$total->execute($params);
$totalCount = $total->fetchColumn();
// 添加分页
$sql .= " ORDER BY b.id DESC LIMIT " . (($page - 1) * $perpage) . ", $perpage";
// 执行查询
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$books = $stmt->fetchAll();
借阅审批的事务处理:
php复制// 获取借阅信息
$borrowId = intval($_POST['id']);
$borrowInfo = $pdo->query("SELECT * FROM borrow WHERE id = $borrowId")->fetch();
if (!$borrowInfo) {
die('借阅记录不存在');
}
$pdo->beginTransaction();
try {
// 更新借阅状态
$pdo->prepare("UPDATE borrow SET status = ?, approve_time = NOW() WHERE id = ?")
->execute([$_POST['action'] == 'approve' ? 1 : 3, $borrowId]);
// 更新图书库存
if ($_POST['action'] == 'approve') {
$pdo->prepare("UPDATE books SET stock = stock - 1 WHERE id = ? AND stock > 0")
->execute([$borrowInfo['book_id']]);
}
// 记录操作日志
logBorrowAction($_SESSION['user_id'], $borrowId, $_POST['action']);
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
die('操作失败: ' . $e->getMessage());
}
输入验证:
密码安全:
会话安全:
数据库优化:
前端优化:
代码优化:
php复制function deleteBook($bookId) {
global $pdo;
// 检查是否存在未归还记录
$unreturned = $pdo->query("SELECT COUNT(1) FROM borrow
WHERE book_id = $bookId AND status = 1")->fetchColumn();
if ($unreturned > 0) {
return '存在未归还的借阅记录,不能删除';
}
// 使用事务确保数据一致性
$pdo->beginTransaction();
try {
// 删除借阅记录
$pdo->exec("DELETE FROM borrow WHERE book_id = $bookId");
// 删除图书
$pdo->exec("DELETE FROM books WHERE id = $bookId");
$pdo->commit();
return true;
} catch (Exception $e) {
$pdo->rollBack();
return '删除失败: ' . $e->getMessage();
}
}
php复制function deleteCategory($categoryId) {
global $pdo;
// 检查是否有图书使用该分类
$bookCount = $pdo->query("SELECT COUNT(1) FROM books
WHERE category_id = $categoryId")->fetchColumn();
if ($bookCount > 0) {
// 将相关图书移动到默认分类
$pdo->exec("UPDATE books SET category_id = 1
WHERE category_id = $categoryId");
}
// 删除分类
$pdo->exec("DELETE FROM categories WHERE id = $categoryId");
return true;
}
数据备份:
日志监控:
安全更新:
系统设计时已考虑扩展性,以下是一些可能的扩展方向:
多图书馆支持:
移动端适配:
高级搜索功能:
数据分析:
API接口:
在实际开发中,我发现最值得投入时间的是数据一致性和权限控制这两个方面。图书管理系统的特殊性在于很多操作都会产生连锁反应,比如借书会影响库存,还书会影响借阅记录,删除分类会影响图书分类等。良好的事务处理和合理的约束条件可以避免很多潜在问题。
另一个经验是,不要过早优化。初期应该专注于核心功能的完整性和正确性,等系统真正运行起来后,根据实际性能瓶颈进行有针对性的优化。比如我们的系统最初没有考虑分页性能问题,当图书数量超过1万条时才出现了明显的性能下降,这时再专门优化分页查询反而更有针对性。