docs(plan): 选图拦截 v2 设计——硬封锁+仅本机判定

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-29 14:14:06 +08:00
parent a90cb9953e
commit 29204bc08a
@@ -0,0 +1,67 @@
# 选图点选拦截 v2:硬封锁 + 仅本机判定(去服务器半场、去强制放行)
日期:2026-09-29 | 状态:用户拍板(方案 A:接受"服务端已删照片无法重传"限制)
## 背景与问题
v1(提交 7e5c550+0673f39)上线后两个实际问题:
1. **选图不灵敏**:`selectPredicate` 每次点选 await 判定,判定含服务器单项预检
(时间帽 2s)。弱网下每次点新照片勾选延迟 0.5~2s,用户以为没点上就反复点
(wechat_assets_picker `selectAsset` 源码:谓词返回前 `provider.selectAsset`
不执行——勾选必然等判定)。
2. **拦截形同虚设**:v1 设计了"二次点击强制选择"活口(escape hatch),配合上述
反复点击习惯,重复照片多点几次必然被强制选中——用户明确要求**重复照片不能
被选择**。
## 方案(v2)
### 1. 点选判定只查本机(治卡顿)
- `_judge` 仅保留本机 TaskStore 半场(同账号+相册+completed,filename+bytes),
删除服务器单项预检及其 2s 时间帽——点选延迟从「最多 2s 网络」降为「毫秒级
本地查询」。
- 服务器查重不消失:**入队时刻统一弹窗**(checkDuplicatesUnified,一次批量
≤500 张)照旧——跨设备/重装场景的重复仍在点上传时拦,服务端 409 兜底不变。
- iOS 注意:`title` 为空(需 FilterOption.needTitle)时本机半场无从匹配 →
放行;iOS 点选拦截实际不生效,跨设备判定依赖入队弹窗(与 v1 服务器半场
行为的差异登记于此)。
### 2. 硬封锁(去强制放行)
- 删除 `_forced` 二次点击放行集:本机命中的重复照片**每次点击都拒选**,
判定缓存命中即秒拦,提示文案改为「这张已上传到该相册,无法重复选择」
(en: This photo has already been uploaded to this album),不再引导再点。
- SnackBar clearSnackBars 防堆积行为保留。
### 3. 判定缓存升级为 in-flight Future 共享
- `_verdicts` 从 `Map<String, bool>` 改为 `Map<String, Future<bool>>`,
`putIfAbsent` 并发去重——同一照片双击/连点只发起一次判定(清偿 9-29 评审
登记 Minor 遗留)。`_judge` 内部全捕获不抛错,缓存 Future 无失败态。
## 已接受的限制(用户拍板 A)
- 照片在后台被删除后想重传同一文件:本机历史仍记「已传」→ 永远拦死。
解决办法:清 App 数据或换设备(罕见场景,先接受;后续可加设置开关/服务端
存活性免疫,未排期)。
## 构造签名变化
`PickerDuplicateGate` 删除 `client` 与 `serverCheckTimeout` 入参(点选路径无
网络依赖);`multiImagePickerProvider` 相应不再注入 ApiClient。
## 测试(enqueue_test 拦截器组重写)
- 本机命中 → 拒选;**连续多次点击均拒选**(无强制路径);新提示文案;
SnackBar 不堆积(第二个重复资产仅一条)。
- 不同 bytes / filename / 相册 / 账号 → 放行。
- in-flight 共享:并发两次 decideFact,fileSize 闭包仅被调用一次。
- title=null(iOS)→ 放行;fileSize 失败 → 放行;取消选择(selected=true)
恒放行且零查询(fileSize 计数为 0)。
- v1 服务器相关用例(②④⑥)随服务器半场移除而删除。
## 影响面
`enqueue.dart`(gate + provider 注入)+ `enqueue_test.dart` + l10n 双语一处
文案。不涉及服务端、契约、其它页面。