留言板数据应优先用数据库(如SQLite/MySQL),仅单机调试可用加flock()锁的JSON文件;需过滤XSS、限制长度、重定向防重复提交,并注意路径权限与JSON解析错误处理。

留言板数据怎么存:用文件还是数据库?
直接写文件最轻量,适合单机调试或极小流量场景。但并发写入时容易丢数据,比如两人同时提交,fopen($file, 'a') 可能导致内容错位或覆盖。真要上生产,必须换 MySQL 或 SQLite。本地快速验证可以先用文件,但得加 flock() 锁住写操作,否则一刷新就乱。
- 文件路径建议固定为
data/messages.json,避免相对路径出错
- 每条留言至少存
name、content、created_at 三个字段
- 写入前用
json_encode($data, JSON_UNESCAPED_UNICODE) 保中文不乱码
- 读取失败时别静默吞掉错误,
file_get_contents() 返回 false 要检查
表单提交后怎么安全接收和过滤?
PHP 默认不会自动过滤用户输入,$_POST['name'] 和 $_POST['content'] 必须手动处理。直接拼进 JSON 或输出到页面,XSS 和文件路径遍历风险立刻出现。
- 用
trim() 去首尾空格,再用 htmlspecialchars() 转义输出(仅限显示时)
- 留言内容长度限制建议设在 PHP 层:
if (strlen($_POST['content']) > 500),别只靠前端 maxlength
- 名字字段禁用 HTML 标签,可用
strip_tags($_POST['name']),但更稳妥是白名单正则:preg_replace('/[^a-zA-Z0-9\u4e00-\u9fa5\x20]/u', '', $name)
- 提交后必须用
header('Location: .') 重定向,防止刷新重复提交
怎么读取并显示留言列表?
从 data/messages.json 读取时,file_get_contents() 失败很常见——目录没写权限、文件被锁、JSON 格式损坏都会返回空或警告。不能假设文件一定存在或格式一定正确。
- 先用
is_readable('data/messages.json') 判断可读性
-
json_decode(file_get_contents(...), true) 后必须检查返回值是否为 null,再用 json_last_error() !== JSON_ERROR_NONE 确认解析成功
- 显示时每条留言用
echo htmlspecialchars($msg['content'], ENT_QUOTES, 'UTF-8'),ENT_QUOTES 很关键,不然单引号双引号都可能破环 HTML 结构
- 时间字段建议统一用
date('Y-m-d H:i', $msg['created_at']),别直接 echo 时间戳
为什么本地测试正常,上线就报错?
最常踩的坑是路径和权限。开发时用 localhost,data/ 目录可能就在根目录下;但上线后 Web 服务器(如 Nginx)的文档根目录通常是 /var/www/html,而 PHP 进程用户(如 www-data)对 data/ 目录没有写权限。
- 绝对路径比相对路径可靠:
<strong>DIR</strong> . '/data/messages.json'
- 检查目录权限:
ls -l data/ 看是否为 www-data 可写(或加 chmod 755 data)
- 开启
display_errors = Off 时,fopen 失败不会报错,只能看 Web 服务器错误日志(如 /var/log/nginx/error.log)
- SQLite 方案反而更稳:只要
db.sqlite 文件可写,不用额外配 MySQL 用户
data/messages.json,避免相对路径出错 name、content、created_at 三个字段 json_encode($data, JSON_UNESCAPED_UNICODE) 保中文不乱码 file_get_contents() 返回 false 要检查 $_POST['name'] 和 $_POST['content'] 必须手动处理。直接拼进 JSON 或输出到页面,XSS 和文件路径遍历风险立刻出现。
- 用
trim()去首尾空格,再用htmlspecialchars()转义输出(仅限显示时) - 留言内容长度限制建议设在 PHP 层:
if (strlen($_POST['content']) > 500),别只靠前端maxlength - 名字字段禁用 HTML 标签,可用
strip_tags($_POST['name']),但更稳妥是白名单正则:preg_replace('/[^a-zA-Z0-9\u4e00-\u9fa5\x20]/u', '', $name) - 提交后必须用
header('Location: .')重定向,防止刷新重复提交
怎么读取并显示留言列表?
从 data/messages.json 读取时,file_get_contents() 失败很常见——目录没写权限、文件被锁、JSON 格式损坏都会返回空或警告。不能假设文件一定存在或格式一定正确。
- 先用
is_readable('data/messages.json') 判断可读性
-
json_decode(file_get_contents(...), true) 后必须检查返回值是否为 null,再用 json_last_error() !== JSON_ERROR_NONE 确认解析成功
- 显示时每条留言用
echo htmlspecialchars($msg['content'], ENT_QUOTES, 'UTF-8'),ENT_QUOTES 很关键,不然单引号双引号都可能破环 HTML 结构
- 时间字段建议统一用
date('Y-m-d H:i', $msg['created_at']),别直接 echo 时间戳
为什么本地测试正常,上线就报错?
最常踩的坑是路径和权限。开发时用 localhost,data/ 目录可能就在根目录下;但上线后 Web 服务器(如 Nginx)的文档根目录通常是 /var/www/html,而 PHP 进程用户(如 www-data)对 data/ 目录没有写权限。
- 绝对路径比相对路径可靠:
<strong>DIR</strong> . '/data/messages.json'
- 检查目录权限:
ls -l data/ 看是否为 www-data 可写(或加 chmod 755 data)
- 开启
display_errors = Off 时,fopen 失败不会报错,只能看 Web 服务器错误日志(如 /var/log/nginx/error.log)
- SQLite 方案反而更稳:只要
db.sqlite 文件可写,不用额外配 MySQL 用户
is_readable('data/messages.json') 判断可读性 json_decode(file_get_contents(...), true) 后必须检查返回值是否为 null,再用 json_last_error() !== JSON_ERROR_NONE 确认解析成功 echo htmlspecialchars($msg['content'], ENT_QUOTES, 'UTF-8'),ENT_QUOTES 很关键,不然单引号双引号都可能破环 HTML 结构 date('Y-m-d H:i', $msg['created_at']),别直接 echo 时间戳 localhost,data/ 目录可能就在根目录下;但上线后 Web 服务器(如 Nginx)的文档根目录通常是 /var/www/html,而 PHP 进程用户(如 www-data)对 data/ 目录没有写权限。
- 绝对路径比相对路径可靠:
<strong>DIR</strong> . '/data/messages.json' - 检查目录权限:
ls -l data/看是否为www-data可写(或加chmod 755 data) - 开启
display_errors = Off时,fopen失败不会报错,只能看 Web 服务器错误日志(如/var/log/nginx/error.log) - SQLite 方案反而更稳:只要
db.sqlite文件可写,不用额外配 MySQL 用户
文件方案的临界点其实很清晰:一旦需要分页、搜索、管理员删帖、IP 记录,就得切数据库。别硬撑。



















