json编码含未转义html的字符串会失败,因双引号、unicode控制符(如\u2028)、换行符等破坏json结构;须先净化html、再严格json编码、最后加校验头,并在消费者端检查json_last_error()、还原实体、过滤xss。

不能直接 json_encode() 含未转义 HTML 的字符串发给 RabbitMQ,否则消费者端 json_decode() 会失败或返回 null,后续字段访问直接报错。
为什么 JSON 编码富文本会出问题?
HTML 字符串里常见双引号、反斜杠、换行符、Unicode 控制字符(如 \u2028)、未闭合标签等。PHP 的 json_encode() 默认不处理这些,尤其当内容来自用户输入或 WYSIWYG 编辑器时:
- 未转义的双引号导致 JSON 结构断裂:
{"content":"<p class="intro">..."}</p>→ 解析失败 - 编辑器插入的零宽空格、行分隔符(
\u2028)在 JSON 中非法,json_decode()直接返回false - 嵌套过深或超长 HTML 触发
json_encode()内部限制(如JSON_ERROR_DEPTH或JSON_ERROR_UTF8)
安全序列化的三步实操法
核心是:**先净化 HTML → 再严格 JSON 编码 → 最后加校验头**。不依赖消费者端“尽力修复”:
- 用
htmlspecialchars()或更稳妥的htmlentities($html, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8')预处理原始 HTML,把所有特殊字符转成实体("→",→ <code>),避免破坏 JSON 结构 - 调用
json_encode()时必须传入JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE三个 flag,强制 UTF-8 安全输出,且不转义斜杠和中文 - 生产者发消息前,检查
json_last_error() === JSON_ERROR_NONE;若非零,直接丢弃或打日志,绝不强发
示例:
$raw_html = '<p class="editor">用户粘贴的<script>alert(1)</script></p>';
$safe_html = htmlentities($raw_html, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
$payload = ['html' => $safe_html, 'meta' => ['version' => 2]];
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON encode failed: ' . json_last_error_msg());
}
$message = new AMQPMessage($json, ['delivery_mode' => 2, 'content_type' => 'application/json']);
$channel->basic_publish($message, '', 'rich_text_queue');
消费者端必须做的解码防护
收到 $msg->body 后,不能假设它一定是合法 JSON:
- 先用
json_decode($msg->body, true)解析,再立刻检查json_last_error()—— 不是=== JSON_ERROR_NONE就该nack并进死信队列,不能continue - 解析成功后,对
['html']字段必须再次用html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8')还原,否则页面显示的是<p></p>而不是<p></p> - 如果业务需要渲染 HTML,还原后仍需用
strip_tags()或 DOMDocument 白名单过滤,防止 XSS(html_entity_decode()不等于安全)
绕过 JSON 的替代方案(仅限特定场景)
若 HTML 极大(>1MB)或含二进制数据(如 Base64 图片),JSON 编码开销高且易触发内存限制,可改用:
-
base64_encode(gzdeflate($raw_html))压缩 + 编码,content_type改为application/octet-stream,并在消息 header 里加encoding: gzip+base64 - 生产者端存 HTML 到对象存储(如 OSS/S3),只发 URL 和签名过期时间到 RabbitMQ,消费者按需拉取
- 注意:这两种方式都要求生产者与消费者约定协议,且无法被 RabbitMQ Management UI 直观查看内容
真正麻烦的从来不是编码本身,而是 HTML 来源不可控、消费者没做 json_last_error() 检查、以及还原后忘了过滤 XSS —— 这三处漏掉任一,轻则消息积压,重则服务崩溃或安全漏洞。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











