签入版本
This commit is contained in:
@@ -1,140 +1,55 @@
|
||||
# 7.3 富文本处理
|
||||
|
||||
:::tip 概述
|
||||
富文本(如文章内容)中的图片/视频也需要处理。使用 `[IMG_URL]` 占位符机制,支持域名变更场景。
|
||||
富文本持久化遵循**占位符契约**:正文内本站文件地址统一存 `[IMG_URL]/{相对路径}`,读侧唯一还原出口是 `ExpandContentFields`。换域名、迁移环境时只改配置,正文零改动。
|
||||
:::
|
||||
|
||||
## 处理流程图
|
||||
## 契约全景
|
||||
|
||||
```html
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 写入流程(add/update) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 前端编辑器插入图片 │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 上传到临时目录 temp/日期/ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 提交 HTML 内容到后端 │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ BaseLogic::processContentFieldsOnSave() │
|
||||
│ │ │
|
||||
│ ├─ 解析 <img> 标签的 src │
|
||||
│ ├─ 解析 <video>/<source>/<embed> 标签的 src │
|
||||
│ ├─ 解析 <a> 标签的文件 href │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ save_file() 迁移临时文件 → 正式目录 │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 替换为 [IMG_URL]/相对路径 占位符 │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 数据库存储含占位符的 HTML │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```text
|
||||
写侧(保存文章时,ContentFields 声明):
|
||||
正文 HTML 中本站文件 URL → 提取为相对路径 → [IMG_URL]/{相对路径}
|
||||
引用的 temp 文件 → saveTransform 转正到模块目录
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 读取流程(detail/pageList) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 数据库读取含占位符的 HTML │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ BaseLogic::processContentFieldsOnRead() │
|
||||
│ │ │
|
||||
│ ├─ 替换 [IMG_URL] 为实际域名 │
|
||||
│ │ 如 https://cdn.example.com │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 返回完整 URL 的 HTML 给前端 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
存库:
|
||||
content 列存占位符形态的 HTML
|
||||
|
||||
读侧(Detail/Page 读取):
|
||||
ExpandContentFields 唯一还原出口:
|
||||
[IMG_URL]/{相对路径} → file.domainUrl + "/" + 相对路径
|
||||
```
|
||||
|
||||
## 在 Logic 中配置
|
||||
## 为什么用占位符
|
||||
|
||||
```php
|
||||
class ArticleLogic extends BaseLogic
|
||||
{
|
||||
/**
|
||||
* 富文本字段
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $contentFields = ['content'];
|
||||
}
|
||||
| 直存完整 URL 的问题 | 占位符契约的解法 |
|
||||
|---|---|
|
||||
| 换域名要刷全库正文 | 改 file.domainUrl 即全站生效 |
|
||||
| 环境迁移(测试→生产)正文带测试域名 | 相对路径随库迁移 |
|
||||
| 正文引用 temp 未转正文件 | 写侧统一转正 |
|
||||
|
||||
## ModuleSpec 声明
|
||||
|
||||
```go
|
||||
// internal/service 各内容模块(以 articleSpec 为例)
|
||||
ContentFields: []string{"content"}, // 富文本字段名单
|
||||
FileSaveDir: "article", // 正文内文件转正目录
|
||||
```
|
||||
|
||||
配置后 BaseLogic 自动处理:
|
||||
- **add/update**:调用 `save_content()` 处理临时文件
|
||||
- **detail/pageList/allList**:调用 `processContentFieldsOnRead()` 替换占位符
|
||||
写侧占位符化 + 转正、读侧还原,全部由基座按声明执行——**还原逻辑唯一出口**是 ExpandContentFields,任何模块不得自行拼接正文 URL。
|
||||
|
||||
## 为什么使用占位符?
|
||||
## 前端配合
|
||||
|
||||
:::tip 域名变更场景
|
||||
假设初始配置 `FILE.DOMAIN_URL = https://cdn.example.com`,文章内容中图片 URL 为:
|
||||
`https://cdn.example.com/article/20260920/abc.jpg`
|
||||
|
||||
存储时替换为:`[IMG_URL]/article/20260920/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/common.php 中的 save_content 函数
|
||||
|
||||
function save_content(string &$content, string $saveDir = 'article'): bool
|
||||
{
|
||||
$config = config('file');
|
||||
$domainUrl = rtrim($config['domain_url'] ?? '', '/');
|
||||
|
||||
if (empty($domainUrl) || empty($content)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集所有需要处理的 URL
|
||||
$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;
|
||||
}
|
||||
```
|
||||
- Editor 组件(TinyMCE / vue-quill 双引擎)产出标准 HTML
|
||||
- 编辑回显经读侧接口拿到的已是完整 URL(还原后)
|
||||
- 正文内插入图片走上传组件,拿到相对路径由后端写侧处理
|
||||
|
||||
## 注意事项
|
||||
|
||||
:::warning 富文本安全
|
||||
- 富文本内容可能包含 XSS 攻击代码,建议在前端使用编辑器的安全模式
|
||||
- `save_content` 只处理本站域名下的 URL,外部 URL 不做处理
|
||||
- 图片标签的 `src` 必须是完整 URL(包含域名),否则无法识别
|
||||
:::
|
||||
- 正文内**外部站点**图片保持原样(只有本站文件被占位符化)
|
||||
- 手写导出正文时不要绕过 ExpandContentFields
|
||||
- 清理 upload 目录时注意正文引用关系(删除文件前查引用)
|
||||
|
||||
## 相关
|
||||
|
||||
- [6.8 文章管理](/zh/modules/article.html)(消费模块)
|
||||
- [7.1 文件上传全流程](/zh/tools/upload.html)
|
||||
|
||||
Reference in New Issue
Block a user