fix(data): 清理旧数据并保护服务端写入
This commit is contained in:
@@ -117,6 +117,7 @@ DevTask 没有"已完成"状态,"已提测"就是终态——开发交付完
|
||||
**V2.1(当前):** 通用服务端文档表 `app_data`
|
||||
- 后端:`apps/server/src/modules/data/` 提供 `GET/PUT /api/v1/data/:key`
|
||||
- 数据库:Prisma `AppData` 模型,表名 `app_data`,`key` 为主键,`value` 为 JSONB
|
||||
- 一致性:`GET` 返回 `updatedAt` 派生的 `version`;前端保存时带上最近读取的 `version`,后端用 `key + updatedAt` 原子更新,版本不匹配返回 `409 APP_DATA_CONFLICT`
|
||||
- 前端:各 Zustand store 保持现有数据形状,通过 `apps/web/lib/server-data.ts` 读写服务端
|
||||
- 覆盖范围:产品/项目/版本树、需求池、调研/产品方案/UI 计划、开发任务、测试用例、Bug、成员/角色/部门、任务类型、任务工时日志、加班记录
|
||||
- 浏览器仅保留登录会话(`ftb_auth_session` / `ftb_auth_persist`),不再作为业务数据主存储
|
||||
|
||||
@@ -476,3 +476,17 @@
|
||||
- HTTPS 先交给云负载均衡、CDN、宿主机证书工具或外层 Nginx 终止;Compose 内置 Nginx 保持 HTTP 反代基线。
|
||||
|
||||
**理由**:Docker Compose 足够覆盖当前单机云服务器和本地服务器形态,部署成本低、可读性强,也符合现阶段 2 核 4G 云主机目标。同域反代能减少 CORS 和公网端口暴露面。本地服务器默认 8080,避免占用 80 端口或要求管理员权限;云服务器继续使用 80 作为外层入口。HTTPS 证书自动续期和域名接入在不同云环境差异较大,先作为外层能力处理,避免把生产部署模板绑死在某一种证书方案上。
|
||||
|
||||
## 39. AppData 文档写入采用乐观锁,先阻止静默覆盖
|
||||
|
||||
**问题**:V2.1 阶段业务数据仍按模块存成 `app_data.value` 整份 JSON 文档。多人同时打开同一模块后,如果 A 和 B 都基于旧副本编辑,原来的无条件 `upsert` 会让后保存的人覆盖先保存的人,尤其是任务、Bug、成员等高频写入数据。
|
||||
|
||||
**决策**:
|
||||
- `GET /api/v1/data/:key` 返回 `version`,由 `AppData.updatedAt.toISOString()` 派生;空文档返回 `version: null`。
|
||||
- 前端 `server-data.ts` 在读取和保存成功后缓存每个 key 的最新 `version`。
|
||||
- 前端保存已读取过的 key 时提交 `{ value, version }`;尚未读取过的兼容路径仍可提交 `{ value }` 走旧式 upsert。
|
||||
- 后端收到字符串 `version` 时使用 `updateMany({ where: { key, updatedAt }, data: { value } })` 原子比较并更新;`count !== 1` 返回 `409 APP_DATA_CONFLICT`。
|
||||
- 后端收到 `version: null` 时只允许创建不存在的行;如果其他客户端已经创建,返回同样的 `409 APP_DATA_CONFLICT`。
|
||||
- 冲突响应返回 `currentVersion` 和 `currentValue`,但当前前端不自动合并、不自动重试,避免把旧本地副本用新版本号再次覆盖服务端数据。
|
||||
|
||||
**理由**:这是 AppData 阶段成本最低、收益最高的一致性补强。它不能提供字段级协同编辑,但能阻止最危险的“静默最后写入覆盖”。后续拆成关系表和领域 API 后,再在具体实体上做更细粒度的事务、唯一约束、审计日志和冲突合并 UI。
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
### 已完成(按时间倒序)
|
||||
|
||||
**2026-07-02**
|
||||
- `app_data` 读写增加乐观锁版本:`GET` 返回 `version`,前端保存携带最近版本,后端用 `key + updatedAt` 原子更新
|
||||
- stale version / create race 返回 `409 APP_DATA_CONFLICT`,阻止多人同时编辑时的静默覆盖
|
||||
- 新增后端 AppData 并发写入回归测试和前端 `server-data` 版本缓存测试
|
||||
|
||||
**2026-07-01**
|
||||
- 补齐云服务器生产部署基线:`Dockerfile.web`、`Dockerfile.server`、`docker-compose.prod.yml`、Nginx 反代模板和 `.env.production.example`
|
||||
- 补齐本地服务器/局域网部署基线:`docker-compose.local.yml`、`.env.local-server.example`、`deploy:local:*` 脚本
|
||||
|
||||
@@ -135,6 +135,13 @@
|
||||
- 涉及 UI 改动:`curl http://localhost:3000/<path>` 检查 200
|
||||
- 不会自动跑 dev server,假定它已经运行
|
||||
|
||||
## AppData 并发写入流程
|
||||
|
||||
- 前端通过 `loadServerData(key)` 读取业务文档时,必须缓存响应里的 `version`。
|
||||
- 前端通过 `saveServerData(key, value)` 保存已读取过的 key 时,必须把最近一次成功读取/保存得到的 `version` 一起提交。
|
||||
- 后端只在 `key + updatedAt(version)` 匹配时更新;如果其他用户已经先保存,返回 `409 APP_DATA_CONFLICT`,响应包含当前服务端 `currentVersion` 和 `currentValue`。
|
||||
- 收到 `ServerDataConflictError` 时,不要自动重试覆盖。当前处理策略是阻止静默覆盖,后续 UI 冲突合并能力再单独补。
|
||||
|
||||
## 与我相关(Workspace)数据流
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user