1. 为什么选择PHP和MySQL来搭建图书管理系统?
如果你刚接触Web开发,想找一个能快速上手、又能做出实用项目的技术组合,那我强烈推荐你试试PHP和MySQL。我自己带过不少新手,发现很多人一开始就被各种框架和复杂概念吓住了,其实完全没必要。PHP这门语言,语法直白,跟写英语句子差不多,你几乎可以边学边做。MySQL数据库呢,用SQL语句操作数据,逻辑也非常清晰。把它们俩放一起,就像炒菜有了锅和铲,是最经典、最趁手的搭档。
这个图书管理系统项目,就是一个绝佳的练手机会。它麻雀虽小,五脏俱全,涵盖了Web开发最核心的流程:设计数据库表、用PHP连接数据库、处理用户提交的表单、把数据展示在网页上。做完这个项目,你就能把“前端页面”、“后端逻辑”、“数据库操作”这几个抽象的概念串成一条线,真正理解一个动态网站是怎么跑起来的。而且,图书管理的业务逻辑非常贴近生活,比如图书的增删改查,就像管理你自己的书架一样直观,不会让你在业务理解上卡壳。
市面上有很多集成环境,比如phpStudy、XAMPP、WAMP,它们把PHP、MySQL、Web服务器(如Apache)打包好了,一键安装,省去了配置环境的繁琐。这让我们可以跳过那些令人头疼的配置环节,直接聚焦在代码逻辑本身。我当年也是从phpStudy入门的,它确实让学习门槛降低了不少。接下来,我们就从搭建这个“舞台”开始。
2. 十分钟搞定开发环境:用phpStudy一键搭建
工欲善其事,必先利其器。对于新手来说,自己手动配置PHP、Apache和MySQL,光是处理版本兼容和配置文件就够劝退的了。所以,我们直接使用phpStudy这类集成环境,它能让我们在几分钟内就拥有一个可用的开发服务器。
2.1 下载与安装phpStudy
首先,去phpStudy的官网下载最新版本。安装过程非常简单,几乎就是一路“下一步”。这里有个小建议:安装路径不要包含中文或空格。比如,你可以安装在 D:\phpStudy 或 C:\phpStudy。这是很多编程相关软件的通用规则,能避免一些莫名其妙的路径错误。
安装完成后,打开phpStudy,你会看到一个简洁的控制面板。通常,它会自动启动Apache和MySQL服务(状态显示为绿色)。如果没启动,手动点一下对应的“启动”按钮就行。看到两个绿灯,恭喜你,你的本地服务器已经运行起来了!
2.2 创建你的第一个项目目录
phpStudy默认的网站根目录通常是 phpStudy安装目录\WWW\。所有你通过浏览器访问的PHP文件,都应该放在这个目录或其子目录下。我们来为图书管理系统创建一个专属文件夹。
- 打开
WWW文件夹。 - 新建一个文件夹,命名为
book_management(或者其他你喜欢的英文名)。 - 以后,我们所有的项目代码文件都会放在这个
book_management文件夹里。
现在,你可以在浏览器地址栏输入 http://localhost/book_management/ 来访问这个目录了。虽然现在里面是空的,但很快它就会充满我们写的代码。
2.3 测试环境是否正常
为了确保一切就绪,我们写一个最简单的PHP文件来测试。在你的 book_management 文件夹里,新建一个文本文档,重命名为 test.php(注意扩展名是 .php)。用记事本或任何代码编辑器(比如VS Code、HBuilder)打开它,输入以下代码:
<?php
echo "Hello, 我的图书管理系统!";
phpinfo();
?>
保存文件,然后在浏览器访问 http://localhost/book_management/test.php。你应该会先看到“Hello, 我的图书管理系统!”这行字,下面跟着一大片关于PHP配置的详细信息。如果看到了,说明你的PHP环境完全正常。如果没看到,而是显示了代码或者报错,请检查phpStudy的服务是否启动,以及文件是否放在了正确的 WWW 目录下。
3. 数据库设计:先画蓝图,再建“仓库”
写代码最怕什么?最怕写到一半发现数据库结构设计错了,要推倒重来。数据库就像是系统的“仓库”,设计得好,存取数据又快又方便;设计得不好,后面写代码会处处碰壁。所以,动手建表之前,我们得先想清楚需要存哪些数据。
对于一个最基础的图书管理系统,我们至少需要两张核心表:管理员表 和 图书信息表。有的复杂系统还会有读者表、借阅记录表等,我们先从最简单的入手,把核心流程跑通。
3.1 管理员表 (manager_info)
这张表用来存储可以登录系统、管理图书的用户信息。字段不需要太复杂。
| 字段名 | 数据类型 | 说明 | 备注 |
|---|---|---|---|
manager_id | INT | 管理员ID | 主键,自动增长 |
manager_username | VARCHAR(50) | 用户名 | 唯一,用于登录 |
manager_password | VARCHAR(255) | 密码 | 存储加密后的密码 |
这里有个非常重要的安全实践:密码绝对不能明文存储。我们会在PHP代码里对用户输入的密码进行加密,然后把加密后的字符串存进数据库。这样即使数据库泄露,攻击者也无法直接得到用户的真实密码。常用的加密函数是 password_hash(),它非常安全。
3.2 图书信息表 (book_info)
这是系统的核心,存放所有图书的详细信息。
| 字段名 | 数据类型 | 说明 | 备注 |
|---|---|---|---|
book_id | INT | 图书ID | 主键,自动增长 |
book_name | VARCHAR(100) | 书名 | |
author | VARCHAR(50) | 作者 | |
publisher | VARCHAR(100) | 出版社 | |
page | INT | 页码 | 整数类型 |
number | INT | 库存数量 | 整数类型 |
这个设计已经能满足基本的增删改查了。book_id 作为主键,能唯一标识每一本书。book_name 我们设置了唯一约束,这意味着系统不允许录入两本完全同名的书(在实际项目中,可能用ISBN号更合适,这里为了简化先用书名)。
3.3 使用phpMyAdmin创建数据库和表
phpStudy自带了一个非常好用的数据库管理工具叫phpMyAdmin。在浏览器输入 http://localhost/phpmyadmin 就能访问,用户名和密码默认是 root 和 root(具体密码请查看phpStudy面板的MySQL设置)。
- 创建数据库:登录后,在左侧点击“新建”,输入数据库名,比如
book_db,排序规则选utf8mb4_general_ci(支持中文和更多字符),点击“创建”。 - 创建表:进入新建的
book_db,在“新建数据表”区域,输入表名manager_info,字段数填3,点击“执行”。 - 设计字段:按照上面表格的规划,逐一填写每个字段的名称、类型、长度/值、属性(如
manager_id勾选A_I即自动递增)、索引(manager_id选PRIMARY,manager_username选UNIQUE)。全部填好后,点击“保存”。 - 重复步骤:用同样的方法创建
book_info表。
手动点来点去虽然直观,但效率低,而且不利于迁移。更专业的做法是使用SQL语句来创建。你可以把下面的SQL代码复制到phpMyAdmin的“SQL”标签页中执行,一次性创建所有表结构:
CREATE DATABASE IF NOT EXISTS `book_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `book_db`;
CREATE TABLE `manager_info` (
`manager_id` int(11) NOT NULL AUTO_INCREMENT,
`manager_username` varchar(50) NOT NULL UNIQUE,
`manager_password` varchar(255) NOT NULL,
PRIMARY KEY (`manager_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `book_info` (
`book_id` int(11) NOT NULL AUTO_INCREMENT,
`book_name` varchar(100) NOT NULL UNIQUE,
`author` varchar(50) DEFAULT NULL,
`publisher` varchar(100) DEFAULT NULL,
`page` int(11) DEFAULT NULL,
`number` int(11) DEFAULT '0',
PRIMARY KEY (`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
执行成功后,你的数据库“仓库”就建好了,并且规划好了不同的“货架”(表)和“储物格”(字段)。
4. 连接数据库:让PHP和MySQL“握手”
数据库建好了,PHP环境也准备好了,现在需要让它们俩认识一下,建立连接。我们把连接数据库的代码单独写在一个文件里,比如叫 conn.php。这样,其他任何需要操作数据库的页面,只要用 include "conn.php"; 引入这个文件,就能直接使用数据库连接了,这叫“代码复用”,是很好的编程习惯。
在 book_management 文件夹下创建 conn.php,写入以下代码:
<?php
// conn.php - 数据库连接文件
header("Content-Type: text/html; charset=utf-8"); // 设置页面编码,防止中文乱码
$servername = "localhost"; // 数据库服务器,本地就是localhost
$username = "root"; // 数据库用户名,phpStudy默认是root
$password = "root"; // 数据库密码,请根据你的phpStudy设置修改
$dbname = "book_db"; // 我们刚才创建的数据库名
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检测连接是否成功
if ($conn->connect_error) {
// 如果连接失败,用die()函数输出错误信息并终止脚本
die("连接失败: " . $conn->connect_error . "<br>请检查数据库配置。");
}
// 设置客户端字符集为utf8mb4,确保能正确处理中文
$conn->set_charset("utf8mb4");
// 提示:在实际项目中,连接成功后通常不会输出内容,这里为了新手调试方便才加上。
// echo "数据库连接成功!<br>";
?>
代码解读与避坑指南:
new mysqli(...):这是PHP中面向对象的MySQL连接方式,比老式的mysql_connect()更安全、更现代。- 密码错误:这是新手最常遇到的问题!
$password的值必须和你phpStudy里MySQL的密码一致。如果不确定,可以打开phpStudy面板,在MySQL设置里查看或修改。 - 字符集设置:
set_charset("utf8mb4")这行至关重要。没有它,你从网页表单提交的中文,存到数据库里可能会变成一堆问号???。utf8mb4是utf8的超集,支持更全的字符(比如emoji)。 - 错误处理:
if ($conn->connect_error) {...}这段代码是必须的。它能在连接出错时(比如密码错、数据库名错)立刻告诉你原因,而不是让页面一片空白,让你无从下手调试。
你可以单独在浏览器访问 http://localhost/book_management/conn.php 测试一下。如果页面空白(或者只看到我们注释掉的提示),说明连接成功;如果看到“连接失败”的提示,就根据提示信息去排查问题。
5. 管理员模块实战:登录与注册
有了数据库连接,我们就可以开始实现功能了。首先从管理员模块开始,因为只有登录成功的管理员才能管理图书。这个模块会涉及两个核心操作:验证用户身份(登录) 和 创建新用户(注册)。
5.1 登录功能实现 (login.php)
登录的本质是:用户在前端页面输入用户名和密码,提交后,PHP后端去数据库里查询,是否存在一条记录,其用户名和密码(加密后)与用户输入的匹配。
第一步:创建登录表单 (login.html) 这是一个纯HTML页面,用于展示输入框和提交按钮。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理员登录 - 图书管理系统</title>
<!-- 引入Bootstrap CSS,让我们页面快速变漂亮 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white text-center">
<h4>管理员登录</h4>
</div>
<div class="card-body">
<!-- 表单提交到 login.php 进行处理 -->
<form action="login.php" method="POST">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" required placeholder="请输入用户名">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required placeholder="请输入密码">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">登录</button>
<a href="register.html" class="btn btn-outline-secondary">还没有账号?去注册</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
第二步:处理登录逻辑 (login.php) 这是核心的PHP文件,接收表单数据,并与数据库交互。
<?php
// login.php - 处理登录请求
session_start(); // 开启Session,用于记录用户登录状态
include "conn.php"; // 引入数据库连接
// 判断是否是表单提交过来的(防止直接访问这个PHP文件)
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 1. 获取用户输入,并用 trim() 去除首尾空格
$input_username = trim($_POST['username']);
$input_password = trim($_POST['password']);
// 2. 准备SQL查询语句,查询用户名是否存在
// 使用问号 ? 作为占位符,这是防止SQL注入攻击的关键一步!
$sql = "SELECT manager_id, manager_password FROM manager_info WHERE manager_username = ?";
$stmt = $conn->prepare($sql); // 预处理语句
$stmt->bind_param("s", $input_username); // 绑定参数,s代表字符串类型
$stmt->execute(); // 执行查询
$stmt->store_result(); // 存储结果集
$stmt->bind_result($db_id, $db_password); // 绑定结果到变量
// 3. 判断是否查询到用户
if ($stmt->num_rows > 0) {
$stmt->fetch(); // 获取查询结果
// 4. 验证密码
// 重要:注册时我们用 password_hash() 加密,这里就用 password_verify() 验证
if (password_verify($input_password, $db_password)) {
// 密码正确,登录成功
$_SESSION['manager_id'] = $db_id; // 将用户ID存入Session
$_SESSION['manager_username'] = $input_username;
echo "<script>alert('登录成功!'); window.location.href='index.php';</script>";
} else {
// 密码错误
echo "<script>alert('密码错误!'); history.back();</script>";
}
} else {
// 用户名不存在
echo "<script>alert('用户名不存在!'); history.back();</script>";
}
$stmt->close(); // 关闭预处理语句
$conn->close(); // 关闭数据库连接
} else {
// 如果不是POST请求,直接跳回登录页
header("Location: login.html");
}
?>
安全与细节剖析:
- SQL注入防御:代码中使用了
prepare预处理语句和bind_param绑定参数。这是目前防止SQL注入最有效、最推荐的方式。它把用户输入的数据当做纯数据处理,而不是可执行的SQL代码的一部分,从根本上杜绝了注入风险。这是必须养成的习惯! - 密码验证:我们使用了
password_verify()函数。它内部会自动处理加密盐值和算法对比,你只需要传入用户输入的明文密码和数据库存储的哈希值即可。这比用md5()或sha1()安全得多。 - Session管理:
session_start()和$_SESSION用于在用户登录后,在其浏览器会话期间记住他的身份。这样,用户在访问其他页面(如index.php)时,我们就能判断他是否已登录。 - 用户体验:使用
alert()进行简单提示,并用history.back()或window.location.href进行页面跳转,让操作有反馈。
5.2 注册功能实现 (register.php)
注册的逻辑是:检查用户名是否已存在,如果不存在,则将新用户信息(加密后的密码)插入数据库。
注册表单 (register.html) 和登录表单类似,主要是多一个“确认密码”的字段,并通过JavaScript或PHP确保两次输入一致。这里省略HTML代码,我们重点关注PHP处理逻辑。
<?php
// register.php - 处理注册请求
include "conn.php";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST['username']);
$password = trim($_POST['password']);
$confirm_pwd = trim($_POST['confirm_password']);
// 1. 基础验证
if (empty($username) || empty($password)) {
die("<script>alert('用户名和密码不能为空!'); history.back();</script>");
}
if ($password !== $confirm_pwd) {
die("<script>alert('两次输入的密码不一致!'); history.back();</script>");
}
// 2. 检查用户名是否已存在
$check_sql = "SELECT manager_id FROM manager_info WHERE manager_username = ?";
$check_stmt = $conn->prepare($check_sql);
$check_stmt->bind_param("s", $username);
$check_stmt->execute();
$check_stmt->store_result();
if ($check_stmt->num_rows > 0) {
die("<script>alert('该用户名已被占用,请换一个!'); history.back();</script>");
}
$check_stmt->close();
// 3. 对密码进行哈希加密
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// 4. 插入新用户
$insert_sql = "INSERT INTO manager_info (manager_username, manager_password) VALUES (?, ?)";
$insert_stmt = $conn->prepare($insert_sql);
$insert_stmt->bind_param("ss", $username, $hashed_password);
if ($insert_stmt->execute()) {
echo "<script>alert('注册成功!请登录。'); window.location.href='login.html';</script>";
} else {
echo "<script>alert('注册失败,请稍后重试。'); history.back();</script>";
}
$insert_stmt->close();
$conn->close();
} else {
header("Location: register.html");
}
?>
关键点提醒:
password_hash($password, PASSWORD_DEFAULT):这是PHP官方推荐的密码哈希函数。PASSWORD_DEFAULT表示使用当前PHP版本推荐的最强算法(通常是bcrypt)。它会自动生成并管理“盐值”(salt),使得每次加密的结果都不同,但password_verify()都能正确验证。- 先查询后插入:在执行插入操作前,先检查用户名是否存在,这是一个标准的防重复流程。
6. 图书管理核心:增删改查(CRUD)
管理员登录后,就进入了系统的核心——图书管理。我们将实现四个基本操作:Create(新增)、Read(读取/查询)、Update(更新)、Delete(删除),也就是常说的CRUD。
6.1 首页展示图书列表 (index.php)
这个页面是登录后的主页,负责从数据库读取所有图书并以表格形式展示,同时提供“新增”、“修改”、“删除”操作的入口。
<?php
// index.php - 系统首页,展示图书列表
session_start();
// 检查是否登录,未登录则跳回登录页
if (!isset($_SESSION['manager_username'])) {
header("Location: login.html");
exit();
}
include "conn.php";
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>图书管理系统 - 首页</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div class="container">
<a class="navbar-brand" href="index.php">图书管理系统</a>
<span class="navbar-text text-light ms-auto">
欢迎,<?php echo htmlspecialchars($_SESSION['manager_username']); ?>!
</span>
<a href="logout.php" class="btn btn-outline-light btn-sm ms-3">退出登录</a>
</div>
</nav>
<div class="container">
<h2 class="mb-4">图书信息总览</h2>
<a href="add_book.html" class="btn btn-success mb-3">+ 添加新图书</a>
<?php
// 查询所有图书
$sql = "SELECT book_id, book_name, author, publisher, page, number FROM book_info ORDER BY book_id DESC";
$result = $conn->query($sql);
if ($result && $result->num_rows > 0) {
?>
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>书名</th>
<th>作者</th>
<th>出版社</th>
<th>页码</th>
<th>库存</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
<?php while($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo $row['book_id']; ?></td>
<td><?php echo htmlspecialchars($row['book_name']); ?></td>
<td><?php echo htmlspecialchars($row['author']); ?></td>
<td><?php echo htmlspecialchars($row['publisher']); ?></td>
<td><?php echo $row['page']; ?></td>
<td><?php echo $row['number']; ?></td>
<td class="text-center">
<a href="update_book.php?id=<?php echo $row['book_id']; ?>" class="btn btn-sm btn-primary">修改</a>
<a href="javascript:if(confirm('确定要删除《<?php echo $row['book_name']; ?>》吗?')){location.href='delete_book.php?id=<?php echo $row['book_id']; ?>'}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
<?php
} else {
echo '<div class="alert alert-info" role="alert">当前还没有图书信息,请点击上方按钮添加。</div>';
}
$conn->close();
?>
</div>
</div>
</body>
</html>
代码亮点与技巧:
- Session验证:页面开头通过
isset($_SESSION['manager_username'])判断用户是否登录,这是保护后台页面的基本手段。 htmlspecialchars()函数:在输出从数据库取出的数据(尤其是文本内容)到HTML页面时,务必使用这个函数。它可以将特殊字符(如<,>,&,")转换为HTML实体,有效防止XSS(跨站脚本)攻击。这是一个非常重要的安全习惯。- 循环输出表格:使用
while($row = $result->fetch_assoc())循环遍历查询结果集,动态生成表格的每一行。fetch_assoc()方法将一行数据返回为关联数组,键名就是字段名。 - 操作链接:“修改”链接将图书ID通过URL参数 (
?id=xxx) 传递到修改页面。“删除”链接使用了JavaScript的confirm()进行二次确认,避免误操作。
6.2 新增图书功能 (add_book.php)
新增功能需要一个表单页面 (add_book.html) 来填写信息,和一个处理页面 (add_book.php) 来将数据存入数据库。表单页面比较简单,我们直接看处理逻辑。
<?php
// add_book.php - 处理添加图书请求
session_start();
if (!isset($_SESSION['manager_username'])) {
header("Location: login.html");
exit();
}
include "conn.php";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 接收并清理数据
$book_name = trim($_POST['book_name']);
$author = trim($_POST['author']);
$publisher = trim($_POST['publisher']);
$page = intval($_POST['page']); // 转换为整数
$number = intval($_POST['number']);
// 简单验证
if (empty($book_name)) {
die("<script>alert('书名不能为空!'); history.back();</script>");
}
// 检查书名是否已存在(根据业务需求,可选)
$check_sql = "SELECT book_id FROM book_info WHERE book_name = ?";
$check_stmt = $conn->prepare($check_sql);
$check_stmt->bind_param("s", $book_name);
$check_stmt->execute();
$check_stmt->store_result();
if ($check_stmt->num_rows > 0) {
die("<script>alert('该书名已存在!'); history.back();</script>");
}
$check_stmt->close();
// 插入数据
$sql = "INSERT INTO book_info (book_name, author, publisher, page, number) VALUES (?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("sssii", $book_name, $author, $publisher, $page, $number); // 注意类型:s字符串,i整数
if ($stmt->execute()) {
echo "<script>alert('图书添加成功!'); window.location.href='index.php';</script>";
} else {
// 更友好的错误提示(实际项目中可记录日志)
echo "<script>alert('添加失败,请检查数据或稍后重试。'); history.back();</script>";
}
$stmt->close();
$conn->close();
} else {
header("Location: add_book.html");
}
?>
数据类型绑定细节: bind_param("sssii", ...) 中的 "sssii" 指明了后面五个参数的类型:三个字符串 (s),两个整数 (i)。必须严格按照字段的数据类型来定义,否则执行会出错。
6.3 修改图书功能 (update_book.php)
修改功能通常是两个步骤:首先,根据传入的ID,从数据库取出该图书的旧信息,填充到表单里供用户修改;然后,用户提交修改后的表单,更新数据库。
第一步:展示编辑表单(同样在 update_book.php 中处理GET请求)
<?php
// update_book.php - 展示编辑表单并处理更新
session_start();
if (!isset($_SESSION['manager_username'])) {
header("Location: login.html");
exit();
}
include "conn.php";
// 处理GET请求:展示表单
if (!isset($_GET['id']) || empty($_GET['id'])) {
die("<script>alert('无效的图书ID!'); window.location.href='index.php';</script>");
}
$book_id = intval($_GET['id']); // 获取并转换ID为整数
// 查询当前图书信息
$sql = "SELECT book_name, author, publisher, page, number FROM book_info WHERE book_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $book_id);
$stmt->execute();
$stmt->bind_result($book_name, $author, $publisher, $page, $number);
$stmt->fetch();
$stmt->close();
// 如果没查到这本书
if (empty($book_name)) {
$conn->close();
die("<script>alert('未找到指定的图书!'); window.location.href='index.php';</script>");
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>修改图书信息</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>修改图书信息</h2>
<form action="update_book.php" method="POST">
<!-- 隐藏域,用于传递图书ID -->
<input type="hidden" name="book_id" value="<?php echo $book_id; ?>">
<div class="mb-3">
<label for="book_name" class="form-label">书名 *</label>
<input type="text" class="form-control" id="book_name" name="book_name" value="<?php echo htmlspecialchars($book_name); ?>" required>
</div>
<div class="mb-3">
<label for="author" class="form-label">作者</label>
<input type="text" class="form-control" id="author" name="author" value="<?php echo htmlspecialchars($author); ?>">
</div>
<div class="mb-3">
<label for="publisher" class="form-label">出版社</label>
<input type="text" class="form-control" id="publisher" name="publisher" value="<?php echo htmlspecialchars($publisher); ?>">
</div>
<div class="mb-3">
<label for="page" class="form-label">页码</label>
<input type="number" class="form-control" id="page" name="page" value="<?php echo $page; ?>">
</div>
<div class="mb-3">
<label for="number" class="form-label">库存数量</label>
<input type="number" class="form-control" id="number" name="number" value="<?php echo $number; ?>" min="0">
</div>
<button type="submit" class="btn btn-primary">提交修改</button>
<a href="index.php" class="btn btn-secondary">取消</a>
</form>
</div>
</body>
</html>
<?php
// 注意:这里PHP代码还没结束,下面要处理POST请求
第二步:处理更新提交(在同一个文件的后续部分)
<?php
// 接上面的HTML部分,处理POST请求(更新操作)
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 获取表单数据
$book_id = intval($_POST['book_id']);
$book_name = trim($_POST['book_name']);
$author = trim($_POST['author']);
$publisher = trim($_POST['publisher']);
$page = intval($_POST['page']);
$number = intval($_POST['number']);
if (empty($book_name)) {
die("<script>alert('书名不能为空!'); history.back();</script>");
}
// 更新数据库
$update_sql = "UPDATE book_info SET book_name=?, author=?, publisher=?, page=?, number=? WHERE book_id=?";
$update_stmt = $conn->prepare($update_sql);
$update_stmt->bind_param("sssiii", $book_name, $author, $publisher, $page, $number, $book_id);
if ($update_stmt->execute()) {
echo "<script>alert('图书信息更新成功!'); window.location.href='index.php';</script>";
} else {
echo "<script>alert('更新失败!'); history.back();</script>";
}
$update_stmt->close();
$conn->close();
exit(); // 处理完POST请求后退出,避免执行下面的GET逻辑
}
// 如果是GET请求,会继续执行到上面的HTML展示部分
?>
一个文件处理两种请求:这是很常见的模式。通过判断 $_SERVER[“REQUEST_METHOD”] 是 GET 还是 POST,来决定是展示页面还是处理数据。注意在POST处理结束后使用 exit(),防止代码继续向下执行。
6.4 删除图书功能 (delete_book.php)
删除操作相对简单,但危险性最高,所以一定要做好权限验证和确认。
<?php
// delete_book.php - 处理删除图书请求
session_start();
if (!isset($_SESSION['manager_username'])) {
header("Location: login.html");
exit();
}
include "conn.php";
// 只接受GET请求,并且必须有id参数
if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['id'])) {
$book_id = intval($_GET['id']);
// 在实际项目中,这里可以增加更严格的权限检查,比如检查这本书是否可以被当前用户删除
$sql = "DELETE FROM book_info WHERE book_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $book_id);
if ($stmt->execute()) {
// 判断是否真的有行被删除
if ($stmt->affected_rows > 0) {
echo "<script>alert('删除成功!'); window.location.href='index.php';</script>";
} else {
echo "<script>alert('未找到该图书或删除失败!'); window.location.href='index.php';</script>";
}
} else {
echo "<script>alert('删除操作执行出错!'); window.location.href='index.php';</script>";
}
$stmt->close();
$conn->close();
} else {
// 非法访问,跳回首页
header("Location: index.php");
}
?>
安全与健壮性考虑:
affected_rows:这个属性很重要,它告诉我们执行删除(或更新)操作后,实际影响了多少行数据。如果为0,说明没有找到对应的ID,可能是已经被删除了。- 直接使用GET请求删除的风险:我们的删除链接是
delete_book.php?id=xxx,这存在CSRF(跨站请求伪造)风险。更安全的做法是使用POST请求,并生成一个随机的Token进行验证。但对于入门项目,通过JavaScript的confirm()确认和Session登录验证,已经能提供基础保护。在正式项目中务必考虑CSRF防护。 - 外键约束:如果未来系统增加了“借阅记录”表,并且与
book_info表通过外键关联,那么直接删除图书会导致错误。这时应该先检查是否有关联记录,或者设置外键的级联删除规则。这是我们数据库设计时需要考虑的扩展性。
7. 项目优化与安全加固实战
基础功能跑通后,我们得回过头来看看哪些地方可以做得更好、更安全。我见过很多新手项目,功能实现了,但漏洞百出,上线就是灾难。下面分享几个我踩过坑后总结的优化点。
1. 统一的身份验证与权限检查
我们现在的每个后台页面(index.php, add_book.php等)开头都有一段重复的Session检查代码。这违反了DRY(Don‘t Repeat Yourself)原则。更好的做法是创建一个公共的包含文件,比如 check_login.php:
<?php
// check_login.php
session_start();
if (!isset($_SESSION['manager_id']) || !isset($_SESSION['manager_username'])) {
// 可以记录一下尝试访问的页面
header("Location: login.html?error=请先登录");
exit();
}
?>
然后在每个需要登录才能访问的页面最开头,只需要写一句 include ‘check_login.php’; 就行了。这样,以后如果要修改验证逻辑(比如增加角色权限),只需要改这一个文件。
2. 防止SQL注入的终极姿势
我们已经用了预处理语句,这很好。但还有一个细节:对于不是用预处理语句的动态部分,比如排序字段 ORDER BY,如果直接拼接用户输入,依然有风险。对于这种情况,应该使用“白名单”机制。例如,只允许按几个特定的字段排序:
$allowed_orders = ['book_id', 'book_name', 'number'];
$order_by = isset($_GET['order']) && in_array($_GET['order'], $allowed_orders) ? $_GET['order'] : 'book_id';
$sql = "SELECT * FROM book_info ORDER BY $order_by DESC";
3. 密码加密存储的升级
我们用了 password_hash(),这很棒。但存储密码的字段 manager_password 类型是 VARCHAR(255),这是为了兼容未来可能更长的哈希算法结果。记住,永远不要自己发明加密算法。
4. 错误处理与日志记录
现在我们的错误提示直接 alert() 给用户看,这在开发阶段没问题,但上线后应该避免暴露数据库细节(比如具体的SQL错误)。我们可以定义一个错误处理函数,在生产环境中将错误记录到日志文件,同时给用户一个友好的提示。
// 在 conn.php 或 config.php 中设置错误报告级别
ini_set('display_errors', 0); // 上线后关闭错误显示
error_reporting(E_ALL); // 但记录所有错误
// 自定义错误处理函数
function handleError($errno, $errstr, $errfile, $errline) {
$msg = date('Y-m-d H:i:s') . " [Error $errno] $errstr in $errfile on line $errline\n";
error_log($msg, 3, 'error.log'); // 记录到文件
// 给用户一个通用提示
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
echo '系统发生了一个错误,请稍后再试。';
exit;
}
set_error_handler("handleError");
5. 代码结构与MVC思想 虽然我们这个项目很小,但已经可以初步体会MVC(模型-视图-控制器)的分离思想。你可以尝试把代码组织一下:
- 模型 (Model):专门处理数据库操作的函数,放在
models/目录下,比如BookModel.php、UserModel.php。 - 视图 (View):纯HTML和少量用于展示的PHP代码,放在
views/目录下。 - 控制器 (Controller):接收用户请求,调用模型,选择视图渲染,放在根目录或
controllers/目录下。
这样做的好处是,以后修改数据库表结构,只需要改模型文件;修改页面样式,只需要改视图文件;逻辑流程变了,才需要改控制器。代码会清晰得多,维护起来也方便。
8. 源码获取与下一步学习建议
按照上面的步骤一步步写下来,一个具备管理员登录、注册和图书增删改查功能的简易图书管理系统就完成了。我把这个项目的完整源码文件整理好了,你可以直接下载使用。源码里包含了我们上面讲解的所有文件,以及一个导入了表结构的SQL文件 book_db.sql,你只需要在phpMyAdmin里导入这个文件,就能快速拥有我们设计好的数据库。
获取完整源码的方式:由于平台限制,我无法直接在这里贴出下载链接。你可以关注我的个人博客或技术社区账号(搜索相关关键词如“PHP图书管理系统实战源码”),通常在文章末尾会提供Git仓库地址或网盘链接。源码包解压后,放到phpStudy的 WWW 目录下,修改 conn.php 中的数据库密码,导入SQL文件,就可以直接运行了。
这个项目虽然简单,但它像一颗种子,包含了Web应用最基本的生长要素。在你成功运行起这个系统之后,我建议你可以尝试以下几个方向去扩展它,这会让你的学习更有深度:
第一,增加前端交互体验。 我们现在的删除操作需要刷新页面,体验不好。可以尝试用JavaScript的Fetch API或者jQuery的Ajax,实现“点击删除 -> 后台异步处理 -> 前端动态移除表格行”的效果。这样页面不用刷新,用户体验会提升一个档次。
第二,实现图书搜索和分页功能。 在首页增加一个搜索框,可以根据书名或作者进行模糊查询(SQL中用 LIKE 语句)。当图书数量很多时,分页是必须的。研究一下SQL的 LIMIT 子句,以及如何计算总页数和当前页码,这会让你对数据处理有更深的理解。
第三,引入前端框架让界面更美观。 我们用了Bootstrap,但它主要是CSS框架。你可以尝试学习一个前端JavaScript框架,比如Vue.js。用Vue来渲染图书列表,管理页面状态,你会立刻感受到前后端分离开发的魅力。这几乎是现在Web开发的标配。
第四,尝试一个小型框架。 当你觉得用原生PHP写多个页面开始变得混乱时,就是学习框架的好时机。从超轻量级的Phalcon、Slim,或者国内比较流行的ThinkPHP、Laravel入门都可以。框架能帮你更好地组织代码(MVC)、处理路由、管理依赖。用框架重写一遍这个图书管理系统,你会对现代PHP开发有全新的认识。
我自己就是从这样一个简单的图书管理系统开始,一步步摸索过来的。过程中会遇到无数报错,浏览器一片空白或者满屏警告是家常便饭。别怕,每一个错误都是学习的机会。善用 var_dump() 或 echo 打印变量,用浏览器的开发者工具查看网络请求和响应,这些是调试的利器。坚持把功能一个个实现,当你看到自己写的程序能真正管理数据时,那种成就感是无与伦比的。希望这个实战项目能成为你PHP学习路上的一块坚实垫脚石。
8797


&spm=1001.2101.3001.11974&articleId=149108631&d=1&t=3&u=788df61edb30400386783ceb6f923564)

被折叠的 条评论
为什么被折叠?



