Gridea Pro 版本
main 分支 HEAD (b84db26)
操作系统
macOS (Apple Silicon)
系统版本
Darwin 25.4.0(问题与平台无关)
优先级
P1(数据生命周期 / 轻度隐私问题)
Bug 描述
backend/internal/service/cdn_upload_service.go:299-388 的 UploadMediaForDeploy 只做"上传本地媒体到远端",从不删除"本地已不存在、远端仍在"的文件。
组合场景:
- 用户在文章里插了一张敏感图
post-images/leaked.png,部署后走了 CDN(例如 GitHub + jsdelivr)。
- 发现问题后删掉文章、删掉本地
post-images/leaked.png。
- 再次部署。
- 本地 HTML 产物里已经不再引用
leaked.png,但:
- 远端 GitHub 仓库
post-images/leaked.png 依旧存在;
- jsdelivr CDN
https://cdn.jsdelivr.net/gh/user/repo@branch/post-images/leaked.png 依旧可公开访问;
- jsdelivr 的 CDN 缓存会保留这个 URL 相当长的时间,即便 GitHub 那边删了文件也不会立刻失效。
用户的心智模型是"删除 = 下线",但实际上:
- 最好的情况——永远下不掉,需要用户手工登 GitHub 删文件 + 申请 jsdelivr purge。
- 最坏的情况——用户根本不知道文件还在外网能被访问。
对于"分享过链接 → 想撤回"或"上传错图 → 立刻下线"的场景,这是一个真实可复现的信任问题。
复现步骤
- 配置 GitHub + jsdelivr CDN。
- 写一篇文章包含
post-images/test-leak.png,发布。
- 复制 jsdelivr URL 在浏览器打开,确认可访问。
- 删除本地
post-images/test-leak.png(在应用或文件系统层)。
- 再次发布。
- 刷新刚才的 jsdelivr URL:图片依然显示。
- 打开 GitHub repo 查看对应文件:还在那儿,带着 Gridea Pro 最近一次提交时间。
期望行为
- CDN 上传任务结束时,识别"远端有、本地没"的孤儿文件。
- 默认策略:移动到
_trash/<date>/ 前缀下(软删除,保留 30 天),让用户后悔时还能恢复。
- 可选策略(设置开关):硬删除,并(对支持的 CDN)尝试触发 purge。
- UI 在部署摘要里显示"清理了 X 个不再使用的图片"。
实际行为
本地删图后远端永远留存,用户无感知,且可能被公开访问。
截图 / 日志
无。
补充信息 — 最佳解决方案
基于本地 CDN manifest 做 diff 清理
前置:先落 issue #45 的 local cdn-manifest.json。
流程:
- 扫描本地
post-images/images/media/,生成 localSet = { path: sha }。
- 读取上一次成功部署的
cdn-manifest.json,生成 knownSet。
orphans = knownSet - localSet(在上一次部署时存在、本次已不存在的文件)。
- 根据设置:
softDelete(默认):将 orphan 从原路径 move 到 _trash/<YYYY-MM-DD>/<path>(GitHub 走 Git Data API 一次性 tree 变更)。
hardDelete:直接删除。
- 硬删除 + 使用公共 CDN(如 jsdelivr)时:
- 显示警告:"CDN 缓存可能仍可访问该文件长达 12 小时",并给出 purge 按钮(跳转 jsdelivr purge 页面)。
- 每次部署自动清理
_trash/ 中超过 30 天的内容。
重要安全提示
- 此功能首次启用时给用户一个明确的 onboarding:"启用后本地删除的文件会在远端也被清理,确认开启?"
- 在"测试上传"按钮旁增加"测试清理"按钮,让用户在真正发布前预览会动的 orphan 列表。
额外优化
- 对其它存储类型(自定义域名 CDN 走 COS/OSS/S3 等),用相同的 manifest diff 逻辑,动作调用对应 API。
- 考虑提供"孤儿文件"列表 view,让用户可手工决定哪些留哪些删。
影响文件:
检查清单
Gridea Pro 版本
main 分支 HEAD (b84db26)
操作系统
macOS (Apple Silicon)
系统版本
Darwin 25.4.0(问题与平台无关)
优先级
P1(数据生命周期 / 轻度隐私问题)
Bug 描述
backend/internal/service/cdn_upload_service.go:299-388的UploadMediaForDeploy只做"上传本地媒体到远端",从不删除"本地已不存在、远端仍在"的文件。组合场景:
post-images/leaked.png,部署后走了 CDN(例如 GitHub + jsdelivr)。post-images/leaked.png。leaked.png,但:post-images/leaked.png依旧存在;https://cdn.jsdelivr.net/gh/user/repo@branch/post-images/leaked.png依旧可公开访问;用户的心智模型是"删除 = 下线",但实际上:
对于"分享过链接 → 想撤回"或"上传错图 → 立刻下线"的场景,这是一个真实可复现的信任问题。
复现步骤
post-images/test-leak.png,发布。post-images/test-leak.png(在应用或文件系统层)。期望行为
_trash/<date>/前缀下(软删除,保留 30 天),让用户后悔时还能恢复。实际行为
本地删图后远端永远留存,用户无感知,且可能被公开访问。
截图 / 日志
无。
补充信息 — 最佳解决方案
基于本地 CDN manifest 做 diff 清理
前置:先落 issue #45 的 local
cdn-manifest.json。流程:
post-images/images/media/,生成localSet = { path: sha }。cdn-manifest.json,生成knownSet。orphans = knownSet - localSet(在上一次部署时存在、本次已不存在的文件)。softDelete(默认):将 orphan 从原路径 move 到_trash/<YYYY-MM-DD>/<path>(GitHub 走 Git Data API 一次性 tree 变更)。hardDelete:直接删除。_trash/中超过 30 天的内容。重要安全提示
额外优化
影响文件:
backend/internal/service/cdn_upload_service.gobackend/internal/service/cdn_manifest.go(issue [Bug] CDN 上传每文件先 GET 后 PUT,API 配额爆炸 & 性能差,缺少本地 manifest 缓存 #45 已规划)backend/internal/domain/cdn_setting.go(新增 softDelete/hardDelete 开关)检查清单