Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_EleVue/zh/tools/rich-text.md
T
2026-10-08 17:50:29 +08:00

181 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7.3 富文本处理
:::tip 概述
富文本(如文章内容)中的图片/视频链接同样需要处理。项目采用 `[IMG_URL]` 占位符机制:入库时迁移临时文件并把域名替换为占位符,读取时再还原为当前配置的域名——支持域名变更场景。
:::
## 处理流程图
```text
┌─────────────────────────────────────────────────────────────────┐
│ 写入流程(add/update) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 前端编辑器插入图片 │
│ │ │
│ ▼ │
│ 上传到临时目录(uploadFile 接口) │
│ │ │
│ ▼ │
│ 提交 HTML 内容到后端 │
│ │ │
│ ▼ │
│ Service(use HasFileProcessing) │
│ processContentFieldsOnSave() │
│ │ │
│ ├─ 解析 <img> 标签的 src │
│ ├─ 解析 <video>/<source>/<embed> 标签的 src │
│ ├─ 解析 <a> 标签的文件 href │
│ │ │
│ ▼ │
│ save_content(&$content, $saveDir) ← 引用传递,原地修改 │
│ │ │
│ ├─ 站内临时文件 → save_file() 迁移至正式目录 │
│ ├─ 正式文件 → 去掉域名 │
│ │ │
│ ▼ │
│ 替换为 [IMG_URL]/相对路径 占位符 │
│ │ │
│ ▼ │
│ 数据库存储含占位符的 HTML │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 读取流程(detail/pageList) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 数据库读取含占位符的 HTML │
│ │ │
│ ▼ │
│ Service.processContentFieldsOnRead() │
│ │ │
│ ├─ 替换 [IMG_URL] 为 config/file.php 的 domain_url │
│ │ 如 https://cdn.example.com │
│ │ │
│ ▼ │
│ 返回完整 URL 的 HTML 给前端 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## 在 Service 中配置
```php
class ArticleService extends BaseTenantService
{
// 必须 use HasFileProcessing(否则占位实现直接透传,不处理)
use HasFileProcessing, /* 其他 Trait */;
/**
* 富文本字段
*
* @var array
*/
protected array $contentFields = ['content'];
}
```
配置后自动处理:
- **add/update**:`processContentFieldsOnSave()` → `save_content()` 处理临时文件与占位符
- **detail/pageList/allList**:`processContentFieldsOnRead()` / `...ReadList()` 还原占位符
:::warning 未启用 Trait 不生效
`BaseService` 中的 `processContentFieldsOnSave` 是**占位实现**(直接返回原数据)。子类必须 `use HasFileProcessing` 真实实现才会处理——声明了 `contentFields` 却不 `use`,富文本将原样入库。
:::
## 为什么使用占位符?
:::tip 域名变更场景
假设初始配置 `FILE_DOMAIN_URL=https://cdn.example.com`,文章内容中图片 URL 为:
```text
https://cdn.example.com/uploads/article/20261002/abc.jpg
```
存储时替换为:
```text
[IMG_URL]/uploads/article/20261002/abc.jpg
```
如果后续域名变更为 `https://cdn2.example.com`,只需修改 `.env` 中的 `FILE_DOMAIN_URL`,**已存储的内容无需逐条更新**,读取时自动替换为新域名。
:::
## 处理范围
`save_content()` 函数处理以下 HTML 标签:
| 标签 | 属性 | 说明 |
|------|------|------|
| `<img>` | `src` | 图片 |
| `<video>` | `src` | 视频 |
| `<source>` | `src` | 视频源 |
| `<embed>` | `src` | 嵌入内容 |
| `<a>` | `href` | 文件链接(仅处理带扩展名的链接) |
## 代码示例
```php
// app/Helpers/helpers.php 中的 save_content(来自项目实际代码的函数签名)
/**
* 处理富文本内容:站内临时文件转正、正式文件去掉域名,最终替换为 [IMG_URL] 占位。
*
* @param string $content 富文本 HTML(引用传递,原地修改)
* @param string $saveDir 正式保存子目录
* @return bool 是否发生替换
*/
function save_content(string &$content, string $saveDir = 'article'): bool
{
$config = config('file');
$domainUrl = rtrim($config['domain_url'] ?? '', '/');
if (empty($domainUrl) || empty($content)) {
return false;
}
$urlMap = [];
// 1. 匹配 <img> 标签的 src
save_content_match($content, '/<img[^>]+src=["\']?([^"\'>\s]+)/i', $domainUrl, $saveDir, $urlMap);
// 2. 匹配 <video>/<source>/<embed> 标签的 src
save_content_match($content, '/<(?:video|source|embed)[^>]+src=["\']?([^"\'>\s]+)/i', $domainUrl, $saveDir, $urlMap);
// 3. 匹配 <a> 标签的 href(指向文件的情况)
save_content_match($content, '/<a[^>]+href=["\']?([^"\'>\s]+\.\w{2,5})["\']?/i', $domainUrl, $saveDir, $urlMap);
// 批量替换
if (! empty($urlMap)) {
$content = str_replace(array_keys($urlMap), array_values($urlMap), $content);
}
return true;
}
```
**处理规则(来自 `save_content` 实现注释):**
- 站内临时文件 → 迁移到正式目录后记为 `[IMG_URL]` 占位
- 正式文件 → 去掉域名后记录为 `[IMG_URL]` 占位
- 外部 URL → 不处理(保持原样)
## 注意事项
:::warning 富文本安全
- 富文本内容可能包含 XSS 攻击代码,建议在前端使用编辑器的安全模式(粘贴过滤)
- `save_content` 只处理本站域名下的 URL,外部 URL 不做处理
- 图片标签的 `src` 必须是完整 URL(包含域名),否则无法识别
- 响应统一 `Content-Type: application/json`(ForceJsonResponse 中间件),内容不会被浏览器按 HTML 渲染
:::
:::tip 与文件字段的分工
- **单文件/多文件字段**(`fileFields`/`multiFileFields`)→ 结构化字段,见 [7.1 文件上传全流程](/zh/tools/upload.html)
- **富文本字段**(`contentFields`)→ HTML 内嵌资源,本页说明
- **配置项的 value**(按 `type` 分派,富文本类型为 `ueditor`)→ `HasConfigValueProcessing` Trait,见 [5.12 系统配置管理](/zh/core/config.html)
前两者由 `HasFileProcessing` Trait 提供,按属性声明分别启用;配置项不声明字段列表,而是由记录自身的 `type` 决定处理方式。
:::