refactor(phone-image): 配置字段对齐,架构文档更新

- PHP getConfigFields 移除 template/font,新增 pageAlignments
- validateConfig 字段与 getConfigFields 一致
- 架构文档更新数据流、配置体系、已知问题、修复记录章节
This commit is contained in:
augushong
2026-05-11 21:24:16 +08:00
parent e9d839ae8a
commit 518085d493
2 changed files with 126 additions and 50 deletions

View File

@@ -53,7 +53,44 @@ PhoneImageEngine.render() -- L129: 管线入口,带并发
## 2. 数据流
### 2.1 HTML内容的三个来源三源问题
### 2.1 修复后的单一数据源模型(当前状态
修复后,引擎闭包变量 `postData.content_html` 成为唯一的权威数据源,所有读写操作统一通过引擎 API 进行。
**权威数据源**:闭包 `postData.content_html``phone-image.js:32`),通过以下两个 API 对外暴露:
| API | 位置 | 用途 |
|-----|------|------|
| `getContentHtml()` | `phone-image.js:1485-1487` | 返回 `postData.content_html`,外部获取内容的唯一标准接口 |
| `updateConfig(newConfig)` | `phone-image.js:1488-1492` | 使用 `$.extend(config, newConfig)` 更新配置,**不重置** `postData` |
**DOM `#post-content-html` 的角色降级**:仅作为初始化数据源和显示层,不再被保存操作读取。保存后主动同步 `$('#post-content-html').html(PhoneImageEngine.getContentHtml())``phone_image.html:271`, `phone_image.html:295`),保持显示层与权威源一致。
**各操作的数据流**
```
保存配置 (#btn-save):
content_html 取自 PhoneImageEngine.getContentHtml() -- L268
保存后同步: $('#post-content-html').html(getContentHtml()) -- L271
生成并保存 (#btn-generate):
content_html 取自 PhoneImageEngine.getContentHtml() -- L290
保存后同步: $('#post-content-html').html(getContentHtml()) -- L295
重新排版 (doRender):
用 PhoneImageEngine.updateConfig(newConfig) -- L239
不再调用 init(),不重置 postData.content_html
加载历史 (loadFromHistory):
PhoneImageEngine.init(postData, historyConfig) -- L414 (全量初始化)
PhoneImageEngine.render() -- L416
```
**分页标记操作**仍然直接修改闭包内的 `postData.content_html``insertPageBreak` L1223, `removePageBreak` L1261但保存时通过 `getContentHtml()` 读取,确保标记不会丢失。
### 2.2 修复前的三源问题(历史记录)
> 以下记录修复前的问题状态,保留作为设计决策参考。
文章HTML内容在运行时存在于三个独立位置彼此之间没有同步机制
@@ -74,9 +111,9 @@ PhoneImageEngine.render() -- L129: 管线入口,带并发
-> 闭包 postData.content_html = options.contentHtml (phone-image.js:85)
```
**问题**:分页标记操作(`insertPageBreak`/`removePageBreak`)只更新闭包内的 `postData.content_html``phone-image.js:1223`, `phone-image.js:1261`),不更新 DOM `#post-content-html` 和模板局部的 `postData.contentHtml`。而保存操作(`#btn-save`, `#btn-generate`)读取的是 `$('#post-content-html').html()``phone_image.html:267`, `phone_image.html:288`,这意味着分页标记在保存时会丢失。
**问题(已修复)**:分页标记操作(`insertPageBreak`/`removePageBreak`)只更新闭包内的 `postData.content_html``phone-image.js:1223`, `phone-image.js:1261`),不更新 DOM `#post-content-html` 和模板局部的 `postData.contentHtml`。而保存操作(`#btn-save`, `#btn-generate`原来读取的是 `$('#post-content-html').html()`,这意味着分页标记在保存时会丢失。此问题已通过将保存操作改为读取 `getContentHtml()` 修复
### 2.2 完整数据流图
### 2.3 完整数据流图(修复后)
```
[数据库 Post.content_html]
@@ -98,15 +135,16 @@ phone_image.html 模板渲染
|
v
渲染管线 (phone-image.js render())
- 读取 postData.content_html
- 读取 postData.content_html (唯一权威源)
- 生成 pages 数组
- 截图显示缩略图
|
v
用户交互
- 分页标记 -> 修改 postData.content_html (闭包内)
- 保存配置 -> POST savePostOutputConfig (content_html 取自 DOM)
- 生成保存 -> POST savePostOutput (content_html 取自 DOM)
- 保存配置 -> POST savePostOutputConfig (content_html 取自 getContentHtml())
- 生成保存 -> POST savePostOutput (content_html 取自 getContentHtml())
- 重新排版 -> updateConfig() + render() (不重置 postData)
|
v
后端存储
@@ -114,18 +152,18 @@ phone_image.html 模板渲染
- PostOutputFile (每页图片文件记录)
```
### 2.3 历史记录加载流
### 2.4 历史记录加载流(修复后)
```
loadFromHistory(outputId) (phone_image.html:371)
loadFromHistory(outputId) (phone_image.html:374)
-> GET loadPostOutputConfig?id=XXX
-> 更新表单控件 (size/fontSize/watermark)
-> 更新 postData.contentHtml (模板局部变量)
-> 更新 DOM #post-content-html
-> doRender(renderConfig) (含 pageAlignments)
-> PhoneImageEngine.init(postData, historyConfig) (全量初始化, L414)
-> PhoneImageEngine.render() (L416)
```
注意:`loadFromHistory` 更新的是模板局部的 `postData.contentHtml` 和 DOM但没有直接更新闭包内的 `postData.content_html`。它依赖 `doRender -> init()` 的间接传递来同步
修复后 `loadFromHistory` 使用 `init + render` 全量初始化引擎,直接将历史内容传入闭包,不再依赖 `doRender` 的间接传递。
## 3. 存储模型
@@ -182,17 +220,20 @@ loadFromHistory(outputId) (phone_image.html:371)
## 4. 配置体系
### 4.1 PHP 端配置字段定义
### 4.1 PHP 端配置字段定义(对齐后)
`PhoneImage::getConfigFields()` (L15-23) 定义了5个字段:
`PhoneImage::getConfigFields()` (L15-23) 定义了4个字段,已与 JS 端对齐
| 字段 | 类型 | 选项/范围 | 默认值 |
|------|------|-----------|--------|
| `template` | select | minimal/magazine/mixed | minimal |
| `size` | select | xiaohongshu/douyin | xiaohongshu |
| `font` | select | source-han-sans/alibaba-puhuiti/lxgw-wenkai | source-han-sans |
| `fontSize` | number | 10-24 | 14 |
| `watermark` | text | - | '' |
| `pageAlignments` | json | - | '{}' |
**已移除的字段**`template`3个模板选项 minimal/magazine/mixed`font`3个字体选项 source-han-sans/alibaba-puhuiti/lxgw-wenkai已从 `getConfigFields()` 中移除,因为 JS 端无对应的模板/字体切换逻辑。
**新增的字段**`pageAlignments` 已新增到 `getConfigFields()`,类型为 `json`,默认值 `'{}'`,与 JS 端的逐页对齐功能对齐。
### 4.2 JS 端实际使用的配置
@@ -209,52 +250,63 @@ config = {
}
```
### 4.3 PHP/JS 字段对齐
### 4.3 PHP/JS 字段对齐状态(修复后)
两端存在明显的字段定义差异
修复后两端字段已基本对齐
| 问题 | PHP 定义 | JS 实际使用 |
|------|----------|-------------|
| `template` | PHP有定义 (3个模板选项) | JS完全不使用无任何模板切换逻辑 |
| `font` | PHP有定义 (3个字体选项) | JS不使用字体通过CSS引入无切换机制 |
| `pageAlignments` | PHP未定义 | JS核心功能保存在config中 |
| `contentPadding` | PHP未定义 | JS硬编码为20 (L23) |
| 字段 | PHP 定义 | JS 使用 | 状态 |
|------|----------|---------|------|
| `size` | select, 2个选项 | 核心功能 | 已对齐 |
| `fontSize` | number, 10-24 | 核心功能 | 已对齐 |
| `watermark` | text | 核心功能 | 已对齐 |
| `pageAlignments` | json | 核心功能 | 已对齐 |
| `contentPadding` | 未定义 | 硬编码为20 (L23) | 仍为 JS 内部常量,无需 PHP 定义 |
`validateConfig()` (L29-50) 验证 `template`/`size`/`font`/`fontSize``pageAlignments` 不在验证范围内。前端发送的配置比PHP定义的字段多PHP不做字段白名单过滤全部存入JSON。
`validateConfig()` (L28-50) 验证 `size`/`fontSize``pageAlignments` 为 JSON 类型不做严格校验。前端发送的配置比 PHP 定义的字段多(如 `content_html``sizes``contentPadding`PHP 不做字段白名单过滤,全部存入 JSON config 字段
> **修复前的字段不对齐记录**`template`PHP 有定义但 JS 不使用)和 `font`PHP 有定义但 JS 不使用)已从 PHP 端移除;`pageAlignments`JS 核心功能但 PHP 未定义)已新增到 PHP 端。
## 5. 已知问题
### 5.1 分页标记丢失(根因分析)
### 5.1 已修复
#### 5.1.1 分页标记丢失(已修复)
**现象**:用户通过中间栏的 "+" 按钮插入分页标记后,点击"保存"或"生成并保存",重新加载页面时分页标记消失。
**根因**:数据源不一致。
**根因**:数据源不一致。分页标记写入闭包 `postData.content_html`,但保存时读取的是 DOM `#post-content-html`(不含标记)。
分页标记的插入流程 (`insertPageBreak`, L1198-1227)
1. 从闭包 `postData.content_html` 读取内容
2. 在目标位置插入 `<hr>` 标签
3. 写回闭包 `postData.content_html`L1223
**修复方案**:保存操作改用 `PhoneImageEngine.getContentHtml()` 读取闭包内的权威数据源(`phone_image.html:268`, `phone_image.html:290`),保存后主动同步 DOM`phone_image.html:271`, `phone_image.html:295`)。
保存操作的配置构建 (`phone_image.html:262-268`, `284-289`)
```javascript
content_html: $('#post-content-html').html() // 读取的是DOM元素
```
#### 5.1.2 双数据源不一致(已修复)
DOM `#post-content-html` 的内容在页面初始化后从未被 `insertPageBreak` 更新。因此保存时发送的 `content_html` 是初始值,不含 `<hr>` 分页标记。下次加载页面时,服务端用这个无标记的内容渲染,标记丢失。
**现象**`doRender` 调用 `init()` 重置 `postData.content_html`,导致分页标记在重新排版时丢失。
### 5.2 保存数据量限制
**根因**`doRender` 通过 `init()` 传递配置,`init()` 会用 DOM 内容覆盖闭包变量。
**修复方案**`doRender` 改用 `PhoneImageEngine.updateConfig(newConfig)``phone_image.html:239`),仅更新配置不重置 `postData`
#### 5.1.3 PHP/JS 字段不对齐(已修复)
**现象**PHP 定义了 JS 不使用的 `template`/`font` 字段,缺少 JS 核心使用的 `pageAlignments` 字段。
**修复方案**PHP `getConfigFields()` 移除 `template`/`font`,新增 `pageAlignments``PhoneImage.php:17-22`)。
### 5.2 仍存在的问题
#### 5.2.1 保存数据量限制
`saveImages()` 有16MB的客户端检查`phone-image.js:1317-1320`但服务端没有对应的分块接收机制。大文章超过约30页的base64数据可能超出 PHP `post_max_size` 配置。
### 5.3 目录权限 0777
#### 5.2.2 目录权限 0777
`PhoneImage.php:95``:140``mkdir` 使用 0777 权限,与项目其他位置的 anti-pattern 一致。
### 5.4 接口 `PostOutputManagerInterface` 未被充分利用
#### 5.2.3 接口 `PostOutputManagerInterface` 未被充分利用
`PhoneImage` 实现了 `process()``getPreview()` 方法,但两者都返回空值(`PhoneImage.php:56-67`)。实际业务完全在控制器中直接调用 `PhoneImage` 的其他方法,绕过了接口定义的流程。接口仅作为类型约束存在。
### 5.5 `saveConfigOnly` 是静态方法但 `createOutput` 是实例方法
#### 5.2.4 `saveConfigOnly` 是静态方法但 `createOutput` 是实例方法
`PhoneImage::saveConfigOnly()` 是 static 方法L223而同类的 `createOutput()` 是实例方法L193。两者的实现几乎相同都是 `PostOutput::create()`),但调用方式不同。控制器中 `savePostOutput` 通过实例调用 `createOutput``savePostOutputConfig` 通过静态调用 `saveConfigOnly`,风格不统一。
@@ -305,11 +357,40 @@ DOM `#post-content-html` 的内容在页面初始化后从未被 `insertPageBrea
---
## 7. 修复记录
### 7.1 数据源统一修复
**修复日期**2026-05-11
**核心原理**将三个独立的数据源DOM、闭包变量、模板局部变量统一为单一权威数据源——引擎闭包变量 `postData.content_html`,通过新增的引擎 API 对外暴露。
**变更清单**
| 变更 | 文件 | 说明 |
|------|------|------|
| 新增 `getContentHtml()` | `phone-image.js:1485-1487` | 返回闭包内 `postData.content_html`,外部获取内容的唯一接口 |
| 新增 `updateConfig(newConfig)` | `phone-image.js:1488-1492` | 使用 `$.extend(config, newConfig)` 更新配置,不重置 `postData` |
| 保存按钮改用 `getContentHtml()` | `phone_image.html:268` | `#btn-save``content_html` 取自引擎 API 而非 DOM |
| 生成按钮改用 `getContentHtml()` | `phone_image.html:290` | `#btn-generate``content_html` 取自引擎 API 而非 DOM |
| 保存后同步 DOM | `phone_image.html:271,295` | 保存成功后 `$('#post-content-html').html(getContentHtml())` |
| `doRender` 改用 `updateConfig()` | `phone_image.html:239` | 替代 `init()` 调用,避免重置 `postData.content_html` |
| `loadFromHistory` 改用 `init + render` | `phone_image.html:414-416` | 全量初始化引擎,直接传入历史内容 |
| PHP `getConfigFields` 移除 `template`/`font` | `PhoneImage.php:17-22` | 移除 JS 端不使用的字段定义 |
| PHP `getConfigFields` 新增 `pageAlignments` | `PhoneImage.php:21` | 与 JS 端逐页对齐功能对齐 |
**修复效果**
- 分页标记在保存和重新排版后不再丢失
- 历史记录加载正确恢复内容和配置
- PHP/JS 端配置字段定义一致
---
*文档基于以下源文件的完整阅读:*
- `public/static/js/phone-image.js` (1487行)
- `view/admin/post/phone_image.html` (425行)
- `public/static/js/phone-image.js` (1495行)
- `view/admin/post/phone_image.html` (436行)
- `app/admin/controller/Post.php` (L350-579)
- `app/common/tools/PhoneImage.php` (234行)
- `app/common/tools/PhoneImage.php` (229行)
- `app/common/tools/PostOutputManagerInterface.php` (28行)
- `app/model/PostOutput.php` (62行)
- `app/model/PostOutputFile.php` (26行)