发送邮件正文图片链接改为内置附件
This commit is contained in:
@@ -41,7 +41,8 @@ export const emailConst = {
|
||||
COMPLAINED: 4,
|
||||
DELAYED: 5,
|
||||
SAVING: 6,
|
||||
NOONE: 7
|
||||
NOONE: 7,
|
||||
FAILED: 8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import orm from '../entity/orm';
|
||||
import { att } from '../entity/att';
|
||||
import { and, eq, isNull, inArray } from 'drizzle-orm';
|
||||
import { and, eq, isNull, inArray, desc } 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 { v4 as uuidv4 } from 'uuid';
|
||||
import domainUtils from '../utils/domain-uitls';
|
||||
import BizError from '../error/biz-error';
|
||||
import settingService from "./setting-service";
|
||||
|
||||
const attService = {
|
||||
|
||||
@@ -46,22 +47,27 @@ const attService = {
|
||||
).all();
|
||||
},
|
||||
|
||||
async toImageUrlHtml(c, content, r2Domain) {
|
||||
async toImageUrlHtml(c, content) {
|
||||
|
||||
const { r2Domain } = await settingService.query(c);
|
||||
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const images = Array.from(document.querySelectorAll('img'));
|
||||
|
||||
const attDataList = [];
|
||||
let imageDataList = [];
|
||||
|
||||
for (const img of images) {
|
||||
|
||||
//邮件正文base64图片转cid附件
|
||||
const src = img.getAttribute('src');
|
||||
if (src && src.startsWith('data:image')) {
|
||||
const file = fileUtils.base64ToFile(src);
|
||||
const buff = await file.arrayBuffer();
|
||||
const cid = uuidv4().replace(/-/g, '');
|
||||
const key = constant.ATTACHMENT_PREFIX + await fileUtils.getBuffHash(buff) + fileUtils.getExtFileName(file.name);
|
||||
img.setAttribute('src', domainUtils.toOssDomain(r2Domain) + '/' + key);
|
||||
|
||||
img.setAttribute('src', 'cid:' + cid);
|
||||
|
||||
const attData = {};
|
||||
attData.key = key;
|
||||
@@ -69,8 +75,24 @@ const attService = {
|
||||
attData.mimeType = file.type;
|
||||
attData.size = file.size;
|
||||
attData.buff = buff;
|
||||
attData.content = fileUtils.base64ToDataStr(src);
|
||||
attData.contentId = cid;
|
||||
|
||||
attDataList.push(attData);
|
||||
imageDataList.push(attData);
|
||||
}
|
||||
|
||||
//邮件正文站内图片转cid附件
|
||||
if (src && src.startsWith(domainUtils.toOssDomain(r2Domain))) {
|
||||
|
||||
const cid = uuidv4().replace(/-/g, '')
|
||||
img.setAttribute('src', 'cid:' + cid);
|
||||
|
||||
const attData = {};
|
||||
attData.key = src.replace(domainUtils.toOssDomain(r2Domain) + '/','');
|
||||
attData.path = src;
|
||||
attData.contentId = cid;
|
||||
attData.type = attConst.type.EMBED;
|
||||
imageDataList.push(attData);
|
||||
}
|
||||
|
||||
const hasInlineWidth = img.hasAttribute('width');
|
||||
@@ -82,7 +104,26 @@ const attService = {
|
||||
img.setAttribute('style', newStyle);
|
||||
}
|
||||
}
|
||||
return { attDataList, html: document.toString() };
|
||||
|
||||
//查询已有内嵌url图片信息
|
||||
const keys = [...new Set(imageDataList.filter(item => item.path).map(item => item.key))];
|
||||
const dbImageList = await this.selectOneByKeys(c, keys);
|
||||
|
||||
//设置给当前附件
|
||||
imageDataList.forEach(image => {
|
||||
dbImageList.forEach(dbImage => {
|
||||
if (image.path && (image.key === dbImage.key)) {
|
||||
image.size = dbImage.size;
|
||||
image.filename = dbImage.filename;
|
||||
image.mimeType = dbImage.mimeType;
|
||||
image.contentType = dbImage.mimeType;
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
imageDataList = imageDataList.filter(image => !image.path || image.size);
|
||||
|
||||
return { imageDataList, html: document.toString() };
|
||||
},
|
||||
|
||||
async saveSendAtt(c, attList, userId, accountId, emailId) {
|
||||
@@ -194,8 +235,14 @@ const attService = {
|
||||
},
|
||||
|
||||
async removeByAccountId(c, accountId) {
|
||||
console.log(accountId)
|
||||
await this.removeAttByField(c, "account_id", [accountId])
|
||||
},
|
||||
|
||||
selectOneByKeys(c, keys) {
|
||||
if (!keys || keys.length === 0) {
|
||||
return []
|
||||
}
|
||||
return orm(c).select().from(att).where(inArray(att.key, keys)).orderBy(desc(att.attId)).groupBy(att.key).all();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ const emailService = {
|
||||
|
||||
const { resendTokens, r2Domain, send } = await settingService.query(c);
|
||||
|
||||
let { attDataList, html } = await attService.toImageUrlHtml(c, content, r2Domain);
|
||||
let { imageDataList, html } = await attService.toImageUrlHtml(c, content);
|
||||
|
||||
if (send === settingConst.send.CLOSE) {
|
||||
throw new BizError(t('disabledSend'), 403);
|
||||
@@ -173,11 +173,11 @@ const emailService = {
|
||||
}
|
||||
|
||||
|
||||
if (attDataList.length > 0 && !r2Domain) {
|
||||
if (imageDataList.length > 0 && !r2Domain) {
|
||||
throw new BizError(t('noOsDomainSendPic'));
|
||||
}
|
||||
|
||||
if (attDataList.length > 0 && !await r2Service.hasOSS(c)) {
|
||||
if (imageDataList.length > 0 && !await r2Service.hasOSS(c)) {
|
||||
throw new BizError(t('noOsSendPic'));
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ const emailService = {
|
||||
|
||||
const resend = new Resend(resendToken);
|
||||
|
||||
//如果是分开发送
|
||||
if (manyType === 'divide') {
|
||||
|
||||
let sendFormList = [];
|
||||
@@ -275,7 +276,7 @@ const emailService = {
|
||||
subject: subject,
|
||||
text: text,
|
||||
html: html,
|
||||
attachments: attachments
|
||||
attachments: [...imageDataList, ...attachments]
|
||||
};
|
||||
|
||||
if (sendType === 'reply') {
|
||||
@@ -296,7 +297,10 @@ const emailService = {
|
||||
throw new BizError(error.message);
|
||||
}
|
||||
|
||||
html = this.imgReplace(html, null, r2Domain);
|
||||
imageDataList = imageDataList.map(item => ({...item, contentId: `<${item.contentId}>`}))
|
||||
|
||||
//把图片标签cid标签切换会通用url
|
||||
html = this.imgReplace(html, imageDataList, r2Domain);
|
||||
|
||||
const emailData = {};
|
||||
emailData.sendEmail = accountRow.email;
|
||||
@@ -348,11 +352,12 @@ const emailService = {
|
||||
}
|
||||
|
||||
const emailRowList = await Promise.all(
|
||||
|
||||
emailDataList.map(async (emailData) => {
|
||||
const emailRow = await orm(c).insert(email).values(emailData).returning().get();
|
||||
|
||||
if (attDataList.length > 0) {
|
||||
await attService.saveArticleAtt(c, attDataList, userId, accountId, emailRow.emailId);
|
||||
if (imageDataList.length > 0) {
|
||||
await attService.saveArticleAtt(c, imageDataList, userId, accountId, emailRow.emailId);
|
||||
}
|
||||
|
||||
if (attachments?.length > 0 && await r2Service.hasOSS(c)) {
|
||||
|
||||
@@ -34,6 +34,12 @@ const resendService = {
|
||||
params.message = null
|
||||
}
|
||||
|
||||
if (body.type === 'email.failed') {
|
||||
params.status = emailConst.status.FAILED
|
||||
params.resendEmailId = body.data.email_id
|
||||
params.message = body.data.failed.reason
|
||||
}
|
||||
|
||||
const emailRow = await emailService.updateEmailStatus(c, params)
|
||||
|
||||
if (!emailRow) {
|
||||
|
||||
@@ -14,6 +14,10 @@ const fileUtils = {
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
||||
base64ToDataStr(base64) {
|
||||
return base64.split(',')[1] || base64;
|
||||
},
|
||||
|
||||
base64ToUint8Array(base64) {
|
||||
const binaryStr = atob(base64);
|
||||
const len = binaryStr.length;
|
||||
|
||||
Reference in New Issue
Block a user