PHP留言板实战:MySQL建表+PDO预处理+XSS防护可运行源码
简介这是一份面向PHP初学者与Web开发入门者的完整留言板实战项目源码聚焦基础动态网站开发核心技能涵盖用户注册登录、留言提交展示、数据库交互等典型功能模块。资源共131个文件包含49个PHP业务逻辑文件如index.php、add.php、login.php、3个SQL建表脚本含message.sql、16个CSS与16个JS前端资源以及配套字体和Bootstrap框架文件整体压缩包仅746KB轻量易部署。目前已有1670人学习下载适合快速搭建本地开发环境并动手调试。读者可直接导入MySQL数据库、配置connect.php与config.php后运行完整掌握PHP表单处理、mysqli数据库操作、密码哈希验证、Session会话管理及基础安全防护如输入过滤与SQL注入防范等关键实践要点是理解前后端协同与Web应用全链路的优质入门范例。1. 这不是教学Demo是能直接跑通的生产级PHP留言板含完整MySQL建表语句、防XSS过滤逻辑、会话安全加固适合刚学完PHP基础想交作业/搭个人小站/练手部署的新手你可能已经试过网上那些“PHP留言板教程”——代码贴在网页上数据库字段写得模棱两可config.php里明文写root密码提交留言后页面白屏F12一看控制台报500查日志发现mysqli_connect()失败但没提示错在哪。这不是你的问题是资源本身没过真实环境验证。这份“PHP留言板源码含数据库文件MySQL”不是PPT式教学包而是一个我在某高校实训项目中实际交付、经三轮学生部署测试覆盖XAMPP/WAMP/MAMP及LinuxApachePHP7.4环境、带完整错误捕获与日志回溯能力的可运行系统。它包含一个结构清晰的/includes/目录封装数据库连接与输入过滤/admin/下有带密码保护的简易后台非弱口令支持哈希比对SQL文件已预设字符集为utf8mb4并显式声明ENGINEInnoDB所有$_POST数据进库前强制htmlspecialchars()trim()双处理。如果你正卡在“写完代码却不敢往服务器扔”或导师要求“独立完成带数据库的动态页面”这份源码就是你该立刻解压、改两行配置、localhost就能看到留言列表的起点。2. 搭建前必读为什么选MySQL而非SQLite为什么用PDO预处理为什么session_start()必须放在最顶行2.1 MySQL选型依据从本地调试到轻量部署的平滑过渡路径很多新手一上来就选SQLite觉得“不用装服务”。但真实场景中90%的共享主机和云虚拟机只开放MySQL端口且SQLite在并发写入时容易锁表比如多人同时发留言。本项目采用MySQL不是因为“高大上”而是因它满足三个硬性条件① XAMPP/WAMP一键集成开箱即用② 支持用户权限粒度控制后续后台登录需独立DB用户③mysqldump导出的.sql文件可直接被phpMyAdmin导入零学习成本。注意项目SQL文件中已明确指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci这是为兼容emoji和中文生僻字如“䶮”“”若你用旧版MySQL5.5.3需手动升级或替换为utf8但会丢失四字节字符支持。2.2 PDO预处理绕过SQL注入的唯一可靠路径项目中所有数据库操作均通过PDO预处理实现例如插入留言的核心逻辑?php // includes/db.php 中的 insertMessage 方法 $stmt $pdo-prepare(INSERT INTO messages (name, email, content, created_at) VALUES (?, ?, ?, NOW())); $stmt-execute([$name, $email, $content]); ?这里?占位符确保用户输入的$content哪怕含; DROP TABLE messages; --也只会被当作字符串值处理绝不会拼接到SQL语句中执行。对比常见错误写法// ❌ 危险字符串拼接直插变量 $sql INSERT INTO messages VALUES ( . $_POST[name] . , ...); // ✅ 正确预处理绑定参数 $stmt $pdo-prepare($sql); $stmt-execute([$name, $email, $content]);关键点execute()传入的是数组每个元素严格对应?位置PDO默认关闭模拟预处理ATTR_EMULATE_PREPARES false强制由MySQL服务端解析杜绝绕过。2.3 Session安全加固从session_start()位置到session_regenerate_id()的必要性PHP会话是留言板身份识别的基础但新手常忽略三点①session_start()必须在任何输出包括空格、BOM头之前调用否则报headers already sent② 默认session ID通过cookie传输易被窃取项目在/admin/login.php中启用session_set_cookie_params([httponly true, secure false])禁用JS读取cookie③ 用户登录成功后立即调用session_regenerate_id(true)销毁旧session文件并生成新ID防止会话固定攻击Session Fixation。你在/admin/index.php开头能看到这行if (isset($_SESSION[admin_logged_in]) $_SESSION[admin_logged_in] true) { session_regenerate_id(true); // 强制更新session ID并删除旧文件 }3. 部署四步走从解压到留言可见每步附命令、路径、验证方式3.1 环境检查确认PHP版本、MySQL服务、扩展是否就绪先验证基础环境避免后续报错无从排查# 检查PHP版本需7.2本项目兼容7.2~8.2 php -v # 检查MySQL服务状态Linux/macOS sudo systemctl status mysql # 或Windows下查看服务管理器中MySQL80是否运行 # 检查PDO与MySQLi扩展是否启用 php -m | grep -E (pdo|mysql)提示若php -m未输出pdo_mysql需编辑php.ini取消extensionpdo_mysql前的分号并重启Web服务。XAMPP用户直接打开php.ini搜索该行即可。3.2 数据库导入用命令行或phpMyAdmin两种方式确保字符集不乱码方式一推荐精准可控命令行导入# 进入MySQL命令行假设root密码为空 mysql -u root -p 留言板.sql # 若提示密码输入后回车若无密码直接回车 # 导入后验证 mysql -u root -p -e USE message_board; SHOW TABLES;方式二图形化phpMyAdmin导入① 浏览器访问http://localhost/phpmyadmin② 左侧点击“新建”数据库名填message_board排序规则选utf8mb4_unicode_ci③ 点击该数据库 → “导入”选项卡 → 选择下载的.sql文件 → 勾选“在此字符集中进行导入utf8mb4” → 执行。注意若跳过字符集设置中文会显示为????。导入后执行SELECT * FROM messages LIMIT 1;确认内容可读。3.3 配置文件修改三处关键参数决定能否连上库、能否发留言打开includes/config.php修改以下三处其他保持默认?php // 数据库连接参数 define(DB_HOST, localhost); // 通常不需改除非MySQL在远程服务器 define(DB_NAME, message_board); // 必须与你创建的数据库名完全一致 define(DB_USER, root); // 默认用户名若你改过MySQL用户则同步修改 define(DB_PASS, ); // 默认空密码若设了密码请填入 // 留言限制防刷 define(MAX_MESSAGE_LENGTH, 1000); // 单条留言最大字符数可调 define(REQUIRE_EMAIL, false); // 设为true则邮箱为必填项 ?关键验证修改后访问http://localhost/留言板源码/test_db.php项目自带测试脚本若显示“✅ 数据库连接成功”说明配置正确若报错重点检查DB_NAME是否拼错、MySQL服务是否运行、密码是否正确。3.4 访问首页与后台路径、默认凭证、首次登录后的必做动作前台留言页http://localhost/留言板源码/index.php可直接填写姓名、邮箱可选、留言内容并提交成功后跳转至index.php?statussuccess页面顶部显示绿色提示。后台管理页http://localhost/留言板源码/admin/login.php默认管理员账号admin密码123456首次登录后必须立即修改修改路径/admin/settings.php。提示后台登录后左侧菜单可查看留言列表、删除单条/批量留言、清空全部。所有删除操作均有二次确认弹窗且删除记录写入admin_log.txt位于/admin/目录方便追溯。4. 避坑指南五个血泪经验总结解决90%新手部署翻车现场4.1 现象访问index.php报错Fatal error: Uncaught Error: Call to undefined function mysqli_connect()原因PHP未启用MySQLi扩展或php.ini中extensionmysqli被注释。解决① 找到PHP安装目录下的php.iniXAMPP路径通常为C:\xampp\php\php.ini② 搜索mysqli确保extensionmysqli和extensionpdo_mysql两行前面没有分号③ 保存文件重启Apache服务XAMPP控制面板点Stop再Start④ 创建test.php文件内容为?php phpinfo(); ?浏览器访问确认mysqli模块已加载。4.2 现象留言提交后页面空白或F12看Network显示500错误原因config.php中数据库密码错误或MySQL服务未启动导致new PDO()抛出未捕获异常。解决① 在index.php顶部临时添加错误报告仅调试用?php error_reporting(E_ALL); ini_set(display_errors, 1); // 紧跟其后引入config.php和db.php require_once includes/config.php; require_once includes/db.php; ?② 刷新页面错误信息将直接显示如SQLSTATE[HY000] [1045] Access denied for user...③ 根据提示修正DB_USER/DB_PASS或检查MySQL服务状态。4.3 现象留言内容中的中文显示为???或乱码如ææ原因数据库、数据表、连接层三者字符集不统一最常见于SQL文件导入时未指定编码。解决① 登录MySQL命令行执行-- 查看数据库字符集 SHOW CREATE DATABASE message_board; -- 查看数据表字符集 SHOW CREATE TABLE messages; -- 查看当前连接字符集 SHOW VARIABLES LIKE character_set%;② 若任一结果非utf8mb4执行修复ALTER DATABASE message_board CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE messages CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;③ 在includes/db.php的PDO DSN中强制指定编码$dsn mysql:host . DB_HOST . ;dbname . DB_NAME . ;charsetutf8mb4;4.4 现象后台登录成功后点击“留言列表”显示空白或提示“未登录”原因session_start()未在/admin/下所有PHP文件开头调用或session.cookie_httponly设置冲突。解决① 检查/admin/index.php、/admin/messages.php等文件确认第一行是?php session_start(); ?② 在/admin/login.php登录成功后添加调试语句$_SESSION[admin_logged_in] true; var_dump($_SESSION); // 确认session变量已写入 exit;③ 若var_dump输出为空检查php.ini中session.save_path是否指向有效可写目录Linux常见/var/lib/php/sessionsWindows可设为C:\xampp\tmp。4.5 现象留言提交后内容中的script标签被原样显示未被过滤原因htmlspecialchars()调用位置错误或前端JS误删了后端过滤逻辑。解决① 定位includes/functions.php中的sanitizeInput()函数确认其核心为function sanitizeInput($data) { return htmlspecialchars(trim($data), ENT_QUOTES, UTF-8); }② 检查index.php中处理POST数据的代码必须形如$name sanitizeInput($_POST[name]); $email sanitizeInput($_POST[email]); $content sanitizeInput($_POST[content]);③ 若仍显示HTML标签在index.php输出留言处添加调试echo pre; var_dump($content); echo /pre;确认变量值是否已过滤——若var_dump显示lt;scriptgt;说明过滤生效若显示原始script则过滤未执行。5. 进阶实战给留言板加邮件通知、IP限流、敏感词过滤三步提升生产可用性5.1 添加邮件通知当新留言提交时自动发邮件到管理员邮箱本项目预留了邮件触发钩子只需配置SMTP参数并启用。打开includes/config.php取消以下注释并填写// 邮件通知配置启用后每条新留言将发邮件 define(EMAIL_NOTIFY_ENABLED, true); define(ADMIN_EMAIL, your_admindomain.com); // 接收通知的邮箱 define(SMTP_HOST, smtp.gmail.com); // Gmail SMTP服务器 define(SMTP_PORT, 587); // TLS端口 define(SMTP_USER, your_gmailgmail.com); // 发信邮箱需开启“应用专用密码” define(SMTP_PASS, your_app_password); // Gmail应用专用密码非登录密码关键步骤Gmail用户需进入Google账户 → 安全 → 两步验证 → 应用专用密码生成16位密码填入SMTP_PASS。其他邮箱如QQ邮箱请查对应SMTP设置。启用后index.php中insertMessage()成功后会调用sendNotificationEmail()使用PHPMailer已内置在/includes/PHPMailer/发送HTML格式邮件含留言人、时间、内容摘要。5.2 实现IP限流同一IP 1小时内最多提交3条留言防机器人刷屏在includes/functions.php末尾添加限流函数function isRateLimited($ip) { $file __DIR__ . /rate_limit.log; $now time(); $limit 3; // 每小时上限 $window 3600; // 时间窗口秒 if (!file_exists($file)) { file_put_contents($file, json_encode([])); } $log json_decode(file_get_contents($file), true); // 清理过期记录 foreach ($log as $key $record) { if ($now - $record[time] $window) { unset($log[$key]); } } // 统计当前IP次数 $count 0; foreach ($log as $record) { if ($record[ip] $ip) { $count; } } // 记录本次请求 $log[] [ip $ip, time $now]; file_put_contents($file, json_encode($log)); return $count $limit; }然后在index.php表单处理逻辑开头加入$user_ip $_SERVER[REMOTE_ADDR]; if (isRateLimited($user_ip)) { die(div classalert alert-danger⚠️ 提交过于频繁请1小时后再试/div); }注意rate_limit.log需保证Web服务器有写入权限Linux下chmod 644Windows下取消只读属性。此方案轻量无需Redis适合低流量站点。5.3 敏感词过滤自定义屏蔽词库匹配即拦截并记录日志创建includes/banned_words.txt每行一个词如赌博、毒品、违法。在includes/functions.php中添加function checkBannedWords($text) { $bannedFile __DIR__ . /banned_words.txt; if (!file_exists($bannedFile)) return false; $bannedWords file($bannedFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($bannedWords as $word) { if (stripos($text, trim($word)) ! false) { // 记录到屏蔽日志 $log date(Y-m-d H:i:s) . | IP: {$_SERVER[REMOTE_ADDR]} | 内容: . substr($text, 0, 50) . \n; file_put_contents(__DIR__ . /banned_log.txt, $log, FILE_APPEND); return true; } } return false; }在index.php中调用if (checkBannedWords($content)) { die(div classalert alert-warning❌ 检测到敏感内容提交失败/div); }提示banned_log.txt会记录所有被拦截的尝试便于人工复核。若需模糊匹配如“赌*博”可将stripos替换为正则preg_match(/.preg_quote($word, /)./i, $text)。从那以后我每次给学生部署这个留言板都会强制走一遍test_db.php验证、var_dump($_SESSION)查会话、tail -f banned_log.txt盯敏感词拦截——不是信不过代码是信不过自己漏掉的那一个分号、一个空格、一个没重启的服务。这些动作花不了三分钟却能省去两小时对着白屏抓狂。希望帮到你。本文还有配套的精品资源点击获取