Files
photo-live/docs/plans/2026-09-28-App潜在问题完整分析与解决方案.md
T
浅念 98e073d8c8 docs: App potential issues v2 — full analysis with server-side dedup (confirmed, ready to execute)
v2 补齐 v1 自查发现的 8 个漏洞:三重指纹(filename+bytes+CRC32)、批量弹窗
(单次确认非逐张)、photos 表 dedup 索引、force 权限约束(owner/admin)、
上传并发限制(3)、相册开关预检、上传进度百分比、磁盘空间预检。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 10:24:46 +08:00

8.5 KiB
Raw Blame History

App 端潜在问题完整分析与解决方案(v2 修订版,含服务端查重)

日期:2026-09-28 · 版本:v2(含自查漏洞修复) · 状态:已确认,待实施 范围:apps/mobile 全量 + services/api 涉及上传/查重的接口


一、总览

级别 数量 说明
🔴 P0 高 8 影响核心使用/数据正确性/上传引擎健康
🟡 P1 中 8 特定场景触发/体验缺陷
🟢 P2 低 7 改善项/增强功能

二、🔴 P0 高优先级

P0-1 上传照片无重复判断(用户核心痛点)

现状:同一张照片选 10 次就传 10 次,没有任何提醒或确认。

层 1:App 端本地查重(三重指纹 + 批量弹窗)

指纹:filename + bytes + 前后各4KB CRC32(快速,~1ms,不卡 UI)

  • 三重组合大幅降低假阳性(不同照片同名同大小但内容不同 → CRC 不同 → 正确放行)
  • 改名后的同内容照片查不出(接受局限——服务端层 2 兜底)

批量 UX:

选 20 张 → 入队前一次性扫描全部 → 8 张匹配 → 单次弹窗:

⚠️ 8 张照片可能已上传过(共选择 20 张)
IMG_001.jpg · IMG_005.jpg · IMG_012.jpg …

[跳过重复项,上传 12 张]  [全部上传 20 张]  [取消]

改动文件:

文件 改动
preparer.dart 加 computeFingerprint(path) 方法(读文件头尾 4KB → CRC32)
task_store.dart 加 findByFingerprint(accountId, albumId, fingerprint) 查询方法
enqueue.dart enqueueForUpload 改为返回 EnqueueResult{enqueued, duplicates}
capture_page.dart _pickFromGallery 收到 duplicates → 批量弹窗
album_detail_page.dart 同上(相册详情的从相册选择入口)
app_zh/en.arb 加弹窗文案 key

层 2:服务端查重(跨设备 + 精确哈希 + 索引 + force 安全)

查重逻辑(uploads.service.ts init 事务内):

initUpload 请求 →
  WHERE albumId + uploaderId + bytes 匹配 photos 表(有索引)
  → 有匹配 → 409 { code: 'PHOTO_DUPLICATE', existingPhotoId, uploadedAt }
  → 无匹配 或 force=true 且 isAdmin → 正常建会话

安全约束:force: true 仅 owner/admin 生效——协作者无法绕过。

数据库迁移:

CREATE INDEX idx_photos_dedup
  ON photos (albumId, uploaderId, bytes)
  WHERE visibility != 'deleted';

契约改动(openapi.yaml):

  • UploadInitRequest 加可选 contentHash(string, SHA-256 hex,精确查重备用)
  • UploadInitRequest 加可选 force(boolean, 默认 false)
  • 409 响应体加 code: PHOTO_DUPLICATE + existingPhotoId

改动文件:

文件 改动
openapi.yaml 上述契约字段
uploads.service.ts init 事务内查重 + force 权限检查
dto.ts UploadInitRequestDto 加 contentHash/force
prisma migration idx_photos_dedup 索引
api_client.dart(App) initUpload 加参数;409 PHOTO_DUPLICATE 处理
upload_engine.dart(App) 409 不标记失败 → 转交 UI 确认

P0-2 照片查看器无缩放

在 PageView 每页包 InteractiveViewer,缩放时禁用 PageView 滑动:

InteractiveViewer(
  maxScale: 5.0,
  onInteractionUpdate: (details) {
    _pageController.physics = details.scale != 1.0
      ? const NeverScrollableScrollPhysics()
      : const PageScrollPhysics();
  },
  child: PhotoThumbnail(...),
)

工时:~30min


P0-3 相机后台未释放

CapturePage 加 WidgetsBindingObserver:

  • paused → dispose 相机
  • resumed → 重新 initialize

工时:~30min


P0-4 无离线感知

  • connectivity_plus 依赖
  • NetworkStatusProvider(Stream)
  • 离线 → 上传引擎暂停 + UI 黄条提示
  • 恢复 → 自动重试

工时:~3h


P0-5 照片 EXIF 方向未处理

App 端上传前读取 EXIF orientation → 非 1 时本地旋转修正。

工时:~2h


