正确做法是入库原样存储HTML,输出时按上下文编码:HTML正文用htmlspecialchars,属性值同理,JS中用json_encode,富文本必须用HTMLPurifier清洗后输出。

一、入库:保持原始 HTML,禁用任何自动转义
PHP 8.5.7 + PDO/MySQLi 默认不会转义 HTML —— 这是好事,要维持它:
- 绝不调用
htmlspecialchars()、addslashes()或strip_tags()后再存库,否则富文本会变成乱码(如<p>Hello</p>) - 避免手写
Db::execute("INSERT INTO ... '$html'")这类拼接 SQL,它既可能二次转义,更会引发 SQL 注入 - 务必使用预处理语句:
$stmt = $pdo->prepare("INSERT INTO posts(content) VALUES (?)"); $stmt->execute([$rawHtml]); - 数据库字段类型设为
TEXT或MEDIUMTEXT,别用VARCHAR(255)截断长 HTML
二、输出:按位置决定怎么编码,不是“统一转义”
同一段 HTML 可能出现在不同地方,每种场景需不同处理:
- 渲染到 HTML 正文区域(如
<article><?php echo $content; ?></article>)→ 用htmlspecialchars($content, ENT_QUOTES | ENT_HTML5, 'UTF-8') - 填入
<input value="...">属性 → 同样用htmlspecialchars,且确保属性用双引号包裹 - 嵌入 JS 字符串(如
var desc = "<?php echo $content; ?>";)→ 必须改用json_encode($content, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) - 输出到
<div data-desc="...">→ 同样是属性上下文,htmlspecialchars即可
三、富文本场景:HTMLPurifier 是刚需,不是可选项
如果用户能发带 <img>、<a>、<strong> 的内容(比如后台编辑器),htmlspecialchars 会把所有标签干掉 —— 这时必须清洗,而非过滤:
- 安装 HTMLPurifier(兼容 PHP 8.5+)
- 配置白名单:
'HTML.Allowed' => 'p,b,i,strong,em,a[href|title],img[src|alt|width|height]' - 只在输出前调用:
$clean = $purifier->purify($rawHtml); echo $clean; - 别在入库时 purify —— 后台可能需要原始 HTML 编辑,且 purify 结果不可逆



















