feat: clean orphaned attachments

This commit is contained in:
zaunist committed 2025-03-10 19:28:14 +08:00
1 parent 9f1697fd02
commit 34a6e73fa9
1 file changed
+168 -24
+168 -24
View File
@@ -132,6 +132,47 @@ export async function deleteMailbox(db: D1Database, address: string): Promise<vo
await db.prepare(`DELETE FROM mailboxes WHERE address = ?`).bind(address).run();
}
/**
* 清理孤立的附件(没有关联到任何邮件的附件)
* @param db 数据库实例
* @returns 删除的附件数量
*/
async function cleanupOrphanedAttachments(db: D1Database): Promise<number> {
try {
// 获取所有孤立附件的ID列表
const orphanedAttachments = await db.prepare(`SELECT id, is_large FROM attachments a WHERE NOT EXISTS (SELECT 1 FROM emails e WHERE e.id = a.email_id)`).all();
if (!orphanedAttachments.results || orphanedAttachments.results.length === 0) {
return 0;
}
console.log(`找到 ${orphanedAttachments.results.length} 个孤立附件,准备清理...`);
// 清理每个孤立附件
for (const attachment of orphanedAttachments.results) {
const attachmentId = attachment.id as string;
const isLarge = !!attachment.is_large;
// 如果是大型附件,先删除所有分块
if (isLarge) {
await db.prepare(`DELETE FROM attachment_chunks WHERE attachment_id = ?`).bind(attachmentId).run();
console.log(`已清理孤立附件 ${attachmentId} 的所有分块`);
}
}
// 删除所有孤立附件记录
const result = await db.prepare(`DELETE FROM attachments WHERE NOT EXISTS (SELECT 1 FROM emails e WHERE e.id = attachments.email_id)`).run();
const deletedCount = result.meta?.changes || 0;
console.log(`已清理 ${deletedCount} 个孤立附件记录`);
return deletedCount;
} catch (error) {
console.error('清理孤立附件时出错:', error);
return 0;
}
}
/**
* 清理过期邮箱
* @param db 数据库实例
@@ -139,11 +180,134 @@ export async function deleteMailbox(db: D1Database, address: string): Promise<vo
*/
export async function cleanupExpiredMailboxes(db: D1Database): Promise<number> {
const now = getCurrentTimestamp();
// 获取过期邮箱的ID列表
const expiredMailboxes = await db.prepare(`SELECT id FROM mailboxes WHERE expires_at <= ?`).bind(now).all();
if (expiredMailboxes.results && expiredMailboxes.results.length > 0) {
const mailboxIds = expiredMailboxes.results.map(row => row.id as string);
// 记录日志
console.log(`找到 ${mailboxIds.length} 个过期邮箱,准备清理...`);
// 由于设置了外键级联删除,删除邮箱时会自动删除相关的邮件和附件
// 但为了确保附件被正确清理,我们先获取这些邮箱中的所有邮件ID
for (const mailboxId of mailboxIds) {
const emails = await db.prepare(`SELECT id FROM emails WHERE mailbox_id = ?`).bind(mailboxId).all();
if (emails.results && emails.results.length > 0) {
const emailIds = emails.results.map(row => row.id as string);
console.log(`邮箱 ${mailboxId} 中有 ${emailIds.length} 封邮件需要清理`);
// 清理每封邮件的附件
for (const emailId of emailIds) {
await cleanupAttachments(db, emailId);
}
}
}
}
// 删除过期邮箱(会级联删除邮件和附件)
const result = await db.prepare(`DELETE FROM mailboxes WHERE expires_at <= ?`).bind(now).run();
// 清理可能存在的孤立附件
await cleanupOrphanedAttachments(db);
return result.meta?.changes || 0;
}
/**
* 清理过期邮件
* @param db 数据库实例
* @returns 删除的邮件数量
*/
export async function cleanupExpiredMails(db: D1Database): Promise<number> {
const now = getCurrentTimestamp();
const oneDayAgo = now - 24 * 60 * 60; // 24小时前的时间戳(秒)
// 获取过期邮件的ID列表
const expiredEmails = await db.prepare(`SELECT id FROM emails WHERE received_at <= ?`).bind(oneDayAgo).all();
if (expiredEmails.results && expiredEmails.results.length > 0) {
const emailIds = expiredEmails.results.map(row => row.id as string);
console.log(`找到 ${emailIds.length} 封过期邮件,准备清理...`);
// 清理每封邮件的附件
for (const emailId of emailIds) {
await cleanupAttachments(db, emailId);
}
}
// 删除过期邮件(会级联删除附件)
const result = await db.prepare(`DELETE FROM emails WHERE received_at <= ?`).bind(oneDayAgo).run();
// 清理可能存在的孤立附件
await cleanupOrphanedAttachments(db);
return result.meta?.changes || 0;
}
/**
* 清理已被阅读的邮件
* @param db 数据库实例
* @returns 删除的邮件数量
*/
export async function cleanupReadMails(db: D1Database): Promise<number> {
// 获取已读邮件的ID列表
const readEmails = await db.prepare(`SELECT id FROM emails WHERE is_read = 1`).all();
if (readEmails.results && readEmails.results.length > 0) {
const emailIds = readEmails.results.map(row => row.id as string);
console.log(`找到 ${emailIds.length} 封已读邮件,准备清理...`);
// 清理每封邮件的附件
for (const emailId of emailIds) {
await cleanupAttachments(db, emailId);
}
}
// 删除已读邮件(会级联删除附件)
const result = await db.prepare(`DELETE FROM emails WHERE is_read = 1`).run();
// 清理可能存在的孤立附件
await cleanupOrphanedAttachments(db);
return result.meta?.changes || 0;
}
/**
* 清理指定邮件的所有附件
* @param db 数据库实例
* @param emailId 邮件ID
*/
async function cleanupAttachments(db: D1Database, emailId: string): Promise<void> {
try {
// 获取邮件的所有附件
const attachments = await db.prepare(`SELECT id, is_large FROM attachments WHERE email_id = ?`).bind(emailId).all();
if (attachments.results && attachments.results.length > 0) {
console.log(`邮件 ${emailId} 有 ${attachments.results.length} 个附件需要清理`);
for (const attachment of attachments.results) {
const attachmentId = attachment.id as string;
const isLarge = !!attachment.is_large;
// 如果是大型附件,先删除所有分块
if (isLarge) {
await db.prepare(`DELETE FROM attachment_chunks WHERE attachment_id = ?`).bind(attachmentId).run();
console.log(`已清理附件 ${attachmentId} 的所有分块`);
}
}
// 删除所有附件记录
await db.prepare(`DELETE FROM attachments WHERE email_id = ?`).bind(emailId).run();
console.log(`已清理邮件 ${emailId} 的所有附件`);
}
} catch (error) {
console.error(`清理邮件 ${emailId} 的附件时出错:`, error);
}
}
/**
* 保存邮件
* @param db 数据库实例
@@ -395,29 +559,9 @@ async function getAttachmentContent(db: D1Database, attachmentId: string, chunks
* @param id 邮件ID
*/
export async function deleteEmail(db: D1Database, id: string): Promise<void> {
// 先清理邮件的所有附件
await cleanupAttachments(db, id);
// 然后删除邮件
await db.prepare(`DELETE FROM emails WHERE id = ?`).bind(id).run();
}
/**
* 清理过期邮件
* @param db 数据库实例
* @returns 删除的邮件数量
*/
export async function cleanupExpiredMails(db: D1Database): Promise<number> {
const now = getCurrentTimestamp();
const oneDayAgo = now - 24 * 60 * 60; // 24小时前的时间戳(秒)
const result = await db.prepare(`DELETE FROM emails WHERE received_at <= ?`).bind(oneDayAgo).run();
return result.meta?.changes || 0;
}
/**
* 清理已被阅读的邮件
* @param db 数据库实例
* @returns 删除的邮件数量
*/
export async function cleanupReadMails(db: D1Database): Promise<number> {
const result = await db.prepare(`DELETE FROM emails WHERE is_read = 1`).run();
return result.meta?.changes || 0;
}