P0-6 图片无内存管理

PhotoGrid 的 Image 加 cacheWidth(按屏幕像素 / 列数)。

工时:~1h


P0-7 上传并发无限制(🆕 v2 新增)

现状:无 maxConcurrent 控制——50 张同时 PUT。

方案:上传引擎加并发信号量(上限 3),排队执行。

改动:upload_engine.dart 加 _Semaphore 类 + _drainQueue 改为限流。

工时:~1h


P0-8 入队前不检查相册上传开关(🆕 v2 新增)

现状:enqueueForUpload 不查 album.uploadEnabled——关闭后仍复制+init→403。

方案:入队前检查 → false 时提示「该相册已关闭上传」+ 不入队。

改动:enqueue.dart + capture_page / album_detail_page。

工时:~30min


三、🟡 P1 中优先级

# 问题 方案 工时
P1-1 海报渲染 OOM pixelRatio 3→2 + try/catch 降级 15min
P1-2 相机分辨率过高 ResolutionPreset.max → high 5min
P1-3 闪光灯状态丢失 flip 后恢复 flash state 15min
P1-4 无全局崩溃捕获 FlutterError.onError + runZonedGuarded + 本地日志 1h
P1-5 上传引擎无生命周期 WidgetsBindingObserver pause/resume 1h
P1-6 分享缩略图 PNG 超 32KB 改 JPEG 编码或限制尺寸更小 30min
P1-7 上传无进度百分比(🆕) 任务行加 LinearProgressIndicator(onReceiveProgress 已有) 30min
P1-8 磁盘空间无预检(🆕) 复制前 path_provider 检查可用空间 30min

四、🟢 P2 低优先级 / 增强

# 问题 方案
P2-1 无深度链接 app_links + router 集成
P2-2 无推送通知 FCM / 极光
P2-3 表单草稿不持久 shared_preferences 定时保存
P2-4 无图片压缩 resize 最大 2048px + JPEG 85%
P2-5 无批量管理 照片网格多选批量隐藏/删除
P2-6 多语言无 RTL 支持阿拉伯语等
P2-7 App 体积 64MB 偏大(🆕) --split-per-abi(arm64-v8a 约 25MB)或 --obfuscate

五、上传引擎优化(🆕 v2 新增)

# 优化 说明
1 哈希计算放 Isolate 如做 SHA-256,必须 Isolate.run() 否则卡 UI
2 上传重试退避策略 网络错→立即重试;5xx→指数退避(1s/2s/4s/8s);403→不重试
3 源文件清理提前 上传成功+服务端确认后 24h 即清理(非当前定期批量)
4 上传优先级 刚拍的排前面(用户在等),补传的排后面
5 取消原子性 先标记 cancelled 再异步清理,UI 即时反馈

六、修订后实施排期

批次 内容 工时 备注
第 1 批 P0-2 缩放 + P0-3 相机释放 + P0-6 内存管理 ~2h 核心体验
第 2 批 P0-1 层1(本地查重 + 批量弹窗) ~3h 用户痛点
第 2.5 批 P0-7 并发限制 + P0-8 开关预检 + P1-8 磁盘预检 ~2h 上传引擎健康
第 3 批 P0-4 离线感知 + P0-5 EXIF 方向 ~5h 稳定性
第 4 批 P0-1 层2(服务端查重 + 索引 + force 安全) ~6h 跨设备查重
第 5 批 P1 全部 + 上传进度 + 重试退避 ~5h 品质
第 6 批 P2 + App 瘦身 按需 增强

总工时:~23h(P0 全部 + P1 全部);第 1-2.5 批可一天内完成。


七、测试要点

查重测试

  • 同一张照片选两次 → 第二次弹窗
  • 不同照片但同名 → 不弹窗(bytes/CRC 不同)
  • 批量选 20 张含 8 张重复 → 单次弹窗(不是 8 次)
  • 确认「跳过重复项」→ 只上传 12 张
  • 确认「全部上传」→ 上传 20 张
  • 确认「取消」→ 不入队
  • 跨设备:手机 A 传了 → 手机 B 选同一张 → 服务端 409 → 弹窗
  • force: true owner 可绕过 → 协作者不可绕过

上传引擎测试

  • 并发上限 3:入队 10 张 → 任何时刻最多 3 个 uploading
  • 相册关闭上传 → 提示 + 不入队
  • 磁盘不足 → 友好提示
  • 离线 → 任务暂停 → 恢复网络 → 自动重传
  • 5xx 错误 → 指数退避(非立即重试)

查看器测试

  • 双指放大缩小 → 松手回弹/保持
  • 放大状态下滑动 → 照片内平移(不切页)
  • 恢复 1x 后 → 左右滑动切页

v2 修订版——已补齐自查发现的 8 个漏洞(指纹精度、批量弹窗、索引、force 安全、并发、预检、进度、磁盘)。确认后按排期实施。