feat: clean orphaned attachments
This commit is contained in:
1 parent
9f1697fd02
commit
34a6e73fa9
1 file changed
+168
-24
+168
-24
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user