From 6f69740e050a9bee5a262e0a9b4028d8f11af529 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:08:21 +0800 Subject: [PATCH 01/24] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e6dd517c..e0e3d0c0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## 项目展示 -- [在线演示](https://skymail.ink)
+- [在线演示](https://mail.ygyang.uk)
- [部署文档](https://doc.skymail.ink)
- [界面部署](https://doc.skymail.ink/guide/via-ui.html) @@ -122,7 +122,7 @@ cloud-mail ## 赞助 - + From ed78638f3607cc495a84bec26bbfea78408585f0 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:11:54 +0800 Subject: [PATCH 02/24] Create ads.txt --- mail-vue/src/ads.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mail-vue/src/ads.txt diff --git a/mail-vue/src/ads.txt b/mail-vue/src/ads.txt new file mode 100644 index 00000000..eb21f1d3 --- /dev/null +++ b/mail-vue/src/ads.txt @@ -0,0 +1,3 @@ +# ads.txt 文件 - https://ygyang.uk/ +# Google AdSense 授权信息 +google.com, pub-2519025983341297, DIRECT, f08c47fec0942fa0 From 58f0635649b88e10e70c6e24fbda025cc935422f Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:26:08 +0800 Subject: [PATCH 03/24] Update wrangler.toml --- mail-worker/wrangler.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mail-worker/wrangler.toml b/mail-worker/wrangler.toml index b8ec3891..b82dca8d 100644 --- a/mail-worker/wrangler.toml +++ b/mail-worker/wrangler.toml @@ -8,16 +8,16 @@ enabled = true #[[d1_databases]] #binding = "db" #d1数据库绑定名默认不可修改 -#database_name = "" #d1数据库名字 -#database_id = "" #d1数据库id +#database_name = "cnmailcn" #d1数据库名字 +#database_id = "9e755ce9-d779-4afe-af61-20996946cf16" #d1数据库id #[[kv_namespaces]] #binding = "kv" #kv绑定名默认不可修改 -#id = "" #kv数据库id +#id = "a2693f55110b49f088671539b2aa723a" #kv数据库id #[[r2_buckets]] #binding = "r2" #r2对象存储绑定名默认不可修改 -#bucket_name = "" #r2对象存储桶的名字 +#bucket_name = "cnmailcn" #r2对象存储桶的名字 [assets] binding = "assets" #静态资源绑定名默认不可修改 @@ -31,9 +31,9 @@ crons = ["0 16 * * *"] #定时任务每天晚上12点执行 #[vars] #orm_log = false -#domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"] -#admin = "" #管理员的邮箱 示例: admin@example.com -#jwt_secret = "" #jwt令牌的密钥,随便填一串字符串 +#domain = ["ygyang.uk","cnmailcn.dpdns.org","ygyang.us.kg","cnygyang.dpdns.org","xiaoqie.dpdns.org","substracker.dpdns.org","ygyang.ip-ddns.com","admin.admin"] #邮件域名可可配置多个 示例: ["example1.com","example2.com"] +#admin = "admin@admin.admin" #管理员的邮箱 示例: admin@example.com +#jwt_secret = "Z7f!xPq8mV@L2bC$r9X&N1t" #jwt令牌的密钥,随便填一串字符串 [build] command = "pnpm --prefix ../mail-vue install && pnpm --prefix ../mail-vue run build" From dba18402a6fdaed14780d19bf86ae7049056bb83 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:38:05 +0800 Subject: [PATCH 04/24] Create ads.txt --- mail-worker/src/ads.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mail-worker/src/ads.txt diff --git a/mail-worker/src/ads.txt b/mail-worker/src/ads.txt new file mode 100644 index 00000000..eb21f1d3 --- /dev/null +++ b/mail-worker/src/ads.txt @@ -0,0 +1,3 @@ +# ads.txt 文件 - https://ygyang.uk/ +# Google AdSense 授权信息 +google.com, pub-2519025983341297, DIRECT, f08c47fec0942fa0 From 43fceb866defd00fe35c50945f2687c64101f384 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:46:50 +0800 Subject: [PATCH 05/24] Create ads.txt --- mail-vue/ads.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mail-vue/ads.txt diff --git a/mail-vue/ads.txt b/mail-vue/ads.txt new file mode 100644 index 00000000..eb21f1d3 --- /dev/null +++ b/mail-vue/ads.txt @@ -0,0 +1,3 @@ +# ads.txt 文件 - https://ygyang.uk/ +# Google AdSense 授权信息 +google.com, pub-2519025983341297, DIRECT, f08c47fec0942fa0 From a21d5380598632393872b386e109c4ae1f3545a5 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:53:12 +0800 Subject: [PATCH 06/24] Create ads.txt --- mail-vue/src/views/ads.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mail-vue/src/views/ads.txt diff --git a/mail-vue/src/views/ads.txt b/mail-vue/src/views/ads.txt new file mode 100644 index 00000000..eb21f1d3 --- /dev/null +++ b/mail-vue/src/views/ads.txt @@ -0,0 +1,3 @@ +# ads.txt 文件 - https://ygyang.uk/ +# Google AdSense 授权信息 +google.com, pub-2519025983341297, DIRECT, f08c47fec0942fa0 From c6c9e812ddf35c2beda03ccb071078646cef4680 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:55:44 +0800 Subject: [PATCH 07/24] Create ads.txt --- mail-worker/ads.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mail-worker/ads.txt diff --git a/mail-worker/ads.txt b/mail-worker/ads.txt new file mode 100644 index 00000000..eb21f1d3 --- /dev/null +++ b/mail-worker/ads.txt @@ -0,0 +1,3 @@ +# ads.txt 文件 - https://ygyang.uk/ +# Google AdSense 授权信息 +google.com, pub-2519025983341297, DIRECT, f08c47fec0942fa0 From c38dc02968ed770fbcef39d9d72f4d1dc0298b08 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sun, 7 Dec 2025 10:23:49 +0800 Subject: [PATCH 08/24] Create ads.txt --- mail-vue/public/ads.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mail-vue/public/ads.txt diff --git a/mail-vue/public/ads.txt b/mail-vue/public/ads.txt new file mode 100644 index 00000000..eb21f1d3 --- /dev/null +++ b/mail-vue/public/ads.txt @@ -0,0 +1,3 @@ +# ads.txt 文件 - https://ygyang.uk/ +# Google AdSense 授权信息 +google.com, pub-2519025983341297, DIRECT, f08c47fec0942fa0 From 66bf23f956b051743680407b1332e0123c230dd1 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:57:01 +0800 Subject: [PATCH 09/24] =?UTF-8?q?=E7=AB=99=E5=86=85=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E7=9B=B4=E6=8E=A5=E5=8F=91=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mail-worker/src/service/email-service.js | 231 ++++++++++++++--------- 1 file changed, 142 insertions(+), 89 deletions(-) diff --git a/mail-worker/src/service/email-service.js b/mail-worker/src/service/email-service.js index 11387dfa..15d4ea44 100644 --- a/mail-worker/src/service/email-service.js +++ b/mail-worker/src/service/email-service.js @@ -237,14 +237,6 @@ const emailService = { } - const domain = emailUtils.getDomain(accountRow.email); - const resendToken = resendTokens[domain]; - - if (!resendToken) { - throw new BizError(t('noResendToken')); - } - - if (!name) { name = emailUtils.getName(accountRow.email); } @@ -263,22 +255,89 @@ const emailService = { } - let resendResult = null; + //把图片标签cid标签切换会通用url + imageDataList = imageDataList.map(item => ({...item, contentId: `<${item.contentId}>`})) + html = this.imgReplace(html, imageDataList, r2Domain); - const resend = new Resend(resendToken); + const emailData = {}; + emailData.sendEmail = accountRow.email; + emailData.name = name; + emailData.subject = subject; + emailData.content = html; + emailData.text = text; + emailData.accountId = accountId; + emailData.type = emailConst.type.SEND; + emailData.userId = userId; + emailData.status = emailConst.status.SENT; - //如果是分开发送 - if (manyType === 'divide') { + const emailDataList = []; + const resendEmailList = []; + const localEmailList = []; - let sendFormList = []; + // 获取本服务的域名列表 + let domainList = c.env.domain; + if (typeof domainList === 'string') { + domainList = JSON.parse(domainList); + } + + // 分离站内邮件和外部邮件 + receiveEmail.forEach(recipientEmail => { + const recipientDomain = emailUtils.getDomain(recipientEmail); + if (domainList.includes(recipientDomain)) { + localEmailList.push(recipientEmail); + } else { + resendEmailList.push(recipientEmail); + } + }); + + // 处理外部邮件发送 + if (resendEmailList.length > 0) { + const domain = emailUtils.getDomain(accountRow.email); + const resendToken = resendTokens[domain]; + + if (!resendToken) { + throw new BizError(t('noResendToken')); + } + + let resendResult = null; + const resend = new Resend(resendToken); + + //如果是分开发送 + if (manyType === 'divide') { + + let sendFormList = []; + + resendEmailList.forEach(email => { + const sendForm = { + from: `${name} <${accountRow.email}>`, + to: [email], + subject: subject, + text: text, + html: html, + attachments: [...imageDataList, ...attachments] + }; + + if (sendType === 'reply') { + sendForm.headers = { + 'in-reply-to': emailRow.messageId, + 'references': emailRow.messageId + }; + } + + sendFormList.push(sendForm); + }); + + resendResult = await resend.batch.send(sendFormList); + + } else { - receiveEmail.forEach(email => { const sendForm = { from: `${name} <${accountRow.email}>`, - to: [email], + to: [...resendEmailList], subject: subject, text: text, - html: html + html: html, + attachments: [...imageDataList, ...attachments] }; if (sendType === 'reply') { @@ -288,98 +347,92 @@ const emailService = { }; } - sendFormList.push(sendForm); - }); - - resendResult = await resend.batch.send(sendFormList); - - } else { + resendResult = await resend.emails.send(sendForm); - const sendForm = { - from: `${name} <${accountRow.email}>`, - to: [...receiveEmail], - subject: subject, - text: text, - html: html, - attachments: [...imageDataList, ...attachments] - }; - - if (sendType === 'reply') { - sendForm.headers = { - 'in-reply-to': emailRow.messageId, - 'references': emailRow.messageId - }; } - resendResult = await resend.emails.send(sendForm); - - } - - const { data, error } = resendResult; - + const { data, error } = resendResult; - if (error) { - throw new BizError(error.message); - } - - imageDataList = imageDataList.map(item => ({...item, contentId: `<${item.contentId}>`})) - - //把图片标签cid标签切换会通用url - html = this.imgReplace(html, imageDataList, r2Domain); - - const emailData = {}; - emailData.sendEmail = accountRow.email; - emailData.name = name; - emailData.subject = subject; - emailData.content = html; - emailData.text = text; - emailData.accountId = accountId; - emailData.type = emailConst.type.SEND; - emailData.userId = userId; - emailData.status = emailConst.status.SENT; - - const emailDataList = []; - - if (manyType === 'divide') { + if (error) { + throw new BizError(error.message); + } - receiveEmail.forEach((item, index) => { + // 添加发送记录 + if (manyType === 'divide') { + resendEmailList.forEach((item, index) => { + const emailDataItem = { ...emailData }; + emailDataItem.resendEmailId = data.data[index].id; + emailDataItem.recipient = JSON.stringify([{ address: item, name: '' }]); + emailDataList.push(emailDataItem); + }); + } else { const emailDataItem = { ...emailData }; - emailDataItem.resendEmailId = data.data[index].id; - emailDataItem.recipient = JSON.stringify([{ address: item, name: '' }]); + emailDataItem.resendEmailId = data.id; + emailDataItem.recipient = JSON.stringify(resendEmailList.map(item => ({ address: item, name: '' }))); emailDataList.push(emailDataItem); - }); - - } else { - - emailData.resendEmailId = data.id; - - const recipient = []; - - receiveEmail.forEach(item => { - recipient.push({ address: item, name: '' }); - }); - - emailData.recipient = JSON.stringify(recipient); + } + } - emailDataList.push(emailData); + // 处理站内邮件 + for (const recipientEmail of localEmailList) { + // 添加发送记录 + const emailDataItem = { ...emailData }; + emailDataItem.recipient = JSON.stringify([{ address: recipientEmail, name: '' }]); + emailDataList.push(emailDataItem); + + // 查找收件人账户 + const recipientAccount = await accountService.selectByEmailIncludeDel(c, recipientEmail); + + if (recipientAccount && recipientAccount.isDel === isDel.NORMAL) { + // 创建收件记录 + const receiveData = { + name: name, + sendEmail: accountRow.email, + toEmail: recipientEmail, + subject: subject, + content: html, + text: text, + accountId: recipientAccount.accountId, + userId: recipientAccount.userId, + type: emailConst.type.RECEIVE, + status: emailConst.status.RECEIVE, + recipient: JSON.parse(emailDataItem.recipient), + unread: emailConst.unread.UNREAD + }; + + if (sendType === 'reply') { + receiveData.inReplyTo = emailRow.messageId; + receiveData.relation = emailRow.messageId; + } + + // 插入收件记录 + const receiveEmailRow = await orm(c).insert(email).values(receiveData).returning().get(); + + // 保存附件到收件记录 + if (imageDataList.length > 0) { + await attService.saveArticleAtt(c, imageDataList, recipientAccount.userId, recipientAccount.accountId, receiveEmailRow.emailId); + } + + if (attachments?.length > 0 && await r2Service.hasOSS(c)) { + await attService.saveSendAtt(c, attachments, recipientAccount.userId, recipientAccount.accountId, receiveEmailRow.emailId); + } + } } if (sendType === 'reply') { - emailDataList.forEach(emailData => { - emailData.inReplyTo = emailRow.messageId; - emailData.relation = emailRow.messageId; + emailDataList.forEach(emailDataItem => { + emailDataItem.inReplyTo = emailRow.messageId; + emailDataItem.relation = emailRow.messageId; }); } - if (roleRow.sendCount) { await userService.incrUserSendCount(c, receiveEmail.length, userId); } const emailRowList = await Promise.all( - - emailDataList.map(async (emailData) => { - const emailRow = await orm(c).insert(email).values(emailData).returning().get(); + emailDataList.map(async (emailDataItem) => { + const emailRow = await orm(c).insert(email).values(emailDataItem).returning().get(); if (imageDataList.length > 0) { await attService.saveArticleAtt(c, imageDataList, userId, accountId, emailRow.emailId); From 160ec5459adc7ee892cd67877a5eb8592c83fb2d Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 20 Dec 2025 20:14:21 +0800 Subject: [PATCH 10/24] Update email-service.js --- mail-worker/src/service/email-service.js | 35 +++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/mail-worker/src/service/email-service.js b/mail-worker/src/service/email-service.js index 15d4ea44..620fbb6d 100644 --- a/mail-worker/src/service/email-service.js +++ b/mail-worker/src/service/email-service.js @@ -269,6 +269,8 @@ const emailService = { emailData.type = emailConst.type.SEND; emailData.userId = userId; emailData.status = emailConst.status.SENT; + emailData.toEmail = ''; + emailData.toName = ''; const emailDataList = []; const resendEmailList = []; @@ -358,19 +360,23 @@ const emailService = { } // 添加发送记录 - if (manyType === 'divide') { - resendEmailList.forEach((item, index) => { + if (manyType === 'divide') { + resendEmailList.forEach((item, index) => { + const emailDataItem = { ...emailData }; + emailDataItem.resendEmailId = data.data[index].id; + emailDataItem.recipient = JSON.stringify([{ address: item, name: '' }]); + emailDataItem.toEmail = item; + emailDataItem.toName = ''; + emailDataList.push(emailDataItem); + }); + } else { const emailDataItem = { ...emailData }; - emailDataItem.resendEmailId = data.data[index].id; - emailDataItem.recipient = JSON.stringify([{ address: item, name: '' }]); + emailDataItem.resendEmailId = data.id; + emailDataItem.recipient = JSON.stringify(resendEmailList.map(item => ({ address: item, name: '' }))); + emailDataItem.toEmail = resendEmailList.join(','); + emailDataItem.toName = ''; emailDataList.push(emailDataItem); - }); - } else { - const emailDataItem = { ...emailData }; - emailDataItem.resendEmailId = data.id; - emailDataItem.recipient = JSON.stringify(resendEmailList.map(item => ({ address: item, name: '' }))); - emailDataList.push(emailDataItem); - } + } } // 处理站内邮件 @@ -378,6 +384,8 @@ const emailService = { // 添加发送记录 const emailDataItem = { ...emailData }; emailDataItem.recipient = JSON.stringify([{ address: recipientEmail, name: '' }]); + emailDataItem.toEmail = recipientEmail; + emailDataItem.toName = ''; emailDataList.push(emailDataItem); // 查找收件人账户 @@ -396,8 +404,9 @@ const emailService = { userId: recipientAccount.userId, type: emailConst.type.RECEIVE, status: emailConst.status.RECEIVE, - recipient: JSON.parse(emailDataItem.recipient), - unread: emailConst.unread.UNREAD + recipient: JSON.stringify([{ address: recipientEmail, name: '' }]), + unread: emailConst.unread.UNREAD, + toName: '' }; if (sendType === 'reply') { From 794964eef7e1fc8ed95e705813a8c474a206d690 Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 27 Dec 2025 10:26:26 +0800 Subject: [PATCH 11/24] 1 --- mail-worker/src/entity/setting.js | 4 +- mail-worker/src/index.js | 3 ++ mail-worker/src/service/att-service.js | 71 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/mail-worker/src/entity/setting.js b/mail-worker/src/entity/setting.js index 4ff0eae2..583f1ae0 100644 --- a/mail-worker/src/entity/setting.js +++ b/mail-worker/src/entity/setting.js @@ -47,6 +47,8 @@ export const setting = sqliteTable('setting', { tgMsgTo: text('tg_msg_to').default('show').notNull(), tgMsgText: text('tg_msg_text').default('hide').notNull(), minEmailPrefix: integer('min_email_prefix').default(0).notNull(), - emailPrefixFilter: text('email_prefix_filter').default('').notNull() + emailPrefixFilter: text('email_prefix_filter').default('').notNull(), + r2MaxSize: integer('r2_max_size').default(10737418240).notNull(), + r2FileExpireDays: integer('r2_file_expire_days').default(7).notNull() }); export default setting diff --git a/mail-worker/src/index.js b/mail-worker/src/index.js index 4c950cd3..bb0ed9d0 100644 --- a/mail-worker/src/index.js +++ b/mail-worker/src/index.js @@ -5,6 +5,7 @@ import verifyRecordService from './service/verify-record-service'; import emailService from './service/email-service'; import kvObjService from './service/kv-obj-service'; import oauthService from "./service/oauth-service"; +import attService from './service/att-service'; export default { async fetch(req, env, ctx) { @@ -28,5 +29,7 @@ export default { await userService.resetDaySendCount({ env }) await emailService.completeReceiveAll({ env }) await oauthService.clearNoBindOathUser({ env }) + await attService.cleanExpiredAttachments({ env }) + await attService.checkAndCleanOldAttachments({ env }) }, }; diff --git a/mail-worker/src/service/att-service.js b/mail-worker/src/service/att-service.js index b06fbb0e..370256e7 100644 --- a/mail-worker/src/service/att-service.js +++ b/mail-worker/src/service/att-service.js @@ -13,6 +13,11 @@ import settingService from "./setting-service"; const attService = { async addAtt(c, attachments) { + // 计算新附件总大小 + const newAttachmentsSize = attachments.reduce((total, attachment) => total + attachment.size, 0); + + // 检查并清理旧附件 + await this.checkAndCleanOldAttachments(c, newAttachmentsSize); for (let attachment of attachments) { @@ -129,6 +134,7 @@ const attService = { async saveSendAtt(c, attList, userId, accountId, emailId) { const attDataList = []; + let newAttachmentsSize = 0; for (let att of attList) { att.buff = fileUtils.base64ToUint8Array(att.content); @@ -136,12 +142,16 @@ const attService = { const attData = { userId, accountId, emailId }; attData.key = att.key; attData.size = att.buff.length; + newAttachmentsSize += att.buff.length; attData.filename = att.filename; attData.mimeType = att.type; attData.type = attConst.type.ATT; attDataList.push(attData); } + // 检查并清理旧附件 + await this.checkAndCleanOldAttachments(c, newAttachmentsSize); + await orm(c).insert(att).values(attDataList).run(); for (let att of attList) { @@ -154,6 +164,11 @@ const attService = { }, async saveArticleAtt(c, attDataList, userId, accountId, emailId) { + // 计算新附件总大小 + const newAttachmentsSize = attDataList.reduce((total, attData) => total + attData.size, 0); + + // 检查并清理旧附件 + await this.checkAndCleanOldAttachments(c, newAttachmentsSize); for (let attData of attDataList) { attData.userId = userId; @@ -243,6 +258,62 @@ const attService = { return [] } return orm(c).select().from(att).where(inArray(att.key, keys)).orderBy(desc(att.attId)).groupBy(att.key).all(); + }, + + async getTotalSize(c) { + const result = await c.env.db.prepare('SELECT SUM(size) as totalSize FROM attachments').run(); + return result.results[0]?.totalSize || 0; + }, + + async checkAndCleanOldAttachments(c, newAttachmentSize = 0) { + const setting = await settingService.query(c); + const { r2MaxSize } = setting; + let totalSize = await this.getTotalSize(c); + + if (totalSize + newAttachmentSize <= r2MaxSize) { + return; + } + + // 需要删除的大小 + const needDeleteSize = (totalSize + newAttachmentSize) - r2MaxSize; + let deletedSize = 0; + + // 获取最旧的附件,按创建时间排序 + const oldAttachments = await c.env.db.prepare( + 'SELECT att_id, key, size FROM attachments ORDER BY create_time ASC' + ).all(); + + for (const attachment of oldAttachments) { + if (deletedSize >= needDeleteSize) { + break; + } + + // 删除R2中的文件 + await r2Service.delete(c, attachment.key); + + // 从数据库中删除 + await c.env.db.prepare('DELETE FROM attachments WHERE att_id = ?').bind(attachment.att_id).run(); + + deletedSize += attachment.size; + } + }, + + async cleanExpiredAttachments(c) { + const setting = await settingService.query(c); + const { r2FileExpireDays } = setting; + + // 获取所有过期的附件 + const expiredAttachments = await c.env.db.prepare( + "SELECT att_id, key FROM attachments WHERE create_time < DATETIME('now', ? || ' days')" + ).bind(-r2FileExpireDays).all(); + + for (const attachment of expiredAttachments.results) { + // 删除R2中的文件 + await r2Service.delete(c, attachment.key); + + // 从数据库中删除 + await c.env.db.prepare('DELETE FROM attachments WHERE att_id = ?').bind(attachment.att_id).run(); + } } }; From ad96de81dd7468bf7f98e1d266426ed621ae391c Mon Sep 17 00:00:00 2001 From: Yige Yang <141805738+ygyang2023@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:01:29 +0800 Subject: [PATCH 12/24] 1 --- mail-vue/src/i18n/zh.js | 11 ++++ mail-vue/src/request/my.js | 4 ++ mail-vue/src/views/setting/index.vue | 50 +++++++++++++++++- mail-vue/src/views/sys-setting/index.vue | 66 ++++++++++++++++++++++++ mail-worker/src/api/my-api.js | 5 ++ mail-worker/src/entity/user.js | 1 + mail-worker/src/index.js | 1 + mail-worker/src/service/email-service.js | 20 +++++++ mail-worker/src/service/user-service.js | 8 +++ 9 files changed, 164 insertions(+), 2 deletions(-) diff --git a/mail-vue/src/i18n/zh.js b/mail-vue/src/i18n/zh.js index 4354bcaa..a2c2bdfd 100644 --- a/mail-vue/src/i18n/zh.js +++ b/mail-vue/src/i18n/zh.js @@ -21,6 +21,10 @@ const zh = { changePassword: '修改密码', newPassword: '新的密码', confirmPassword: '确认密码', + emailAutoDelete: '邮件自动删除', + emailAutoDeleteDesc: '设置邮件自动删除天数,星标邮件除外', + emailAutoDeleteDays: '自动删除天数', + dayUnit: '天', add: '添加', manage: '管理', rename: '改名', @@ -161,6 +165,13 @@ const zh = { turnstileSetting: 'Turnstile 人机验证', signUpVerification: '注册验证', addEmailVerification: '添加验证', + r2StorageSetting: 'R2 存储设置', + r2MaxSize: '存储大小限制', + r2MaxSizeDesc: '设置 R2 存储的最大容量,超过后将自动删除旧附件', + r2FileExpireDays: '附件过期时间', + r2FileExpireDaysDesc: '设置附件的保存天数,超过后将自动删除', + gbUnit: 'GB', + dayUnit: '天', about: '关于', version: '版本', community: '交流', diff --git a/mail-vue/src/request/my.js b/mail-vue/src/request/my.js index 93fc3a5a..f8362744 100644 --- a/mail-vue/src/request/my.js +++ b/mail-vue/src/request/my.js @@ -12,3 +12,7 @@ export function userDelete() { return http.delete('/my/delete') } +export function setEmailAutoDeleteDays(days) { + return http.put('/my/setEmailAutoDeleteDays', {emailAutoDeleteDays: days}) +} + diff --git a/mail-vue/src/views/setting/index.vue b/mail-vue/src/views/setting/index.vue index a895f1de..d773e6c3 100644 --- a/mail-vue/src/views/setting/index.vue +++ b/mail-vue/src/views/setting/index.vue @@ -29,6 +29,25 @@ {{$t('changePwdBtn')}} +
+
{{$t('emailAutoDelete')}}
+
+ + + + + +
+
{{$t('deleteUser')}}
@@ -49,8 +68,8 @@