附件内容去重(hash):上传改为内容寻址 + store 按 hash 去重 [tsk_fTul9G4eutVn]
- types: Attachment 增 hash?/size? 可选字段(向后兼容,老数据零迁移) - 上传端点 POST .../attachments 改为内容寻址:流式计算 sha256,写临时文件再 按 <sha256>.<ext> 原子 rename;同 hash 已存在则丢弃临时文件(真去重); 超限/异常清理临时文件不留垃圾。磁盘名仅由内容 hash 决定,杜绝撞名覆盖丢数据。 - store.addAttachments 去重键改为 hash ?? path(later-wins,老数据回退 path)。 - DELETE 端点补注释:附件按 task 隔离,无需跨任务 refcount。 - 安全保持并固化:GET inline 仅硬白名单图片,其余强制 attachment+octet-stream +nosniff+CSP sandbox;路径越界防护;hash 命名不含路径可控字符。 - 测试:新增同内容去重/不同内容同名各留一条/响应含 hash+size 等用例。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+6
-4
@@ -420,10 +420,12 @@ export class Store {
|
||||
const t = this.getTaskRow(taskId);
|
||||
if (!t) throw new StoreError(`任务不存在: ${taskId}`);
|
||||
const cur: Attachment[] = t.attachments ? JSON.parse(t.attachments) as Attachment[] : [];
|
||||
// 按 path 去重(later-wins):path 唯一对应一份磁盘文件,重传同名文件只保留最新一条元数据。
|
||||
const byPath = new Map<string, Attachment>();
|
||||
for (const a of [...cur, ...items]) byPath.set(a.path, a);
|
||||
const next = [...byPath.values()];
|
||||
// 按内容 hash 去重(later-wins):磁盘文件内容寻址,相同内容=同一份磁盘文件;
|
||||
// 重传相同内容(即便改名)只保留最新一条元数据(更新展示名/type)。老数据无 hash → 回退按 path 去重。
|
||||
const keyOf = (a: Attachment) => a.hash ?? a.path;
|
||||
const byKey = new Map<string, Attachment>();
|
||||
for (const a of [...cur, ...items]) byKey.set(keyOf(a), a);
|
||||
const next = [...byKey.values()];
|
||||
this.db.prepare(`UPDATE tasks SET attachments = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(next), now(), taskId);
|
||||
this.emit(t.project_id, taskId, 'task.updated', { field: 'attachments', count: next.length });
|
||||
return next;
|
||||
|
||||
Reference in New Issue
Block a user