修复精选请求并发与缓存响应头
累计追加 Authorization 到 Vary 并保留已有响应字段 将精选请求 generation 绑定到已提交 effect 并在 cleanup 失效 补充中间件与 Suspense 放弃渲染回归测试 同步精选点赞决策与排障文档
This commit is contained in:
@@ -6900,6 +6900,6 @@
|
||||
## 2026-08-10 陶泥儿精选点赞状态使用可选鉴权 viewer 投影
|
||||
|
||||
- 背景:`editor_showcase_asset_like` 已持久化用户点赞,`editor_showcase_asset.like_count` 也会事务更新,但公开精选 GET 没有返回当前浏览者状态,前端每次挂载都从空本地集合开始,导致刷新后已点赞图标消失。
|
||||
- 决策:保留同一个公开 `GET /api/editor/showcase/resources` 并接受可选 Bearer。匿名每项返回 `viewerLiked=false`;登录态只从已验证 claims 派生 viewer user ID,通过现有 runtime service identity 调用专用 SpacetimeDB viewer procedure,在同一事务快照中返回 `viewerLiked + likeCount`。无效 Bearer、viewer procedure 或私有读取失败显式失败,不降级匿名。GET 与点赞 POST 固定 `Cache-Control: private, no-store`、`Vary: Authorization`。
|
||||
- 写入与前端:现有 like 去重表继续是用户状态真相,不新增字段、迁移或历史修复。写路径只保留返回 `viewerLiked + likeCount` 的 viewer set procedure,并要求 runtime service identity;删除只返回素材聚合快照的旧 toggle procedure,输入 `user_id` 只表示经 BFF 验证后代入的业务主体,不能充当调用授权。前端不做乐观图标 / 计数更新,成功只采纳服务端权威回包,失败保留原状态并显示可访问短错误;登录、退出、账号切换与鉴权恢复重载列表,并按 viewer scope / request generation 丢弃旧首屏、分页和写回响应。不做跨 Tab 实时同步。
|
||||
- 决策:保留同一个公开 `GET /api/editor/showcase/resources` 并接受可选 Bearer。匿名每项返回 `viewerLiked=false`;登录态只从已验证 claims 派生 viewer user ID,通过现有 runtime service identity 调用专用 SpacetimeDB viewer procedure,在同一事务快照中返回 `viewerLiked + likeCount`。无效 Bearer、viewer procedure 或私有读取失败显式失败,不降级匿名。GET 与点赞 POST 固定 `Cache-Control: private, no-store`,并在保留响应已有 `Vary` 字段的基础上追加 `Authorization`。
|
||||
- 写入与前端:现有 like 去重表继续是用户状态真相,不新增字段、迁移或历史修复。写路径只保留返回 `viewerLiked + likeCount` 的 viewer set procedure,并要求 runtime service identity;删除只返回素材聚合快照的旧 toggle procedure,输入 `user_id` 只表示经 BFF 验证后代入的业务主体,不能充当调用授权。前端不做乐观图标 / 计数更新,成功只采纳服务端权威回包,失败保留原状态并显示可访问短错误;登录、退出、账号切换与鉴权恢复重载列表,并按 viewer scope / request generation 丢弃旧首屏、分页和写回响应。request generation 只在已提交的精选加载 effect 中激活,并在 cleanup 中失效,禁止 render 阶段写 ref 让被放弃的并发渲染污染当前请求。不做跨 Tab 实时同步。
|
||||
- 影响范围:SpacetimeDB typed procedure 与 bindings、`spacetime-client` mapper / facade、api-server 可选鉴权与 public DTO、`editorProjectClient`、`CreationLandingView` 和对应回归测试;不修改持久 schema、migration 或 External v1 契约。
|
||||
|
||||
@@ -4475,5 +4475,5 @@
|
||||
|
||||
- 现象:陶泥儿精选点赞写入成功、总点赞数也正确,但刷新或重新挂载后图标恢复成未点赞;前端再次点击会发出错误意图或让计数体验混乱。
|
||||
- 原因:`editor_showcase_asset.like_count` 只表达全局聚合,公开列表未携带 `editor_showcase_asset_like(showcase_id:user_id)` 的 viewer 状态;前端用生命周期内的空 `Set` 充当真相,刷新必然丢失。仅靠 `likeCount > 0` 无法判断其中是否包含当前用户。
|
||||
- 处理:公开列表使用可选鉴权 viewer 投影;登录态从 Bearer claims 派生 user ID,并在公开列表事务内按确定性 like 主键返回 `viewerLiked`,匿名固定 false。个性化响应禁止共享缓存或错误降级。写入采用服务端确认式更新,账号 / 鉴权 scope 变化后重载并丢弃旧请求回包;输入 user ID 不能代替 runtime service identity 鉴权。
|
||||
- 验证:覆盖刷新 / remount 保持已点赞、pending 期间不改图标计数、失败保留旧状态并播报错误、分页保留 viewer state、鉴权恢复不发匿名请求、登录 / 退出 / 换号与旧首屏 / 分页 / POST 回包竞态,以及无效 Bearer 返回 `401 + private,no-store + Vary: Authorization`。
|
||||
- 处理:公开列表使用可选鉴权 viewer 投影;登录态从 Bearer claims 派生 user ID,并在公开列表事务内按确定性 like 主键返回 `viewerLiked`,匿名固定 false。个性化响应禁止共享缓存或错误降级,追加 `Vary: Authorization` 时不得覆盖 handler 或内层中间件已有字段。写入采用服务端确认式更新,账号 / 鉴权 scope 变化后重载并丢弃旧请求回包;request generation 的激活与失效必须跟随已提交 effect,不能在 render 阶段修改 ref;输入 user ID 不能代替 runtime service identity 鉴权。
|
||||
- 验证:覆盖刷新 / remount 保持已点赞、pending 期间不改图标计数、失败保留旧状态并播报错误、分页保留 viewer state、鉴权恢复不发匿名请求、登录 / 退出 / 换号与旧首屏 / 分页 / POST 回包竞态、被 Suspense 放弃的 viewer 渲染不影响当前已提交请求,以及无效 Bearer 返回 `401 + private,no-store + Vary: Authorization`;中间件测试另需证明已有 `Vary` 字段被保留。
|
||||
|
||||
Reference in New Issue
Block a user