From a860fd71169559bc08052742572a1bfaa086e42a Mon Sep 17 00:00:00 2001 From: eoao Date: Wed, 31 Dec 2025 23:33:52 +0800 Subject: [PATCH] =?UTF-8?q?KV=E5=AD=98=E5=82=A8=E6=94=B9=E4=B8=BA=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=BC=80=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保存 保存 --- mail-vue/src/components/tiny-editor/index.vue | 4 +-- mail-vue/src/i18n/en.js | 5 ++- mail-vue/src/i18n/zh.js | 5 ++- mail-vue/src/utils/convert.js | 6 +++- mail-vue/src/views/sys-setting/index.vue | 24 ++++++++------ mail-worker/src/email/email.js | 8 +---- mail-worker/src/i18n/en.js | 1 - mail-worker/src/i18n/zh.js | 1 - mail-worker/src/init/init.js | 9 +++++- mail-worker/src/service/att-service.js | 17 ++++++++-- mail-worker/src/service/email-service.js | 19 +---------- mail-worker/src/service/login-service.js | 2 +- mail-worker/src/service/r2-service.js | 12 +++---- mail-worker/src/service/setting-service.js | 32 ++++++------------- mail-worker/src/service/user-service.js | 4 +++ mail-worker/wrangler-dev.toml | 6 ++++ 16 files changed, 75 insertions(+), 80 deletions(-) diff --git a/mail-vue/src/components/tiny-editor/index.vue b/mail-vue/src/components/tiny-editor/index.vue index 22f2c57..68e8f2a 100644 --- a/mail-vue/src/components/tiny-editor/index.vue +++ b/mail-vue/src/components/tiny-editor/index.vue @@ -92,8 +92,8 @@ function initEditor() { statusbar: false, height: "100%", auto_focus: true, - relative_urls: false, //阻止 img标签域名和网站域名相同 自动把链接转换相对路径 - remove_script_host: false, // 阻止删除 URL 中的域名 + //relative_urls: false, //阻止 img标签域名和网站域名相同 自动把链接转换相对路径 + //remove_script_host: false, // 阻止删除 URL 中的域名 forced_root_block: 'div', skin: `${uiStore.dark ? 'oxide-dark' : 'oxide'}`, content_css: `/tinymce/css/index.css,${uiStore.dark ? 'dark' : 'default'}`, diff --git a/mail-vue/src/i18n/en.js b/mail-vue/src/i18n/en.js index afc6b18..6af00b1 100644 --- a/mail-vue/src/i18n/en.js +++ b/mail-vue/src/i18n/en.js @@ -150,6 +150,7 @@ const en = { resendToken: 'Resend Token', oss: 'Object Storage', osDomain: 'Domain', + ossDomainDesc: 'Leave empty if using KV storage.', emailPush: 'Email Push', tgBot: 'Telegram Bot', disable: 'Disable', @@ -293,13 +294,11 @@ const en = { include: 'Include', delAllEmailConfirm: 'Do you really want to delete it?', s3Configuration: 'S3 Configuration', - s3Desc: 'If another S3-compatible storage is configured, R2 will take priority if it’s already connected', confirmDeletionOfContacts: 'Confirm clearing contacts?', recentContacts: 'Recent contacts', selectContacts: 'Select', forcePathStyleDesc: 'Some self-hosted object storages require path-style access to be enabled', - kvStorageDesc: 'Replace object storage with KV, and update the access domain to a Worker custom domain', - kvStorage: 'KV Storage', + storageType: 'Storage Location', customDomainDesc: 'Worker custom domain', show: 'Show', hide: 'Hide', diff --git a/mail-vue/src/i18n/zh.js b/mail-vue/src/i18n/zh.js index 4354bca..c9ca0ae 100644 --- a/mail-vue/src/i18n/zh.js +++ b/mail-vue/src/i18n/zh.js @@ -150,6 +150,7 @@ const zh = { resendToken: 'Resend Token', oss: '对象存储', osDomain: '访问域名', + ossDomainDesc: '如果是KV存储不要填', emailPush: '邮件推送', tgBot: 'Telegram 机器人', disable: '关闭', @@ -293,13 +294,11 @@ const zh = { include: '包含', delAllEmailConfirm: '确定要删除吗?', s3Configuration: 'S3 配置', - s3Desc: '设置其他S3协议存储,如果绑定了R2会优先用R2', confirmDeletionOfContacts: '确认清除这些联系人吗?', recentContacts: '最近联系人', selectContacts: '选中', forcePathStyleDesc: '路径样式访问,一些自建的对象存储需要打开', - kvStorageDesc: '使用KV替代对象存储,访问域名改成worker自定义域', - kvStorage: 'KV 存储', + storageType: '存储类型', customDomainDesc: 'Worker 自定义域', show: '显示', hide: '隐藏', diff --git a/mail-vue/src/utils/convert.js b/mail-vue/src/utils/convert.js index ae02ab4..1083edf 100644 --- a/mail-vue/src/utils/convert.js +++ b/mail-vue/src/utils/convert.js @@ -13,6 +13,10 @@ export function cvtR2Url(key) { let domain = settings.r2Domain + if (!domain) { + return key; + } + if (!domain.startsWith('http')) { return 'https://' + domain + '/' + key } @@ -26,7 +30,7 @@ export function cvtR2Url(key) { export function toOssDomain(domain) { if (!domain) { - return null + return '' } if (!domain.startsWith('http')) { diff --git a/mail-vue/src/views/sys-setting/index.vue b/mail-vue/src/views/sys-setting/index.vue index 80a736e..92c16e3 100644 --- a/mail-vue/src/views/sys-setting/index.vue +++ b/mail-vue/src/views/sys-setting/index.vue @@ -197,7 +197,12 @@
{{ $t('oss') }}
-
{{ $t('osDomain') }}
+
+ {{ $t('osDomain') }} + + + +
{{ setting.r2Domain || '' }} @@ -208,9 +213,6 @@
{{ $t('s3Configuration') }} - - -
@@ -220,14 +222,12 @@
- {{ $t('kvStorage') }} - - - + {{ $t('storageType') }}
- +
+ {{ setting.storageType }} +
@@ -1703,6 +1703,10 @@ function editSetting(settingForm, refreshStatus = true) { grid-template-columns: 1fr auto; align-items: center; + .storage-type { + margin-right: 3px; + } + span { overflow: hidden; white-space: nowrap; diff --git a/mail-worker/src/email/email.js b/mail-worker/src/email/email.js index 9306c1b..5abdb77 100644 --- a/mail-worker/src/email/email.js +++ b/mail-worker/src/email/email.js @@ -152,13 +152,7 @@ export async function email(message, env, ctx) { attachment.accountId = emailRow.accountId; }); - try { - if (attachments.length > 0 && await r2Service.hasOSS({ env })) { - await attService.addAtt({ env }, attachments); - } - } catch (e) { - console.error(e); - } + await attService.addAtt({ env }, attachments); emailRow = await emailService.completeReceive({ env }, account ? emailConst.status.RECEIVE : emailConst.status.NOONE, emailRow.emailId); diff --git a/mail-worker/src/i18n/en.js b/mail-worker/src/i18n/en.js index 7b1192b..c4a2f65 100644 --- a/mail-worker/src/i18n/en.js +++ b/mail-worker/src/i18n/en.js @@ -11,7 +11,6 @@ const en = { delMyAccount: 'Cannot delete your own account', noUserAccount: 'This email does not belong to the current user', usernameLengthLimit: 'Username length exceeds the limit', - noOsDomainSendPic: 'Cannot send body images: object storage domain not configured', noOsSendPic: 'Cannot send body images: object storage not configured', noOsDomainSendAtt: 'Cannot send attachments: object storage domain not configured', noOsSendAtt: 'Cannot send attachments: object storage not configured', diff --git a/mail-worker/src/i18n/zh.js b/mail-worker/src/i18n/zh.js index ba928cd..b793c5b 100644 --- a/mail-worker/src/i18n/zh.js +++ b/mail-worker/src/i18n/zh.js @@ -11,7 +11,6 @@ const zh = { delMyAccount: '不可以删除自己的邮箱', noUserAccount: '该邮箱不属于当前用户', usernameLengthLimit: '用户名长度超出限制', - noOsDomainSendPic: '对象存储域名未配置不能发送正文图片', noOsSendPic: '对象存储未配置不能发送正文图片', noOsDomainSendAtt: '对象存储域名未配置不能发送附件', noOsSendAtt: '对象存储未配置不能发送附件', diff --git a/mail-worker/src/init/init.js b/mail-worker/src/init/init.js index c1ce9be..894dc53 100644 --- a/mail-worker/src/init/init.js +++ b/mail-worker/src/init/init.js @@ -30,6 +30,14 @@ const init = { return c.text(t('initSuccess')); }, + async v2_7DB(c) { + try { + await c.env.db.prepare(`ALTER TABLE account ADD COLUMN all_receive INTEGER NOT NULL DEFAULT 0;`).run(); + } catch (e) { + console.error(e) + } + }, + async v2_6DB(c) { try { await c.env.db.prepare(`ALTER TABLE account ADD COLUMN all_receive INTEGER NOT NULL DEFAULT 0;`).run(); @@ -90,7 +98,6 @@ const init = { try { await c.env.db.batch([ c.env.db.prepare(`ALTER TABLE setting ADD COLUMN force_path_style INTEGER NOT NULL DEFAULT 1;`), - c.env.db.prepare(`ALTER TABLE setting ADD COLUMN kv_storage INTEGER NOT NULL DEFAULT 1;`), c.env.db.prepare(`ALTER TABLE setting ADD COLUMN custom_domain TEXT NOT NULL DEFAULT '';`), c.env.db.prepare(`ALTER TABLE setting ADD COLUMN tg_msg_to TEXT NOT NULL DEFAULT 'show';`), c.env.db.prepare(`ALTER TABLE setting ADD COLUMN tg_msg_from TEXT NOT NULL DEFAULT 'only-name';`) diff --git a/mail-worker/src/service/att-service.js b/mail-worker/src/service/att-service.js index b06fbb0..ed69ad2 100644 --- a/mail-worker/src/service/att-service.js +++ b/mail-worker/src/service/att-service.js @@ -82,17 +82,28 @@ const attService = { } //邮件正文站内图片转cid附件 - if (src && src.startsWith(domainUtils.toOssDomain(r2Domain))) { + if (src && (src.startsWith(domainUtils.toOssDomain(r2Domain)) || src.startsWith('attachments/'))) { const cid = uuidv4().replace(/-/g, '') img.setAttribute('src', 'cid:' + cid); const attData = {}; - attData.key = src.replace(domainUtils.toOssDomain(r2Domain) + '/',''); - attData.path = src; + + if (src.startsWith(domainUtils.toOssDomain(r2Domain))) { + attData.key = src.replace(domainUtils.toOssDomain(r2Domain) + '/',''); + attData.path = src; + } + + if (src.startsWith('attachments/')) { + const origin = new URL(c.req.url).origin; + attData.key = src; + attData.path = origin + '/' + src; + } + attData.contentId = cid; attData.type = attConst.type.EMBED; imageDataList.push(attData); + } const hasInlineWidth = img.hasAttribute('width'); diff --git a/mail-worker/src/service/email-service.js b/mail-worker/src/service/email-service.js index 11387df..b2238a5 100644 --- a/mail-worker/src/service/email-service.js +++ b/mail-worker/src/service/email-service.js @@ -197,23 +197,6 @@ const emailService = { } - - if (imageDataList.length > 0 && !r2Domain) { - throw new BizError(t('noOsDomainSendPic')); - } - - if (imageDataList.length > 0 && !await r2Service.hasOSS(c)) { - throw new BizError(t('noOsSendPic')); - } - - if (attachments.length > 0 && !r2Domain) { - throw new BizError(t('noOsDomainSendAtt')); - } - - if (attachments.length > 0 && !await r2Service.hasOSS(c)) { - throw new BizError(t('noOsSendAtt')); - } - if (attachments.length > 0 && manyType === 'divide') { throw new BizError(t('noSeparateSend')); } @@ -385,7 +368,7 @@ const emailService = { await attService.saveArticleAtt(c, imageDataList, userId, accountId, emailRow.emailId); } - if (attachments?.length > 0 && await r2Service.hasOSS(c)) { + if (attachments?.length > 0) { await attService.saveSendAtt(c, attachments, userId, accountId, emailRow.emailId); } diff --git a/mail-worker/src/service/login-service.js b/mail-worker/src/service/login-service.js index be7f8be..873b8fa 100644 --- a/mail-worker/src/service/login-service.js +++ b/mail-worker/src/service/login-service.js @@ -230,7 +230,7 @@ const loginService = { let authInfo = await c.env.kv.get(KvConst.AUTH_INFO + userRow.userId, { type: 'json' }); - if (authInfo) { + if (authInfo && (authInfo.user.email === userRow.email)) { if (authInfo.tokens.length > 10) { authInfo.tokens.shift(); diff --git a/mail-worker/src/service/r2-service.js b/mail-worker/src/service/r2-service.js index f9e9aaa..caf54d8 100644 --- a/mail-worker/src/service/r2-service.js +++ b/mail-worker/src/service/r2-service.js @@ -5,20 +5,20 @@ import { settingConst } from '../const/entity-const'; const r2Service = { - async hasOSS(c) { + async storageType(c) { const setting = await settingService.query(c); - const { kvStorage, bucket, endpoint, s3AccessKey, s3SecretKey } = setting; + const { bucket, endpoint, s3AccessKey, s3SecretKey } = setting; - if (kvStorage === settingConst.kvStorage.OPEN) { - return true; + if (!!(bucket && endpoint && s3AccessKey && s3SecretKey)) { + return 'S3'; } if (c.env.r2) { - return true; + return 'R2'; } - return !!(bucket && endpoint && s3AccessKey && s3SecretKey); + return 'KV'; }, async putObj(c, key, content, metadata) { diff --git a/mail-worker/src/service/setting-service.js b/mail-worker/src/service/setting-service.js index 76dbca6..cccc6d5 100644 --- a/mail-worker/src/service/setting-service.js +++ b/mail-worker/src/service/setting-service.js @@ -1,12 +1,12 @@ import KvConst from '../const/kv-const'; import setting from '../entity/setting'; import orm from '../entity/orm'; -import { verifyRecordType } from '../const/entity-const'; +import {verifyRecordType} from '../const/entity-const'; import fileUtils from '../utils/file-utils'; import r2Service from './r2-service'; import constant from '../const/constant'; import BizError from '../error/biz-error'; -import { t } from '../i18n/i18n' +import {t} from '../i18n/i18n' import verifyRecordService from './verify-record-service'; const settingService = { @@ -101,6 +101,8 @@ const settingService = { settingRow.regVerifyOpen = regVerifyOpen settingRow.addVerifyOpen = addVerifyOpen + settingRow.storageType = await r2Service.storageType(c); + return settingRow; }, @@ -131,37 +133,21 @@ const settingService = { return; } - const hasOss = await r2Service.hasOSS(c); - - if (hasOss) { - - if (background) { - await r2Service.delete(c,background) - await orm(c).update(setting).set({ background: '' }).run(); - await this.refresh(c) - } - + if (background) { + await r2Service.delete(c,background) + await orm(c).update(setting).set({ background: '' }).run(); + await this.refresh(c) } }, async setBackground(c, params) { - const settingRow = await this.query(c); - let { background } = params await this.deleteBackground(c); if (background && !background.startsWith('http')) { - if (!await r2Service.hasOSS(c)) { - throw new BizError(t('noOsUpBack')); - } - - if (!settingRow.r2Domain) { - throw new BizError(t('noOsDomainUpBack')); - } - const file = fileUtils.base64ToFile(background) const arrayBuffer = await file.arrayBuffer(); @@ -183,7 +169,7 @@ const settingService = { async websiteConfig(c) { - const settingRow = await this.get(c, true) + const settingRow = await this.get(c, true); return { register: settingRow.register, diff --git a/mail-worker/src/service/user-service.js b/mail-worker/src/service/user-service.js index bb14a98..333145b 100644 --- a/mail-worker/src/service/user-service.js +++ b/mail-worker/src/service/user-service.js @@ -25,6 +25,10 @@ const userService = { const userRow = await userService.selectById(c, userId); + if (!userRow) { + throw new BizError(t('authExpired'), 401); + } + const [account, roleRow, permKeys] = await Promise.all([ accountService.selectByEmailIncludeDel(c, userRow.email), roleService.selectById(c, userRow.type), diff --git a/mail-worker/wrangler-dev.toml b/mail-worker/wrangler-dev.toml index 2943643..fb49a8a 100644 --- a/mail-worker/wrangler-dev.toml +++ b/mail-worker/wrangler-dev.toml @@ -22,6 +22,12 @@ id = "2io01d4b299e481b9de060ece9e7785c" #binding = "r2" #bucket_name = "email" +[assets] +binding = "assets" +directory = "./dist" +not_found_handling = "single-page-application" +run_worker_first = true + [vars] orm_log = false