# 文件编辑锁契约 v1 ## 公共接口 ```text POST /api/v1/files/{storage_object_id}/lock POST /api/v1/file-locks/{edit_session_id}/heartbeat DELETE /api/v1/file-locks/{edit_session_id} ``` 三个接口均要求用户身份和 Workspace 身份。当前开发基线使用 `X-User-ID`、`X-Workspace-ID`,后续接入 JWT 时保持路径和业务 DTO 不变。 加锁成功返回一次性原始 `lock_token`。心跳和释放请求体均为: ```json {"lock_token": "raw-token-returned-by-acquire"} ``` 原始 token 只由客户端持有,禁止写入 MySQL 和日志。 ## Redis 数据 ```text Key: lock:file:{workspace_id}:{storage_object_id} TTL: 45000 ms ``` Value: ```json { "edit_session_id": "01J...", "user_id": "01J...", "display_name": "张三", "token_hash": "sha256-hex", "acquired_at": "UTC timestamp" } ``` 加锁必须使用: ```text SET key value NX PX 45000 ``` 心跳和释放必须执行 Lua 原子操作,并同时比较 `edit_session_id + token_hash`: ```text heartbeat: compare owner -> PEXPIRE 45000 release: compare owner -> DEL ``` 禁止使用 `GET` 后单独 `DEL`,也禁止在 MySQL 文件表增加 `is_locked`。 ## 状态与错误 - Redis 是实时锁唯一权威。 - `edit_sessions` 记录 `active/closed/expired`,仅用于审计。 - 冲突返回 HTTP 409、错误码 `FILE_LOCK_CONFLICT`,并包含当前编辑者及租约到期时间。 - 错误 token 返回 HTTP 403,且不得续期或释放现有锁。 - 浏览器建议每 15 秒心跳;关闭、断线或心跳超时后由主动释放或 TTL 释放。