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 @@