简介:一份PHP留言板项目源码,配套MySQL数据库文件,面向正在学习PHP与Web开发的初学者或需要快速搭建互动留言模块的开发者。压缩包共131个文件,包含49个PHP脚本、16个CSS样式、16个JavaScript脚本及Bootstrap前端组件,另有SQL建库脚本、字体图标文件和对应的map映射文件,整体仅746KB,便于本地部署和二次修改。核心学习价值在于完整呈现留言板从数据库设计到前端交互的实现链路:connect.php与config.php演示数据库连接与配置,login.php和register.php覆盖用户认证与会话管理,add.php展示表单提交、数据校验和防SQL注入处理,messages表结构清晰,方便对照学习;同时还能看到XSS、CSRF防护思路、错误处理与日志记录等安全实践。对于希望深入理解PHP+MySQL经典开发模式的开发者,这套源码提供了可直接运行的样例,包含用户登录注册、留言增删、分页展示等常见功能模块,适合以此为基础扩展成更完整的Web应用。目前已有1670人学习,值得作为入门实践项目,边阅读边调试,逐步掌握Web开发的完整链路和常见安全策略。
1. 拆一份 PHP 留言板源码:它到底能帮你解决什么问题
留言板这类 PHP + MySQL 的经典组合,几乎是每个做 Web 的人迟早要碰一遍的东西。这份资源不是花架子,就是一份能跑起来的 PHP 留言板源码,外加一个建表用的 MySQL 数据库文件。你下载之后要面对的核心问题很简单:把 sql 导进数据库,改好连接参数,页面就能提交留言、展示留言。它适合拿来练手、改造成自己的第一个交互式应用,也适合新手用来理解 PHP 是怎么和 MySQL 配合的。别小看这份老代码,留言板虽然功能少,但表单提交、入库、列表查询、分页、字符集这些坑一个都不少,踩一遍比看十遍教程都管用。
2. 环境与数据导入:PHP 版本、MySQL 5.7/8.0 和 sql 文件的正确打开方式
2.1 PHP 版本选择:先看代码调了哪些函数,再决定用 7.4 还是 8.x
很多人在这一步翻车。下载源码后不先看代码,直接装最新版 PHP 8.3,然后打开页面就是一片 Fatal error。原因很简单:PHP 8 移除了mysql_*那一整组老函数,如果你手上的留言板源码还在用mysql_connect(),那只会在 PHP 7.4 及以下版本里正常跑。
我拿到任何一份老 PHP 源码,第一件事是打开根目录的 PHP 文件,全局搜索mysql_这个前缀。搜出来的是mysql_connect、mysql_query开头的写法,那就老老实实用 PHP 7.4;搜出来是mysqli_或者PDO,那 PHP 8.x 基本没压力。这份资源里的留言板,多数实现会用mysqli扩展,但你也可能碰到历史版本用了老 API,不能靠猜。
环境选择上,本地练手我一般直接上 XAMPP 或者 phpStudy,它们把 Apache、PHP、MySQL 打包在一起,版本可以切换。如果你是拿这份源码做课程设计或者交给别人部署,建议统一用 PHP 7.4 + MySQL 5.7,这是老 PHP 代码兼容性最稳的组合。MySQL 8.0 也能跑,但要注意 8.0 默认的密码认证插件改成了caching_sha2_password,后面第四章会细说这个坑。
版本对应关系可以参考下面这张表:
| 源码中的数据库扩展 | 推荐 PHP 版本 | 备注 |
|---|---|---|
mysql_*老函数 | PHP 5.6 / 7.0 | PHP 7.0 以上已弃用,8.0 直接移除,不推荐硬跑 |
mysqli_* | PHP 7.4 / 8.x | 最常见,这份资源大概率是这个写法 |
PDO | PHP 7.4 / 8.x | 新代码主流,安全性最好 |
提示:如果页面打开直接白屏或者抛错,先把
php.ini里的display_errors打开,错误信息会告诉你到底死在哪个函数上,这是排查这类问题最快的一条路。
2.2 建库建表:导入 sql 前,先花两分钟把表结构和字符集读明白
sql 是这份资源里最有价值的部分。它不只包含CREATE TABLE,通常还带着CREATE DATABASE、USE语句和几条示例数据。很多人拿到手就往 phpMyAdmin 里点导入,然后看到「成功」就以为完事了,实际上表名、字段名、字符集都没看,后面写代码时连nickname还是name都对不上。
我的习惯是双击打开 sql 文件,用编辑器看一遍。重点看三处:一是表名是什么,二是主键和字段类型,三是ENGINE和DEFAULT CHARSET。留言板核心表长这样,字段名可能略有差异,但结构八九不离十:
CREATE TABLE `messages` ( `id` int(11) NOT NULL AUTO_INCREMENT, `nickname` varchar(50) NOT NULL DEFAULT '', `content` text NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;AUTO_INCREMENT让 id 自增,不需要手动维护;nickname用varchar(50)控制长度;content用text是因为留言内容长短不可控,varchar(255)可能不够用;create_time用datetime而不是时间戳,排序和展示都更直观。CHARSET=utf8mb4是重中之重,utf8mb4 才是真正完整的 UTF-8 支持,能存 emoji,也能避免中文乱码。
导入方式有两种,选一个就行。第一种用 phpMyAdmin:先新建一个空数据库,名字随意,比如message_board,然后选中它,点「导入」,选择 sql 文件,执行。第二种用命令行,适合你已经在用终端工作流的情况:
mysql -u root -p message_board < message_board.sql-u root指定用户,-p提示输入密码,message_board是目标数据库名,<把文件内容重定向给 mysql 命令执行。如果一个 sql 文件里本身带了CREATE DATABASE message_board,你也可以先不进库直接全量导入,但我更倾向于拆开只导表,避免数据库重名的尴尬。
2.3 连库配置文件:四行参数,改完就能跑
留言板源码里几乎必然有一个数据库连接文件,名字可能是db.php、conn.php或者config.php。打开它,核心就四行配置。你把上一节建好的库名、用户名、密码填进去,应用就能连上数据库:
<?php $host = 'localhost'; // 数据库地址,XAMPP 默认 localhost $user = 'root'; // 数据库用户名,本地环境默认 root $pass = ''; // 数据库密码,XAMPP 默认空,phpStudy 默认 root $dbname = 'message_board'; // 库名,和导入 sql 时用的库保持一致 $conn = new mysqli($host, $user, $pass, $dbname); $conn->set_charset('utf8mb4'); if ($conn->connect_error) { die('数据库连接失败: ' . $conn->connect_error); }new mysqli()四个参数依次是地址、用户名、密码、库名,顺序别记反,很多人把用户名和密码写反了就开始怀疑人生。set_charset('utf8mb4')这行是防中文乱码的关键,PHP 连接 MySQL 时的字符集和建表字符集必须一致,否则入库的数据看着是好的,查出来就是一遍乱码。
改完这份配置文件后,先别急着直接访问首页。单独写一个test.php,内容只有一行<?php require 'db.php'; echo 'ok';,浏览器访问一下,能输出ok说明数据库连接这条路通了。这一步能帮你把问题和后面的代码逻辑隔离开。
3. 留言板核心逻辑:写留言、读留言、渲染列表的完整拆解
3.1 写留言入库:为什么我建议你用预处理而不是直接拼 SQL
留言板的「写」操作,本质上就三步:接收用户提交的nickname和content,写入数据库,然后跳回列表页。很多老源码在这里直接做了字符串拼接,比如"INSERT INTO messages (nickname, content) VALUES ('" . $_POST['name'] . "', '" . $_POST['content'] . "')"。这种写法短平快,但有一个致命问题:只要用户在留言内容里输入一个单引号,SQL 语句的语法就被破坏,轻则报错,重则被塞进一段恶意 SQL,这就是典型的 SQL 注入。
我建议你把写留言的代码改成预处理语句,改动量不大,安全性完全不一样:
<?php require 'db.php'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $nickname = trim($_POST['nickname'] ?? ''); $content = trim($_POST['content'] ?? ''); if ($nickname === '' || $content === '') { die('昵称和留言内容都不能为空'); } $stmt = $conn->prepare('INSERT INTO messages (nickname, content, create_time) VALUES (?, ?, NOW())'); $stmt->bind_param('ss', $nickname, $content); $stmt->execute(); header('Location: index.php'); exit; } ?>$conn->prepare()把 SQL 结构提前发给 MySQL 服务器,?是占位符,bind_param('ss', ...)里的s表示参数类型是字符串,两个s对应两个占位符。用户输入无论包含什么特殊字符,都会被当作纯文本处理,不会改变 SQL 语义。create_time直接用 MySQL 的NOW()生成,省去了 PHP 端格式化时间的步骤,时区问题也留给数据库统一处理。
$_POST['nickname'] ?? ''是 PHP 7 以上才有的语法,作用是当这个键不存在时返回空字符串,避免提交表单缺字段时直接抛未定义索引警告。如果你要跑在老 PHP 5.x 上,需要换成isset($_POST['nickname']) ? $_POST['nickname'] : ''。
3.2 列表页读取与渲染:HTML 混排和纯 PHP 输出怎么选
留言板的「读」比「写」复杂一档,因为它涉及循环输出和 HTML 混排。主流的写法有两种:一种是把 PHP 标签直接嵌进 HTML 模板里,另一种是用 PHP 拼接字符串然后统一echo。新手更推荐前者,结构清楚,改样式方便。
列表页核心代码长这样:
<?php require 'db.php'; $result = $conn->query('SELECT id, nickname, content, create_time FROM messages ORDER BY id DESC'); while ($row = $result->fetch_assoc()) { $nickname = htmlspecialchars($row['nickname'], ENT_QUOTES, 'UTF-8'); $content = htmlspecialchars($row['content'], ENT_QUOTES, 'UTF-8'); ?> <div class="message"> <span class="name"><?php echo $nickname; ?></span> <span class="time"><?php echo $row['create_time']; ?></span> <p class="text"><?php echo nl2br($content); ?></p> </div> <?php } ?>$conn->query()适合这种没有用户输入参与查询的固定 SQL,不需要预处理。ORDER BY id DESC让最新留言排在最前面,符合留言板的阅读习惯。fetch_assoc()每次取一行,返回关联数组,字段名就是表的列名,循环到没有数据为止。
这里最关键的是htmlspecialchars()。用户提交的内容里如果带有<script>标签,直接原样输出,这段脚本会在其他访客的浏览器里执行,也就是 XSS 攻击。htmlspecialchars把<转成<,>转成>,脚本就变成了纯文本。ENT_QUOTES让单引号双引号都转义,'UTF-8'告诉函数按 UTF-8 处理字符,这两个参数很容易漏写,漏了在某些特殊字符下会出乱码或者转义不完全。
nl2br()把用户输入里的换行转成<br>,否则用户按回车换的行在 HTML 里会挤成一行。少数的老源码没有这一步,留言内容密密麻麻一团,体验很差。
4. 常见问题与避坑排查:这份留言板最容易翻车的五个现场
现象 1:导入 sql 后,页面和数据库里中文全部变成???或者乱码。原因:建表语句里的字符集是latin1,或者db.php里少了set_charset('utf8mb4')。我遇到过一份 sql 文件,建表时只写了DEFAULT CHARSET=utf8,表面看没问题,但utf8在 MySQL 里是utf8mb3的别名,不支持四字节字符,部分中文和 emoji 入库就变成问号。解决:打开 sql 文件,把CHARSET=utf8全局替换成CHARSET=utf8mb4,重新导入,然后确认 PHP 连接也设置了 utf8mb4。记住一个原则:库、表、连接三处字符集必须统一。
现象 2:打开页面直接白屏,或者报Fatal error: Uncaught Error: Call to undefined function mysql_connect()。原因:源码用的是 PHP 5 时代的mysql_*函数,你装的是 PHP 7.4 以上,这个函数已经被移除了。解决:先全局搜索确认是不是这种情况。如果是mysqli_connect,那就是另一个问题——php.ini里extension=mysqli没启用。Windows 上用的 XAMPP,打开php.ini把这行前面的分号去掉;如果是新代码用了mysqli类的写法,确认类是原生存在的。PHP 8 下没有mysqli类,通常是因为环境没配好而不是代码错了。
现象 3:数据库密码明明是 root,但页面一直提示Access denied for user 'root'@'localhost'。原因:MySQL 8.0 默认用caching_sha2_password密码认证,老版本的 PHP mysqli 驱动默认只认mysql_native_password,两边握手失败。解决:最省事的方式是把 MySQL 用户认证插件改回老格式,执行以下 SQL 命令:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;如果你用的是 phpStudy 这类集成环境,注意区分它内置 MySQL 的端口。很多时候端口是 3306 而不是默认的 3306,连接配置里的$host要写成127.0.0.1:3307这种格式,否则connect_error报的是无法连接而不是密码错。
现象 4:留言提交后,页面变成下载文件或者直接显示 PHP 源码。原因:Apache 没有正确解析 PHP 文件,PHP 模块没加载。这个问题几乎和代码无关,纯环境问题。解决:在项目根目录放一个info.php,里面写<?php phpinfo(); ?>,浏览器访问如果能看到 PHP 信息页,说明解析正常;如果还是下载或显示源码,检查 Apache 的httpd.conf里有没有加载 PHP 模块,或者.htaccess里加了奇怪的AddType配置。卸载重装环境的事我干过不止一次,后来学乖了,先看info.php再决定动不动环境。
现象 5:用户留言里带了<img>标签,页面排版直接崩掉。原因:输出时没有做htmlspecialchars()转义,HTML 标签被浏览器当成了真实标签解析。这个比有人想象中严重得多,不只是个表情图片的事,完全可以塞入<script>或者<iframe>挂马。解决:回头检查列表页的输出代码,所有从数据库取出来的用户内容,一律经过htmlspecialchars($value, ENT_QUOTES, 'UTF-8')再输出。这个函数是留言板的最后一道防线,每次改模板都要记得带上。
5. 进阶技巧:分页与基础校验,把留言板改成能拿得出手的版本
5.1 分页参数的计算:LIMIT 别写死
留言多了以后,一个页面全部渲染出来,数据库压力和白屏风险都上来了。分页的数学很简单,但细节决定体验。每页显示条数建议提取成变量,方便后续调整:
<?php $perPage = 10; $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $totalSql = 'SELECT COUNT(*) AS cnt FROM messages'; $totalResult = $conn->query($totalSql); $totalCount = $totalResult->fetch_assoc()['cnt']; $totalPages = max(1, (int)ceil($totalCount / $perPage)); $page = min($page, $totalPages); $offset = ($page - 1) * $perPage; $result = $conn->query("SELECT nickname, content, create_time FROM messages ORDER BY id DESC LIMIT $perPage OFFSET $offset"); while ($row = $result->fetch_assoc()) { // 渲染逻辑与之前一致 } ?>max(1, (int)$_GET['page'])防止负数页码,min($page, $totalPages)防止用户手输一个超出范围的页码把数据库查空。OFFSET是跳过的行数,LIMIT是取多少行,LIMIT 10 OFFSET 20表示跳过前面 20 条,取第 21 到 30 条。这里有个新手容易犯的错:把$perPage直接写进 SQL 字符串没有问题,但如果哪天改成从用户输入取$perPage,那就必须强制转成整数,否则又是注入点。
5.2 提交校验:后端比前端更值得信任
很多留言板源码只在前端写了required,后端收到数据直接就入库了。这个做法在本地练手没问题,但你要认清一个现实:前端的限制随时可以被绕过,真正可靠的校验必须放在 PHP 端。除了前面代码里的空值判断,建议再加两个约束:留言长度限制和提交频率限制。
if (mb_strlen($content) > 500) { die('留言内容不能超过 500 字'); } if (isset($_SESSION['last_post']) && time() - $_SESSION['last_post'] < 10) { die('留言太频繁,请稍后再试'); } $_SESSION['last_post'] = time();mb_strlen是按字符数统计的,如果你用strlen,一个中文字符在 UTF-8 下占 3 字节,500 字的中文会被误判成 1500 的长度,所以多字节字符必须用mb_系列函数。频率限制是防止有人拿着脚本刷留言板,简单的 session 时间戳就能挡住大部分情况。这两处加上之后,这份源码的可用性基本到了可以直接拿去做课设或者个人站点的程度。
做这套改造的时候我又踩了一次老代码的坑:第一次只加了前端maxlength,想着后端反正有 js 限制,结果用 Postman 一测,5000 字的长文本照样进了库,整个列表页排版直接崩了。从那以后我每次接手这类 PHP 老源码,都强制先做四件事:打开错误显示、读建表语句、核对字符集、用脚本把每个输入字段都灌一次超长数据。前三件事帮我定位环境问题,第四件事帮我看清代码的真实底线。这四步下来,留言板能不能跑、能扛住多恶劣的输入,基本心里有数了。希望帮到你。
本文还有配套的精品资源,点击获取