字段审查结论
为避免后端过度设计,这一次只保留前端保存和下次恢复所必需的数据。
保留字段
| 字段 | 保留原因 |
|---|---|
reviewId | 定位审核记录,沿用现有ReviewArticle.id |
markupVersion/expectedMarkupVersion | 解决多人同时编辑时的覆盖问题 |
baseContentHash | 判断当前标记是否仍基于同一份原文 |
markupJson | 云端唯一需要持久化的标记快照 |
annotations | 备注和修改列表,下次进入页面需要还原 |
blockPath/from/to/quote | 用于把标记定位回原文 |
updatedBy/updatedAt | 仅用于展示最近保存信息和冲突提示 |
第一版去掉的字段
| 字段 | 去掉原因 |
|---|---|
reviewVersion/expectedReviewVersion | 审核状态变化可通过后端校验monitorStatus = 0解决;标记并发由markupVersion解决 |
createdBy/createdAt/updatedBy/updatedAt(annotation 内) | 本期全量覆盖保存,不做单条批注审计;每条标记的独立历史不准确也不必要 |
prefix/suffix/blockTextHash | 原文冻结且只允许同块选区,blockPath/from/to/quote足够;这些字段可作为后续增强 |
finalContentHtml/finalContentMarkdown(草稿保存) | 终稿可由前端实时计算;草稿阶段只需要保存markupJson |
contentFormat | 当前内容格式由既有content字段约定;若后端后续支持 HTML/Markdown 混合,再扩展 |
editable | 前端已有权限和状态判断,后端保存时仍必须做权限/状态校验;不是恢复标记所必需 |
数据结构
ReviewMarkupJson
interface ReviewMarkupJson { schema: 'review-markup/v1'; baseContentHash: string; annotations: ReviewMarkupAnnotation[]; }字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
schema | string | 是 | 标记数据协议版本,固定review-markup/v1 |
baseContentHash | string | 是 | 原文内容 hash,用于判断原文是否变化 |
annotations | array | 是 | 当前有效的备注和修改列表 |
markupVersion不放在markupJson内部,由接口顶层字段维护,避免前后端版本不一致。
ReviewMarkupAnnotation
interface ReviewMarkupAnnotation { id: string; type: 'comment' | 'replace'; anchor: ReviewTextAnchor; comment?: string; replacement?: string; }字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 前端生成的全局唯一标记 id,例如ann_... |
type | comment/replace | 是 | comment=备注,replace=替换建议 |
anchor | object | 是 | 标记在原文中的位置 |
comment | string | 条件必填 | type=comment时必填 |
replacement | string | 条件必填 | type=replace时必填 |
ReviewTextAnchor
interface ReviewTextAnchor { blockPath: string; from: number; to: number; quote: string; }字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
blockPath | string | 是 | 文本块位置,例如h1[0]、p[2]、li[4] |
from | number | 是 | 在该文本块纯文本中的起始 offset,包含 |
to | number | 是 | 在该文本块纯文本中的结束 offset,不包含 |
quote | string | 是 | 被选中的原文片段 |
示例
{ "schema": "review-markup/v1", "baseContentHash": "sha256:6adf9c...", "annotations": [ { "id": "ann_001", "type": "comment", "anchor": { "blockPath": "p[2]", "from": 10, "to": 18, "quote": "智能化高" }, "comment": "这里表述偏绝对,建议补充数据来源" }, { "id": "ann_002", "type": "replace", "anchor": { "blockPath": "p[4]", "from": 5, "to": 11, "quote": "行业领先" }, "replacement": "表现较强" } ] }接口设计
统一响应结构
沿用现有后端风格:
interface GeoApiResponse<T> { success: boolean; code: number; message: string; data: T; }如果后端支持 HTTP 409,冲突场景建议直接返回 HTTP 409。如果后端统一返回 HTTP 200,则必须使用明确业务 code,例如40901、40902。
接口 1:查询文章标记数据
GET /geo/api/review/getReviewMarkupByReviewId?reviewId=11入参:
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
reviewId | query | number | 是 | 审核记录 id,即当前ReviewArticle.id |
出参:
interface GetReviewMarkupResponse { reviewId: number; baseContentHash: string; markupVersion: number; markupJson: ReviewMarkupJson | null; updatedBy?: string; updatedAt?: string; }字段说明:
reviewId:审核记录 id,等于当前列表行ReviewArticle.id。baseContentHash:后端根据当前原文生成,前端保存和提交时必须原样传回。markupVersion:标记数据版本,标记保存成功时递增。markupJson为空时,前端按无标记初始化。updatedBy/updatedAt:最近一次成功保存标记的人和时间,仅用于展示或冲突提示。
说明:
- 文章标题、正文、状态继续复用现有
ReviewArticle数据;如果后端希望详情页完全不依赖列表数据,可另行把title/content/monitorStatus合并进该接口,但第一版不是必须。 - 前端权限仍按现有
content_review_check和monitorStatus判断;后端保存时必须再次校验状态和权限。
示例:
{ "success": true, "code": 200, "message": "success", "data": { "reviewId": 11, "baseContentHash": "sha256:6adf9c...", "markupVersion": 2, "markupJson": { "schema": "review-markup/v1", "baseContentHash": "sha256:6adf9c...", "annotations": [] }, "updatedBy": "zhangsan", "updatedAt": "2026-07-03 15:20:00" } }接口 2:保存标记草稿
PUT /geo/api/review/saveReviewMarkup调用时机:
- 用户在备注弹窗点击保存。
- 用户在修改弹窗点击保存。
- 用户删除或编辑已有备注/修改后点击确认。
保存语义:
- 前端每次提交当前文章的完整
markupJson,不是只提交本次新增或修改的单条 annotation。 - 后端只在
expectedMarkupVersion、baseContentHash都校验通过,且当前审核状态仍允许编辑时覆盖保存。 - 覆盖对象是该文章当前草稿的整份
markup_json。 - 校验失败时必须返回冲突,不能覆盖云端已有草稿。
入参:
interface SaveReviewMarkupParams { reviewId: number; expectedMarkupVersion: number; baseContentHash: string; markupJson: ReviewMarkupJson; }字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
reviewId | number | 是 | 审核记录 id |
expectedMarkupVersion | number | 是 | 前端当前基于的标记版本 |
baseContentHash | string | 是 | 前端当前基于的原文 hash |
markupJson | object | 是 | 完整标记 JSON |
出参:
interface SaveReviewMarkupResponse { reviewId: number; markupVersion: number; updatedBy: string; updatedAt: string; }后端校验:
reviewId存在。- 当前审核状态仍允许编辑,例如
monitorStatus = 0。 expectedMarkupVersion等于后端当前markupVersion。baseContentHash等于后端当前原文 hash。markupJson.baseContentHash等于后端当前原文 hash。- 每个
anchor.quote能在对应blockPath/from/to上匹配到原文。 - 第一版建议不允许有效标记区间互相重叠,尤其是
replace与replace不能重叠。
成功保存后:
- 后端用本次请求的完整
markupJson整体覆盖保存该文章的markup_json。 markupVersion + 1。- 返回最新版本。
接口 3:提交审核结果
沿用现有接口,扩展标记快照和并发校验字段:
PUT /geo/api/review/updateArticleReview入参:
interface UpdateArticleReviewWithMarkupParams { id: number; monitorStatus: 2 | 3; rejectReason?: string; expectedMarkupVersion: number; baseContentHash: string; markupJson?: ReviewMarkupJson; }字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | number | 是 | 审核记录 id |
monitorStatus | 2/3 | 是 | 2=未通过,3=待投放 |
rejectReason | string | 条件必填 | 未通过时必填 |
expectedMarkupVersion | number | 是 | 前端当前基于的标记版本 |
baseContentHash | string | 是 | 前端当前基于的原文 hash |
markupJson | object | 否 | 当前标记快照。建议提交时携带,便于后端事务内保存最终标记 |
后端事务要求:
- 锁定审核记录和标记记录。
- 校验当前
monitorStatus仍允许提交。 - 校验
expectedMarkupVersion等于当前markupVersion。 - 校验
baseContentHash等于当前原文 hash。 - 如果请求携带
markupJson,校验所有 anchor 合法后保存为最终标记快照,并递增markupVersion。 - 更新审核状态为
monitorStatus。 - 写入审核历史。
提交接口必须把"保存最终标记 + 更新审核状态 + 写审核历史"作为原子操作处理。