46 lines
2.9 KiB
Markdown
46 lines
2.9 KiB
Markdown
# 2026-03-13 - 文章详情预览统一与内容安全
|
||
|
||
## 问题 / 场景
|
||
|
||
- 文章详情目前小程序侧只展示约 20% 内容作为预览,再引导用户付费解锁。
|
||
- 历史实现中前端本地按 20% 计算预览,后端曾同时返回外层 `content`(预览)和 `data.content`(全文),存在「接口约定不统一」和「误用 data.content 泄露全文」的风险。
|
||
- 需求:统一由后端按业务规则截取预览(改为 50%),小程序只按「是否已付费」选择用预览还是全文;未付费时,无论字段层级都不能拿到全文。
|
||
|
||
## 解决方案
|
||
|
||
- 在 `internal/handler/book.go` 中调整章节预览逻辑:
|
||
- `previewContent` 改为按字符数取正文前 50%(`total/2`),同时保证预览不少于 100 个字符;
|
||
- 预览结尾统一追加 `……(购买后阅读完整内容)` 作为提示文案。
|
||
- 在 `findChapterAndRespond` 中统一内容返回策略:
|
||
- 先根据 system_config.free_chapters / chapter_config.freeChapters / chapters.is_free / price 判断章节是否免费;
|
||
- 免费章节:`returnContent = ch.Content`(全文);
|
||
- 付费章节:
|
||
- 若 `checkUserChapterAccess` 判断用户已购买 / VIP / 全书:`returnContent = ch.Content`(全文);
|
||
- 否则:`returnContent = previewContent(ch.Content)`(仅预览);
|
||
- 构造响应时,将 `chForResponse.Content = returnContent`,并通过:
|
||
- 外层 `content: returnContent`,
|
||
- 内层 `data: chForResponse`(其中 `content` 也为 `returnContent`),
|
||
- 确保未授权用户在任意字段上都拿不到完整正文。
|
||
|
||
## 与前端的接口约定
|
||
|
||
- 小程序阅读页通过 `userId` 查询章节详情,`accessManager` 基于返回的章节信息与用户购买状态计算 `accessState`:
|
||
- 当 `accessState` 为 `free` 或 `unlocked_purchased` 时,前端使用 `res.data.content ?? res.content` 渲染全文;
|
||
- 当 `accessState` 为未登录 / 未购买时,前端只使用 `res.content` 渲染预览。
|
||
- 预览比例完全由后端控制(当前为 50%),小程序不再自行用 20% 做二次截断,只是把后端提供的预览完整展示出来。
|
||
|
||
## 代码位置
|
||
|
||
- 后端:
|
||
- `soul-api/internal/handler/book.go`
|
||
- 小程序(前端配合):
|
||
- `miniprogram/pages/read/read.js`
|
||
- `miniprogram/pages/read/read.wxml`
|
||
|
||
## 对后续开发的约定
|
||
|
||
- 预览长度(包括比例、最小字符数、提示文案)统一由后端控制;如需调整比例,只需修改 `previewContent`,保持接口字段含义不变。
|
||
- 任何需要「只返回部分内容预览」的场景,应优先复用「外层 `content` + 内层 `data.content` 保持一致」的安全模式,避免在不同字段中混放全文与预览内容。
|
||
- 涉及付费内容时,优先在后端用「权限判断 + 统一内容裁剪」实现安全边界,前端只根据状态选择展示预览还是全文。
|
||
|