新增支持s3协议对象存储

This commit is contained in:
eoao
2025-08-31 12:04:19 +08:00
parent 562528e968
commit 86a80900c1
27 changed files with 1981 additions and 277 deletions

View File

@@ -156,19 +156,6 @@ const accountService = {
await orm(c).insert(account).values(list).run();
},
async physicsDeleteAll(c) {
const accountIdsRow = await orm(c).select({accountId: account.accountId}).from(account).where(eq(account.isDel,isDel.DELETE)).limit(99);
if (accountIdsRow.length === 0) {
return;
}
const accountIds = accountIdsRow.map(item => item.accountId)
await emailService.physicsDeleteAccountIds(c, accountIds);
await orm(c).delete(account).where(inArray(account.accountId,accountIds)).run();
if (accountIdsRow.length === 99) {
await this.physicsDeleteAll(c)
}
},
async physicsDeleteByUserIds(c, userIds) {
await emailService.physicsDeleteUserIds(c, userIds);
await orm(c).delete(account).where(inArray(account.userId,userIds)).run();

View File

@@ -117,63 +117,45 @@ const attService = {
},
async removeByUserIds(c, userIds) {
await this.removeAttByField(c, att.userId, userIds);
await this.removeAttByField(c, 'user_id', userIds);
},
async removeByEmailIds(c, emailIds) {
await this.removeAttByField(c, att.emailId, emailIds);
},
async removeByAccountIds(c, accountIds) {
await this.removeAttByField(c, att.accountId, accountIds);
},
async removeAttByField(c, fieldName, fieldValues) {
const condition = inArray(fieldName, fieldValues);
const attList = await orm(c).select().from(att).where(condition).limit(99);
if (attList.length === 0) {
return;
}
const attIds = attList.map(attRow => attRow.attId);
const keys = attList.map(attRow => attRow.key);
await orm(c).delete(att).where(inArray(att.attId, attIds)).run();
const existAttRows = await orm(c).select().from(att).where(inArray(att.key, keys)).all();
const existKeys = existAttRows.map(attRow => attRow.key);
const delKeyList = keys.filter(key => !existKeys.includes(key));
if (delKeyList.length > 0) {
await c.env.r2.delete(delKeyList);
}
if (attList.length >= 99) {
await this.removeAttByField(c, fieldName, fieldValues);
}
await this.removeAttByField(c, 'email_id', emailIds);
},
selectByEmailIds(c, emailIds) {
return orm(c).select().from(att).where(
and(
inArray(att.emailId,emailIds),
inArray(att.emailId, emailIds),
eq(att.type, attConst.type.ATT)
))
.all();
},
async deleteByEmailIds(c, emailIds) {
async removeAttByField(c, fieldName, fieldValues) {
const queryAttSql = fieldValues.map(value =>
c.env.db.prepare(`SELECT a.key, a.att_id
FROM attachments a
JOIN (SELECT key
FROM attachments
GROUP BY key
HAVING COUNT (*) = 1) t
ON a.key = t.key
WHERE a.${fieldName} = ?;`).bind(value));
const queryAttSql = emailIds.map(emailId => c.env.db.prepare(`SELECT key,att_id FROM attachments WHERE email_id = ${emailId} GROUP BY key HAVING COUNT(*) = 1;`))
const attListResult = await c.env.db.batch(queryAttSql);
const delKeyList = attListResult.flatMap(r => r.results.map(row => row.key));
if (delKeyList.length > 0) {
await this.batchDelete(c, delKeyList)
await this.batchDelete(c, delKeyList);
}
const delAttSql = emailIds.map(emailId => c.env.db.prepare(`DELETE FROM attachments WHERE email_id = ${emailId}`))
const delAttSql = fieldValues.map(value => c.env.db.prepare(`DELETE
FROM attachments
WHERE ${fieldName} = ?`).bind(value));
await c.env.db.batch(delAttSql);
},

View File

@@ -17,6 +17,7 @@ import starService from './star-service';
import dayjs from 'dayjs';
import kvConst from '../const/kv-const';
import { t } from '../i18n/i18n'
import r2Service from './r2-service';
const emailService = {
@@ -128,7 +129,18 @@ const emailService = {
async send(c, params, userId) {
let { accountId, name, sendType, emailId, receiveEmail, manyType, text, content, subject, attachments } = params;
let {
accountId,
name,
sendType,
emailId,
receiveEmail,
manyType,
text,
content,
subject,
attachments
} = params;
const { resendTokens, r2Domain, send } = await settingService.query(c);
@@ -164,7 +176,7 @@ const emailService = {
throw new BizError(t('noOsDomainSendPic'));
}
if (attDataList.length > 0 && !c.env.r2) {
if (attDataList.length > 0 && !await r2Service.hasOSS(c)) {
throw new BizError(t('noOsSendPic'));
}
@@ -172,7 +184,7 @@ const emailService = {
throw new BizError(t('noOsDomainSendAtt'));
}
if (attachments.length > 0 && !c.env.r2) {
if (attachments.length > 0 && !await r2Service.hasOSS(c)) {
throw new BizError(t('noOsSendAtt'));
}
@@ -342,7 +354,7 @@ const emailService = {
await attService.saveArticleAtt(c, attDataList, userId, accountId, emailRow.emailId);
}
if (attachments?.length > 0 && c.env.r2) {
if (attachments?.length > 0 && await r2Service.hasOSS(c)) {
await attService.saveSendAtt(c, attachments, userId, accountId, emailRow.emailId);
}
@@ -443,21 +455,6 @@ const emailService = {
return list;
},
async physicsDeleteAll(c) {
const emailIdsRow = await orm(c).select({ emailId: email.emailId }).from(email).where(eq(email.isDel, isDel.DELETE)).limit(99);
if (emailIdsRow.length === 0) {
return;
}
const emailIds = emailIdsRow.map(row => row.emailId);
await attService.removeByEmailIds(c, emailIds);
await starService.removeByEmailIds(c, emailIds);
await orm(c).delete(email).where(inArray(email.emailId, emailIds)).run();
if (emailIdsRow.length === 99) {
await this.physicsDeleteAll(c);
}
},
async physicsDelete(c, params) {
let { emailIds } = params;
emailIds = emailIds.split(',').map(Number);
@@ -466,11 +463,6 @@ const emailService = {
await orm(c).delete(email).where(inArray(email.emailId, emailIds)).run();
},
async physicsDeleteAccountIds(c, accountIds) {
await attService.removeByAccountIds(c, accountIds);
await orm(c).delete(email).where(inArray(email.accountId, accountIds)).run();
},
async physicsDeleteUserIds(c, userIds) {
await attService.removeByUserIds(c, userIds);
await orm(c).delete(email).where(inArray(email.userId, userIds)).run();
@@ -657,7 +649,7 @@ const emailService = {
return;
}
await attService.deleteByEmailIds(c, emailIds);
await attService.removeByEmailIds(c, emailIds);
await orm(c).delete(email).where(conditions.length > 1 ? and(...conditions) : conditions[0]).run();
}

View File

@@ -1,8 +1,34 @@
import s3Service from './s3-service';
import settingService from './setting-service';
const r2Service = {
async hasOSS(c) {
if (c.env.r2) {
return true;
}
const setting = await settingService.query(c);
const { bucket, region, endpoint, s3AccessKey, s3SecretKey } = setting;
return !!(bucket && region && endpoint && s3AccessKey && s3SecretKey);
},
async putObj(c, key, content, metadata) {
await c.env.r2.put(key, content, {
httpMetadata: {...metadata}
});
if (c.env.r2) {
await c.env.r2.put(key, content, {
httpMetadata: { ...metadata }
});
} else {
await s3Service.putObj(c, key, content, metadata);
}
},
async getObj(c, key) {
@@ -10,7 +36,17 @@ const r2Service = {
},
async delete(c, key) {
await c.env.r2.delete(key);
if (c.env.r2) {
await c.env.r2.delete(key);
} else {
await s3Service.deleteObj(c, key);
}
}
};

View File

@@ -0,0 +1,54 @@
import { S3Client, PutObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
import settingService from './setting-service';
import domainUtils from '../utils/domain-uitls';
const s3Service = {
async putObj(c, key, content, metadata) {
const client = await this.client(c);
const { bucket } = await settingService.query(c);
await client.send(
new PutObjectCommand({ Bucket: bucket, Key: key, Body: content, ...metadata })
)
},
async deleteObj(c,keys) {
if (typeof keys === 'string') {
keys = [keys]
}
if (keys.length === 0) {
return
}
const client = await this.client(c)
const { bucket } = await settingService.query(c);
await client.send(
new DeleteObjectsCommand({
Bucket: bucket,
Delete: {
Objects: keys.map(key => ({Key: key}))
}
})
)
},
async client(c) {
const { region, endpoint, s3AccessKey, s3SecretKey } = await settingService.query(c);
return new S3Client({
region: region,
endpoint: domainUtils.toOssDomain(endpoint),
credentials: {
accessKeyId: s3AccessKey,
secretAccessKey: s3SecretKey,
},
});
}
}
export default s3Service

View File

@@ -17,12 +17,18 @@ const settingService = {
async refresh(c) {
const settingRow = await orm(c).select().from(setting).get();
settingRow.resendTokens = JSON.parse(settingRow.resendTokens);
c.set('setting', settingRow);
await c.env.kv.put(KvConst.SETTING, JSON.stringify(settingRow));
},
async query(c) {
if (c.get('setting')) {
return c.get('setting')
}
const setting = await c.env.kv.get(KvConst.SETTING, { type: 'json' });
let domainList = c.env.domain;
if (typeof domainList === 'string') {
@@ -39,6 +45,7 @@ const settingService = {
domainList = domainList.map(item => '@' + item);
setting.domainList = domainList;
c.set('setting', setting);
return setting;
},
@@ -49,11 +56,17 @@ const settingService = {
verifyRecordService.selectListByIP(c)
]);
settingRow.siteKey = settingRow.siteKey ? `${settingRow.siteKey.slice(0, 12)}******` : null;
settingRow.secretKey = settingRow.secretKey ? `${settingRow.secretKey.slice(0, 12)}******` : null;
Object.keys(settingRow.resendTokens).forEach(key => {
settingRow.resendTokens[key] = `${settingRow.resendTokens[key].slice(0, 12)}******`;
});
settingRow.s3AccessKey = settingRow.s3AccessKey ? `${settingRow.s3AccessKey.slice(0, 12)}******` : null;
settingRow.s3SecretKey = settingRow.s3SecretKey ? `${settingRow.s3SecretKey.slice(0, 12)}******` : null;
settingRow.hasR2 = !!c.env.r2
let regVerifyOpen = false
let addVerifyOpen = false
@@ -91,7 +104,7 @@ const settingService = {
if (background && !background.startsWith('http')) {
if (!c.env.r2) {
if (!await r2Service.hasOSS(c)) {
throw new BizError(t('noOsUpBack'));
}
@@ -124,12 +137,6 @@ const settingService = {
return background;
},
async physicsDeleteAll(c) {
await emailService.physicsDeleteAll(c);
await accountService.physicsDeleteAll(c);
await userService.physicsDeleteAll(c);
},
async websiteConfig(c) {
const settingRow = await this.get(c)

View File

@@ -87,20 +87,6 @@ const userService = {
await c.env.kv.delete(kvConst.AUTH_INFO + userId)
},
async physicsDeleteAll(c) {
const userIdsRow = await orm(c).select().from(user).where(eq(user.isDel, isDel.DELETE)).limit(99);
if (userIdsRow.length === 0) {
return;
}
const userIds = userIdsRow.map(item => item.userId);
await accountService.physicsDeleteByUserIds(c, userIds);
await orm(c).delete(user).where(inArray(user.userId, userIds)).run();
if (userIdsRow.length === 99) {
await this.physicsDeleteAll(c);
}
},
async physicsDelete(c, params) {
const { userId } = params
await accountService.physicsDeleteByUserIds(c, [userId])