Files
cloud-mail/mail-worker/src/service/att-service.js
T
2025-09-04 01:22:00 +08:00

186 lines
5.0 KiB
JavaScript

import orm from '../entity/orm';
import { att } from '../entity/att';
import { and, eq, isNull, inArray } from 'drizzle-orm';
import r2Service from './r2-service';
import constant from '../const/constant';
import fileUtils from '../utils/file-utils';
import { attConst } from '../const/entity-const';
import { parseHTML } from 'linkedom';
import domainUtils from '../utils/domain-uitls';
const attService = {
async addAtt(c, attachments) {
for (let attachment of attachments) {
let metadate = {
contentType: attachment.mimeType,
}
if (!attachment.contentId) {
metadate.contentDisposition = `attachment; filename="${attachment.filename}"`
} else {
metadate.contentDisposition = `inline; filename="${attachment.filename}"`
metadate.cacheControl = `max-age=259200`
}
await r2Service.putObj(c, attachment.key, attachment.content, metadate);
}
await orm(c).insert(att).values(attachments).run();
},
list(c, params, userId) {
const { emailId } = params;
return orm(c).select().from(att).where(
and(
eq(att.emailId, emailId),
eq(att.userId, userId),
eq(att.type, attConst.type.ATT),
isNull(att.contentId)
)
).all();
},
async toImageUrlHtml(c, content, r2Domain) {
const { document } = parseHTML(content);
const images = Array.from(document.querySelectorAll('img'));
const attDataList = [];
for (const img of images) {
const src = img.getAttribute('src');
if (src && src.startsWith('data:image')) {
const file = fileUtils.base64ToFile(src);
const buff = await file.arrayBuffer();
const key = constant.ATTACHMENT_PREFIX + await fileUtils.getBuffHash(buff) + fileUtils.getExtFileName(file.name);
img.setAttribute('src', domainUtils.toOssDomain(r2Domain) + '/' + key);
const attData = {};
attData.key = key;
attData.filename = file.name;
attData.mimeType = file.type;
attData.size = file.size;
attData.buff = buff;
attDataList.push(attData);
}
const hasInlineWidth = img.hasAttribute('width');
const style = img.getAttribute('style') || '';
const hasStyleWidth = /(^|\s)width\s*:\s*[^;]+/.test(style);
if (!hasInlineWidth && !hasStyleWidth) {
const newStyle = (style ? style.trim().replace(/;$/, '') + '; ' : '') + 'max-width: 100%;';
img.setAttribute('style', newStyle);
}
}
return { attDataList, html: document.toString() };
},
async saveSendAtt(c, attList, userId, accountId, emailId) {
const attDataList = [];
for (let att of attList) {
att.buff = fileUtils.base64ToUint8Array(att.content);
att.key = constant.ATTACHMENT_PREFIX + await fileUtils.getBuffHash(att.buff) + fileUtils.getExtFileName(att.filename);
const attData = { userId, accountId, emailId };
attData.key = att.key;
attData.size = att.buff.length;
attData.filename = att.filename;
attData.mimeType = att.type;
attData.type = attConst.type.ATT;
attDataList.push(attData);
}
await orm(c).insert(att).values(attDataList).run();
for (let att of attList) {
await r2Service.putObj(c, att.key, att.buff, {
contentType: att.type,
contentDisposition: `attachment; filename="${att.filename}"`
});
}
},
async saveArticleAtt(c, attDataList, userId, accountId, emailId) {
for (let attData of attDataList) {
attData.userId = userId;
attData.emailId = emailId;
attData.accountId = accountId;
attData.type = attConst.type.EMBED;
await r2Service.putObj(c, attData.key, attData.buff, {
contentType: attData.mimeType,
cacheControl: `max-age=259200`,
contentDisposition: `inline; filename="${attData.filename}"`
});
}
await orm(c).insert(att).values(attDataList).run();
},
async removeByUserIds(c, userIds) {
await this.removeAttByField(c, 'user_id', userIds);
},
async removeByEmailIds(c, emailIds) {
await this.removeAttByField(c, 'email_id', emailIds);
},
selectByEmailIds(c, emailIds) {
return orm(c).select().from(att).where(
and(
inArray(att.emailId, emailIds),
eq(att.type, attConst.type.ATT)
))
.all();
},
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 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);
}
const delAttSql = fieldValues.map(value => c.env.db.prepare(`DELETE FROM attachments WHERE ${fieldName} = ?`).bind(value));
await c.env.db.batch(delAttSql);
},
async batchDelete(c, keys) {
if (!keys.length) return;
const BATCH_SIZE = 1000;
for (let i = 0; i < keys.length; i += BATCH_SIZE) {
const batch = keys.slice(i, i + BATCH_SIZE);
await r2Service.delete(c, batch);
}
}
};
export default attService;