# 项目：商品描述审阅与协作工作台

原文: https://yishto.com/learn/project-labs/lab-product-description-review/

作者: [Yishto](https://yishto.com/about/)

设计草稿版本、审阅权限和证据校验，让 AI 建议经过人工批准后导出，处理多租户访问与旧版本冲突。

首次发布: 2026-11-10
内容更新: 2026-11-10

商品描述审阅工作台接收已有描述和佐证材料，让 AI 提出修改建议，再由有权限的审阅者批准具体版本。它适合练习多租户权限、版本冲突和模型结果校验：生成建议、批准版本、导出内容是三个独立动作。

先把范围定小：支持一个商品的一份草稿、一次模型建议和一次人工审阅。例子中的租户、商品和成员都是合成数据，不代表真实客户。完成这条路径后，再增加批量操作、通知和团队管理。

## 把需求写成可验收的行为

作者可以创建草稿、查看原文与建议、提交新版本；审阅者可以批准或退回自己有权处理的版本；导出只读取已批准内容。AI 没有批准或发布权限。收到一次模型响应，不应让草稿自动进入已批准状态。

验收要包含失败行为：另一租户看不到草稿，作者不能冒充审阅者，旧页面不能批准已被修改的内容。模型超时后保留原文，重复提交不会创建第二次逻辑审阅，导出失败不撤销已完成的批准。

这些规则应写进接口和数据约束，不能只靠页面隐藏按钮。浏览器发送的租户、角色和操作者字段都是输入，服务端要从已验证身份和成员关系确定实际权限。

## 为版本与审阅分别建模

核心实体可以分为租户、成员关系、商品、草稿版本、审阅记录和模型运行记录。商品保存稳定标识，草稿版本保存不可变内容及父版本，审阅记录明确针对哪个版本、由谁以何种权限决定。

不要把一个 `approved` 布尔值放在会不断变化的文本上，然后认为批准永远有效。新修改应形成新版本；旧版本的批准仍作为历史保留，但不能被借来导出新文本。审阅结果还需要理由、时间和规则版本等可追溯信息。

模型运行记录关联输入版本、引用材料版本、输出、错误类别和处理状态。审计记录描述谁执行了什么动作，不默认复制整段用户内容。模型调用在数据库事务外执行，避免长时间等待占住事务和锁。

## 用 Supabase 划清访问边界

对 Data API 暴露的表配置表权限与 RLS。它们处理不同层次：角色是否有权访问表，与能访问表里的哪些行，需要分别定义。仅写 `TO authenticated` 不能限制租户或资源范围，还要结合可信的成员关系判断。

读取草稿检查当前用户属于对应租户；写入还检查允许的角色和动作。更新策略应同时考虑旧行可访问性、新行约束以及所需的读取权限，防止改写 `tenant_id` 或所有者把资源移到另一个范围。租户身份不能由用户可编辑的元数据直接授权。

前端使用适当的公开客户端配置，管理用 secret key 或旧式 service-role 凭据只留在服务端。当前文档说明：没有用户 access token 的管理请求可通过 service_role 绕过 RLS；带用户 token 的请求会按该用户策略执行。因此，不要把“客户端初始化用了管理密钥”当成唯一权限判断。特权服务还要显式检查业务授权。

## 把接口和模型结果分开校验

创建草稿接口接收商品标识、内容和请求去重标识，服务端验证长度、身份与商品归属。提交审阅需要目标版本与预期版本；批准接口同时验证审阅角色和版本，失败时返回稳定的权限或冲突类别。

模型建议可以包含替换文本、引用片段、建议理由和待确认事项。结构化输出约束字段和形式，不会证明引用支持结论。程序应校验引用确实来自允许使用的材料、商品标识正确、字段长度受限，再让审阅者比较原文与建议。

拒绝、输出中断、供应商错误和业务校验失败分别保留状态。不要把流式输出的一小段 JSON 当成完整建议，也不要因为重试成功就抹掉此前的失败记录。模型适配器返回业务需要的结果，页面无需理解供应商的所有事件格式。

## 用版本检查阻止旧页面批准

下面只模拟租户边界和乐观版本检查。草稿版本从 1 变为 2 后，仍提交版本 1 的审阅请求返回冲突，另一个租户的请求返回禁止。

```python
draft = {"tenant": "T-1", "version": 1, "text": "original", "approved": False}
def approve(tenant, version):
    if tenant != draft["tenant"]:
        return "forbidden"
    if version != draft["version"]:
        return "conflict"
    draft["approved"] = True
    return "approved"
assert approve("T-2", 1) == "forbidden"
draft["version"] = 2
assert approve("T-1", 1) == "conflict"
assert approve("T-1", 2) == "approved"
print(draft)
```

输出中的版本为 2，`approved` 为 `True`。这段代码没有验证真实身份、审阅角色、数据库并发或 RLS，也没有保存历史版本。它只展示两个检查条件。真实服务要在可协调的事务或条件更新中检查并记录批准，不能把先读取、后写入的内存步骤当成并发保证。

## 按一条完整路径实施

先建立实体和约束，用两名合成用户及两个租户验证读写边界。随后实现创建、读取和修订草稿，确认刷新后内容与版本一致。再实现批准和退回，把权限、版本检查及审计写入同一个业务操作。

接下来用固定建议结果接通界面：原文、修改差异、出处、状态和按钮都要能工作。最后接入模型适配器，加入超时、拒绝和无效引用。固定结果可以帮助隔离界面问题与模型问题，不代表模型质量已经通过。

导出请求是独立操作，绑定已批准版本的不可变内容，保存目标格式、版本和状态；后台任务重试时不能读取最新草稿代替已批准内容。若版本已撤回批准，是否仍允许正在执行的导出需要明确政策。

批量导出要限制条数、并发与总预算。每项有独立状态和结果，部分失败时仍能查看已经完成的内容，并按原版本重试失败项，不能用整批成功掩盖遗漏。

## 练习：证明权限与版本都有效

构造作者、审阅者、另一租户成员三种身份。检查跨租户读取、作者批准、审阅者提交旧版本，以及两次相同请求。每次拒绝后，草稿内容、版本和批准记录都不应出现未经授权的变化。

再模拟模型给出不存在的引用和导出中断。无依据建议应进入待处理或失败分支；已批准版本应能重新导出，不能重新批准一个不同版本来掩盖错误。完成后保留接口请求、预期状态和数据库检查结果，单独记录模型建议的人工评估。

## 资料来源与延伸阅读

- [Row-level security](https://supabase.com/docs/guides/database/postgres/row-level-security)
- [OpenAPI specification](https://spec.openapis.org/oas/latest.html)
- [Structured outputs guide](https://developers.openai.com/api/docs/guides/structured-outputs)
- [PostgreSQL transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [流式输出与结构化结果：两种不同的可靠性](https://yishto.com/learn/models/streaming-and-structured-output/)
- [从真实工作中找到值得做的产品问题](https://yishto.com/learn/product-building/needs-interviews-and-product-discovery/)
