推荐用tinymce(带markdown按钮)编辑,保存原始markdown文本,后端用markdown-it渲染;禁用html解析防xss,blade中用htmlstring安全输出,图片路径需统一处理,前后端均需校验空内容与不可见字符。

用 tinymce + markdown-it 插件实现双向渲染
纯 Markdown 编辑器在 Laravel 后台并不推荐直接嵌入「所见即所得」型编辑器(如 CKEditor)之外的方案——因为后台运营人员普遍不熟悉语法,而纯预览模式又无法实时编辑。真正可行的是:前端用 TinyMCE(带 Markdown 按钮插件),后端保存 Markdown 源文本,渲染时用 markdown-it 转 HTML。
关键点在于避免「富文本转 Markdown」的不可逆损失。所以编辑流程必须是:TinyMCE 编辑 → 点击「切换为 Markdown 源码」按钮 → 手动微调 → 提交保存原始 .md 字符串。
-
TinyMCE需启用code_sample和自定义按钮,用editor.getContent({ format: 'text' })获取纯文本,再交由前端markdown-it渲染预览 - 不要用
toMarkdown类库反向转换 HTML → Markdown,会丢失缩进、列表嵌套层级等信息 - Laravel 控制器接收字段时不做任何过滤,
strip_tags()或htmlspecialchars()会破坏```php这类代码块
markdown-it 在 Blade 中安全渲染需关掉 HTML 转义
Blade 默认对 {{ $content }} 做 HTML 转义,但 markdown-it 输出的是已转义过的 HTML 字符串(如 <p></p>),如果再被 Blade 二次转义就变成可见的 <p>。
正确做法是:在服务提供者中注册一个 Blade 组件或辅助函数,内部调用 markdown-it 并返回 Illuminate\Support\HtmlString 实例。
use Illuminate\Support\HtmlString;
use MarkdownIt\MarkdownIt;
<p>function md(string $text): HtmlString
{
$md = new MarkdownIt();
return new HtmlString($md->render($text));
}然后在 Blade 中直接写:{{ md($post->body) }} —— 不加 !!,也不用 {!! !!},避免 XSS 风险失控。
- 务必禁用
html选项:new MarkdownIt(['html' => false]),否则用户可注入<script></script> - 若需支持表格,启用
table插件;需支持任务列表,加task-lists插件(需额外npm install) - 不要在模型的
getBodyAttribute里自动渲染,会导致 JSON API 返回 HTML,破坏前后端分离
文件上传与图片路径需手动处理,markdown-it 不解析相对路径
用户在编辑器里拖入图片,TinyMCE 默认生成类似  的语法,但 Laravel 的 storage:link 是软链到 public/storage,而 Markdown 渲染器不会帮你补前缀或做路径映射。
解决方案分两步:上传时统一存到 public/uploads(跳过 storage),或保留 storage 但重写图片规则。
- 推荐方式:前端上传走
/api/upload接口,后端用Storage::disk('public')->putFile('uploads', $request->file('image')),返回/uploads/xxx.png - 若坚持用
storage/app,则需在markdown-it渲染前,用正则替换所有!\[.*?\]\((.*?)\)中的路径,例如把(storage/app/xxx.jpg)替成(/storage/xxx.jpg) - 别依赖
markdown-it-anchor自动生成 ID,Laravel 的 SEO 友好 URL 已有 slug,锚点应由业务控制
表单提交前校验 Markdown 语法,避免空内容或非法符号炸掉页面
用户可能误删全部内容,或粘贴进不可见控制字符(如零宽空格 \u200B),导致 markdown-it 渲染出错或 Blade 报 htmlspecialchars(): Argument #1 must be string。
最轻量的防御是在提交前用 JS 做基础检查:
if (!value.trim()) {
alert('内容不能为空');
return false;
}
if (/[\u200B-\u200D\uFEFF]/.test(value)) {
alert('检测到不可见字符,请清除后重试');
return false;
}后端也应兜底:
- 控制器中用
mb_detect_encoding($request->input('body'), 'UTF-8', true) === false拦截乱码 - 用
!is_string($body) || strlen($body) > 100000限制长度,防 DoS - 不要用
trim($body) === ''判断空,要配合preg_replace('/\s/u', '', $body) === ''去除全空白符
Markdown 解析本身没有「语法错误」概念,但非法嵌套(如未闭合的代码块)会导致后续段落格式错乱——这种问题只能靠前端预览+人工核对,没法全自动修复。










